Syntax and parser generators II-III
Lecture 4-5
Table of Contents
- Review
 - Implementing semantic rules
 - Visitor pattern
 - Example: Counting addition/subtraction operations
 - Visitor implementation pseudo-code
 - Defining traversal return type: generics
 - Implementing the Counter visitor
 - Program transformation with visitors
 - Implementing infix to postfix
 - Implementing an evaluator
 - Homework
 
Review
Questions about the last class?
Infix-to-postfix transformation
- Grammar
 - Semantic rules
 
Tree traversals
- Binary trees
 - Write preorder, postorder, and inorder traversals of the tree
 
What do you notice about {post,pre,in}fix notation and traversals?
Quiz
Write pseudo-code for a pre-order traversal of a binary tree.
Quiz Discussion
Implementing semantic rules
- Parser creates trees based on grammar
 - Compiler traverses tree
- Applies semantic rules to tree
 
 
Visitor pattern
- Visitors add methods to objects without modifying the class
 - We can abstract away new traversal into new visitor classes
 
Example implementation of double-dispatch Visitor in java
https://github.com/appleseedlab/superc/blob/master/src/xtc/tree/Visitor.java
Uses reflection to pick the right visitXXX function based on the object type of the child
Example: Counting addition/subtraction operations
- Recall Calc grammar
 - Only want to count additions
- One visitor function per grammar construct
 
 - Visitor class handles dispatching to correct method
 
Visitor implementation pseudo-code
int visitMulDiv(n) {
  return visit(leftexpr) + visit(rightexpr);
}
int visitAddSub(n) {
  return visit(leftexpr) + visit(rightexpr) + 1;
}
int visitInt(n) {
  return 0;
}
int visitParens(n) {
  return visit(expr);
}
Defining traversal return type: generics
ANTLR's generate visitor base class:
public class CalcBaseVisitor<T> extends AbstractParseTreeVisitor<T> implements CalcVisitor<T> { // ... }Our implementation
public class Counter extends CalcBaseVisitor<Integer> { // ... }
Implementing the Counter visitor
Generating the parser with a visitor
# add antlr to your classpath; your path to the runtime may vary export CLASSPATH=/usr/share/java/antlr4-runtime.jar:$CLASSPATH # this generates the parser, -visitor includes the visitor classes antlr4 -visitor Calc.g4
Using ANTLR's visitor
- Have the grammar file open as a reference
 - Use the CalcVisitor (or CalcBaseVisitor) as a guide
 - Visiting child nodes
visit(ctx.expr())
 - Many child nodes of same type
visit(ctx.expr(0)) + visit(ctx.expr(1))
 
Implementing the visitor
- (Together in class)
 
public class Counter extends CalcBaseVisitor<Integer> {
  @Override public Integer visitParens(CalcParser.ParensContext ctx) {
    return visit(ctx.expr());
  }
  @Override public Integer visitMulDiv(CalcParser.MulDivContext ctx) {
    return visit(ctx.expr(0)) + visit(ctx.expr(1));
  }
  @Override public Integer visitAddSub(CalcParser.AddSubContext ctx) {
    return visit(ctx.expr(0)) + visit(ctx.expr(1)) + 1;
  }
  @Override public Integer visitInt(CalcParser.IntContext ctx) {
    return 0;
  }
}
Running the visitor
- Boilerplate for ANTLR-generated parsers
 - Visiting the root of the node
 Building
javac RunCounter.java; javac Counter.java
Running
echo "1+2*3" | java RunCounter echo "((1))+2*3+3+5*(((7+2))*2)" | java RunCounter
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.*;
import java.io.PrintWriter;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.FileWriter;
public class RunCounter {
  public static void main(String[] args) throws Exception {
    // process input file
    String inputFile = null;
    if (args.length > 0) inputFile = args[0];
    CharStream input;
    if (inputFile != null) {
      input = CharStreams.fromFileName(inputFile);
    } else {
      input = CharStreams.fromStream(System.in);
    }
    // lexing and parsing
    CalcLexer lexer = new CalcLexer(input);
    CommonTokenStream tokens = new CommonTokenStream(lexer);
    CalcParser parser = new CalcParser(tokens);
    ParseTree tree = parser.expr();
    System.err.println(tree.toStringTree(parser));
    // visitor
    Counter counter = new Counter();
    Integer result = counter.visit(tree);
    System.out.println(result);
  }
}
Program transformation with visitors
Transformation source code
- Input: program source code
 - Output: program source code
 - Simplest transformation: the identity transfom
- Rewrites to same program
 
 - Why identity?
- Starting point for transformations
 
 
Identity transform with ANTLR visitors
- What is the return type?
 - Creating the base visitor
 - Using a StringBuilder for ease-of-use/performance
 
Getting the text of tokens in ANTLR
- Getting the text of a token
ctx.INT().getText()ctx.op.getText()
 
Creating the visitor
- (Together in class)
 - Keep the grammar open as a reference
 - Start with the base visitor
 - Define the return type
 - Define the transformation for each construct
- Use ANTLR's accessors to get values
 
 
import java.lang.StringBuilder;
public class Identity extends CalcBaseVisitor<String> {
  @Override public String visitParens(CalcParser.ParensContext ctx) {
    StringBuilder sb = new StringBuilder();
    sb.append("(");
    sb.append(visit(ctx.expr()));
    sb.append(")");
    return sb.toString();
  }
  @Override public String visitMulDiv(CalcParser.MulDivContext ctx) {
    StringBuilder sb = new StringBuilder();
    sb.append(visit(ctx.expr(0)));
    sb.append(ctx.op.getText());
    sb.append(visit(ctx.expr(1)));
    return sb.toString();
  }
  @Override public String visitAddSub(CalcParser.AddSubContext ctx) {
    StringBuilder sb = new StringBuilder();
    sb.append(visit(ctx.expr(0)));
    sb.append(ctx.op.getText());
    sb.append(visit(ctx.expr(1)));
    return sb.toString();
  }
  @Override public String visitInt(CalcParser.IntContext ctx) {
    StringBuilder sb = new StringBuilder();
    sb.append(ctx.INT().getText());
    return sb.toString();
  }
}
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.*;
import java.io.PrintWriter;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.FileWriter;
public class RunIdentity {
  public static void main(String[] args) throws Exception {
    // process input file
    String inputFile = null;
    if (args.length > 0) inputFile = args[0];
    CharStream input;
    if (inputFile != null) {
      input = CharStreams.fromFileName(inputFile);
    } else {
      input = CharStreams.fromStream(System.in);
    }
    // lexing and parsing
    CalcLexer lexer = new CalcLexer(input);
    CommonTokenStream tokens = new CommonTokenStream(lexer);
    CalcParser parser = new CalcParser(tokens);
    ParseTree tree = parser.expr();
    System.err.println(tree.toStringTree(parser));
    // visitor
    Identity identity = new Identity();
    String result = identity.visit(tree);
    System.out.println(result);
  }
}
Compiling the transform
Building and running the identity transform
javac Identity.java; javac RunIdentity.java echo "1*2+3" | java RunIdentity
Implementing infix to postfix
- How can we modify the identity transform to make postfix?
 
Implementing an evaluator
How could we use the visitor to evaluate the expression?
ANTLR tip
Checking the op
ctx.op.getType() == CalcParser.ADD
Adding storage to the evaluator
- How could we add variables to our calculator?
 
Homework
(1 week)
- Create an infix to prefix translator based on the identity transform and its runner