An Alpha Interpreter

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

Design Model

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:

Implementation

Expressions

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

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

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

Visitors

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.

The Interpreter

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");
   }
}

A Session

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

Question

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?

Project

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