SEN2001 · Week 2 · Describing Syntax and Semantics I

Syntax: BNF, derivation, parse tree and ambiguity

Writing language rules with BNF and EBNF, producing leftmost derivations, drawing parse trees, proving ambiguity and reading precedence/associativity from a grammar. The part of the midterm that brings the most points.

syntaxsemanticslexemetokenBNFEBNFnonterminalterminalderivationleftmost derivationsentential formparse treeambiguous grammaroperator precedenceassociativitydangling else

This topic is the part that brings the most points on the midterm. The question types are fixed: write a leftmost derivation with a given grammar, draw a parse tree, prove that a grammar is ambiguous, read precedence and associativity from a grammar, convert EBNF to BNF. All of it is mechanical. Whoever knows the recipe does not lose.

Golden rules:

  1. Leftmost derivation: on each line replace only the leftmost single nonterminal.
  2. In the tree, what is lower is computed first. A high-precedence operator is deep in the tree.
  3. Left recursion gives left associativity, right recursion gives right associativity.
  4. Proof of ambiguity = one sentence + two different trees. “There are two alternatives in the rule” is not a proof.
  5. EBNF adds no expressive power. Every EBNF rule can be written in BNF.

Syntax and semantics

The definition of a language has two parts. Syntax is the form of expressions: which character sequences are valid programs? Semantics is what those expressions do. That while (x < 5) x++; is written correctly is syntax; that the loop increases x until it is 5 is semantics.

The smallest meaningful unit of a program is the lexeme: sum, =, 17, ;. Each lexeme belongs to a category, and this category is called a token. sum and total are different lexemes but the same token: IDENT.

LexemeToken
countIDENT
=ASSIGN_OP
countIDENT
+ADD_OP
1INT_LIT
;SEMICOLON

There are two ways to define a language. A recognizer takes a sentence and decides “does it belong to the language?”; the compiler’s syntax analyzer is this. A generator produces the sentences of the language; a grammar is this.

BNF

BNF (Backus-Naur Form) was used for ALGOL 58/60 in the late 1950s and has the same power as Chomsky’s class of context-free grammars. A rule (production) has this form:

<if_stmt> -> if <logic_expr> then <stmt>
           | if <logic_expr> then <stmt> else <stmt>
  • The left side (LHS) is a single nonterminal: an abstraction in angle brackets, like <if_stmt>.
  • The right side (RHS) is a sequence of terminals and nonterminals. Terminals are the actual symbols of the language: if, then, +.
  • | means an alternative for the same LHS.
  • The start symbol is usually the LHS of the first rule: <program>, <assign>.

BNF has no loops for repetition. Lists are written with recursion:

<ident_list> -> ident | ident , <ident_list>

This rule produces x, x , y, x , y , z …

Derivation

A derivation starts from the start symbol and applies rules to reach a sentence. Each intermediate line is a sentential form, and the last line, consisting only of terminals, is a sentence. Our standard grammar:

<assign> -> <id> = <expr>
<id>     -> A | B | C
<expr>   -> <expr> + <term> | <term>
<term>   -> <term> * <factor> | <factor>
<factor> -> ( <expr> ) | <id>

Leftmost derivation for A = B + C * A:

<assign> => <id> = <expr>
         => A = <expr>
         => A = <expr> + <term>
         => A = <term> + <term>
         => A = <factor> + <term>
         => A = <id> + <term>
         => A = B + <term>
         => A = B + <term> * <factor>
         => A = B + <factor> * <factor>
         => A = B + <id> * <factor>
         => A = B + C * <factor>
         => A = B + C * <id>
         => A = B + C * A

13 steps. How did you decide which rule to choose? Look at the top-level operator of the sentence. Here + is at the top (multiplication will be computed before it), so <expr> was first expanded as <expr> + <term>. In the sentence B = ( A + C ) * B, the only operator outside the parentheses is *, so you take the path <expr> => <term> => <term> * <factor>.

Rightmost derivation does the same job starting from the rightmost nonterminal. It corresponds to the same tree, only the order of expansion differs:

<assign> => <id> = <expr>
         => <id> = <expr> + <term>
         => <id> = <expr> + <factor>
         => <id> = <expr> + <id>
         => <id> = <expr> + C
         ...
         => A = B + C

PITFALL · one change per line. Expanding two nonterminals in one step, or taking a shortcut that is not in the rules (like <expr> => B; there are <term>, <factor>, <id> in between) loses points. Apply exactly one rule on each line.

Parse tree

The hierarchical form of a derivation. The root is the start symbol, each interior node is a nonterminal whose children are the RHS of the applied rule (left to right), and the leaves are terminals. For A = B + C * A:

<assign>
├── <id> ── A
├── =
└── <expr>
    ├── <expr> ── <term> ── <factor> ── <id> ── B
    ├── +
    └── <term>
        ├── <term> ── <factor> ── <id> ── C
        ├── *
        └── <factor> ── <id> ── A

Read the leaves left to right: A = B + C * A. If it matches, the tree is correct. The tree has another use: it shows the meaning. C * A is in a subtree, so it is computed first, and the result is added to B.

Ambiguity

A grammar is ambiguous if it can produce two different parse trees for at least one sentence. Equivalent definition: that sentence has two different leftmost derivations.

<E> -> <E> + <E> | <E> * <E> | x

For x + x * x there are two trees: + at the root (x + (x * x)) or * at the root ((x + x) * x). The difference shows when you plug in values: 2 + 3 * 4 is 14 in the first tree and 20 in the second. The compiler cannot know which code to generate; this is why ambiguity is bad.

Proof recipe:

  1. Choose a short sentence with two of the same operator (a - b - a) or two different operators (a + b * c).
  2. Draw the two trees, with a different operator at the root.
  3. Show that the leaves of both trees give the same sentence.

Removing ambiguity: precedence and associativity

Use a separate nonterminal for each precedence level. The low-precedence operator is near the start symbol (higher in the tree), and the high-precedence one is lower.

LevelNonterminalOperatorRule
lowest<expr>+ -<expr> -> <expr> + <term>
middle<term>* /<term> -> <term> * <factor>
highest<factor>parentheses, id<factor> -> ( <expr> )

Associativity comes from which side the recursion is on:

  • Left recursion (<expr> -> <expr> + <term>): the LHS repeats on the left of the RHS. The operation on the left stays deep. It associates to the left: 9 - 4 - 2 = (9 - 4) - 2 = 3.
  • Right recursion (<t> -> <f> ^ <t>): it associates to the right: 2 ^ 3 ^ 2 = 2 ^ (3 ^ 2) = 512. This is the right choice for exponentiation.
<e> -> <e> - <t> | <t>          - associates to the left, low precedence
<t> -> <f> ^ <t> | <f>          ^ associates to the right, high precedence
<f> -> NUM

With this grammar 10 - 2 ^ 3 - 1 = (10 - (2 ^ 3)) - 1 = 1.

PITFALL · the grammar imposes the rule. On exams a “reversed” grammar is sometimes given: <e> -> <e> * <t>, <t> -> <t> + <f>. Here + is lower, so it is computed first: 2 * 3 + 4 = 2 * (3 + 4) = 14. Apply the tree, not the rule you know from mathematics. Likewise, since <e> -> <t> - <e> is right recursive, 10 - 4 - 3 = 10 - (4 - 3) = 9.

Dangling else

<stmt> -> if c then <stmt> | if c then <stmt> else <stmt> | s

if c then if c then s else s has two trees: the else may belong to the inner or the outer if. The rule of Java, C, C++: the else attaches to the nearest unmatched if. To enforce it with a grammar, statements are split into <matched> (every if has its else) and <unmatched>; then the same sentence is left with a single tree. Python solves the problem in syntax with indentation, Ruby and Ada with end / end if, and Perl by requiring curly braces on every branch.

EBNF

EBNF (Extended BNF) does not increase power; it shortens rules and makes them readable.

EBNFMeaningBNF equivalent
[ X ]optional (0 or 1)two alternatives, with and without X
{ X }0 or more repetitionsa recursive helper rule
( a | b )choice inside the RHSseparate alternatives
EBNF:  <expr> -> <term> { (+ | -) <term> }
BNF:   <expr> -> <expr> + <term> | <expr> - <term> | <term>

EBNF:  <args> -> ( [ id { , id } ] )
BNF:   <args> -> ( ) | ( <list> )
       <list> -> id | id , <list>

When converting a repetition written with { } to BNF, choosing left recursion preserves the usual left associativity for + and -. { } does not mean “at least one”; zero repetitions are included. For “at least one”, write X { X }.

Writing grammars

Two patterns that come up often:

one or more a, then one or more b:
<S> -> <A> <B>
<A> -> a <A> | a
<B> -> b <B> | b

a^n b^2n, n >= 1 (counts tied):
<S> -> a <S> b b | a b b

If the counts are tied (“equal number”, “twice as many”), the two ends are produced together in the same rule. If they are independent, separate nonterminals are placed side by side. After writing, check with two or three sentences, and also think about whether a sentence that doesn’t belong to the language is generated.

Which sentences are generated?

<S> -> a <S> | <B>
<B> -> b <B> c | b c

First describe the language: zero or more a’s at the front, then an equal number (at least one) of b’s and c’s. aabc ✔, abbcc ✔, bc ✔, abcc ✘ (the number of b’s and c’s differ), aab ✘ (no c).

Common mistakes

WrongCorrect
Expanding the right nonterminal in a leftmost derivationOn each line the leftmost single nonterminal
A shortcut like <expr> => B<expr> => <term> => <factor> => <id> => B
“There are two alternatives, so it is ambiguous”Show a concrete sentence and two different trees
Drawing two trees with the same structureThe root operator or the grouping must differ
Forgetting terminals in the tree (=, +, parentheses)Every RHS symbol becomes a child
{ X } = at least oneZero or more
Applying mathematical precedence in a reversed grammarThe depth in the tree decides
Writing subtraction with right recursion10 - 4 - 3 = 9; use left recursion

Test yourself

Exam-style questions. Solve them on paper first, then reveal the answer.

Question 1medium

Using the grammar below, how many derivation steps (⇒) does the leftmost derivation of A = B + C * A take?

<assign> -> <id> = <expr>
<id>     -> A | B | C
<expr>   -> <expr> + <term> | <term>
<term>   -> <term> * <factor> | <factor>
<factor> -> ( <expr> ) | <id>
Show answer
13

Every interior node (nonterminal) in the tree is one ⇒ step. Leftmost derivation:

<assign> => <id> = <expr> => A = <expr>
=> A = <expr> + <term> => A = <term> + <term>
=> A = <factor> + <term> => A = <id> + <term> => A = B + <term>
=> A = B + <term> * <factor> => A = B + <factor> * <factor>
=> A = B + <id> * <factor> => A = B + C * <factor>
=> A = B + C * <id> => A = B + C * A
13 arrows in total. + is at the top because * is produced deeper (at the <term> level).

Question 2medium

Give the first four sentential forms of the leftmost derivation of B = ( A + C ) * B with the same grammar.

<assign> -> <id> = <expr>
<id>     -> A | B | C
<expr>   -> <expr> + <term> | <term>
<term>   -> <term> * <factor> | <factor>
<factor> -> ( <expr> ) | <id>
Show answer
<assign> ⇒ <id> = <expr> ⇒ B = <expr> ⇒ B = <term>

The top operation is * (the only operator outside the parentheses). So <expr> must first become a single <term>; the + rule is not chosen here. The rest: B = <term> * <factor>, B = <factor> * <factor>, B = ( <expr> ) * <factor> ... It takes 16 steps in total.

Question 3easy

Which of the following sentences are generated by the grammar? (a) aabc (b) abbcc (c) abcc (d) bc (e) aab

<S> -> a <S> | <B>
<B> -> b <B> c | b c
Show answer
(a), (b), (d)

<S> puts as many a's at the front as it wants (including zero). <B> produces an equal number of b's and c's, at least one pair. The language: ambncna^m b^n c^n, m≥0m \ge 0, n≥1n \ge 1.
(a) aa + bc ✔. (b) a + bbcc ✔. (c) 1 b, 2 c: not equal ✘. (d) bc ✔. (e) no c ✘.

Question 4medium

How many distinct parse trees does the sentence x + x * x have in this grammar? Is the grammar ambiguous?

<E> -> <E> + <E> | <E> * <E> | x
Show answer
2; yes, ambiguous

Tree 1: + at the root, the right child is x * x (that is, x + (x * x)). Tree 2: * at the root, the left child is x + x (that is, (x + x) * x). Because a single sentence has two different trees, the grammar is ambiguous. With numbers: 2 + 3 * 4 gives 14 in the first tree and 20 in the second.

Question 5hard

In the same ambiguous grammar <E> -> <E> + <E> | <E> * <E> | x, how many parse trees does x + x + x + x have?

Show answer
5

Which of the three + operators goes to the root is free. 1st + at the root: 2 trees for the x + x + x on the right. 2nd + at the root: 1 on the left, 1 on the right → 1. 3rd + at the root: 2 trees on the left. Total 2 + 1 + 2 = 5 (the Catalan number C3C_3).

Question 6medium

Using the grammar below, evaluate 2 ^ 3 ^ 2 and 9 - 4 - 2 according to their parse trees. What is the associativity of ^ and of -?

<e> -> <e> - <t> | <t>
<t> -> <f> ^ <t> | <f>
<f> -> NUM
Show answer
512 and 3; ^ is right associative, - is left associative

<t> -> <f> ^ <t> is right recursive: the ^ on the right stays deeper in the tree and is computed first: 2 ^ (3 ^ 2) = 2 ^ 9 = 512.<e> -> <e> - <t> is left recursive: the subtraction on the left is deep: (9 - 4) - 2 = 3.
Also, ^ is at the <t> level, below -, so it has higher precedence.

Question 7hard

In the grammar below, what value does the parse tree give for 2 * 3 + 4?

<e> -> <e> * <t> | <t>
<t> -> <t> + <f> | <f>
<f> -> NUM
Show answer
14

This grammar is the opposite of the usual: + is at the <t> level, lower in the tree, so it is computed first. The tree builds the structure 2 * (3 + 4): 2 * 7 = 14. The grammar imposes its own structure, not the rule from mathematics.

Question 8medium

With the right-recursive grammar below, what is the value of 10 - 4 - 3?

<e> -> <t> - <e> | <t>
<t> -> NUM
Show answer
9

Right recursion gives right associativity: 10 - (4 - 3) = 10 - 1 = 9. Not (10 - 4) - 3 = 3 as in mathematics. To model subtraction correctly, <e> -> <e> - <t> | <t> (left recursion) should be written.

Question 9easy

Convert the EBNF rule to BNF: <args> -> ( [ id { , id } ] )

Show answer
<args> -> ( ) | ( <list> ) ; <list> -> id | id , <list>

[ ... ] is optional: the inside of the parentheses is empty or filled, two alternatives. { , id } is zero or more repetitions: written with a recursive helper nonterminal <list>. The sentences produced: ( ), ( id ), ( id , id ), ...

Question 10medium

Write a BNF grammar for the language anb2na^n b^{2n}, n≥1n \ge 1 (e.g. abb, aabbbb).

Show answer
<S> -> a <S> b b | a b b

Because the counts are tied, the a's and b's must be added together in the same rule: each application puts one a on the left and two b's on the right. The base is a b b, so it starts from n = 1. Check: aabbbb: S ⇒ aSbb ⇒ aabbbb ✔. Writing two independent parts like <A> <B> does not equalize the counts.

Question 11medium

How many parse trees does if c then if c then s else s have in this grammar, and which language rule resolves the problem in Java?

<stmt> -> if c then <stmt> | if c then <stmt> else <stmt> | s
Show answer
2; else matches the nearest unmatched if

Tree 1: the else belongs to the inner if. Tree 2: the else belongs to the outer if. Two trees = ambiguous (dangling else). Java, C, C++: the else attaches to the nearest unmatched if. When this is enforced in the grammar with a matched/unmatched distinction, the same sentence is left with a single tree.

Question 12hard

Give the second and third sentential forms of the rightmost derivation of A = B + C in the assignment grammar (expr/term/factor).

Show answer
<id> = <expr> + <term> ; <id> = <expr> + <factor>

In rightmost, at each step the rightmost nonterminal is expanded. <assign> ⇒ <id> = <expr> ⇒ <id> = <expr> + <term> ⇒ <id> = <expr> + <factor> ⇒ <id> = <expr> + <id> ⇒ <id> = <expr> + C ⇒ ... The leftmost <id> becomes A last, in the final step.

Verification: All derivations, tree counts and values computed from grammars were checked with node: verify/SEN2001/lib/grammar.mjs counts all parse trees, writes leftmost/rightmost derivations and computes values from trees (verify/SEN2001/02.mjs). The EBNF/BNF equivalence was tested by brute force with all strings up to length 7.

Review card

syntax = form, semantics = meaning. lexeme: sum, + token: IDENT, ADD_OP BNF: LHS -> RHS, | alternative, list = recursion: <L> -> id | id , <L> leftmost: at each step the LEFTMOST single nonterminal. number of steps = number of interior nodes intermediate line = sentential form, last line (terminals only) = sentence parse tree: root = start symbol, leaves left to right = sentence ambiguous = ONE sentence + TWO different trees (or two different leftmost derivations) candidate sentences: a - b - a , a + b * c precedence: the lower one is computed first. low precedence near the start (<expr>) E -> E + T | T ; T -> T * F | F ; F -> ( E ) | id left recursion = left associativity ; right recursion = right associativity (**, ^) EBNF: [X] = 0/1 (two alternatives) {X} = 0+ (recursion) (a|b) = choice EBNF adds no power, it only abbreviates dangling else: nearest unmatched if in Java/C