CSCE 434 Lecture 16

From Notes
Jump to navigation Jump to search

« previous | Monday, September 30, 2013 | next »

Lecture Slides

Tangent: Recursive Programs

Speed: Recursion is not usually fast.

  1. Create stack frame
  2. push arguments onto stack
  3. execute function call
  4. pop stack

Some compilers convert them to loops instead of recursion.

Optimizing a for-loop is easier than a while-loop because a for-loop is bounded. (something about GPUs) Both are difficult, but an unbounded while-loop is harder.

Predictive parsing

  • tables built from specification
  • state stack
  • parser reads from tables; process is an implementation of a finite state machine

LL(1) Parse Algorithm

int tos = 0;
stack[tos++] = eof;
stack[tos++] = start_symbol;
Token token = next_token();
Token X = Stack[tos];
do {
  if (X is a terminal || X == eof) {
    if (X == Token) {
      pop X;
      token = next_token();
    } else error();
  } else {  // X is a non-terminal
    if (M[X,token] == "X ::= { Y[1], Y[2], ..., Y[k] }")
      pop X;
      push Y[k], Y[k-1], ..., Y[1]
    } else error()
  }
  X = stack[tos];
} while (X != eof)

LL(1) Table for our grammar

  id num + - * / eof
<goal> <g> ::= <e> <g> ::= <e>
<expr> <e> ::= <t> <e'> <e> ::= <t> <e'>
<expr'> <e'> ::= + <t> <e'> <e'> ::= - <t> <e'> <e'> ::= ε
<term> <t> ::= <f> <t'> <t> ::= <f> <t'>
<term'> <t'> ::= ε <t'> ::= ε <t'> ::= * <f> <t'> <t'> ::= / <f> <t'> <t'> ::= ε
<factor> <f> ::= id <f> ::= num

First Set

first(α) is the set of terminal symbols that begin strings derived from α

If α*ϵ, then ϵfirst(α).

Building first(X):

  1. If X is a terminal, first(X)={X}.
  2. If X::=ϵ, then ϵfirst(X)
  3. If X::=Y1Y2Yk, then put first(Y1) in first(X)
  4. If X is a non-terminal and X::=Y1Y2Yk, then afrist(X) if afirst(Yi) and ϵfirst(Yj) for 1j<i (keep looking at the next Yi until we get one that can't go to ϵ)

Follow Set

For nonterminal A, define follow(A) as the set of terminals that can appear immediately to the right of A in some sentential form.

Thus a non-terminal's follow set specifies the tokens that can legally appear after it

A terminal symbol has now follow set.

To build:

  1. eoffollow(goal)
  2. If A::=αBβ, then put first(β){ϵ} in follow(B). We don't know enough about A, but this rule tells us something about B.
  3. If A::=αB, then put follow(A) in follow(B)
  4. If A::=αBβ and ϵfirst(β), then put follow(A) in follow(B) (almost the same thing as above; if nothing follows B, then anything that follows A can follow B by this rule)

Constructing a Parse Table

  1. For any production A::=α, perform steps 2–4
  2. for any terminal a in first(\alpha), add A::=α to M[A,a]
  3. If ϵfirst(α), add A::=a to M[A,b] for all terminals bfollow(A).
  4. If ϵfirst(α) and eoffollow(A), add A::=α to M[A,eof].
  5. Set each undefined entry of M to error

If this fails, the grammar is not LL(1), so we can make it LL(1) by adding non-terminals:

Adding non-terminals to the grammar does not change the language it accepts.

LL(1) Grammars

A grammar is LL(1) if and only if for all non-terminals A, teach distinct pair of productions A::=β and A::=γ satisfy the following condition: first(β)first(γ)=.

Properties:

  • no left-recursive grammar is LL(1)
  • no ambiguous grammar is LL(1)
  • LL(1) parsers operate in linear time
  • epsilon-free grammar , where each alternative expansion for A begins with a distinct terminal is a simple LL(1) grammar.