Live data from Hacker News

Intuiting Pratt Parsing

louis.co.nz

41–50 of 51 posts

Re: Intuiting Pratt Parsing

#41
post #35
post #6

Earlier quoted context omitted.

Quick other one: To parse infix expressions, every time you see "x·y | (z | w)", find the operator of least binding power: In my example, I've given "|" less binding power than "·". Anyway, this visually breaks the expression into two halves: "x·y" and "(z | w)". Recursively parse those two subexpressions. Essentially, that's it. The symbols "·" and "|" don't mean anything - I've chosen them to be visually intuitive:…

An even easier approach is to give all infix operators the same precedence and force the programmer to group subexpressions.

You can always write lisp but most people can read code better that doesnt have that many (((()))))))

Re: Intuiting Pratt Parsing

#42
post #2

Love Pratt parsing! Not a compiler guy, but I've spent way too many hours reflecting on parsing. I remember trying to get though the dragon book so many times and reading all about formal grammar etc. Until I landed on; recursive descent parsing + Pratt for expressions. Super simple technique, and for me is sufficient. I'm sure it doesn't cover all cases, but just for toy languages it feels like we can usually do eve…

Not to step on anyone's toes, I just don't feel that formal grammar theory is that important in practice. :^)

Well, it depends how formal you're talking about. I have to say that the standard you mention, recursive descent parsing + Pratt for expressions. actually requires you to understand what a formal language is - that it's a "thing" that can't (or shouldn't) be an object or a data structure but exists abstractly before any objects created by the program.

Moreover, the standard way of producing a recursive descend parser is to begin with your language in Chomsky normal form or some human understandable format and then convert to Greibach Normal form and that specification converts readily to your series of recursive functions. So all language transforms are useful to know (though you can skip steps if you have a good intuition of your language).

Re: Intuiting Pratt Parsing

#43
post #19

Earlier quoted context omitted.

Some people appreciate that an LR/LALR parser generator can prove non-ambiguity and linear time parse-ability of a grammar. A couple of examples are the creator of the Oil shell, and one of the guys responsible for Rust. It does make me wonder though about why grammars have to be so complicated that such high-powered tools are needed. Isn't the gist of LR/LALR that the states of an automaton that can parse CFGs can b…

Production compilers must have robust error recovery and great error messages, and those are pretty straightforward in recursive descent, even if ad hoc.

[deleted]

Re: Intuiting Pratt Parsing

#45
post #35

Earlier quoted context omitted.

An even easier approach is to give all infix operators the same precedence and force the programmer to group subexpressions.

You can always write lisp but most people can read code better that doesnt have that many (((()))))))

I'm sure there's a middle ground which still gives you some of the metaprogramming power of Lisp. OTOH this: https://www.gingerbill.org/article/2026/02/21/does-syntax-ma...

Re: Intuiting Pratt Parsing

#46

An even simpler way imo, is explicit functions instead of a precedence table, then the code pretty much has the same structure as EBNF. Need to parse * before +? Begin at add, have it call parse_mul for its left and right sides, and so on. parse_mul() { left = parse_literal() while(is_mul_token()) { // left associative right = parse_literal() make_mul_node(left, right) } } parse_add() { left = parse_mul() while(is_ad…

Systemverilog has an operator precedence table with 16 levels.

https://www.academia.edu/figures/3550818/table-2-operator-pr...

Writing a recursive descent for this would require writing 16 functions, and you'd end up spending most of your time cycling through the functions to finally come across the one which applies for the given situation.

I've written straight-forward expressions parsers as you suggest, but when I had to do it for systemverilog, I used a classic shunting yard parser. You see the operator, compare its precedence against the stack and you know immediately what to do, vs possibly drilling down 16 levels of function calls to figure out what to do.

Another advantage of table-driven expression parsers is you can bail in error cases without needing to unwind countless levels of stack.

Re: Intuiting Pratt Parsing

#47
post #13

Earlier quoted context omitted.

The Dragon book is not very good, to be honest. It was probably decent when all you had was something like Pascal and you wanted to write a C compiler. Parsing and compiling and interpreting etc are all much more at home in functional languages. Much easier to understand there. And once you do, then you can translate back into imperative. For parsing: by default you should be using parser combinators.

I was just going into the second quarter of compiler design when the dragon book came out. My copy was still literally “hot of the press” — still warm from the ink baking ovens. It was worlds better that anything else available at the time.

Oh, I don't doubt that in the bad old days the Dragon book was a step forward. It's just pretty bad compared to what you can get today.

Re: Intuiting Pratt Parsing

#48
post #13

Earlier quoted context omitted.

The Dragon book is not very good, to be honest. It was probably decent when all you had was something like Pascal and you wanted to write a C compiler. Parsing and compiling and interpreting etc are all much more at home in functional languages. Much easier to understand there. And once you do, then you can translate back into imperative. For parsing: by default you should be using parser combinators.

The Dragon book wasn't good for me either but I'd disagree about using parser combinators. The problem that I'd see the Dragon book having is basically starting to use concepts (phases of compilation) before it introduces and motivates them in the abstract. I can see how people who already know these concepts can look at the Dragon book and say "oh, that's a good treatment of this" so perhaps it's good reference but…

I wrote a bunch of parser combinator libraries myself, and used plenty more.

They are basically the same idea as regular expressions, but more flexible: you have a bunch of combining operations for your regular languages to build bigger regular languages. But that doesn't tell you how it's implemented in the backend. Could be Recursive, could be automata, could be backtracking, could be anything.

If you want to write your first compiler, I'd even go so far as to suggest to use something with a Lisp syntax as your source language, explicitly so you can minimise the parsing aspect.

Parsing is a lot of fun by itself, but it doesn't actually have much to do with the core of what makes compilers interesting and challenging. It's almost an independent pursuit, and very useful outside of writing compilers, too.

> As far as I can tell, parser combinators are just one way that promises to let "write a compiler without understanding abstract languages" but all these methods actually wind-up being libraries that are far complicated than gp's "recursive descent + pratt parsing", which is easy once you understand the idea of an abstract language.

Where do you suspect the complexity here? You can write a toy library for parser combinators that's really simple, if your implementation language is at least as capable as Rust or even Python (with Haskell and OCaml being probably the easiest). If you are using an off-the-shelf industrial-stength, production-grade parser combinator library: of course, that's complicated under the hood. That's the price the authors wilingly pay for great error handling and performance etc.

For most people writing their first compilers, they would better off starting the project from when they already have some tree or DAG representation. Plenty of (more!) interesting challenges left. Going from stream of bits to the parsed syntax tree is something they can learn about later (or not at all), without missing much.

Re: Intuiting Pratt Parsing

#49
post #2

Love Pratt parsing! Not a compiler guy, but I've spent way too many hours reflecting on parsing. I remember trying to get though the dragon book so many times and reading all about formal grammar etc. Until I landed on; recursive descent parsing + Pratt for expressions. Super simple technique, and for me is sufficient. I'm sure it doesn't cover all cases, but just for toy languages it feels like we can usually do eve…

Professional compiler writer here. All you really need to use is a recursive descent parser. Very easy to understand. Very easy to implement. While also being very powerful.

Re: Intuiting Pratt Parsing

#50

Earlier quoted context omitted.

Is there a production compiler out there that doesn't use recursive descent, preferably constructed from combinators? Table-driven parsers seem now to be a "tell" of an old compiler or a hobby project.

The thing about LR parsers is that since it is parsing bottom-up, you have no idea what larger syntactic structure is being built, so error recovery is ugly, and giving the user a sensible error message is a fool’s errand. In the end, all the hard work in a compiler is in the back-end optimization phases. Put your mental energy there.

I have worked on compilers (mostly) for high-performance computing for over 40 years, writing every part of a production compiler twice or more. Optimization and code generation and register allocation/scheduling are definitely the most fun -- but the hardest work is in parsing and semantics, where "hardest" means it takes the most work to get things right for the language and to deal with user errors in the most graceful and informative manner. This is especially true for badly specified legacy languages like Fortran.
Post reply on HN