Repetition and separation
Section titled “Repetition and separation”These combinators handle patterns that repeat: lists, separated values, delimited blocks, and operator chains.
Basic repetition
Section titled “Basic repetition”Parseff.many applies a parser zero or more times. Returns a list of results. Always succeeds (returns [] if the parser fails immediately).
val many : (unit -> 'a) -> unit -> 'a listlet digits () = Parseff.many Parseff.digit ()(* "123" -> [1; 2; 3] *)(* "" -> [] *)(* "abc" -> [] *)many ~at_least:1
Section titled “many ~at_least:1”Parseff.many with ~at_least:1 requires at least one match. Fails if the parser doesn’t succeed at least once.
val many : ?at_least:int -> (unit -> 'a) -> unit -> 'a listlet digits1 () = Parseff.many ~at_least:1 Parseff.digit ()(* "123" -> [1; 2; 3] *)(* "" -> Error *)(* "abc" -> Error *)Parseff.count applies a parser exactly n times. Fails if the parser doesn’t match n times.
val count : int -> (unit -> 'a) -> unit -> 'a listlet three_digits () = Parseff.count 3 Parseff.digit ()(* "123" -> [1; 2; 3] *)(* "12" -> Error *)Useful for fixed-width formats:
let hex_digit () = Parseff.satisfy (fun c -> (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) ~label:"hex digit"
(* Parse #RRGGBB color *)let hex_color () = let _ = Parseff.char '#' in let r = Parseff.count 2 hex_digit () in let g = Parseff.count 2 hex_digit () in let b = Parseff.count 2 hex_digit () in (r, g, b)(* "#ff00aa" -> (['f';'f'], ['0';'0'], ['a';'a']) *)Separated lists
Section titled “Separated lists”sep_by
Section titled “sep_by”Parseff.sep_by parses zero or more elements separated by a separator. The separator’s return value is discarded. Always succeeds.
val sep_by : (unit -> 'a) -> (unit -> 'b) -> unit -> 'a listlet csv_line () = Parseff.sep_by (fun () -> Parseff.take_while (fun c -> c <> ',' && c <> '\n')) (fun () -> Parseff.char ',') ()(* "a,b,c" -> ["a"; "b"; "c"] *)(* "" -> [""] *)sep_by ~at_least:1
Section titled “sep_by ~at_least:1”Parseff.sep_by with ~at_least:1 requires at least one element.
val sep_by : ?at_least:int -> (unit -> 'a) -> (unit -> 'b) -> unit -> 'a listlet csv_line1 () = Parseff.sep_by ~at_least:1 (fun () -> Parseff.take_while ~at_least:1 (fun c -> c <> ',' && c <> '\n') ~label:"value") (fun () -> Parseff.char ',') ()(* "a,b,c" -> ["a"; "b"; "c"] *)(* "a" -> ["a"] *)(* "" -> Error *)Delimiters and terminators
Section titled “Delimiters and terminators”between
Section titled “between”Parseff.between parses an opening delimiter, then the body, then a closing delimiter. Returns the body’s value.
val between : (unit -> 'a) -> (unit -> 'b) -> (unit -> 'c) -> unit -> 'clet parens p = Parseff.between (fun () -> Parseff.char '(') (fun () -> Parseff.char ')') p
let parenthesized_digit () = parens (fun () -> Parseff.skip_whitespace (); let n = Parseff.digit () in Parseff.skip_whitespace (); n) ()(* "(42)" -> 42 *)(* "( 42 )" -> Error (only parses one digit) *)Works well for bracketed structures:
let braces p = Parseff.between (fun () -> Parseff.char '{') (fun () -> Parseff.char '}') p
let brackets p = Parseff.between (fun () -> Parseff.char '[') (fun () -> Parseff.char ']') pend_by
Section titled “end_by”Parseff.end_by parses zero or more elements, each followed by a separator. Unlike Parseff.sep_by, the separator comes after each element (including the last).
val end_by : (unit -> 'a) -> (unit -> 'b) -> unit -> 'a list(* Parse semicolon-terminated statements *)let statements () = Parseff.end_by (fun () -> Parseff.take_while ~at_least:1 (fun c -> c <> ';' && c <> '\n') ~label:"statement") (fun () -> Parseff.char ';') ()(* "a;b;c;" -> ["a"; "b"; "c"] *)(* "" -> [] *)end_by ~at_least:1
Section titled “end_by ~at_least:1”Parseff.end_by with ~at_least:1 requires at least one element.
val end_by : ?at_least:int -> (unit -> 'a) -> (unit -> 'b) -> unit -> 'a listOperator chains
Section titled “Operator chains”These combinators parse sequences of values joined by operators, handling associativity. They’re the standard tool for expression parsing with operator precedence.
fold_left
Section titled “fold_left”Parseff.fold_left parses one or more values separated by an operator, combining them left-associatively. The operator parser returns a function that combines two values. If no elements match, use ~otherwise to provide a fallback value.
val fold_left : (unit -> 'a) -> (unit -> 'a -> 'a -> 'a) -> ?otherwise:'a -> unit -> 'a(* Parse "1-2-3" as ((1-2)-3) = -4 *)let subtraction () = Parseff.fold_left (fun () -> Parseff.digit ()) (fun () -> let _ = Parseff.char '-' in fun a b -> a - b) ()(* "1-2-3" -> -4 (left-associative: (1-2)-3) *)With ~otherwise, returns the fallback if zero elements match:
let maybe_subtract () = Parseff.fold_left (fun () -> Parseff.digit ()) (fun () -> let _ = Parseff.char '-' in fun a b -> a - b) ~otherwise:0 ()(* "1-2" -> -1 *)(* "" -> 0 *)fold_right
Section titled “fold_right”Parseff.fold_right is like Parseff.fold_left but combines right-associatively. Use ~otherwise to provide a fallback value when no elements match.
val fold_right : (unit -> 'a) -> (unit -> 'a -> 'a -> 'a) -> ?otherwise:'a -> unit -> 'a(* Parse "2^3^2" as 2^(3^2) = 512 *)let power () = Parseff.fold_right (fun () -> Parseff.digit ()) (fun () -> let _ = Parseff.char '^' in fun a b -> int_of_float (float_of_int a ** float_of_int b)) ()(* "2^3^2" -> 512 (right-associative: 2^(3^2)) *)Complete example: JSON array
Section titled “Complete example: JSON array”let integer () = let sign = Parseff.optional (fun () -> Parseff.char '-') () in let digits = Parseff.take_while ~at_least:1 (fun c -> c >= '0' && c <= '9') ~label:"digit" in let n = int_of_string digits in match sign with Some _ -> -n | None -> n
let json_array () = let _ = Parseff.char '[' in Parseff.skip_whitespace (); let values = Parseff.sep_by (fun () -> Parseff.skip_whitespace (); let n = integer () in Parseff.skip_whitespace (); n) (fun () -> Parseff.char ',') () in Parseff.skip_whitespace (); let _ = Parseff.char ']' in Parseff.end_of_input (); values
let () = match Parseff.parse "[1, -2, 3]" json_array with | Ok nums -> Printf.printf "Sum: %d\n" (List.fold_left ( + ) 0 nums) | Error { pos; error = `Expected msg } -> Printf.printf "Error at %d: %s\n" pos msg | Error _ -> print_endline "Parse error"