Fixed record constructor resolving from namespaces
[simantics/platform.git] / bundles / org.simantics.scl.compiler / src / org / simantics / scl / compiler / elaboration / expressions / ERecord.java
1 package org.simantics.scl.compiler.elaboration.expressions;
2
3 import java.util.Arrays;
4
5 import org.simantics.scl.compiler.constants.SCLConstructor;
6 import org.simantics.scl.compiler.elaboration.contexts.TranslationContext;
7 import org.simantics.scl.compiler.elaboration.contexts.TranslationContext.ExistentialFrame;
8 import org.simantics.scl.compiler.elaboration.expressions.records.FieldAssignment;
9 import org.simantics.scl.compiler.elaboration.modules.SCLValue;
10 import org.simantics.scl.compiler.environment.AmbiguousNameException;
11 import org.simantics.scl.compiler.errors.Locations;
12
13 import gnu.trove.map.hash.THashMap;
14
15 public class ERecord extends ASTExpression {
16
17     public static final boolean DEBUG = false;
18     
19     public final EVar constructor;
20     public final FieldAssignment[] fields;
21     
22     public ERecord(EVar constructor, FieldAssignment[] fields) {
23         this.constructor = constructor;
24         this.fields = fields;
25     }
26
27     @Override
28     public Expression resolve(TranslationContext context) {
29         return resolve(context, false);
30     }
31     
32     @Override
33     public Expression resolveAsPattern(TranslationContext context) {
34         return resolve(context, true);
35     }
36     
37     public Expression resolve(TranslationContext context, boolean asPattern) {
38         SCLValue constructorValue; 
39         try {
40             constructorValue = context.resolveRecordConstructor(location, constructor.name);
41         } catch (AmbiguousNameException e) {
42             context.getErrorLog().log(constructor.location, e.getMessage());
43             return new EError(constructor.location);
44         }
45         if(constructorValue == null) {
46             context.getErrorLog().log(constructor.location, "Couldn't resolve the record constructor " + constructor.name + ".");
47             return new EError(constructor.location);
48         }
49         if(!(constructorValue.getValue() instanceof SCLConstructor)) {
50             context.getErrorLog().log(constructor.location, "Value " + constructor.name + " is not a record constructor.");
51             return new EError(constructor.location);
52         }
53         String[] fieldNames = ((SCLConstructor)constructorValue.getValue()).recordFieldNames;
54         if(fieldNames == null) {
55             context.getErrorLog().log(constructor.location, "Value " + constructor.name + " is not a record constructor.");
56             return new EError(constructor.location);
57         }
58         Expression[] parameters = translateFieldsToFunctionParameters(context, fields, fieldNames, false);
59         if(parameters == null)
60             return new EError(location);
61         if(asPattern)
62             for(int i=0;i<parameters.length;++i) {
63                 Expression parameter = parameters[i];
64                 if(parameter == null)
65                     parameters[i] = Expressions.blank(null);
66                 else
67                     parameters[i] = parameter.resolveAsPattern(context);
68             }
69         else {
70             boolean error = false;
71             for(int i=0;i<parameters.length;++i) {
72                 Expression parameter = parameters[i];
73                 if(parameter == null) {
74                     ExistentialFrame frame = context.getCurrentExistentialFrame();
75                     if(frame == null || frame.disallowNewExistentials) {
76                         context.getErrorLog().log(location, "Field " + fieldNames[i] + " not defined.");
77                         error = true;
78                     }
79                     else
80                         parameters[i] = frame.createBlank(location); 
81                 }
82                 else
83                     parameters[i] = parameter.resolve(context);
84             }
85             if(error)
86                 return new EError(location);
87         }
88         EApply result = new EApply(new EConstant(constructorValue), parameters);
89         result.setLocationDeep(location);
90         return result;
91     }
92     
93     public static Expression[] translateFieldsToFunctionParameters(TranslationContext context, FieldAssignment[] fields, String[] fieldNames, boolean chrLiteral) {
94         if(DEBUG) {
95             System.out.println("translateFieldsToFunctionParameters");
96             System.out.println("    fieldNames = " + Arrays.toString(fieldNames));
97             System.out.print("    fields = {");
98             for(int i=0;i<fields.length;++i) {
99                 FieldAssignment field = fields[i];
100                 if(i > 0)
101                     System.out.print(", ");
102                 System.out.print(field.name);
103                 if(field.value != null) {
104                     System.out.print(" = ");
105                     System.out.print(field.value);
106                 }
107             }
108             System.out.println("}");
109         }
110         
111         THashMap<String,FieldAssignment> recordMap = new THashMap<String,FieldAssignment>(fields.length);
112         boolean error = false;
113         FieldAssignment wildcardField = null;
114         for(FieldAssignment field : fields) {
115             if(field.value == null) {
116                 String actualName = field.name;
117                 if(actualName.equals(FieldAssignment.WILDCARD)) {
118                     if(wildcardField != null)
119                         context.getErrorLog().log(field.location, "The record has more than one wildcard.");
120                     wildcardField = field;
121                     continue;
122                 }
123                 if(actualName.charAt(0) == '?')
124                     actualName = actualName.substring(1);
125                 String bestMatch = null;
126                 int bestMatchLength = 0;
127                 for(int i=0;i<fieldNames.length;++i) {
128                     String fieldName = fieldNames[i];
129                     if(actualName.startsWith(fieldName) && fieldName.length() > bestMatchLength) {
130                         bestMatch = fieldName;
131                         bestMatchLength = fieldName.length();
132                     }
133                 }
134                 if(bestMatch == null) {
135                     context.getErrorLog().log(field.location, "Invalid shorthand field " + field.name + " is defined twice.");
136                     error = true;
137                 }
138                 field.value = new EVar(field.location, field.name);
139                 field.name = bestMatch;
140             }
141             if(recordMap.put(field.name, field) != null) {
142                 context.getErrorLog().log(field.location, "Field " + field.name + " is defined more than once.");
143                 error = true;
144             }
145         }
146         if(error)
147             return null;
148         Expression[] parameters = new Expression[fieldNames.length];
149         for(int i=0;i<fieldNames.length;++i) {
150             FieldAssignment assignment = recordMap.remove(fieldNames[i]);
151             if(assignment != null)
152                 parameters[i] = assignment.value;
153             else if(wildcardField != null) {
154                 String variableName = fieldNames[i];
155                 if(chrLiteral)
156                     variableName = "?" + variableName;
157                 parameters[i] = new EVar(wildcardField.location, variableName);
158             }
159         }
160         if(!recordMap.isEmpty()) {
161             for(FieldAssignment field : recordMap.values())
162                 context.getErrorLog().log(field.location, "Field " + field.name + " is not defined in the constructor.");
163             return null;
164         }
165         if(DEBUG)
166             System.out.println(" => parameters = " + Arrays.toString(parameters));
167         return parameters;
168     }
169
170     @Override
171     public void setLocationDeep(long loc) {
172         if(location == Locations.NO_LOCATION) {
173             location = loc;
174             for(FieldAssignment field : fields)
175                 if(field.value != null)
176                     field.value.setLocationDeep(loc);
177         }
178     }
179     
180     @Override
181     public Expression accept(ExpressionTransformer transformer) {
182         return transformer.transform(this);
183     }
184     
185     @Override
186     public void accept(ExpressionVisitor visitor) {
187         visitor.visit(this);
188     }
189 }