]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/elaboration/contexts/TranslationContext.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.scl.compiler / src / org / simantics / scl / compiler / elaboration / contexts / TranslationContext.java
diff --git a/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/elaboration/contexts/TranslationContext.java b/bundles/org.simantics.scl.compiler/src/org/simantics/scl/compiler/elaboration/contexts/TranslationContext.java
new file mode 100644 (file)
index 0000000..961fa90
--- /dev/null
@@ -0,0 +1,494 @@
+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);
+    }
+}