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.1.0.tbz
sha256=097c71a38b39ab5925518e16c0efdf3b77a6b3b2185c82f168e0f1f4cb0772bf
sha512=811fbd770148bf3004ffc764dc08fa1a3ded9b4613f5749a6d2841c1af868de7afff4dd6b808b38254d28592433e23613573707159dfa171687839c520e93bb3

doc/diagnostics.html

Diagnostics

Use diagnostics when you want to keep parsing and report multiple validation problems instead of stopping at the first one.

Types

The result types for diagnostics-aware parsing:

type 'd diagnostic = { pos : int; diagnostic : 'd }

A non-fatal diagnostic emitted during parsing.

type ('e, 'd) error_with_diagnostics = {
  pos : int;
  error : 'e;
  diagnostics : 'd diagnostic list;
}

An error with collected diagnostics.

type ('a, 'e, 'd) result_with_diagnostics =
  ('a * 'd diagnostic list, ('e, 'd) error_with_diagnostics) result

Parse outcome with diagnostics in both success and failure cases.

Emitting diagnostics

warn

Parseff.warn records a non-fatal diagnostic at the current parser position.

val warn : 'd -> unit

warn_at

Parseff.warn_at records a non-fatal diagnostic at an explicit position.

val warn_at : pos:int -> 'd -> unit

Runners

parse_until_end

Parseff.parse_until_end runs a parser on a string, enforces full input consumption implicitly, and returns both:

val parse_until_end :
  ?max_depth:int ->
  string ->
  (unit -> 'a) ->
  ('a, [> `Expected of string | `Unexpected_end_of_input | `Depth_limit_exceeded of string ], 'd)
  result_with_diagnostics
  • Ok (value, diagnostics) on success
  • Error { pos; error; diagnostics } on failure

Semantically, this is equivalent to running your parser and then calling Parseff.end_of_input once more at the end. It does not change how explicit Parseff.end_of_input calls behave inside your parser.

parse_source_until_end

Parseff.parse_source_until_end is the streaming equivalent of Parseff.parse_until_end for Parseff.Source.t inputs.

val parse_source_until_end :
  ?max_depth:int ->
  Source.t ->
  (unit -> 'a) ->
  ('a, [> `Expected of string | `Unexpected_end_of_input | `Depth_limit_exceeded of string ], 'd)
  result_with_diagnostics

In plain terms: parse_source_until_end always does one extra Parseff.end_of_input check after your parser returns.

If your parser already calls Parseff.end_of_input itself, that call keeps the same behavior as before. The runner just adds a final safety check.

Example

type validation = [ `Octet_out_of_range of int ]

let number_lenient () =
  let start = Parseff.position () in
  let digits = Parseff.many1 Parseff.digit () in
  let n = List.fold_left (fun acc d -> (acc * 10) + d) 0 digits in
  if n > 255 then Parseff.warn_at ~pos:start (`Octet_out_of_range n);
  n

let ip_address_lenient () =
  let a = number_lenient () in
  let _ = Parseff.char '.' in
  let b = number_lenient () in
  let _ = Parseff.char '.' in
  let c = number_lenient () in
  let _ = Parseff.char '.' in
  let d = number_lenient () in
  (a, b, c, d)

let () =
  let outcome =
    Parseff.parse_until_end "999.2.3.256" ip_address_lenient
  in
  match outcome with
  | Ok ((a, b, c, d), diagnostics) ->
      Printf.printf "Parsed: %d.%d.%d.%d\n" a b c d;
      List.iter
        (fun ({ pos; diagnostic } : validation Parseff.diagnostic) ->
          match diagnostic with
          | `Octet_out_of_range n ->
              Printf.printf "  at %d: octet %d out of range (0-255)\n" pos n)
        diagnostics
  | Error { pos; error = `Expected msg; diagnostics } ->
      Printf.printf "Parse error at %d: %s\n" pos msg;
      List.iter
        (fun ({ pos; diagnostic } : validation Parseff.diagnostic) ->
          match diagnostic with
          | `Octet_out_of_range n ->
              Printf.printf "  noted at %d: octet %d out of range\n" pos n)
        diagnostics
  | Error { pos; error = `Unexpected_end_of_input; _ } ->
      Printf.printf "Unexpected end of input at %d\n" pos
  | Error _ -> Printf.printf "Parse failed\n"

Backtracking semantics

Diagnostics are transactional with parser control flow:

This prevents diagnostics from leaking out of speculative branches.