(* This file is free software, part of containers. See file "license" for more details. *) (** {1 Simple S-expression parsing/printing} *) type 'a or_error = ('a, string) result type 'a gen = unit -> 'a option module type SEXP = CCSexp_intf.BASIC_SEXP module type S = CCSexp_intf.S0 let equal_string (a : string) b = Stdlib.( = ) a b let compare_string (a : string) b = Stdlib.compare a b let _with_in filename f = let ic = open_in filename in try let x = f ic in close_in ic; x with e -> close_in ic; Error (Printexc.to_string e) let _with_out filename f = let oc = open_out filename in try let x = f oc in close_out oc; x with e -> close_out oc; raise e module Make (Sexp : SEXP) = struct type t = Sexp.t type sexp = t let atom = Sexp.atom let list = Sexp.list let of_int x = Sexp.atom (string_of_int x) let of_float x = Sexp.atom (string_of_float x) let of_bool x = Sexp.atom (string_of_bool x) let of_unit = Sexp.list [] let of_list l = Sexp.list l let of_rev_list l = Sexp.list (List.rev l) let of_pair (x, y) = Sexp.list [ x; y ] let of_triple (x, y, z) = Sexp.list [ x; y; z ] let of_quad (x, y, z, u) = Sexp.list [ x; y; z; u ] let of_variant name args = Sexp.list (Sexp.atom name :: args) let of_field name t = Sexp.list [ Sexp.atom name; t ] let of_record l = Sexp.list (List.map (fun (n, x) -> of_field n x) l) (** {3 Printing} *) let rec to_buf b t = Sexp.match_ t ~atom:(fun s -> Printf.bprintf b "%d:%s" (String.length s) s) ~list:(function | [] -> Buffer.add_string b "()" | [ x ] -> Printf.bprintf b "(%a)" to_buf x | l -> Buffer.add_char b '('; List.iter (to_buf b) l; Buffer.add_char b ')') let to_string t = let b = Buffer.create 128 in to_buf b t; Buffer.contents b let rec pp_noindent fmt t = Sexp.match_ t ~atom:(fun s -> Format.fprintf fmt "%d:%s" (String.length s) s) ~list:(function | [] -> Format.pp_print_string fmt "()" | [ x ] -> Format.fprintf fmt "(%a)" pp_noindent x | l -> Format.fprintf fmt "("; List.iter (pp_noindent fmt) l; Format.fprintf fmt ")") let pp = pp_noindent let rec to_chan oc t = Sexp.match_ t ~atom:(fun s -> Printf.fprintf oc "%d:%s" (String.length s) s) ~list:(function | [] -> output_string oc "()" | [ x ] -> Printf.fprintf oc "(%a)" to_chan x | l -> output_char oc '('; List.iter (to_chan oc) l; output_char oc ')') let to_file_iter filename iter = _with_out filename (fun oc -> iter (fun t -> to_chan oc t)) let to_file filename t = to_file_iter filename (fun k -> k t) (** {3 Parsing} *) module type INPUT = sig exception EOF val read_char : unit -> char val read_string : int -> string end module Decoder (I : INPUT) = struct let[@inline] is_num_ c = Char.code c >= Char.code '0' && Char.code c <= Char.code '9' let[@inline] as_num_ c = Char.code c - Char.code '0' let next_ () : sexp or_error * bool = let rec read_string_len n = match I.read_char () with | c when is_num_ c -> read_string_len ((n * 10) + as_num_ c) | ':' -> let s = I.read_string n in atom s | _ -> failwith "expected string length" and eat_colon () = match I.read_char () with | ':' -> () | _ -> failwith "expected ':'" and read_in_paren acc = match I.read_char () with | ')' -> list (List.rev acc) | c when is_num_ c -> let sexp = read_string_len (as_num_ c) in read_in_paren (sexp :: acc) | '(' -> let sexp = read_in_paren [] in read_in_paren (sexp :: acc) | _ -> failwith "expected list of sexprs" in (* read a S-expr *) try match I.read_char () with | exception I.EOF -> Error "unexpected EOF", true | '(' -> Ok (read_in_paren []), false | '0' -> eat_colon (); Ok (atom ""), false | c when is_num_ c -> Ok (read_string_len (as_num_ c)), false | _ -> Error "unexpected char, expected toplevel sexpr", false with Failure e -> Error e, false let to_list () : _ or_error = let rec iter acc = match next_ () with | Error _, true -> Ok (List.rev acc) | Ok x, _ -> iter (x :: acc) | (Error _ as res), _ -> res in try iter [] with e -> Error (Printexc.to_string e) let[@inline] next_or_error () : _ or_error = fst (next_ ()) end [@@inline] module Decoder_str (X : sig val s : string end) = Decoder (struct exception EOF let i = ref 0 let n = String.length X.s let read_char () = if !i >= n then raise_notrace EOF; let c = String.unsafe_get X.s !i in incr i; c let read_string len = if !i + len > n then raise_notrace EOF; let res = String.sub X.s !i len in i := !i + len; res end) [@@inline] let parse_string s : t or_error = let module D = Decoder_str (struct let s = s end) in D.next_or_error () let parse_string_list s : t list or_error = let module D = Decoder_str (struct let s = s end) in D.to_list () module Decoder_ic (X : sig val ic : in_channel end) = Decoder (struct exception EOF = End_of_file let[@inline] read_char () = input_char X.ic let read_string n = match n with | 0 -> "" | 1 -> String.make 1 (read_char ()) | _ -> let buf = Bytes.make n '\000' in let i = ref 0 in while !i < n do let len = input X.ic buf !i (n - !i) in i := !i + len done; Bytes.unsafe_to_string buf end) [@@inline] let parse_chan_ ?file ic : sexp or_error = let module D = Decoder_ic (struct let ic = ic end) in match D.next_or_error (), file with | Error s, Some file -> Error (Printf.sprintf "%s in '%s'" s file) | r, _ -> r let parse_chan_list_ ?file ic = let module D = Decoder_ic (struct let ic = ic end) in match D.to_list (), file with | Error s, Some file -> Error (Printf.sprintf "%s in '%s'" s file) | r, _ -> r let parse_chan ic = parse_chan_ ic let parse_chan_list ic = parse_chan_list_ ic let parse_chan_gen ic = let module D = Decoder_ic (struct let ic = ic end) in fun () -> match D.next_ () with | _, true -> None | Error e, _ -> Some (Error e) | Ok x, _ -> Some (Ok x) let parse_file filename = _with_in filename (parse_chan_ ~file:filename) let parse_file_list filename = _with_in filename (parse_chan_list_ ~file:filename) end type t = [ `Atom of string | `List of t list ] let rec equal a b = match a, b with | `Atom s1, `Atom s2 -> equal_string s1 s2 | `List l1, `List l2 -> (try List.for_all2 equal l1 l2 with Invalid_argument _ -> false) | `Atom _, _ | `List _, _ -> false let rec compare_list a b = match a, b with | [], [] -> 0 | [], _ :: _ -> -1 | _ :: _, [] -> 1 | x :: xs, y :: ys -> (match compare x y with | 0 -> compare_list xs ys | c -> c) and compare a b = match a, b with | `Atom s1, `Atom s2 -> compare_string s1 s2 | `List l1, `List l2 -> compare_list l1 l2 | `Atom _, _ -> -1 | `List _, _ -> 1 module Basic_ = struct type nonrec t = t let atom x = `Atom x let list x = `List x let match_ x ~atom ~list = match x with | `Atom x -> atom x | `List l -> list l end include (Make (Basic_) : S with type t := t)