package edu.ucsd.arcum.interpreter.ast.expressions; import static com.google.common.collect.Lists.transform; import static edu.ucsd.arcum.ArcumPlugin.DEBUG; import static edu.ucsd.arcum.util.StringUtil.separate; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.List; import java.util.Set; import org.eclipse.jdt.core.dom.*; import com.google.common.base.Function; import com.google.common.collect.Lists; import edu.ucsd.arcum.exceptions.ArcumError; import edu.ucsd.arcum.exceptions.SourceLocation; import edu.ucsd.arcum.interpreter.ast.TraitSignature; import edu.ucsd.arcum.interpreter.fragments.SignatureEntity; import edu.ucsd.arcum.interpreter.query.Entity; import edu.ucsd.arcum.interpreter.query.EntityType; import edu.ucsd.arcum.interpreter.query.IEntityLookup; import edu.ucsd.arcum.interpreter.query.VariablePlaceholder; import edu.ucsd.arcum.interpreter.satisfier.BindingMap; import edu.ucsd.arcum.interpreter.satisfier.BindingsSet; import edu.ucsd.arcum.interpreter.satisfier.Satisfier; // A BuildInFunction cannot be used for binding elements: they can only check // properties of already bound elements. This limitation is to avoid, for // example, the use of isPrivate(a) returning every "a" that is private. If // there is a use-case for this in the future this could be revisited, but the // current implementation means it would consume a lot of memory. // // MACNEIL: Could include a type checking phase for these functions, to be // sure that their args are the right type, instead of waiting until evaluation public enum BuiltInFunction implements IFunction { HAS_SIGNATURE("hasSignature", EntityType.TRAIT, EntityType.SIGNATURE) { public boolean _evaluate(Object typeWrapped, Object signatureWrapped) { ITypeBinding type = unwrap(ITypeBinding.class, typeWrapped); SignatureEntity signature = unwrap(SignatureEntity.class, signatureWrapped); IMethodBinding[] methods = type.getDeclaredMethods(); for (IMethodBinding method : methods) { if (signature.hasSameSignatureAs(method)) { return true; } } return false; } }, // EXAMPLE: Ensure that a _evaluate method is defined for each type. This implies // there is a need for Arcum code to be embedded in Java code (perhaps), because // the check is local only to the class an likely does not need to be generalized, // although a generalized form would fit the notion of soft interfaces WITHIN("within", EntityType.ANY, EntityType.ANY) { // DOCUMENTATION: Currently only supports ASTNodes, although types are a // clear choice for the second parameter as well public boolean _evaluate(Object element, Object container) { ASTNode node = unwrap(ASTNode.class, element); ASTNode potentialParent = unwrap(ASTNode.class, container); for (;;) { ASTNode parent = node.getParent(); if (parent == null) break; if (parent == potentialParent) return true; node = parent; } return false; } }, SAME_TYPE("sameType", EntityType.TYPE, EntityType.TYPE) { public boolean _evaluate(Object e1, Object e2) { return Entity.compareTo(e1, e2) == 0; } }, IS_A("isA", EntityType.ANY /*EXPR or TYPE*/, EntityType.TYPE) { public boolean _evaluate(Object typeLHSWrapped, Object typeRHSWrapped) { // GETDONE Was here last... in transformation mode, we should make this one pass? // OR, we should store away what was true and keep track of the bindings // under the generate and test scheme, we could do the wrong thing! ITypeBinding lhsType = coerce(ITypeBinding.class, typeLHSWrapped); ITypeBinding rhsType = unwrap(ITypeBinding.class, typeRHSWrapped); // POSSIBLE_ECLIPSE_BUG: It would return "false" for // two different bindings of java.lang.String, yet the isEqualTo works, // so we need to call our own isAssignableFrom (below) in the mean time boolean equal = isAssignableFrom(rhsType, lhsType); boolean canAssign = lhsType.isAssignmentCompatible(rhsType); boolean result = equal || canAssign; return result; } private boolean isAssignableFrom(ITypeBinding variableType, ITypeBinding exprType) { if (exprType == null) { return false; } else if (exprType.isEqualTo(variableType)) { return true; } else { if (variableType.isInterface()) { ITypeBinding[] interfaces = exprType.getInterfaces(); for (ITypeBinding superinterface : interfaces) { if (isAssignableFrom(variableType, superinterface)) return true; } return false; } else { ITypeBinding superclass = exprType.getSuperclass(); return isAssignableFrom(variableType, superclass); } } } }, IS_JAVA_IDENTIFIER("isJavaIdentifier", EntityType.STRING) { public boolean _evaluate(Object strWrapped) { String str = unwrap(String.class, strWrapped); if (str.length() < 1) { return false; } if (!Character.isJavaIdentifierStart(str.charAt(0))) { return false; } for (int i = 1; i < str.length(); ++i) { if (!Character.isJavaIdentifierPart(str.charAt(i))) { return false; } } return true; } }, IS_REFERENCE_TYPE("isReferenceType", EntityType.TYPE) { public boolean _evaluate(Object value) { if (value instanceof ITypeBinding) { ITypeBinding type = unwrap(ITypeBinding.class, value); return !type.isPrimitive(); } else { return (value instanceof AbstractTypeDeclaration); } } }, IS_EXPRESSION_STATEMENT("isExpressionStatement", EntityType.EXPR) { public boolean _evaluate(Object exprWrapped) { Expression expr = unwrap(Expression.class, exprWrapped); ASTNode parent = expr.getParent(); return (parent instanceof ExpressionStatement); } }, IS_CLASS("isClass", EntityType.TYPE) { public boolean _evaluate(Object typeWrapped) { ITypeBinding type = unwrap(ITypeBinding.class, typeWrapped); return type.isClass(); } }, IS_INTERFACE("isInterface", EntityType.TYPE) { public boolean _evaluate(Object typeWrapped) { ITypeBinding type = unwrap(ITypeBinding.class, typeWrapped); return type.isInterface(); } }, IS_ENUM("isEnum", EntityType.TYPE) { public boolean _evaluate(Object typeWrapped) { ITypeBinding type = unwrap(ITypeBinding.class, typeWrapped); return type.isEnum(); } }, IS_ANNOTATION_TYPE("isAnnotationType", EntityType.TYPE) { public boolean _evaluate(Object typeWrapped) { ITypeBinding type = unwrap(ITypeBinding.class, typeWrapped); return type.isAnnotation(); } }, IS_SIMPLE_ASSIGNMENT("isSimpleAssignment", EntityType.EXPR) { public boolean _evaluate(Object exprWrapped) { Expression expr = unwrap(Expression.class, exprWrapped); if (expr instanceof Assignment) { Assignment assignment = (Assignment)expr; return assignment.getOperator() == Assignment.Operator.ASSIGN; } return false; } }, IS_ABSTRACT("isAbstract", EntityType.FIELD) { public boolean _evaluate(Object fieldWrapped) { FieldDeclaration field = unwrap(FieldDeclaration.class, fieldWrapped); return testForModifier(field, new ModifierChecker() { public boolean check(Modifier modifier) { return modifier.isAbstract(); } }); } }, IS_FINAL("isFinal", EntityType.FIELD) { public boolean _evaluate(Object fieldWrapped) { FieldDeclaration field = unwrap(FieldDeclaration.class, fieldWrapped); return testForModifier(field, new ModifierChecker() { public boolean check(Modifier modifier) { return modifier.isFinal(); } }); } }, IS_NATIVE("isNative", EntityType.FIELD) { public boolean _evaluate(Object fieldWrapped) { FieldDeclaration field = unwrap(FieldDeclaration.class, fieldWrapped); return testForModifier(field, new ModifierChecker() { public boolean check(Modifier modifier) { return modifier.isNative(); } }); } }, IS_PRIVATE("isPrivate", EntityType.FIELD) { public boolean _evaluate(Object fieldWrapped) { FieldDeclaration field = unwrap(FieldDeclaration.class, fieldWrapped); return testForModifier(field, new ModifierChecker() { public boolean check(Modifier modifier) { return modifier.isPrivate(); } }); } }, IS_PROTECTED("isProtected", EntityType.FIELD) { public boolean _evaluate(Object fieldWrapped) { FieldDeclaration field = unwrap(FieldDeclaration.class, fieldWrapped); return testForModifier(field, new ModifierChecker() { public boolean check(Modifier modifier) { return modifier.isProtected(); } }); } }, IS_PUBLIC("isPublic", EntityType.FIELD) { public boolean _evaluate(Object fieldWrapped) { FieldDeclaration field = unwrap(FieldDeclaration.class, fieldWrapped); return testForModifier(field, new ModifierChecker() { public boolean check(Modifier modifier) { return modifier.isPublic(); } }); } }, IS_STATIC("isStatic", EntityType.FIELD) { public boolean _evaluate(Object fieldWrapped) { FieldDeclaration field = unwrap(FieldDeclaration.class, fieldWrapped); return testForModifier(field, new ModifierChecker() { public boolean check(Modifier modifier) { return modifier.isStatic(); } }); } }, IS_STRICTFP("isStrictfp", EntityType.FIELD) { public boolean _evaluate(Object fieldWrapped) { FieldDeclaration field = unwrap(FieldDeclaration.class, fieldWrapped); return testForModifier(field, new ModifierChecker() { public boolean check(Modifier modifier) { return modifier.isStrictfp(); } }); } }, IS_SYNCHRONIZED("isSynchronized", EntityType.FIELD) { public boolean _evaluate(Object fieldWrapped) { FieldDeclaration field = unwrap(FieldDeclaration.class, fieldWrapped); return testForModifier(field, new ModifierChecker() { public boolean check(Modifier modifier) { return modifier.isSynchronized(); } }); } }, IS_TRANSIENT("isTransient", EntityType.FIELD) { public boolean _evaluate(Object fieldWrapped) { FieldDeclaration field = unwrap(FieldDeclaration.class, fieldWrapped); return testForModifier(field, new ModifierChecker() { public boolean check(Modifier modifier) { return modifier.isTransient(); } }); } }, IS_VOLATILE("isVolatile", EntityType.FIELD) { public boolean _evaluate(Object fieldWrapped) { FieldDeclaration field = unwrap(FieldDeclaration.class, fieldWrapped); return testForModifier(field, new ModifierChecker() { public boolean check(Modifier modifier) { return modifier.isVolatile(); } }); } }, STATIC_TRAIT("<STATIC TRAIT>") { @Override public BindingsSet evaluate(List<Object> args, IEntityLookup entityLookup, BindingMap theta, boolean matchingMode, SourceLocation location) { throw new RuntimeException("Internal error: should be in static trait%n"); } }, ; protected String functionName; private EntityType[] params; private Method reflectiveMethod = null; private IEntityLookup entityLookup; private static HashMap<String, BuiltInFunction> lookup; static { lookup = new HashMap<String, BuiltInFunction>(); for (BuiltInFunction function : values()) { lookup.put(function.functionName, function); } } public static Set<String> getBuiltInNames() { return lookup.keySet(); } @Override public synchronized BindingsSet evaluate(List<Object> args, IEntityLookup lookup, BindingMap theta, boolean dummy2, SourceLocation location) { IEntityLookup prevEntityLookup = this.entityLookup; try { this.entityLookup = lookup; if (reflectiveMethod == null) { Class[] reflectTypes = new Class[params.length]; for (int i = 0; i < reflectTypes.length; ++i) { reflectTypes[i] = Object.class; } Class<? extends BuiltInFunction> clazz = this.getClass(); this.reflectiveMethod = clazz.getMethod("_evaluate", reflectTypes); } Object[] reflectArgs = new Object[args.size()]; for (int i = 0; i < reflectArgs.length; ++i) { Object arg = args.get(i); if (arg instanceof VariablePlaceholder) { VariablePlaceholder placeholder = (VariablePlaceholder)arg; String placeholderName = placeholder.getName(); arg = lookup.lookupEntity(placeholderName); if (arg == null) { ArcumError.fatalUserError(location, "Built-in functions expect" + " arguments that are already bound, but variable %s has no" + " binding", placeholderName); } } reflectArgs[i] = arg; } if (reflectArgs.length != params.length) { ArcumError.fatalUserError(location, "Mismatched number of arguments for \"%s\":" + " expected %d, but got %d%n", functionName, params.length, reflectArgs.length); } Boolean answer; try { SourceLocation.pushLocation(location); answer = (Boolean)reflectiveMethod.invoke(this, reflectArgs); } finally { SourceLocation.popLocation(); } if (DEBUG) { System.err.printf("Invoked %s(%s):%n ==>%b%n", this.functionName, separate(transform(args, new Function<Object, String>() { @Override public String apply(Object from) { return Entity.getDisplayString(from); } })), answer); } if (answer) { return Satisfier.trueResult(theta); } else { return Satisfier.falseResult(theta); } } catch (RuntimeException e) { throw e; } catch (InvocationTargetException e) { throw new RuntimeException(e.getCause()); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } finally { this.entityLookup = prevEntityLookup; } } private BuiltInFunction(String functionName, EntityType... params) { this.functionName = functionName; this.params = params; } public static BuiltInFunction lookup(String name) { if (lookup.containsKey(name)) { return lookup.get(name); } else { return STATIC_TRAIT; } } // EXAMPLE: The casting and the unwrap calls wouldn't be needed if there were // extra checking protected <T> T unwrap(Class<T> clazz, Object entity) { return clazz.cast(entity); } protected <T> T coerce(Class<T> clazz, Object entity) { if (clazz.equals(ITypeBinding.class) && entity instanceof ASTNode) { return (T)entityLookup.lookupTypeBinding((ASTNode)entity); } return unwrap(clazz, entity); } private interface ModifierChecker { boolean check(Modifier modifier); } private static boolean testForModifier(FieldDeclaration field, ModifierChecker checker) { for (Object obj : field.modifiers()) { IExtendedModifier modifierOrAnnotation = (IExtendedModifier)obj; if (modifierOrAnnotation.isModifier()) { Modifier modifier = (Modifier)modifierOrAnnotation; if (checker.check(modifier)) return true; } } return false; } @Override public List<EntityType> checkArgs(SourceLocation position, List<TraitSignature> tupleSets, int numGiven) { int numExpectedArgs = params.length; if (numExpectedArgs != numGiven) { ArcumError.fatalUserError(position, "The built-in function \"%s\" expects %d arguments, instead found %d", functionName, numExpectedArgs, numGiven); } return Lists.newArrayList(params); } @Override public String toString() { return functionName; } @Override public String getName() { return functionName; } }