Alpha is a simple expression-oriented language with LISP-like syntax.
Here's an EBNF for Alpha:
<Expression> ::= <Literal> | <FunCall>
<FunCall> ::= (
<Operator> <Expression>+ )
<Operator> ::= + | *
<Literal> ::= <Digit>*(.<Digit>+)?
<Digit> ::= 0 .. 9
Here's a sample session with the Alpha interpreter:
-> -3.1416
value = -3.1416
-> ( + 2 3 )
value = 5.0
-> ( + ( * 2 3 ) ( * 4 ( + 5 3.1416 ) ) )
value = 38.5664
-> quit
bye
The design of Alpha uses three patterns:
Composite
Interpreter
Visitor
We use the Composite Pattern to represent Alpha expressions:
Each node knows how to parse itself.
Each node knows how to evaluate itself. (Interpreter Pattern)
Each node has a method that accepts an object implementing a visitor interface:
abstract class Expression {
�� public static Expression parse(Scanner
tokens) throws Exception {
����� if (tokens.hasNext("\\("))
{ ��� // if next token is a left paren
�������� return FunCall.parse(tokens); //
then it must be a FunCall
����� } else {
�������� return Literal.parse(tokens); //
else it must be a Literal
����� }
�� }
�� abstract public double eval() throws
Exception;
�� public void accept(ExpVisitor v) { }
}
class Literal extends Expression {
�� private Double token;
�� public static Expression parse(Scanner
tokens) throws Exception {
����� Literal result = new Literal();
����� result.token = tokens.nextDouble();
����� return result;
�� }
�� public String toString() { return
"" + token; }
�� public double eval() throws Exception {
return token; }
�� public void accept(ExpVisitor v) {
v.visit(this); }
}
class FunCall extends Expression {
�� private List<Expression> operands
= new ArrayList<Expression>();
�� private String operator =
"?";
�� public String getOperator() { return
operator; }
�� public static Expression parse(Scanner
tokens) throws Exception {
����� FunCall result = new FunCall();
����� tokens.next(); // toss left paren
����� result.operator = tokens.next();
����� while
(!tokens.hasNext("\\)")) {� //
parse operands until closing right paren encountered
�������� result.operands.add(Expression.parse(tokens));
����� }
����� tokens.next(); // toss right paren
����� return result;
�� }
�� public void accept(ExpVisitor v) {
v.visit(this); }
�� public Iterator<Expression>
iterator() {
����� return operands.iterator();
�� }
�� public String toString() {
����� String result = "(";
����� for(Expression node: operands) {
�������� result = result + node + "
";
����� }
����� return result + ")";
�� }
�� public double eval()
throws Exception {
����� double result = 0;
����� List<Double> args = new
ArrayList<Double>();
����� for(Expression operand: operands) {
�������� args.add(operand.eval());
����� }
����� if (operator.equals("+"))
{
�������� for(Double arg: args) {
����������� result += arg;
�������� }
����� } else if
(operator.equals("*")) {
�������� result = 1;
�������� for(Double arg: args) {
����������� result *= arg;
�������� }
����� } else {
�������� throw new
Exception("unrecognized operator: " + operator);
����� }
����� return result;
�� }
}
An expression visitor is anything that implements the ExpVisitor interface:
interface ExpVisitor {
�� void visit(Literal node);
�� void visit(FunCall node);
}
For example, a tree print visitor displays each token on a separate line preceded by N spaces, where N is the depth of nesting of the token:
class TreePrintVisitor implements ExpVisitor {
�� private String indent = " ";
�� public void visit(Literal node) {
����� System.out.println(indent + node);
�� }
�� public void visit(FunCall node) {
����� System.out.println(indent +
node.getOperator());
����� Iterator<Expression> p =
node.iterator();
����� indent += " "; // add a
space
����� while(p.hasNext()) {
�������� p.next().accept(this);
����� }
����� indent = indent.substring(1); //
remove a space
�� }
}
A tree seach visitor compares each node to a given token, incrementing a counter each time a match is found:
class TreeSearchVisitor implements ExpVisitor {
�� private String target;
�� private int occurrences = 0;
�� public int getOccurrences () { return
occurrences; }
�� public TreeSearchVisitor(String t) {
target = t; }
�� public void visit(Literal node) {
����� if
(target.equals(node.toString()))occurrences++;
�� }
�� public void visit(FunCall node) {
����� if
(target.equals(node.getOperator())) occurrences++;
����� Iterator<Expression> p =
node.iterator();
����� while(p.hasNext()) {
�������� p.next().accept(this);
����� }
�� }
}
Notice that visiting a composite node (FunCall) requires us to visit each child. The type of the child (simple or composite) will polymorphically determine which variant of visit is called.
We implement a REPL-style interpreter (Read-Evaluate-Print Loop).
To demonstrate visitors, we ask a tree print visitor to visit the parse tree as well as a search visitor that counts the number of occurrences of pi in the expression:
public class Interpreter {
�� public static void main(String[] args)
{
����� while(true) {
�������� try {
����������� System.out.print("->
");
����������� Scanner kbd = new
Scanner(System.in);
����������� if
(kbd.hasNext("quit")) break;
����������� Expression exp =
Expression.parse(kbd);
����������� System.out.println("value
= " + exp.eval());
����������� TreePrintVisitor v1 = new TreePrintVisitor();
����������� exp.accept(v1);
����������� TreeSearchVisitor v2 = new
TreeSearchVisitor("3.1416");
����������� exp.accept(v2);
����������� System.out.println("#pi =
" + v2.getOccurrences());
�������� } catch(Exception e) {
����������� System.err.println(e.getMessage());
�������� }
����� }
����� System.out.println("bye");
�� }
}
Here's a sample session:
-> -3.1416
value = -3.1416
expression tree:
�-3.1416
#pi = 0
-> ( * 2 3 4 )
value = 24.0
expression tree:
�*
� 2.0
� 3.0
� 4.0
#pi = 0
-> ( * ( + 4 5 ) ( + 2 ( * 6 3.1416 ) ) )
value = 187.6464
expression tree:
�*
� +
�� 4.0
�� 5.0
� +
�� 2.0
�� *
��� 6.0
��� 3.1416
#pi = 1
->
Note that the implementation of accept is the same for Literal.accept(Visitor v) and FunCall.accept(Visitor v). Namely:
v.visit(this);
Why not simply pull this implementation up to Expression.accept(Visitor v) and get rid of the subclass overrides?
Beta extends Alpha by adding symbols and definitions:
<Phrase> ::= <Expression> | <Definition>
<Expression> ::= <Literal> | <Symbol> | <FunCall>
<Definition> ::= [ define
<Symbol> <Expression> ]
<Symbol> ::= <Char>(<Char> | <Digit>)*
<Char> ::= a..z | A..Z
Here's a sample session:
-> [ define pi 3.14 ]
done
-> ( + 2 pi )
value = 5.14
->
Design and write an interpreter for Beta. Hint: eval now needs a symbol table parameter:
abstract class Phrase
{
�� abstract double eval(Map<Symbol,
Double> env);
�� // etc.
}