/** Nine different interpreters for Jam that differ in binding * policy and cons evaluation policy. * * Binding Policy is either: * call-by-value, * call-by-name, or * call-by-need. * * The cons evaluation policy is either: * call-by-value (eager), * call-by-name (redundant lazy), or * call-by-need (efficient lazy). */ import java.io.IOException; import java.io.Reader; /** Interpreter Classes */ /** * The exception class for Jam run-time errors. */ class EvalException extends RuntimeException { /** * Constructor for this exception. * @param msg description */ EvalException(String msg) { super(msg); } } /** * The visitor interface for interpreting ASTs. */ interface EvalVisitor extends ASTVisitor { /** * Constructs a new visitor of same form with specified environment e. * @param e environment * @return evaluation visitor with the specified environment */ EvalVisitor newEvalVisitor(PureList e); /** * Returns the environment embedded in this EvalVisitor. * @return environment */ PureList env(); } /** * The interface supported by various binding evaluation policies: call-by-value, call-by-name, and call-by-need. */ interface BindingPolicy { /** * Constructs the appropriate binding object for this, binding var to ast in the evaluator ev. * @param var variable * @param ast AST * @param ev evaluation visitor * @return new binding */ Binding newBinding(Variable var, AST ast, EvalVisitor ev); /** * Constructs the appropriate dummy binding object for this. * @param var variable * @return new dummy binding */ Binding newDummyBinding(Variable var); } /** * Interface for various cons evaluation policies. A ConsPolicy is typically embedded inside an BindingPolicy. */ interface ConsPolicy { /** * Constructs the appropriate cons. * @param args arguments * @param ev evaluation visitor * @return cons */ JamVal evalCons(AST[] args, EvalVisitor ev); } /** * Interface for classes with a variable field (Variable and the various Binding classes). */ interface WithVariable { /** * Accessor for the variable. * @return variable */ Variable var(); } /** * A lookup visitor class that returns element matching the embedded var. If no match found, returns null. */ class LookupVisitor implements PureListVisitor { /** * Variable to look up. */ Variable var; // the lexer guarantees that there is only one Variable for a given name /** * Constructor for a look-up visitor. * @param v variable to look up */ LookupVisitor(Variable v) { var = v; } /** * Case for empty lists. * @param e host * @return always null */ public ElemType forEmpty(Empty e) { return null; } /** * Case for non-empty lists. * @param c host * @return the element of the list if it is found, otherwise null */ public ElemType forCons(Cons c) { // System.err.println("forCons in LookUpVisitor invoked; c = " + c); ElemType e = c.first(); if (var == e.var()) { return e; } return c.rest().accept(this); } } /** * Interpreter class. */ class Interpreter { /** * Parser to use. */ Parser parser = null; /** * Parsed AST. */ AST prog = null; /** * Constructor for the interpreter. * @param fileName file name * @throws IOException */ Interpreter(String fileName) throws IOException { parser = new Parser(fileName); prog = parser.parseAndCheck(); } /** * Constructor for the interpreter. * @param p parser */ Interpreter(Parser p) { parser = p; prog = parser.parseAndCheck(); } /** * Constructor for the interpreter. * @param reader reader with input */ Interpreter(Reader reader) { parser = new Parser(reader); prog = parser.parseAndCheck(); } /** * Parses and VV interprets the input embeded in parser. * @return result of evaluation */ public JamVal valueValue() { return prog.accept(valueValueVisitor); } /** * Parses and VNm interprets the input embeded in parser * @return result of evaluation */ public JamVal valueName() { return prog.accept(valueNameVisitor); } /** * Parses and VNd interprets the input embeded in parser * @return result of evaluation */ public JamVal valueNeed() { return prog.accept(valueNeedVisitor); } /** * Parses and NmV interprets the input embeded in parser * @return result of evaluation */ public JamVal nameValue() { return prog.accept(nameValueVisitor); } /** * Parses and NmNm interprets the input embeded in parser * @return result of evaluation */ public JamVal nameName() { return prog.accept(nameNameVisitor); } /** * Parses and NmNd interprets the input embeded in parser * @return result of evaluation */ public JamVal nameNeed() { return prog.accept(nameNeedVisitor); } /** * Parses and NmV interprets the input embeded in parser * @return result of evaluation */ public JamVal needValue() { return prog.accept(needValueVisitor); } /** * Parses and NmNm interprets the input embeded in parser * @return result of evaluation */ public JamVal needName() { return prog.accept(needNameVisitor); } /** * Parses and NmNd interprets the input embeded in parser * @return result of evaluation */ public JamVal needNeed() { return prog.accept(needNeedVisitor); } /** * Binding policy for call-by-value. */ static final BindingPolicy CALL_BY_VALUE = new BindingPolicy() { public Binding newBinding(Variable var, AST arg, EvalVisitor ev) { return new ValueBinding(var, arg.accept(ev)); } public Binding newDummyBinding(Variable var) { return new ValueBinding(var, null); } }; /** * Binding policy for call-by-name. */ static final BindingPolicy CALL_BY_NAME = new BindingPolicy() { public Binding newBinding(Variable var, AST arg, EvalVisitor ev) { return new NameBinding(var, new Suspension(arg, ev)); } public Binding newDummyBinding(Variable var) { return new NameBinding(var, null); } }; /** * Binding policy for call-by-need. */ static final BindingPolicy CALL_BY_NEED = new BindingPolicy() { public Binding newBinding(Variable var, AST arg, EvalVisitor ev) { return new NeedBinding(var, new Suspension(arg, ev)); } public Binding newDummyBinding(Variable var) { return new NeedBinding(var, null); } }; /** * Eager cons evaluation policy. presume that args has exactly 2 elements. */ public static final ConsPolicy EAGER = new ConsPolicy() { public JamVal evalCons(AST[] args, EvalVisitor ev) { JamVal val0 = args[0].accept(ev); JamVal val1 = args[1].accept(ev); if (val1 instanceof JamList) { return new JamCons(val0, (JamList)val1); } throw new EvalException("Second argument " + val1 + " to `cons' is not a JamList"); } }; /** * Call-by-name lazy cons evaluation policy. */ public static final ConsPolicy LAZYNAME = new ConsPolicy() { public JamVal evalCons(AST[] args, EvalVisitor ev) { /* System.out.println("LazyNameCons called on " + ToString.toString(args, ", ")); */ return new JamLazyNameCons(args[0], args[1], ev); } }; /** * Call-by-need lazy cons evaluation policy. */ public static final ConsPolicy LAZYNEED = new ConsPolicy() { public JamVal evalCons(AST[] args, EvalVisitor ev) { /* System.out.println("LazyNeedCons called on " + ToString.toString(args, ", ")); */ return new JamLazyNeedCons(args[0], args[1], ev); } }; /** * Value-value visitor. */ static final ASTVisitor valueValueVisitor = new Evaluator(CALL_BY_VALUE, EAGER); /** * Value-name visitor. */ static final ASTVisitor valueNameVisitor = new Evaluator(CALL_BY_VALUE, LAZYNAME); /** * Value-need visitor. */ static final ASTVisitor valueNeedVisitor = new Evaluator(CALL_BY_VALUE, LAZYNEED); /** * Name-value visitor. */ static final ASTVisitor nameValueVisitor = new Evaluator(CALL_BY_NAME, EAGER); /** * Name-name visitor. */ static final ASTVisitor nameNameVisitor = new Evaluator(CALL_BY_NAME, LAZYNAME); /** * Name-need visitor. */ static final ASTVisitor nameNeedVisitor = new Evaluator(CALL_BY_NAME, LAZYNEED); /** * Need-value visitor. */ static final ASTVisitor needValueVisitor = new Evaluator(CALL_BY_NEED, EAGER); /** * Need-name visitor. */ static final ASTVisitor needNameVisitor = new Evaluator(CALL_BY_NEED, LAZYNAME); /** * Need-need visitor. */ static final ASTVisitor needNeedVisitor = new Evaluator(CALL_BY_NEED, LAZYNEED); /** * JVM entry point. Interpret the specified file. * @param args command line arguments * @throws IOException */ public static void main(String[] args) throws IOException { Parser p; if (args.length == 0) { System.out.println("Usage: java Interpreter { value | name | need }"); return; } p = new Parser(args[0]); // AST prog = p.parse(); // System.out.println("Parse tree is: " + prog); Interpreter i = new Interpreter(p); if (args.length == 1) { System.out.println("value-value Answer is: " + i.valueValue()); System.out.println("value-name Answer is: " + i.valueName()); System.out.println("value-need Answer is: " + i.valueNeed()); System.out.println("name-value Answer is: " + i.nameValue()); System.out.println("name-name Answer is: " + i.nameName()); System.out.println("name-need Answer is: " + i.nameNeed()); System.out.println("need-value Answer is: " + i.needValue()); System.out.println("need-name Answer is: " + i.needName()); System.out.println("need-need Answer is: " + i.needNeed()); } else if (args[1].equals("vv")) { System.out.println("value-value Answer is: " + i.valueValue()); } else if (args[1].equals("vn")) { System.out.println("value-name Answer is: " + i.valueName()); } else if (args[1].equals("vd")) { System.out.println("value-need Answer is: " + i.valueNeed()); } else if (args[1].equals("nv")) { System.out.println("name-value Answer is: " + i.nameValue()); } else if (args[1].equals("nn")) { System.out.println("name-name Answer is: " + i.nameName()); } else if (args[1].equals("nd")) { System.out.println("name-need Answer is: " + i.nameNeed()); } else if (args[1].equals("dv")) { System.out.println("need-value Answer is: " + i.needValue()); } else if (args[1].equals("dn")) { System.out.println("need-name Answer is: " + i.needName()); } else if (args[1].equals("dd")) { System.out.println("need-need Answer is: " + i.needNeed()); } } } /** * Primary visitor class for performing interpretation */ class Evaluator implements EvalVisitor { // Assumes that: // OpTokens are unique // Variable objects are unique: v1.name.equals(v.name) => v1 == v2 // only objects used as boolean values are BoolConstant.TRUE and BoolConstant.FALSE // Hence, == can be used to compare Variable objects, OpTokens, and // BoolConstants /** * Environment. */ PureList env; /** * Policy to create bindings. */ BindingPolicy bindingPolicy; /** * Policy to create cons. */ ConsPolicy consPolicy; /** * Constructor for this evaluation visitor. * @param e environment * @param bp binding policy * @param cp cons policy */ private Evaluator(PureList e, BindingPolicy bp, ConsPolicy cp) { env = e; bindingPolicy = bp; consPolicy = cp; } /** * Constructor for this evaluation visitor with an empty environment. * @param bp binding policy * @param cp cons policy */ public Evaluator(BindingPolicy bp, ConsPolicy cp) { this(new Empty(), bp, cp); } /* EvalVisitor methods */ /** * Factory method that constructs a visitor similar to this with environment env * @param env environment for the new visitor. * @return new visitor with the same policies */ public EvalVisitor newEvalVisitor(PureList env) { return new Evaluator(env, bindingPolicy, consPolicy); } /** * Getter for env field. * @return environment */ public PureList env() { return env; } /** * Case for BoolConstants. * @param b host * @return host */ public JamVal forBoolConstant(BoolConstant b) { return b; } /** * Case for IntConstants. * @param i host * @return host */ public JamVal forIntConstant(IntConstant i) { return i; } /** * Case for NullConstants. * @param n host * @return empty Jam list */ public JamVal forNullConstant(NullConstant n) { return JamEmpty.ONLY; } /** * Case for Variables. * @param v host * @return value of variable in current environment */ public JamVal forVariable(Variable v) { Binding match = env.accept(new LookupVisitor(v)); if (match == null) { throw new EvalException("variable " + v + " is unbound"); } return match.value(); } /** * Case for PrimFuns. * @param f host * @return host */ public JamVal forPrimFun(PrimFun f) { return f; } /** * Case for UnOpApps. * @param u host * @return value of unary operator application */ public JamVal forUnOpApp(UnOpApp u) { return u.rator().accept(new UnOpEvaluator(u.arg().accept(this))); } /** * Case for BinOpApps. * @param b host * @return value of unary operator application */ public JamVal forBinOpApp(BinOpApp b) { return b.rator().accept(new BinOpEvaluator(b.arg1(), b.arg2())); } /** * Case for Apps. * @param a host * @return value of application */ public JamVal forApp(App a) { JamVal rator = a.rator().accept(this); if (rator instanceof JamFun) { return ((JamFun)rator).accept(new FunEvaluator(a.args())); } throw new EvalException(rator + " appears at head of application " + a + " but it is not a valid function"); } /** * Case for Maps. * @param m host * @return closure */ public JamVal forMap(Map m) { return new JamClosure(m, env); } /** * Case for Ifs. * @param i host * @return value of conditional expression */ public JamVal forIf(If i) { JamVal test = i.test().accept(this); if (!(test instanceof BoolConstant)) { throw new EvalException("non Boolean " + test + " used as test in if"); } if (test == BoolConstant.TRUE) { return i.conseq().accept(this); } return i.alt().accept(this); } /** * Case for Lets. * @param l host * @return value of body */ public JamVal forLet(Let l) { /* recursive let semantics */ /* Extract binding vars and exps (rhs's) from l */ Variable[] vars = l.vars(); AST[] exps = l.exps(); int n = vars.length; // construct newEnv for Let body and exps; vars are bound to values of corresponding exps using newEvalVisitor PureList newEnv = env(); Binding[] bindings = new Binding[n]; for(int i = n - 1; i >= 0; i--) { bindings[i] = bindingPolicy.newDummyBinding(vars[i]); // bind var[i] to dummy value null newEnv = newEnv.cons(bindings[i]); // add new Binding to newEnv; it is shared! } EvalVisitor newEV = newEvalVisitor(newEnv); // fix up the dummy values for(int i = 0; i < n; i++) { bindings[i].setBinding(exps[i], newEV); // modifies newEnv and newEvalVisitor } return l.body().accept(newEV); } /* Inner classes */ /** * Function evaluator. */ class FunEvaluator implements FunVisitor { /** * Unevaluated arguments */ AST[] args; /** * Constructor for the function evaluator. * @param asts unevaluated arguments */ FunEvaluator(AST[] asts) { args = asts; } /* Support for FunVisitor interface */ /** * Case for Jam closures. * @param closure closure * @return result of application */ public JamVal forJamClosure(JamClosure closure) { Map map = closure.body(); int n = args.length; Variable[] vars = map.vars(); if (vars.length != n) { throw new EvalException("closure " + closure + " applied to " + n + " arguments"); } // construct newEnv for JamClosure body using JamClosure env PureList newEnv = closure.env(); for(int i = n - 1; i >= 0; i--) { newEnv = newEnv.cons(bindingPolicy.newBinding(vars[i], args[i],Evaluator.this)); } return map.body().accept(newEvalVisitor(newEnv)); } /** * Case for PrimFuns. * @param primFun host * @return result of application */ public JamVal forPrimFun(PrimFun primFun) { return primFun.accept(primEvaluator); } /** * Evaluator for PrimFuns. */ PrimFunVisitor primEvaluator = new PrimFunVisitor() { /** * Evaluate args using evaluation visitor in whose closure this object is. * @return array of evaluated arguments. */ private JamVal[] evalArgs() { int n = args.length; JamVal[] vals = new JamVal[n]; for(int i = 0; i < n; i++) { vals[i] = args[i].accept(Evaluator.this); } return vals; } /** * Throw an error. */ private void primFunError(String fn) { throw new EvalException("Primitive function `" + fn + "' applied to " + args.length + " arguments"); } /** * Evaluate an argument that has to be a Jam cons. * @param arg argument * @param fun function performing this evaluation * @return Jam cons */ private JamCons evalJamConsArg(AST arg, String fun) { JamVal val = arg.accept(Evaluator.this); if (val instanceof JamCons) { return (JamCons)val; } throw new EvalException("Primitive function `" + fun + "' applied to argument " + val + " that is not a JamCons"); } /** * Case for function? * * @return BoolConstant.TRUE if argument evaluates to a JamFun */ public JamVal forFunctionPPrim() { JamVal[] vals = evalArgs(); if (vals.length != 1) { primFunError("function?"); } return BoolConstant.toBoolConstant(vals[0] instanceof JamFun); } /** * Case for number? * * @return BoolConstant.TRUE if argument evaluates to a IntConstant */ public JamVal forNumberPPrim() { JamVal[] vals = evalArgs(); if (vals.length != 1) { primFunError("number?"); } return BoolConstant.toBoolConstant(vals[0] instanceof IntConstant); } /** * Case for list? * * @return BoolConstant.TRUE if argument evaluates to a JamList */ public JamVal forListPPrim() { JamVal[] vals = evalArgs(); if (vals.length != 1) { primFunError("list?"); } return BoolConstant.toBoolConstant(vals[0] instanceof JamList); } /** * Case for cons? * * @return BoolConstant.TRUE if argument evaluates to a JamCons */ public JamVal forConsPPrim() { JamVal[] vals = evalArgs(); if (vals.length != 1) { primFunError("cons?"); } return BoolConstant.toBoolConstant(vals[0] instanceof JamCons); } /** * Case for null? * * @return BoolConstant.TRUE if argument evaluates to a JamEmpty */ public JamVal forNullPPrim() { JamVal[] vals = evalArgs(); if (vals.length != 1) { primFunError("null?"); } return BoolConstant.toBoolConstant(vals[0] instanceof JamEmpty); } /** * Case for cons * * @return JamCons */ public JamVal forConsPrim() { if (args.length != 2) { primFunError("cons"); } return consPolicy.evalCons(args, Evaluator.this); /* Evaluation strategy determined by consEp */ } /** * Case for arity * @return IntConstant representing the arity of the argument */ public JamVal forArityPrim() { JamVal[] vals = evalArgs(); if (vals.length != 1) { primFunError("arity"); } if (!(vals[0] instanceof JamFun)) { throw new EvalException("arity applied to argument " + vals[0]); } return ((JamFun)vals[0]).accept(new FunVisitor() { public IntConstant forJamClosure(JamClosure jc) { return new IntConstant(jc.body().vars().length); } public IntConstant forPrimFun(PrimFun jpf) { return new IntConstant(jpf.accept(new PrimFunVisitor() { public Integer forFunctionPPrim() { return 1; } public Integer forNumberPPrim() { return 1; } public Integer forListPPrim() { return 1; } public Integer forConsPPrim() { return 1; } public Integer forNullPPrim() { return 1; } public Integer forArityPrim() { return 1; } public Integer forConsPrim() { return 2; } public Integer forFirstPrim() { return 1; } public Integer forRestPrim() { return 1; } })); } }); } /** * Case for first * @return first */ public JamVal forFirstPrim() { return evalJamConsArg(args[0], "first").first(); } /** * Case for rest * @return rest */ public JamVal forRestPrim() { return evalJamConsArg(args[0], "rest").rest(); } }; } /** * Evaluator for unary operators. */ static class UnOpEvaluator implements UnOpVisitor { /** * Value of the operand. */ private JamVal val; /** * Constructor for this evaluator. * @param jv value of the operand */ UnOpEvaluator(JamVal jv) { val = jv; } /** * Return the value of the operand if it is an IntConstant, otherwise throw an exception, * @param op operator the value gets applied to * @return value of the operand, if it is an IntConstant */ private IntConstant checkInteger(UnOp op) { if (val instanceof IntConstant) { return (IntConstant)val; } throw new EvalException("Unary operator `" + op + "' applied to non-integer " + val); } /** * Return the value of the operand if it is a BoolConstant, otherwise throw an exception, * @param op operator the value gets applied to * @return value of the operand, if it is an BoolConstant */ private BoolConstant checkBoolean(UnOp op) { if (val instanceof BoolConstant) { return (BoolConstant)val; } throw new EvalException("Unary operator `" + op + "' applied to non-boolean " + val); } /** * Case for unary plus. * @param op host * @return value of the operator application */ public JamVal forUnOpPlus(UnOpPlus op) { return checkInteger(op); } /** * Case for unary minus. * @param op host * @return value of the operator application */ public JamVal forUnOpMinus(UnOpMinus op) { return new IntConstant(-checkInteger(op).value()); } /** * Case for not. * @param op host * @return value of the operator application */ public JamVal forOpTilde(OpTilde op) { return checkBoolean(op).not(); } } /** * Evaluator for binary operators. */ class BinOpEvaluator implements BinOpVisitor { /** * Unevaluated arguments. */ private AST arg1, arg2; /** * Constructor for this evaluator * @param a1 unevaluted first argument * @param a2 unevaluated second argument */ BinOpEvaluator(AST a1, AST a2) { arg1 = a1; arg2 = a2; } /** * Return the value of the argument if it is an IntConstant, otherwise throw an exception, * @param arg argument to evaluate and check * @param b operator the value gets applied to * @return value of the argument, if it is an IntConstant */ private IntConstant evalIntegerArg(AST arg, BinOp b) { JamVal val = arg.accept(Evaluator.this); if (val instanceof IntConstant) { return (IntConstant)val; } throw new EvalException("Binary operator `" + b + "' applied to non-integer " + val); } /** * Return the value of the argument if it is a BoolConstant, otherwise throw an exception, * @param arg argument to evaluate and check * @param b operator the value gets applied to * @return value of the argument, if it is a BoolConstant */ private BoolConstant evalBooleanArg(AST arg, BinOp b) { JamVal val = arg.accept(Evaluator.this); if (val instanceof BoolConstant) { return (BoolConstant)val; } throw new EvalException("Binary operator `" + b + "' applied to non-boolean " + val); } /** * Case for binary plus. * @param op host * @return value of the operator application */ public JamVal forBinOpPlus(BinOpPlus op) { return new IntConstant(evalIntegerArg(arg1, op).value() + evalIntegerArg(arg2, op).value()); } /** * Case for binary minus. * @param op host * @return value of the operator application */ public JamVal forBinOpMinus(BinOpMinus op) { return new IntConstant(evalIntegerArg(arg1, op).value() - evalIntegerArg(arg2, op).value()); } /** * Case for times. * @param op host * @return value of the operator application */ public JamVal forOpTimes(OpTimes op) { return new IntConstant(evalIntegerArg(arg1, op).value() * evalIntegerArg(arg2, op).value()); } /** * Case for divide. * @param op host * @return value of the operator application */ public JamVal forOpDivide(OpDivide op) { int divisor = evalIntegerArg(arg2, op).value(); if (divisor == 0) { throw new EvalException("Attempt to divide by zero"); } return new IntConstant(evalIntegerArg(arg1, op).value() / divisor); } /** * Case for equals. * @param op host * @return value of the operator application */ public JamVal forOpEquals(OpEquals op) { return BoolConstant.toBoolConstant(arg1.accept(Evaluator.this).equals(arg2.accept(Evaluator.this))); } /** * Case for not equals. * @param op host * @return value of the operator application */ public JamVal forOpNotEquals(OpNotEquals op) { return BoolConstant.toBoolConstant(!arg1.accept(Evaluator.this).equals(arg2.accept(Evaluator.this))); } /** * Case for less than. * @param op host * @return value of the operator application */ public JamVal forOpLessThan(OpLessThan op) { return BoolConstant.toBoolConstant(evalIntegerArg(arg1, op).value() < evalIntegerArg(arg2, op).value()); } /** * Case for greater than. * @param op host * @return value of the operator application */ public JamVal forOpGreaterThan(OpGreaterThan op) { return BoolConstant.toBoolConstant(evalIntegerArg(arg1, op).value() > evalIntegerArg(arg2, op).value()); } /** * Case for less than or equals. * @param op host * @return value of the operator application */ public JamVal forOpLessThanEquals(OpLessThanEquals op) { return BoolConstant.toBoolConstant(evalIntegerArg(arg1, op).value() <= evalIntegerArg(arg2, op).value()); } /** * Case for greater than or equals. * @param op host * @return value of the operator application */ public JamVal forOpGreaterThanEquals(OpGreaterThanEquals op) { return BoolConstant.toBoolConstant(evalIntegerArg(arg1, op).value() >= evalIntegerArg(arg2, op).value()); } /** * Case for and. * @param op host * @return value of the operator application */ public JamVal forOpAnd(OpAnd op) { BoolConstant b1 = evalBooleanArg(arg1, op); if (b1 == BoolConstant.FALSE) { return BoolConstant.FALSE; } return evalBooleanArg(arg2, op); } /** * Case for or. * @param op host * @return value of the operator application */ public JamVal forOpOr(OpOr op) { BoolConstant b1 = evalBooleanArg(arg1, op); if (b1 == BoolConstant.TRUE) { return BoolConstant.TRUE; } return evalBooleanArg(arg2, op); } } }