I really like the animations, makes it intuitive to understand what is happening!
One disadvantage that I think parser combinators have, for example compared to parser generator libraries (where the parser is generated from a seperate grammar file), is that you have to add a lot of mapping / unwrapping code. In my opinion this makes the parsers harder to read.
As an attempt to improve this, I have been working on a parser combinator library in rust. It adds two extra combinators (capture and bind) which can store the results of parsers in buckets (bind) and use the values stored in these buckets later to construct results (capture). This allows you to eliminate the unwrapping / mapping code and seperate the grammar from the result contstruction.
If you have the time, I would really appreciate feedback on this!
You can find it here: https://github.com/ArneCode/marser
I don't understand why this can't be on a single page. Makes it really hard to refer back to earlier "chapters" as you get new information when it's a slideshow. Can't search either.
Seems like we're needlessly reintroducing limitations from the paper medium that don't exist in digital media for weird aesthetic reasons?
I think this is really cool website and love this topic
But I think you might want to test this page on smaller screens, just very limited screen real estate for the content of interest, the top right half lots of padding, the scrollable content basically has 1/3 of the available screen space, the left hand side kind of has another 1/3
Look your website your vision, I get it and I respect your trying something different with the UI but if your goal here is to communicate your ideas, as a reader I'd appreciate having more screen space for the content, when I clicked on the link to see this specific post I'm just not interested in your other posts (or at least until I've read the one I went out of my way to read).
FWIW, I'm using a macbook air right now.
Very cool visual guide! I feel it is appropriate here to recite this little poem about parsers by Fritz Ruehr that stuck with me since my first semester of CS. With this, you will never forget the type signature of a parser.
„A Parser for Things is a function from Strings to Lists of Pairs of Things and Strings!“
The first address should probably not be parsed as an IP address as it can also be a hostname. Might seem like a nitpick, but it creates an opportunity to demonstrate what to do when you expect either of more than one kind of construct.
The article was somewhat difficult to read due to the limited area devoted to the body of the text. I had to scroll on several pages despite the short chapters.
Not a good starting type:
type Parser<T> = (input: string) => [T, string]
This implies that it accepts a string and will return a T. Parsers might return a T.The article also starts with the concept of a 'partial parser', from which other (non-partial?) parsers can be constructed?
Or, and this is where the real power of partial parsers comes in: we wrap the chain up as a new parser of its own.
Nope, there's no distinction. If you combine partial parsers, you end up with a partial parser. If you combine full parsers (whatever that means) you end up with full parsers.(Ironically there is a fitting definition of 'partial' here: the function isn't well defined on all inputs. For some inputs, T is returned, on other inputs, maybe an error is thrown, who knows..)
It's a cool guide, but you kind of missed the opportunity to parse the packet metadata as arbitrary key-value pairs, i.e. for:
64 bytes from 1.1.1.1: icmp_seq=3 ttl=52 time=47.1 ms
everything after the colon is key-value data, and throwing in a generic parser for `pKeyVal()` would make an even better point of how elegant the approach is.An underrated quality of parser combinators is that they're often written in continuation passing style (monadic style is a simple generalisation of this). So, you get "incremental" parsing for free (as "what to do next" is trivially available). So, many of these parsers work well on streams (e.g. TCP sockets) without manually maintaining a state machine - as you may do in C. Further to this, such a state machine is derivable by defunctionalising the parser combinator code. Of course, you get the same benefit if you have decent coroutine support, but it's nice to see techniques that work around not having runtime/language support for such niceties.
There's another, more obscure, technique in the functional programming world called [iteratees](https://okmij.org/ftp/Haskell/Iteratee/index.html) which are quite neat and (as done in related article) linked to "online parser combinators".
Just as Claude is working in the background to fix any bug it can find in my ancient parser-combinator library in OCaml [0]. It's written in literate programing style, so it's not for everyone (I guess f you love the posted animated slide show, you won't like literate programming :)) but it has some cool features (and, according to Claude, some frightening bugs too): it can parse in stream mode, and it can be given an "error budget" to automatically fix syntax errors (be careful with this, as the complexity goes to the roof so quick I've never benn able to use this feature in any real scenario).
The main advantage of such a parser (and the reason why I'm reviving this one right now) is because they accept syntaxes that most other type of parsers would find ambiguous.
I would suggest parsing my scroll wheel input and combinating it into page events
Thanks for sharing. This was very easy to follow on many levels. I could see it's only 17 slides and the code was simple enough to read and understand. I have read the term parser combinators many time and now I actually understand it. Kudos to the writer for making it easy to understand.
Others mentioned, but to make it more clear, the problem with regex is not that it gets messy/complex, but that it's formally proven that it's not possible to use regex for recursive parsing.
In fact regex has a regular grammar, and based on language theories, it's not possible to parse context-free grammar with a regular language.
See langsec for more.
Love me some combinators. In my earlier days it helped bridge the academic gap of CS grammars and languages to something I could not readily apply to internship dev work. I seem to recall an attempt to map or generate the from regex and in reverse, but I might be crazy this early in the morning.
Nice visuals btw. If I may be so bold: slightly smaller font and header would make scrolling through the bottom readable code snippets a little easier!
I love parser combinators so much. Those are possible because parsing itself exhibits monadic structure, it forms transition monoid, of its more well known name as the semiautomaton in the world of CS.
Great little series!
OP has written other fantastic posts, like this one on colors:
https://t3.at/blog/2026-08-21-color-1-rgb-cube/
@schindlabua: Your explanations made some things click for me for the very first time, thank you! (Also, the YouTube channel you linked to is indeed a fascinating rabbit hole: https://m.youtube.com/@ooqui )
In case you take suggestions for additional parts in that series: I would love to learn more about common color spaces such as DCI-P3 and Adobe RGB and how they relate to sRGB, and what the pipeline
(An application on, say, Linux/Wayland defines a pixel to have color C in color space S.)
->
(My color-corrected screen which supports/doesn't fully support color space S shows that color C.)
looks like exactly.I'm working on landing a similar feature in a language project, but in a general way, geared torward high level application flows. The general idea is that you can declare a "seam" between a function that returns a type set, and some other actor that exposes a method set that covers said typeset. The compiler can deal with the inbetween code, so the mainline of the app is just a set of typespace expansions/compressions. A callstack within a single thread domain can be expressed like:
bytes → (NewOrder | Cancel) → handlers → (Ack | Reject) →
https://github.com/hale-lang/hale/issues/1530#issuecomment-6...
They should at least point out the limitations of the approach. For example that they cannot handle left-recursive grammars, and that they tend to backtrack a lot on ambiguous grammars leading to exponentially bad performance.
Just me or is clicking on share from the browser sharing the link with “example.com” instead of “t3.at”?
Can I parse (X)HTML that way, or should I go back to regex?
How does the parser decide _where_ to split the statement?
That was beautifully done!
Great article!
Observation: In LISP and LISP-like languages, aka Functional programming languages, parsers can be written elegantly, as combinations of simpler parsing functions...
In theory this could also be done in Imperative or Object-Oriented programming languages (other programming language paradigms) as well, but the key to the elegance seems to come from two particular language aspects:
1) The ability for functions to return lists (not specific to Functional programming languages, many languages have this ability)
2) The ability of those lists to contain unstructured asymmetric data types of individual list elements at runtime, including the ability for arbitrary individual list elements to be first class, unnamed, dynamically created functions aka "Lambda Functions" at runtime.
Any Language which supports #1 and #2 has the ability to create some very elegant parsing functions based on simpler parsing functions.
Of course, parsers with the equivalent functionality can be written in any Turing-complete programming language (which is basically every programming language!), although with slightly less (and yes, aesthetics are always in the proverbial "eye of the beholder" -- so any given individuals's personal ones may be different from any other given individual's, and probably different than mine!) elegant-looking (IMHO) code...
Anyway, great article!
Nice design
- 404 on your page?
looks like standard functional programming to me
[flagged]
[flagged]
[dead]
If you're on Python and your problem is too big for a single regex, but too small for a full-blown parser like in the article, I can recommend the built-in re.Scanner[0]. It's a regex-based tokenizer[1] (so no recursive parsing) in the `re` module, that for reasons is completely missing from any official documentation.
You give it a pattern for each token type, and a function to process each match, and you get back a list of processed tokens.
Importantly, it processes the list in one pass and ensures the matches are contiguous, where a naive `re.findall` with capture groups will ignore unmatched characters. You also get a reference to the running scanner, so you can record the location of the match for reporting errors.
[0]: https://stackoverflow.com/a/693818/252218[1]: https://en.wikipedia.org/wiki/Lexical_analysis#Tokenization