Operator precedence by textual substitution
kmjn.org
kmjn.org
For a more elegant solution, see these tiny languages parsed using Floyd's operator precedence parsing:
[1] https://en.wikipedia.org/wiki/Operator-precedence_parser
Not sure exactly what to call this category of limitation, but you see it in diverse places where people have designed things without considering the upper limits of what they can cope with.
Essentially you take note of the first operator's precedence and keep consuming input until you reach an operator with lower precedence than you currently have. The precedence you use for comparison is updated each step.
Here's some code that does that: https://github.com/uibk-dps-teaching/ps_cc_2021/blob/0ad022c...
Only thing you have to watch here: with a recursive descent parser you might need an extra step to fix the associativity (right-to-left vs. left-to-right) if that's important for your application.
Here's that: https://github.com/uibk-dps-teaching/ps_cc_2021/blob/0ad022c...
Doesn't matter whether that information is language defined or user defined.
For instance in Haskell, that's all the information you provide. You'd use either `infixl` or `infixr` (depending on which associativity you want) followed by an integer defining the precedence for the given operator.
The parser just needs a table to look up the precedence (and associativity) for a given operator. But a parser already needs a bunch of different tables in order to work, so that's certainly not a problem.
Think about how you'd implement an independent parser for such a language, say for the sake of pretty printing it to html. Module A uses a user-defined operator that has been defined in Module B. You effectively cannot parse A correctly before you parse and load B, but the fact that you need to parse and load B is "hidden" in some import declaration that in turn needs to be parsed first. That's what I call a mess.
These days the general consensus is to use the Pratt parser, which is somewhat harder to understand, but ends up not too hard to build and integrate into a recursive decent parser, and is supereasy to modify.
Here's one popular explanation:
https://journal.stuffwithstuff.com/2011/03/19/pratt-parsers-...
What I find funny is that Pratt's parser was never really a theoretical result and was never properly covered in popular compiler textbook.
Sorry, what?
The theory behind precedence parsing was studied a decade earlier by Floyd (who is cited in Pratt's paper). Pratt's own paper is more about the justification of considering operators to have semantic importance rather than productions of nonterminals.
And this idea makes all the difference for language implementers.
In my parser, when it encounters two binary expressions `a * b + c`, it always initially parses them as `a * (b + c)` into temporary AST nodes. Then it compares the precedence of the operators and, if needed, rewrites the nodes to form the correct expression.
Like all the claims about making languages: Truth until is a big fat lie.
Try to do the full precedence for more complex languages than formula evaluators could become harder than expected (you think you nailed, then a edge-case happens).
Good question!