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 ArrayList defs; // the expression to be evaluated public ExprTree body; /** @param d a list of the function blocks that are to be part of the program @param b a tree representing the expression to be evaluated by the program */ // this class has no mutators and returns no mutable // values, so cloning the constructor's arguments // is unnecessary public Program(ArrayList d, ExprTree b) { defs = d; body = b; } /** @return the value of the program */ // update the global environment with the // bindings in in the definitions, // and then evaluate the expression in the // resulting environment public int evaluate() { return body.evaluate(new Environment(defs), "main program"); } } // end class Program /** A class for representing translated function blocks in the given programming language */ // Note that an alternate implementation would be to remove // the name component from the class, and map function names // to closures within a frame in the same way that variable // names are mapped to values. private class Def { // the function name public String name; // the first and second formal parameters public String param1; public String param2; // the immediately nested (translated) function blocks public ArrayList defs; // a tree representing the expression // to be evaluated by the program public ExprTree body; /** @param n the name of the function @param p1 the first formal parameter @param p2 the second formal parameter @param d a list of the function blocks that are to be nested immediately inside this block @param b a tree representing the body of the function */ // this class has no mutators and returns no mutable // values, so cloning the constructor's arguments // is unnecessary public Def(String n, String p1, String p2, ArrayList d, ExprTree b) { name = n; param1 = p1; param2 = p2; defs = d; body = b; } /** @return the name of the function */ public String getName() { return name; } /** Applies the function to a pair of evaluated arguments @param arg1 the first evaluated argument @param arg2 the second evaluated argument @param e the environment in which evaluation is to take place @return the value of the function */ // updates the environment with by binding the // formal parameters to the given arguments // and passing along the internal definitions // and then evaluates the function body in the // augmented environment public int apply(int arg1, int arg2, Environment e) { e.addFrame(param1, arg1, param2, arg2, defs); return body.evaluate(e, name); } } // end class Def /** Instances of this class represent syntax trees (analogs of parse trees that are used for expression evaluation). Their methods are abstract, since different subclasses are used for leaves and nonleaves. */ private abstract class ExprTree { abstract public int evaluate(Environment e, String caller); } // end ExprTree class /** Instances of this class represent syntax trees with single nodes representing operands. */ private class ExprLeaf extends ExprTree { // the operand private String atom; // null iff operand is a literal private Integer value; /** @param token the string representing the operand @param intValue the integer value if the operand is an integer literal (null otherwise) */ public ExprLeaf(String token, Integer intValue) { atom = token; value = intValue; } /** Evaluates the operand in the 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 value of the operand (i.e., of the syntax tree) */ public int evaluate(Environment e, String caller) { if (value == null) { Integer newValue = e.getValue(atom); if (newValue != null) return newValue; else throw new InputMismatchException( "no value found for " + atom + " in " + caller); } else return value; } } // end ExprLeaf class /** Instances of this class represent syntax trees with more than one node. Roots represent functions, and children represent operands. According to the given grammar, all functions are assumed to be binary and, except for + and *, functions are expected to be user-defined. */ private class ExprInternalNode extends ExprTree { // the top-level function private String operator; // the first operand private ExprTree firstChild; // the second operand private ExprTree secondChild; /** A constructor that takes as arguments the three desired components of the new tree @param op the top-level function, represented as a String @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(ArrayList d) { addFrame(null,0,null,0,d); } /** Adds a given frame, representing a function block, to the environment @param p1 the first formal parameter of the function @param v1 the argument bound to this parameter @param p2 the first formal parameter of the function @param v2 the argument bound to this parameter @param d the immediately nested function blocks for this function */ public void addFrame(String p1, int v1, String p2, int v2, ArrayList defs) { Frame f = new Frame(p1, v1, p2, v2); for (Def d : defs) f.addDef(d); stack.addFirst(f); } /** Evaluates a variable in the environment @param var the variable name @return the value bound to the variable if there is one, null otherwise. */ // Sequentially searches the frames (most recent // first) until a binding for the symbol is found public Integer getValue(String var) { for (Frame f : stack) { Integer value = f.getVarBinding(var); if (value != null) return value; } return null; } /** Looks up a function name in the environment @param var the function name @return the closest function block corresponding to the function name if there is one, null otherwise. */ // Sequentially searches the frames (most recent // first) until a binding for the name is found public Def getDef(String var) { for (Frame f : stack) { Def value = f.getDefBinding(var); if (value != null) return value; } return null; } /** Represents a frame in a dynamic scoping environment */ private class Frame { // the first and second formal parameters private String param1; private String param2; // the values bound respectively to these parameters private int value1; private int value2; // the immediately nested (translated) function blocks private LinkedList defs; /** @param p1 the first formal parameter of the function @param v1 the argument bound to this parameter @param p2 the first formal parameter of the function @param v2 the argument bound to this parameter */ public Frame(String p1, int v1, String p2, int v2) { param1 = p1; param2 = p2; value1 = v1; value2 = v2; defs = new LinkedList(); } /** Adds a nested function binding to the frame */ public void addDef(Def def) { defs.add(def); } /** Looks up a variable in the frame @param var the variable name @return the value bound to the variable if there is one, null otherwise. */ public Integer getVarBinding(String var) { if (var.equals(param1)) return value1; if (var.equals(param2)) return value2; return null; } /** Looks up a function name in the frame @param var the function name @return the function block corresponding to the function name if there is one, null otherwise. */ public Def getDefBinding(String var) { for (Def d : defs) if (d.getName().equals(var)) return d; return null; } } // end class Frame } // end class Environment /** Evaluate an expression consistent with the given grammar @param input the input string representing the expression @return the integer value @throws InputMismatchException for ill-formed input strings */ public int evaluate(String input) { return parse(input).evaluate(); } /////////////////// PARSING METHODS ///////////////////////// /** Parse a string that is assumed to represent a program that can be generated by the given grammar @param input the input string @return a syntax tree for the expression, represented as an ExprTree @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 deflist = new ArrayList(); while (lookahead.equals("define")) { Def def = parseDef(); deflist.add(def); } ExprTree e = parseExpr(); return new Program(deflist, e); } /** Parse a string that is assumed to be generable from the <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 deflist = new ArrayList(); while (lookahead.equals("define")) { Def def = parseDef(); deflist.add(def); } ExprTree e = parseExpr(); if (!lookahead.equals("end")) throw new InputMismatchException( "end expected in " + name + " -- " + lookahead + " found"); lookahead = tokenizer.next(); return new Def(name, param1, param2, deflist, e); } /** Parse a string that is assumed to be generable from the <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<number> 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 parseNumber() { for (int i=0; i<fname> 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 function name @throws InputMismatchException for ill-formed input strings */ private String parseFunctionName() { if (lookahead.equals("+") || lookahead.equals("*")) { String name = lookahead; lookahead = tokenizer.next(); return name; } else { return parseId(); } }}