CSC 3315 Lexical and Syntax Analysis Hamid Harroud

  • Slides: 32
Download presentation
CSC 3315 Lexical and Syntax Analysis Hamid Harroud School of Science and Engineering, Akhawayn

CSC 3315 Lexical and Syntax Analysis Hamid Harroud School of Science and Engineering, Akhawayn University http: //www. aui. ma/~H. Harroud/csc 3315/ CSC 3315 (Spring 2009) 1

Syntax Description vs. Syntax Analysis n Syntax Description: the set of rules that can

Syntax Description vs. Syntax Analysis n Syntax Description: the set of rules that can be used to generate any sentence in the language n n We can construct derivations (or parse trees) to generate (synthesize) arbitrary sentences Syntax Analysis: the reverse process n Given a sentence, obtain the derivation (or parse tree) that would generate this sentence based on some grammar

The Parsing Problem n Given the sequence of tokens of a source program (a

The Parsing Problem n Given the sequence of tokens of a source program (a sentence), we ask: is it syntactically valid, i. e. could it be generated from that grammar? n If it is not; find all syntax errors (or as many as possible) » for each error, produce a diagnostic message and recover quickly– i. e. do not “crash” n If it is; construct the corresponding parse tree (i. e. the derivation)

The Parsing Problem n A parser is a program that solves the parsing problem

The Parsing Problem n A parser is a program that solves the parsing problem n n n It is the second phase of a compiler Parse tree constructed by parser is used in the subsequent compilation phase: semantic analysis and code generation A recognizer is similar to a parser, except that it does not produce the parse tree- it only gives a Yes or No answer

Syntax Description vs. Syntax Analysis n n Parser design is nearly always directly based

Syntax Description vs. Syntax Analysis n n Parser design is nearly always directly based on a grammar description (BNF) of language syntax Two categories of parsers n n Top down - parse tree is constructed starting at root, using an order of a leftmost derivation Bottom up - parse tree is constructed starting at leaves, using an order of the reverse of a rightmost derivation

Top-down Parsing: LL Parsing n Top-down parsers are also called LL-parsers n n n

Top-down Parsing: LL Parsing n Top-down parsers are also called LL-parsers n n n First L: Scan input sentence from left to right Second L: Produce a left-most derivation Scans the token stream from left to right: For each input token, it decides which rule to use to expand the leftmost non-terminal in the current sentential form n This decision is based only on whether the leftmost terminal generated by the current leftmost non-terminal matches the current input token => one token of lookahead => LL parsers that use one token of lookahead are called LL(1) parsers n

LL(1) Parsing n n The LL Parser must expand <A> using some rule of

LL(1) Parsing n n The LL Parser must expand <A> using some rule of the form: <A> RHS Obviously, this decision must be based both on the grammar and the unparsed portion of the token stream A LL(1) parser determines the correct rule to use to expand a nonterminal <A> based only on the first token generated by <A> Definition: if X is the RHS of some grammar rule then FIRST(X) = {a | =>* a. Y} n where a : a terminal symbol n =>* : zero or more derivation steps

LL(1) Parsing n Example: n Grammar: <A> b<B> | c<B>b | a Suppose the

LL(1) Parsing n Example: n Grammar: <A> b<B> | c<B>b | a Suppose the input token currently being processed is c n If the current sentential form is x<A>a, then there are three possible rules we can apply, and the possible next sentential forms are then: xb<B>a, xc<B>ba, and xaa => But which rule to use next? => Choose the rule such that the leftmost terminal obtained when <A> is expanded matches our current input token “c” n

LL(1) Parsing n Example (cont. ) Consider now a slightly modified version of this

LL(1) Parsing n Example (cont. ) Consider now a slightly modified version of this grammar: <A> c<B> | c<B>b | a => this grammar actually cannot be used with a LL(1) parser, since it is sometimes impossible to decide on next rule based on the current token only n

Recursive-Descent Parsing n n This is a recursive implementation of LL(1) parsers Based directly

Recursive-Descent Parsing n n This is a recursive implementation of LL(1) parsers Based directly on BNF description of the language: n n n There is a subprogram for each non-terminal in the grammar, which can parse sentences generated by that non-terminal EBNF is ideally suited for being the basis for a recursive descent parser, because EBNF tends to minimize the number of non-terminals in grammar In a way, this is analogous to how the lexical analyzer is implemented directly from the state diagram of the corresponding FA

Recursive-Descent Parsing n n Suppose a non-terminal <A> has only one RHS The subprogram

Recursive-Descent Parsing n n Suppose a non-terminal <A> has only one RHS The subprogram for <A> is implemented as follows: n n n Scan the symbols of RHS one by one from left to right For each terminal symbol in the RHS, compare it with the next input token; if they match, then read the next input token and continue; else report an error For each non-terminal symbol in the RHS, call the subprogram associated with that non-terminal

Recursive-Descent Parsing n n Example: <A> a<B>cb The subprogram for non-terminal <A> does the

Recursive-Descent Parsing n n Example: <A> a<B>cb The subprogram for non-terminal <A> does the following: n n Check if next token == ‘a’ else error() Call subprogram for non-terminal <B> Check if next token == ‘c’ else error() Check if next token == ‘b’ else error()

Recursive-Descent Parsing n n Suppose a non-terminal <A> has more than one RHS The

Recursive-Descent Parsing n n Suppose a non-terminal <A> has more than one RHS The subprogram for <A> is implemented as follows: n n First find rule <A> X such that FIRST(X) == next token » If none of the RHS’s of <A> satisfies this, then report an error » If more than one RHS of <A> satisfies this, then the grammar is not appropriate for recursive descent parsing to begin with! Continue as in the previous case

Recursive-Descent Parsing n Assumptions: n n n We have a lexical analyzer subprogram Lex(),

Recursive-Descent Parsing n Assumptions: n n n We have a lexical analyzer subprogram Lex(), which whenever called, puts the next token code in a global variable next. Token At the beginning of every parsing subprogram, the next input token is in next. Token Obviously, the first subprogram called is the one associated with the start symbol of the grammar

Recursive-Descent Parsing n Example: consider the following grammar <A> a<B>cb | c<B> | ba<C>

Recursive-Descent Parsing n Example: consider the following grammar <A> a<B>cb | c<B> | ba<C> n n n Let ASub be subprogram associated with nonterminal <A> Let BSub be subprogram associated with nonterminal <B> Let CSub be subprogram associated with nonterminal <C>

Recursive-Descent Parsing Parser() { //this is the main function of the parser of this

Recursive-Descent Parsing Parser() { //this is the main function of the parser of this grammar Lex(); //get the first token in the source program ASub(); //call the subprogram for the start symbol of the grammar } ASub() { switch (next. Token) case ‘a’: Lex(); BSub(); if next. Token != ‘c’ then error(); Lex(); if next. Token != ‘b’ then error(); Lex(); break; case ‘c’: Lex(); BSub(); break; default: CSub(); }

Recursive-Descent Parsing An example in EBNF: <expr> <term> {(+ | -) <term>} <term> <factor>

Recursive-Descent Parsing An example in EBNF: <expr> <term> {(+ | -) <term>} <term> <factor> {(* | /) <factor>} <factor> id | ( <expr> ) n n n Let expr() be subprogram associated with non-terminal <expr> Let term() be subprogram associated with non-terminal <term> Let factor() be subprogram associated with nonterminal <factor>

Recursive-Descent Parsing /* Parse strings in the language generated by the rule: <expr> <term>

Recursive-Descent Parsing /* Parse strings in the language generated by the rule: <expr> <term> {(+ | -) <term>} */ void expr() { term(); /* Parse the first term */ while (next. Token == PLUS_CODE || next. Token == MINUS_CODE) { lex(); term(); } }

Recursive-Descent Parsing /* Parse strings in the language generated by the rule: <term> <factor>

Recursive-Descent Parsing /* Parse strings in the language generated by the rule: <term> <factor> {(* | /) <factor>} */ void term() { factor(); /* Parse first factor */ while (next. Token == MULT_CODE || next. Token == DIV_CODE) { lex(); factor(); } }

Recursive-Descent Parsing /* Parse strings in the language generated by the rule: <factor> id

Recursive-Descent Parsing /* Parse strings in the language generated by the rule: <factor> id | (<expr>) */ void factor() { if (next. Token) == ID_CODE) lex(); else if (next. Token == LEFT_PAREN_CODE) { lex(); expr(); if (next. Token == RIGHT_PAREN_CODE) lex(); else error(); } /* End of else if (next. Token ==. . . */ else error(); /* Neither RHS matches */ }

Recursive-Descent Parsing Limitations: n Recursive descent cannot be used with a grammar that: contains

Recursive-Descent Parsing Limitations: n Recursive descent cannot be used with a grammar that: contains a left recursive rule => an infinite loop! OR n is such that we cannot always choose correct RHS based on a single token of lookahead n n These two features are in fact problematic for LL(1) parsers in general

Left Recursion Problem Examples: n Direct recursion: <A> + <B> n Indirect recursion: <A>

Left Recursion Problem Examples: n Direct recursion: <A> + <B> n Indirect recursion: <A> <C>a <C> <A>b => In both cases, the subprogram for parsing <A> will call itself indefinitely (hence an infinite loop) n what about non-left recursion; does it always lead to an infinite loop?

Left Recursion Problem n n A grammar can always be converted into one without

Left Recursion Problem n n A grammar can always be converted into one without left recursion n EBNF is especially useful for this purpose Example: <expr> <term> | <expr> + <term> <expr> <term> {+ <term>}

Pairwise Disjointness n n Lack of pairwise disjointness: the inability to determine the correct

Pairwise Disjointness n n Lack of pairwise disjointness: the inability to determine the correct RHS (to expand a non-terminal) on the basis of a single token of lookahead To make sure a grammar does not have this problem, grammar must pass the pairwise disjointness test: n n n For each non-terminal A in the grammar, Let α 1, α 2, …, αm be the RHS’s for the m rules in which A appears on the LHS For any pair (αi, αj) we must have FIRST(αi) η FIRST(αj) = Ø

Pairwise Disjointness n n The pair disjointness problem can sometimes be solved via a

Pairwise Disjointness n n The pair disjointness problem can sometimes be solved via a technique called left factoring Example: <variable> identifier | identifier [<expression>] <variable> identifier <new> [<expression>] | ε <variable> identifier [ [<expression>] ]

Pairwise Disjointness n n Exercise: <S>a | b What is the language generated by

Pairwise Disjointness n n Exercise: <S>a | b What is the language generated by this grammar? For which input token streams will the subprogram for <S> run into an infinite loop? Does this grammar pass the pairwise disjointness test?

Pairwise Disjointness n n n Exercise: <A> a | <B>b | <C><B> c<C> |

Pairwise Disjointness n n n Exercise: <A> a | <B>b | <C><B> c<C> | c <C> b | a<B> Does this grammar pass the pairwise disjointness test? If not, re-write grammar to fix the problem.

LR Parsing n n LL(k) parsers predict which production to use, considering only the

LR Parsing n n LL(k) parsers predict which production to use, considering only the first k tokens. What if we could postpone choosing a production until we see all input tokens corresponding to the entire right-hand side of a production? LR(k): rightmost derivation, lookahead of k tokens How is rightmost derivation compatible with left-to -right parsing of input?

LR Parsing Top-down parsing n n n Start at most abstract level (grammar sentence)

LR Parsing Top-down parsing n n n Start at most abstract level (grammar sentence) and work down to most concrete level (tokens) Leftmost derivation LL(k), recursive-descent or predictive parsing Bottom-up parsing n n n Work from tokens up to sentences Rightmost derivation LR(k)

LR Parsing

LR Parsing

LR Parsing n n An LR(k) parser uses the contents of its stack and

LR Parsing n n An LR(k) parser uses the contents of its stack and the next k tokens of the input to decide which action to take. (In practice, k = 1. ) The parser knows when to shift and reduce by applying a DFA to the stack. Edges are labeled with terminals and non-terminals. Transitions indicate actions such as “shift and go to state n”, “reduce by rule k”, “accept”.

LR Parsing

LR Parsing