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