Compiler Design

105601
Back to Compiler Design

Module 3: Semantic Analysis, Symbol Table, Run-time environment

  1. Q1c. A shift reduce parser carries out the actions specified within braces immediately after reducing with the corresponding rule of grammar: S \to xxW {print "1"}, S \to y {print "2"}, S \to Sz {print "3"} What is the translation of xxxxyzz using the syntax directed translation scheme described by the above rules? (i) 23131 (ii) 11233 (iii) 11231 (iv) 3321120232m

    Module 3: Semantic Analysis, Symbol Table, Run-time environment

    A shift reduce parser carries out the actions specified within braces immediately after reducing with the corresponding rule of grammar:
    S→xxWS \to xxW {print "1"}, S→yS \to y {print "2"}, S→SzS \to Sz {print "3"}
    What is the translation of xxxxyzzxxxxyzz using the syntax directed translation scheme described by the above rules?
    (i) 23131
    (ii) 11233
    (iii) 11231
    (iv) 33211

    View this question on its own page →
    Worked Solution

    Answer: (iii) 11231

    Reasoning (shift–reduce with actions executed on reduce)

    Grammar:
    S→xxW {print 1},S→y {print 2},S→Sz {print 3}S \to xxW \ \{\text{print }1\}, \qquad S \to y \ \{\text{print }2\}, \qquad S \to Sz \ \{\text{print }3\}

    Input string: x x x x y z z.

    Reducing the string from the input up to SS (the actions fire in the order the reductions are performed):

    Step Stack contents (top right) Action Printed
    1 x x collected, W → … y reduce xxW → S 1
    2 next x x + … reduce xxW → S 1
    3 y reduce y → S 2
    4 S + z reduce Sz → S 3
    5 S + z then top combine reduce (top-level xxW/final) 1

    Collecting the prints in the order the reductions happen:

    11231\boxed{11231}

    Hence the correct choice is (iii).

  2. Q1e. Consider a symbol table implemented using hashing with table size 10. For key 27, using h(k) = k \bmod 10, the initial position is: (i) 5 (ii) 7 (iii) 6 (iv) 820262m

    Module 3: Semantic Analysis, Symbol Table, Run-time environment

    Consider a symbol table implemented using hashing with table size 1010. For key 2727, using h(k)=k mod 10h(k) = k \bmod 10, the initial position is:
    (i) 55
    (ii) 77
    (iii) 66
    (iv) 88

    View this question on its own page →
    Worked Solution

    Answer: (ii) 7

    Calculation

    h(27)=27 mod 10=7h(27) = 27 \bmod 10 = 7

    because 27=2×10+727 = 2 \times 10 + 7. The key 2727 is initially placed at index 77 of the hash table.

    Note: if slot 77 is already occupied (a collision, e.g. key 1717), it is resolved by chaining (linked list at slot 7) or open addressing (linear probing to slot 8, 9, ...).

    (ii) 7\boxed{\text{(ii) } 7}

  3. Q1f. Under static scoping, the meaning of a variable reference is determined primarily by: (i) Runtime call sequence (ii) CPU register contents (iii) Heap location (iv) Lexical program structure20262m

    Module 3: Semantic Analysis, Symbol Table, Run-time environment

    Under static scoping, the meaning of a variable reference is determined primarily by:
    (i) Runtime call sequence
    (ii) CPU register contents
    (iii) Heap location
    (iv) Lexical program structure

    View this question on its own page →
    Worked Solution

    Answer: (iv) Lexical program structure

    Explanation

    In static (lexical) scoping, a variable reference is bound to the declaration found by looking at the textual nesting of blocks/procedures in the source code: first the innermost enclosing block, then the next one outward, and so on.

    • The binding can be decided at compile time, without running the program.
    • The call sequence at run time (option i) decides the binding only in dynamic scoping.
    • Registers (ii) and heap location (iii) have nothing to do with name resolution.
    int x = 10;
    void f() { printf("%d", x); }   // x refers to the global x
    void g() { int x = 20; f(); }   // still prints 10 under static scoping
    

    (iv)\boxed{\text{(iv)}}

  4. Q1h. Synthesized attributed are calculated (i) From the values of attributes of the children of the node (ii) From the values of attributes of the parent of the node (iii) From the values of attributes of the siblings of the node (iv) None of these20232m

    Module 3: Semantic Analysis, Symbol Table, Run-time environment

    Synthesized attributed are calculated
    (i) From the values of attributes of the children of the node
    (ii) From the values of attributes of the parent of the node
    (iii) From the values of attributes of the siblings of the node
    (iv) None of these

    View this question on its own page →
    Worked Solution

    Answer: (iii) children of the node (option (i))

    Explanation

    Attributes in a syntax-directed definition are classified by the direction of their dependency:

    1. Synthesized attribute (S-attribute): computed from the attribute values of the children of a node. Its value at a node is a function of the values at its descendants, so the tree is evaluated bottom-up. A classic example is E.val = E1.val * T.val.

    2. Inherited attribute (I-attribute): computed from the parent and/or siblings — flows top-down or sideways.

    Since the question is about synthesized attributes, they are calculated:

    from the values of the attributes of the children of the node → option (i)\boxed{\text{from the values of the attributes of the children of the node → option (i)}}

  5. Q2b. What is an activation record? Explain how they are used to access various local and global variables.20237m

    Module 3: Semantic Analysis, Symbol Table, Run-time environment

    What is an activation record? Explain how they are used to access various local and global variables.

    View this question on its own page →
    Worked Solution

    What is an activation record? How are local/global variables accessed?

    Activation record (AR) — also called a stack frame — is a block of memory pushed onto the run-time stack for each call of a procedure/function. It holds everything needed to return from the call and to run the callee. When the procedure returns, its record is popped (unless it is kept for certain closures).

    Typical layout (higher addresses → lower):

       [ actual parameters ]
       [ return value (if any) ]
       [ access link / control link (saved frame pointer) ]
       [ return address (saved PC) ]
       [ saved machine status ]
       [ local data (locals, temporaries) ]
       [ saved top-of-stack pointer ]
    

    Accessing local variables:

    • While a procedure is active, the runtime keeps a base pointer (frame pointer) that names the bottom of the current AR.
    • Every local variable is stored at a fixed offset from the start of the AR, an offset chosen at compile time.
    • So x is accessed as base + offset(x) — no runtime search: just load [base+off].

    Accessing global (non-local) variables:

    • Globals live in a global/static data area (not on the stack in most compilers). Each global has a fixed compile-time address/offset from a global base register/area, so access is again just an absolute/relative memory load.
    • Some compilers (old Pascal-style) used an access link/display to reach enclosing non-locals: each frame holds a pointer to the frame of the statically enclosing procedure; accessing a nonlocal of depth dd follows dd links. (Dynamic scoping instead uses a dynamic link up the call chain.)

    Example (C):

    int g = 10;                 /* global */
    int f(int p) { int l; l = g + p; return l; }
    

    At run time while inside f:

    • p and l are at FP + offset_p and FP + offset_l (local, in the AR).
    • g is at its fixed global-area address (single load).

    Conclusion

    • Locals → offsets within the current activation record on the run-time stack (FP + offset).
    • Globals → fixed addresses in a global data region, known at compile time.
  6. Q4a. Given the Syntax-Directed Definition below with the synthesized attribute val. Draw the annotated parse tree for the expression (3+4) * (5+6) L \to E, L.val = E.val E \to T, E.val = T.val E \to E_1 + T, E.val = E_1.val + T.val T \to F, T.val = F.val T \to T_1 * F, T.val = T_1.val * F.val F \to (E), F.val = E.val F \to digit, F.val = digit.lexval20237m

    Module 3: Semantic Analysis, Symbol Table, Run-time environment

    Given the Syntax-Directed Definition below with the synthesized attribute val. Draw the annotated parse tree for the expression (3+4)∗(5+6)(3+4) * (5+6)

    L→EL \to E, L.val=E.valL.val = E.val
    E→TE \to T, E.val=T.valE.val = T.val
    E→E1+TE \to E_1 + T, E.val=E1.val+T.valE.val = E_1.val + T.val
    T→FT \to F, T.val=F.valT.val = F.val
    T→T1∗FT \to T_1 * F, T.val=T1.val∗F.valT.val = T_1.val * F.val
    F→(E)F \to (E), F.val=E.valF.val = E.val
    F→digitF \to digit, F.val=digit.lexvalF.val = digit.lexval

    View this question on its own page →
    Worked Solution

    Annotated parse tree for (3+4)×(5+6)(3+4)\times(5+6)

    Syntax-directed definition (synthesized val):

    • L→EL \to E,  L.val=E.val\ L.val = E.val
    • E→TE \to T, E.val=T.valE.val = T.val
    • E→E1+TE \to E_1+T, E.val=E1.val+T.valE.val = E_1.val + T.val
    • T→FT \to F, T.val=F.valT.val = F.val
    • T→T1∗FT \to T_1*F, T.val=T1.val×F.valT.val = T_1.val \times F.val
    • F→(E)F \to (E), F.val=E.valF.val = E.val
    • F→digitF \to \text{digit}, F.val=digit.lexvalF.val = \text{digit.lexval}

    Parse tree with every node's val annotated (evaluated bottom-up):

                  L ─ E.val = 77                    (L → E, L.val = E.val)
                      |
                      E.val = 77                    (E → T,   E.val = T.val)
                      |
                      T.val = 77                    (T → T1 * F,  T.val = 7 * 11)
                     /         \
            T1.val = 7          F.val = 11
                 |                    |
                 F.val = 7           F → ( E )  .val = 11
                 F → ( E ) .val=7         |
                    |                    E → E1 + T  .val = 5 + 6 = 11
                    E.val = 7            /        \
                  E1.val = 3          T.val = 3   ?  ... (details below)
    

    To avoid a confusing drawing, here is the node-by-node annotated tree (the expected exam answer):

    L → E            : val = 77
    E → T            : val = 77
    T → T1 * F       : val = T1.val * F.val = 7 * 11 = 77
    
    Left subtree  (3+4):
      T1 → F                          : val = 7
      F → (E)                         : val = 7      [parenthesized]
         E → E1 + T
            E1 → T → F → digit(3)     : val = 3
            T  → F → digit(4)         : val = 4
            E.val = 3 + 4 = 7
      ⇒ T1 (left factor) val = 7
    
    Right subtree  (5+6):
      F → ( E )                       : val = 11     [parenthesized]
         E → E1 + T
            E1 → T → F → digit(5)     : val = 5
            T → F → digit(6)          : val = 6
            E.val = 5 + 6 = 11
      ⇒ F (right factor) val = 11
    
    Root:
      T.val = 7 * 11 = 77,  E.val = 77,  L.val = 77
    

    Each digit is a leaf via F→digitF\to\text{digit}, with F.val=digit.lexvalF.val = \text{digit.lexval} (3, 4, 5, 6).

    E.val=7×11=77\boxed{E.val = 7 \times 11 = 77}

  7. Q5a. Explain the structure of an activation record and its role during procedure execution.20267m

    Module 3: Semantic Analysis, Symbol Table, Run-time environment

    Explain the structure of an activation record and its role during procedure execution.

    View this question on its own page →
    Worked Solution

    Activation record (stack frame)

    An activation record is the block of memory pushed on the run-time stack each time a procedure is called. It holds everything one activation of the procedure needs. Records are pushed on a call and popped on return, so the stack mirrors the chain of active calls.

    Structure of an activation record

    SVGVector Architecture Diagram (SVG)
    lower address Returned value Actual parameters Control link (dynamic link) Access link (static link) Saved machine status Local variables Temporaries higher address <- fp
    Field Purpose
    Returned value Space where the callee stores its result for the caller
    Actual parameters Arguments supplied by the caller
    Control (dynamic) link Points to the caller's activation record, so the stack can be unwound on return
    Access (static) link Points to the record of the lexically enclosing procedure, used to reach non-local variables
    Saved machine status Return address and the register contents of the caller, restored on return
    Local variables Data declared inside the procedure
    Temporaries Intermediate values of expression evaluation

    Role during procedure execution

    1. Call sequence. The caller evaluates and stores the actual parameters and the return address; the callee saves registers, sets the frame pointer fp, and allocates space for locals and temporaries.
    2. Execution. Local variables are accessed at a fixed offset from fp. Non-local variables are reached by following access links (static scope) the required number of levels, then adding the offset. Global variables live in static memory with a fixed address.
    3. Return sequence. The callee stores the return value, restores the saved registers and fp, pops its record, and jumps to the return address.
    4. Recursion. Every call gets its own record, so each recursive activation has its own copy of locals and parameters.

    Accessing variables: for a local x use [fp + offset_x]. For a non-local y declared kk nesting levels up, follow the access link kk times to that procedure's record, then use [record + offset_y].

  8. Q5b. Explain how type checking and error reporting are performed in compiler. Draw syntax tree and DAG for the statement: a = (a * b + c) \uparrow (b + c) * b + c20237m

    Module 3: Semantic Analysis, Symbol Table, Run-time environment

    Explain how type checking and error reporting are performed in compiler. Draw syntax tree and DAG for the statement:
    a=(a∗b+c)↑(b+c)∗b+ca = (a * b + c) \uparrow (b + c) * b + c

    View this question on its own page →
    Worked Solution

    Type checking, error reporting, and the syntax tree / DAG for
    a=(a∗b+c)↑(b+c)∗b+ca = (a*b + c) \uparrow (b+c)*b + c

    Part 1 — Type checking

    • Type checking verifies each operator's operands have the expected type and computes/infers the result type while traversing the syntax tree.
    • It is typically performed in the semantic-analysis phase using a syntax-directed definition where each expression node carries a synthesized type attribute, e.g. TE=f(TE1,TT)T_E = f(T_{E_1}, T_T) for E→E1+TE \to E_1+T.
    • Type synthesis: int + int → int, float op … → float; conversions (e.g. int → float) may be inserted automatically as a unary node inttofloat (type coercion).
    • Checking can be static (compile time) or dynamic (run time).

    Part 2 — Error reporting

    • Each phase reports its errors to a central error handler: lexical (illegal token), syntactic (parse errors, e.g. mismatched brackets), semantic (type mismatch, undeclared/unused variable).
    • The parser uses error-recovery strategies (panic mode, error productions, phrase-level recovery) so it can report more than one syntax error in a single run.
    • Semantic errors are reported with line number, offending token, and message; code generation is attempted only when there are no fatal errors.

    Part 3 — Syntax tree for the statement

    a = (a*b + c) ↑ (b + c)*b + c

    Precedence order: ↑ > * > +. So the RHS reads as:

    (a∗b+c) ↑ ((b+c)∗b) + c(a*b + c)\ \uparrow\ ((b+c)*b)\ +\ c

    Syntax tree (AST):

                      =
                     / \
                    a   +
                       / \
                    ↑     c
                   / \
            (a*b + c)  ( (b+c) * b )
              /   \         /  \
          (a*b)  + c     (b+c)  b
          / \           /  \
         a   b         b    c
    
     Equivalently, using operator-labelled nodes:
    
                      =
                     / \
                    a   +
                       / \
                 (↑)      c
                 /   \
              (a*b+c)   * (mul)
                 |        / \
              (a*b)+c    (b+c)  b
              /  \      / \
            (a*b) c   b   c
            /   \
           a     b
    

    DAG (Directed Acyclic Graph)

    In the DAG, each unique subexpression appears exactly once; repeated uses share the same node.

    The statement contains these distinct nodes (reading bottom-up):

    1. a*b — node * with leaves (a, b)
    2. (a*b)+c — node + over (1) and leaf c
    3. b+c — node + with leaves (b, c)
    4. (b+c)*b — node * over (3) and leaf b
    5. ↑ — node ↑ over (2) and (4)
    6. RHS + c — node + over (5) and leaf c
    7. a = RHS — node = with leaves (a, 6)
                    =
                   / \
                  a   +                     ← (6)
                     / \
                  ↑     c
                 / \
            (a*b)+c   (b+c)*b               ← nodes (2), (4)
              / \         / \
           (a*b) c     (b+c) b              ← nodes (1), (3)
            / \          / \
           a   b        b   c
    

    Note the leaf b and c are each single nodes shared by all their uses (this is what makes it a DAG rather than a tree).

  9. Q5b. Discuss the structure of a symbol table and the information stored for each identifier.20267m

    Module 3: Semantic Analysis, Symbol Table, Run-time environment

    Discuss the structure of a symbol table and the information stored for each identifier.

    View this question on its own page →
    Worked Solution

    Symbol table

    A symbol table is a data structure used by the compiler to store information about every identifier (variables, constants, functions, labels, types, parameters) found in the source program. It is created during lexical/syntax analysis and used by every later phase.

    Structure

    Each entry is a record, and the table is organized for fast lookup and insertion.

    MermaidDiagram
    Rendering diagram…

    Common organizations:

    Implementation Lookup Remarks
    Unordered linear list O(n)O(n) Simple, only for tiny programs
    Ordered list / binary search tree O(log⁡n)O(\log n) Slower insertion
    Hash table O(1)O(1) average Most common; collisions resolved by chaining

    Handling scope: one table per scope, chained to its enclosing scope (or a single stack-like table). Entering a block creates a new table; leaving it discards that table. Lookup searches the innermost scope first, then outwards.

    Operations: insert(name, attributes), lookup(name), delete / exit scope, modify attributes.

    Information stored for each identifier

    Attribute Meaning
    Name The lexeme (or an index into a string table)
    Kind / category Variable, constant, function, procedure, parameter, label, type name
    Data type int, float, char, array, struct, pointer, ...
    Size Bytes needed (for arrays: element type and dimensions/bounds)
    Scope / nesting level Block or procedure where the name is visible
    Storage location Address or offset in the activation record (or static address)
    For functions Number, order and types of parameters, return type, parameter-passing mode
    For records/classes Fields, their types and offsets
    Line numbers Declaration and usage lines, for error messages and cross-reference
    Other flags Initialized or not, constant value, access mode

    Example: for float rate; declared in main, the entry is
    <rate, variable, float, 4 bytes, level 1, offset 8>.

    Use by the phases: the scanner inserts names, the parser and semantic analyzer add type and scope information and check for undeclared or redeclared names, type checking reads types, and the code generator reads sizes and offsets.

  10. Q7a. Differentiate between S-attribute SDT and L-attribute SDT with suitable examples.20237m

    Module 3: Semantic Analysis, Symbol Table, Run-time environment

    Differentiate between S-attribute SDT and L-attribute SDT with suitable examples.

    View this question on its own page →
    Worked Solution

    S-attribute SDT vs L-attribute SDT

    S-Attribute (Synthesized) SDTs

    • An SDT is S-attributed ("synthesized") if every attribute is a synthesized attribute — computed from the children of a nonterminal.
    • All the evaluation is bottom-up.
    • An S-attributed SDT can be evaluated during a bottom-up parse — the semantic action is placed at the right end of the production: A → α { do action }.
    • Supported: only bottom-up based LL/LR? Precisely: LR parser can implement any S-attributed SDD without changing the parsing stack (just attach the attributes value with each stack symbol).

    L-Attributed SDTs

    • An SDT is L-attributed when:
      1. each attribute is either synthesized, or inherited, and
      2. inherited attributes depend only on: attributes of the left siblings, the parent, and attributes of the symbols themselves (i.e. dependencies can flow from left to right, and from parent → children).
    • Condition: In the production A→X1X2…XnA \to X_1 X_2 \dots X_n, an inherited attribute of XiX_i can only depend on:
      • attributes of X1,X2,…,Xi−1X_1,X_2,\dots, X_{i-1} (the symbols to its left), and
      • inherited attributes of AA (the parent).
    • Evaluation is basically top-down / left-to-right — works with LL / predictive parsers too, and can be implemented in a top-down or LR parser with suitable actions.

    Key differences:

    Point S-attributed L-attributed
    Attributes All synthesized synthesized + inherited (with L- constraint)
    Evaluation pass bottom–up depth-first left-to-right following structure
    Parser suitable any LR / bottom-up LL / top-down (recursive-descent; LR also if aug.)
    Action placement can move to the end of RHS must keep in position (actions constrained)
    Which is larger — L is a superset of S

    Every S-attributed definition is also L-attributed, but not vice versa.

    Example

    S-attribute example: grammar with val
    E→E1+TE.val=E1.val+T.valE \to E_1+T \quad E.val=E_1.val+T.val
    All attributes synthesized ⇒ S-attributed (also L).

    L-attribute (non-S) example: an inherited attribute such as in a C float conversion
    Y→X1X2Y.inh=... ; X1.inh=...,X2.inh=f(Y.inh,X1.att)Y \to X_1 X_2 \quad Y.inh=...\,;\ X_1.inh=..., X_2.inh=f(Y.inh, X_1.att)
    For instance, declaring arrays:
    D → T L, L → id , L_1; L.inh = T.type; L1.inh = L.inh. The inherited attribute inh must always be computed before the symbol is parsed — that's the left-to-right constraint.

    Summary: an S-SDT = simply synthesized attributes; evaluate bottom-up. L-SDT = a wider class, allows inherited attributes under the restriction that they use only attributes from the left (or inherited from context above) — evaluated in a depth-first left-first traversal. Since L is a superset of S, any S-scheme is a special case of L.

  11. Budget App•POCKETLY

    Pocketly on Android

    Log daily food, travel, and hostel expenses instantly. Set monthly spending limits and see where your money goes without complex spreadsheets.

    Install Pocketly
  12. Q8b. Consider the following grammar: E \to E + T \mid T T \to T * F \mid F F \to \text{digit} Define suitable synthesized attributes to evaluate an arithmetic expression. Construct the annotated syntax tree and evaluate the following expression: 3 + 5 * 220267m

    Module 3: Semantic Analysis, Symbol Table, Run-time environment

    Consider the following grammar:
    E→E+T∣TE \to E + T \mid T
    T→T∗F∣FT \to T * F \mid F
    F→digitF \to \text{digit}
    Define suitable synthesized attributes to evaluate an arithmetic expression. Construct the annotated syntax tree and evaluate the following expression:
    3+5∗23 + 5 * 2

    View this question on its own page →
    Worked Solution

    Grammar: E→E+T∣TE \to E + T \mid T,   T→T∗F∣F\;T \to T * F \mid F,   F→digit\;F \to \text{digit}

    Synthesized attribute: give every non-terminal EE, TT, FF an attribute val (its numeric value). The terminal digit has the attribute lexval supplied by the lexical analyzer.

    Syntax-directed definition

    Production Semantic rule
    E→E1+TE \to E_1 + T E.val=E1.val+T.valE.val = E_1.val + T.val
    E→TE \to T E.val=T.valE.val = T.val
    T→T1∗FT \to T_1 * F T.val=T1.val×F.valT.val = T_1.val \times F.val
    T→FT \to F T.val=F.valT.val = F.val
    F→digitF \to \text{digit} F.val=digit.lexvalF.val = \text{digit}.lexval

    Every attribute is computed from the attributes of the children, so val is synthesized and the definition is S-attributed (it can be evaluated during bottom-up parsing).

    Annotated parse tree for 3+5∗23 + 5 * 2

    Because of the grammar's structure, * sits lower in the tree than +, so 5 * 2 is evaluated first.

    MermaidDiagram
    Rendering diagram…

    Evaluation (bottom-up, post-order)

    Step Node Rule used Value
    1 FF (digit 3) F.val=digit.lexvalF.val = \text{digit}.lexval 3
    2 TT over that FF T.val=F.valT.val = F.val 3
    3 EE over that TT E.val=T.valE.val = T.val 3
    4 FF (digit 5) F.val=digit.lexvalF.val = \text{digit}.lexval 5
    5 TT over that FF T.val=F.valT.val = F.val 5
    6 FF (digit 2) F.val=digit.lexvalF.val = \text{digit}.lexval 2
    7 T→T∗FT \to T * F T.val=5×2T.val = 5 \times 2 10
    8 E→E+TE \to E + T E.val=3+10E.val = 3 + 10 13
    3+5∗2=13\boxed{3 + 5 * 2 = 13}