Your First Parser

https://lobste.rs/rss Hits: 11
Summary

Build a config file parser from scratch, learning Parseff along the way. Parser combinators let you write small parsers that each handle one thing, then compose them. Instead of a single regex that’s hard to read, test, and extend, you get modular pieces with clear error messages. There are three ways to compose parsers: Sequence: parse A, then parse B (let bindings) Choice: try A, if it fails try B (Parseff.or_) Repetition: parse A zero or more times (Parseff.many) This tutorial builds a key-value config file parser from scratch, adding one feature at a time. Lines starting with # are comments. Blank lines are skipped. Everything else is key = value. Each parser reads input by calling combinators like Parseff.take_while (with ~at_least:1) and Parseff.char, and returns a value. Parseff.take_while ~at_least:1 (fun c -> (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c = '_') Parseff.take_while ~at_least:1 (fun c -> c <> '\n') ~label:"value" Parseff.skip_while (fun c -> c = ' ' || c = '\t'); let _ = Parseff.char '=' in Parseff.skip_while (fun c -> c = ' ' || c = '\t'); take_while ~at_least:1 scans characters while the predicate holds and requires at least one match. The ~label appears in error messages if nothing matches. char '=' matches a single character. skip_while advances past whitespace without allocating a string. Sequencing is just let bindings. Each line advances the cursor through the input. To run it: match Parseff.parse "host = localhost" entry with| Ok (k, v) -> Printf.printf "%s -> %s\n" k v (* "host" -> "localhost" *)| Error { pos; error = `Expected msg } -> Printf.printf "Error at %d: %s\n" pos msg| Error _ -> print_endline "Parse error" Parseff.parse returns Ok value on success, or Error { pos; error } on failure. A comment line starts with #. A line can be a comment, an entry, or blank. We need alternation: try one option, and if it fails, try the next. Parseff.or_ does this for two alternatives: let _ = Parseff.char '#' in let _ = Parseff.take_...

First seen: 2026-03-25 17:52

Last seen: 2026-03-26 03:59