|
|
|
|
|
by vidarh
2678 days ago
|
|
I first tried shunting yard without knowing what it was by mechanically translating the stack usage from a Pratt parser, so I agree. The reason was that I'd extended it to handle a lot more unusual constructs, and at that point the recursive version got much harder to read. It was part of a (ill advised, probably) attempt at using it to parse an entire C-like language, control structures at all. It worked, but it was painful to figure out all the precedence rules, and e.g. if I remember correct modelling if () {} else {} as operator "if" with operands (condition if-block else-expression). The changes to allow weird prefix operators like that with multiple operands were otherwise reasonably straightforward, though, it was the precedence table that got really hard to reason about. [I wish I still had the source; it was a fun, though very much dated, project, that allowed arbitrary inline M68k assembler in expressions (e.g. you could write "if D0.W == $1000 { }" to compare the lower 16-bit word of data register 0 to hex 1000), with "mentioning" the registers making the register allocator treat them as off limits for that scope] |
|
When I wrote "Pratt Parsing and Precedence Climbing Are the Same Algorithm", multiple people replied and suggested that the Shunting Yard algorithm is also the same, with the equivalence you suggest.
In particular the author of the repo Jean-Marc Bourguet also thought that. Then he implemented it and realized it wasn't true.
See this section of the README:
https://github.com/bourguet/operator_precedence_parsing
I had the mistaken impression that they were "just" replacing an explicit stack with the call stack of the implementation language but that was not clear enough from Andy's code. It was not clear because that's not the case. Compare Pratt's algorithm with recursive_operator_precedence.py, which is a recursive implementation of operator precedence, to see the difference. Pratt and operator climbing are really top down techniques: they call semantic actions as soon as possible, they don't delay the call until the whole handle is seen as operator precedence does. My current characterization is that they are to LL parsing and recursive descent what operator precedence is to LR and shift-reduce
I think we had a discussion about an analogy LL vs. LR. Those are obviously not equivalent algorithms -- despite both being linear time, they recognize different languages.
I think in the Shunting Yard algorithm, when you parse 1 + 2 / 3, the 2 / 3 tree is made first. I guess that is somewhat true of Pratt parsing too for natural reasons of tree building. You build the tree when you bubble out of the recursion.
But it DECIDES that it has "led" for + before it decides it has "led" for /.
So in summary, Shunting Yard "decides" 2 / 3 first, where as Pratt parsing "decides" 1+... first. That's what I think anyway. I think you could probably make this rigorous, but I'm not sure.
I think of Pratt Parsing as LL(2) parsing for an operator grammar, although that's not technically correct because the precedence table is an extra piece of info outside the "grammar". You either lookahead 1 or 2 tokens, for null and led respectively.
So basically I think you could translate Pratt parsing to use an explicit stack -- but then you'd still be using Pratt parsing. The Shunting Yard algorithm is something else.