Index: lib/src/compiler/js_metalet.dart |
diff --git a/lib/src/compiler/js_metalet.dart b/lib/src/compiler/js_metalet.dart |
index 5bddf5a8e65fea24f71ee543016f750da4c3ab60..c0581d638a1ed6f1ac10fdcff68c98b7c63f954c 100644 |
--- a/lib/src/compiler/js_metalet.dart |
+++ b/lib/src/compiler/js_metalet.dart |
@@ -36,7 +36,7 @@ class MetaLet extends Expression { |
/// If the expression does not end up using `x` more than once, or if those |
/// expressions can be treated as [stateless] (e.g. they are non-mutated |
/// variables), then the resulting code will be simplified automatically. |
- final Map<String, Expression> variables; |
+ final Map<MetaLetVariable, Expression> variables; |
/// A list of expressions in the body. |
/// The last value should represent the returned value. |
@@ -171,49 +171,40 @@ class MetaLet extends Expression { |
} |
Block _finishStatement(List<Statement> statements) { |
- var params = <TemporaryId>[]; |
- var values = <Expression>[]; |
- var block = _build(params, values, new Block(statements)); |
- if (params.isEmpty) return block; |
- |
- var vars = []; |
- for (int i = 0; i < params.length; i++) { |
- vars.add(new VariableInitialization(params[i], values[i])); |
- } |
- |
- return new Block(<Statement>[ |
- new VariableDeclarationList('let', vars).toStatement(), |
- block |
- ]); |
- } |
- |
- Node _build(List<TemporaryId> params, List<Expression> values, Node node) { |
// Visit the tree and count how many times each temp was used. |
var counter = new _VariableUseCounter(); |
+ var node = new Block(statements); |
node.accept(counter); |
// Also count the init expressions. |
for (var init in variables.values) init.accept(counter); |
- var substitutions = {}; |
- _substitute(node) => new Template(null, node).safeCreate(substitutions); |
- |
- variables.forEach((name, init) { |
+ var initializers = <VariableInitialization>[]; |
+ var substitutions = <MetaLetVariable, Expression>{}; |
+ variables.forEach((variable, init) { |
// Since this is let*, subsequent variables can refer to previous ones, |
// so we need to substitute here. |
- init = _substitute(init); |
- int n = counter.counts[name]; |
- if (n == null || n < 2) { |
- substitutions[name] = _substitute(init); |
+ init = _substitute(init, substitutions); |
+ int n = counter.counts[variable]; |
+ if (n == 1) { |
+ // Replace interpolated exprs with their value, if it only occurs once. |
+ substitutions[variable] = init; |
} else { |
- params.add(substitutions[name] = new TemporaryId(name)); |
- values.add(init); |
+ // Otherwise replace it with a temp, which will be assigned once. |
+ var temp = new TemporaryId(variable.displayName); |
+ substitutions[variable] = temp; |
+ initializers.add(new VariableInitialization(temp, init)); |
} |
}); |
- // Interpolate the body: |
- // Replace interpolated exprs with their value, if it only occurs once. |
- // Otherwise replace it with a temp, which will be assigned once. |
- return _substitute(node); |
+ // Interpolate the body. |
+ node = _substitute(node, substitutions); |
+ if (initializers.isNotEmpty) { |
+ node = new Block([ |
+ new VariableDeclarationList('let', initializers).toStatement(), |
+ node |
+ ]); |
+ } |
+ return node; |
} |
/// If we finish with an assignment to an identifier, try to simplify the |
@@ -231,11 +222,10 @@ class MetaLet extends Expression { |
/// |
MetaLet _simplifyAssignment(Identifier left, {bool isDeclaration: false}) { |
// See if the result value is a let* temporary variable. |
- if (body.last is! InterpolatedExpression) return null; |
+ if (body.last is! MetaLetVariable) return null; |
- InterpolatedExpression last = body.last; |
- String name = last.nameOrPosition; |
- if (!variables.containsKey(name)) return null; |
+ MetaLetVariable result = body.last; |
+ if (!variables.containsKey(result)) return null; |
// Variables declared can't be used inside their initializer, so make |
// sure we don't transform an assignment into an initializer. |
@@ -252,8 +242,8 @@ class MetaLet extends Expression { |
if (finder.found) return null; |
} |
- var vars = new Map<String, Expression>.from(variables); |
- var value = vars.remove(name); |
+ var vars = new Map<MetaLetVariable, Expression>.from(variables); |
+ var value = vars.remove(result); |
Expression assign; |
if (isDeclaration) { |
// Technically, putting one of these in a comma expression is not |
@@ -266,18 +256,59 @@ class MetaLet extends Expression { |
} |
var newBody = new Expression.binary([assign]..addAll(body), ','); |
- Binary comma = new Template(null, newBody).safeCreate({name: left}); |
- return new MetaLet(vars, comma.commaToExpressionList(), |
+ newBody = _substitute(newBody, {result: left}); |
+ return new MetaLet(vars, newBody.commaToExpressionList(), |
statelessResult: statelessResult); |
} |
} |
+/// Similar to [Template.instantiate] but works with free variables. |
+Node _substitute(Node tree, Map<MetaLetVariable, Expression> substitutions) { |
+ var generator = new InstantiatorGeneratorVisitor(/*forceCopy:*/ false); |
+ var instantiator = generator.compile(tree); |
+ var nodes = new List<MetaLetVariable>.from(generator |
+ .analysis.containsInterpolatedNode |
+ .where((n) => n is MetaLetVariable)); |
+ if (nodes.isEmpty) return tree; |
+ |
+ return instantiator(new Map.fromIterable(nodes, |
+ key: (v) => (v as MetaLetVariable).nameOrPosition, |
+ value: (v) => substitutions[v] ?? v)); |
+} |
+ |
+/// A temporary variable used in a [MetaLet]. |
+/// |
+/// Each instance of this class represents a fresh variable. The same object |
+/// should be used everywhere to refer to the same variable. Different variables |
+/// with the same name are different, and will be renamed later on, if needed. |
+/// |
+/// These variables will be replaced when the `let*` is complete, depending on |
+/// how often they occur and whether they can be optimized away. See [MetaLet] |
+/// for more information. |
+/// |
+/// This class should never reach our final JS code. |
+class MetaLetVariable extends InterpolatedExpression { |
+ /// The suggested display name of this variable. |
+ /// |
+ /// This name should not be used |
+ final String displayName; |
+ |
+ /// Compute fresh IDs to avoid |
+ static int _uniqueId = 0; |
+ |
+ MetaLetVariable(String displayName) |
+ : displayName = displayName, |
+ super(displayName + '@${++_uniqueId}'); |
+} |
+ |
class _VariableUseCounter extends BaseVisitor { |
- final counts = <String, int>{}; |
+ final counts = <MetaLetVariable, int>{}; |
@override |
visitInterpolatedExpression(InterpolatedExpression node) { |
- int n = counts[node.nameOrPosition]; |
- counts[node.nameOrPosition] = n == null ? 1 : n + 1; |
+ if (node is MetaLetVariable) { |
+ int n = counts[node]; |
+ counts[node] = n == null ? 1 : n + 1; |
+ } |
} |
} |