Recursive ascent parser explained

In computer science, recursive ascent parsing is a technique for implementing an LR parser which uses mutually-recursive functions rather than tables. Thus, the parser is directly encoded in the host language similar to recursive descent. Direct encoding usually yields a parser which is faster than its table-driven equivalent[1] for the same reason that compilation is faster than interpretation. It is also (nominally) possible to hand edit a recursive ascent parser, whereas a tabular implementation is nigh unreadable to the average human.

Recursive ascent was first described by Thomas Pennello in his article Book: Very fast LR parsing. 10.1145/12276.13326 . http://portal.acm.org/citation.cfm?id=13310.13326. Proceedings of the 1986 SIGPLAN symposium on Compiler construction - SIGPLAN '86 . 1986 . Pennello . Thomas J. . 145–151 . 0897911970 . 17214407 . in 1986. He was not intending to create a hand-editable implementation of an LR parser, but rather a maintainable and efficient parser implemented in assembly language. The technique was later expounded upon by G.H. Roberts[2] in 1988 as well as in an article by Leermakers, Augusteijn, Kruseman Aretz[3] in 1992 in the journal Theoretical Computer Science. An extremely readable description of the technique was written by Morell and Middleton[4] in 2003. A good exposition can also be found in a TOPLAS article by Sperber and Thiemann.[5]

Recursive ascent has also been merged with recursive descent, yielding a technique known as recursive ascent/descent. This implementation technique is arguably easier to hand-edit due to the reduction in states and fact that some of these states are more intuitively top-down rather than bottom up. It can also yield some minimal performance improvements over conventional recursive ascent.[6]

Summary

Intuitively, recursive ascent is a literal implementation of the LR parsing concept. Each function in the parser represents a single LR automaton state. Within each function, a multi-branch statement is used to select the appropriate action based on the current token read from the input stream. Once the token has been identified, action is taken based on the state being encoded. There are two different fundamental actions which may be taken based on the token in question:

There is also a third LR automaton action which may be taken in a given state, but only after a reduce where the shift counter has decremented to zero (indicating that the current state should handle the result). This is the goto action, which is essentially a special case of shift designed to handle non-terminals in a production. This action must be handled after the multi-branch statement, since this is where any reduction results will "resurface" from farther down the call stack.

Example

Consider the following grammar in bison syntax:

expr : expr '+' term   { $$ = $1 + $3; }
     | expr '-' term   { $$ = $1 - $3; }
     | term            { $$ = $1; }
     ;

term : '(' expr ')'    { $$ = $2; }
     | num             { $$ = $1; }
     ;

num : '0'              { $$ = 0; }
    | '1'              { $$ = 1; }
    ;

This grammar is LR(0) in that it is left-recursive (in the expr non-terminal) but does not require any lookahead. Recursive ascent is also capable of handling grammars which are LALR(1) in much the same way that table-driven parsers handle such cases (by pre-computing conflict resolutions based on possible lookahead).

The following is a Scala implementation of a recursive ascent parser based on the above grammar:

object ExprParser

The following is a Prolog implementation of a recursive ascent parser based on the above grammar:state(S), [S] --> [S].state(S0, S), [S] --> [S0].

/* 0. S --> E$ 1. E --> E + T 2. E --> E - T 3. E --> T 4. T --> (E) 5. T --> N 6. N --> 0 7. N --> 1

accept --> state(s([], [e(_)])).r(1) --> state(s(Ts, [t(A1), '+', e(A0)|Ss]), s(Ts, [e(A0+A1)|Ss])).r(2) --> state(s(Ts, [t(A1), '-', e(A0)|Ss]), s(Ts, [e(A0-A1)|Ss])).r(3) --> state(s(Ts, [t(A)|Ss]), s(Ts, [e(A)|Ss])).r(4) --> state(s(Ts, [')', e(A), '('|Ss]), s(Ts, [t(A)|Ss])).r(5) --> state(s(Ts, [n(A)|Ss]), s(Ts, [t(A)|Ss])).r(6) --> state(s(Ts, ['0'|Ss]), s(Ts, [n(0)|Ss])).r(7) --> state(s(Ts, ['1'|Ss]), s(Ts, [n(1)|Ss])).t(T) --> state(s([T|Ts], Ss), s(Ts, [T|Ss])).

/* S --> .E$ E --> .E + T E --> .E - T E --> .T T --> .(E) T --> .N N --> .0 N --> .1

s0 --> t('('), s3, s2, s1.s0 --> t('0'), s11, s10, s2, s1.s0 --> t('1'), s12, s10, s2, s1.

/* S --> E.$ E --> E. + T E --> E. - T

s1 --> accept.s1 --> t('+'), s7, s1.s1 --> t('-'), s8, s1.

/* E --> T.

s2 --> r(3).

/* T --> (.E) E --> .E + T E --> .E - T E --> .T T --> .(E) T --> .N N --> .0 N --> .1

s3 --> t('('), s3, s2, s4.s3 --> t('0'), s11, s10, s2, s4.s3 --> t('1'), s12, s10, s2, s4.

/* T --> (E.) E --> E .+ T E --> E .- T

s4 --> t(')'), s9.s4 --> t('+'), s7, s4.s4 --> t('-'), s8, s4.

/* E --> E + T.

s5 --> r(1).

/* E --> E - T.

s6 --> r(2).

/* E --> E + .T T --> .(E) T --> .N N --> .0 N --> .1

s7 --> t('('), s3, s5.s7 --> t('0'), s11, s10, s5.s7 --> t('1'), s12, s10, s5.

/* E --> E - .T T --> .(E) T --> .N N --> .0 N --> .1

s8 --> t('('), s3, s6.s8 --> t('0'), s11, s10, s6.s8 --> t('1'), s12, s10, s6.

/* T --> (E).

s9 --> r(4).

/* T --> N.

s10 --> r(5).

/* N --> '0'.

s11 --> r(6).

/* N --> '1'.

s12 --> r(7).

parser(Cs, T) :- length(Cs, _), phrase(s0, [s(Cs, [])], [s([], [e(T)])]).

% state(S0, S), [S] --> [S0, S].%?- S0 = [s("(1+1)", [])|_], phrase(s0, S0, [S]), maplist(portray_clause, S0).

See also

Notes and References

  1. Very fast LR parsing. Thomas J Penello. ACM SIGPLAN Notices . 1986. 21 . 7 . 145–151 . 10.1145/13310.13326 . free .
  2. Recursive ascent: an LR analog to recursive descent. 1988. G.H. Roberts. ACM SIGPLAN Notices . 23 . 8 . 23–29 . 10.1145/47907.47909 . 12740771 . free .
  3. A functional LR parser. Leermakers, Augusteijn, Kruseman Aretz. Theoretical Computer Science . 1992. 104 . 2 . 313–323 . 10.1016/0304-3975(92)90128-3 . free .
  4. News: Recursive-ascent parsing. Larry Morell . David Middleton . amp . 2003. Journal of Computing Sciences in Colleges. 18. 6. 186–201.
  5. Generation of LR parsers by partial evaluation. Sperber and Thiemann. ACM Transactions on Programming Languages and Systems . 2000. 22 . 2 . 224–264 . 10.1145/349214.349219 . 14955687 . free .
  6. Web site: ScalaBison Recursive Ascent-Descent Parser Generator. John Boyland . Daniel Spiewak . amp . 2009. https://web.archive.org/web/20090718161230/http://www.cs.uwm.edu/~boyland/papers/scala-bison.pdf. 2009-07-18. dead.