Top-Down LR Parsing
pavpanchekha.com
pavpanchekha.com
I can tell you one thing: It is much easier to transform your grammar than to switch to an exotic parser generator.
That means that once you got your precedences in order, the simplest approach will always be recursive descent implemented with whatever parser combinators exist in your language of choice. Yes, generators work and can be very fast, but they are a maintenance burden and might make simple stuff ("just parse these specific sub-epxresssions for this web form") unnecessary hard. So don't switch without a good reason from whatever is the de-facto standard.
Oh, and parsing is a little bit like crypto: Don't roll your own unless you are either a pro or doing it for educational purposes.
Oh but unfortunately I don't agree with the last paragraph. In my experience it's the opposite, parsing works the best if you roll everything on your own, in fact in my codebases my principle is parsing is a no library zone (except regex, if necessary). This is because writing a recursive descent parser in any language is easy and using libraries simply increases the maintenance cost imho. What value do you get from not rolling your own parser?
I have experience in writing parsers and small langs but whenever I have to approach all those theories behind parsers and grammar then Im lost
I dont understand why would you want to approach those problems in such a way, or at least describe it in such a way that feels so out of touch with programming
This theory is so unintuitive damn
Idk maybe ive hurt myself by starting with those topics by practice instead of theory, but who knows?
When I want to parse a thing I know that all I need to do is turn it into a bunch of tokens and then gradually loop over all the tokens until I've reduced them into something that can't be reduced further, and that lets me do infix and all of that good stuff.
I don't quite understand how to bridge the gap between that understanding and all the theory.
I think it did a huge disservice to our students. It completely elided probably the three most important strategies for parsing "in practice", in order: recursive descent (RD), Pratt, and Earley. The time it spent on algorithmic reduction was insufficient to teach students the practice of translating algorithms to simpler, but more efficient, forms (parametric algorithm design).
I think it's because there's not a lot of formal "meat" on RD, Pratt, and Earley — the first two are engineering solutions, and the latter is too difficult for lower-division to do the proofs.
Anyways, learn Pratt and RD. That's all you need, in practice.
The personal site in my bio hasn't had an email address on it because I thought I'd have one running on my own domain by now, but I'll go stick a gmail address on there like right now in case you're willing share
I don’t know. But the “langsec” (language security) branch of software security argues that a lot of security issues stem from insecure approaches to parsing.
I don’t know how much the theory of parsing helps with that. But it can help you make sure that your grammar has certain properties.
Bigger factor seems to be strong standard library with safe string primitives
Aka NOT C.
That has everything to do with grammar theory (and with how clumsy parser generators are and with how brittle many hand-written parsers are and how often they don't quite implement the spec).
The first half of the course is on parsing.
I'm hoping I'm giving a lot of examples and there is some coding (using PEG, which is closer to "programmer intuition").
https://link.springer.com/article/10.1007/BF01934444
These are essentially the LR(k) grammars that become LL(k) with a combination of a left-corner transform (to eliminate left recursion) and factorization of common prefixes.
Looking to pick a fight with lispers? haha
Shameless self-plug, in case you want to understand more about LR parsing: https://www.youtube.com/watch?v=8UDWd-Axd5A
That makes error recovery really really difficult, because you have to unwind the call stack to the next recovery state rather just messing with the contents of the array and pointer.
I can understand the teaching value in doing it this way but students really need to understand the value of table driven tools in the real world
[0] http://journal.stuffwithstuff.com/2011/02/13/extending-synta...
I imagine a slighlty more elaborate error recovery approach could return different kinds of bogus nodes and check for them and process them differently in different productions.
One easy trick to optimize back-tracking is memoization.
Another is the left-recursion into right-recursion trick, which is also rather straightforward to apply.
These two tricks are used in IParse Studio [0] which is an interpreting parser that parses a grammar and uses that grammar to parse some input. I also can help you with rewriting the grammar using the described approach.
[0] https://fransfaase.github.io/MCH2022ParserWorkshop/IParseStu...
Expr ::= CanBeLhsOfAssignExpr | CanBeLhsOfAssignExpr "=" Expr | CanNotBeLhsOfAssignExpr
but in practice, it's easily parsed in the following manner: def parse_Expr(self):
# parse_ExprInner parses CanBeLhsOfAssignExpr or CanNotBeLhsOfAssignExpr cases
result = self.parse_ExprInner()
if self.peekToken() == TOKEN_ASSIGN:
if result.is_CanBeLhsOfAssignExpr:
token = self.skipToken()
# assignment is right-associative
rhs = self.parse_Expr()
result = self.make_AssignExpr(token, result, rhs)
else:
self.raiseParseError("{} can't be target of assignment", result)
return result
The idea is to parse the prefix under "relaxed" rules, delaying the well-formedness check until it's actually needed.This is exemplified in the two definitions of `parse_add_addexpr_cont` in the article.
It's much harder to teach the inner working / implementation of such parsers. Table generation adds an extra layer of indirection. I skip this in my class, because I doubt it will be very valuable to students to be able to implement an LR parser compared to other things we cover in the class.
Doing the OP's approach, you can get some intuition for how a LR parser behaves / is implemented without going to the full complexity.
Expr parse_addexpr() {
Expr result = parse_mulexpr();
while (peek_token() == TokenType.PLUS) {
expect_token(TokenType.PLUS);
Expr rhs = parse_mulexpr();
result = new AddExpr(result, rhs);
}
return result;
}
which corresponds to "addexpr: <mulexpr> (+ <mulexpr>)*" but with left-associative tree-composing.