Copyright R.A. van Engelen, FSU Department of Computer Science, 2000

 

Semantic Analysis

In this set of notes you will learn about:
  • Static semantics
  • Dynamic semantics
  • Attribute grammars
  • Abstract syntax trees
  • Putting theory into practice:
    • A Java interpreter of simple expressions
    • A Java translator of simple expressions to Lisp
Note: These slides cover Chapter 4 of the textbook upto and including Section 4.3

Static and Dynamic Semantics

  • Syntax concerns the form of a valid program, while semantics concerns its meaning
  • Static semanticDefine this term rules are enforced by a compiler at compile time
    • Implemented in semantic analysis phase of the compiler
    • Context-free grammars are not powerful enough to describe certain rules, such as checking variable declaration with variable use
    • Examples: Type checking; Identifiers are used in appropriate context; Check subroutine call arguments; Check labels
  • Dynamic semanticDefine this term rules are enforced by the compiler by generating code to perform the checks
    • Examples: Array subscript values are within bounds; Arithmetic errors; Pointers are not dereferenced unless pointing to valid object; A variable is used but hasn't been initialized
    • Some languages (Euclid, Eiffel) allow programmers to add explicit dynamic semantic checks in the form of assertions, e.g.
    • assert denominator not= 0
    • When a check fails at run time, an exceptionDefine this term is raised

 

Attribute Grammars

  • An attribute grammarDefine this termlinks syntax with semantics
    • Every grammar production has a semantic ruleDefine this term with actions to modify attributesDefine this term of (non)terminals
    • A (non)terminal may have a number of attributesDefine this term to hold semantic information about the (non)terminal
    • General form:
    • Production Semantic rule
      <A> -> <B> <C>
      A.a := ...; B.a := ...; C.a := ...
  • Used by a compiler to enforce static semantics and/or to produce an abstract syntax tree while parsing token stream
  • Can also be used to build simple interpreters

 

Example Attribute Grammar

Example attribute grammar for evaluating simple expressions
Production Semantic rule
<E1> -> <E2> + <T>
<E1> -> <E2> - <T>
<E> -> <T>
<T1> -> <T2> * <F>
<T1> -> <T2> / <F>
<T> -> <F>
<F1> -> - <F2>
<F> -> ( <E> )
<F> -> unsigned_int
E1.val := E2.val + T.val
E1.val := E2.val - T.val
E.val := T.val
T1.val := T2.val * F.val
T1.val := T2.val / F.val
T.val := F.val
F1.val := -F2.val
F.val := E.val
F.val := unsigned_int.val
  • The val attribute of a (non)terminal holds the subtotal value of the subexpression described by the (non)terminal
  • Nonterminals are indexed in the attribute grammar (e.g. <T1>) to distinghuish multiple occurrences of the nonterminal in a production

 

Decorated Parse Trees

  • Parse trees are decorated with the attribute values
    Example decorated parse tree of (1+3)*2 showing val attribute values
  • The val attribute of a node holds the subtotal value of the subexpression down the node

 

Synthesized and Inherited Attributes

  • Synthesized attributes hold values used by the parent node and flow upwards
     
    Production Semantic rule
    <E1> -> <E2> + <T>
    E1.val := E2.val + T.val
    Synthesized attribute flow
  • Inherted attributes are defined by the parent node and flow downwards
     
    Production Semantic rule
    <E> -> <T> <TT>
    <TT1> -> + <T> <TT2>
      
    <TT> -> e
    TT.st := T.val; E.val := TT.val
    TT2.st := TT1.st + T.val;
       TT1.val := TT2.val
    TT.val := TT.st
    Inherited attribute flow

 

Attribute Flow

  • An attribute flow algorithm propagates attribute values through the parse tree by traversing the tree according to the defined-used dependencies between attributes
Production
Action
Attribute flow
<E> -> <T> <TT>
TT.st := T.val
<TT1> -> + <T> <TT2>
TT2.st :=
   TT1.st + T.val
<TT> -> e
TT.val := TT.st
<TT1> -> + <T> <TT2>
TT1.val := TT2.val
<E> -> <T> <TT>
E.val := TT.val

 

S- and L-Attributed Grammars

  • A grammar is called S-attributed if all attributes are synthesized
  • A grammar is called L-attributed if the parse tree traversal is left-to-right and depth-first
    • An essential grammar property for a one-pass compiler, because semantic rules can be applied directly during parsing and parse trees do not need to be kept in memory
Example L-attributed grammar for top-down parsing and evaluation of simple expressions
Production Semantic rule
<E> -> <T> <TT>
<TT1> -> + <T> <TT2>
  
<TT1> -> - <T> <TT2>
  
<TT> -> e
<T> -> <F> <FT>
<FT1> -> * <F> <FT2>
  
<FT1> -> / <F> <FT2>
  
<FT> -> e
<F1> -> - <F2>
<F> -> ( <E> )
<F> -> unsigned_int
TT.st := T.val; E.val := TT.val
TT2.st := TT1.st + T.val;
   TT1.val := TT2.val
TT2.st := TT1.st - T.val;
   TT1.val := TT2.val
TT.val := TT.st
FT.st := F.val; T.val := FT.val
FT2.st := FT1.st * F.val;
   FT1.val := FT2.val
FT2.st := FT1.st / F.val;
   FT1.val := FT2.val
FT.val := FT.st
F1.val := -F2.val
F.val := E.val
F.val := unsigned_int.val

 

Example Decorated Parse Tree

  • Fully decorated parse tree of (1+3)*2
Decorated parse tree

 

Constructing Abstract Syntax Trees

Example L-attributed grammar for constructing AST of simple expressions
Production Semantic rule
<E> -> <T> <TT>
<TT1> -> + <T> <TT2>
  
<TT1> -> - <T> <TT2>
  
<TT> -> e
<T> -> <F> <FT>
<FT1> -> * <F> <FT2>
  
<FT1> -> / <F> <FT2>
  
<FT> -> e
<F1> -> - <F2>
<F> -> ( <E> )
<F> -> unsigned_int
TT.st := T.ptr; E.ptr := TT.ptr
TT2.st := mk_bin_op("+", TT1.st, T.ptr);
  TT1.ptr := TT2.ptr
TT2.st := mk_bin_op("-", TT1.st, T.ptr);
  TT1.ptr := TT2.ptr
TT.ptr := TT.st
FT.st := F.ptr; T.ptr := FT.ptr
FT2.st := mk_bin_op("*", FT1.st, F.ptr);
  FT1.ptr := FT2.ptr
FT2.st := mk_bin_op("/", FT1.st, F.ptr);
  FT1.ptr := FT2.ptr
FT.ptr := FT.st
F1.ptr := mk_un_op("-", F2.ptr)
F.ptr := E.ptr
F.ptr := make_leaf(unsigned_int.val)
  • mk_bin_op: constructs binary operator AST-node and returns pointer
  • mk_un_op: constructs unary operator AST-node and returns pointer
  • mk_leaf: constructs AST-leaf and returns pointer

 

Example Decorated Parse Tree With AST

  • Decorated parse tree of (1+3)*2 with constructed AST

 

Recursive Descent Parser for L-Attributed Grammar

  • Productions for each nonterminal are implemented as a subroutine
  • Subroutine returns synthesized attributes of the nonterminal
  • Subroutine takes inherited attributes of the nonterminal as subroutine arguments
Production Semantic rule
<E> -> <T> <TT>
<TT1> -> + <T> <TT2>
  
<TT> -> e
TT.st := T.val; E.val := TT.val
TT2.st := TT1.st + T.val;
  TT1.val := TT2.val
TT.val := TT.st
function E()
  Tval := T()
  TTst := Tval
  TTval := TT(TTst)
  return TTval
function TT(TT1st)
  case (input_token())
  of '+': Tval := T()
          TT2st := TT1st + Tval
          TT2val := TT(TT2st)
          TT1val := TT2val
  otherwise: TT1val := TT1st
  return TT1val  
Exercise: Write a recursive descent parser in Java to evaluate simple expressions. Answer:Define this term
Exercise: Write a recursive descent parser in Java to construct an abstract syntax tree (AST) for simple expressions. Modify the parser to generate Lisp expressions from the AST. Answer:Define this term