package edu.ucsd.arcum.interpreter.satisfier; import static com.google.common.collect.Lists.transform; import static edu.ucsd.arcum.ArcumPlugin.DEBUG; import static edu.ucsd.arcum.interpreter.ast.FormalParameter.getIdentifier; import static edu.ucsd.arcum.interpreter.ast.RealizationStatement.checkAndExtractSingleton; import static edu.ucsd.arcum.interpreter.fragments.ProgramFragmentFactory.EMBEDDED_VALUE_PREFIX; import java.util.*; import java.util.Map.Entry; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTNode; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import edu.ucsd.arcum.exceptions.ArcumError; import edu.ucsd.arcum.exceptions.SourceLocation; import edu.ucsd.arcum.exceptions.Unreachable; import edu.ucsd.arcum.interpreter.ast.*; import edu.ucsd.arcum.interpreter.ast.expressions.*; import edu.ucsd.arcum.interpreter.fragments.ProgramFragment; import edu.ucsd.arcum.interpreter.fragments.ProgramFragmentFactory; import edu.ucsd.arcum.interpreter.fragments.ResolvedEntity; import edu.ucsd.arcum.interpreter.fragments.SubtreeList; import edu.ucsd.arcum.interpreter.parser.ArcumStructureParser.EmbeddedExpression; import edu.ucsd.arcum.interpreter.query.*; public class Satisfier { private final ConstraintExpression expression; private TypeLookupTable savedTypesForCallback; // EXAMPLE: edb and ast are possible examples of a union or either/or kind of selection idiom // (although in the current form, only edb can be null, due to the <..> feature) // edb may be null private EntityDataBase edb; private OptionMatchTable table; public AST ast; public Satisfier(ConstraintExpression expression) { this.expression = expression; } public Collection<List<EntityTuple>> getMatches(List<TraitSignature> traitSignature, EntityDataBase edb, OptionMatchTable optionMatchTable) { BindingsSet set; Collection<List<EntityTuple>> entityTuples; clearGlobals(); try { TypeLookupTable types = new TypeLookupTable(traitSignature); this.edb = edb; this.table = optionMatchTable; this.ast = newAST(); BindingMap bindingMap = optionMatchTable.extractAsBindings(); set = sat(expression, types, EntityType.ERROR, bindingMap, null); entityTuples = set.extractAsEntityTuples(traitSignature); return entityTuples; } finally { clearGlobals(); } } public NodesWithLocations generateSingletons(List<TraitSignature> tuplesRealized, OptionMatchTable optionMatchTable, OptionInterface optionIntf) { final BindingMap bindingMap; final BindingsSet set; final Collection<List<EntityTuple>> entityTuples; final List<EntityTuple> singletons; final List<EntityTuple> optionLevelSingletons; final List<TraitValue> builtIns; final NodesWithLocations result; clearGlobals(); try { TypeLookupTable types = new TypeLookupTable(tuplesRealized); this.edb = null; this.table = optionMatchTable; this.ast = newAST(); bindingMap = optionMatchTable.extractAsBindings(); set = sat(expression, types, EntityType.ERROR, bindingMap, null); entityTuples = set.extractAsEntityTuples(tuplesRealized); singletons = checkAndExtractSingleton(optionMatchTable, entityTuples); optionLevelSingletons = Lists.newArrayList(); BindingMap onlyEntry = set.iterator().next(); builtIns = onlyEntry.extractBuiltInTraits(); for (EntityTuple singleton : singletons) { String anonymousOrNamedAbstract = singleton.getType().getName(); if (!optionIntf.hasSingletonMember(anonymousOrNamedAbstract)) { optionLevelSingletons.add(singleton); } } result = new NodesWithLocations(optionLevelSingletons, builtIns); return result; } finally { clearGlobals(); } } public NodesWithLocations generateLocals(List<TraitSignature> tuplesRealized, OptionMatchTable optionMatchTable) { final BindingMap bindingMap; final BindingsSet set; final Collection<List<EntityTuple>> entityTuples; final List<EntityTuple> locals; final List<TraitValue> builtIns; final NodesWithLocations result; clearGlobals(); try { TypeLookupTable types = new TypeLookupTable(tuplesRealized); this.edb = null; this.table = optionMatchTable; this.ast = newAST(); bindingMap = optionMatchTable.extractAsBindings(); set = sat(expression, types, EntityType.ERROR, bindingMap, null); entityTuples = set.extractAsEntityTuples(tuplesRealized); locals = Lists.newArrayList(); for (List<EntityTuple> entityTupleList : entityTuples) { if (entityTupleList.size() != 1) { ArcumError.fatalError("Internal error: should only have one"); } EntityTuple entityTuple = entityTupleList.get(0); locals.add(entityTuple); } builtIns = Lists.newArrayList(); for (BindingMap generated : set) { builtIns.addAll(generated.extractBuiltInTraits()); } result = new NodesWithLocations(locals, builtIns); return result; } finally { clearGlobals(); } } public EntityTuple generateEntityReplacement(List<TraitSignature> tuplesRealized, OptionMatchTable optionMatchTable, EntityTuple entity, AST ast) { final BindingMap bindingMap; final BindingMap traitBindings; final BindingsSet set; final Collection<List<EntityTuple>> entityTuples; final List<EntityTuple> singletons; final List<TraitValue> builtIns; clearGlobals(); try { TypeLookupTable types = new TypeLookupTable(tuplesRealized); this.edb = null; this.table = optionMatchTable; this.ast = ast; bindingMap = optionMatchTable.extractAsBindings(); Map<String, Object> values = Maps.newHashMap(entity.getValues()); ASTNode rootNode = entity.getRootNode(); String rootName = null; Iterator<Entry<String, Object>> it = values.entrySet().iterator(); while (it.hasNext()) { Entry<String, Object> entry = it.next(); rootName = entry.getKey(); Object o = entry.getValue(); if (o == rootNode) { it.remove(); break; } } traitBindings = new BindingMap(values); bindingMap.addBindings(traitBindings); set = sat(expression, types, EntityType.ERROR, bindingMap, null); entityTuples = set.extractAsEntityTuples(tuplesRealized); singletons = checkAndExtractSingleton(optionMatchTable, entityTuples); EntityTuple unrooted = Iterables.getOnlyElement(singletons); values = unrooted.getValues(); Object rootValue = values.get(rootName); ASTNode root = rootValue instanceof ASTNode ? (ASTNode)rootValue : null; EntityTuple replacement = new EntityTuple(unrooted.getType(), values, root); return replacement; } finally { clearGlobals(); } } // Evaluates only (returns a Boolean result instead of a list of matches) public boolean evaluate(IEntityLookup lookup, EntityDataBase edb, OptionMatchTable symTab) { clearGlobals(); try { TypeLookupTable types = lookup.getTypeLookupTable(); this.edb = edb; this.table = symTab; this.ast = newAST(); BindingMap bindingMap = lookup.extractAsBindings(); BindingsSet sat = sat(expression, types, EntityType.ERROR, bindingMap, null); boolean result = representsTrue(sat, bindingMap); return result; } finally { clearGlobals(); } } public Object evaluateEntityValue(IEntityLookup lookup, EntityDataBase edb, OptionMatchTable symTab) { clearGlobals(); try { TypeLookupTable types = new TypeLookupTable(new ArrayList<TraitSignature>()); this.edb = edb; this.table = symTab; this.ast = newAST(); BindingMap bindingMap = lookup.extractAsBindings(); BindingsSet sat = sat(expression, types, EntityType.ERROR, bindingMap, null); for (BindingMap map : sat) { return map.getResult(); } return null; } finally { clearGlobals(); } } private boolean representsTrue(BindingsSet exprEvaluation, BindingMap originalInput) { return !exprEvaluation.isEquivalentTo(BindingsSet.newEmptySet()); } // matches against something already found in "in" or literal values (such as // Java class names) public BindingsSet immediateMatchingSat(ConstraintExpression phi, EntityType expectedType, BindingMap in) { if (savedTypesForCallback == null) { ArcumError.fatalError("Invalid callback"); } final EntityDataBase realEDB = this.edb; try { EntityDataBase unpopulatedEDB = new EntityDataBase(null); this.edb = unpopulatedEDB; return sat(phi, savedTypesForCallback, expectedType, in, null); } finally { this.edb = realEDB; } } private void debugOutput(BindingMap in, String varName) { Object entity = in.lookupEntity(varName); if (entity != null) { System.out.printf("%s == %s%n", varName, ASTUtil.getDebugString(entity)); } } // If the given entity data-base is null, then we generate matching entities. // Otherwise, we return actual entities in the program that match. private BindingsSet sat(ConstraintExpression phi, TypeLookupTable types, EntityType expectedType, BindingMap in, Object knownEntity) { final boolean matchingMode = (edb != null); if (phi instanceof UnificationExpression) { UnificationExpression unifyExpr = (UnificationExpression)phi; String lhsName = unifyExpr.getName(); Object lhsValue = in.lookupEntity(lhsName); ConstraintExpression rhs = unifyExpr.getRightHandSide(); EntityType type = types.lookupType(lhsName); BindingsSet result = BindingsSet.newEmptySet(); if (rhs instanceof VariableReferenceExpression) { VariableReferenceExpression vre = (VariableReferenceExpression)rhs; String rhsName = vre.getName(); Object rhsValue = in.lookupEntity(rhsName); System.out.printf("It's the special case: %s (%s), %s (%s)%n", lhsName, Entity.getDisplayString(lhsValue), rhsName, Entity .getDisplayString(rhsValue)); // TUESDAY ... if (lhsValue == null && rhsValue == null) { // Bind both vars to all instances of the most specific type } else if (lhsValue != null && rhsValue != null) { // Compare values: if equal including locations, return the // binding we started with if (Entity.compareToWithLocations(lhsValue, rhsValue) == 0) { result.addEntry(in); } } else { // Unify the unbound var with the already bound one String unboundName = (lhsValue == null) ? lhsName : rhsName; Object boundValue = (lhsValue == null) ? rhsValue : lhsValue; in.bind(unboundName, boundValue, type); result.addEntry(in); } } else { BindingsSet sat = sat(rhs, types, type, in, lhsValue); for (BindingMap theta : sat) { Object otherEntity = theta.getResult(); if (lhsValue != null && otherEntity != lhsValue) { continue; } theta.bindResultAs(lhsName); result.addEntry(theta); } } // System.out.printf("Unification %s%nWith binding map:%n %s,%nReturns: %s%n%n", // unifyExpr, in, result); return result; } else if (phi instanceof PatternExpression) { PatternExpression patternExpr = (PatternExpression)phi; CurrentBindingsLookup lookup = new CurrentBindingsLookup(table, in); if (patternExpr.hasEmbeddedExpressions()) { BindingMap embeddedBindings = BindingMap.newEmptyMap(); // evaluate and collapse all embedded expressions first List<EmbeddedExpression> embeds = patternExpr.getEmbeddedExpressions(); for (int embedIndex = 0; embedIndex < embeds.size(); ++embedIndex) { EmbeddedExpression embed = embeds.get(embedIndex); FormalParameter boundVar = embed.getBoundVar(); ConstraintExpression embeddedExpr = embed.getConstraintExpression(); TypeLookupTable nextScope = TypeLookupTable.newScope(types, boundVar); BindingsSet bigTheta = sat(embeddedExpr, nextScope, EntityType.ERROR, in, knownEntity); String projectionVar = boundVar.getIdentifier(); List<ProgramFragment> members = Lists.newArrayList(); for (BindingMap theta : bigTheta) { Object entity = theta.lookupEntity(projectionVar); ProgramFragment member = ResolvedEntity.newInstance(entity); members.add(member); } SubtreeList set = new SubtreeList(members, SubtreeList.Kind.UNORDERED); String setKey = String.format("%s%d", EMBEDDED_VALUE_PREFIX, embedIndex); embeddedBindings.bind(setKey, set, EntityType.PUNT); } lookup = new CurrentBindingsLookup(lookup, embeddedBindings); } BindingsSet result; if (matchingMode && !patternExpr.isImmediatePattern()) { if (knownEntity == null) { result = edb.enumerateMatchingBindings(patternExpr, expectedType, in, lookup, types); } else { result = edb.immeditateMatchingBinding(patternExpr, expectedType, in, lookup, types, knownEntity); } } else { final ProgramFragmentFactory builder; final List<ProgramFragment> fragments; builder = new ProgramFragmentFactory(patternExpr, expectedType, lookup, types, false); fragments = builder.getAbstractProgramFragments(); result = BindingsSet.newEmptySet(); for (ProgramFragment fragment : fragments) { BindingMap theta; if (fragment.isResolved()) { theta = fragment.matchResolvedEntity(); } else { theta = fragment.generateNode(lookup, ast); } if (theta != null) { Object rootEntity = theta.getResult(); if (rootEntity == null) { ArcumError.fatalError("This shouldn't happen"); } BindingMap merge = in.consistentMerge(theta); if (merge != null) { BindingMap mergedWithRoot = new BindingMap(rootEntity); mergedWithRoot.addBindings(merge); result.addEntry(mergedWithRoot); } } } } return result; } else if (phi instanceof BooleanDisjunction) { BooleanDisjunction disjunction = (BooleanDisjunction)phi; Collection<? extends ConstraintExpression> disjuncts; disjuncts = disjunction.getClauses(); // an empty disjunct is equivalent to false BindingsSet result = BindingsSet.newEmptySet(); for (ConstraintExpression disjunct : disjuncts) { BindingsSet sat = sat(disjunct, types, expectedType, in, knownEntity); result = result.union(sat); } return result; } else if (phi instanceof BooleanConjunction) { // For each conjunction, collect the newly declared variables and treat // the conjunction as if it were in an "exists" BooleanConjunction conjunction = (BooleanConjunction)phi; Collection<? extends ConstraintExpression> conjuncts; conjuncts = conjunction.getClauses(); if (conjuncts.isEmpty()) { // an empty conjunct is equivalent to true return trueResult(in); } else { Iterator<? extends ConstraintExpression> iterator = conjuncts.iterator(); ConstraintExpression conjunct = iterator.next(); BindingsSet bigTheta = sat(conjunct, types, expectedType, in, knownEntity); while (iterator.hasNext()) { conjunct = iterator.next(); BindingsSet result = BindingsSet.newEmptySet(); for (BindingMap theta : bigTheta) { BindingsSet sat = sat(conjunct, types, expectedType, theta, knownEntity); result = result.union(sat); } bigTheta = result; } return bigTheta; } } else if (phi instanceof BooleanEquivalence) { BooleanEquivalence equivalence = (BooleanEquivalence)phi; Collection<ConstraintExpression> terms = equivalence.getClauses(); Iterator<ConstraintExpression> iterator = terms.iterator(); ConstraintExpression term = iterator.next(); BindingsSet first = sat(term, types, expectedType, in, knownEntity); while (iterator.hasNext()) { term = iterator.next(); BindingsSet current = sat(term, types, expectedType, in, knownEntity); if (!first.isEquivalentTo(current)) { return falseResult(in); } } return trueResult(in); } else if (phi instanceof TrueLiteral) { return trueResult(in); } else if (phi instanceof FalseLiteral) { return falseResult(in); } else if (phi instanceof BooleanNegation) { BooleanNegation negation = (BooleanNegation)phi; ConstraintExpression operand = negation.getOperand(); BindingsSet sat = sat(operand, types, expectedType, in, knownEntity); if (representsTrue(sat, in)) { return falseResult(in); } else { return trueResult(in); } } else if (phi instanceof FunctionalExpression) { FunctionalExpression func = (FunctionalExpression)phi; CurrentBindingsLookup lookup = new CurrentBindingsLookup(table, in); TypeLookupTable prevValue = this.savedTypesForCallback; try { this.savedTypesForCallback = types; BindingsSet result = func.evaluate(in, lookup, this, matchingMode); // System.out.printf("Function %s%nWith binding map:%n %s,%nReturns: %s%n%n", // func, in, result); return result; } finally { this.savedTypesForCallback = prevValue; } } else if (phi instanceof VariableReferenceExpression) { VariableReferenceExpression refExpr = (VariableReferenceExpression)phi; String varName = refExpr.getName(); Object entity = in.lookupEntity(varName); if (entity == null) { if (refExpr.isSpecialAnyVariable()) { entity = new VariablePlaceholder(refExpr.getPosition()); } else if (types.hasInformationFor(varName)) { entity = new VariablePlaceholder(refExpr, types.lookupType(varName)); } else { // May have been a typo, in which case it's a user error, not a // match not found ArcumError.fatalUserError(phi.getPosition(), "Variable \"%s\" has not been declared or is not in scope", varName); throw new Unreachable(); } } BindingsSet result = singleResult(in, entity); return result; } else if (phi instanceof UniversalQuantifier) { UniversalQuantifier forallExpr = (UniversalQuantifier)phi; List<FormalParameter> boundVars = forallExpr.getBoundVars(); TypeLookupTable nextScope = TypeLookupTable.newScope(types, boundVars); ConstraintExpression initExpr = forallExpr.getInitialSet(); BindingsSet bigTheta = sat(initExpr, nextScope, EntityType.ERROR, in, knownEntity); ConstraintExpression body = forallExpr.getBody(); boolean allTrue = true; eachElementCheck: for (BindingMap theta : bigTheta) { if (DEBUG) { List<String> entityReps = Lists.newArrayList(); for (FormalParameter boundVar : boundVars) { String id = boundVar.getIdentifier(); Object entity = theta.lookupEntity(id); entityReps.add(String.format("%s=%s", id, Entity .getDisplayString(entity))); } System.err.printf("Now checking: %s on %s%n", entityReps, body); } BindingsSet sat = sat(body, nextScope, expectedType, theta, knownEntity); if (!representsTrue(sat, theta)) { allTrue = false; if (forallExpr.hasErrorMessage()) { ErrorMessage message = forallExpr.getErrorMessage(); CurrentBindingsLookup lookup = new CurrentBindingsLookup(table, theta); String text = message.getMessage(lookup); FormalParameter rootVar = boundVars.get(0); SourceLocation location; if (message.hasLocation()) { location = message.getLocation(lookup, edb, table); } else { Object root = lookup.lookupEntity(rootVar.getIdentifier()); location = SourceLocation.fromEntity(root, edb); } ArcumError.userError(location, "%s", text); } else { // short-circuit: no detailed error messages are needed, so // we don't have to continue break eachElementCheck; } } } return (allTrue) ? trueResult(in) : falseResult(in); } else if (phi instanceof ExistentialQuantifier) { ExistentialQuantifier existsExpr = (ExistentialQuantifier)phi; List<FormalParameter> boundVars = existsExpr.getBoundVars(); Set<String> varNames = Sets.newHashSet(transform(boundVars, getIdentifier)); TypeLookupTable nextScope = TypeLookupTable.newScope(types, boundVars); ConstraintExpression body = existsExpr.getBody(); BindingsSet bigTheta = sat(body, nextScope, EntityType.ERROR, in, knownEntity); BindingsSet result = BindingsSet.newEmptySet(); for (BindingMap theta : bigTheta) { result.addEntry(theta.withVarsRemoved(varNames)); } return result; } else if (phi instanceof SelectExpression) { SelectExpression condExpr = (SelectExpression)phi; List<ConstraintExpression> conditions = condExpr.getConditions(); List<ConstraintExpression> values = condExpr.getValues(); Iterator<ConstraintExpression> valueIter = values.iterator(); BindingsSet result = BindingsSet.newEmptySet(); boolean allConditionsFailed = true; for (ConstraintExpression condition : conditions) { ConstraintExpression value = valueIter.next(); BindingsSet conditionResult = sat(condition, types, expectedType, in, knownEntity); if (representsTrue(conditionResult, in)) { allConditionsFailed = false; BindingsSet conditionValue = sat(value, types, expectedType, in, knownEntity); result = result.union(conditionValue); } } if (allConditionsFailed) { ConstraintExpression elseValue = valueIter.next(); result = sat(elseValue, types, expectedType, in, knownEntity); } return result; } else { ArcumError.fatalError("Can't handle this case: %s%n", phi.getClass()); return null; } } public static BindingsSet singleResult(BindingMap in, Object entity) { BindingMap theta = new BindingMap(entity); theta = theta.consistentMerge(in); BindingsSet result = BindingsSet.newSet(theta); return result; } public static BindingsSet trueResult(BindingMap in) { return BindingsSet.newSet(in); } public static BindingsSet falseResult(BindingMap in) { return BindingsSet.newEmptySet(); } private void clearGlobals() { this.savedTypesForCallback = null; this.edb = null; this.table = null; this.ast = null; } private AST newAST() { return AST.newAST(AST.JLS3); } }