Diagnostics
Section titled “Diagnostics”Use diagnostics when you want to keep parsing and report multiple validation problems instead of stopping at the first one.
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) resultParse outcome with diagnostics in both success and failure cases.
Emitting diagnostics
Section titled “Emitting diagnostics”Parseff.warn records a non-fatal diagnostic at the current parser position.
val warn : 'd -> unitwarn_at
Section titled “warn_at”Parseff.warn_at records a non-fatal diagnostic at an explicit position.
val warn_at : pos:int -> 'd -> unitRunners
Section titled “Runners”parse_until_end
Section titled “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_diagnosticsOk (value, diagnostics)on successError { pos; error; diagnostics }on failure Semantically, this is equivalent to running your parser and then callingParseff.end_of_inputonce more at the end. It does not change how explicitParseff.end_of_inputcalls behave inside your parser.
parse_source_until_end
Section titled “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_diagnosticsIn 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
Section titled “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
Section titled “Backtracking semantics”Diagnostics are transactional with parser control flow:
Parseff.or_: diagnostics from failed branches are rolled backParseff.many: diagnostics from the final failing attempt are rolled backParseff.look_ahead: diagnostics are rolled back This prevents diagnostics from leaking out of speculative branches.