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

Unified Diff: sdk/lib/_internal/compiler/implementation/js/builder.dart

Issue 237583014: JS templates (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: cleanup Created 6 years, 8 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
Index: sdk/lib/_internal/compiler/implementation/js/builder.dart
diff --git a/sdk/lib/_internal/compiler/implementation/js/builder.dart b/sdk/lib/_internal/compiler/implementation/js/builder.dart
index 31e88f1d6bac335587fe37e6d35b5f9147600001..d1e2cc27743e8a2e452bb4060554d46814f0e5fc 100644
--- a/sdk/lib/_internal/compiler/implementation/js/builder.dart
+++ b/sdk/lib/_internal/compiler/implementation/js/builder.dart
@@ -7,6 +7,189 @@
part of js;
+
+/**
+ * Global template manager. We should aim to have a fixed number of
+ * templates. This implies that we do not use js('xxx') to parse text that is
+ * constructed from values that depend on names in the Dart program.
+ *
+ * TODO(sra): Find the remaining places where js('xxx') used to parse an
floitsch 2014/04/22 16:11:18 I'm currently pushing to remove all statics from t
sra1 2014/04/23 02:33:50 I'm not sure how to find it without making the tem
+ * unbounded number of expression, or institute a cache policy.
+ */
+TemplateManager templateManager = new TemplateManager();
+
+
+/**
+
+[js] is a singleton instace of JsBuilder. JsBuilder is a set of conveniences
+for constructing JavaScript ASTs.
+
+[string] and [number] are used to create leaf AST nodes:
+
+ var s = js.string('hello'); // s = new LiteralString('"hello"')
+ var n = js.number(123); // n = new LiteralNumber(123)
+ var vFoo = new VariableUse('foo') --> foo
floitsch 2014/04/22 16:11:18 what does `new VariableUse` have to do with `js`?
sra1 2014/04/23 02:33:50 Done.
+
+In the line above `a --> b` means Dart expression `a` evaluates to a JavaScript
+AST that would pretty-print as `b`.
+
+The [call] method constructs an Expression AST. Since the builder is bound to
floitsch 2014/04/22 16:11:18 Not sure I understand the second sentence. It's ju
sra1 2014/04/23 02:33:50 Done.
+[js] `js` is used to construct JavaScript from a fragment of source.
+
+No argument
+
+ js('window.alert("hello")') --> window.alert("hello")
+
+The input text can contain placeholders `#` that are replace with provided
floitsch 2014/04/22 16:11:18 replaced
sra1 2014/04/23 02:33:50 Done.
+arguments. A single argument can be passed directly:
+
+ js('window.alert(#)', s) --> window.alert("hello")
+
+Multiple arguments are passed as a list:
+
+ js('# + #', [s, s]) --> "hello" + "hello"
+
+The [statement] method constructs a Statement AST, but is otherwise like the
+[call] method. This constructs a Return AST:
+
+ var ret = js.statement('return #;', n); --> return 123;
+
+A placeholder in a Statement context must be followed by a semicolon ';'. You
+can think of a statement placeholder as being `#;` to explain why the output
+still has one semicolon:
+
+ js.statement('if (happy) #;', ret)
+ -->
+ if (happy)
+ return 123;
+
+If the placeholder is not followed by a semicolon, it is part of an expression.
+Here the paceholder is in the position of a function in a function call.
+
+ js.statement('if (happy) #("Happy!")', vFoo)
+ -->
+ if (happy)
+ foo("Happy!");
+
+Generally, a placeholder in an expression position requires an Expression AST as
+an argument and a placeholder in a statement position requires a Statement AST.
+An expression will be converted to a Statement of needed by creating an
+ExpessionStatement. A String argument in a will be converted into a VariableUse
+and requires that the string is a JavaScript identifier.
+
+ js('# + 1', vFoo) --> foo + 1
+ js('# + 1', 'foo') --> foo + 1
+ js('# + 1', 'foo.bar') --> assertion failure
+
+Some placeholder positions are _splicing contexts_. A function argument list is
+a splicing expression context. A placeholder in a splicing expression context
+can take a single Expression (or String, converted to VariableUse) or an
+Iterable of Expressions (and/or Strings).
+
+ // non-splicing argument:
+ js('#(#)', ['say', s]) --> say("hello")
+ // splicing arguments:
+ js('#(#)', ['say', []]) --> say()
+ js('#(#)', ['say', [s]]) --> say("hello")
+ js('#(#)', ['say', [s, n]]) --> say("hello", 123)
+
+A splicing context can be used to append 'lists' and add extra elements:
+
+ js('foo(#, #, 1)', [ ['a', n], s]) --> foo(a, 123, "hello", 1)
+ js('foo(#, #, 1)', [ ['a', n], [s, n]]) --> foo(a, 123, "hello", 123, 1)
+ js('foo(#, #, 1)', [ [], [s, n]]) --> foo("hello", 123, 1)
+ js('foo(#, #, 1)', [ [], [] ]) --> foo(1)
+
+The generation of a compile-time optional argument expression can be chosen by
+providing an empty or singleton list.
+
+In addition to Expressions and Statements, there are Parameters, which occur
+only in the parameter list of a function expression or declaration.
+Placeholders in parameter positions behave like placeholders in Expression
+positions, except only Parameter AST nodes are permitted. String arguments for
+parameter placeholders are converted to Parameter AST nodes.
+
+ var pFoo = new Parameter('foo')
+ js('function(#) { return #; }', [pFoo, vFoo]) --> function(foo){return foo;}
+
+Expressions and Parameters are not compatible with each other's context:
+
+ js('function(#) { return #; }', [vFoo, vFoo]) --> error
+ js('function(#) { return #; }', [pFoo, pFoo]) --> error
+
+The parameter context is a splicing context. When combined with the
+context-sensitive conversion of Strings, this simplifies the construction of
+trampoline-like functions:
+
+ var args = ['a', 'b'];
+ js('function(#) { return f(this, #); }', [args, args])
+ -->
+ function(a, b) { return f(this, a, b); }
+
+A statement placeholder in a Block is also in a splicing context. In addition
+to splicing Iterables, statement placeholders in a Block will also splice a
+Block or an EmptyStatement. This flattens nested blocks and allows blocks to be
+appended.
+
+ var b1 = js.statement('{ 1; 2; }');
+ var sEmpty = new Emptystatement();
+ js.statement('{ #; #; #; #; }', [sEmpty, b1, b1, sEmpty])
+ -->
+ { 1; 2; 1; 2; }
+
+A placeholder in the context of an if-statement condition also accepts a Dart
+bool argument, which select then-part or else-part of the if-statement:
+
+ js.statement('if (#) return;', vFoo) --> if (foo) return;
+ js.statement('if (#) return;', true) --> return;
+ js.statement('if (#) return;', false) --> ; // empty statement
+ var eTrue = new LiteralBool(true);
+ js.statement('if (#) return;', eTrue) --> if (true) return;
+
+Combined with block splicing, if-statement condition context placeholders allows
+the creation of tenplates that select code depending on variables.
+
+ js.statement('{ 1; if (#) 2; else { 3; 4; } 5;}', true)
+ --> { 1; 2; 5; }
+
+ js.statement('{ 1; if (#) 2; else { 3; 4; } 5;}', false)
+ --> { 1; 3; 4; 5; }
+
+A placeholder following a period in a property access is in a property access
+context. This is just like an expression context, except String arguments are
+converted to JavaScript property accesses. In JavaScript, `a.b` is short-hand
+for `a["b"]`:
+
+ js('a[#]', vFoo) --> a[foo]
+ js('a[#]', s) --> a.hello (i.e. a["hello"]).
+ js('a[#]', 'x') --> a[x]
+
+ js('a.#', vFoo) --> a[foo]
+ js('a.#', s) --> a.hello (i.e. a["hello"])
+ js('a.#', 'x') --> a.x (i.e. a["x"])
+
+(Question - should `.#` be restricted to permit only String arguments? The
+template should probably be writted with `[]` if non-strings are accepted.)
floitsch 2014/04/22 16:11:18 I would say yes. Let's not allow non-string argume
sra1 2014/04/23 02:33:50 Presumably you saw that a.# was used extensively.
+
+
+Object initialiers allow placeholders in the key posiition:
floitsch 2014/04/22 16:11:18 initializers position
sra1 2014/04/23 02:33:50 Done.
+
+ js('{#:1, #:2}', [s, 'bye']) --> {hello: 1, bye: 2}
+
+
+TODO: Array initializers and object initializers could support splicing. In
floitsch 2014/04/22 16:11:18 TODO(sra) or TODO(issue-number)
sra1 2014/04/23 02:33:50 I rephased this since I'm not sure it is a TODO.
+the array case, we would need some way to know if an ArrayInitializer argument
+should be splice or is intended as a single value.
+
+TODO: There are no placeholders in definition contexts:
floitsch 2014/04/22 16:11:18 ditto.
sra1 2014/04/23 02:33:50 I rephased this since I'm not sure it is a TODO.
+
+ function #(){}
+ var #=1;
+
+*/
+const JsBuilder js = const JsBuilder();
floitsch 2014/04/22 16:11:18 In order to get rid of the static state we could r
floitsch 2014/04/22 16:11:18 On the other hand I can see how this makes repeate
sra1 2014/04/23 02:33:50 That is part of the plan :-)
sra1 2014/04/23 02:33:50 Or we could make the state independent of any comp
+
+
class JsBuilder {
const JsBuilder();
@@ -15,45 +198,79 @@ class JsBuilder {
*
* See the MiniJsParser class.
*
- * [expression] can be an [Expression] or a list of [Expression]s, which will
- * be interpolated into the source at the '#' signs.
+ * [arguments] can be a single [Node] (e.g. an [Expression] or [Statement]) or
+ * a list of [Node]s, which will be interpolated into the source at the '#'
+ * signs.
+ */
+ Expression call(String source, [var arguments]) {
+ Template template = _findExpressionTemplate(source);
+ if (arguments == null) return template.instantiate([]);
+ return template.instantiate(arguments is List ? arguments : [arguments]);
+ }
+
+ /**
+ * Parses a JavaScript Statement, otherwise just like [call].
*/
- Expression call(String source, [var expression]) {
- var result = new MiniJsParser(source).expression();
- if (expression == null) return result;
+ Statement statement(String source, [var arguments]) {
+ Template template = _findStatementTemplate(source);
+ if (arguments == null) return template.instantiate([]);
+ return template.instantiate(arguments is List ? arguments : [arguments]);
+ }
- List<Node> nodes;
- if (expression is List) {
- nodes = expression;
+ /**
+ * Parses JavaScript written in the `JS` foreign instruction.
+ *
+ * The [source] must be a JavaScript expression or a JavaScript throw
+ * statement.
+ */
+ Template parseForeignJS(String source) {
+ // TODO(sra): Parse with extra validation to forbid `#` interpolation in
+ // functions, as this leads to unanticipated capture of temporaries that can
floitsch 2014/04/22 16:11:18 -can-
sra1 2014/04/23 02:33:50 Done.
+ // are reused after capture.
+ if (source.startsWith("throw ")) {
+ return _findStatementTemplate(source);
} else {
- nodes = <Node>[expression];
+ return _findExpressionTemplate(source);
}
- if (nodes.length != result.interpolatedNodes.length) {
- throw 'Unmatched number of interpolated expressions given ${nodes.length}'
- ' expected ${result.interpolatedNodes.length}';
- }
- for (int i = 0; i < nodes.length; i++) {
- result.interpolatedNodes[i].assign(nodes[i]);
+ }
+
+ Template _findExpressionTemplate(String source) {
+ Template template = templateManager.lookupExpressionTemplate(source);
+ if (template == null) {
+ MiniJsParser parser = new MiniJsParser(source);
+ Expression expression = parser.expression();
+ template = templateManager.defineExpressionTemplate(source, expression);
}
+ return template;
+ }
- return result.value;
+ Template _findStatementTemplate(String source) {
+ Template template = templateManager.lookupStatementTemplate(source);
+ if (template == null) {
+ MiniJsParser parser = new MiniJsParser(source);
+ Statement expression = parser.statement();
floitsch 2014/04/22 16:11:18 s/expression/statement
sra1 2014/04/23 02:33:50 Done.
+ template = templateManager.defineStatementTemplate(source, expression);
+ }
+ return template;
}
- Statement statement(String source) {
- var result = new MiniJsParser(source).statement();
- // TODO(sra): Interpolation.
- return result;
+ /**
+ * Creates an Expression template without caching the result.
+ */
+ Template uncachedExpressionTemplate(String source) {
+ MiniJsParser parser = new MiniJsParser(source);
+ Expression expression = parser.expression();
+ return new Template(
+ source, expression, isExpression: true, forceCopy: false);
}
- // Parse JavaScript written in the JS foreign instruction.
- Expression parseForeignJS(String source, [var expression]) {
- // We can parse simple JS with the mini parser. At the moment we can't
- // handle JSON literals and function literals, both of which contain "{".
- if (source.contains("{") || source.startsWith("throw ")) {
- assert(expression == null);
- return new LiteralExpression(source);
- }
- return call(source, expression);
+ /**
+ * Create an Expression template which has [ast] as the result. This is used
+ * to wrap a generated AST in a zero-argument Template so it can be passed to
+ * context that expects a template.
+ */
+ Template expressionTemplateYielding(Node ast) {
+ return new Template.withExpressionResult(ast);
}
/// Creates a litteral js string from [value].
@@ -90,120 +307,35 @@ class JsBuilder {
LiteralNumber number(num value) => new LiteralNumber('$value');
- If if_(condition, thenPart, [elsePart]) {
- condition = toExpression(condition);
- return (elsePart == null)
- ? new If.noElse(condition, toStatement(thenPart))
- : new If(condition, toStatement(thenPart), toStatement(elsePart));
- }
-
- Return return_([value]) {
- return new Return(value == null ? null : toExpression(value));
- }
-
- Block block(statement) {
- if (statement is Block) {
- return statement;
- } else if (statement is List) {
- List<Statement> statements = statement
- .map(toStatement)
- .where((s) => s is !EmptyStatement)
- .toList();
- return new Block(statements);
- } else {
- return new Block(<Statement>[toStatement(statement)]);
- }
- }
-
- Fun fun(parameters, body) {
- Parameter toParameter(parameter) {
- if (parameter is String) {
- return new Parameter(parameter);
- } else if (parameter is Parameter) {
- return parameter;
- } else {
- throw new ArgumentError('parameter should be a String or a Parameter');
- }
- }
- if (parameters is! List) {
- parameters = [parameters];
- }
- return new Fun(parameters.map(toParameter).toList(), block(body));
- }
-
- VariableDeclarationList defineVar(String name, [initializer]) {
- if (initializer != null) {
- initializer = toExpression(initializer);
- }
- var declaration = new VariableDeclaration(name);
- var initialization = [new VariableInitialization(declaration, initializer)];
- return new VariableDeclarationList(initialization);
- }
-
- Statement toStatement(statement) {
- if (statement is List) {
- return block(statement);
- } else if (statement is Node) {
- return statement.toStatement();
- } else {
- throw new ArgumentError('statement');
- }
- }
-
- Expression toExpression(expression) {
- if (expression == null) {
- return null;
- } else if (expression is Expression) {
- return expression;
- } else if (expression is String) {
- return this(expression);
- } else if (expression is num) {
- return new LiteralNumber('$expression');
- } else if (expression is bool) {
- return new LiteralBool(expression);
- } else if (expression is Map) {
- if (!expression.isEmpty) {
- throw new ArgumentError('expression should be an empty Map');
- }
- return new ObjectInitializer([]);
- } else if (expression is List) {
- var values = new List<ArrayElement>.generate(expression.length,
- (index) => new ArrayElement(index, toExpression(expression[index])));
- return new ArrayInitializer(values.length, values);
- } else {
- throw new ArgumentError('expression should be an Expression, '
- 'a String, a num, a bool, a Map, or a List;');
- }
- }
-
- ForIn forIn(String name, object, statement) {
- return new ForIn(defineVar(name),
- toExpression(object),
- toStatement(statement));
- }
-
- For for_(init, condition, update, statement) {
- return new For(
- toExpression(init), toExpression(condition), toExpression(update),
- toStatement(statement));
- }
-
- While while_(condition, statement) {
- return new While(
- toExpression(condition), toStatement(statement));
- }
-
- Try try_(body, {catchPart, finallyPart}) {
- if (catchPart != null) catchPart = toStatement(catchPart);
- if (finallyPart != null) finallyPart = toStatement(finallyPart);
- return new Try(toStatement(body), catchPart, finallyPart);
- }
+ //Expression toExpression(expression) {
floitsch 2014/04/22 16:11:18 dead code?
sra1 2014/04/23 02:33:50 Yes. It was the only one that was slightly painfu
+ // if (expression == null) {
+ // return null;
+ // } else if (expression is Expression) {
+ // return expression;
+ // } else if (expression is String) {
+ // return this(expression);
+ // } else if (expression is num) {
+ // return new LiteralNumber('$expression');
+ // } else if (expression is bool) {
+ // return new LiteralBool(expression);
+ // } else if (expression is Map) {
+ // if (!expression.isEmpty) {
+ // throw new ArgumentError('expression should be an empty Map');
+ // }
+ // return new ObjectInitializer([]);
+ // } else if (expression is List) {
+ // var values = new List<ArrayElement>.generate(expression.length,
+ // (index) => new ArrayElement(index, toExpression(expression[index])));
+ // return new ArrayInitializer(values.length, values);
+ // } else {
+ // throw new ArgumentError('expression should be an Expression, '
+ // 'a String, a num, a bool, a Map, or a List;');
+ // }
+ //}
Comment comment(String text) => new Comment(text);
}
-const JsBuilder js = const JsBuilder();
-
LiteralString string(String value) => js.string(value);
class MiniJsParserError {
@@ -245,9 +377,7 @@ class MiniJsParserError {
/// * var declarations.
/// * operator precedence.
/// Notable things it can't do yet include:
-/// * non-empty object literals.
-/// * throw, return.
-/// * statements, including any flow control (if, while, for, etc.)
+/// * some statements are still missing (do-while, while, switch.
floitsch 2014/04/22 16:11:18 missing closing parenthesis.
sra1 2014/04/23 02:33:50 Done.
///
/// It's a fairly standard recursive descent parser.
///
@@ -402,18 +532,24 @@ class MiniJsParser {
for (;;) {
if (position >= src.length) break;
int code = src.codeUnitAt(position);
- // Skip '//' style comment.
+ // Skip '//' and '/*' style comments.
if (code == charCodes.$SLASH &&
- position + 1 < src.length &&
- src.codeUnitAt(position + 1) == charCodes.$SLASH) {
- int nextPosition = src.indexOf('\n', position);
- if (nextPosition == -1) nextPosition = src.length;
- position = nextPosition;
- } else {
- if (category(code) != WHITESPACE) break;
- if (code == charCodes.$LF) skippedNewline = true;
- ++position;
+ position + 1 < src.length) {
+ if (src.codeUnitAt(position + 1) == charCodes.$SLASH) {
+ int nextPosition = src.indexOf('\n', position);
+ if (nextPosition == -1) nextPosition = src.length;
+ position = nextPosition;
+ continue;
+ } else if (src.codeUnitAt(position + 1) == charCodes.$STAR) {
+ int nextPosition = src.indexOf('*/', position + 2);
+ if (nextPosition == -1) error('Unterminated comment');
+ position = nextPosition + 2;
+ continue;
+ }
}
+ if (category(code) != WHITESPACE) break;
+ if (code == charCodes.$LF) skippedNewline = true;
+ ++position;
}
if (position == src.length) {
@@ -509,6 +645,7 @@ class MiniJsParser {
// Accept semicolon or automatically inserted semicolon before close brace.
// Miniparser forbids other kinds of semicolon insertion.
if (RBRACE == lastCategory) return true;
+ if (NONE == lastCategory) return true; // end of input
if (skippedNewline) {
error('No automatic semicolon insertion at preceding newline');
}
@@ -568,7 +705,8 @@ class MiniJsParser {
Expression expression = new RegExpLiteral(regexp + flags);
return expression;
} else if (acceptCategory(HASH)) {
- InterpolatedExpression expression = new InterpolatedExpression(null);
+ InterpolatedExpression expression =
+ new InterpolatedExpression(interpolatedValues.length);
interpolatedValues.add(expression);
return expression;
} else {
@@ -589,17 +727,26 @@ class MiniJsParser {
Expression parseFun() {
List<Parameter> params = <Parameter>[];
+
expectCategory(LPAREN);
- String argumentName = lastToken;
- if (acceptCategory(ALPHA)) {
- params.add(new Parameter(argumentName));
- while (acceptCategory(COMMA)) {
- argumentName = lastToken;
- expectCategory(ALPHA);
- params.add(new Parameter(argumentName));
+ if (!acceptCategory(RPAREN)) {
+ for (;;) {
+ if (acceptCategory(HASH)) {
+ InterpolatedParameter parameter =
+ new InterpolatedParameter(interpolatedValues.length);
+ interpolatedValues.add(parameter);
+ params.add(parameter);
+ } else {
+ String argumentName = lastToken;
+ expectCategory(ALPHA);
+ params.add(new Parameter(argumentName));
+ }
+ if (acceptCategory(COMMA)) continue;
+ expectCategory(RPAREN);
+ break;
}
}
- expectCategory(RPAREN);
+
expectCategory(LBRACE);
Block block = parseBlock();
return new Fun(params, block);
@@ -616,6 +763,13 @@ class MiniJsParser {
propertyName = new LiteralString('"$identifier"');
} else if (acceptCategory(STRING)) {
propertyName = new LiteralString(identifier);
+ } else if (acceptCategory(SYMBOL)) { // e.g. void
+ propertyName = new LiteralString('"$identifier"');
+ } else if (acceptCategory(HASH)) {
+ InterpolatedLiteral interpolatedLiteral =
+ new InterpolatedLiteral(interpolatedValues.length);
+ interpolatedValues.add(interpolatedLiteral);
+ propertyName = interpolatedLiteral;
} else {
error('Expected property name');
}
@@ -678,6 +832,12 @@ class MiniJsParser {
}
Expression getDotRhs(Expression receiver) {
+ if (acceptCategory(HASH)) {
+ InterpolatedSelector property =
+ new InterpolatedSelector(interpolatedValues.length);
+ interpolatedValues.add(property);
+ return new PropertyAccess(receiver, property);
+ }
String identifier = lastToken;
// In ES5 keywords like delete and continue are allowed as property
// names, and the IndexedDB API uses that, so we need to allow it here.
@@ -829,9 +989,6 @@ class MiniJsParser {
if (lastCategory != NONE || position != src.length) {
error("Unparsed junk: ${categoryToString(lastCategory)}");
}
- if (!interpolatedValues.isEmpty) {
- return new JSExpression(expression, interpolatedValues);
- }
return expression;
}
@@ -857,6 +1014,8 @@ class MiniJsParser {
Statement parseStatement() {
if (acceptCategory(LBRACE)) return parseBlock();
+ if (acceptCategory(SEMICOLON)) return new EmptyStatement();
+
if (lastCategory == ALPHA) {
if (acceptString('return')) return parseReturn();
@@ -876,6 +1035,8 @@ class MiniJsParser {
if (acceptString('function')) return parseFunctionDeclaration();
+ if (acceptString('try')) return parseTry();
+
if (acceptString('var')) {
Expression declarations = parseVariableDeclarationList();
expectSemicolon();
@@ -886,22 +1047,31 @@ class MiniJsParser {
lastToken == 'do' ||
lastToken == 'while' ||
lastToken == 'switch' ||
- lastToken == 'try' ||
lastToken == 'with') {
error('Not implemented in mini parser');
}
}
- if (acceptCategory(HASH)) {
- InterpolatedStatement statement = new InterpolatedStatement(null);
- interpolatedValues.add(statement);
- return statement;
- }
// TODO: label: statement
+ bool checkForInterpolatedStatement = lastCategory == HASH;
+
Expression expression = parseExpression();
expectSemicolon();
+
+ if (checkForInterpolatedStatement) {
+ // 'Promote' the interpolated expression `#;` to an interpolated
+ // statement.
+ if (expression is InterpolatedExpression) {
+ assert(identical(interpolatedValues.last, expression));
+ InterpolatedStatement statement =
+ new InterpolatedStatement(expression.name);
+ interpolatedValues[interpolatedValues.length - 1] = statement;
+ return statement;
+ }
+ }
+
return new ExpressionStatement(expression);
}
@@ -977,7 +1147,12 @@ class MiniJsParser {
Expression objectExpression = parseExpression();
expectCategory(RPAREN);
Statement body = parseStatement();
- return new ForIn(js.defineVar(identifier), objectExpression, body);
+ return new ForIn(
+ new VariableDeclarationList([
+ new VariableInitialization(
+ new VariableDeclaration(identifier), null)]),
+ objectExpression,
+ body);
}
Expression declarations = finishVariableDeclarationList(identifier);
expectCategory(SEMICOLON);
@@ -995,141 +1170,30 @@ class MiniJsParser {
Expression fun = parseFun();
return new FunctionDeclaration(new VariableDeclaration(name), fun);
}
-}
-
-/**
- * Clone a JSExpression node into an expression where all children
- * have been cloned, and [InterpolatedExpression]s have been replaced
- * with real [Expression].
- */
-class UninterpolateJSExpression extends BaseVisitor<Node> {
- final List<Expression> arguments;
- int argumentIndex = 0;
-
- UninterpolateJSExpression(this.arguments);
-
- void error(message) {
- throw message;
- }
- Node visitNode(Node node) {
- error('Cannot handle $node');
- return null;
- }
-
- Node copyPosition(Node oldNode, Node newNode) {
- newNode.sourcePosition = oldNode.sourcePosition;
- newNode.endSourcePosition = oldNode.endSourcePosition;
- return newNode;
- }
-
- Node visit(Node node) {
- return node == null ? null : node.accept(this);
- }
-
- List<Node> visitList(List<Node> list) {
- return list.map((e) => visit(e)).toList();
- }
-
- Node visitLiteralString(LiteralString node) {
- return node;
- }
-
- Node visitVariableUse(VariableUse node) {
- return node;
- }
-
- Node visitAccess(PropertyAccess node) {
- return copyPosition(node,
- new PropertyAccess(visit(node.receiver), visit(node.selector)));
- }
-
- Node visitCall(Call node) {
- return copyPosition(node,
- new Call(visit(node.target), visitList(node.arguments)));
- }
-
- Node visitInterpolatedExpression(InterpolatedExpression expression) {
- return arguments[argumentIndex++];
- }
-
- Node visitInterpolatedStatement(InterpolatedStatement statement) {
- return arguments[argumentIndex++];
- }
-
- Node visitJSExpression(JSExpression expression) {
- assert(argumentIndex == 0);
- Node result = visit(expression.value);
- if (argumentIndex != arguments.length) {
- error("Invalid number of arguments");
+ Statement parseTry() {
+ expectCategory(LBRACE);
+ Block body = parseBlock();
+ String token = lastToken;
+ Catch catchPart = null;
+ if (acceptString('catch')) catchPart = parseCatch();
+ Block finallyPart = null;
+ if (acceptString('finally')) {
+ expectCategory(LBRACE);
+ finallyPart = parseBlock();
+ } else {
+ if (catchPart == null) error("expected 'finally'");
}
- assert(result is! JSExpression);
- return result;
- }
-
- Node visitLiteralExpression(LiteralExpression node) {
- assert(argumentIndex == 0);
- return copyPosition(node,
- new LiteralExpression.withData(node.template, arguments));
+ return new Try(body, catchPart, finallyPart);
}
- Node visitAssignment(Assignment node) {
- return copyPosition(node,
- new Assignment._internal(visit(node.leftHandSide),
- visit(node.compoundTarget),
- visit(node.value)));
- }
-
- Node visitRegExpLiteral(RegExpLiteral node) {
- return node;
- }
-
- Node visitLiteralNumber(LiteralNumber node) {
- return node;
- }
-
- Node visitBinary(Binary node) {
- return copyPosition(node,
- new Binary(node.op, visit(node.left), visit(node.right)));
- }
-
- Node visitPrefix(Prefix node) {
- return copyPosition(node,
- new Prefix(node.op, visit(node.argument)));
- }
-
- Node visitPostfix(Postfix node) {
- return copyPosition(node,
- new Postfix(node.op, visit(node.argument)));
- }
-
- Node visitNew(New node) {
- return copyPosition(node,
- new New(visit(node.target), visitList(node.arguments)));
- }
-
- Node visitArrayInitializer(ArrayInitializer node) {
- return copyPosition(node,
- new ArrayInitializer(node.length, visitList(node.elements)));
- }
-
- Node visitArrayElement(ArrayElement node) {
- return copyPosition(node,
- new ArrayElement(node.index, visit(node.value)));
- }
-
- Node visitConditional(Conditional node) {
- return copyPosition(node,
- new Conditional(visit(node.condition),
- visit(node.then),
- visit(node.otherwise)));
- }
-
- Node visitLiteralNull(LiteralNull node) {
- return node;
- }
-
- Node visitLiteralBool(LiteralBool node) {
- return node;
+ Catch parseCatch() {
+ expectCategory(LPAREN);
+ String identifier = lastToken;
+ expectCategory(ALPHA);
+ expectCategory(RPAREN);
+ expectCategory(LBRACE);
+ Block body = parseBlock();
+ return new Catch(new VariableDeclaration(identifier), body);
}
}

Powered by Google App Engine
This is Rietveld 408576698