package typing;
import java.util.Enumeration;
import javax.swing.event.EventListenerList;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import expressions.Lambda;
import expressions.And;
import expressions.Application;
import expressions.Condition;
import expressions.Constant;
import expressions.Expression;
import expressions.Identifier;
import expressions.InfixOperation;
import expressions.Let;
import expressions.LetRec;
import expressions.Or;
import expressions.Projection;
import expressions.Recursion;
import expressions.Tuple;
/**
* The tree of proof nodes required to prove a
* type judgement.
*
* @author Benedikt Meurer
* @version $Id$
*/
public final class ProofTree implements TreeModel, TypeVariableAllocator {
/**
* Allocates a new proof tree with an inital type
* <code>environment</code> and the <code>expression</code>
* whose type should be determined.
*
* @param environment the inital type {@link Environment}.
* @param expression the {@link expressions.Expression} whose type
* should be determined.
*/
ProofTree(Environment environment, Expression expression) {
// allocate the new judgement
Judgement judgement = new Judgement(environment, expression, allocateTypeVariable());
// allocate the root node
this.root = new ProofNode(judgement);
}
/**
* {@inheritDoc}
*
* @see javax.swing.tree.TreeModel#addTreeModelListener(javax.swing.event.TreeModelListener)
*/
public void addTreeModelListener(TreeModelListener l) {
this.listenerList.add(TreeModelListener.class, l);
}
/**
* {@inheritDoc}
*
* @see javax.swing.tree.TreeModel#removeTreeModelListener(javax.swing.event.TreeModelListener)
*/
public void removeTreeModelListener(TreeModelListener l) {
this.listenerList.remove(TreeModelListener.class, l);
}
/**
* {@inheritDoc}
*
* @see javax.swing.tree.TreeModel#getChild(java.lang.Object, int)
*/
public ProofNode getChild(Object parent, int index) {
return ((ProofNode)parent).getChildAt(index);
}
/**
* {@inheritDoc}
*
* @see javax.swing.tree.TreeModel#getChildCount(java.lang.Object)
*/
public int getChildCount(Object parent) {
return ((ProofNode)parent).getChildCount();
}
/**
* {@inheritDoc}
*
* @see javax.swing.tree.TreeModel#getIndexOfChild(java.lang.Object, java.lang.Object)
*/
public int getIndexOfChild(Object parent, Object child) {
return ((ProofNode)parent).getIndex((ProofNode)child);
}
/**
* {@inheritDoc}
*
* @see javax.swing.tree.TreeModel#getRoot()
*/
public ProofNode getRoot() {
return this.root;
}
/**
* {@inheritDoc}
*
* @see javax.swing.tree.TreeModel#isLeaf(java.lang.Object)
*/
public boolean isLeaf(Object node) {
return (getChildCount(node) == 0);
}
/**
* {@inheritDoc}
*
* This method is not implemented for the {@link ProofTree} class,
* and will simply throw and {@link UnsupportedOperationException}
* exception when being invoked.
*
* @see javax.swing.tree.TreeModel#valueForPathChanged(javax.swing.tree.TreePath, java.lang.Object)
*/
public void valueForPathChanged(TreePath path, Object newValue) {
throw new UnsupportedOperationException("valueForPathChanged is not supported for ProofTree");
}
/**
* {@inheritDoc}
*
* @see typing.TypeVariableAllocator#allocateTypeVariable()
*/
public TypeVariable allocateTypeVariable() {
return new TypeVariable("\u03B1" + this.nextTypeVariable++);
}
/**
* Applies <code>rule</code> for the <code>node</code> in
* this proof tree and returns the new proof tree that is
* the result of applying <code>rule</code> at <code>node</code>.
*
* The <code>node</code> must be a valid node for the proof tree,
* and no type rule must have been applied to <code>node</code>
* already, that is {@link ProofNode#getRule()} must return
* <code>null</code> for <code>node</code>.
*
* @param rule the {@link Rule} to apply at <code>node</code>
* @param node the {@link ProofNode} at which to apply <code>rule</code>.
*
* @return the resulting {@link ProofTree}.
*
* @throws IllegalArgumentException if the <code>node</code> is not
* valid for the tree or the <code>node</code>
* is already proven.
* @throws ProofRuleException if the <code>rule</code> cannot be applied to
* the <code>node</code>.
* @throws UnificationException if the unification failed.
* @throws UnknownIdentifierException if an identifier could not be found in the
* type environment of a judgement.
*
* @see ProofNode#getRule()
*/
public ProofTree apply(Rule rule, ProofNode node) throws InvalidRuleException, UnificationException, UnknownIdentifierException {
return applyWithGuessedType(rule, node, null);
}
/**
* Guesses the given <code>type</code> for the <code>node</code> and
* returns the new proof tree that is the result of setting the type
* of <code>node</code> to <code>type</code>.
*
* The <code>node</code> must be a valid node for the proof tree,
* and no type rule must have been applied to <code>node</code>
* already, that is {@link ProofNode#getRule()} must return
* <code>null</code> for <code>node</code>.
*
* @param type the {@link MonoType} to set for <code>node</code>.
* @param node the {@link ProofNode} at which to apply <code>rule</code>.
*
* @return the resulting {@link ProofTree}.
*
* @throws IllegalArgumentException if the <code>node</code> is not
* valid for the tree or the <code>node</code>
* is already proven.
* @throws ProofRuleException shouldn't happen.
* @throws UnificationException if the unification failed.
* @throws UnknownIdentifierException if an identifier couldn't be found.
*/
public ProofTree guess(ProofNode node, MonoType type) throws InvalidRuleException, UnificationException, UnknownIdentifierException {
// verify that the node is valid for the tree
if (this.root != node && !this.root.containsChild(node))
throw new IllegalArgumentException("The proof node is not valid for the proof tree");
// determine the judgement
Judgement judgement = node.getJudgement();
// determine the judgement parameters
Environment environment = judgement.getEnvironment();
Expression expression = judgement.getExpression();
// determine the rule from the expression
Rule rule = Rule.getRuleForExpression(expression, environment);
// apply the rule to the tree with the guessed type
ProofTree tree = applyWithGuessedType(rule, node, type);
// finish the subtree
return tree.finishSubTreeAtExpression(expression);
}
/**
* Returns the {@link Judgement} for the <code>node</code> in this
* proof tree.
*
* @param node a {@link ProofNode} in this proof tree.
*
* @return the {@link Judgement} for the <code>node</code>.
*/
public Judgement getJudgementForNode(Object node) {
return ((ProofNode)node).getJudgement();
}
/**
* Returns {@link Rule} for the <code>node</code> in this
* proof tree.
*
* @param node a {@link ProofNode} in this proof tree.
*
* @return the {@link Rule} for the <code>node</code>.
*/
public Rule getRuleForNode(Object node) {
return ((ProofNode)node).getRule();
}
// allocates a new tree with the given root
private ProofTree(ProofNode root, int nextTypeVariable) {
this.nextTypeVariable = nextTypeVariable;
this.root = root;
}
private ProofTree applyWithGuessedType(Rule rule, ProofNode node, MonoType guessedTypeOrNull) throws InvalidRuleException, UnificationException, UnknownIdentifierException {
// verify that the node isn't already proven
if (node.getRule() != null)
throw new IllegalArgumentException("A type rule was already applied for the proof node");
// verify that the node is valid for the tree
if (this.root != node && !this.root.containsChild(node))
throw new IllegalArgumentException("The proof node is not valid for the proof tree");
// determine the judgement
Judgement judgement = node.getJudgement();
// determine the judgement attributes
MonoType tau = judgement.getType();
Expression expression = judgement.getExpression();
Environment environment = judgement.getEnvironment();
// allocate the new node as replacement for the node
ProofNode newNode = new ProofNode(judgement, rule);
// start with an empty equation list as base for the unification
EquationList equations = EquationList.EMPTY_LIST;
// backward apply the rule
if (expression instanceof Constant && rule == Rule.CONST) {
// generate a new type equation for the judgement type and the constant type
Type constType = Type.getTypeForExpression(expression);
if (constType instanceof MonoType)
equations = equations.extend(tau, (MonoType)constType);
else
throw new InvalidRuleException(node, rule);
}
else if ((expression instanceof Constant
|| expression instanceof Projection)
&& rule == Rule.P_CONST) {
// generate a new type equation for the judgement type and the
// instantiated (polymorphic) constant type
MonoType constType = instantiate(Type.getTypeForExpression(expression));
equations = equations.extend(tau, constType);
}
else if (expression instanceof Identifier && rule == Rule.ID) {
// generate a new type equation for the judgement type and the identifier type
Type idType = environment.get(((Identifier)expression).getName());
if (idType instanceof MonoType)
equations = equations.extend(tau, (MonoType)idType);
else
throw new InvalidRuleException(node, rule);
}
else if (expression instanceof Identifier && rule == Rule.P_ID) {
// generate a new type equation for the judgement type and the
// instantiated (polymorphic) identifier type
MonoType idType = instantiate(environment.get(((Identifier)expression).getName()));
equations = equations.extend(tau, idType);
}
else if (expression instanceof Application && rule == Rule.APP) {
// split into tau1 and tau2 for the application
TypeVariable tau2 = allocateTypeVariable();
ArrowType tau1 = new ArrowType(tau2, tau);
// generate new sub nodes
Application application = (Application)expression;
newNode.addChild(new Judgement(environment, application.getE1(), tau1));
newNode.addChild(new Judgement(environment, application.getE2(), tau2));
}
else if (expression instanceof Condition && rule == Rule.COND) {
// generate new sub nodes
Condition condition = (Condition)expression;
newNode.addChild(new Judgement(environment, condition.getE0(), PrimitiveType.BOOL));
newNode.addChild(new Judgement(environment, condition.getE1(), tau));
newNode.addChild(new Judgement(environment, condition.getE2(), tau));
}
else if (expression instanceof Lambda && rule == Rule.ABSTR) {
// generate new type variables
TypeVariable tau1 = allocateTypeVariable();
TypeVariable tau2 = allocateTypeVariable();
// add type equations for tau and tau1->tau2
equations = equations.extend(tau, new ArrowType(tau1, tau2));
// generate a new sub node
Lambda abstraction = (Lambda)expression;
newNode.addChild(new Judgement(environment.extend(abstraction.getId(), tau1), abstraction.getE(), tau2));
}
else if (expression instanceof Let && rule == Rule.LET) {
// generate a new type variable
TypeVariable tau1 = allocateTypeVariable();
// generate new sub nodes
Let let = (Let)expression;
newNode.addChild(new Judgement(environment, let.getE1(), tau1));
newNode.addChild(new Judgement(environment.extend(let.getId(), tau1), let.getE2(), tau));
}
else if (expression instanceof Let && rule == Rule.P_LET) {
// generate a new type variable
TypeVariable tau1 = allocateTypeVariable();
// generate only the first sub node, the second one will
// be added once the first sub tree is finished, see
// ProofNode.cloneSubstituteAndReplace()
Let let = (Let)expression;
newNode.addChild(new Judgement(environment, let.getE1(), tau1));
}
else if (expression instanceof LetRec && rule == Rule.LET_REC) {
// generate a new type variable
TypeVariable tau1 = allocateTypeVariable();
// generate new sub nodes
LetRec letRec = (LetRec)expression;
newNode.addChild(new Judgement(environment.extend(letRec.getId(), tau1), letRec.getE1(), tau1));
newNode.addChild(new Judgement(environment.extend(letRec.getId(), tau1), letRec.getE2(), tau));
}
else if (expression instanceof Recursion && rule == Rule.REC) {
// generate a new type variable
TypeVariable tau1 = allocateTypeVariable();
// add equation tau = tau1
equations = equations.extend(tau, tau1);
// generate new sub node
Recursion recursion = (Recursion)expression;
newNode.addChild(new Judgement(environment.extend(recursion.getId(), tau1), recursion.getE(), tau1));
}
else if (expression instanceof InfixOperation && rule == Rule.INFIX) {
// generate two new type variables
TypeVariable tau1 = allocateTypeVariable();
TypeVariable tau2 = allocateTypeVariable();
// generate new sub nodes
InfixOperation operation = (InfixOperation)expression;
newNode.addChild(new Judgement(environment, operation.getOp(), new ArrowType(tau1, new ArrowType(tau2, tau))));
newNode.addChild(new Judgement(environment, operation.getE1(), tau1));
newNode.addChild(new Judgement(environment, operation.getE2(), tau2));
}
else if (expression instanceof And && rule == Rule.AND) {
// add equation tau = bool
equations = equations.extend(tau, PrimitiveType.BOOL);
// generate new sub nodes
And and = (And)expression;
newNode.addChild(new Judgement(environment, and.getE1(), PrimitiveType.BOOL));
newNode.addChild(new Judgement(environment, and.getE2(), PrimitiveType.BOOL));
}
else if (expression instanceof Or && rule == Rule.OR) {
// add equation tau = bool
equations = equations.extend(tau, PrimitiveType.BOOL);
// generate new sub nodes
Or or = (Or)expression;
newNode.addChild(new Judgement(environment, or.getE1(), PrimitiveType.BOOL));
newNode.addChild(new Judgement(environment, or.getE2(), PrimitiveType.BOOL));
}
else if (expression instanceof Tuple && rule == Rule.TUPLE) {
// cast to tuple expression
Tuple tuple = (Tuple)expression;
// allocate type variables for the tuple type
TypeVariable[] types = new TypeVariable[tuple.getArity()];
Expression[] expressions = tuple.getExpressions();
for (int n = 0; n < types.length; ++n) {
// allocate a type variable for this subexpression
types[n] = allocateTypeVariable();
// allocate a type node for the subexpression
newNode.addChild(new Judgement(environment, expressions[n], types[n]));
}
// add equation tau = tau1 * ... * taun
equations = equations.extend(tau, new TupleType(types));
}
else {
// well, not possible then
throw new InvalidRuleException(node, rule);
}
// add an equation for the guessed type if any
if (guessedTypeOrNull != null)
equations = equations.extend(guessedTypeOrNull, judgement.getType());
// determine the unificator
Substitution substitution = equations.unify();
// allocate a root item for the new tree
ProofNode newRoot = this.root.cloneSubstituteAndReplace(substitution, node, newNode, this);
// allocate the new tree
return new ProofTree(newRoot, nextTypeVariable);
}
private ProofTree finishSubTreeAtExpression(Expression expression) throws UnknownIdentifierException, InvalidRuleException, UnificationException {
// determine the node for the expression
ProofNode node = this.root.findNodeByExpression(expression);
if (node != null && !node.isFinished()) {
// check if the node is not already done
if (node.getRule() == null) {
// determine the rule to apply for the expression
Rule rule = Rule.getRuleForExpression(expression, node.getJudgement().getEnvironment());
// apply the rule for the node
ProofTree tree = apply(rule, node);
// and start once again
return tree.finishSubTreeAtExpression(expression);
}
else if (node.getChildCount() > 0) {
// start with the current tree
ProofTree tree = this;
// process all children for the first time
for (Enumeration children = node.children(); children.hasMoreElements(); )
tree = tree.finishSubTreeAtExpression(((ProofNode)children.nextElement()).getJudgement().getExpression());
// return the resulting tree, processing it a second time, because some rules like
// (P-LET) require a second pass on the tree
return tree.finishSubTreeAtExpression(expression);
}
}
return this;
}
private MonoType instantiate(Type type) {
if (type instanceof PolyType) {
PolyType polyType = (PolyType)type;
return polyType.instantiate(this);
}
else {
return (MonoType)type;
}
}
// member attributes
private EventListenerList listenerList = new EventListenerList();
private int nextTypeVariable;
private ProofNode root;
}