package edu.ucsd.arcum.interpreter.ast; import java.util.ArrayList; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.jdt.core.dom.ITypeBinding; import com.google.common.collect.Lists; import edu.ucsd.arcum.exceptions.ArcumError; import edu.ucsd.arcum.exceptions.JavaFragmentCompilationProblem; import edu.ucsd.arcum.exceptions.SourceLocation; import edu.ucsd.arcum.interpreter.parser.FragmentParser; import edu.ucsd.arcum.interpreter.query.ArcumDeclarationTable; import edu.ucsd.arcum.interpreter.query.EntityType; import edu.ucsd.arcum.interpreter.transformation.ResolvedConceptMapEntry; import edu.ucsd.arcum.util.MultiDictionary; import edu.ucsd.arcum.util.StringUtil; public class RequireMap extends TopLevelConstruct { private static int serialCounter = 0; private MultiDictionary<String, List<MapNameValueBinding>> optionsUsed; private IdentityHashMap<List<MapNameValueBinding>, String> argsPresent; private IdentityHashMap<List<MapNameValueBinding>, SourceLocation> locations; private List<ResolvedConceptMapEntry> resolvedBindings; private IProject project; private IResource resource; public static RequireMap newArcumMap(final String imports, final IProject project, ArcumDeclarationTable table, final IResource resource) { final String name = "AnonymousMap" + (serialCounter++); return table.conditionalCreate(name, new ConstructorThunk<RequireMap>() { public RequireMap create() { return new RequireMap(name, imports, project, resource); } }); } private RequireMap(String name, String imports, IProject project, IResource resource) { super(name, imports); this.optionsUsed = MultiDictionary.newInstance(); this.argsPresent = new IdentityHashMap<List<MapNameValueBinding>, String>(); this.locations = new IdentityHashMap<List<MapNameValueBinding>, SourceLocation>(); this.resolvedBindings = null; this.project = project; this.resource = resource; } public void addBindings(String optionUsed, List<MapNameValueBinding> bindings, String optionClauseText, SourceLocation location) { optionsUsed.addDefinition(optionUsed, bindings); argsPresent.put(bindings, optionClauseText); locations.put(bindings, location); for (MapNameValueBinding binding : bindings) { binding.setOptionClauseText(optionClauseText); } } @Override public String toString() { StringBuilder buff = new StringBuilder(); buff.append("require\n{"); for (Map.Entry<String, List<List<MapNameValueBinding>>> entry : optionsUsed) { buff.append("\n "); String key = entry.getKey(); List<List<MapNameValueBinding>> bindingsList = entry.getValue(); for (List<MapNameValueBinding> bindings : bindingsList) { buff.append(key); buff.append("("); StringUtil.separate(buff, bindings, ", "); buff.append(")"); buff.append(";\n"); } } buff.append("}\n"); return buff.toString(); } @Override public void doTypeCheck(ArcumDeclarationTable table) { this.resolvedBindings = new ArrayList<ResolvedConceptMapEntry>(); for (Map.Entry<String, List<List<MapNameValueBinding>>> entry : optionsUsed) { String optionName = entry.getKey(); List<List<MapNameValueBinding>> bindingsList = entry.getValue(); for (List<MapNameValueBinding> bindings : bindingsList) { checkAndDisambiguateBinding(optionName, bindings, table); } } } // See if the binding is a valid option and that the correct arguments // were passed. Also tries to disambiguate any ambiguous arguments, // replacing them with a correct value private void checkAndDisambiguateBinding(String optionName, List<MapNameValueBinding> args, ArcumDeclarationTable table) { Option option = table.lookup(optionName, Option.class); SourceLocation location = locations.get(args); if (option == null) { ArcumError.fatalUserError(location, "The name %s is not a known option", optionName); } List<FormalParameter> params = option.getSingletonParameters(table); // for each argument, there is a parameter List<MapNameValueBinding> disambiguated = new ArrayList<MapNameValueBinding>(); for (MapNameValueBinding arg : args) { String argName = arg.getName(); FormalParameter formal = null; for (FormalParameter param : params) { if (param.getIdentifier().equals(argName)) { formal = param; break; } } if (formal == null) { ArcumError.fatalUserError(location, "The argument named %s does not match any valid parameter names", argName); } disambiguated .add(checkAndDisambiguateArgument(arg, formal, option, location)); } // for each parameter, there is an argument for (FormalParameter param : params) { String paramName = param.getIdentifier(); boolean found = false; for (MapNameValueBinding arg : args) { String argName = arg.getName(); if (paramName.equals(argName)) { found = true; break; } } if (!found) { ArcumError.fatalUserError(location, "Expected a parameter named \"%s\"", paramName); } } ResolvedConceptMapEntry resolvedEntry; resolvedEntry = new ResolvedConceptMapEntry(option, disambiguated, argsPresent .get(args), location, (IFile)resource); this.resolvedBindings.add(resolvedEntry); } private MapNameValueBinding checkAndDisambiguateArgument(MapNameValueBinding binding, FormalParameter formal, Option option, SourceLocation location) { String argumentName = binding.getName(); EntityType type = formal.getType(); if (binding instanceof MapAmbiguousArgument) { MapAmbiguousArgument ambiguous = (MapAmbiguousArgument)binding; switch (type) { case TYPE: case DECLARATION_ELEMENT: String body = ambiguous.getBody(); FragmentParser parser = new FragmentParser(getImports(), project); ITypeBinding resolved; try { resolved = parser.getTypeBinding(body); } catch (JavaFragmentCompilationProblem jfcp) { String message = StringUtil.enumerate(jfcp.getMessages()); ArcumError.fatalUserError(location, "%s", message); return null; } if (resolved == null) { ArcumError.fatalUserError(location, "Cannot resolve \"%s\"", body); } MapTypeArgument mapTypeArg = new MapTypeArgument(location, ambiguous .getName(), resolved); return mapTypeArg; case ANNOTATION: case STATEMENT: case STRING: case BOOLEAN: case EXPR: case ACCESS_SPECIFIER: case FIELD: case METHOD: // URGENT: for field and method we want to grab what's before the // last dot and treat it as a type, and then what's after the dot // treat it as a name -- for method we might also look at a formal // parameter list, in order to cope with overloading case MODIFIERS: case ERROR: ArcumError.fatalError("Cannot disambiguate a " + type); break; } return null; } else if (binding instanceof MapBooleanArgument) { checkTypes(argumentName, EntityType.BOOLEAN, type, location); } else if (binding instanceof MapStringArgument) { checkTypes(argumentName, EntityType.STRING, type, location); } else if (binding instanceof MapTraitArgument) { // DOCUMENTATION: There is a bootstrapping issue here of sorts. We could // allow for the map trait argument to refer to static traits defined // in the option interface, but those definitions might depend on the // parameters (i.e. maybe even the map trait argument itself) and would // need to be circularly solved. We could allow the same for Option // defined static traits too, so the tupleSets defined in the option // instead of the optionInterface should be passed. However, it is done // this way currently so that the built-in trait tupleSets are passed, // which do not have the circular issues. OptionInterface optionInterface = option.getOptionInterface(); List<TraitSignature> tupleSets = optionInterface.getTraitSignatures(); checkTypeOfTraitArgument((MapTraitArgument)binding, formal, tupleSets, location); } return binding; } private void checkTypes(String name, EntityType actualType, EntityType expectedType, SourceLocation location) { if (actualType != expectedType) { ArcumError.fatalUserError(location, "The argument named %s must be" + " of type \'%s\', but a \'%s\' was passed instead", name, expectedType, actualType); } } private void checkTypeOfTraitArgument(MapTraitArgument mapTraitArgument, FormalParameter formal, List<TraitSignature> tupleSets, SourceLocation location) { if (!formal.isSubTrait() || !EntityType.TRAIT.equals(formal.getType())) { ArcumError.fatalUserError(location, "Expected %s to be a predicate expression", mapTraitArgument); } tupleSets = Lists.newArrayList(tupleSets); tupleSets.add(formal.getSubTraitType()); mapTraitArgument.checkUserDefinedPredicates(tupleSets); } // valid only after a type check public List<ResolvedConceptMapEntry> getResolvedBindings() { return resolvedBindings; } }