ppx_combin/lib/combin.ml

157 lines
4 KiB
OCaml

module type INPUT = sig
type t
type token
val peek : t -> token option
val advance : t -> t
val position : t -> int
val show_token : token -> string
end
type span = { start_pos : int; end_pos : int }
type error = {
span : span;
expected : string list;
}
let merge_errors e1 e2 =
if e1.span.end_pos > e2.span.end_pos then e1
else if e2.span.end_pos > e1.span.end_pos then e2
else { span = { start_pos = min e1.span.start_pos e2.span.start_pos; end_pos = e1.span.end_pos }; expected = e1.expected @ e2.expected }
let pure x _input = Ok (x, _input)
let fail msg _input = Error { span = { start_pos = 0; end_pos = 0 }; expected = [msg] }
module Char_input : sig
include INPUT with type token = char
val of_string : string -> t
end = struct
type token = char
type t = { data : string; pos : int }
let of_string s = { data = s; pos = 0 }
let peek { data; pos } =
if pos >= String.length data then None
else Some (String.get data pos)
let advance t = { t with pos = t.pos + 1 }
let position t = t.pos
let show_token c = Printf.sprintf "'%c'" c
end
let parse_string parser s =
let input = Char_input.of_string s in
parser (module Char_input : INPUT with type t = Char_input.t and type token = char) input
let format_error ?(input = "") e =
let context =
if String.length input > 0 && e.span.end_pos < String.length input then
let start = max 0 (e.span.start_pos - 5) in
let len = min 30 (String.length input - start) in
Printf.sprintf " near '%s'" (String.sub input start len)
else ""
in
let pos_str =
if e.span.start_pos = e.span.end_pos then
Printf.sprintf "position %d" e.span.start_pos
else
Printf.sprintf "positions %d-%d" e.span.start_pos e.span.end_pos
in
Printf.sprintf "parse error at %s%s: expected %s"
pos_str context (String.concat " or " e.expected)
module Memo : sig
type 'a table
val create : unit -> 'a table
val find : 'a table -> string -> int -> 'a option
val add : 'a table -> string -> int -> 'a -> unit
end = struct
type 'a table = (string * int, 'a) Hashtbl.t
let create () = Hashtbl.create 64
let find tbl name pos = Hashtbl.find_opt tbl (name, pos)
let add tbl name pos v = Hashtbl.replace tbl (name, pos) v
end
type 'a memo_table = ('a * Char_input.t, error) result Memo.table
module Stream_input : sig
include INPUT with type token = char
val of_channel : in_channel -> t
val of_seq : char Seq.t -> t
end = struct
type token = char
type t = {
mutable buffer : char list;
mutable source : char Seq.t;
mutable pos : int;
}
let of_seq seq = { buffer = []; source = seq; pos = 0 }
let of_channel ch =
let rec seq () =
match input_char ch with
| c -> Seq.Cons (c, seq)
| exception End_of_file -> Seq.Nil
in
of_seq seq
let ensure_buffered t n =
let rec fill needed =
if needed <= 0 then ()
else match t.source () with
| Seq.Nil -> ()
| Seq.Cons (c, rest) ->
t.buffer <- t.buffer @ [c];
t.source <- rest;
fill (needed - 1)
in
let buffered = List.length t.buffer in
if buffered < n then fill (n - buffered)
let peek t =
ensure_buffered t 1;
match t.buffer with
| [] -> None
| c :: _ -> Some c
let advance t =
ensure_buffered t 1;
match t.buffer with
| [] -> t
| _ :: rest -> { t with buffer = rest; pos = t.pos + 1 }
let position t = t.pos
let show_token c = Printf.sprintf "'%c'" c
end
module type TOKEN = sig
type t
val equal : t -> t -> bool
val show : t -> string
end
module Make_list_input (T : TOKEN) : sig
include INPUT with type token = T.t
val of_list : T.t list -> t
end = struct
type token = T.t
type t = { data : T.t list; pos : int }
let of_list lst = { data = lst; pos = 0 }
let peek { data; _ } =
match data with
| [] -> None
| x :: _ -> Some x
let advance t =
match t.data with
| [] -> t
| _ :: rest -> { data = rest; pos = t.pos + 1 }
let position t = t.pos
let show_token = T.show
end