package a5;
import java.util.InputMismatchException;
import java.util.Scanner;
import java.util.ArrayList;
import java.util.LinkedList;
/**
This class contains an evaluate method that interprets
programs in the simple functional language defined by the grammar
given below. In case of syntactic or semantic error, the method
will throw an InputMismatchException that contains an
appropriate error message.
It assumes that dynamic scoping is in use.
The program representation is as a string of tokens separated
by whitespace characters as recognized by the
Character.isWhitespace predicate.
There is no explicit constructor; the default constructor is sufficient.
The grammar for boolean expressions has start symbol <Program> and productions
Program := Def* E
E := ( Fname E E )
E := Number
E := Id
Def := define Id ( Id Id ) Def* E end
Fname := Id | + | *
Id := Letter+
Number := Digit+
Note that recursive descent parsing is appropriate for this grammar.
*/
public class Interpreter
{
// used to divide the program into tokens
// and to retrieve the next token
private Scanner tokenizer;
// the next unprocessed token of the program
private String lookahead;
/// CLASSES FOR REPRESENTING PARSED PROGRAMS & PROGRAM SEGMENTS ///
/**
This class represents parsed programs in the given language.
*/
private class Program
{
// a list of the function blocks in the program
public ArrayListString
@param first the first operand
@param second the second operand
*/
// this class has no mutators and returns no mutable
// values, so cloning the constructor's arguments
// is unnecessary
public ExprInternalNode(String op, ExprTree first, ExprTree second) {
operator = op;
firstChild = first;
secondChild = second; }
/**
Evaluates the represented expression in a given environment
@param e the environment to be used for evaluation
@param caller the name of the caller
(used for error messages only)
@return the integer value obtained by evaluating the tree
@throws InputMismatchException if an undefined function or
variable is present
*/
// The evaluation algorithm is a recursive evaluation that's
// equivalent to a postorder traversal
// It looks up the operator and the operands in the environment
// and applies the operator to the operands
// Note that processing of + and * is hard-coded into the method.
// However the ordering of the cases in the current version of
// this method could handle the situation where these operators
// can be redefined by the user.
public int evaluate(Environment e, String caller) {
Def d = e.getDef(operator);
if (d != null) {
String fname = d.getName();
return d.apply(firstChild.evaluate(e, fname),
secondChild.evaluate(e, fname), e); }
if (operator.equals("+")) {
return firstChild.evaluate(e,"+")
+ secondChild.evaluate(e,"+"); }
if (operator.equals("*")) {
return firstChild.evaluate(e,"*")
* secondChild.evaluate(e,"*"); }
else
throw new InputMismatchException(
"undefined function " + operator + " in " + caller); }
} // end ExprInternalNode class
///////////////////// ENVIRONMENTS //////////////////////
/**
This class is used to represent environments in which
expressions may be evaluated using dynamic scoping
*/
// No global frame is assumed -- the treatment of + and *
// is hard-coded into the "evaluate" method
// The default constructor suffices in the 0-argument case
private class Environment
{
// A stack of frames
private LinkedList stack =
new LinkedList();
/**
Constructs an environment with a given list
of function blocks
@param d the list of function blocks
*/
public Environment(ArrayListExprTree
@throws InputMismatchException for ill-formed input strings
*/
// The parsing algorithm is recursive descent.
// A dummy symbol is appended to each input string so
// that lookahead never fails
// The input string is entered into a tokenizer,
// which really ought to be closed as in Assignment 2
private Program parse(String input) {
if (input == null)
throw new InputMismatchException(
"input string cannot be null");
tokenizer = new Scanner(input + " $end-of-file$");
lookahead = tokenizer.next();
Program result = parseProgram();
tokenizer.close();
if (!lookahead.equals("$end-of-file$"))
throw new InputMismatchException(
"symbols remain after end of expression");
return result; }
///////////// The parsing methods for particular nonterminals ////////////////
/**
Parse a string that is assumed to be generable from the
<program> symbol in the given grammar
The string is assumed to be a prefix of the contents
of the tokenizer. After parsing, all tokens of the
string will have been removed from the tokenizer.
@return a Program representation for the string
(which is thus assumed to represent a legal program)
@throws InputMismatchException for ill-formed input strings
*/
private Program parseProgram() {
ArrayList<def> symbol in the given grammar
The string is assumed to be a prefix of the contents
of the tokenizer. After parsing, all tokens of the
string will have been removed from the tokenizer.
@return a Def representation for the string
(which is thus assumed to represent a function block)
@throws InputMismatchException for ill-formed input strings
*/
private Def parseDef() {
// could remove this test -- lookahead is already checked
if (!lookahead.equals("define"))
throw new InputMismatchException(
"define expected -- " + lookahead + " found");
lookahead = tokenizer.next();
String name = parseId();
if (!lookahead.equals("("))
throw new InputMismatchException(
"( expected in " + name + " -- " + lookahead + " found");
lookahead = tokenizer.next();
String param1 = parseId();
String param2 = parseId();
if (!lookahead.equals(")"))
throw new InputMismatchException(
") expected in " + name + " -- " + lookahead + " found");
lookahead = tokenizer.next();
ArrayList<expr> symbol in the given grammar
The string is assumed to be a prefix of the contents
of the tokenizer. After parsing, all tokens of the
string will have been removed from the tokenizer.
@return a syntax tree for the expression, represented as
an ExprTree
@throws InputMismatchException for ill-formed input strings
*/
private ExprTree parseExpr() {
// try the 3rd rule
if (lookahead.equals("(")) {
lookahead = tokenizer.next();
String f = parseFunctionName();
ExprTree child1 = parseExpr();
ExprTree child2 = parseExpr();
if (!lookahead.equals(")"))
throw new InputMismatchException(
") expected in " + f + " -- " + lookahead + " found");
lookahead = tokenizer.next();
return new ExprInternalNode(f, child1, child2); }
// try the 1st rule
else if (Character.isLetter(lookahead.charAt(0))) {
return new ExprLeaf(parseId(), null); }
// try the 2nd rule
else if (Character.isDigit(lookahead.charAt(0))) {
String numberAsString = parseNumber();
return new ExprLeaf(numberAsString,
Integer.parseInt(numberAsString)); }
else
throw new InputMismatchException(
"( expected -- " + lookahead + " found"); }
/**
Parse a string that is assumed to be generable from the
<id> symbol in the given grammar
The string is assumed to be a prefix of the contents
of the tokenizer. After parsing, all tokens of the
string will have been removed from the tokenizer.
@return a String representing the token
@throws InputMismatchException for ill-formed input strings
*/
private String parseId() {
for (int i=0; i