--- /dev/null
+package org.simantics.scl.compiler.elaboration.contexts;
+
+import gnu.trove.list.array.TIntArrayList;
+import gnu.trove.map.hash.THashMap;
+import gnu.trove.procedure.TObjectProcedure;
+import gnu.trove.set.hash.THashSet;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import org.simantics.scl.compiler.common.names.Name;
+import org.simantics.scl.compiler.common.precedence.Associativity;
+import org.simantics.scl.compiler.common.precedence.Precedence;
+import org.simantics.scl.compiler.elaboration.expressions.Case;
+import org.simantics.scl.compiler.elaboration.expressions.EConstant;
+import org.simantics.scl.compiler.elaboration.expressions.EEntityTypeAnnotation;
+import org.simantics.scl.compiler.elaboration.expressions.EError;
+import org.simantics.scl.compiler.elaboration.expressions.EFieldAccess;
+import org.simantics.scl.compiler.elaboration.expressions.ELambda;
+import org.simantics.scl.compiler.elaboration.expressions.EVar;
+import org.simantics.scl.compiler.elaboration.expressions.EVariable;
+import org.simantics.scl.compiler.elaboration.expressions.Expression;
+import org.simantics.scl.compiler.elaboration.expressions.Variable;
+import org.simantics.scl.compiler.elaboration.expressions.accessor.FieldAccessor;
+import org.simantics.scl.compiler.elaboration.expressions.accessor.IdAccessor;
+import org.simantics.scl.compiler.elaboration.expressions.block.LetStatement;
+import org.simantics.scl.compiler.elaboration.modules.SCLValue;
+import org.simantics.scl.compiler.elaboration.query.pre.PreQuery;
+import org.simantics.scl.compiler.elaboration.relations.SCLRelation;
+import org.simantics.scl.compiler.environment.AmbiguousNameException;
+import org.simantics.scl.compiler.environment.Environment;
+import org.simantics.scl.compiler.environment.Environments;
+import org.simantics.scl.compiler.environment.LocalEnvironment;
+import org.simantics.scl.compiler.environment.Namespace;
+import org.simantics.scl.compiler.environment.filter.AcceptAllNamespaceFilter;
+import org.simantics.scl.compiler.errors.ErrorLog;
+import org.simantics.scl.compiler.errors.Locations;
+import org.simantics.scl.compiler.internal.parsing.declarations.DValueAst;
+
+public class TranslationContext extends TypeTranslationContext implements EnvironmentalContext {
+
+ THashMap<String, Variable> variables = new THashMap<String, Variable>();
+ ArrayList<Entry> variableEntries = new ArrayList<Entry>();
+ LocalEnvironment localEnvironment;
+ TIntArrayList frames = new TIntArrayList();
+ ArrayList<THashSet<String>> frameNameSets = new ArrayList<THashSet<String>>();
+ ArrayList<THashSet<String>> existentialFrames = new ArrayList<THashSet<String>>();
+ ArrayList<ArrayList<Variable>> blanksInExistentialFrame = new ArrayList<ArrayList<Variable>>();
+ SCLValue bindFunction;
+
+ public EEntityTypeAnnotation currentEntityTypeAnnotation;
+ public PreQuery currentPreQuery;
+
+ THashMap<String, SCLRelation> relations = new THashMap<String, SCLRelation>();
+ TIntArrayList relationFrames = new TIntArrayList();
+ ArrayList<RelationEntry> relationEntries = new ArrayList<RelationEntry>();
+
+ static class Entry {
+ String name;
+ Variable variable;
+ public Entry(String name, Variable variable) {
+ this.name = name;
+ this.variable = variable;
+ }
+ }
+
+ static class RelationEntry {
+ String name;
+ SCLRelation relation;
+ public RelationEntry(String name, SCLRelation relation) {
+ this.name = name;
+ this.relation = relation;
+ }
+ }
+
+ public TranslationContext(ErrorLog errorLog,
+ Environment environment, LocalEnvironment localEnvironment) {
+ super(errorLog, environment);
+ this.localEnvironment = localEnvironment;
+ }
+
+ public static boolean isConstructorName(String name) {
+ char firstChar = name.charAt(0);
+ return Character.isUpperCase(firstChar);
+ }
+
+ /* Tries to resolve name as a local variable. It is assumed
+ * that name does not contain '.'.
+ */
+ private Expression resolveLocalVariable(long location, String name) {
+ Variable variable = variables.get(name);
+ if(variable != null)
+ return new EVariable(location, variable);
+
+ char c = name.charAt(0);
+ switch(c) {
+ case '?':
+ if(existentialFrames.isEmpty()) {
+ errorLog.log(location, "Existential variables can be used only in queries.");
+ return new EError(location);
+ }
+ variable = new Variable(name);
+ variables.put(name, variable);
+ existentialFrames.get(existentialFrames.size()-1).add(name);
+ return new EVariable(variable);
+ case '_':
+ if(name.length()==1) {
+ variable = new Variable("_");
+ if(blanksInExistentialFrame.isEmpty()) {
+ errorLog.log(location, "Cannot use blank variables in this context.");
+ return new EError(location);
+ }
+ blanksInExistentialFrame.get(blanksInExistentialFrame.size()-1).add(variable);
+ return new EVariable(variable);
+ }
+ break;
+ case '#':
+ if(name.length() > 1 && Character.isLetter(name.charAt(1))) {
+ if(currentEntityTypeAnnotation == null) {
+ errorLog.log(location, "Attribute references cannot be made in this context.");
+ return new EError(location);
+ }
+ return currentEntityTypeAnnotation.resolveAttribute(this, location, name.substring(1));
+ }
+ break;
+ }
+ return null;
+ }
+
+ private FieldAccessor createFieldAccessor(char accessSeparator, String name) {
+ IdAccessor accessor = new IdAccessor(name);
+ accessor.accessSeparator = accessSeparator;
+ return accessor;
+ }
+
+ private Expression resolveFieldAccess(Expression base, int pos, String name) {
+ ArrayList<FieldAccessor> accessors = new ArrayList<FieldAccessor>(2);
+ while(pos != -1) {
+ int p = findSeparator(name, pos+1);
+ accessors.add(createFieldAccessor(
+ name.charAt(pos),
+ name.substring(pos+1, p==-1 ? name.length() : p-1)));
+ pos = p;
+ }
+ return new EFieldAccess(base,
+ accessors.toArray(new FieldAccessor[accessors.size()]));
+ }
+
+ private Expression resolveIn(long location, Namespace namespace, String name) {
+ SCLValue value = resolveValueIn(location, namespace, name);
+ if(value == null)
+ return new EError(location);
+ return new EConstant(location, value);
+ }
+
+ private Expression resolveComplexNameIn(long location, Namespace namespace, int startPos, String name) {
+ int pos = name.length();
+ {
+ int hashPos = name.lastIndexOf('#');
+ if(hashPos >= 0)
+ pos = hashPos;
+ }
+ while(pos > startPos) {
+ SCLValue value;
+ try {
+ value = namespace.getValue(name.substring(startPos, pos));
+ } catch (AmbiguousNameException e) {
+ errorLog.log(location, e.getMessage());
+ return new EError(location);
+ }
+ if(value != null) {
+ Expression result = new EConstant(location, value);
+ if(pos < name.length())
+ result = resolveFieldAccess(result, pos, name);
+ return result;
+ }
+ pos = name.lastIndexOf('.', pos-1);
+ }
+ errorLog.log(location, "Couldn't resolve variable " + name + ".");
+ return new EError(location);
+ }
+
+ private static int findSeparator(String name, int fromIndex) {
+ while(fromIndex < name.length()) {
+ char c = name.charAt(fromIndex);
+ if(c == '.' || c == '#')
+ return fromIndex;
+ ++fromIndex;
+ }
+ return -1;
+ }
+
+ public Expression resolveExpression(long location, String name) {
+ int p = findSeparator(name, 1 /* Initial # is not a separator */);
+ if(p == -1) {
+ Expression result = resolveLocalVariable(location, name);
+ if(result != null)
+ return result;
+
+ if(localEnvironment != null) {
+ result = localEnvironment.resolve(environment, name);
+ if(result != null) {
+ result.setLocationDeep(location);
+ return result;
+ }
+ }
+
+ return resolveIn(location, environment.getLocalNamespace(), name);
+ }
+ else {
+ if(localEnvironment != null) {
+ Expression result = localEnvironment.resolve(environment, name);
+ if(result != null) {
+ result.setLocationDeep(location);
+ return result;
+ }
+ }
+
+ String prefix = name.substring(0, p);
+ Expression result = resolveLocalVariable(location, prefix);
+ if(result != null)
+ return resolveFieldAccess(result, p, name);
+
+ Namespace namespace = environment.getLocalNamespace();
+ int pos = 0;
+ while(name.charAt(p)=='.') {
+ Namespace temp = namespace.getNamespace(prefix);
+ if(temp == null)
+ break;
+ namespace = temp;
+ pos = p+1;
+ p = findSeparator(name, pos);
+ if(p < 0)
+ return resolveIn(location, namespace, name.substring(pos));
+ prefix = name.substring(pos, p);
+ }
+
+ return resolveComplexNameIn(location, namespace, pos, name);
+ }
+ }
+
+ public Expression resolvePattern(EVar name) {
+ char firstChar = name.name.charAt(0);
+ if(firstChar == '_' && name.name.length()==1) {
+ return new EVariable(new Variable("_"));
+ }
+ else if(!Character.isUpperCase(firstChar)) {
+ if(!frameNameSets.get(frameNameSets.size()-1).add(name.name))
+ errorLog.log(name.location, "Repeated variable "+name.name+" in pattern.");
+ return new EVariable(name.location, newVariable(name.name));
+ }
+ else
+ return resolveExpression(name.location, name.name);
+ }
+
+ /**
+ * Starts a new environment frame. New variables defined in this frame shadow
+ * the old variables and when the frame is popped, the old variables are again
+ * visible.
+ */
+ public void pushFrame() {
+ frames.add(variableEntries.size());
+ frameNameSets.add(new THashSet<String>());
+ }
+
+ /**
+ * Ends an environment frame. See {@link #pushFrame}.
+ */
+ public void popFrame() {
+ int frame = frames.removeAt(frames.size()-1);
+ int i = variableEntries.size();
+ while(i > frame) {
+ --i;
+ Entry entry = variableEntries.remove(i);
+ if(entry.variable == null)
+ variables.remove(entry.name);
+ else
+ variables.put(entry.name, entry.variable);
+ }
+ frameNameSets.remove(frameNameSets.size()-1);
+ }
+
+ public void pushRelationFrame() {
+ relationFrames.add(relationEntries.size());
+ }
+
+ public void popRelationFrame() {
+ int frame = relationFrames.removeAt(relationFrames.size()-1);
+ int i = relationEntries.size();
+ while(i > frame) {
+ --i;
+ RelationEntry entry = relationEntries.remove(i);
+ if(entry.relation == null)
+ relations.remove(entry.name);
+ else
+ relations.put(entry.name, entry.relation);
+ }
+ }
+
+ public void pushExistentialFrame() {
+ pushFrame();
+ existentialFrames.add(new THashSet<String>());
+ blanksInExistentialFrame.add(new ArrayList<Variable>(2));
+ }
+
+ public Variable[] popExistentialFrame() {
+ popFrame();
+ THashSet<String> set = existentialFrames.remove(existentialFrames.size()-1);
+ ArrayList<Variable> blanks = blanksInExistentialFrame.remove(blanksInExistentialFrame.size()-1);
+ Variable[] result = new Variable[set.size() + blanks.size()];
+ int i=0;
+ for(String name : set)
+ result[i++] = variables.remove(name);
+ for(Variable blank : blanks)
+ result[i++] = blank;
+ return result;
+ }
+
+ public Variable newVariable(String name) {
+ Variable variable = new Variable(name);
+ Variable oldVariable = variables.put(name, variable);
+ variableEntries.add(new Entry(name, oldVariable));
+ return variable;
+ }
+
+ public THashMap<String, Variable> getVariables() {
+ return variables;
+ }
+
+ public void newRelation(String name, SCLRelation relation) {
+ SCLRelation oldRelation = relations.put(name, relation);
+ relationEntries.add(new RelationEntry(name, oldRelation));
+ }
+
+ public Precedence getPrecedence(Name op) {
+ Precedence prec = environment.getValue(op).getPrecedence();
+ if(prec == null)
+ return new Precedence(1, Associativity.NONASSOC);
+ else
+ return prec;
+ }
+
+ private SCLValue resolveValueIn(long location, Namespace namespace, final String name) {
+ try {
+ SCLValue value = namespace.getValue(name);
+ if(value == null) {
+ StringBuilder message = new StringBuilder();
+ message.append("Couldn't resolve variable ").append(name).append(".");
+
+ final THashSet<String> candidateNames = new THashSet<String>(4);
+ namespace.findValuesForPrefix("", AcceptAllNamespaceFilter.INSTANCE,
+ new TObjectProcedure<SCLValue>() {
+ @Override
+ public boolean execute(SCLValue value) {
+ if(value == null) {
+ new Exception().printStackTrace();
+ return true;
+ }
+ String valueName = value.getName().name;
+ if(name.equalsIgnoreCase(valueName))
+ candidateNames.add(valueName);
+ return true;
+ }
+ });
+ if(localEnvironment != null)
+ localEnvironment.forNames(new TObjectProcedure<String>() {
+ @Override
+ public boolean execute(String valueName) {
+ if(name.equalsIgnoreCase(valueName))
+ candidateNames.add(valueName);
+ return true;
+ }
+ });
+
+ if(candidateNames.size() > 0) {
+ message.append(" Did you mean ");
+ String[] ns = candidateNames.toArray(new String[candidateNames.size()]);
+ Arrays.sort(ns);
+ for(int i=0;i<ns.length;++i) {
+ if(i > 0) {
+ message.append(", ");
+ if(i == ns.length-1)
+ message.append("or ");
+ }
+ message.append(ns[i]);
+ }
+ message.append('?');
+ }
+
+ errorLog.log(location, message.toString());
+ return null;
+ }
+ return value;
+ } catch (AmbiguousNameException e) {
+ errorLog.log(location, e.getMessage());
+ return null;
+ }
+ }
+
+ public Case translateCase(Expression lhs, Expression rhs) {
+ ArrayList<Expression> parameters = new ArrayList<Expression>(4);
+ lhs.getParameters(this, parameters);
+ Expression[] patterns = new Expression[parameters.size()];
+ pushFrame();
+ for(int i=0;i<patterns.length;++i) {
+ Expression pattern = parameters.get(i);
+ pattern = pattern.resolveAsPattern(this);
+ patterns[i] = pattern;
+ }
+ rhs = rhs.resolve(this);
+ popFrame();
+ Case case_ = new Case(patterns, rhs);
+ case_.setLhs(lhs.location);
+ return case_;
+ }
+
+ public Expression translateCases2(ArrayList<DValueAst> definitions) {
+ Case[] cases = new Case[definitions.size()];
+ for(int i=0;i<cases.length;++i) {
+ DValueAst def = definitions.get(i);
+ cases[i] = translateCase(def.lhs, def.value);
+ }
+ // check arity consistency
+ int arity = cases[0].patterns.length;
+ for(int i=1;i<cases.length;++i)
+ if(cases[i].patterns.length != arity)
+ errorLog.log(definitions.get(i).lhs.location,
+ "Inconsistent arity. " +
+ "This case has arity " + cases[i].patterns.length +
+ " while previous cases had arity " + arity + ".");
+ if(cases.length == 1 && cases[0].patterns.length == 0)
+ return cases[0].value;
+ else
+ return new ELambda(
+ Locations.combine(definitions.get(0).location, definitions.get(definitions.size()-1).location),
+ cases);
+ }
+
+ public Expression translateCases(ArrayList<LetStatement> definitions) {
+ Case[] cases = new Case[definitions.size()];
+ for(int i=0;i<cases.length;++i) {
+ LetStatement def = definitions.get(i);
+ cases[i] = translateCase(def.pattern, def.value);
+ }
+ // check arity concistency
+ int arity = cases[0].patterns.length;
+ for(int i=1;i<cases.length;++i)
+ if(cases[i].patterns.length != arity)
+ errorLog.log(definitions.get(i).pattern.location,
+ "Inconsistent arity. " +
+ "This case has arity " + cases[i].patterns.length +
+ " while previous cases had arity " + arity + ".");
+ if(arity == 0) {
+ if(cases.length > 1)
+ errorLog.log(cases[1].value.location, "Cannot give multiple cases for arity 0 function.");
+ return cases[0].value;
+ }
+ return new ELambda(
+ Locations.combine(definitions.get(0).location, definitions.get(definitions.size()-1).location),
+ cases);
+ }
+
+ private static final Name BIND = Name.create("Prelude", ">>=");
+
+ public SCLValue getBindFunction() {
+ if(bindFunction == null) {
+ bindFunction = getEnvironment().getValue(BIND);
+ }
+ return bindFunction;
+ }
+
+ public SCLRelation resolveRelation(long location, String name) {
+ SCLRelation relation = relations.get(name);
+ if(relation != null)
+ return relation;
+
+ try {
+ relation = Environments.getRelation(environment, name);
+ /*if(relation == null) {
+ errorLog.log(location, "Couldn't resolve relation " + name + ".");
+ return null;
+ }*/
+ return relation;
+ } catch (AmbiguousNameException e) {
+ errorLog.log(location, e.getMessage());
+ return null;
+ }
+ }
+
+ @Override
+ public SCLValue getValue(Name name) {
+ return environment.getValue(name);
+ }
+}