LL(1) parsing
LL(1) parsing is a method of top-down parsing in which the parser decides which rule of the grammar to apply by looking at the next token of input and nothing more[1][2]. The name describes it: the input is read from Left to right, a Leftmost derivation is produced, and 1 token of lookahead is used. The parser starts from the start symbol and repeatedly replaces the leftmost nonterminal by the right-hand side of a rule, choosing the rule from a table indexed by the nonterminal and the next token. The table is computed from two sets derived from the grammar, FIRST and FOLLOW. A grammar is LL(1) if no entry of the table holds more than one rule, and then parsing takes linear time and never backtracks. An LL(1) parser can be written as a table and a stack, or as a recursive-descent parser with one function per nonterminal, which is how many hand-written compilers parse. Grammars with left recursion, ambiguity or common prefixes are not LL(1) and have to be rewritten first.
Definitions
For a string of grammar symbols , is the set of terminals that can begin a string derived from , and is nullable if it can derive the empty string. For a nonterminal , is the set of terminals that can appear immediately after in some sentential form, including the end marker if can end one[4]:
A rule should be chosen when the next token is in its predict set, which is , together with if is nullable. The grammar is LL(1) when the rules for each nonterminal have pairwise disjoint predict sets. For two rules this means
LL() generalizes this to tokens of lookahead, with FIRST and FOLLOW sets of strings of length [1][2].
Computing the sets
All three sets are least fixed points of simple equations and are computed by iterating until nothing changes. A nonterminal is nullable if one of its rules has a right-hand side made entirely of nullable symbols. contains the FIRST set of each right-hand side of , where the FIRST set of a sequence is the union of for the prefix of symbols up to and including the first one that is not nullable. For every rule , contains , and also if is nullable[4].
Nullable, FIRST and FOLLOW by fixed-point iteration, and the LL(1) table.
(* Context-free grammars, the nullable, FIRST and FOLLOW sets, and theLL(1) parse table. Terminals are strings; "$" marks the end of input. *)type sym = T of string | N of stringtype grammar = { start : string; rules : (string * sym list) list }module S = Set.Make (String)let nonterminals g = List.sort_uniq compare (List.map fst g.rules)(* Iterate a monotone update until nothing changes. *)let fixpoint step =let changed = ref true inwhile !changed do changed := false; step changed donelet analyse g =let nullable = Hashtbl.create 16 and first = Hashtbl.create 16and follow = Hashtbl.create 16 inList.iter (fun a -> Hashtbl.replace first a S.empty; Hashtbl.replace follow a S.empty)(nonterminals g);Hashtbl.replace follow g.start (S.singleton "$");let is_nullable = function T _ -> false | N a -> Hashtbl.mem nullable a in(* FIRST of a sequence of symbols. *)let rec first_seq = function| [] -> S.empty| T t :: _ -> S.singleton t| N a :: rest ->let f = Hashtbl.find first a inif Hashtbl.mem nullable a then S.union f (first_seq rest) else finlet grow tbl a s changed =let old = Hashtbl.find tbl a inlet n = S.union old s inif not (S.equal n old) then (Hashtbl.replace tbl a n; changed := true)infixpoint (fun changed ->List.iter (fun (a, rhs) ->if List.for_all is_nullable rhs && not (Hashtbl.mem nullable a) then(Hashtbl.replace nullable a (); changed := true);grow first a (first_seq rhs) changed) g.rules);(* For A -> ... B beta: FIRST(beta) is in FOLLOW(B), and FOLLOW(A) tooif beta is nullable. *)fixpoint (fun changed ->List.iter (fun (a, rhs) ->let rec walk = function| [] -> ()| T _ :: rest -> walk rest| N b :: rest ->grow follow b (first_seq rest) changed;if List.for_all is_nullable rest then grow follow b (Hashtbl.find follow a) changed;walk restinwalk rhs) g.rules);let predict (a, rhs) =if List.for_all is_nullable rhs then S.union (first_seq rhs) (Hashtbl.find follow a)else first_seq rhsin(is_nullable, first, follow, predict)(* The table maps (nonterminal, lookahead) to the rules that apply. Thegrammar is LL(1) when no cell holds more than one rule. *)let table g =let _, _, _, predict = analyse g inlet t = Hashtbl.create 32 inList.iter (fun ((a, _) as r) ->S.iter (fun x ->let old = Option.value ~default:[] (Hashtbl.find_opt t (a, x)) inHashtbl.replace t (a, x) (old @ [r])) (predict r)) g.rules;tlet conflicts g =Hashtbl.fold (fun k rs acc -> if List.length rs > 1 then (k, rs) :: acc else acc) (table g) []|> List.sort comparelet show_rhs = function| [] -> "ε"| rhs -> String.concat " " (List.map (function T t | N t -> t) rhs)let show_rule (a, rhs) = a ^ " → " ^ show_rhs rhslet show_set s = "{" ^ String.concat ", " (S.elements s) ^ "}"
The usual expression grammar, with the left recursion removed.
open Ll1(* The expression grammar with left recursion removed. *)let expr = { start = "E"; rules = ["E", [N "T"; N "E'"];"E'", [T "+"; N "T"; N "E'"];"E'", [];"T", [N "F"; N "T'"];"T'", [T "*"; N "F"; N "T'"];"T'", [];"F", [T "("; N "E"; T ")"];"F", [T "id"] ] }
Running it. Empty cells, shown as dots, are syntax errors.
E nullable false FIRST {(, id} FOLLOW {$, )}E' nullable true FIRST {+} FOLLOW {$, )}T nullable false FIRST {(, id} FOLLOW {$, ), +}T' nullable true FIRST {*} FOLLOW {$, ), +}F nullable false FIRST {(, id} FOLLOW {$, ), *, +}id + * ( ) $E T E' . . T E' . .E' . + T E' . . ε εT F T' . . F T' . .T' . ε * F T' . ε εF id . . ( E ) . .conflicts: 0
The parser
The table-driven parser keeps a stack of grammar symbols that remain to be matched, initially just the start symbol. If the top of the stack is a terminal, it must equal the next token, and both are removed. If it is a nonterminal , the parser looks up and the next token in the table and replaces by the right-hand side of that rule. The input is accepted when the stack and the input are both empty. Each step either consumes a token or expands a nonterminal, and for a grammar without cycles of nullable rules the number of expansions between two tokens is bounded, so the parser runs in linear time[4].
The table-driven parser, printing each step.
open Ll1(* The table-driven parser: a stack of symbols still to be matched. Aterminal on top must equal the next token; a nonterminal is replacedby the right-hand side that the table selects for the next token. *)let run g tokens =let t = table g inlet words l = String.concat " " (List.map (function T x | N x -> x) l) inlet rec loop stack input =let show action =Printf.printf "%-14s %-20s %s\n" (words stack) (String.concat " " input) action inmatch stack, input with| [], [ "$" ] -> show "accept"| T x :: rest, y :: ys when x = y -> show ("match " ^ x); loop rest ys| N a :: rest, y :: _ ->(match Hashtbl.find_opt t (a, y) with| Some [ ((_, rhs) as r) ] -> show (show_rule r); loop (rhs @ rest) input| _ -> show (Printf.sprintf "error: no rule for %s on %s" a y))| _, y :: _ -> show ("error: unexpected " ^ y)| _, [] -> assert falseinPrintf.printf "%-14s %-20s %s\n" "stack" "input" "action";loop [ N g.start ] (tokens @ [ "$" ])
Parsing id + id * id, and a syntax error.
stack input actionE id + id * id $ E → T E'T E' id + id * id $ T → F T'F T' E' id + id * id $ F → idid T' E' id + id * id $ match idT' E' + id * id $ T' → εE' + id * id $ E' → + T E'+ T E' + id * id $ match +T E' id * id $ T → F T'F T' E' id * id $ F → idid T' E' id * id $ match idT' E' * id $ T' → * F T'* F T' E' * id $ match *F T' E' id $ F → idid T' E' id $ match idT' E' $ T' → εE' $ E' → ε$ accept--stack input actionE id + * id $ E → T E'T E' id + * id $ T → F T'F T' E' id + * id $ F → idid T' E' id + * id $ match idT' E' + * id $ T' → εE' + * id $ E' → + T E'+ T E' + * id $ match +T E' * id $ error: no rule for T on *
The sequence of rules chosen is a leftmost derivation, and it spells out the parse tree from the top down, left to right. The parser detects an error as soon as the input read so far cannot be the beginning of any sentence of the language: here, on the * that follows +.
Recursive descent
An LL(1) table is a compact description of a program with one procedure per nonterminal. The procedure for looks at the next token, chooses the rule the table would choose, and then, for each symbol of its right-hand side in order, either matches a terminal or calls the procedure of a nonterminal. The call stack of this recursive-descent parser plays the role of the explicit stack[4][5]. Written by hand, the tail rules such as become loops, which also give the operators their usual left associativity.
A recursive-descent parser for the same grammar, producing a syntax tree.
(* Recursive descent: one function per nonterminal, choosing a rule bylooking at the next token. The tail rules E' and T' become loops,which also make + and * associate to the left. *)type ast = Id of string | Add of ast * ast | Mul of ast * astlet parse (tokens : string list) =let toks = ref tokens inlet peek () = match !toks with t :: _ -> t | [] -> "$" inlet advance () = toks := List.tl !toks inlet expect t = if peek () = t then advance () else failwith ("expected " ^ t) inlet rec e () = (* E → T E' *)let l = ref (t ()) inwhile peek () = "+" do advance (); l := Add (!l, t ()) done;!land t () = (* T → F T' *)let l = ref (f ()) inwhile peek () = "*" do advance (); l := Mul (!l, f ()) done;!land f () = (* F → ( E ) | id *)match peek () with| "(" -> advance (); let x = e () in expect ")"; x| "$" -> failwith "unexpected end of input"| tok when tok <> ")" && tok <> "+" && tok <> "*" -> advance (); Id tok| tok -> failwith ("unexpected " ^ tok)inlet x = e () inif peek () <> "$" then failwith ("unexpected " ^ peek ()); xlet rec show = function| Id x -> x| Add (a, b) -> "(+ " ^ show a ^ " " ^ show b ^ ")"| Mul (a, b) -> "(* " ^ show a ^ " " ^ show b ^ ")"
Running it.
a + b * c (+ a (* b c))a * b + c (+ (* a b) c)a - b error: unexpected -a + b + c (+ (+ a b) c)( a + b ) * c (* (+ a b) c)a + * b error: unexpected *( a + b error: expected )
Recursive descent is easy to write, to debug and to extend with semantic actions and good error messages, and it is how many production compilers parse. GCC replaced its generated C++ parser with a hand-written recursive-descent parser in 2004[11], and Clang, the Go and Rust compilers and most JavaScript engines parse by recursive descent. For expressions with many precedence levels, recursive descent is often combined with Pratt parsing, which handles precedence and associativity with a table of binding powers instead of one procedure per level. Parser combinators are a library form of recursive descent.
Grammars that are not LL(1)
A left-recursive rule such as can never be LL() for any : every token that can begin can also begin , and a recursive-descent procedure for would call itself without consuming input. Two rules that share a prefix, such as , also conflict. The standard transformations remove these problems[4]:
The first is left-recursion elimination and the second left factoring. An ambiguous grammar is not LL() for any , and no transformation of this kind removes the ambiguity of the dangling else: after left factoring, the conflict remains.
The conflicts in a left-recursive grammar and in the left-factored dangling else.
open Ll1(* Left recursion: both rules for E start with whatever starts E. *)let left_recursive = { start = "E"; rules = ["E", [N "E"; T "+"; N "T"];"E", [N "T"];"T", [T "id"] ] }(* The dangling else, after left factoring. *)let dangling_else = { start = "S"; rules = ["S", [T "if"; T "c"; T "then"; N "S"; N "S'"];"S", [T "a"];"S'", [T "else"; N "S"];"S'", [] ] }
Running it.
left recursion:M[E, id] = E → E + T | E → Tdangling else:M[S', else] = S' → else S | S' → ε
Parsers resolve the dangling else by always choosing when the next token is , which attaches each to the nearest . The conflict entry is removed from the table by hand.
Expressive power
Every LL() grammar is unambiguous and is also LR(), so LR parsing handles strictly more grammars[8][3]. The LL() languages form a strict hierarchy: for every there are languages that are LL() but not LL()[12]. Some deterministic languages are not LL() for any , such as , where a top-down parser would have to decide between the two forms before seeing any or [10]. Rosenkrantz and Stearns showed that, unlike for context-free grammars in general, it is decidable whether two LL() grammars generate the same language[2].
Parr and Fisher's LL(*) removes the fixed bound on lookahead by running a finite automaton over the remaining input to choose a rule[6], and ALL(*), used by ANTLR 4, performs that analysis while parsing and accepts any grammar without left recursion, returning one parse of an ambiguous input[7].
History
Recursive-descent parsing was used in compilers by the early 1960s\; Lucas described it in 1961 for a compiler of ALGOL 60[9]. Lewis and Stearns defined LL() grammars in 1968 in their study of syntax-directed translation[1], and Rosenkrantz and Stearns developed their theory in 1970[2]. Knuth's paper “Top-down syntax analysis” of 1971 related them to LR grammars and to the parsing methods of the time[3]. Wirth designed Pascal and its successors so that they could be parsed by recursive descent with one token of lookahead, and taught the method through his textbooks[5]. The table-driven formulation with FIRST and FOLLOW sets became standard through compiler textbooks such as Aho and Ullman's[4], and Parr's ANTLR brought LL parser generators into wide use from the 1990s[6][7].
see also
- LR parsingBottom-up parsing driven by a table of states: the parser shifts tokens onto a stack, and reduces the top of the stack by a grammar rule when the next token says to. A grammar for which the table cannot be built without a choice has conflicts, reported as shift/reduce or reduce/reduce.
- Parser combinatorA parser combinator is a higher-order function that builds a parser from smaller parsers: a sequence of two parsers, a choice between them, a repetition of one. Parsers are ordinary values in the host language, so a grammar is written as a program that is itself the parser, with no separate generator. Parser combinators produce recursive descent parsers; they cannot use left-recursive rules directly, and with unlimited backtracking can take exponential time, which memoization (packrat parsing) reduces to linear.
- Earley parsingEarley parsing is an algorithm that parses any context-free grammar, including ambiguous and left-recursive ones. For each position in the input it builds a set of items, grammar rules with a dot marking how much of the right-hand side has been recognized and the position where recognition began, using three operations: prediction adds the rules for a nonterminal the parser expects, scanning moves past a matching token, and completion advances the rules that were waiting for a nonterminal that has just been recognized. It runs in O(n³) time in general, O(n²) on unambiguous grammars and linear time on most grammars used in practice. Jay Earley published it in 1970.
- Pratt parsingPratt parsing, or top-down operator precedence parsing, is a technique for parsing expressions in which every operator has a numeric binding power on each side, and a single recursive function parses an expression by consuming operators for as long as they bind more tightly than a given minimum. Prefix, infix, postfix, mixfix and bracketing operators are handled by attaching to each token a null denotation, its meaning at the start of an expression, and a left denotation, its meaning after one. The method handles any number of precedence levels and both associativities in linear time with one function instead of one grammar rule per level, and new operators can be added by extending a table. Vaughan Pratt introduced it in 1973.
further reading
- [1]P. M. Lewis II, R. E. Stearns, “Syntax-directed transduction”, Journal of the ACM 15 (1968).
- [2]D. J. Rosenkrantz, R. E. Stearns, “Properties of deterministic top-down grammars”, Information and Control 17 (1970).
- [3]D. E. Knuth, “Top-down syntax analysis”, Acta Informatica 1 (1971).
- [4]A. V. Aho, M. S. Lam, R. Sethi, J. D. Ullman, Compilers: Principles, Techniques, and Tools, §4.4, Addison-Wesley (2nd ed., 2006).
- [5]N. Wirth, Algorithms + Data Structures = Programs, ch. 5, Prentice-Hall (1976).
- [6]T. Parr, K. Fisher, “LL(*): the foundation of the ANTLR parser generator”, PLDI (2011).
- [7]T. Parr, S. Harwell, K. Fisher, “Adaptive LL(*) parsing: the power of dynamic analysis”, OOPSLA (2014).
- [8]D. E. Knuth, “On the translation of languages from left to right”, Information and Control 8 (1965).
- [9]P. Lucas, “Die Strukturanalyse von Formelübersetzern”, Elektronische Rechenanlagen 3 (1961).
- [10]D. Grune, C. J. H. Jacobs, Parsing Techniques: A Practical Guide, ch. 8, Springer (2nd ed., 2008).
- [11]GCC release notes, “GCC 3.4 changes” (2004).
- [12]R. Kurki-Suonio, “Notes on top-down languages”, BIT 9 (1969).
last updated