0% found this document useful (0 votes)
5 views67 pages

Compiler Design: Syntax Analysis Overview

This document outlines the concepts of syntax analysis in compiler design, focusing on the role of parsers, parse trees, and various parsing techniques such as top-down and bottom-up parsing. It discusses the importance of eliminating ambiguity in grammars and introduces methods like left recursion elimination and left factoring. Additionally, it covers LL(1) grammars, predictive parsing, and shift-reduce parsing, providing examples and exercises for better understanding.

Uploaded by

Adharsh Rajan
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
5 views67 pages

Compiler Design: Syntax Analysis Overview

This document outlines the concepts of syntax analysis in compiler design, focusing on the role of parsers, parse trees, and various parsing techniques such as top-down and bottom-up parsing. It discusses the importance of eliminating ambiguity in grammars and introduces methods like left recursion elimination and left factoring. Additionally, it covers LL(1) grammars, predictive parsing, and shift-reduce parsing, providing examples and exercises for better understanding.

Uploaded by

Adharsh Rajan
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd

Compiler Design

Syntax Analysis
Module 2 (Part-1)
Outline-Half of Module 2

 Role of Parser-Parse Tree - Elimination of Ambiguity – Top Down Parsing –


Recursive Descent Parsing - LL (1) Grammars – Shift Reduce Parsers-
Operator Precedence Parsing
The role of parser

token
Source Lexical Parse tree Rest of Intermediate
Parser
program Analyzer Front End representation
getNext
Token

Symbol
table
Syntax Analysis

 After lexical analysis (scanning), we have a series of tokens.


 In syntax analysis (or parsing), we want to interpret what those
tokens mean.
 Goal: Recover the structure described by that series of tokens.
 Goal: Report errors if those tokens do not properly encode a structure.
Uses of grammars

E -> E + T | T
T -> T * F | F
F -> (E) | id
After removing Left Recursion

E -> TE’
E’ -> +TE’ | Ɛ
T -> FT’
T’ -> *FT’ | Ɛ
F -> (E) | id
Context free grammars

 Terminals
 Nonterminals
 Start symbol expression -> expression + term
 productions expression -> expression – term
expression -> term
term -> term * factor
term -> term / factor
term -> factor
factor -> (expression)
factor -> id
Derivations

 Productions are treated as rewriting rules to generate a string


 Rightmost and leftmost derivations
 E -> E + E | E * E | -E | (E) | id
 Derivations for –(id+id)
 E => -E => -(E) => -(E+E) => -(id+E)=>-(id+id)
Parse trees

 -(id+id)
 E => -E => -(E) => -(E+E) => -(id+E)=>-(id+id)
Ambiguity

 For some strings there exist more than one parse tree
 Or more than one leftmost derivation
 Or more than one rightmost derivation
 Example: id+id*id
Example of ambiguity

 E -> E + E
 E -> E * E
 E -> id
 For the string id + id * id
 We can't use associativity here as there are two different operators, +
and *. Hence, we need to use "Precedence".
 The order of evaluation must be: id + (id * id) as * has more precedence
than +. The operator with the highest priority must be evaluated first.
Hence, the operators with high priority are to be arranged in the lower
levels of the parse tree.
 If id = 2:
 If + id * id = 2 + 2 * 2 = 6
Hence, the first
derivation tree is the
correct parse tree.
Converting into unambiguous
grammar
 We should write the grammar so that all the highest priority
operators stay in lower levels. Every production should follow a
recursion based on the associativity of the operator used.
 +, -, *, / are left associative operators. Hence, the productions using
these operators must follow left recursion
 ^ and = are right-associative operators. Hence, the productions using
these operators must follow the right recursion.
Elimination of left recursion

 A grammar is left recursive if it has a non-terminal A such that there is


a derivation A=> Aα +

 Top down parsing methods cant handle left-recursive grammars


 A simple rule for direct left recursion elimination:
 For a rule like:
 A -> A α|β
 We may replace it with
 A -> β A’
 A’ -> α A’ | ɛ
Left factoring

 Left factoring is a grammar transformation that is useful for


producing a grammar suitable for predictive or top-down parsing.
 Consider following grammar:
 Stmt -> if expr then stmt else stmt
 | if expr then stmt
 On seeing input if it is not clear for the parser which production
to use
 We can easily perform left factoring:
 If we have A->αβ1 | αβ2 then we replace it with
 A -> αA’
 A’ -> β1 | β2
Left factoring (cont.)
 Algorithm
 For each non-terminal A, find the longest prefix α common to two or
more of its alternatives. If α<> ɛ, then replace all of A-productions A-
>αβ1 |αβ2 | … | αβn | γ by
 A -> αA’ | γ
 A’ -> β1 |β2 | … | βn
Example for Left Factoring

 A → aAB / aBc / aAc


 This can be written as
 A → aA’
 A’ → AB / Bc / Ac

Again, this is a grammar with


common prefixes.
A → aA’
A’ → AD / Bc
D→B/c
This is a left factored grammar.
Parsing Techniques
Top Down Parsing
Introduction

 A Top-down parser tries to create a parse tree from the root


towards the leafs scanning input from left to right
 It can be also viewed as finding a leftmost derivation for an input
string
 Example: id+id*id

E -> TE’ E E E E E E
lm lm lm lm lm
E’ -> +TE’ | Ɛ T E’ T E’ T E’ T E’ T E’
T -> FT’
T’ -> *FT’ | Ɛ F T’ F T’ F T’ F T’ + T E’

F -> (E) | id id id Ɛ id Ɛ
Recursive descent parsing

 Consists of a set of procedures, one for each nonterminal


 Execution begins with the procedure for start symbol
 A typical procedure for a non-terminal

void A() {
choose an A-production, A->X1X2..Xk
for (i=1 to k) {
if (Xi is a nonterminal
call procedure Xi();
else if (Xi equals the current input symbol a)
advance the input to the next symbol;
else /* an error has occurred */
}
}
Recursive descent parsing (cont)

 General recursive descent may require backtracking


 The previous code needs to be modified to allow backtracking
 In general form it cant choose an A-production easily.
 So we need to try all alternatives
 If one failed the input pointer needs to be reset and another alternative
should be tried
 Recursive descent parsers cant be used for left-recursive grammars
Example

S->cAd
A->ab | a Input: cad

S S S

c A d c A d c A d

a b a
LL(1) Grammars

 Predictive parsers are those recursive descent parsers needing no


backtracking
 Grammars for which we can create predictive parsers are called LL(1)
 The first L means scanning input from left to right
 The second L means leftmost derivation
 And 1 stands for using one input symbol for lookahead

*
Finding FIRST()
Finding FOLLOW()
Construction of predictive parsing
table
 Create a Table with Non Terminals on the Rows and Terminals and $ on
the columns.
 For each production A->α in grammar do the following:
1. For each terminal a in First(α) add A-> in M[A,a]
2. If ɛ is in First(α), then for each terminal b in Follow(A) add A-> ɛ to M[A,b]. If
ɛ is in First(α) and $ is in Follow(A), add A-> ɛ to M[A,$] as well
 If after performing the above, there is no production in M[A,a] then set
M[A,a] to error
Exercise-1

FIRST FOLLOW
 S->aABb {a} {$}
 A->c/Ɛ
{c, Ɛ} {d,b}
 B->d/Ɛ
{d, Ɛ} {b}
a b c d $
S S->aABb
A A->Ɛ A->c A->Ɛ
B Parse the string ‘acdb’
B->Ɛ B->d
Parsing the string ‘acdb’

Stack Input Output


$S acdb$ S->aABb
$bBAa acdb$ Pop a
$bBA cdb$ A->c
$bBc cdb$ Pop c
$bB db$ B->d
$bd db$ Pop d
$b b$ Pop b
$ $ Accept
Exercise-2

E -> TE’
E’ -> +TE’ | Ɛ
T -> FT’
T’ -> *FT’ | Ɛ
F -> (E) | id
First Follow

E -> TE’ E {id, (} { ), $}


E’ {+, ɛ} { ), $}
E’ -> +TE’ | Ɛ {id, (} {+,), $}
T -> FT’ T
T’ {*,ɛ} {+,), $}
T’ -> *FT’ | Ɛ {*, +, ), $}
F -> (E) | id F {id, (}
Input Symbol
Non -
terminal id + * ( ) $
E E -> TE’ E -> TE’

E’ E’ -> +TE’ E’ -> Ɛ E’ -> Ɛ

T T -> FT’ T -> FT’

T’ T’ -> Ɛ T’ -> *FT’ T’ -> Ɛ T’ -> Ɛ

F F -> id F -> (E)


Parsing Example id +id
Exercise-3 (I/P string is ibta)
S -> iEtSS’ | a
S’ -> eS | Ɛ
E -> b

Input Symbol
Non -
terminal a b e i t $
S S -> a S -> iEtSS’

S’ S’ -> Ɛ S’ -> Ɛ
S’ -> eS
E E -> b
Exercise-4

( ) a $
 S->(L)/a S S->(L) S->a
 L->SL’ L L->SL’ L->SL’
 L’->)SL’/ Ɛ L’ L’->)SL’
L’-> Ɛ

FIRST (S) = {(,a} FOLLOW(S)= {),$}


FIRST (L) = {(,a} FOLLOW(L)= {)}
FIRST (L’) = {),Ɛ} FOLLOW(L’)= {)}
 Parse the string (a)
Limitation

 After the construction of the parsing table, if for any non-terminal


symbol in the table we have more than one production rule for any
terminal symbol in the table column, then the grammar is not LL(1).
Otherwise, then grammar is considered as LL(1).
Non-recursive predicting parsing

a + b $

Predictive
parsing output
stack X
Y program
Z
$
Parsing
Table
M
Predictive parsing algorithm

Set ip point to the first symbol of w;


Set X to the top stack symbol;
While (X<>$) { /* stack is not empty */
if (X is a) pop the stack and advance ip;
else if (X is a terminal) error();
else if (M[X,a] is an error entry) error();
else if (M[X,a] = X->Y1Y2..Yk) {
output the production X->Y1Y2..Yk;
pop the stack;
push Yk,…,Y2,Y1 on to the stack with Y1 on top;
}
set X to the top stack symbol;
}
Bottom-up Parsing
Introduction

 Constructs parse tree for an input string beginning at the leaves


(the bottom) and working towards the root (the top)
 Example: id*id

E -> E + T | T id*id F * id T * id T*F F E


T -> T * F | F
T*F F
F -> (E) | id id F F id

id id F id T*F

id F id

id
Shift-reduce parser

 The general idea is to shift some symbols of input to the stack until a
reduction can be applied
 At each reduction step, a specific substring matching the body of a
production is replaced by the nonterminal at the head of the production
 The key decisions during bottom-up parsing are about when to reduce
and about what production to apply
 A reduction is a reverse of a step in a derivation
 The goal of a bottom-up parser is to construct a derivation in reverse:
 E=>T=>T*F=>T*id=>F*id=>id*id
Handle pruning

 A Handle is a substring that matches the body of a production and


whose reduction represents one step along the reverse of a
rightmost derivation

Right sentential form Handle Reducing production


id*id id F->id
F*id F T->F
T*id id F->id
T*F T*F E->T*F
Shift reduce parsing

 A stack is used to hold grammar symbols


 Handle always appear on top of the stack
 Initial configuration:
Stack Input
$ w$
 Acceptance configuration
Stack Input
$S $
Shift reduce parsing (cont.)

 Basic operations:
 Shift
 Reduce Stack Input Action
 Accept $ id*id$ shift
 Error $id *id$ reduce by F->id
 Example 1: id*id $F *id$ reduce by T->F
$T *id$ shift
$T* id$ shift
E-> E+T/T $T*id $ reduce by F->id
T->T*F/F $T*F $ reduce by T->T*F
F->id reduce by E->T
$T $
$E $ accept
Note: You don't need left-recursion elimination if you use shift-reduce parsing
Example 2

Stack Input Action


E->E-E $ id-id*id$ Shift
E->E*E $id -id*id$ Reduce
E->id
$E -id*id$ Shift
$E- id*id$ Shift
String
$E-id *id$ Reduce
id-id*id
$E-E *id$ Reduce
$E *id$ Shift
$E* id$ Shift
$E*id $ Reduce
$E*E $ Reduce
$E $ Accept
Example 3

S->(L)/a
L->L,S/S
Parse the input string (a, (a,a))
Stack Input Action
$ (a,(a,a))$ Shift
$( a,(a,a))$ Shift
$(a ,(a,a))$ Reduce Continue this
$(S ,(a,a))$ Reduce parsing table and
$(L ,(a,a))$ Shift find the answer
$(L, (a,a))$ Shift
$(L,( a,a))$ Shift
. . .
. . .
Example 4

 E –> 2E2
E –> 3E3
E –> 4
 Perform Shift Reduce parsing for input string “32423”.
Operator Precedence Parsing

 Operator precedence parsing is a type of Shift Reduce Parsing. In


operator precedence parsing, an operator grammar and an input
string are fed as input to the operator precedence parser, which
may generate a parse tree. Note that a parse tree will only be
generated if the input string gets accepted.
 In operator precedence parsing, the shift and reduce operations
are done based on the priority between the symbol at the top of
the stack and the current input symbol.
 The operator precedence parser performs the operator
precedence parsing using operator grammar.
Rules for performing Operator
Precedence parsing

A grammar is said to be an operator grammar if it follows


these two properties:
[Link] should be no ε (epsilon) on the right-hand side of
any production.
[Link] should be no two non-terminals adjacent to each
other.
Examples of operator grammar are A + B, A - B, A x B, etc.
Relations

 There are three operator precedence relations.


 a ⋗ b: This relation implies that terminal “a” has higher precedence
than terminal “b”.
 a ⋖ b: This relation implies that terminal “a” has lower precedence than
terminal “b”.
 a ≐ b: This relation implies that terminal “a” has equal precedence to
terminal “b”.
 Now, let’s see the steps to perform the operator precedence parsing.
Precedence Table

 A precedence table is used in operator precedence parsing to establish


the relative precedence of operators and to resolve shift-reduce conflicts
during the parsing process.
 The table instructs the parser when to shift (consume the input and
proceed to the next token) and when to reduce (apply a production rule
to reduce a set of tokens to a non-terminal symbol).
 It is an essential Data Structure for building a shift-reduce parser.
Example 1

 T → T+T
 T → T*T
 T → id

+ * id $
+ ⋗ ⋖ ⋖ ⋗
* ⋗ ⋗ ⋖ ⋗
Id ⋗ ⋗ — ⋗
$ ⋖ ⋖ ⋖ A
Parsing the string id+id*id

Stack Relation Input Buffer Parsing Action


$ ⋖ id+id*id$ Shift
$id ⋗ +id*id$ Reduce by T → id
$T ⋖ +id*id$ Shift
$T+ ⋖ Id*id$ Shift
$T+id ⋗ *id$ Reduce by T → id
$T+T ⋖ *id$ Shift
$T+T* ⋖ id$ Shift
$T+T*id ⋗ $ Reduce by T → id
$T+T*T ⋗ $ Reduce by T → T *
T
$T+T ⋗ $ Reduce by T → T +
Using functions

 Compilers using operator-precedence parsers need not store the table of


precedence relations. In most cases, the table can be encoded by two
precedence functions f and g that map terminal symbols to integers. We
attempt to select f and g so that, for symbols a and b. 1. f (a) < g(b)
whenever a g(b) whenever a ·> b.
Algorithm for Constructing
Precedence Functions:
1. Create functions fa for each grammar terminal a and for the end of
string symbol.
2. Partition the symbols in groups so that fa and gb are in the same group
if a = b (there can be symbols in the same group even if they are not
connected by this relation).
Algorithm contd..

1. Create a directed graph whose nodes are in the groups, next for each
symbols a and b do: place an edge from the group of gb to the group
of fa if a b place an edge from the group of fa to that of gb.
2. If the constructed graph has a cycle then no precedence functions
exist. When there are no cycles collect the length of the longest paths
from the groups of fa and gb respectively.
Precedence Graph
LEADING and TRAILING
Computation of LEADING:
• Leading is defined for every non-terminal.
• Terminals that can be the first terminal in a string derived from that
non-terminal.
• LEADING(A)={ a| A=>+ γaδ },where γ is ε or any non-terminal,
=>+ indicates derivation in one or more steps, A is a non-terminal.
Computation of TRAILING:
• Trailing is defined for every non-terminal.
• Terminals that can be the last terminal in a string derived from that
non-terminal.
• TRAILING(A)={ a| A=>+ γaδ },where δ is ε or any non-terminal,
=>+ indicates derivation in one or more steps, A is a non-terminal.
Example
Compute LEADING and TRAILING:
Example OP using Leading and
Trailing
 E→E + T
 E→T
 T→T * F
 T→F
 F→(E)
 F→id
Parse the string (id+id)*id$
If the grammar is not according to OP
rules, convert it
 P->SR/S
 R->bSR/bS
 S->WbS/W
 W->L*W/L
 L->id
 Converting this to OP Grammar
 P->SbP/SbS/S
 S->WbS/W
 W->L*W/L
 L->id

You might also like