package parseff

  1. Overview
  2. Docs

Combinators

Combinators compose simple parsers into more complex ones. They handle alternation, error labeling, lookahead, and recursion.

Alternation

or_

Parseff.or_ tries the left parser. If it fails, backtracks (resets the cursor) and tries the right parser.

val or_ : (unit -> 'a) -> (unit -> 'a) -> unit -> 'a
let bool_parser () =
  Parseff.or_
    (fun () ->
      let _ = Parseff.consume "true" in
      true)
    (fun () ->
      let _ = Parseff.consume "false" in
      false)
    ()
(* "true"  -> Ok true  *)
(* "false" -> Ok false *)
(* "maybe" -> Error { pos = 0; error = `Expected "false" } *)

On "maybe", the left branch fails at position 0 (expected "true", got "m"), backtracks, and the right branch also fails (expected "false", got "m"). The error from the last branch attempted is reported.

or_ is ideal for two alternatives. When you have more, use Parseff.one_of, which takes a list and avoids nested or_ calls:

let keyword_with_or () =
  Parseff.or_
    (fun () -> Parseff.consume "let")
    (fun () ->
      Parseff.or_
        (fun () -> Parseff.consume "const")
        (fun () -> Parseff.consume "var")
        ())
    ()

let keyword_with_one_of () =
  Parseff.one_of
    [
      (fun () -> Parseff.consume "let");
      (fun () -> Parseff.consume "const");
      (fun () -> Parseff.consume "var");
    ]
    ()

one_of

Parseff.one_of tries each parser in order until one succeeds. Equivalent to chaining or_, but cleaner for more than two alternatives.

val one_of : (unit -> 'a) list -> unit -> 'a
let json_value () =
  Parseff.one_of
    [
      null_parser;
      bool_parser;
      number_parser;
      string_parser;
      array_parser;
      object_parser;
    ]
    ()

If all parsers fail, one_of fails with the error from the last parser attempted.

one_of_labeled

Parseff.one_of_labeled is like Parseff.one_of, but each parser has a label. On failure, the error message reports all labels:

val one_of_labeled : (string * (unit -> 'a)) list -> unit -> 'a
let literal () =
  Parseff.one_of_labeled
    [
      ("number", fun () -> Number (Parseff.digit ()));
      ("string", string_parser);
      ("boolean", bool_parser);
    ]
    ()
(* On failure: "expected one of: number, string, boolean" *)

This gives users a clear picture of what was expected at a given position, without exposing internal parser details.

optional

Parseff.optional tries the parser. Returns Some result on success, None on failure (without consuming input).

val optional : (unit -> 'a) -> unit -> 'a option
let signed_number () =
  let sign = Parseff.optional (fun () -> Parseff.char '-') () in
  let n = number () in
  match sign with
  | Some _ -> -n
  | None -> n
(* "42"  -> 42  *)
(* "-42" -> -42 *)

Lookahead

look_ahead

Parseff.look_ahead runs a parser without consuming input. The cursor stays where it was before the call. Fails if the parser fails.

val look_ahead : (unit -> 'a) -> 'a
let peek_open_paren () =
  let _ = Parseff.look_ahead (fun () -> Parseff.char '(') in
  (* cursor hasn't moved, '(' is still the next character *)
  parse_parenthesized_expr ()

Useful for context-sensitive decisions: peek at what's coming, then choose which parser to run.

Error labeling

expect

Parseff.expect runs a parser and relabels its failure with a clearer message.

val expect : string -> (unit -> 'a) -> 'a
let dot () =
  Parseff.expect "a dot separator" (fun () -> Parseff.char '.')

let digit_val () = Parseff.expect "a digit (0-9)" Parseff.digit

Use expect at grammar boundaries where user-facing wording is better than raw token names:

let ip_address () =
  let a = number () in
  let _ = Parseff.expect "a dot separator" (fun () -> Parseff.char '.') in
  let b = number () in
  let _ = Parseff.expect "a dot separator" (fun () -> Parseff.char '.') in
  let c = number () in
  let _ = Parseff.expect "a dot separator" (fun () -> Parseff.char '.') in
  let d = number () in
  Parseff.end_of_input ();
  (a, b, c, d)

Without expect, a failed char '.' reports expected '.'. With expect, it reports expected a dot separator.

Recursion

rec_

Parseff.rec_ marks a recursive entry point for depth tracking. Wrap the body of recursive parsers with rec_ so that Parseff.parse can enforce max_depth and fail cleanly instead of overflowing the stack.

val rec_ : (unit -> 'a) -> 'a
let rec json () =
  Parseff.rec_ (fun () ->
    Parseff.skip_whitespace ();
    Parseff.one_of
      [
        array_parser;
        object_parser;
        null_parser;
        bool_parser;
        number_parser;
        string_parser;
      ]
      ()
  )

and array_parser () =
  let _ = Parseff.consume "[" in
  (* ... calls json () recursively ... *)
  let _ = Parseff.consume "]" in
  Array elements

The ~max_depth parameter on Parseff.parse controls the limit (default: 128):

(* Reject inputs nested deeper than 64 levels *)
Parseff.parse ~max_depth:64 input json

When the limit is exceeded, parsing fails with "maximum nesting depth N exceeded" rather than a stack overflow.

Tip: Only wrap the top-level recursive entry point with rec_. You don't need it on every mutually recursive function, just the one that represents "entering a new nesting level".