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.
}