Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(595)

Unified Diff: frog/gen.dart

Issue 10548047: Remove frog from the repository. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Move test and update apidoc.gyp. Created 8 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « frog/frogc.dart ('k') | frog/lang.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: frog/gen.dart
diff --git a/frog/gen.dart b/frog/gen.dart
deleted file mode 100644
index 7ee24a3b4e1e52002ab29f045a6450538ca6bf64..0000000000000000000000000000000000000000
--- a/frog/gen.dart
+++ /dev/null
@@ -1,2545 +0,0 @@
-// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-/**
- * Top level generator object for writing code and keeping track of
- * dependencies.
- *
- * Should have two compilation models, but only one implemented so far.
- *
- * 1. Do a top-level resolution of all types and their members.
- * 2. Start from main and walk the call-graph compiling members as needed.
- * 2a. That includes compiling overriding methods and calling methods by
- * selector when invoked on var.
- * 3. Spit out all required code.
- */
-class WorldGenerator {
- MethodMember main;
- CodeWriter writer;
- CodeWriter _mixins;
-
- final CallingContext mainContext;
-
- /**
- * Whether the app has any static fields used. Note this could still be true
- * and [globals] be empty if no static field has a default initialization.
- */
- bool hasStatics = false;
-
- /** Global const and static field initializations. */
- Map<String, GlobalValue> globals;
- CoreJs corejs;
-
- /** */
- Set<Type> typesWithDynamicDispatch;
-
- /**
- * For a type, which type-checks are on the prototype chain, and to they match
- * or not? Type checks are in-predicates (x is T) and type assertions.
- */
- Map<Type, Map<String, bool>> typeEmittedTests;
-
- WorldGenerator(main, this.writer)
- : this.main = main,
- mainContext = new MethodGenerator(main, null),
- globals = {},
- corejs = new CoreJs();
-
- analyze() {
- // Walk all code and find all NewExpressions - to determine possible types
- int nlibs=0, ntypes=0, nmems=0, nnews=0;
- for (var lib in world.libraries.getValues()) {
- nlibs += 1;
- for (var type in lib.types.getValues()) {
- // TODO(jmesserly): we can't accurately track if DOM types are
- // created or not, so we need to prepare to handle them.
- // This should be fixed by tightening up the return types in DOM.
- // Until then, this 'analysis' just marks all the DOM types as used.
- // TODO(jimhug): Do we still need this? Or do/can we handle this by
- // using return values?
- if (type.library.isDomOrHtml || type.isHiddenNativeType) {
- if (type.isClass) type.markUsed();
- }
-
- ntypes += 1;
- var allMembers = [];
- allMembers.addAll(type.constructors.getValues());
- allMembers.addAll(type.members.getValues());
- type.factories.forEach((f) => allMembers.add(f));
- for (var m in allMembers) {
- if (m.isAbstract || !(m.isMethod || m.isConstructor)) continue;
- m.methodData.analyze();
- }
- }
- }
- }
-
- run() {
- var mainTarget = new TypeValue(main.declaringType, main.span);
- var mainCall = main.invoke(mainContext, null, mainTarget, Arguments.EMPTY);
- main.declaringType.markUsed();
-
- if (options.compileAll) {
- markLibrariesUsed(
- [world.coreimpl, world.corelib, main.declaringType.library]);
- }
-
- // These are essentially always used through literals - just include them
- world.numImplType.markUsed();
- world.stringImplType.markUsed();
-
- if (corejs.useIndex || corejs.useSetIndex) {
- if (!options.disableBoundsChecks) {
- // These exceptions might be thrown by array bounds checks.
- markTypeUsed(world.corelib.types['IndexOutOfRangeException']);
- markTypeUsed(world.corelib.types['IllegalArgumentException']);
- }
- }
-
- // Only wrap the app as an isolate if the isolate library was imported.
- if (world.isolatelib != null) {
- corejs.useIsolates = true;
- MethodMember isolateMain =
- world.isolatelib.lookup('startRootIsolate', main.span);
- mainCall = isolateMain.invoke(mainContext, null,
- new TypeValue(world.isolatelib.topType, main.span),
- new Arguments(null, [main._get(mainContext, main.definition, null)]));
- }
-
- typeEmittedTests = new Map<Type, Map<String, bool>>();
-
- writeTypes(world.coreimpl);
- writeTypes(world.corelib);
-
- // Write the main library. This will cause all libraries to be written in
- // the topological sort order.
- writeTypes(main.declaringType.library);
-
- // Write out any inherited concrete members.
- // TODO(jmesserly): this won't need to come last once we are sorting types
- // correctly.
- if (_mixins != null) writer.write(_mixins.text);
-
- writeDynamicDispatchMetadata();
-
- writeGlobals();
- writer.writeln("if (typeof window != 'undefined' && typeof document != 'undefined' &&");
- writer.writeln(" window.addEventListener && document.readyState == 'loading') {");
- writer.writeln(" window.addEventListener('DOMContentLoaded', function(e) {");
- writer.writeln(" ${mainCall.code};");
- writer.writeln(" });");
- writer.writeln("} else {");
- writer.writeln(" ${mainCall.code};");
- writer.writeln("}");
- }
-
- void markLibrariesUsed(List<Library> libs) =>
- getAllTypes(libs).forEach(markTypeUsed);
-
- void markTypeUsed(Type type) {
- if (!type.isClass) return;
-
- type.markUsed();
- type.isTested = true;
- // (e.g. Math, console, process)
- type.isTested = !type.isTop && !(type.isNative &&
- type.members.getValues().every((m) => m.isStatic && !m.isFactory));
- final members = new List.from(type.members.getValues());
- members.addAll(type.constructors.getValues());
- type.factories.forEach((f) => members.add(f));
- for (var member in members) {
- if (member is PropertyMember) {
- if (member.getter != null) genMethod(member.getter);
- if (member.setter != null) genMethod(member.setter);
- }
-
- if (member is MethodMember) genMethod(member);
- }
- }
-
- void writeAllDynamicStubs(List<Library> libs) =>
- getAllTypes(libs).forEach((Type type) {
- if (type.isClass || type.isFunction) _writeDynamicStubs(type);
- });
-
- List<Type> getAllTypes(List<Library> libs) {
- List<Type> types = <Type>[];
- Set<Library> seen = new Set<Library>();
- for (var mainLib in libs) {
- Queue<Library> toCheck = new Queue.from([mainLib]);
- while (!toCheck.isEmpty()) {
- var lib = toCheck.removeFirst();
- if (seen.contains(lib)) continue;
- seen.add(lib);
- lib.imports.forEach((i) => toCheck.addLast(lib));
- lib.types.getValues().forEach((t) => types.add(t));
- }
- }
- return types;
- }
-
- GlobalValue globalForStaticField(FieldMember field, Value exp,
- List<Value> dependencies) {
- hasStatics = true;
- var key = "${field.declaringType.jsname}.${field.jsname}";
- var ret = globals[key];
- if (ret === null) {
- ret = new GlobalValue(exp.type, exp.code, field.isFinal, field, null,
- exp, exp.span, dependencies);
- globals[key] = ret;
- }
- return ret;
- }
-
- GlobalValue globalForConst(Value exp, List<Value> dependencies) {
- // Include type name to ensure unique constants - this matches
- // the code above that includes the type name for static fields.
- var key = '${exp.type.jsname}:${exp.code}';
- var ret = globals[key];
- if (ret === null) {
- // another egregious hack!!!
- var ns = globals.length.toString();
- while (ns.length < 4) ns = '0$ns';
- var name = "const\$${ns}";
- ret = new GlobalValue(exp.type, name, true, null, name, exp,
- exp.span, dependencies);
- globals[key] = ret;
- }
- assert(ret.type == exp.type);
- return ret;
- }
-
- writeTypes(Library lib) {
- if (lib.isWritten) return;
-
- // Do this first to be safe in the face of circular refs.
- lib.isWritten = true;
-
- // Ensure all imports have been written.
- for (var import in lib.imports) {
- writeTypes(import.library);
- }
-
- // Ensure that our source files have a notion of "order" so we can emit
- // types in the same order source files are imported.
- for (int i = 0; i < lib.sources.length; i++) {
- lib.sources[i].orderInLibrary = i;
- }
-
- writer.comment('// ********** Library ${lib.name} **************');
- if (lib.isCore) {
- // Generates the JS natives for dart:core.
- writer.comment('// ********** Natives dart:core **************');
- corejs.generate(writer);
- }
- for (var file in lib.natives) {
- var filename = basename(file.filename);
- writer.comment('// ********** Natives $filename **************');
- writer.writeln(file.text);
- }
- lib.topType.markUsed(); // TODO(jimhug): EGREGIOUS HACK
-
- var orderedTypes = _orderValues(lib.types);
-
- for (var type in orderedTypes) {
- if (type.isUsed && type.isClass) {
- writeType(type);
- // TODO(jimhug): Performance is terrible if we use current
- // reified generics approach for reified generic Arrays.
- if (type.isGeneric && type !== world.listFactoryType) {
- for (var ct in _orderValues(type._concreteTypes)) {
- if (ct.isUsed) writeType(ct);
- }
- }
- } else if (type.isFunction && type.varStubs.length > 0) {
- // Emit stubs on "Function" if needed
- writer.comment('// ********** Code for ${type.jsname} **************');
- _writeDynamicStubs(type);
- }
- // Type check functions for builtin JS types
- if (type.typeCheckCode != null) {
- writer.writeln(type.typeCheckCode);
- }
- }
- }
-
- genMethod(MethodMember meth) {
- meth.methodData.run(meth);
- }
-
- String _prototypeOf(Type type, String name) {
- if (type.isSingletonNative) {
- // e.g. window.console.log$1
- return '${type.jsname}.$name';
- } else if (type.isHiddenNativeType) {
- corejs.ensureDynamicProto();
- _usedDynamicDispatchOnType(type);
- return '\$dynamic("$name").${type.definition.nativeType.name}';
- } else {
- return '${type.jsname}.prototype.$name';
- }
- }
-
- /**
- * Make sure the methods that we add to Array and Object are
- * non-enumerable, so that we don't mess up any other third-party JS
- * libraries we might be using.
- * We return the necessary suffix (if any) we need to complete the patching.
- */
- String _writePrototypePatch(Type type, String name, String functionBody,
- CodeWriter writer, [bool isOneLiner=true]) {
- var writeFunction = writer.writeln;
- String ending = ';';
- if (!isOneLiner) {
- writeFunction = writer.enterBlock;
- ending = '';
- }
- if (type.isObject) {
- world.counters.objectProtoMembers++;
- }
- if (type.isObject || type.genericType == world.listFactoryType) {
- // We special case these two so that by default we can use "= function()"
- // syntax for better readability of the others.
- if (isOneLiner) {
- ending = ')$ending';
- }
- corejs.ensureDefProp();
- writeFunction(
- '\$defProp(${type.jsname}.prototype, "$name", $functionBody$ending');
- if (isOneLiner) return '}';
- return '});';
- } else {
- writeFunction('${_prototypeOf(type, name)} = ${functionBody}${ending}');
- return isOneLiner? '': '}';
- }
- }
-
- _maybeIsTest(Type onType, Type checkType) {
- bool isSubtype = onType.isSubtypeOf(checkType);
-
- var onTypeMap = typeEmittedTests[onType];
- if (onTypeMap == null) typeEmittedTests[onType] = onTypeMap = {};
-
- Type protoParent = onType.genericType == onType
- ? onType.parent
- : onType.genericType;
-
- needToOverride(checkName) {
- if (protoParent != null) {
- var map = typeEmittedTests[protoParent];
- if (map != null) {
- bool protoParentIsSubtype = map[checkName];
- if (protoParentIsSubtype != null &&
- protoParentIsSubtype == isSubtype) {
- return false;
- }
- }
- }
- return true;
- }
-
- if (checkType.isTested) {
- String checkName = 'is\$${checkType.jsname}';
- onTypeMap[checkName] = isSubtype;
- if (needToOverride(checkName)) {
- // TODO(jmesserly): cache these functions? they just return true or
- // false.
- _writePrototypePatch(onType, checkName,
- 'function(){return $isSubtype}', writer);
- }
- }
-
- if (checkType.isChecked) {
- String checkName = 'assert\$${checkType.jsname}';
- onTypeMap[checkName] = isSubtype;
- if (needToOverride(checkName)) {
- String body = 'return this';
- if (!isSubtype) {
- // Get the code to throw a TypeError.
- // TODO(jmesserly): it'd be nice not to duplicate this code, and
- // instead be able to refer to the JS function.
- body = world.objectType.varStubs[checkName].body;
- } else if (onType == world.stringImplType
- || onType == world.numImplType) {
- body = 'return ${onType.nativeType.name}(this)';
- }
- _writePrototypePatch(onType, checkName, 'function(){$body}', writer);
- }
- }
- }
-
- writeType(Type type) {
- if (type.isWritten) return;
-
- type.isWritten = true;
- writeType(type.genericType);
- // Ensure parent has been written before the child. Important ordering for
- // IE when we're using $inherits, since we don't have __proto__ available.
- if (type.parent != null) {
- writeType(type.parent);
- }
-
- var typeName = type.jsname != null ? type.jsname : 'top level';
- writer.comment('// ********** Code for ${typeName} **************');
- if (type.isNative && !type.isTop && !type.isConcreteGeneric) {
- var nativeName = type.definition.nativeType.name;
- if (nativeName == '') {
- writer.writeln('function ${type.jsname}() {}');
- } else if (type.jsname != nativeName) {
- if (type.isHiddenNativeType) {
- if (_hasStaticOrFactoryMethods(type)) {
- writer.writeln('var ${type.jsname} = {};');
- }
- } else {
- writer.writeln('var ${type.jsname} = ${nativeName};');
- }
- }
- }
-
- // We need the $inherits call to immediately follow the standard constructor
- // declaration. In particular, it needs to be called before factory
- // constructors are declared, otherwise $inherits will clear out the
- // prototype on IE (which does not have writable __proto__).
- if (!type.isTop) {
- if (type.genericType !== type) {
- corejs.ensureInheritsHelper();
- writer.writeln('\$inherits(${type.jsname}, ${type.genericType.jsname});');
- } else if (!type.isNative) {
- if (type.parent != null && !type.parent.isObject) {
- corejs.ensureInheritsHelper();
- writer.writeln('\$inherits(${type.jsname}, ${type.parent.jsname});');
- }
- }
- }
-
- if (type.isTop) {
- // no preludes for top type
- } else if (type.constructors.length == 0) {
- if (!type.isNative || type.isConcreteGeneric) {
- // TODO(jimhug): More guards to guarantee staticness
- writer.writeln('function ${type.jsname}() {}');
- }
- } else {
- bool wroteStandard = false;
- for (var c in type.constructors.getValues()) {
- if (c.methodData.writeDefinition(c, writer)) {
- if (c.isConstructor && c.constructorName == '') wroteStandard = true;
- }
- }
-
- if (!wroteStandard && (!type.isNative || type.genericType !== type)) {
- writer.writeln('function ${type.jsname}() {}');
- }
- }
-
- // Concrete types (like List<String>) will have this already defined on
- // their prototype from the generic type (like List)
- if (!type.isConcreteGeneric) {
- _maybeIsTest(type, type);
- }
- if (type.genericType._concreteTypes != null) {
- for (var ct in _orderValues(type.genericType._concreteTypes)) {
- _maybeIsTest(type, ct);
- }
- }
-
- if (type.interfaces != null) {
- final seen = new Set();
- final worklist = [];
- worklist.addAll(type.interfaces);
- seen.addAll(type.interfaces);
- while (!worklist.isEmpty()) {
- var interface_ = worklist.removeLast();
- _maybeIsTest(type, interface_.genericType);
- if (interface_.genericType._concreteTypes != null) {
- for (var ct in _orderValues(interface_.genericType._concreteTypes)) {
- _maybeIsTest(type, ct);
- }
- }
- for (var other in interface_.interfaces) {
- if (!seen.contains(other)) {
- worklist.addLast(other);
- seen.add(other);
- }
- }
- }
- }
-
- type.factories.forEach(_writeMethod);
-
- for (var member in _orderValues(type.members)) {
- if (member is FieldMember) {
- _writeField(member);
- }
-
- if (member is PropertyMember) {
- _writeProperty(member);
- }
-
- if (member.isMethod) {
- _writeMethod(member);
- }
- }
-
- _writeDynamicStubs(type);
- }
-
- /**
- * Returns [:true:] if the hidden native type has any static or factory
- * methods.
- *
- * class Float32Array native '*Float32Array' {
- * factory Float32Array(int len) => _construct(len);
- * static _construct(len) native 'return createFloat32Array(len);';
- * }
- *
- * The factory method and static member are generated something like this:
- * var lib_Float32Array = {};
- * lib_Float32Array.Float32Array$factory = ... ;
- * lib_Float32Array._construct = ... ;
- *
- * This predicate determines when we need to define lib_Float32Array.
- */
- bool _hasStaticOrFactoryMethods(Type type) {
- // TODO(jmesserly): better tracking if the methods are actually called.
- // For now we assume that if the type is used, the method is used.
- return type.members.getValues().some((m) => m.isMethod && m.isStatic)
- || !type.factories.isEmpty();
- }
-
- _writeDynamicStubs(Type type) {
- for (var stub in orderValuesByKeys(type.varStubs)) {
- if (!stub.isGenerated) stub.generate(writer);
- }
- }
-
- _writeStaticField(FieldMember field) {
- // Final static fields must be constants which will be folded and inlined.
- if (field.isFinal) return;
-
- var fullname = "${field.declaringType.jsname}.${field.jsname}";
- if (globals.containsKey(fullname)) {
- var value = globals[fullname];
- if (field.declaringType.isTop && !field.isNative) {
- writer.writeln('\$globals.${field.jsname} = ${value.exp.code};');
- } else {
- writer.writeln('\$globals.${field.declaringType.jsname}_${field.jsname}'
- + ' = ${value.exp.code};');
- }
- }
- // No need to write code for a static class field with no initial value.
- }
-
- _writeField(FieldMember field) {
- // Generate declarations for static top-level fields with no value.
- if (field.declaringType.isTop && !field.isNative && field.value == null) {
- writer.writeln('var ${field.jsname};');
- }
-
- // generate code for instance fields
- if (field._provideGetter &&
- !field.declaringType.isConcreteGeneric) {
- _writePrototypePatch(field.declaringType, field.jsnameOfGetter,
- 'function() { return this.${field.jsname}; }', writer);
- }
- if (field._provideSetter &&
- !field.declaringType.isConcreteGeneric) {
- _writePrototypePatch(field.declaringType, field.jsnameOfSetter,
- 'function(value) { return this.${field.jsname} = value; }', writer);
- }
-
- // TODO(jimhug): Currently choose not to initialize fields on objects, but
- // instead to rely on uninitialized === null in our generated code.
- // Investigate the perf pros and cons of this.
- }
-
- _writeProperty(PropertyMember property) {
- if (property.getter != null) _writeMethod(property.getter);
- if (property.setter != null) _writeMethod(property.setter);
-
- // TODO(jmesserly): make sure we don't do this on hidden native types!
- if (property.needsFieldSyntax) {
- writer.enterBlock('Object.defineProperty('
- '${property.declaringType.jsname}.prototype, "${property.jsname}", {');
- if (property.getter != null) {
- writer.write(
- 'get: ${property.declaringType.jsname}.prototype.${property.getter.jsname}');
- // The shenanigan below is to make IE happy -- IE 9 doesn't like a
- // trailing comma on the last element in a list.
- writer.writeln(property.setter == null ? '' : ',');
- }
- if (property.setter != null) {
- writer.writeln(
- 'set: ${property.declaringType.jsname}.prototype.${property.setter.jsname}');
- }
- writer.exitBlock('});');
- }
- }
-
- _writeMethod(MethodMember m) {
- m.methodData.writeDefinition(m, writer);
-
- if (m.isNative && m._provideGetter) {
- if (MethodGenerator._maybeGenerateBoundGetter(m, writer)) {
- world.gen.corejs.ensureBind();
- }
- }
- }
-
- writeGlobals() {
- if (globals.length > 0) {
- writer.comment('// ********** Globals **************');
- var list = globals.getValues();
- list.sort((a, b) => a.compareTo(b));
-
- // put all static field initializations in a method
- writer.enterBlock('function \$static_init(){');
- for (var global in list) {
- if (global.field != null) {
- _writeStaticField(global.field);
- }
- }
- writer.exitBlock('}');
-
- // Keep const expressions shared across isolates. Note that the frog
- // isolate library needs this because we wrote it's bootstrap and
- // book-keeping directly in Dart. Specifically, that code uses
- // [HashMapImplementation] which internally uses a constant expression.
- for (var global in list) {
- if (global.field == null) {
- writer.writeln('var ${global.name} = ${global.exp.code};');
- }
- }
- }
-
- if (!corejs.useIsolates) {
- if (hasStatics) {
- writer.writeln('var \$globals = {};');
- }
- if (globals.length > 0) {
- writer.writeln('\$static_init();');
- }
- }
- }
-
- _usedDynamicDispatchOnType(Type type) {
- if (typesWithDynamicDispatch == null) typesWithDynamicDispatch = new Set();
- typesWithDynamicDispatch.add(type);
- }
-
- writeDynamicDispatchMetadata() {
- if (typesWithDynamicDispatch == null) return;
- writer.comment('// ${typesWithDynamicDispatch.length} dynamic types.');
-
- // Build a pre-order traversal over all the types and their subtypes.
- var seen = new Set();
- var types = [];
- visit(type) {
- if (seen.contains(type)) return;
- seen.add(type);
- for (final subtype in _orderCollectionValues(type.directSubtypes)) {
- visit(subtype);
- }
- types.add(type);
- }
- for (final type in _orderCollectionValues(typesWithDynamicDispatch)) {
- visit(type);
- }
-
- var dispatchTypes = types.filter(
- (type) => !type.directSubtypes.isEmpty() &&
- typesWithDynamicDispatch.contains(type));
-
- writer.comment('// ${types.length} types');
- writer.comment(
- '// ${types.filter((t) => !t.directSubtypes.isEmpty()).length} !leaf');
-
- // Generate code that builds the map from type tags used in dynamic dispatch
- // to the set of type tags of types that extend (TODO: or implement) those
- // types. The set is represented as a string of tags joined with '|'. This
- // is easily split into an array of tags, or converted into a regexp.
- //
- // To reduce the size of the sets, subsets are CSE-ed out into variables.
- // The sets could be much smaller if we could make assumptions about the
- // type tags of other types (which are constructor names or part of the
- // result of Object.prototype.toString). For example, if objects that are
- // Dart objects could be easily excluded, then we might be able to simplify
- // the test, replacing dozens of HTMLxxxElement types with the regexp
- // /HTML.*Element/.
-
- var varNames = []; // temporary variables for common substrings.
- var varDefns = {}; // var -> expression
- var tagDefns = {}; // tag -> expression (a string or a variable)
-
- makeExpression(type) {
- var expressions = []; // expression fragments for this set of type keys.
- var subtags = [type.nativeName]; // TODO: Remove if type is abstract.
- walk(type) {
- for (final subtype in _orderCollectionValues(type.directSubtypes)) {
- var tag = subtype.nativeName;
- var existing = tagDefns[tag];
- if (existing == null) {
- subtags.add(tag);
- walk(subtype);
- } else {
- if (varDefns.containsKey(existing)) {
- expressions.add(existing);
- } else {
- var varName = 'v${varNames.length}/*${tag}*/';
- varNames.add(varName);
- varDefns[varName] = existing;
- tagDefns[tag] = varName;
- expressions.add(varName);
- }
- }
- }
- }
- walk(type);
- var constantPart = "'${Strings.join(subtags, '|')}'";
- if (constantPart != "''") expressions.add(constantPart);
- var expression;
- if (expressions.length == 1) {
- expression = expressions[0];
- } else {
- expression = "[${Strings.join(expressions, ',')}].join('|')";
- }
- return expression;
- }
-
- for (final type in dispatchTypes) {
- tagDefns[type.nativeName] = makeExpression(type);
- }
-
- // Write out a thunk that builds the metadata.
-
- if (!tagDefns.isEmpty()) {
- corejs.ensureDynamicSetMetadata();
- writer.enterBlock('(function(){');
-
- for (final varName in varNames) {
- writer.writeln('var ${varName} = ${varDefns[varName]};');
- }
-
- writer.enterBlock('var table = [');
- writer.comment(
- '// [dynamic-dispatch-tag, '
- + 'tags of classes implementing dynamic-dispatch-tag]');
- bool needsComma = false;
- for (final type in dispatchTypes) {
- if (needsComma) {
- writer.write(', ');
- }
- writer.writeln("['${type.nativeName}', ${tagDefns[type.nativeName]}]");
- needsComma = true;
- }
- writer.exitBlock('];');
- writer.writeln('\$dynamicSetMetadata(table);');
-
- writer.exitBlock('})();');
- }
- }
-
- /** Order a list of values in a Map by SourceSpan, then by name. */
- List _orderValues(Map map) {
- // TODO(jmesserly): should we copy the list?
- // Right now, the Maps are returning a copy already.
- List values = map.getValues();
- values.sort(_compareMembers);
- return values;
- }
-
- /** Order a list of values in a Collection by SourceSpan, then by name. */
- List _orderCollectionValues(Collection collection) {
- List values = new List.from(collection);
- values.sort(_compareMembers);
- return values;
- }
-
- int _compareMembers(x, y) {
- if (x.span != null && y.span != null) {
- // First compare by source span.
- int spans = x.span.compareTo(y.span);
- if (spans != 0) return spans;
- } else {
- // With-spans before sans-spans.
- if (x.span != null) return -1;
- if (y.span != null) return 1;
- }
- // If that fails, compare by name, null comes first.
- if (x.name == y.name) return 0;
- if (x.name == null) return -1;
- if (y.name == null) return 1;
- return x.name.compareTo(y.name);
- }
-}
-
-
-/**
- * A naive code generator for Dart.
- */
-class MethodGenerator implements TreeVisitor, CallingContext {
- Member method;
- CodeWriter writer;
- BlockScope _scope;
- MethodGenerator enclosingMethod;
- bool needsThis;
- List<String> _paramCode;
-
- // TODO(jmesserly): if we knew temps were always used like a stack, we could
- // reduce the overhead here.
- List<String> _freeTemps;
- Set<String> _usedTemps;
-
- /**
- * The set of variables that this lambda closes that need to capture
- * with Function.prototype.bind. This is any variable that lives inside a
- * reentrant block scope (e.g. loop bodies).
- *
- * This field is null if we don't need to track this.
- */
- Set<String> captures;
-
- CounterLog counters;
-
- MethodGenerator(this.method, this.enclosingMethod)
- : writer = new CodeWriter(), needsThis = false {
- if (enclosingMethod != null) {
- _scope = new BlockScope(this, enclosingMethod._scope, method.definition);
- captures = new Set();
- } else {
- _scope = new BlockScope(this, null, method.definition);
- }
- _usedTemps = new Set();
- _freeTemps = [];
- counters = world.counters;
- }
-
- Library get library() => method.library;
-
- // TODO(jimhug): Where does this really belong?
- MemberSet findMembers(String name) {
- return library._findMembers(name);
- }
-
- bool get needsCode() => true;
- bool get showWarnings() => false;
-
- bool get isClosure() => (enclosingMethod != null);
-
- bool get isStatic() => method.isStatic;
-
- Value getTemp(Value value) {
- return value.needsTemp ? forceTemp(value) : value;
- }
-
- VariableValue forceTemp(Value value) {
- String name;
- if (_freeTemps.length > 0) {
- name = _freeTemps.removeLast();
- } else {
- name = '\$${_usedTemps.length}';
- }
- _usedTemps.add(name);
- return new VariableValue(value.staticType, name, value.span, false, value);
- }
-
- Value assignTemp(Value tmp, Value v) {
- if (tmp == v) {
- return v;
- } else {
- // TODO(jmesserly): we should mark this returned value with the temp
- // somehow, so getTemp will reuse it instead of allocating a new one.
- // (we could do this now if we had a "TempValue" or something like that)
- return new Value(v.type, '(${tmp.code} = ${v.code})', v.span);
- }
- }
-
- void freeTemp(VariableValue value) {
- // TODO(jimhug): Need to do this right - for now we can just skip freeing.
- /*
- if (_usedTemps.remove(value.code)) {
- _freeTemps.add(value.code);
- } else {
- world.internalError(
- 'tried to free unused value or non-temp "${value.code}"');
- }
- */
- }
-
- run() {
- // Create most generic possible call for this method.
- var thisObject;
- if (method.isConstructor) {
- thisObject = new ObjectValue(false, method.declaringType, method.span);
- thisObject.initFields();
- } else {
- thisObject = new Value(method.declaringType, 'this', null);
- }
- var values = [];
- for (var p in method.parameters) {
- values.add(new Value(p.type, p.name, null));
- }
- var args = new Arguments(null, values);
-
- evalBody(thisObject, args);
- }
-
-
- writeDefinition(CodeWriter defWriter, LambdaExpression lambda/*=null*/) {
- // To implement block scope: capture any variables we need to.
- var paramCode = _paramCode;
- var names = null;
- if (captures != null && captures.length > 0) {
- names = new List.from(captures);
- names.sort((x, y) => x.compareTo(y));
- // Prepend these as extra parameters. We'll bind them below.
- paramCode = new List.from(names);
- paramCode.addAll(_paramCode);
- }
-
- String _params = '(${Strings.join(_paramCode, ", ")})';
- String params = '(${Strings.join(paramCode, ", ")})';
- String suffix = '}';
- // TODO(jmesserly): many of these are similar, it'd be nice to clean up.
- if (method.declaringType.isTop && !isClosure) {
- defWriter.enterBlock('function ${method.jsname}$params {');
- } else if (isClosure) {
- if (method.name == '') {
- defWriter.enterBlock('(function $params {');
- } else if (names != null) {
- if (lambda == null) {
- defWriter.enterBlock('var ${method.jsname} = (function$params {');
- } else {
- defWriter.enterBlock('(function ${method.jsname}$params {');
- }
- } else {
- defWriter.enterBlock('function ${method.jsname}$params {');
- }
- } else if (method.isConstructor) {
- if (method.constructorName == '') {
- defWriter.enterBlock('function ${method.declaringType.jsname}$params {');
- } else {
- defWriter.enterBlock('${method.declaringType.jsname}.${method.constructorName}\$ctor = function$params {');
- }
- } else if (method.isFactory) {
- defWriter.enterBlock('${method.generatedFactoryName} = function$_params {');
- } else if (method.isStatic) {
- defWriter.enterBlock('${method.declaringType.jsname}.${method.jsname} = function$_params {');
- } else {
- suffix = world.gen._writePrototypePatch(method.declaringType,
- method.jsname, 'function$_params {', defWriter, false);
- }
-
- if (needsThis) {
- defWriter.writeln('var \$this = this;');
- }
-
- if (_usedTemps.length > 0 || _freeTemps.length > 0) {
- //TODO(jimhug): assert(_usedTemps.length == 0); // all temps should be freed.
- _freeTemps.addAll(_usedTemps);
- _freeTemps.sort((x, y) => x.compareTo(y));
- defWriter.writeln('var ${Strings.join(_freeTemps, ", ")};');
- }
-
- // TODO(jimhug): Lots of string translation here - perf bottleneck?
- defWriter.writeln(writer.text);
-
- bool usesBind = false;
- if (names != null) {
- usesBind = true;
- defWriter.exitBlock('}).bind(null, ${Strings.join(names, ", ")})');
- } else if (isClosure && method.name == '') {
- defWriter.exitBlock('})');
- } else {
- defWriter.exitBlock(suffix);
- }
- if (method.isConstructor && method.constructorName != '') {
- defWriter.writeln(
- '${method.declaringType.jsname}.${method.constructorName}\$ctor.prototype = '
- '${method.declaringType.jsname}.prototype;');
- }
-
- _provideOptionalParamInfo(defWriter);
-
- if (method is MethodMember) {
- if (_maybeGenerateBoundGetter(method, defWriter)) {
- usesBind = true;
- }
- }
-
- if (usesBind) world.gen.corejs.ensureBind();
- }
-
- static bool _maybeGenerateBoundGetter(MethodMember m, CodeWriter defWriter) {
- if (m._provideGetter) {
- String suffix = world.gen._writePrototypePatch(m.declaringType,
- m.jsnameOfGetter, 'function() {', defWriter, false);
- if (m.parameters.some((p) => p.isOptional)) {
- defWriter.writeln('var f = this.${m.jsname}.bind(this);');
- defWriter.writeln('f.\$optional = this.${m.jsname}.\$optional;');
- defWriter.writeln('return f;');
- } else {
- defWriter.writeln('return this.${m.jsname}.bind(this);');
- }
- defWriter.exitBlock(suffix);
- return true;
- }
- return false;
- }
-
- /**
- * Generates information about the default/named arguments into the JS code.
- * Only methods that are passed as bound methods to "var" need this. It is
- * generated to support run time stub creation.
- */
- _provideOptionalParamInfo(CodeWriter defWriter) {
- if (method is MethodMember) {
- MethodMember meth = method;
- if (meth._provideOptionalParamInfo) {
- var optNames = [];
- var optValues = [];
- meth.genParameterValues(this);
- for (var param in meth.parameters) {
- if (param.isOptional) {
- optNames.add(param.name);
- // TODO(jimhug): Remove this last usage of escapeString.
- optValues.add(_escapeString(param.value.code));
- }
- }
- if (optNames.length > 0) {
- // TODO(jmesserly): the logic for how to refer to
- // static/instance/top-level members is duplicated all over the place.
- // Badly needs cleanup.
- var start = '';
- if (meth.isStatic) {
- if (!meth.declaringType.isTop) {
- start = meth.declaringType.jsname + '.';
- }
- } else {
- start = meth.declaringType.jsname + '.prototype.';
- }
-
- optNames.addAll(optValues);
- var optional = "['${Strings.join(optNames, "', '")}']";
- defWriter.writeln('${start}${meth.jsname}.\$optional = $optional');
- }
- }
- }
- }
-
- _initField(ObjectValue newObject, String name, Value value, SourceSpan span) {
- var field = method.declaringType.getMember(name);
- if (field == null) {
- world.error('bad initializer - no matching field', span);
- }
- if (!field.isField) {
- world.error('"this.${name}" does not refer to a field', span);
- }
- return newObject.setField(field, value, duringInit: true);
- }
-
- evalBody(Value newObject, Arguments args) {
- bool fieldsSet = false;
- if (method.isNative && method.isConstructor && newObject is ObjectValue) {
- newObject.dynamic.seenNativeInitializer = true;
- }
- // Collects parameters for writing signature in the future.
- _paramCode = [];
- for (int i = 0; i < method.parameters.length; i++) {
- var p = method.parameters[i];
- Value currentArg = null;
- if (i < args.bareCount) {
- currentArg = args.values[i];
- } else {
- // Handle named or missing arguments
- currentArg = args.getValue(p.name);
- if (currentArg === null) {
- // Ensure default value for param has been generated
- p.genValue(method, this);
- currentArg = p.value;
- if (currentArg == null) {
- // Not enough arguments, we'll get an error later.
- return;
- }
- }
- }
-
- if (p.isInitializer) {
- _paramCode.add(p.name);
- fieldsSet = true;
- _initField(newObject, p.name, currentArg, p.definition.span);
- } else {
- var paramValue = _scope.declareParameter(p);
- _paramCode.add(paramValue.code);
- if (newObject != null && newObject.isConst) {
- _scope.assign(p.name, currentArg.convertTo(this, p.type));
- }
- }
- }
-
- var initializerCall = null;
- final declaredInitializers = method.definition.dynamic.initializers;
- if (declaredInitializers != null) {
- for (var init in declaredInitializers) {
- if (init is CallExpression) {
- if (initializerCall != null) {
- world.error('only one initializer redirecting call is allowed',
- init.span);
- }
- initializerCall = init;
- } else if (init is BinaryExpression
- && TokenKind.kindFromAssign(init.op.kind) == 0) {
- var left = init.x;
- if (!(left is DotExpression && left.self is ThisExpression
- || left is VarExpression)) {
- world.error('invalid left side of initializer', left.span);
- continue;
- }
- // TODO(jmesserly): eval right side of initializers in static
- // context, so "this." is not in scope
- var initValue = visitValue(init.y);
- fieldsSet = true;
- _initField(newObject, left.name.name, initValue, left.span);
- } else {
- world.error('invalid initializer', init.span);
- }
- }
- }
-
- if (method.isConstructor && initializerCall == null && !method.isNative) {
- var parentType = method.declaringType.parent;
- if (parentType != null && !parentType.isObject) {
- // TODO(jmesserly): we could omit this if all supertypes are using
- // default constructors.
- initializerCall = new CallExpression(
- new SuperExpression(method.span), [], method.span);
- }
- }
-
- if (method.isConstructor && newObject is ObjectValue) {
- var fields = newObject.dynamic.fields;
- for (var field in newObject.dynamic.fieldsInInitOrder) {
- if (field !== null) {
- var value = fields[field];
- if (value !== null) {
- writer.writeln('this.${field.jsname} = ${value.code};');
- }
- }
- }
- }
-
- // TODO(jimhug): Doing this call last does not match spec.
- if (initializerCall != null) {
- evalInitializerCall(newObject, initializerCall, fieldsSet);
- }
-
- if (method.isConstructor && newObject !== null && newObject.isConst) {
- newObject.validateInitialized(method.span);
- } else if (method.isConstructor) {
- var fields = newObject.dynamic.fields;
- for (var field in fields.getKeys()) {
- var value = fields[field];
- if (value === null && field.isFinal &&
- field.declaringType == method.declaringType &&
- !newObject.dynamic.seenNativeInitializer) {
- world.error('uninitialized final field "${field.name}"',
- field.span, method.span);
- }
- }
- }
-
- var body = method.definition.dynamic.body;
-
- if (body === null) {
- // TODO(jimhug): Move check into resolve on method.
- if (!method.isConstructor && !method.isNative) {
- world.error('unexpected empty body for ${method.name}',
- method.definition.span);
- }
- } else {
- visitStatementsInBlock(body);
- }
- }
-
- evalInitializerCall(ObjectValue newObject, CallExpression node,
- [bool fieldsSet = false]) {
- String contructorName = '';
- var targetExp = node.target;
- if (targetExp is DotExpression) {
- DotExpression dot = targetExp;
- targetExp = dot.self;
- contructorName = dot.name.name;
- }
-
- Type targetType = null;
- var target = null;
- if (targetExp is SuperExpression) {
- targetType = method.declaringType.parent;
- target = _makeSuperValue(targetExp);
- } else if (targetExp is ThisExpression) {
- targetType = method.declaringType;
- target = _makeThisValue(targetExp);
- if (fieldsSet) {
- world.error('no initialization allowed with redirecting constructor',
- node.span);
- }
- } else {
- world.error('bad call in initializers', node.span);
- }
-
- var m = targetType.getConstructor(contructorName);
- if (m == null) {
- world.error('no matching constructor for ${targetType.name}', node.span);
- }
-
- // TODO(jimhug): Replace with more generic recursion detection
- method.initDelegate = m;
- // check no cycles in in initialization:
- var other = m;
- while (other != null) {
- if (other == method) {
- world.error('initialization cycle', node.span);
- break;
- }
- other = other.initDelegate;
- }
-
- var newArgs = _makeArgs(node.arguments);
- // ???? wacky stuff ????
- world.gen.genMethod(m);
-
- m._evalConstConstructor(newObject, newArgs);
-
- if (!newObject.isConst) {
- var value = m.invoke(this, node, target, newArgs);
- if (target.type != world.objectType) {
- // No need to actually call Object's empty super constructor.
- writer.writeln('${value.code};');
- }
- }
- }
-
- _makeArgs(List<ArgumentNode> arguments) {
- var args = [];
- bool seenLabel = false;
- for (var arg in arguments) {
- if (arg.label != null) {
- seenLabel = true;
- } else if (seenLabel) {
- // TODO(jimhug): Move this into parser?
- world.error('bare argument cannot follow named arguments', arg.span);
- }
- args.add(visitValue(arg.value));
- }
-
- return new Arguments(arguments, args);
- }
-
- /** Invoke a top-level corelib native method. */
- Value _invokeNative(String name, List<Value> arguments) {
- var args = Arguments.EMPTY;
- if (arguments.length > 0) {
- args = new Arguments(null, arguments);
- }
-
- var method = world.corelib.topType.members[name];
- return method.invoke(this, method.definition,
- new Value(world.corelib.topType, null, null), args);
- }
-
- /**
- * Escapes a string so it can be inserted into JS code as a double-quoted
- * JS string.
- */
- static String _escapeString(String text) {
- // TODO(jimhug): Use a regex for performance here.
- return text.replaceAll('\\', '\\\\').replaceAll('"', '\\"').replaceAll(
- '\n', '\\n').replaceAll('\r', '\\r');
- }
-
- /** Visits [body] without creating a new block for a [BlockStatement]. */
- bool visitStatementsInBlock(Statement body) {
- if (body is BlockStatement) {
- BlockStatement block = body;
- for (var stmt in block.body) {
- stmt.visit(this);
- }
- } else {
- if (body != null) body.visit(this);
- }
- return false;
- }
-
- _pushBlock(Node node, [bool reentrant = false]) {
- _scope = new BlockScope(this, _scope, node, reentrant);
- }
-
- _popBlock(Node node) {
- if (_scope.node !== node) {
- spanOf(n) => n != null ? n.span : null;
- world.internalError('scope mismatch. Trying to pop "${node}" but found '
- + ' "${_scope.node}"', spanOf(node), spanOf(_scope.node));
- }
- _scope = _scope.parent;
- }
-
- /** Visits a loop body and handles fixed point for type inference. */
- _visitLoop(Node node, void visitBody()) {
- if (_scope.inferTypes) {
- _loopFixedPoint(node, visitBody);
- } else {
- _pushBlock(node, reentrant:true);
- visitBody();
- _popBlock(node);
- }
- }
-
- // TODO(jmesserly): we're evaluating the body multiple times, how do we
- // prevent duplicate warnings/errors?
- // We either need a way to collect them before printing, or a check that
- // prevents multiple identical errors at the same source location.
- _loopFixedPoint(Node node, void visitBody()) {
-
- // TODO(jmesserly): should we move the writer/counters into the scope?
- // Also should we save the scope on the node, like how we save
- // MethodGenerator? That would reduce the required work for nested loops.
- var savedCounters = counters;
- var savedWriter = writer;
- int tries = 0;
- var startScope = _scope.snapshot();
- var s = startScope;
- while (true) {
- // Create a nested writer so we can easily discard it.
- // TODO(jmesserly): does this belong on BlockScope?
- writer = new CodeWriter();
- counters = new CounterLog();
-
- _pushBlock(node, reentrant:true);
-
- // If we've tried too many times and haven't converged, disable inference
- if (tries++ >= options.maxInferenceIterations) {
- // TODO(jmesserly): needs more information to actually be useful
- _scope.inferTypes = false;
- }
-
- visitBody();
- _popBlock(node);
-
- if (!_scope.inferTypes || !_scope.unionWith(s)) {
- // We've converged!
- break;
- }
-
- s = _scope.snapshot();
- }
-
- // We're done! Write the final code.
- savedWriter.write(writer.text);
- writer = savedWriter;
- savedCounters.add(counters);
- counters = savedCounters;
- }
-
- MethodMember _makeLambdaMethod(String name, FunctionDefinition func) {
- var meth = new MethodMember.lambda(name, method.declaringType, func);
- meth.enclosingElement = method;
- meth._methodData = new MethodData(meth, this);
- meth.resolve();
- return meth;
- }
-
- visitBool(Expression node) {
- // Boolean conversions in if/while/do/for/conditions require non-null bool.
-
- // TODO(jmesserly): why do we have this rule? It seems inconsistent with
- // the rest of the type system, and just causes bogus asserts unless all
- // bools are initialized to false.
- return visitValue(node).convertTo(this, world.nonNullBool);
- }
-
- visitValue(Expression node) {
- if (node == null) return null;
-
- var value = node.visit(this);
- value.checkFirstClass(node.span);
- return value;
- }
-
- /**
- * Visit [node] and ensure statically or with an runtime check that it has the
- * expected type (if specified).
- */
- visitTypedValue(Expression node, Type expectedType) {
- final val = visitValue(node);
- return expectedType == null ? val : val.convertTo(this, expectedType);
- }
-
- visitVoid(Expression node) {
- // TODO(jmesserly): should we generalize this?
- if (node is PostfixExpression) {
- var value = visitPostfixExpression(node, isVoid: true);
- value.checkFirstClass(node.span);
- return value;
- } else if (node is BinaryExpression) {
- var value = visitBinaryExpression(node, isVoid: true);
- value.checkFirstClass(node.span);
- return value;
- }
- // TODO(jimhug): Some level of warnings for non-void things here?
- return visitValue(node);
- }
-
- // ******************* Statements *******************
-
- bool visitDietStatement(DietStatement node) {
- var parser = new Parser(node.span.file, startOffset: node.span.start);
- visitStatementsInBlock(parser.block());
- return false;
- }
-
- bool visitVariableDefinition(VariableDefinition node) {
- var isFinal = false;
- // TODO(jimhug): Clean this up and share modifier parsing somewhere.
- if (node.modifiers != null && node.modifiers[0].kind == TokenKind.FINAL) {
- isFinal = true;
- }
- writer.write('var ');
- var type = method.resolveType(node.type, false, true);
- for (int i=0; i < node.names.length; i++) {
- if (i > 0) {
- writer.write(', ');
- }
- final name = node.names[i].name;
- var value = visitValue(node.values[i]);
- if (isFinal && value == null) {
- world.error('no value specified for final variable', node.span);
- }
-
- var val = _scope.create(name, type, node.names[i].span, isFinal);
-
- if (value == null) {
- if (_scope.reentrant) {
- // To preserve block scoping, we need to ensure the variable is
- // reinitialized each time the block is entered.
- writer.write('${val.code} = null');
- } else {
- writer.write('${val.code}');
- }
- } else {
- value = value.convertTo(this, type);
- _scope.inferAssign(name, value);
- writer.write('${val.code} = ${value.code}');
- }
- }
- writer.writeln(';');
- return false;
-
- }
-
- bool visitFunctionDefinition(FunctionDefinition node) {
- var meth = _makeLambdaMethod(node.name.name, node);
- var funcValue = _scope.create(meth.name, meth.functionType,
- method.definition.span, isFinal:true);
-
- meth.methodData.createFunction(writer);
- return false;
- }
-
- /**
- * Returns true indicating that normal control-flow is interrupted by
- * this statement. (This could be a return, break, throw, or continue.)
- */
- bool visitReturnStatement(ReturnStatement node) {
- if (node.value == null) {
- // This is essentially "return null".
- // It can't issue a warning because every type is nullable.
- writer.writeln('return;');
- } else {
- if (method.isConstructor) {
- world.error('return of value not allowed from constructor', node.span);
- }
- var value = visitTypedValue(node.value, method.returnType);
- writer.writeln('return ${value.code};');
- }
- return true;
- }
-
- bool visitThrowStatement(ThrowStatement node) {
- // Dart allows throwing anything, just like JS
- if (node.value != null) {
- var value = visitValue(node.value);
- // Ensure that we generate a toString() method for things that we throw
- value.invoke(this, 'toString', node, Arguments.EMPTY);
- writer.writeln('\$throw(${value.code});');
- world.gen.corejs.useThrow = true;
- } else {
- var rethrow = _scope.getRethrow();
- if (rethrow == null) {
- world.error('rethrow outside of catch', node.span);
- } else {
- // Use a normal throw instead of $throw so we don't capture a new stack
- writer.writeln('throw ${rethrow};');
- }
- }
- return true;
- }
-
- bool visitAssertStatement(AssertStatement node) {
- // be sure to walk test for static checking even is asserts disabled
- var test = visitValue(node.test); // TODO(jimhug): check bool or callable.
- if (options.enableAsserts) {
- var span = node.test.span;
-
- // TODO(jmesserly): do we need to include path/line/column here?
- // It should be captured in the stack trace.
- var line = span.file.getLine(span.start) + 1;
- var column = span.file.getColumn(line - 1, span.start) + 1;
-
- // TODO(jimhug): Simplify code for creating const values.
- var args = [
- test,
- Value.fromString(span.text, node.span),
- Value.fromString(span.file.filename, node.span),
- Value.fromInt(line, node.span),
- Value.fromInt(column, node.span)
- ];
-
- var tp = world.corelib.topType;
- Member f = tp.getMember('_assert');
- var value = f.invoke(this, node, new TypeValue(tp, null),
- new Arguments(null, args));
- writer.writeln('${value.code};');
- }
- return false;
- }
-
- bool visitBreakStatement(BreakStatement node) {
- // TODO(jimhug): Lots of flow error checking here and below.
- if (node.label == null) {
- writer.writeln('break;');
- } else {
- writer.writeln('break ${node.label.name};');
- }
- return true;
- }
-
- bool visitContinueStatement(ContinueStatement node) {
- if (node.label == null) {
- writer.writeln('continue;');
- } else {
- writer.writeln('continue ${node.label.name};');
- }
- return true;
- }
-
- bool visitIfStatement(IfStatement node) {
- var test = visitBool(node.test);
- writer.write('if (${test.code}) ');
- var exit1 = node.trueBranch.visit(this);
- if (node.falseBranch != null) {
- writer.write('else ');
- if (node.falseBranch.visit(this) && exit1) {
- return true;
- }
- }
- return false;
- }
-
- bool visitWhileStatement(WhileStatement node) {
- var test = visitBool(node.test);
- writer.write('while (${test.code}) ');
- _visitLoop(node, () {
- node.body.visit(this);
- });
- return false;
- }
-
- bool visitDoStatement(DoStatement node) {
- writer.write('do ');
- _visitLoop(node, () {
- node.body.visit(this);
- });
- var test = visitBool(node.test);
- writer.writeln('while (${test.code})');
- return false;
- }
-
- bool visitForStatement(ForStatement node) {
- _pushBlock(node);
- writer.write('for (');
- if (node.init != null) {
- node.init.visit(this);
- } else {
- writer.write(';');
- }
-
- _visitLoop(node, () {
- if (node.test != null) {
- var test = visitBool(node.test);
- writer.write(' ${test.code}; ');
- } else {
- writer.write('; ');
- }
-
- bool needsComma = false;
- for (var s in node.step) {
- if (needsComma) writer.write(', ');
- var sv = visitVoid(s);
- writer.write(sv.code);
- needsComma = true;
- }
- writer.write(') ');
-
- _pushBlock(node.body);
- node.body.visit(this);
- _popBlock(node.body);
- });
- _popBlock(node);
- return false;
- }
-
- bool _isFinal(typeRef) {
- if (typeRef is GenericTypeReference) {
- typeRef = typeRef.baseType;
- } else if (typeRef is SimpleTypeReference) {
- return false;
- }
- return typeRef != null && typeRef.isFinal;
- }
-
- bool visitForInStatement(ForInStatement node) {
- // TODO(jimhug): visitValue and other cleanups here.
- var itemType = method.resolveType(node.item.type, false, true);
- var list = node.list.visit(this);
- _visitLoop(node, () {
- _visitForInBody(node, itemType, list);
- });
- return false;
- }
-
- void _visitForInBody(ForInStatement node, Type itemType, Value list) {
- // TODO(jimhug): Check that itemType matches list members...
- bool isFinal = node.item.isFinal;
- var itemName = node.item.name.name;
- var item = _scope.create(itemName, itemType, node.item.name.span, isFinal);
- if (list.needsTemp) {
- var listVar = _scope.create('\$list', list.type, null);
- writer.writeln('var ${listVar.code} = ${list.code};');
- list = listVar;
- }
-
- // Special path for concrete Arrays for readability and perf optimization.
- if (list.type.genericType == world.listFactoryType) {
- var tmpi = _scope.create('\$i', world.numType, null);
- var listLength = list.get_(this, 'length', node.list);
- writer.enterBlock('for (var ${tmpi.code} = 0;'
- '${tmpi.code} < ${listLength.code}; ${tmpi.code}++) {');
- var value = list.invoke(this, ':index', node.list,
- new Arguments(null, [tmpi]));
- writer.writeln('var ${item.code} = ${value.code};');
- } else {
- var iterator = list.invoke(this, 'iterator', node.list, Arguments.EMPTY);
- var tmpi = _scope.create('\$i', iterator.type, null);
-
- var hasNext = tmpi.invoke(this, 'hasNext', node.list, Arguments.EMPTY);
- var next = tmpi.invoke(this, 'next', node.list, Arguments.EMPTY);
-
- writer.enterBlock(
- 'for (var ${tmpi.code} = ${iterator.code}; ${hasNext.code}; ) {');
- writer.writeln('var ${item.code} = ${next.code};');
- }
-
- visitStatementsInBlock(node.body);
- writer.exitBlock('}');
- }
-
- void _genToDartException(Value ex) {
- var result = _invokeNative("_toDartException", [ex]);
- writer.writeln('${ex.code} = ${result.code};');
- }
-
- void _genStackTraceOf(Value trace, Value ex) {
- var result = _invokeNative("_stackTraceOf", [ex]);
- writer.writeln('var ${trace.code} = ${result.code};');
- }
-
- bool visitTryStatement(TryStatement node) {
- writer.enterBlock('try {');
- _pushBlock(node.body);
- visitStatementsInBlock(node.body);
- _popBlock(node.body);
-
- if (node.catches.length == 1) {
- // Handle a single catch. We can generate simple code here compared to the
- // multiple catch, such as no extra temp or if-else-if chain.
- var catch_ = node.catches[0];
- _pushBlock(catch_);
- var exType = method.resolveType(catch_.exception.type, false, true);
- var ex = _scope.declare(catch_.exception);
- _scope.rethrow = ex.code;
- writer.nextBlock('} catch (${ex.code}) {');
- if (catch_.trace != null) {
- var trace = _scope.declare(catch_.trace);
- _genStackTraceOf(trace, ex);
- }
- _genToDartException(ex);
-
- if (!exType.isVarOrObject) {
- var test = ex.instanceOf(this, exType, catch_.exception.span,
- isTrue:false, forceCheck:true);
- writer.writeln('if (${test.code}) throw ${ex.code};');
- }
- visitStatementsInBlock(node.catches[0].body);
- _popBlock(catch_);
- } else if (node.catches.length > 0) {
- // Handle more than one catch
- _pushBlock(node);
- var ex = _scope.create('\$ex', world.varType, null);
- _scope.rethrow = ex.code;
- writer.nextBlock('} catch (${ex.code}) {');
- var trace = null;
- if (node.catches.some((c) => c.trace != null)) {
- trace = _scope.create('\$trace', world.varType, null);
- _genStackTraceOf(trace, ex);
- }
- _genToDartException(ex);
-
- // We need a rethrow unless we encounter a "var" or "Object" catch
- bool needsRethrow = true;
-
- for (int i = 0; i < node.catches.length; i++) {
- var catch_ = node.catches[i];
-
- _pushBlock(catch_);
- var tmpType = method.resolveType(catch_.exception.type, false, true);
- var tmp = _scope.declare(catch_.exception);
- if (!tmpType.isVarOrObject) {
- var test = ex.instanceOf(this, tmpType, catch_.exception.span,
- isTrue:true, forceCheck:true);
- if (i == 0) {
- writer.enterBlock('if (${test.code}) {');
- } else {
- writer.nextBlock('} else if (${test.code}) {');
- }
- } else if (i > 0) {
- writer.nextBlock('} else {');
- }
-
- writer.writeln('var ${tmp.code} = ${ex.code};');
- if (catch_.trace != null) {
- // TODO(jmesserly): ensure this is the right type
- var tmptrace = _scope.declare(catch_.trace);
- writer.writeln('var ${tmptrace.code} = ${trace.code};');
- }
-
- visitStatementsInBlock(catch_.body);
- _popBlock(catch_);
-
- if (tmpType.isVarOrObject) {
- // We matched this for sure; no need to keep going
- if (i + 1 < node.catches.length) {
- world.error('Unreachable catch clause', node.catches[i + 1].span);
- }
- if (i > 0) {
- // Close the else block
- writer.exitBlock('}');
- }
- needsRethrow = false;
- break;
- }
- }
-
- if (needsRethrow) {
- // If we didn't have a "catch (var e)", generate a rethrow
- writer.nextBlock('} else {');
- writer.writeln('throw ${ex.code};');
- writer.exitBlock('}');
- }
-
- _popBlock(node);
- }
-
- if (node.finallyBlock != null) {
- writer.nextBlock('} finally {');
- _pushBlock(node.finallyBlock);
- visitStatementsInBlock(node.finallyBlock);
- _popBlock(node.finallyBlock);
- }
-
- // Close the try-catch-finally
- writer.exitBlock('}');
- // TODO(efortuna): This could be more precise by combining all the different
- // paths here. -i.e. if there is a finally block with a return at the end
- // then this can return true, similarly if all blocks have a return at the
- // end then the same holds.
- return false;
- }
-
- bool visitSwitchStatement(SwitchStatement node) {
- var test = visitValue(node.test);
- writer.enterBlock('switch (${test.code}) {');
- for (var case_ in node.cases) {
- if (case_.label != null) {
- world.error('unimplemented: labeled case statement', case_.span);
- }
- _pushBlock(case_);
- for (int i=0; i < case_.cases.length; i++) {
- var expr = case_.cases[i];
- if (expr == null) {
- // Default can only be the last case.
- if (i < case_.cases.length - 1) {
- world.error('default clause must be the last case', case_.span);
- }
- writer.writeln('default:');
- } else {
- var value = visitValue(expr);
- writer.writeln('case ${value.code}:');
- }
- }
- writer.enterBlock('');
- bool caseExits = _visitAllStatements(case_.statements, false);
-
- if (case_ != node.cases[node.cases.length - 1] && !caseExits) {
- var span = case_.statements[case_.statements.length - 1].span;
- writer.writeln('\$throw(new FallThroughError());');
- world.gen.corejs.useThrow = true;
- }
- writer.exitBlock('');
- _popBlock(case_);
- }
- writer.exitBlock('}');
- // TODO(efortuna): When we are passing more information back about
- // control flow by returning something other than bool, return true for the
- // cases where every branch of the switch statement ends with a return
- // statement.
- return false;
- }
-
- bool _visitAllStatements(statementList, exits) {
- for (int i = 0; i < statementList.length; i++) {
- var stmt = statementList[i];
- exits = stmt.visit(this);
- //TODO(efortuna): fix this so you only get one error if you have "return;
- //a; b; c;"
- if (stmt != statementList[statementList.length - 1] && exits) {
- world.warning('unreachable code', statementList[i + 1].span);
- }
- }
- return exits;
- }
-
- bool visitBlockStatement(BlockStatement node) {
- _pushBlock(node);
- writer.enterBlock('{');
- var exits = _visitAllStatements(node.body, false);
- writer.exitBlock('}');
- _popBlock(node);
- return exits;
- }
-
- bool visitLabeledStatement(LabeledStatement node) {
- writer.writeln('${node.name.name}:');
- node.body.visit(this);
- return false;
- }
-
- bool visitExpressionStatement(ExpressionStatement node) {
- if (node.body is VarExpression || node.body is ThisExpression) {
- // TODO(jmesserly): this is a "warning" but not a "type warning",
- // Is that okay? We have a similar issue around unreachable code warnings.
- world.warning('variable used as statement', node.span);
- }
- var value = visitVoid(node.body);
- writer.writeln('${value.code};');
- return false;
- }
-
- bool visitEmptyStatement(EmptyStatement node) {
- writer.writeln(';');
- return false;
- }
-
- _checkNonStatic(Node node) {
- if (isStatic) {
- world.warning('not allowed in static method', node.span);
- }
- }
-
- _makeSuperValue(Node node) {
- var parentType = method.declaringType.parent;
- _checkNonStatic(node);
- if (parentType == null) {
- world.error('no super class', node.span);
- }
- return new SuperValue(parentType, node.span);
- }
-
- _getOutermostMethod() {
- var result = this;
- while (result.enclosingMethod != null) {
- result = result.enclosingMethod;
- }
- return result;
- }
-
-
- // TODO(jimhug): Share code better with _makeThisValue.
- String _makeThisCode() {
- if (enclosingMethod != null) {
- _getOutermostMethod().needsThis = true;
- return '\$this';
- } else {
- return 'this';
- }
- }
-
- /**
- * Creates a reference to the enclosing type ('this') that can be used within
- * closures.
- */
- Value _makeThisValue(Node node) {
- if (enclosingMethod != null) {
- var outermostMethod = _getOutermostMethod();
- outermostMethod._checkNonStatic(node);
- outermostMethod.needsThis = true;
- return new ThisValue(outermostMethod.method.declaringType, '\$this',
- node != null ? node.span : null);
- } else {
- _checkNonStatic(node);
- return new ThisValue(method.declaringType, 'this',
- node != null ? node.span : null);
- }
- }
-
- // ******************* Expressions *******************
- visitLambdaExpression(LambdaExpression node) {
- var name = (node.func.name != null) ? node.func.name.name : '';
-
- MethodMember meth = _makeLambdaMethod(name, node.func);
- return meth.methodData.createLambda(node, this);
- }
-
- visitCallExpression(CallExpression node) {
- var target;
- var position = node.target;
- var name = ':call';
- if (node.target is DotExpression) {
- DotExpression dot = node.target;
- target = dot.self.visit(this);
- name = dot.name.name;
- position = dot.name;
- } else if (node.target is VarExpression) {
- VarExpression varExpr = node.target;
- name = varExpr.name.name;
- // First check in block scopes.
- target = _scope.lookup(name);
- if (target != null) {
- return target.invoke(this, ':call', node, _makeArgs(node.arguments));
- }
-
- target = _makeThisOrType(varExpr.span);
- return target.invoke(this, name, node, _makeArgs(node.arguments));
- } else {
- target = node.target.visit(this);
- }
-
- return target.invoke(this, name, position, _makeArgs(node.arguments));
- }
-
- visitIndexExpression(IndexExpression node) {
- var target = visitValue(node.target);
- var index = visitValue(node.index);
- return target.invoke(this, ':index', node, new Arguments(null, [index]));
- }
-
- bool _expressionNeedsParens(Expression e) {
- return (e is BinaryExpression || e is ConditionalExpression
- || e is PostfixExpression || _isUnaryIncrement(e));
- }
-
- visitBinaryExpression(BinaryExpression node, [bool isVoid = false]) {
- final kind = node.op.kind;
- // TODO(jimhug): Ensure these have same semantics as JS!
- if (kind == TokenKind.AND || kind == TokenKind.OR) {
- var x = visitTypedValue(node.x, world.nonNullBool);
- var y = visitTypedValue(node.y, world.nonNullBool);
- return x.binop(kind, y, this, node);
- } else if (kind == TokenKind.EQ_STRICT || kind == TokenKind.NE_STRICT) {
- var x = visitValue(node.x);
- var y = visitValue(node.y);
- return x.binop(kind, y, this, node);
- }
-
- final assignKind = TokenKind.kindFromAssign(node.op.kind);
- if (assignKind == -1) {
- final x = visitValue(node.x);
- final y = visitValue(node.y);
- return x.binop(kind, y, this, node);
- } else if ((assignKind != 0) && _expressionNeedsParens(node.y)) {
- return _visitAssign(assignKind, node.x,
- new ParenExpression(node.y, node.y.span), node,
- isVoid ? ReturnKind.IGNORE : ReturnKind.POST);
- } else {
- return _visitAssign(assignKind, node.x, node.y, node,
- isVoid ? ReturnKind.IGNORE : ReturnKind.POST);
- }
- }
-
- /**
- * Visits an assignment expression.
- */
- _visitAssign(int kind, Expression xn, Expression yn, Node position,
- int returnKind) {
- // TODO(jimhug): The usual battle with making assign impl not look ugly.
- if (xn is VarExpression) {
- return _visitVarAssign(kind, xn, yn, position, returnKind);
- } else if (xn is IndexExpression) {
- return _visitIndexAssign(kind, xn, yn, position, returnKind);
- } else if (xn is DotExpression) {
- return _visitDotAssign(kind, xn, yn, position, returnKind);
- } else {
- world.error('illegal lhs', xn.span);
- }
- }
-
- // TODO(jmesserly): it'd be nice if we didn't have to deal directly with
- // MemberSets here and in visitVarExpression.
- _visitVarAssign(int kind, VarExpression xn, Expression yn, Node position,
- int returnKind) {
- final name = xn.name.name;
-
- // First check in block scopes.
- var x = _scope.lookup(name);
- var y = visitValue(yn);
-
- if (x != null) {
- y = y.convertTo(this, x.staticType);
- // Update the inferred value
- // Note: for now we aren't very flow sensitive, so this is a "union"
- // rather than simply setting it to "y"
- _scope.inferAssign(name, Value.union(x, y));
-
- // TODO(jimhug): This is "legacy" and should be cleaned ASAP
- if (x.isFinal) {
- world.error('final variable "${x.code}" is not assignable',
- position.span);
- }
-
- // Handle different ReturnKind values here...
- if (kind == 0) {
- return new Value(y.type, '${x.code} = ${y.code}', position.span);
- } else if (x.type.isNum && y.type.isNum && (kind != TokenKind.TRUNCDIV)) {
- // Process everything but ~/ , which has no equivalent JS operator
- // Very localized optimization for numbers!
- if (returnKind == ReturnKind.PRE) {
- world.internalError('should not be here', position.span);
- }
- final op = TokenKind.kindToString(kind);
- return new Value(y.type, '${x.code} $op= ${y.code}', position.span);
- } else {
- var right = x;
- y = right.binop(kind, y, this, position);
- if (returnKind == ReturnKind.PRE) {
- var tmp = forceTemp(x);
- var ret = new Value(x.type,
- '(${tmp.code} = ${x.code}, ${x.code} = ${y.code}, ${tmp.code})',
- position.span);
- freeTemp(tmp);
- return ret;
- } else {
- return new Value(x.type, '${x.code} = ${y.code}', position.span);
- }
- }
- } else {
- x = _makeThisOrType(position.span);
- return x.set_(this, name, position, y, kind: kind,
- returnKind: returnKind);
- }
- }
-
- _visitIndexAssign(int kind, IndexExpression xn, Expression yn,
- Node position, int returnKind) {
- var target = visitValue(xn.target);
- var index = visitValue(xn.index);
- var y = visitValue(yn);
-
- return target.setIndex(this, index, position, y, kind: kind,
- returnKind: returnKind);
- }
-
- _visitDotAssign(int kind, DotExpression xn, Expression yn, Node position,
- int returnKind) {
- // This is not visitValue because types members are assignable.
- var target = xn.self.visit(this);
- var y = visitValue(yn);
-
- return target.set_(this, xn.name.name, xn.name, y, kind: kind,
- returnKind: returnKind);
- }
-
- visitUnaryExpression(UnaryExpression node) {
- var value = visitValue(node.self);
- switch (node.op.kind) {
- case TokenKind.INCR:
- case TokenKind.DECR:
- // TODO(jimhug): Hackish optimization not always correct
- if (value.type.isNum && !value.isFinal && node.self is VarExpression) {
- return new Value(value.type, '${node.op}${value.code}', node.span);
- } else {
- // ++x becomes x += 1
- // --x becomes x -= 1
- var kind = (TokenKind.INCR == node.op.kind ?
- TokenKind.ADD : TokenKind.SUB);
- // TODO(jimhug): Shouldn't need a full-expression here.
- var operand = new LiteralExpression(Value.fromInt(1, node.span),
- node.span);
-
- var assignValue = _visitAssign(kind, node.self, operand, node,
- ReturnKind.POST);
- return new Value(assignValue.type, '(${assignValue.code})',
- node.span);
- }
- }
- return value.unop(node.op.kind, this, node);
- }
-
- visitDeclaredIdentifier(DeclaredIdentifier node) {
- world.error('Expected expression', node.span);
- }
-
- visitAwaitExpression(AwaitExpression node) {
- world.internalError(
- 'Await expressions should have been eliminated before code generation',
- node.span);
- }
-
- visitPostfixExpression(PostfixExpression node, [bool isVoid = false]) {
- // TODO(jimhug): Hackish optimization here to revisit in many ways...
- var value = visitValue(node.body);
- if (value.type.isNum && !value.isFinal && node.body is VarExpression) {
- // Would like to also do on "pure" fields - check to see if possible...
- return new Value(value.type, '${value.code}${node.op}', node.span);
- }
-
- // x++ is equivalent to (t = x, x = t + 1, t), where we capture all temps
- // needed to evaluate x so we're not evaluating multiple times. Likewise,
- // x-- is equivalent to (t = x, x = t - 1, t).
- var kind = (TokenKind.INCR == node.op.kind) ?
- TokenKind.ADD : TokenKind.SUB;
- // TODO(jimhug): Shouldn't need a full-expression here.
- var operand = new LiteralExpression(Value.fromInt(1, node.span),
- node.span);
- var ret = _visitAssign(kind, node.body, operand, node,
- isVoid ? ReturnKind.IGNORE : ReturnKind.PRE);
- return ret;
- }
-
- visitNewExpression(NewExpression node) {
- var typeRef = node.type;
-
- var constructorName = '';
- if (node.name != null) {
- constructorName = node.name.name;
- }
-
- // Named constructors and library prefixes, oh my!
- // At last, we can collapse the ambiguous wave function...
- if (constructorName == '' && typeRef is NameTypeReference &&
- typeRef.names != null) {
-
- // Pull off the last name from the type, guess it's the constructor name.
- var names = new List.from(typeRef.names);
- constructorName = names.removeLast().name;
- if (names.length == 0) names = null;
-
- typeRef = new NameTypeReference(
- typeRef.isFinal, typeRef.name, names, typeRef.span);
- }
-
- var type = method.resolveType(typeRef, true, true);
- if (type.isTop) {
- type = type.library.findTypeByName(constructorName);
- constructorName = '';
- }
-
- if (type is ParameterType) {
- world.error('cannot instantiate a type parameter', node.span);
- return _makeMissingValue(constructorName);
- }
-
- var m = type.getConstructor(constructorName);
- if (m == null) {
- var name = type.jsname;
- if (type.isVar) {
- name = typeRef.name.name;
- }
- world.error('no matching constructor for $name', node.span);
- return _makeMissingValue(name);
- }
-
- if (node.isConst) {
- if (!m.isConst) {
- world.error('can\'t use const on a non-const constructor', node.span);
- }
- for (var arg in node.arguments) {
- if (!visitValue(arg.value).isConst) {
- world.error('const constructor expects const arguments', arg.span);
- }
- }
- }
-
- // Call the constructor on the type we want to construct.
- // NOTE: this is important for correct type checking of factories.
- // If the user calls "new Interface()" we want the result type to be the
- // interface, not the class.
- var target = new TypeValue(type, typeRef.span);
- return m.invoke(this, node, target, _makeArgs(node.arguments));
- }
-
- visitListExpression(ListExpression node) {
- var argValues = [];
- var listType = world.listType;
- var type = world.varType;
- if (node.itemType != null) {
- type = method.resolveType(node.itemType, true, !node.isConst);
- if (node.isConst && (type is ParameterType || type.hasTypeParams)) {
- world.error('type parameter cannot be used in const list literals');
- }
- listType = listType.getOrMakeConcreteType([type]);
- }
- for (var item in node.values) {
- var arg = visitTypedValue(item, type);
- argValues.add(arg);
- if (node.isConst && !arg.isConst) {
- world.error('const list can only contain const values', arg.span);
- }
- }
-
- world.listFactoryType.markUsed();
-
- var ret = new ListValue(argValues, node.isConst, listType, node.span);
- if (ret.isConst) return ret.getGlobalValue();
- return ret;
- }
-
-
- visitMapExpression(MapExpression node) {
- // Special case the empty non-const map.
- if (node.items.length == 0 && !node.isConst) {
- return world.mapType.getConstructor('').invoke(this, node,
- new TypeValue(world.mapType, node.span), Arguments.EMPTY);
- }
-
- var values = <Value>[];
- var valueType = world.varType, keyType = world.stringType;
- var mapType = world.mapType; // TODO(jimhug): immutable type?
- if (node.valueType !== null) {
- if (node.keyType !== null) {
- keyType = method.resolveType(node.keyType, true, !node.isConst);
- // TODO(jimhug): Would be nice to allow arbitrary keys here (this is
- // currently not allowed by the spec).
- if (!keyType.isString) {
- world.error('the key type of a map literal must be "String"',
- keyType.span);
- }
- if (node.isConst &&
- (keyType is ParameterType || keyType.hasTypeParams)) {
- world.error('type parameter cannot be used in const map literals');
- }
- }
-
- valueType = method.resolveType(node.valueType, true, !node.isConst);
- if (node.isConst &&
- (valueType is ParameterType || valueType.hasTypeParams)) {
- world.error('type parameter cannot be used in const map literals');
- }
-
- mapType = mapType.getOrMakeConcreteType([keyType, valueType]);
- }
-
- for (int i = 0; i < node.items.length; i += 2) {
- var key = visitTypedValue(node.items[i], keyType);
- if (node.isConst && !key.isConst) {
- world.error('const map can only contain const keys', key.span);
- }
- values.add(key);
-
- var value = visitTypedValue(node.items[i + 1], valueType);
- if (node.isConst && !value.isConst) {
- world.error('const map can only contain const values', value.span);
- }
- values.add(value);
- }
-
- var ret = new MapValue(values, node.isConst, mapType, node.span);
- if (ret.isConst) return ret.getGlobalValue();
- return ret;
- }
-
- visitConditionalExpression(ConditionalExpression node) {
- var test = visitBool(node.test);
- var trueBranch = visitValue(node.trueBranch);
- var falseBranch = visitValue(node.falseBranch);
-
- // TODO(jmesserly): is there a way to use Value.union here, even though
- // we need different code?
- return new Value(Type.union(trueBranch.type, falseBranch.type),
- '${test.code} ? ${trueBranch.code} : ${falseBranch.code}', node.span);
- }
-
- visitIsExpression(IsExpression node) {
- var value = visitValue(node.x);
- var type = method.resolveType(node.type, true, true);
- if (type.isVar) {
- return Value.comma(value, Value.fromBool(true, node.span));
- }
-
- return value.instanceOf(this, type, node.span, node.isTrue);
- }
-
- visitParenExpression(ParenExpression node) {
- var body = visitValue(node.body);
- // Assumption implicit here that const values never need parens...
- if (body.isConst) return body;
- return new Value(body.type, '(${body.code})', node.span);
- }
-
- visitDotExpression(DotExpression node) {
- // Types are legal targets of .
- var target = node.self.visit(this);
- return target.get_(this, node.name.name, node.name);
- }
-
- visitVarExpression(VarExpression node) {
- final name = node.name.name;
-
- // First check in block scopes.
- var ret = _scope.lookup(name);
- if (ret != null) return ret;
-
- return _makeThisOrType(node.span).get_(this, name, node);
- }
-
- _makeMissingValue(String name) {
- // TODO(jimhug): Probably goes away to be fully replaced by noSuchMethod
- return new Value(world.varType, '$name()/*NotFound*/', null);
- }
-
- _makeThisOrType(SourceSpan span) {
- return new BareValue(this, _getOutermostMethod(), span);
- }
-
- visitThisExpression(ThisExpression node) {
- return _makeThisValue(node);
- }
-
- visitSuperExpression(SuperExpression node) {
- return _makeSuperValue(node);
- }
-
- visitLiteralExpression(LiteralExpression node) {
- return node.value;
- }
-
- _isUnaryIncrement(Expression item) {
- if (item is UnaryExpression) {
- UnaryExpression u = item;
- return u.op.kind == TokenKind.INCR || u.op.kind == TokenKind.DECR;
- } else {
- return false;
- }
- }
-
- String foldStrings(List<StringValue> strings) {
- StringBuffer buffer = new StringBuffer();
- for (var part in strings) buffer.add(part.constValue.actualValue);
- return buffer.toString();
- }
-
- visitStringConcatExpression(StringConcatExpression node) {
- var items = [];
- var itemsConst = [];
- for (var item in node.strings) {
- Value val = visitValue(item);
- assert(val.type.isString);
- if (val.isConst) itemsConst.add(val);
- items.add(val.code);
- }
- if (items.length == itemsConst.length) {
- return new StringValue(foldStrings(itemsConst), true, node.span);
- } else {
- String code = '(${Strings.join(items, " + ")})';
- return new Value(world.stringType, code, node.span);
- }
- }
-
- visitStringInterpExpression(StringInterpExpression node) {
- var items = [];
- var itemsConst = [];
- for (var item in node.pieces) {
- var val = visitValue(item);
- bool isConst = val.isConst && val.type.isString;
- if (!isConst) {
- val.invoke(this, 'toString', item, Arguments.EMPTY);
- }
- // TODO(jimhug): Ensure this solves all precedence problems.
- // TODO(jmesserly): We could be smarter about prefix/postfix, but we'd
- // need to know if it will compile to a ++ or to some sort of += form.
- var code = val.code;
- if (_expressionNeedsParens(item)) {
- code = '(${code})';
- }
- // No need to concat empty strings except the first.
- if (items.length == 0 || (code != "''" && code != '""')) {
- items.add(code);
- if (isConst) itemsConst.add(val);
- }
- }
- if (items.length == itemsConst.length) {
- return new StringValue(foldStrings(itemsConst), true, node.span);
- } else {
- String code = '(${Strings.join(items, " + ")})';
- return new Value(world.stringType, code, node.span);
- }
- }
-}
-
-
-// TODO(jmesserly): move this into its own file?
-class Arguments {
- static Arguments _empty;
- static Arguments get EMPTY() {
- if (_empty == null) {
- _empty = new Arguments(null, []);
- }
- return _empty;
- }
-
- List<Value> values;
- List<ArgumentNode> nodes;
- int _bareCount;
-
- Arguments(this.nodes, this.values);
-
- /** Constructs a bare list of arguments. */
- factory Arguments.bare(int arity) {
- var values = [];
- for (int i = 0; i < arity; i++) {
- // TODO(jimhug): Need a firm rule about null SourceSpans are allowed.
- values.add(new VariableValue(world.varType, '\$$i', null));
- }
- return new Arguments(null, values);
- }
-
- int get nameCount() => length - bareCount;
- bool get hasNames() => bareCount < length;
-
- int get length() => values.length;
-
- String getName(int i) => nodes[i].label.name;
-
- int getIndexOfName(String name) {
- for (int i = bareCount; i < length; i++) {
- if (getName(i) == name) {
- return i;
- }
- }
- return -1;
- }
-
- Value getValue(String name) {
- int i = getIndexOfName(name);
- return i >= 0 ? values[i] : null;
- }
-
- int get bareCount() {
- if (_bareCount == null) {
- _bareCount = length;
- if (nodes != null) {
- for (int i = 0; i < nodes.length; i++) {
- if (nodes[i].label != null) {
- _bareCount = i;
- break;
- }
- }
- }
- }
- return _bareCount;
- }
-
- String getCode() {
- var argsCode = [];
- for (int i = 0; i < length; i++) {
- argsCode.add(values[i].code);
- }
- removeTrailingNulls(argsCode);
- return Strings.join(argsCode, ", ");
- }
-
- List<String> getBareCodes() {
- var result = [];
- for (int i = 0; i < bareCount; i++) {
- result.add(values[i].code);
- }
- return result;
- }
-
- List<String> getNamedCodes() {
- var result = [];
- for (int i = bareCount; i < length; i++) {
- result.add(values[i].code);
- }
- return result;
- }
-
- static removeTrailingNulls(List<Value> argsCode) {
- // We simplify calls with null defaults by relying on JS and our
- // choice to make undefined === null for Dart generated code. This helps
- // and ensures correct defaults values for native calls.
- while (argsCode.length > 0 && argsCode.last() == 'null') {
- argsCode.removeLast();
- }
- }
-
- /** Gets the named arguments. */
- List<String> getNames() {
- var names = [];
- for (int i = bareCount; i < length; i++) {
- names.add(getName(i));
- }
- return names;
- }
-
- /** Gets the argument names used in a call stub; uses $0 $1 for bare args. */
- Arguments toCallStubArgs() {
- var result = [];
- for (int i = 0; i < bareCount; i++) {
- result.add(new VariableValue(world.varType, '\$$i', null));
- }
- for (int i = bareCount; i < length; i++) {
- var name = getName(i);
- if (name == null) name = '\$$i';
- result.add(new VariableValue(world.varType, name, null));
- }
- return new Arguments(nodes, result);
- }
-
- bool matches(Arguments other) {
- if (length != other.length) return false;
- if (bareCount != other.bareCount) return false;
-
- for (int i = 0; i < bareCount; i++) {
- if (values[i].type != other.values[i].type) return false;
- }
- // TODO(jimhug): Needs to check that named args also match!
- return true;
- }
-
-}
-
-class ReturnKind {
- static final int IGNORE = 1;
- static final int POST = 2;
- static final int PRE = 3;
-}
« no previous file with comments | « frog/frogc.dart ('k') | frog/lang.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698