package smallstep;
import java.lang.reflect.Method;
import java.util.Vector;
import common.ProofStep;
import common.interpreters.MutableStore;
import common.interpreters.Store;
import expressions.And;
import expressions.Application;
import expressions.Assign;
import expressions.BinaryCons;
import expressions.BinaryOperator;
import expressions.BinaryOperatorException;
import expressions.BooleanConstant;
import expressions.Condition;
import expressions.Condition1;
import expressions.CurriedLet;
import expressions.CurriedLetRec;
import expressions.Deref;
import expressions.EmptyList;
import expressions.Exn;
import expressions.Expression;
import expressions.Fst;
import expressions.Hd;
import expressions.InfixOperation;
import expressions.IsEmpty;
import expressions.Lambda;
import expressions.Let;
import expressions.LetRec;
import expressions.List;
import expressions.Location;
import expressions.MultiLambda;
import expressions.MultiLet;
import expressions.Or;
import expressions.Projection;
import expressions.Recursion;
import expressions.Ref;
import expressions.Sequence;
import expressions.Snd;
import expressions.Tl;
import expressions.Tuple;
import expressions.UnaryCons;
import expressions.UnaryOperator;
import expressions.UnaryOperatorException;
import expressions.UnitConstant;
import expressions.While;
/**
* Evaluator for expression using the small step
* semantics.
*
* @author Benedikt Meurer
* @version $Id$
*/
final class SmallStepEvaluator {
/**
* The resulting {@link Expression}.
*
* @see #getExpression()
*/
private Expression expression;
/**
* The steps in the proof.
*
* @see #getSteps()
*/
private Vector<ProofStep> steps = new Vector<ProofStep>();
/**
* The resulting {@link common.interpreters.Store}.
*
* @see #getStore()
*/
private Store store;
//
// Constructor
//
/**
* Allocates a new {@link SmallStepEvaluator} used to
* determine the next evaluation step for the given
* <code>expression</code>, including the necessary
* {@link SmallStepProofRule}s.
*
* @param expression the {@link Expression} for which
* to determine the next evaluation
* step in the proof.
* @param store the {@link common.interpreters.Store} to start with.
*/
SmallStepEvaluator(Expression expression, Store store) {
// create store, remember expression
this.expression = expression;
this.store = store;
// evaluate expression
this.expression = evaluate(this.expression);
}
//
// Primitives
//
/**
* Returns the expression as result of the
* evaluation.
*
* @return the resulting expression.
*/
public Expression getExpression() {
return this.expression;
}
/**
* Returns the {@link ProofStep}s that were
* performed during the evaluation.
*
* @return the evaluation steps.
*/
public ProofStep[] getSteps() {
// return the steps in reversed order
ProofStep[] steps = new ProofStep[this.steps.size()];
for (int n = 0; n < steps.length; ++n) {
if (this.expression.isException()) {
// translate meta rules to the associated EXN rules
SmallStepProofRule rule = (SmallStepProofRule)this.steps.elementAt(n).getRule();
steps[n] = new ProofStep(this.steps.elementAt(n).getExpression(), rule.getExnRule());
}
else {
// just use the proof step
steps[n] = this.steps.elementAt(n);
}
}
return steps;
}
/**
* Returns the resulting {@link Store} for the evaluation.
*
* @return the resulting store.
*/
public Store getStore() {
return this.store;
}
//
// Evaluation methods
//
private Expression evaluate(Expression expression) {
try {
// determine the specific evaluate method
Method method = lookupMethod("evaluate", expression.getClass());
// try to invoke the method
return (Expression)method.invoke(this, expression);
}
catch (NoSuchMethodException e) {
// no way to further evaluate the expression
return expression;
}
catch (Exception e) {
// rethrow as runtime exception, as some-
// thing is really completely broken
throw new RuntimeException(e);
}
}
@SuppressWarnings("unused")
private Expression evaluateAnd(And and) {
// determine the sub expressions
Expression e1 = and.getE1();
Expression e2 = and.getE2();
// check if e1 is not already a boolean constant
if (!(e1 instanceof BooleanConstant)) {
// we're about to perform (AND-EVAL)
addProofStep("AND-EVAL", and);
// try to evaluate e1
e1 = evaluate(e1);
// exceptions need special handling
return e1.isException() ? e1 : new And(e1, e2);
}
// determine the boolean constant value
BooleanConstant booleanConstant = (BooleanConstant)e1;
if (booleanConstant.isTrue()) {
// jep, that's (AND-TRUE) then
addProofStep("AND-TRUE", and);
return e2;
}
else {
// jep, that's (AND-FALSE) then
addProofStep("AND-FALSE", and);
return BooleanConstant.FALSE;
}
}
@SuppressWarnings("unused")
private Expression evaluateApplication(Application application) {
// determine the sub expressions
Expression e1 = application.getE1();
Expression e2 = application.getE2();
// check if e1 is not already a value
if (!e1.isValue()) {
// we're about to perform (APP-LEFT)
addProofStep("APP-LEFT", application);
// try to evaluate e1
e1 = evaluate(e1);
// exceptions need special handling
return e1.isException() ? e1 : new Application(e1, e2);
}
// check if e2 is not already a value
if (!e2.isValue()) {
// we're about to perform (APP-RIGHT)
addProofStep("APP-RIGHT", application);
// try to evaluate e2
e2 = evaluate(e2);
// exceptions need special handling
return e2.isException() ? e2 : new Application(e1, e2);
}
// perform the application
return apply(application, e1, e2);
}
@SuppressWarnings("unused")
private Expression evaluateCondition(Condition condition) {
// determine the sub expression
Expression e0 = condition.getE0();
Expression e1 = condition.getE1();
Expression e2 = condition.getE2();
// check if e0 is not already a boolean constant
if (!(e0 instanceof BooleanConstant)) {
// we're about to perform (COND-EVAL)
addProofStep("COND-EVAL", condition);
// try to evaluate e0
e0 = evaluate(e0);
// exceptions need special handling
return e0.isException() ? e0 : new Condition(e0, e1, e2);
}
// determine the boolean constant value
BooleanConstant booleanConstant = (BooleanConstant)e0;
if (booleanConstant.isTrue()) {
// jep, that's (COND-TRUE) then
addProofStep("COND-TRUE", condition);
return e1;
}
else {
// jep, that's (COND-FALSE) then
addProofStep("COND-FALSE", condition);
return e2;
}
}
@SuppressWarnings("unused")
private Expression evaluateCondition1(Condition1 condition1) {
// determine the sub expression
Expression e0 = condition1.getE0();
Expression e1 = condition1.getE1();
// check if e0 is not already a boolean constant
if (!(e0 instanceof BooleanConstant)) {
// we're about to perform (COND-1-EVAL)
addProofStep("COND-1-EVAL", condition1);
// try to evaluate e0
e0 = evaluate(e0);
// exceptions need special handling
return e0.isException() ? e0 : new Condition1(e0, e1);
}
// determine the boolean constant value
BooleanConstant booleanConstant = (BooleanConstant)e0;
if (booleanConstant.isTrue()) {
// jep, that's (COND-1-TRUE) then
addProofStep("COND-1-TRUE", condition1);
return e1;
}
else {
// jep, that's (COND-1-FALSE) then
addProofStep("COND-1-FALSE", condition1);
return UnitConstant.UNIT;
}
}
@SuppressWarnings("unused")
private Expression evaluateCurriedLet(CurriedLet curriedLet) {
// determine the sub expressions and the identifiers
String[] identifiers = curriedLet.getIdentifiers();
Expression e1 = curriedLet.getE1();
Expression e2 = curriedLet.getE2();
// prepend the lambda abstractions to e1
for (int n = identifiers.length - 1; n >= 1; --n)
e1 = new Lambda(identifiers[n], e1);
// we can simply perform (LET-EXEC)
addProofStep("LET-EXEC", curriedLet);
// and perform the substitution
return e2.substitute(identifiers[0], e1);
}
@SuppressWarnings("unused")
private Expression evaluateCurriedLetRec(CurriedLetRec curriedLetRec) {
// determine the sub expressions and the identifiers
String[] identifiers = curriedLetRec.getIdentifiers();
Expression e1 = curriedLetRec.getE1();
Expression e2 = curriedLetRec.getE2();
// prepend the lambda abstractions to e1
for (int n = identifiers.length - 1; n >= 1; --n)
e1 = new Lambda(identifiers[n], e1);
// we can perform (UNFOLD), which includes a (LET-EVAL)
addProofStep("LET-EVAL", curriedLetRec);
addProofStep("UNFOLD", curriedLetRec);
// perform the substitution on e1
e1 = e1.substitute(identifiers[0], new Recursion(identifiers[0], e1));
// generate the new (LET) expression
return new Let(identifiers[0], e1, e2);
}
@SuppressWarnings("unused")
private Expression evaluateInfixOperation(InfixOperation infixOperation) {
// determine the sub expressions and the operator
Expression e1 = infixOperation.getE1();
Expression e2 = infixOperation.getE2();
BinaryOperator op = infixOperation.getOp();
// check if e1 is not already an integer constant
if (!e1.isValue()) {
// we're about to perform (APP-LEFT) and (APP-RIGHT)
addProofStep("APP-LEFT", infixOperation);
addProofStep("APP-RIGHT", infixOperation);
// try to evaluate e1
e1 = evaluate(e1);
// exceptions need special handling
return e1.isException() ? e1 : new InfixOperation(op, e1, e2);
}
// check if e2 is not already a value
if (!e2.isValue()) {
// we're about to perform (APP-RIGHT)
addProofStep("APP-RIGHT", infixOperation);
// try to evaluate e2
e2 = evaluate(e2);
// exceptions need special handling
return e2.isException() ? e2 : new InfixOperation(op, e1, e2);
}
// try to perform the application
return handleBinaryOperator(infixOperation, op, e1, e2);
}
@SuppressWarnings("unused")
private Expression evaluateLet(Let let) {
// determine the sub expressions and the identifier
Expression e1 = let.getE1();
Expression e2 = let.getE2();
String id = let.getId();
// check if e1 is not already a value
if (!e1.isValue()) {
// we're about to perform (LET-EVAL)
addProofStep("LET-EVAL", let);
// try to evaluate e1
e1 = evaluate(e1);
// exceptions need special treatment
return e1.isException() ? e1 : new Let(id, e1, e2);
}
// we can perform (LET-EXEC)
addProofStep("LET-EXEC", let);
// and perform the substitution
return e2.substitute(id, e1);
}
@SuppressWarnings("unused")
private Expression evaluateMultiLet(MultiLet multiLet) {
// determine the identifiers and the sub expressions
String[] identifiers = multiLet.getIdentifiers();
Expression e1 = multiLet.getE1();
Expression e2 = multiLet.getE2();
// check if e1 is not already a value
if (!e1.isValue()) {
// we're about to perform (LET-EVAL)
addProofStep("LET-EVAL", multiLet);
// try to evaluate e1
e1 = evaluate(e1);
// exceptions need special treatment
return e1.isException() ? e1 : new MultiLet(identifiers, e1, e2);
}
// try to perform the (LET-EXEC)
try {
// arity of the tuple must match
Expression[] expressions = ((Tuple)e1).getExpressions();
if (expressions.length != identifiers.length)
return multiLet;
// perform the substitutions
for (int n = 0; n < identifiers.length; ++n) {
e2 = e2.substitute(identifiers[n], expressions[n]);
}
// jep, that was (LET-EXEC) then
addProofStep("LET-EXEC", multiLet);
// return the new expression
return e2;
}
catch (ClassCastException exception) {
// e1 is not a tuple
return multiLet;
}
}
@SuppressWarnings("unused")
private Expression evaluateLetRec(LetRec letRec) {
// determine the expressions and the identifier
Expression e1 = letRec.getE1();
Expression e2 = letRec.getE2();
String id = letRec.getId();
// we perform (UNFOLD), which includes a (LET-EVAL)
addProofStep("LET-EVAL", letRec);
addProofStep("UNFOLD", letRec);
// perform the substitution on e1
e1 = e1.substitute(id, new Recursion(id, e1));
// generate the new (LET) expression
return new Let(id, e1, e2);
}
@SuppressWarnings("unused")
private Expression evaluateOr(Or or) {
// determine the sub expressions
Expression e1 = or.getE1();
Expression e2 = or.getE2();
// check if e1 is not already a boolean constant
if (!(e1 instanceof BooleanConstant)) {
// we're about to perform (OR-EVAL)
addProofStep("OR-EVAL", or);
// try to evaluate e1
e1 = evaluate(e1);
// exceptions need special treatment
return e1.isException() ? e1 : new Or(e1, e2);
}
// determine the boolean constant value
BooleanConstant booleanConstant = (BooleanConstant)e1;
if (booleanConstant.isTrue()) {
// jep, that's (OR-TRUE) then
addProofStep("OR-TRUE", or);
return BooleanConstant.TRUE;
}
else {
// jep, that's (OR-FALSE) then
addProofStep("OR-FALSE", or);
return e2;
}
}
@SuppressWarnings("unused")
private Expression evaluateRecursion(Recursion recursion) {
// determine the expression and the identifier
Expression e = recursion.getE();
String id = recursion.getId();
// we can perform (UNFOLD)
addProofStep("UNFOLD", recursion);
// perform the substitution
return e.substitute(id, recursion);
}
@SuppressWarnings("unused")
private Expression evaluateSequence(Sequence sequence) {
// determine the sub expressions
Expression e1 = sequence.getE1();
Expression e2 = sequence.getE2();
// check if e1 is not already a value
if (!e1.isValue()) {
// we're about to perform (SEQ-EVAL)
addProofStep("SEQ-EVAL", sequence);
// try to evaluate e1
e1 = evaluate(e1);
// exceptions need special treatment
return e1.isException() ? e1 : new Sequence(e1, e2);
}
// we're about to perform (SEQ-EXEC)
addProofStep("SEQ-EXEC", sequence);
// drop e1 from the sequence
return e2;
}
@SuppressWarnings("unused")
private Expression evaluateList(List list) {
// determine the sub expressions
Expression[] expressions = list.getExpressions();
// find the first sub expression that is not already a value
for (int n = 0; n < expressions.length; ++n) {
// check if the expression is not already a value
if (!expressions[n].isValue()) {
// we're about to perform (LIST)
addProofStep("LIST", list);
// try to evaluate the expression
Expression newExpression = evaluate(expressions[n]);
// check if we need to forward an exception
if (newExpression.isException()) {
return newExpression;
}
// otherwise generate a new list with the new expression
Expression[] newExpressions = expressions.clone();
newExpressions[n] = newExpression;
return new List(newExpressions);
}
}
// hm, can we get stuck here?
return list;
}
@SuppressWarnings("unused")
private Expression evaluateTuple(Tuple tuple) {
// determine the sub expressions
Expression[] expressions = tuple.getExpressions();
// find the first sub expression that is not already a value
for (int n = 0; n < expressions.length; ++n) {
// check if the expression is not already a value
if (!expressions[n].isValue()) {
// we're about to perform (TUPLE)
addProofStep("TUPLE", tuple);
// try to evaluate the expression
Expression newExpression = evaluate(expressions[n]);
// check if we need to forward an exception
if (newExpression.isException()) {
return newExpression;
}
// otherwise generate a new tuple with the new expression
Expression[] newExpressions = expressions.clone();
newExpressions[n] = newExpression;
return new Tuple(newExpressions);
}
}
// hm, can we get stuck here?
return tuple;
}
@SuppressWarnings("unused")
private Expression evaluateWhile(While loop) {
// determine the sub expressions
Expression e1 = loop.getE1();
Expression e2 = loop.getE2();
// we're about to perform (WHILE)
addProofStep("WHILE", loop);
// translate to: if e1 then (e2; while e1 do e2)
return new Condition1(e1, new Sequence(e2, loop));
}
//
// Application methods
//
private Expression apply(Application application, Expression e1, Expression e2) {
try {
// determine the specific apply method
Method method = lookupMethod("apply", e1.getClass());
// invoke the specific apply method
return (Expression)method.invoke(this, application, e1, e2);
}
catch (NoSuchMethodException e) {
// no way to further evaluate the expression
return application;
}
catch (Exception e) {
// rethrow as runtime exception, as some-
// thing is really completely broken
throw new RuntimeException(e);
}
}
@SuppressWarnings("unused")
private Expression applyLambda(Application application, Lambda lambda, Expression v) {
addProofStep("BETA-V", application);
return lambda.getE().substitute(lambda.getId(), v);
}
@SuppressWarnings("unused")
private Expression applyMultiLambda(Application application, MultiLambda multiLambda, Expression v) {
try {
// v must be a tuple with the appropriate arity
Tuple tuple = (Tuple)v;
if (tuple.getArity() != multiLambda.getArity()) {
return application;
}
// perform the substitutions
String[] identifiers = multiLambda.getIdentifiers();
Expression[] expressions = tuple.getExpressions();
Expression e = multiLambda.getE();
for (int n = 0; n < identifiers.length; ++n) {
e = e.substitute(identifiers[n], expressions[n]);
}
// yep, that was (BETA-V) then
addProofStep("BETA-V", application);
// and return the new expression
return e;
}
catch (ClassCastException exception) {
// v is not a tuple
return application;
}
}
@SuppressWarnings("unused")
private Expression applyApplication(Application application, Application app1, Expression e2) {
try {
// try to perform the application
return handleBinaryOperator(application, (BinaryOperator)app1.getE1(), app1.getE2(), e2);
}
catch (ClassCastException exception) {
// the first application is not what we need
return application;
}
}
@SuppressWarnings("unused")
private Expression applyDeref(Application application, Deref deref, Location location) {
try {
// lookup the expression for the location
Expression e = this.store.get(location);
// we performed (DEREF)
addProofStep("DEREF", application);
// and return the expression
return e;
}
catch (IllegalArgumentException e) {
// the location is invalid
return application;
}
}
@SuppressWarnings("unused")
private Expression applyRef(Application application, Ref ref, Expression e) {
// we're about to perform (REF)
addProofStep("REF", application);
// allocate a new location and store the value
MutableStore store = new MutableStore(this.store);
Location location = store.alloc();
store.put(location, e);
this.store = store;
// return the new location
return location;
}
@SuppressWarnings("unused")
private Expression applyUnaryOperator(Application application, UnaryOperator operator, Expression e) {
try {
// try to perform the application
e = operator.applyTo(e);
// determine the appropriate rule
if (operator instanceof Fst) {
addProofStep("FST", application);
}
else if (operator instanceof Snd) {
addProofStep("SND", application);
}
else if (operator instanceof Projection) {
addProofStep("PROJ", application);
}
else {
addProofStep("UOP", application);
}
// and return the new expression
return e;
}
catch (UnaryOperatorException exception) {
// we're stuck
return application;
}
}
@SuppressWarnings("unused")
private Expression applyHd(Application application, Hd hd, Expression e) {
// check if e is the empty list
if (e == EmptyList.EMPTY_LIST) {
addProofStep("HD-EMPTY", application);
return Exn.EMPTY_LIST;
}
// check if e is a list
if (e instanceof List) {
addProofStep("HD", application);
return ((List)e).head();
}
// otherwise try to return the first list item
try {
// e must be an application of cons to a pair
Application app1 = (Application)e;
Tuple tuple = (Tuple)app1.getE2();
if (!(app1.getE1() instanceof UnaryCons) || tuple.getArity() != 2) {
// we're stuck
return application;
}
// jep, we can perform (HD) then
addProofStep("HD", application);
// return the first item
return tuple.getExpressions(0);
}
catch(ClassCastException exception) {
// we're stuck
return application;
}
}
@SuppressWarnings("unused")
private Expression applyTl(Application application, Tl tl, Expression e) {
// check if e is the empty list
if (e == EmptyList.EMPTY_LIST) {
addProofStep("TL-EMPTY", application);
return Exn.EMPTY_LIST;
}
// check if e is a list
if (e instanceof List) {
addProofStep("TL", application);
return ((List)e).tail();
}
// otherwise try to return the remaining list
try {
// e must be an application of cons to a pair
Application app1 = (Application)e;
Tuple tuple = (Tuple)app1.getE2();
if (!(app1.getE1() instanceof UnaryCons) || tuple.getArity() != 2) {
// we're stuck
return application;
}
// jep, we can perform (TL) then
addProofStep("TL", application);
// return the remaining list
return tuple.getExpressions(1);
}
catch(ClassCastException exception) {
// we're stuck
return application;
}
}
@SuppressWarnings("unused")
private Expression applyIsEmpty(Application application, IsEmpty isEmpty, Expression e) {
// check if e is the empty list, or an application of cons to a value, or a list
if (e == EmptyList.EMPTY_LIST) {
addProofStep("IS-EMPTY-TRUE", application);
return BooleanConstant.TRUE;
}
else if ((e instanceof List)
|| (e instanceof Application
&& ((Application)e).getE1() instanceof UnaryCons
&& ((Application)e).getE2().isValue())) {
addProofStep("IS-EMPTY-FALSE", application);
return BooleanConstant.FALSE;
}
else {
// we're stuck
return application;
}
}
//
// Helper methods
//
/**
* Adds a new proof step with the specified <code>ruleName</code>
* for the given <code>expression</code>.
*
* @param ruleName the name of the {@link SmallStepProofRule} to add as proof step.
* @param expression the {@link Expression} for the proof step.
*/
private void addProofStep(String ruleName, Expression expression) {
this.steps.add(new ProofStep(expression, SmallStepProofRule.getRule(ruleName)));
}
private Expression handleBinaryOperator(Expression applicationOrInfix, BinaryOperator op, Expression e1, Expression e2) {
try {
// check if we have (ASSIGN), (BOP) or (CONS)
if (op instanceof Assign) {
// we can perform (ASSIGN) now
addProofStep("ASSIGN", applicationOrInfix);
// change the value at the memory location
MutableStore store = new MutableStore(this.store);
store.put((Location)e1, e2);
this.store = store;
// return nothing
return UnitConstant.UNIT;
}
else {
// try to perform the operation
Expression e = op.applyTo(e1, e2);
// yep, that was (BOP) or (CONS) then
addProofStep((op instanceof BinaryCons) ? "CONS" : "BOP", applicationOrInfix);
// return the new expression
return e;
}
}
catch (BinaryOperatorException exception) {
// cannot apply binary operator, we're stuck
return applicationOrInfix;
}
}
private Method lookupMethod(String baseName, Class clazz) throws NoSuchMethodException {
// try for this class and all super classes up to Expression
for (; clazz != Expression.class; clazz = clazz.getSuperclass()) {
// try to find a suitable method
Method[] methods = getClass().getDeclaredMethods();
for (Method method : methods) {
if (method.getName().equals(baseName + clazz.getSimpleName()))
return method;
}
}
throw new NoSuchMethodException(baseName);
}
}