OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 class Constant implements Hashable { | |
6 const Constant(); | |
7 | |
8 bool isNull() => false; | |
9 bool isBool() => false; | |
10 bool isTrue() => false; | |
11 bool isFalse() => false; | |
12 bool isInt() => false; | |
13 bool isDouble() => false; | |
14 bool isNum() => false; | |
15 bool isString() => false; | |
16 bool isList() => false; | |
17 bool isMap() => false; | |
18 bool isConstructedObject() => false; | |
19 /** Returns true if the constant is null, a bool, a number or a string. */ | |
20 bool isPrimitive() => false; | |
21 /** Returns true if the constant is a list, a map or a constructed object. */ | |
22 bool isObject() => false; | |
23 | |
24 abstract void writeJsCode(StringBuffer buffer, ConstantHandler handler); | |
25 /** | |
26 * Unless the constant can be emitted multiple times (as for numbers and | |
27 * strings) adds its canonical name to the buffer. | |
28 */ | |
29 abstract void writeCanonicalizedJsCode(StringBuffer buffer, | |
30 ConstantHandler handler); | |
31 abstract List<Constant> getDependencies(); | |
32 } | |
33 | |
34 class PrimitiveConstant extends Constant { | |
35 abstract get value(); | |
36 const PrimitiveConstant(); | |
37 bool isPrimitive() => true; | |
38 | |
39 bool operator ==(var other) { | |
40 if (other is !PrimitiveConstant) return false; | |
41 PrimitiveConstant otherPrimitive = other; | |
42 // We use == instead of === so that DartStrings compare correctly. | |
43 return value == otherPrimitive.value; | |
44 } | |
45 | |
46 String toString() => value.toString(); | |
47 // Primitive constants don't have dependencies. | |
48 List<Constant> getDependencies() => const <Constant>[]; | |
49 abstract DartString toDartString(); | |
50 | |
51 void writeCanonicalizedJsCode(StringBuffer buffer, ConstantHandler handler) { | |
52 writeJsCode(buffer, handler); | |
53 } | |
54 } | |
55 | |
56 class NullConstant extends PrimitiveConstant { | |
57 factory NullConstant() => const NullConstant._internal(); | |
58 const NullConstant._internal(); | |
59 bool isNull() => true; | |
60 get value() => null; | |
61 | |
62 void writeJsCode(StringBuffer buffer, ConstantHandler handler) { | |
63 buffer.add("(void 0)"); | |
64 } | |
65 | |
66 // The magic constant has no meaning. It is just a random value. | |
67 int hashCode() => 785965825; | |
68 DartString toDartString() => const LiteralDartString("null"); | |
69 } | |
70 | |
71 class NumConstant extends PrimitiveConstant { | |
72 abstract num get value(); | |
73 const NumConstant(); | |
74 bool isNum() => true; | |
75 } | |
76 | |
77 class IntConstant extends NumConstant { | |
78 final int value; | |
79 factory IntConstant(int value) { | |
80 switch(value) { | |
81 case 0: return const IntConstant._internal(0); | |
82 case 1: return const IntConstant._internal(1); | |
83 case 2: return const IntConstant._internal(2); | |
84 case 3: return const IntConstant._internal(3); | |
85 case 4: return const IntConstant._internal(4); | |
86 case 5: return const IntConstant._internal(5); | |
87 case 6: return const IntConstant._internal(6); | |
88 case 7: return const IntConstant._internal(7); | |
89 case 8: return const IntConstant._internal(8); | |
90 case 9: return const IntConstant._internal(9); | |
91 case 10: return const IntConstant._internal(10); | |
92 case -1: return const IntConstant._internal(-1); | |
93 case -2: return const IntConstant._internal(-2); | |
94 default: return new IntConstant._internal(value); | |
95 } | |
96 } | |
97 const IntConstant._internal(this.value); | |
98 bool isInt() => true; | |
99 | |
100 void writeJsCode(StringBuffer buffer, ConstantHandler handler) { | |
101 buffer.add("$value"); | |
102 } | |
103 | |
104 // We have to override the equality operator so that ints and doubles are | |
105 // treated as separate constants. | |
106 // The is [:!IntConstant:] check at the beginning of the function makes sure | |
107 // that we compare only equal to integer constants. | |
108 bool operator ==(var other) { | |
109 if (other is !IntConstant) return false; | |
110 IntConstant otherInt = other; | |
111 return value == otherInt.value; | |
112 } | |
113 | |
114 int hashCode() => value.hashCode(); | |
115 DartString toDartString() => new DartString.literal(value.toString()); | |
116 } | |
117 | |
118 class DoubleConstant extends NumConstant { | |
119 final double value; | |
120 factory DoubleConstant(double value) { | |
121 if (value.isNaN()) { | |
122 return const DoubleConstant._internal(double.NAN); | |
123 } else if (value == double.INFINITY) { | |
124 return const DoubleConstant._internal(double.INFINITY); | |
125 } else if (value == -double.INFINITY) { | |
126 return const DoubleConstant._internal(-double.INFINITY); | |
127 } else if (value == 0.0 && !value.isNegative()) { | |
128 return const DoubleConstant._internal(0.0); | |
129 } else if (value == 1.0) { | |
130 return const DoubleConstant._internal(1.0); | |
131 } else { | |
132 return new DoubleConstant._internal(value); | |
133 } | |
134 } | |
135 const DoubleConstant._internal(this.value); | |
136 bool isDouble() => true; | |
137 | |
138 void writeJsCode(StringBuffer buffer, ConstantHandler handler) { | |
139 if (value.isNaN()) { | |
140 buffer.add("(0/0)"); | |
141 } else if (value == double.INFINITY) { | |
142 buffer.add("(1/0)"); | |
143 } else if (value == -double.INFINITY) { | |
144 buffer.add("(-1/0)"); | |
145 } else { | |
146 buffer.add("$value"); | |
147 } | |
148 } | |
149 | |
150 bool operator ==(var other) { | |
151 if (other is !DoubleConstant) return false; | |
152 DoubleConstant otherDouble = other; | |
153 double otherValue = otherDouble.value; | |
154 if (value == 0.0 && otherValue == 0.0) { | |
155 return value.isNegative() == otherValue.isNegative(); | |
156 } else if (value.isNaN()) { | |
157 return otherValue.isNaN(); | |
158 } else { | |
159 return value == otherValue; | |
160 } | |
161 } | |
162 | |
163 int hashCode() => value.hashCode(); | |
164 DartString toDartString() => new DartString.literal(value.toString()); | |
165 } | |
166 | |
167 class BoolConstant extends PrimitiveConstant { | |
168 factory BoolConstant(value) { | |
169 return value ? new TrueConstant() : new FalseConstant(); | |
170 } | |
171 const BoolConstant._internal(); | |
172 bool isBool() => true; | |
173 | |
174 BoolConstant unaryFold(String op) { | |
175 if (op == "!") return new BoolConstant(!value); | |
176 return null; | |
177 } | |
178 | |
179 abstract BoolConstant negate(); | |
180 } | |
181 | |
182 class TrueConstant extends BoolConstant { | |
183 final bool value = true; | |
184 | |
185 factory TrueConstant() => const TrueConstant._internal(); | |
186 const TrueConstant._internal() : super._internal(); | |
187 bool isTrue() => true; | |
188 | |
189 void writeJsCode(StringBuffer buffer, ConstantHandler handler) { | |
190 buffer.add("true"); | |
191 } | |
192 | |
193 FalseConstant negate() => new FalseConstant(); | |
194 | |
195 bool operator ==(var other) => this === other; | |
196 // The magic constant is just a random value. It does not have any | |
197 // significance. | |
198 int hashCode() => 499; | |
199 DartString toDartString() => const LiteralDartString("true"); | |
200 } | |
201 | |
202 class FalseConstant extends BoolConstant { | |
203 final bool value = false; | |
204 | |
205 factory FalseConstant() => const FalseConstant._internal(); | |
206 const FalseConstant._internal() : super._internal(); | |
207 bool isFalse() => true; | |
208 | |
209 void writeJsCode(StringBuffer buffer, ConstantHandler handler) { | |
210 buffer.add("false"); | |
211 } | |
212 | |
213 TrueConstant negate() => new TrueConstant(); | |
214 | |
215 bool operator ==(var other) => this === other; | |
216 // The magic constant is just a random value. It does not have any | |
217 // significance. | |
218 int hashCode() => 536555975; | |
219 DartString toDartString() => const LiteralDartString("false"); | |
220 } | |
221 | |
222 class StringConstant extends PrimitiveConstant { | |
223 final DartString value; | |
224 int _hashCode; | |
225 | |
226 StringConstant(this.value) { | |
227 // TODO(floitsch): cache StringConstants. | |
228 // TODO(floitsch): compute hashcode without calling toString() on the | |
229 // DartString. | |
230 _hashCode = value.slowToString().hashCode(); | |
231 } | |
232 bool isString() => true; | |
233 | |
234 void writeJsCode(StringBuffer buffer, ConstantHandler handler) { | |
235 buffer.add("'"); | |
236 ConstantHandler.writeEscapedString(value, buffer, (reason) { | |
237 throw new CompilerCancelledException(reason); | |
238 }); | |
239 buffer.add("'"); | |
240 } | |
241 | |
242 bool operator ==(var other) { | |
243 if (other is !StringConstant) return false; | |
244 StringConstant otherString = other; | |
245 return (_hashCode == otherString._hashCode) && (value == otherString.value); | |
246 } | |
247 | |
248 int hashCode() => _hashCode; | |
249 DartString toDartString() => value; | |
250 } | |
251 | |
252 class ObjectConstant extends Constant { | |
253 final Type type; | |
254 | |
255 ObjectConstant(this.type); | |
256 bool isObject() => true; | |
257 | |
258 // TODO(1603): The class should be marked as abstract, but the VM doesn't | |
259 // currently allow this. | |
260 abstract int hashCode(); | |
261 | |
262 void writeCanonicalizedJsCode(StringBuffer buffer, ConstantHandler handler) { | |
263 String name = handler.getNameForConstant(this); | |
264 String isolatePrototype = "${handler.compiler.namer.ISOLATE}.prototype"; | |
265 buffer.add("$isolatePrototype.$name"); | |
266 } | |
267 } | |
268 | |
269 class ListConstant extends ObjectConstant { | |
270 final List<Constant> entries; | |
271 int _hashCode; | |
272 | |
273 ListConstant(Type type, this.entries) : super(type) { | |
274 // TODO(floitsch): create a better hash. | |
275 int hash = 0; | |
276 for (Constant input in entries) hash ^= input.hashCode(); | |
277 _hashCode = hash; | |
278 } | |
279 bool isList() => true; | |
280 | |
281 void writeJsCode(StringBuffer buffer, ConstantHandler handler) { | |
282 // TODO(floitsch): we should not need to go through the compiler to make | |
283 // the list constant. | |
284 String isolatePrototype = "${handler.compiler.namer.ISOLATE}.prototype"; | |
285 buffer.add("$isolatePrototype.makeConstantList"); | |
286 buffer.add("(["); | |
287 for (int i = 0; i < entries.length; i++) { | |
288 if (i != 0) buffer.add(", "); | |
289 Constant entry = entries[i]; | |
290 entry.writeCanonicalizedJsCode(buffer, handler); | |
291 } | |
292 buffer.add("])"); | |
293 } | |
294 | |
295 bool operator ==(var other) { | |
296 if (other is !ListConstant) return false; | |
297 ListConstant otherList = other; | |
298 if (hashCode() != otherList.hashCode()) return false; | |
299 // TODO(floitsch): verify that the generic types are the same. | |
300 if (entries.length != otherList.entries.length) return false; | |
301 for (int i = 0; i < entries.length; i++) { | |
302 if (entries[i] != otherList.entries[i]) return false; | |
303 } | |
304 return true; | |
305 } | |
306 | |
307 int hashCode() => _hashCode; | |
308 | |
309 List<Constant> getDependencies() => entries; | |
310 } | |
311 | |
312 class MapConstant extends ObjectConstant { | |
313 /** The dart class implementing constant map literals. */ | |
314 static final SourceString DART_CLASS = const SourceString("ConstantMap"); | |
315 static final SourceString LENGTH_NAME = const SourceString("length"); | |
316 static final SourceString JS_OBJECT_NAME = const SourceString("_jsObject"); | |
317 static final SourceString KEYS_NAME = const SourceString("_keys"); | |
318 | |
319 final ListConstant keys; | |
320 final List<Constant> values; | |
321 int _hashCode; | |
322 | |
323 MapConstant(Type type, this.keys, this.values) : super(type) { | |
324 // TODO(floitsch): create a better hash. | |
325 int hash = 0; | |
326 for (Constant value in values) hash ^= value.hashCode(); | |
327 _hashCode = hash; | |
328 } | |
329 bool isMap() => true; | |
330 | |
331 void writeJsCode(StringBuffer buffer, ConstantHandler handler) { | |
332 | |
333 void writeJsMap() { | |
334 buffer.add("{"); | |
335 for (int i = 0; i < keys.entries.length; i++) { | |
336 if (i != 0) buffer.add(", "); | |
337 | |
338 StringConstant key = keys.entries[i]; | |
339 key.writeJsCode(buffer, handler); | |
340 buffer.add(": "); | |
341 Constant value = values[i]; | |
342 value.writeCanonicalizedJsCode(buffer, handler); | |
343 } | |
344 buffer.add("}"); | |
345 } | |
346 | |
347 void badFieldCountError() { | |
348 handler.compiler.internalError( | |
349 "Compiler and ConstantMap disagree on number of fields."); | |
350 } | |
351 | |
352 ClassElement classElement = type.element; | |
353 buffer.add("new "); | |
354 buffer.add(handler.getJsConstructor(classElement)); | |
355 buffer.add("("); | |
356 // The arguments of the JavaScript constructor for any given Dart class | |
357 // are in the same order as the members of the class element. | |
358 int emittedArgumentCount = 0; | |
359 for (Element element in classElement.members) { | |
360 if (element.name == LENGTH_NAME) { | |
361 buffer.add(keys.entries.length); | |
362 } else if (element.name == JS_OBJECT_NAME) { | |
363 writeJsMap(); | |
364 } else if (element.name == KEYS_NAME) { | |
365 keys.writeCanonicalizedJsCode(buffer, handler); | |
366 } else { | |
367 // Skip methods. | |
368 if (element.kind == ElementKind.FIELD) badFieldCountError(); | |
369 continue; | |
370 } | |
371 emittedArgumentCount++; | |
372 if (emittedArgumentCount == 3) { | |
373 break; // All arguments have been emitted. | |
374 } else { | |
375 buffer.add(", "); | |
376 } | |
377 } | |
378 if (emittedArgumentCount != 3) badFieldCountError(); | |
379 buffer.add(")"); | |
380 } | |
381 | |
382 bool operator ==(var other) { | |
383 if (other is !MapConstant) return false; | |
384 MapConstant otherMap = other; | |
385 if (hashCode() != otherMap.hashCode()) return false; | |
386 // TODO(floitsch): verify that the generic types are the same. | |
387 if (keys != otherMap.keys) return false; | |
388 for (int i = 0; i < values.length; i++) { | |
389 if (values[i] != otherMap.values[i]) return false; | |
390 } | |
391 return true; | |
392 } | |
393 | |
394 int hashCode() => _hashCode; | |
395 | |
396 List<Constant> getDependencies() { | |
397 List<Constant> result = <Constant>[keys]; | |
398 result.addAll(values); | |
399 return result; | |
400 } | |
401 } | |
402 | |
403 class ConstructedConstant extends ObjectConstant { | |
404 final List<Constant> fields; | |
405 int _hashCode; | |
406 | |
407 ConstructedConstant(Type type, this.fields) : super(type) { | |
408 assert(type !== null); | |
409 // TODO(floitsch): create a better hash. | |
410 int hash = 0; | |
411 for (Constant field in fields) { | |
412 hash ^= field.hashCode(); | |
413 } | |
414 hash ^= type.element.hashCode(); | |
415 _hashCode = hash; | |
416 } | |
417 bool isConstructedObject() => true; | |
418 | |
419 void writeJsCode(StringBuffer buffer, ConstantHandler handler) { | |
420 buffer.add("new "); | |
421 buffer.add(handler.getJsConstructor(type.element)); | |
422 buffer.add("("); | |
423 for (int i = 0; i < fields.length; i++) { | |
424 if (i != 0) buffer.add(", "); | |
425 Constant field = fields[i]; | |
426 field.writeCanonicalizedJsCode(buffer, handler); | |
427 } | |
428 buffer.add(")"); | |
429 } | |
430 | |
431 bool operator ==(var otherVar) { | |
432 if (otherVar is !ConstructedConstant) return false; | |
433 ConstructedConstant other = otherVar; | |
434 if (hashCode() != other.hashCode()) return false; | |
435 // TODO(floitsch): verify that the (generic) types are the same. | |
436 if (type.element != other.type.element) return false; | |
437 if (fields.length != other.fields.length) return false; | |
438 for (int i = 0; i < fields.length; i++) { | |
439 if (fields[i] != other.fields[i]) return false; | |
440 } | |
441 return true; | |
442 } | |
443 | |
444 int hashCode() => _hashCode; | |
445 List<Constant> getDependencies() => fields; | |
446 } | |
447 | |
448 /** | |
449 * The [ConstantHandler] keeps track of compile-time constants, | |
450 * initializations of global and static fields, and default values of | |
451 * optional parameters. | |
452 */ | |
453 class ConstantHandler extends CompilerTask { | |
454 // Contains the initial value of fields. Must contain all static and global | |
455 // initializations of used fields. May contain caches for instance fields. | |
456 final Map<VariableElement, Constant> initialVariableValues; | |
457 | |
458 // Map from compile-time constants to their JS name. | |
459 final Map<Constant, String> compiledConstants; | |
460 | |
461 // The set of variable elements that are in the process of being computed. | |
462 final Set<VariableElement> pendingVariables; | |
463 | |
464 ConstantHandler(Compiler compiler) | |
465 : initialVariableValues = new Map<VariableElement, Dynamic>(), | |
466 compiledConstants = new Map<Constant, String>(), | |
467 pendingVariables = new Set<VariableElement>(), | |
468 super(compiler); | |
469 String get name() => 'ConstantHandler'; | |
470 | |
471 void registerCompileTimeConstant(Constant constant) { | |
472 Function ifAbsentThunk = (() => compiler.namer.getFreshGlobalName("CTC")); | |
473 compiledConstants.putIfAbsent(constant, ifAbsentThunk); | |
474 } | |
475 | |
476 /** | |
477 * Compiles the initial value of the given field and stores it in an internal | |
478 * map. | |
479 * | |
480 * [WorkItem] must contain a [VariableElement] refering to a global or | |
481 * static field. | |
482 */ | |
483 void compileWorkItem(WorkItem work) { | |
484 measure(() { | |
485 assert(work.element.kind == ElementKind.FIELD | |
486 || work.element.kind == ElementKind.PARAMETER | |
487 || work.element.kind == ElementKind.FIELD_PARAMETER); | |
488 VariableElement element = work.element; | |
489 // Shortcut if it has already been compiled. | |
490 if (initialVariableValues.containsKey(element)) return; | |
491 compileVariableWithDefinitions(element, work.resolutionTree); | |
492 assert(pendingVariables.isEmpty()); | |
493 }); | |
494 } | |
495 | |
496 Constant compileVariable(VariableElement element) { | |
497 return measure(() { | |
498 if (initialVariableValues.containsKey(element)) { | |
499 Constant result = initialVariableValues[element]; | |
500 return result; | |
501 } | |
502 TreeElements definitions = compiler.analyzeElement(element); | |
503 Constant constant = compileVariableWithDefinitions(element, definitions); | |
504 return constant; | |
505 }); | |
506 } | |
507 | |
508 Constant compileVariableWithDefinitions(VariableElement element, | |
509 TreeElements definitions) { | |
510 return measure(() { | |
511 Node node = element.parseNode(compiler); | |
512 if (pendingVariables.contains(element)) { | |
513 MessageKind kind = MessageKind.CYCLIC_COMPILE_TIME_CONSTANTS; | |
514 compiler.reportError(node, | |
515 new CompileTimeConstantError(kind, const [])); | |
516 } | |
517 pendingVariables.add(element); | |
518 | |
519 SendSet assignment = node.asSendSet(); | |
520 Constant value; | |
521 if (assignment === null) { | |
522 // No initial value. | |
523 value = new NullConstant(); | |
524 } else { | |
525 Node right = assignment.arguments.head; | |
526 value = compileNodeWithDefinitions(right, definitions); | |
527 } | |
528 initialVariableValues[element] = value; | |
529 pendingVariables.remove(element); | |
530 return value; | |
531 }); | |
532 } | |
533 | |
534 Constant compileNodeWithDefinitions(Node node, TreeElements definitions) { | |
535 return measure(() { | |
536 assert(node !== null); | |
537 CompileTimeConstantEvaluator evaluator = | |
538 new CompileTimeConstantEvaluator(definitions, compiler); | |
539 return evaluator.evaluate(node); | |
540 }); | |
541 } | |
542 | |
543 /** | |
544 * Returns a [List] of static non final fields that need to be initialized. | |
545 * The list must be evaluated in order since the fields might depend on each | |
546 * other. | |
547 */ | |
548 List<VariableElement> getStaticNonFinalFieldsForEmission() { | |
549 return initialVariableValues.getKeys().filter((element) { | |
550 return element.kind == ElementKind.FIELD | |
551 && !element.isInstanceMember() | |
552 && !element.modifiers.isFinal(); | |
553 }); | |
554 } | |
555 | |
556 /** | |
557 * Returns a [List] of static final fields that need to be initialized. The | |
558 * list must be evaluated in order since the fields might depend on each | |
559 * other. | |
560 */ | |
561 List<VariableElement> getStaticFinalFieldsForEmission() { | |
562 return initialVariableValues.getKeys().filter((element) { | |
563 return element.kind == ElementKind.FIELD | |
564 && !element.isInstanceMember() | |
565 && element.modifiers.isFinal(); | |
566 }); | |
567 } | |
568 | |
569 List<Constant> getConstantsForEmission() { | |
570 // We must emit dependencies before their uses. | |
571 Set<Constant> seenConstants = new Set<Constant>(); | |
572 List<Constant> result = new List<Constant>(); | |
573 | |
574 void addConstant(Constant constant) { | |
575 if (!seenConstants.contains(constant)) { | |
576 constant.getDependencies().forEach(addConstant); | |
577 assert(!seenConstants.contains(constant)); | |
578 result.add(constant); | |
579 seenConstants.add(constant); | |
580 } | |
581 } | |
582 | |
583 compiledConstants.forEach((Constant key, ignored) => addConstant(key)); | |
584 return result; | |
585 } | |
586 | |
587 String getNameForConstant(Constant constant) { | |
588 return compiledConstants[constant]; | |
589 } | |
590 | |
591 StringBuffer writeJsCode(StringBuffer buffer, Constant value) { | |
592 value.writeJsCode(buffer, this); | |
593 return buffer; | |
594 } | |
595 | |
596 StringBuffer writeJsCodeForVariable(StringBuffer buffer, | |
597 VariableElement element) { | |
598 if (!initialVariableValues.containsKey(element)) { | |
599 compiler.internalError("No initial value for given element", | |
600 element: element); | |
601 } | |
602 Constant constant = initialVariableValues[element]; | |
603 if (constant.isObject()) { | |
604 String name = compiledConstants[constant]; | |
605 buffer.add("${compiler.namer.ISOLATE}.prototype.$name"); | |
606 } else { | |
607 writeJsCode(buffer, constant); | |
608 } | |
609 return buffer; | |
610 } | |
611 | |
612 /** | |
613 * Write the contents of the quoted string to a [StringBuffer] in | |
614 * a form that is valid as JavaScript string literal content. | |
615 * The string is assumed quoted by single quote characters. | |
616 */ | |
617 static void writeEscapedString(DartString string, | |
618 StringBuffer buffer, | |
619 void cancel(String reason)) { | |
620 Iterator<int> iterator = string.iterator(); | |
621 while (iterator.hasNext()) { | |
622 int code = iterator.next(); | |
623 if (code === $SQ) { | |
624 buffer.add(@"\'"); | |
625 } else if (code === $LF) { | |
626 buffer.add(@'\n'); | |
627 } else if (code === $CR) { | |
628 buffer.add(@'\r'); | |
629 } else if (code === $LS) { | |
630 // This Unicode line terminator and $PS are invalid in JS string | |
631 // literals. | |
632 buffer.add(@'\u2028'); | |
633 } else if (code === $PS) { | |
634 buffer.add(@'\u2029'); | |
635 } else if (code === $BACKSLASH) { | |
636 buffer.add(@'\\'); | |
637 } else { | |
638 if (code > 0xffff) { | |
639 cancel('Unhandled non-BMP character: U+${code.toRadixString(16)}'); | |
640 } | |
641 // TODO(lrn): Consider whether all codes above 0x7f really need to | |
642 // be escaped. We build a Dart string here, so it should be a literal | |
643 // stage that converts it to, e.g., UTF-8 for a JS interpreter. | |
644 if (code < 0x20) { | |
645 buffer.add(@'\x'); | |
646 if (code < 0x10) buffer.add('0'); | |
647 buffer.add(code.toRadixString(16)); | |
648 } else if (code >= 0x80) { | |
649 if (code < 0x100) { | |
650 buffer.add(@'\x'); | |
651 buffer.add(code.toRadixString(16)); | |
652 } else { | |
653 buffer.add(@'\u'); | |
654 if (code < 0x1000) { | |
655 buffer.add('0'); | |
656 } | |
657 buffer.add(code.toRadixString(16)); | |
658 } | |
659 } else { | |
660 buffer.add(new String.fromCharCodes(<int>[code])); | |
661 } | |
662 } | |
663 } | |
664 } | |
665 | |
666 String getJsConstructor(ClassElement element) { | |
667 return compiler.namer.isolatePropertyAccess(element); | |
668 } | |
669 } | |
670 | |
671 class CompileTimeConstantEvaluator extends AbstractVisitor { | |
672 final TreeElements elements; | |
673 final Compiler compiler; | |
674 | |
675 CompileTimeConstantEvaluator(this.elements, this.compiler); | |
676 | |
677 Constant evaluate(Node node) { | |
678 return node.accept(this); | |
679 } | |
680 | |
681 visitNode(Node node) { | |
682 error(node); | |
683 } | |
684 | |
685 Constant visitLiteralBool(LiteralBool node) { | |
686 return new BoolConstant(node.value); | |
687 } | |
688 | |
689 Constant visitLiteralDouble(LiteralDouble node) { | |
690 return new DoubleConstant(node.value); | |
691 } | |
692 | |
693 Constant visitLiteralInt(LiteralInt node) { | |
694 return new IntConstant(node.value); | |
695 } | |
696 | |
697 Constant visitLiteralList(LiteralList node) { | |
698 if (!node.isConst()) error(node); | |
699 List<Constant> arguments = <Constant>[]; | |
700 for (Link<Node> link = node.elements.nodes; | |
701 !link.isEmpty(); | |
702 link = link.tail) { | |
703 arguments.add(evaluate(link.head)); | |
704 } | |
705 // TODO(floitsch): get type from somewhere. | |
706 Type type = null; | |
707 Constant constant = new ListConstant(type, arguments); | |
708 compiler.constantHandler.registerCompileTimeConstant(constant); | |
709 return constant; | |
710 } | |
711 | |
712 Constant visitLiteralMap(LiteralMap node) { | |
713 // TODO(floitsch): check for isConst, once the parser adds it into the node. | |
714 // if (!node.isConst()) error(node); | |
715 List<StringConstant> keys = <StringConstant>[]; | |
716 List<Constant> values = <Constant>[]; | |
717 bool hasProtoKey = false; | |
718 for (Link<Node> link = node.entries.nodes; | |
719 !link.isEmpty(); | |
720 link = link.tail) { | |
721 LiteralMapEntry entry = link.head; | |
722 Constant key = evaluate(entry.key); | |
723 if (!key.isString() || entry.key.asLiteralString() === null) { | |
724 MessageKind kind = MessageKind.KEY_NOT_A_STRING_LITERAL; | |
725 compiler.reportError(entry.key, new ResolutionError(kind, const [])); | |
726 } | |
727 // TODO(floitsch): make this faster. | |
728 StringConstant keyConstant = key; | |
729 if (keyConstant.value == new LiteralDartString("__proto__")) { | |
730 hasProtoKey = true; | |
731 } | |
732 keys.add(key); | |
733 values.add(evaluate(entry.value)); | |
734 } | |
735 if (hasProtoKey) { | |
736 compiler.unimplemented("visitLiteralMap with __proto__ key", | |
737 node: node); | |
738 } | |
739 // TODO(floitsch): this should be a List<String> type. | |
740 Type keysType = null; | |
741 ListConstant keysList = new ListConstant(keysType, keys); | |
742 compiler.constantHandler.registerCompileTimeConstant(keysList); | |
743 ClassElement classElement = | |
744 compiler.jsHelperLibrary.find(MapConstant.DART_CLASS); | |
745 classElement.ensureResolved(compiler); | |
746 // TODO(floitsch): copy over the generic type. | |
747 Type type = new SimpleType(classElement.name, classElement); | |
748 compiler.registerInstantiatedClass(classElement); | |
749 Constant constant = new MapConstant(type, keysList, values); | |
750 compiler.constantHandler.registerCompileTimeConstant(constant); | |
751 return constant; | |
752 } | |
753 | |
754 Constant visitLiteralNull(LiteralNull node) { | |
755 return new NullConstant(); | |
756 } | |
757 | |
758 Constant visitLiteralString(LiteralString node) { | |
759 return new StringConstant(node.dartString); | |
760 } | |
761 | |
762 Constant visitStringJuxtaposition(StringJuxtaposition node) { | |
763 StringConstant left = evaluate(node.first); | |
764 StringConstant right = evaluate(node.second); | |
765 return new StringConstant(new DartString.concat(left.value, right.value)); | |
766 } | |
767 | |
768 Constant visitStringInterpolation(StringInterpolation node) { | |
769 StringConstant initialString = evaluate(node.string); | |
770 DartString accumulator = initialString.value; | |
771 for (StringInterpolationPart part in node.parts) { | |
772 Constant expression = evaluate(part.expression); | |
773 DartString expressionString; | |
774 if (expression.isNum() || expression.isBool()) { | |
775 Object value = expression.value; | |
776 expressionString = new DartString.literal(value.toString()); | |
777 } else if (expression.isString()) { | |
778 expressionString = expression.value; | |
779 } else { | |
780 error(part.expression); | |
781 } | |
782 accumulator = new DartString.concat(accumulator, expressionString); | |
783 StringConstant partString = evaluate(part.string); | |
784 accumulator = new DartString.concat(accumulator, partString.value); | |
785 }; | |
786 return new StringConstant(accumulator); | |
787 } | |
788 | |
789 // TODO(floitsch): provide better error-messages. | |
790 Constant visitSend(Send send) { | |
791 Element element = elements[send]; | |
792 if (Elements.isStaticOrTopLevelField(element)) { | |
793 if (element.modifiers === null || | |
794 !element.modifiers.isFinal()) { | |
795 error(send); | |
796 } | |
797 return compiler.compileVariable(element); | |
798 } else if (send.isPrefix) { | |
799 assert(send.isOperator); | |
800 Constant receiverConstant = evaluate(send.receiver); | |
801 Operator op = send.selector; | |
802 Constant folded; | |
803 switch (op.source.stringValue) { | |
804 case "!": | |
805 folded = const NotOperation().fold(receiverConstant); | |
806 break; | |
807 case "-": | |
808 folded = const NegateOperation().fold(receiverConstant); | |
809 break; | |
810 case "~": | |
811 folded = const BitNotOperation().fold(receiverConstant); | |
812 break; | |
813 default: | |
814 compiler.internalError("Unexpected operator.", node: op); | |
815 break; | |
816 } | |
817 if (folded === null) error(send); | |
818 return folded; | |
819 } else if (send.isOperator && !send.isPostfix) { | |
820 assert(send.argumentCount() == 1); | |
821 Constant left = evaluate(send.receiver); | |
822 Constant right = evaluate(send.argumentsNode.nodes.head); | |
823 Operator op = send.selector.asOperator(); | |
824 Constant folded; | |
825 switch (op.source.stringValue) { | |
826 case "+": | |
827 if (left.isString() && !right.isString()) { | |
828 // At the moment only compile-time concatenation of two strings is | |
829 // allowed. | |
830 error(send); | |
831 } | |
832 folded = const AddOperation().fold(left, right); | |
833 break; | |
834 case "-": | |
835 folded = const SubtractOperation().fold(left, right); | |
836 break; | |
837 case "*": | |
838 folded = const MultiplyOperation().fold(left, right); | |
839 break; | |
840 case "/": | |
841 folded = const DivideOperation().fold(left, right); | |
842 break; | |
843 case "%": | |
844 folded = const ModuloOperation().fold(left, right); | |
845 break; | |
846 case "~/": | |
847 folded = const TruncatingDivideOperation().fold(left, right); | |
848 break; | |
849 case "|": | |
850 folded = const BitOrOperation().fold(left, right); | |
851 break; | |
852 case "&": | |
853 folded = const BitAndOperation().fold(left, right); | |
854 break; | |
855 case "^": | |
856 folded = const BitXorOperation().fold(left, right); | |
857 break; | |
858 case "||": | |
859 folded = const BooleanOr().fold(left, right); | |
860 break; | |
861 case "&&": | |
862 folded = const BooleanAnd().fold(left, right); | |
863 break; | |
864 case "<<": | |
865 folded = const ShiftLeftOperation().fold(left, right); | |
866 break; | |
867 case ">>": | |
868 folded = const ShiftRightOperation().fold(left, right); | |
869 break; | |
870 case "<": | |
871 folded = const LessOperation().fold(left, right); | |
872 break; | |
873 case "<=": | |
874 folded = const LessEqualOperation().fold(left, right); | |
875 break; | |
876 case ">": | |
877 folded = const GreaterOperation().fold(left, right); | |
878 break; | |
879 case ">=": | |
880 folded = const GreaterEqualOperation().fold(left, right); | |
881 break; | |
882 case "==": | |
883 if (left.isPrimitive() && right.isPrimitive()) { | |
884 folded = const EqualsOperation().fold(left, right); | |
885 } | |
886 break; | |
887 case "===": | |
888 if (left.isPrimitive() && right.isPrimitive()) { | |
889 folded = const IdentityOperation().fold(left, right); | |
890 } | |
891 break; | |
892 case "!=": | |
893 if (left.isPrimitive() && right.isPrimitive()) { | |
894 BoolConstant areEquals = const EqualsOperation().fold(left, right); | |
895 if (areEquals === null) { | |
896 folded = null; | |
897 } else { | |
898 folded = areEquals.negate(); | |
899 } | |
900 } | |
901 break; | |
902 case "!==": | |
903 if (left.isPrimitive() && right.isPrimitive()) { | |
904 BoolConstant areIdentical = | |
905 const IdentityOperation().fold(left, right); | |
906 if (areIdentical === null) { | |
907 folded = null; | |
908 } else { | |
909 folded = areIdentical.negate(); | |
910 } | |
911 } | |
912 break; | |
913 default: | |
914 compiler.internalError("Unexpected operator.", node: op); | |
915 break; | |
916 } | |
917 if (folded === null) error(send); | |
918 return folded; | |
919 } | |
920 return super.visitSend(send); | |
921 } | |
922 | |
923 visitSendSet(SendSet node) { | |
924 error(node); | |
925 } | |
926 | |
927 /** Returns the list of constants that are passed to the static function. */ | |
928 List<Constant> evaluateArgumentsToConstructor(Send send, | |
929 FunctionElement target) { | |
930 FunctionParameters parameters = target.computeParameters(compiler); | |
931 List<Constant> arguments = <Constant>[]; | |
932 Selector selector = elements.getSelector(send); | |
933 | |
934 Function compileArgument = evaluate; | |
935 Function compileConstant = compiler.compileVariable; | |
936 bool succeeded = selector.addSendArgumentsToList( | |
937 send, arguments, parameters, compileArgument, compileConstant); | |
938 if (!succeeded) error(send); | |
939 return arguments; | |
940 } | |
941 | |
942 Constant visitNewExpression(NewExpression node) { | |
943 if (!node.isConst()) error(node); | |
944 | |
945 FunctionElement constructor = elements[node.send]; | |
946 ClassElement classElement = constructor.enclosingElement; | |
947 if (classElement.isInterface()) { | |
948 compiler.resolver.resolveMethodElement(constructor); | |
949 constructor = constructor.defaultImplementation; | |
950 classElement = constructor.enclosingElement; | |
951 } | |
952 | |
953 List<Constant> arguments = | |
954 evaluateArgumentsToConstructor(node.send, constructor); | |
955 ConstructorEvaluator evaluator = | |
956 new ConstructorEvaluator(constructor, compiler); | |
957 evaluator.evaluateConstructorFieldValues(arguments); | |
958 List<Constant>jsNewArguments = evaluator.buildJsNewArguments(classElement); | |
959 | |
960 compiler.registerInstantiatedClass(classElement); | |
961 // TODO(floitsch): take generic types into account. | |
962 Type type = classElement.computeType(compiler); | |
963 Constant constant = new ConstructedConstant(type, jsNewArguments); | |
964 compiler.constantHandler.registerCompileTimeConstant(constant); | |
965 return constant; | |
966 } | |
967 | |
968 Constant visitParenthesizedExpression(ParenthesizedExpression node) { | |
969 return node.expression.accept(this); | |
970 } | |
971 | |
972 error(Node node) { | |
973 // TODO(floitsch): get the list of constants that are currently compiled | |
974 // and present some kind of stack-trace. | |
975 MessageKind kind = MessageKind.NOT_A_COMPILE_TIME_CONSTANT; | |
976 compiler.reportError(node, new CompileTimeConstantError(kind, const [])); | |
977 } | |
978 } | |
979 | |
980 class ConstructorEvaluator extends CompileTimeConstantEvaluator { | |
981 FunctionElement constructor; | |
982 final Map<Element, Constant> definitions; | |
983 final Map<Element, Constant> fieldValues; | |
984 | |
985 ConstructorEvaluator(FunctionElement constructor, Compiler compiler) | |
986 : this.constructor = constructor, | |
987 this.definitions = new Map<Element, Constant>(), | |
988 this.fieldValues = new Map<Element, Constant>(), | |
989 super(compiler.resolver.resolveMethodElement(constructor), | |
990 compiler); | |
991 | |
992 Constant visitSend(Send send) { | |
993 Element element = elements[send]; | |
994 if (Elements.isLocal(element)) { | |
995 Constant constant = definitions[element]; | |
996 if (constant === null) { | |
997 compiler.internalError("Local variable without value", node: send); | |
998 } | |
999 return constant; | |
1000 } | |
1001 return super.visitSend(send); | |
1002 } | |
1003 | |
1004 /** | |
1005 * Given the arguments (a list of constants) assigns them to the parameters, | |
1006 * updating the definitions map. If the constructor has field-initializer | |
1007 * parameters (like [:this.x:]), also updates the [fieldValues] map. | |
1008 */ | |
1009 void assignArgumentsToParameters(List<Constant> arguments) { | |
1010 // Assign arguments to parameters. | |
1011 FunctionParameters parameters = constructor.computeParameters(compiler); | |
1012 int index = 0; | |
1013 parameters.forEachParameter((Element parameter) { | |
1014 Constant argument = arguments[index++]; | |
1015 definitions[parameter] = argument; | |
1016 if (parameter.kind == ElementKind.FIELD_PARAMETER) { | |
1017 FieldParameterElement fieldParameterElement = parameter; | |
1018 fieldValues[fieldParameterElement.fieldElement] = argument; | |
1019 } | |
1020 }); | |
1021 } | |
1022 | |
1023 void evaluateSuperOrRedirectSend(FunctionElement targetConstructor, | |
1024 List<Constant> targetArguments) { | |
1025 ConstructorEvaluator evaluator = | |
1026 new ConstructorEvaluator(targetConstructor, compiler); | |
1027 evaluator.evaluateConstructorFieldValues(targetArguments); | |
1028 // Copy over the fieldValues from the super/redirect-constructor. | |
1029 evaluator.fieldValues.forEach((key, value) => fieldValues[key] = value); | |
1030 } | |
1031 | |
1032 /** | |
1033 * Runs through the initializers of the given [constructor] and updates | |
1034 * the [fieldValues] map. | |
1035 */ | |
1036 void evaluateConstructorInitializers() { | |
1037 FunctionExpression functionNode = constructor.parseNode(compiler); | |
1038 NodeList initializerList = functionNode.initializers; | |
1039 | |
1040 bool foundSuperOrRedirect = false; | |
1041 | |
1042 if (initializerList !== null) { | |
1043 for (Link<Node> link = initializerList.nodes; | |
1044 !link.isEmpty(); | |
1045 link = link.tail) { | |
1046 assert(link.head is Send); | |
1047 if (link.head is !SendSet) { | |
1048 // A super initializer or constructor redirection. | |
1049 Send call = link.head; | |
1050 FunctionElement targetConstructor = elements[call]; | |
1051 List<Constant> targetArguments = | |
1052 evaluateArgumentsToConstructor(call, targetConstructor); | |
1053 evaluateSuperOrRedirectSend(targetConstructor, targetArguments); | |
1054 foundSuperOrRedirect = true; | |
1055 } else { | |
1056 // A field initializer. | |
1057 SendSet init = link.head; | |
1058 Link<Node> initArguments = init.arguments; | |
1059 assert(!initArguments.isEmpty() && initArguments.tail.isEmpty()); | |
1060 Constant fieldValue = evaluate(initArguments.head); | |
1061 fieldValues[elements[init]] = fieldValue; | |
1062 } | |
1063 } | |
1064 } | |
1065 | |
1066 if (!foundSuperOrRedirect) { | |
1067 // No super initializer found. Try to find the default constructor if | |
1068 // the class is not Object. | |
1069 ClassElement enclosingClass = constructor.enclosingElement; | |
1070 ClassElement superClass = enclosingClass.superclass; | |
1071 if (enclosingClass != compiler.objectClass) { | |
1072 assert(superClass !== null); | |
1073 assert(superClass.isResolved); | |
1074 FunctionElement targetConstructor = | |
1075 superClass.lookupConstructor(superClass.name); | |
1076 if (targetConstructor === null) { | |
1077 compiler.internalError("no default constructor available"); | |
1078 } | |
1079 evaluateSuperOrRedirectSend(targetConstructor, const <Constant>[]); | |
1080 } | |
1081 } | |
1082 } | |
1083 | |
1084 /** | |
1085 * Simulates the execution of the [constructor] with the given | |
1086 * [arguments] to obtain the field values that need to be passed to the | |
1087 * native JavaScript constructor. | |
1088 */ | |
1089 void evaluateConstructorFieldValues(List<Constant> arguments) { | |
1090 compiler.withCurrentElement(constructor, () { | |
1091 assignArgumentsToParameters(arguments); | |
1092 evaluateConstructorInitializers(); | |
1093 }); | |
1094 } | |
1095 | |
1096 List<Constant> buildJsNewArguments(ClassElement classElement) { | |
1097 List<Constant> jsNewArguments = <Constant>[]; | |
1098 // TODO(floitsch): share this code with the emitter, so that we don't | |
1099 // need to care about the order of fields here. | |
1100 while (classElement != compiler.objectClass) { | |
1101 for (Element member in classElement.members) { | |
1102 if (member.isInstanceMember() && member.kind == ElementKind.FIELD) { | |
1103 Constant fieldValue = fieldValues[member]; | |
1104 if (fieldValue === null) { | |
1105 // Use the default value. | |
1106 fieldValue = compiler.compileVariable(member); | |
1107 } | |
1108 jsNewArguments.add(fieldValue); | |
1109 } | |
1110 } | |
1111 classElement = classElement.superclass; | |
1112 } | |
1113 return jsNewArguments; | |
1114 } | |
1115 } | |
OLD | NEW |