package parseff

  1. Overview
  2. Docs
Direct-style parser combinator library for OCaml 5 powered by algebraic effects

Install

dune-project
 Dependency

Authors

Maintainers

Sources

parseff-0.2.0.tbz
sha256=a49fa685546f8d9bc90d59a62ecc8204a24621b438164503745024e1038ebc9b
sha512=43baed140dcce2fd6e024a1f03daa71a923b1de19efa41c73eced3550528d5d911b8f0fc13dbb795f7af7a6fc963fc9d0a5fa8a9099ca752696e90345514f6ac

doc/repetition.html

Repetition and separation

These combinators handle patterns that repeat: lists, separated values, delimited blocks, and operator chains.

Basic repetition

many

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 list
let digits () = Parseff.many Parseff.digit ()
(* "123"  -> [1; 2; 3]  *)
(* ""     -> []         *)
(* "abc"  -> []         *)

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 list
let digits1 () = Parseff.many ~at_least:1 Parseff.digit ()
(* "123" -> [1; 2; 3]  *)
(* ""    -> Error      *)
(* "abc" -> Error      *)

count

Parseff.count applies a parser exactly n times. Fails if the parser doesn't match n times.

val count : int -> (unit -> 'a) -> unit -> 'a list
let 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

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 list
let 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

Parseff.sep_by with ~at_least:1 requires at least one element.

val sep_by : ?at_least:int -> (unit -> 'a) -> (unit -> 'b) -> unit -> 'a list
let 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

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 -> 'c
let 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 ']')
    p

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

Parseff.end_by with ~at_least:1 requires at least one element.

val end_by : ?at_least:int -> (unit -> 'a) -> (unit -> 'b) -> unit -> 'a list

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

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

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

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"