UP | HOME

Syntax and parser generators II-III
Lecture 4-5

Table of Contents

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

Author: Paul Gazzillo

Created: 2023-04-13 Thu 14:59