Chromium Code Reviews| 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); |
| } |
| } |