Jump to content

  • Log In with Google      Sign In   
  • Create Account


Recursive-descent parsing: Handling left-associativity?


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
8 replies to this topic

#1 BeanDog   Members   -  Reputation: 1063

Like
0Likes
Like

Posted 27 September 2006 - 11:18 AM

I'm writing a small language parser. Addition and subtraction are left-associative. In other words, 3+3+3 means (3+3)+3. In my grammar, it's basically this:
Expression ::= Expression + Term  |  Term
I'm having trouble getting my mind around this problem. If it was right-associative, my parsing function for this rule would be something like this:
Expression ParseExpression() {
  Term tLeft = ParseTerm();
  if(NextToken().Type == Plus)
    return new Addition(tLeft, ParseExpression());
  else
    return tLeft;
}
In fact, I originally wrote the function that way, and it worked great--except that my additions were all right-associative instead of left-associative. The problem, of course, is that my function would get into an infinite loop if I tried to do this instead:
Expression ParseExpression() {
  Expression eLeft = ParseExpression();
  if(NextToken().Type == Plus)
    return new Addition(eLeft, ParseTerm());
  else
    return eLeft;
}
So, what do I have to change to implement left-associativity? [edit: Fixed grammar] Thanks, [Edited by - BeanDog on September 27, 2006 6:28:13 PM]

~BenDilts( void );

Lucidchart: Online Flow Chart Software; Lucidpress: Digital Publishing Software


Sponsor:

#2 Zahlman   Moderators   -  Reputation: 1682

Like
0Likes
Like

Posted 27 September 2006 - 11:33 AM

Change the grammar.

Try 'Expression ::= Term | Term '+' Expression'.

#3 BeanDog   Members   -  Reputation: 1063

Like
0Likes
Like

Posted 27 September 2006 - 12:31 PM

Note: Fixed original grammar in OP.

Unfortunately, changing the grammar as you suggested makes answers incorrect. You would make the operator right-associative. Although additions would work correctly, consider divisions. 1/2/3 would be 1/(2/3) = 3/2 instead of (1/2)/3 = 1/6. So I've got to figure out a way to do it with my original grammar (or come up with an equivalent grammar that's easier to implement).

~BenDilts( void );

Lucidchart: Online Flow Chart Software; Lucidpress: Digital Publishing Software


#4 Anonymous Poster_Anonymous Poster_*   Guests   -  Reputation:

0Likes

Posted 27 September 2006 - 01:11 PM

Recursive descent parsers CANNOT handle left associativity (or right associativity, if they start parsing from the right :) ).

Fortunately, every left-associative BNF grammar can be rewritten into a non-left-associative, equivalent grammar. I believe Zahlman was giving an example, not a solution. So experiment with new ways of writing BNF for arithmetic (and remember, even if no particular RULE is left-recursive, the BNF might still be (cycles in the BNF rules).

#5 Devilogic   Members   -  Reputation: 242

Like
1Likes
Like

Posted 27 September 2006 - 01:36 PM

Usually a better way to parse expressions of this type is to use only 1 recursive call per operator level/precedence. Normally you handle all operators (not just 1) of the same level in a single parsing function, while accumulating the result of this level in the return variable. In practice this means that your function ParseExpression should look something like this:

Expression ParseExpression() {
Term tLeft = ParseTerm();
while (NextToken().Type == Plus) {
tLeft = new Addition(tLeft, ParseTerm());
}

return tLeft;
}
Notice that three things have changed:
1. The if-statement was replaced with a while-statement,
2. The call to Addition now uses ParseTerm as the second parameter, instead of ParseExpression (the function ParseExpression is now only called once per operator level, and the next level [ParseTerm] is invoked directly rather than through a recursive ParseExpression call),
3. The result is now accumulated in tLeft, which is returned from the function when there are no more operators of this level in the input string.

If you do all your arithmetics like this you get left-associativity for free, while also reducing recursion depth and making the code more understandable. Btw, the grammar this is parsing is: Expression = Term {"+" Term}.

P.S. I am not a C programmer so the code snippet I posted above might contain errors!

[Edited by - Devilogic on September 28, 2006 6:36:57 AM]

#6 Zahlman   Moderators   -  Reputation: 1682

Like
0Likes
Like

Posted 27 September 2006 - 04:55 PM

Sorry, my rewrite was equivalent to the right-associative parser you illustrated. For some reason I was thinking of the interpretation process as somehow decoupled from the construct ;)

You might find this useful though. Or search for yourself :)

Edit: NM, it's still all the same: the techniques for removing left-recursion also flip the natural associativity. You additionally need to do something like what Devilogic said. Alternatively, keep the recursion but transform as you go:


Expression ParseExpression() {
Term tLeft = ParseTerm();
if(NextToken().Type == Plus) {
Addition right(ParseExpression());
// Hmm... not sure about this at all, actually :P
return Addition(Addition(tLeft, right.lhs), right.rhs);
} else return tLeft;
}


[Edited by - Zahlman on September 27, 2006 11:55:37 PM]

#7 daerid   Members   -  Reputation: 354

Like
0Likes
Like

Posted 27 September 2006 - 05:14 PM

For example, if you have a grammar like this:



expr = expr * expr
| expr / expr
| expr + expr
| expr - expr
| <...>





It can be refactored to something like the following to eliminate left recursion:



expr = factor * factor
| factor / factor

factor = term + term
| term - term

term = number
| ( expr )





I did a small(sorta) scripting language in Boost.Spirit, which is a recursive descent parser framework.

#8 BeanDog   Members   -  Reputation: 1063

Like
0Likes
Like

Posted 28 September 2006 - 07:01 AM

Daerid: Unfortunately your example refactored grammar eliminates *all* recursion, making something like 1/2/3 impossible.

Devilogic: Rating++ for helping me understand what I'd found on Google. They said that left-recursion should be done in a loop, exiting the loop if the next operator is a lower precedence and continuing the loop if the operator is equal or higher precedence. Didn't make much sense at first, but I see your quick pseudocode would do what I need.

Thanks all!

~BenDilts( void );

Lucidchart: Online Flow Chart Software; Lucidpress: Digital Publishing Software


#9 daerid   Members   -  Reputation: 354

Like
0Likes
Like

Posted 28 September 2006 - 06:58 PM

Quote:
Original post by BeanDog
Daerid: Unfortunately your example refactored grammar eliminates *all* recursion, making something like 1/2/3 impossible.


oh, right.. sorry. the correct grammar would be:



expr = factor * expr
| factor / expr
| factor

factor = term + factor
| term - factor
| term

term = number
| ( expr )






I was thinking with a spirit grammar, which has a nice little kleene star operator, like so:


add_expr
= mul_expr >> *(add_op >> mul_expr)
;






Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS