Support for interpolated declarations in the js parser.

Also use this support to avoid non-constant templates in async_rewrite.

R=floitsch@google.com

Review URL: https://codereview.chromium.org//932053002

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@43837 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
sigurdm@google.com 2015-02-18 13:00:41 +00:00
parent 38512aca1b
commit 6ff10c59a9
6 changed files with 192 additions and 87 deletions

View file

@ -184,11 +184,6 @@ What is not implemented:
array case, we would need some way to know if an ArrayInitializer argument
should be splice or is intended as a single value.
- There are no placeholders in definition contexts:
function #(){}
var # = 1;
*/
const JsBuilder js = const JsBuilder();
@ -766,10 +761,9 @@ class MiniJsParser {
Expression parseFunctionExpression() {
String last = lastToken;
if (acceptCategory(ALPHA)) {
String functionName = last;
return new NamedFunction(new VariableDeclaration(functionName),
parseFun());
if (lastCategory == ALPHA || lastCategory == HASH) {
Declaration name = parseVariableDeclaration();
return new NamedFunction(name, parseFun());
}
return parseFun();
}
@ -1015,27 +1009,25 @@ class MiniJsParser {
}
VariableDeclarationList parseVariableDeclarationList() {
String firstVariable = lastToken;
expectCategory(ALPHA);
Declaration firstVariable = parseVariableDeclaration();
return finishVariableDeclarationList(firstVariable);
}
VariableDeclarationList finishVariableDeclarationList(String firstVariable) {
VariableDeclarationList finishVariableDeclarationList(
Declaration firstVariable) {
var initialization = [];
void declare(String variable) {
void declare(Declaration declaration) {
Expression initializer = null;
if (acceptString("=")) {
initializer = parseAssignment();
}
var declaration = new VariableDeclaration(variable);
initialization.add(new VariableInitialization(declaration, initializer));
}
declare(firstVariable);
while (acceptCategory(COMMA)) {
String variable = lastToken;
expectCategory(ALPHA);
Declaration variable = parseVariableDeclaration();
declare(variable);
}
return new VariableDeclarationList(initialization);
@ -1224,20 +1216,18 @@ class MiniJsParser {
}
if (acceptString('var')) {
String identifier = lastToken;
expectCategory(ALPHA);
Declaration declaration = parseVariableDeclaration();
if (acceptString('in')) {
Expression objectExpression = parseExpression();
expectCategory(RPAREN);
Statement body = parseStatement();
return new ForIn(
new VariableDeclarationList([
new VariableInitialization(
new VariableDeclaration(identifier), null)]),
new VariableInitialization(declaration, null)]),
objectExpression,
body);
}
Expression declarations = finishVariableDeclarationList(identifier);
Expression declarations = finishVariableDeclarationList(declaration);
expectCategory(SEMICOLON);
return finishFor(declarations);
}
@ -1247,11 +1237,24 @@ class MiniJsParser {
return finishFor(init);
}
Declaration parseVariableDeclaration() {
if (acceptCategory(HASH)) {
var nameOrPosition = parseHash();
InterpolatedDeclaration declaration =
new InterpolatedDeclaration(nameOrPosition);
interpolatedValues.add(declaration);
return declaration;
} else {
String token = lastToken;
expectCategory(ALPHA);
return new VariableDeclaration(token);
}
}
Statement parseFunctionDeclaration() {
String name = lastToken;
expectCategory(ALPHA);
Declaration name = parseVariableDeclaration();
Expression fun = parseFun();
return new FunctionDeclaration(new VariableDeclaration(name), fun);
return new FunctionDeclaration(name, fun);
}
Statement parseTry() {
@ -1326,11 +1329,10 @@ class MiniJsParser {
Catch parseCatch() {
expectCategory(LPAREN);
String identifier = lastToken;
expectCategory(ALPHA);
Declaration errorName = parseVariableDeclaration();
expectCategory(RPAREN);
expectCategory(LBRACE);
Block body = parseBlock();
return new Catch(new VariableDeclaration(identifier), body);
return new Catch(errorName, body);
}
}

View file

@ -69,6 +69,7 @@ abstract class NodeVisitor<T> {
T visitInterpolatedParameter(InterpolatedParameter node);
T visitInterpolatedSelector(InterpolatedSelector node);
T visitInterpolatedStatement(InterpolatedStatement node);
T visitInterpolatedDeclaration(InterpolatedDeclaration node);
}
class BaseVisitor<T> implements NodeVisitor<T> {
@ -163,6 +164,9 @@ class BaseVisitor<T> implements NodeVisitor<T> {
=> visitInterpolatedNode(node);
T visitInterpolatedStatement(InterpolatedStatement node)
=> visitInterpolatedNode(node);
T visitInterpolatedDeclaration(InterpolatedDeclaration node) {
return visitInterpolatedNode(node);
}
// Ignore comments by default.
T visitComment(Comment node) => null;
@ -419,7 +423,7 @@ class Try extends Statement {
}
class Catch extends Node {
final VariableDeclaration declaration;
final Declaration declaration;
final Block body;
Catch(this.declaration, this.body);
@ -484,7 +488,7 @@ class Default extends SwitchClause {
}
class FunctionDeclaration extends Statement {
final VariableDeclaration name;
final Declaration name;
final Fun function;
FunctionDeclaration(this.name, this.function);
@ -549,6 +553,10 @@ abstract class Expression extends Node {
Statement toStatement() => new ExpressionStatement(this);
}
abstract class Declaration implements VariableReference {
}
class LiteralExpression extends Expression {
final String template;
final List<Expression> inputs;
@ -620,10 +628,10 @@ class Assignment extends Expression {
class VariableInitialization extends Assignment {
/** [value] may be null. */
VariableInitialization(VariableDeclaration declaration, Expression value)
VariableInitialization(Declaration declaration, Expression value)
: super(declaration, value);
VariableDeclaration get declaration => leftHandSide;
Declaration get declaration => leftHandSide;
accept(NodeVisitor visitor) => visitor.visitVariableInitialization(this);
@ -798,7 +806,7 @@ class VariableUse extends VariableReference {
toString() => 'VariableUse($name)';
}
class VariableDeclaration extends VariableReference {
class VariableDeclaration extends VariableReference implements Declaration {
final bool allowRename;
VariableDeclaration(String name, {this.allowRename: true}) : super(name);
@ -821,7 +829,7 @@ class This extends Parameter {
}
class NamedFunction extends Expression {
final VariableDeclaration name;
final Declaration name;
final Fun function;
NamedFunction(this.name, this.function);
@ -1093,6 +1101,26 @@ class InterpolatedStatement extends Statement with InterpolatedNode {
InterpolatedStatement _clone() => new InterpolatedStatement(nameOrPosition);
}
class InterpolatedDeclaration extends Expression
with InterpolatedNode
implements Declaration {
final nameOrPosition;
InterpolatedDeclaration(this.nameOrPosition);
accept(NodeVisitor visitor) => visitor.visitInterpolatedDeclaration(this);
void visitChildren(NodeVisitor visitor) {}
InterpolatedDeclaration _clone() {
return new InterpolatedDeclaration(nameOrPosition);
}
@override
String get name => throw "No name for the interpolated node";
@override
int get precedenceLevel => PRIMARY;
}
/**
* [RegExpLiteral]s, despite being called "Literal", do not inherit from
* [Literal]. Indeed, regular expressions in JavaScript have a side-effect and

View file

@ -969,6 +969,10 @@ class Printer implements NodeVisitor {
outLn('#${node.nameOrPosition}');
}
visitInterpolatedDeclaration(InterpolatedDeclaration node) {
visitInterpolatedNode(node);
}
void visitComment(Comment node) {
if (shouldCompressOutput) return;
String comment = node.comment.trim();

View file

@ -4,8 +4,6 @@
library rewrite_async;
// TODO(sigurdm): Avoid using variables in templates. It could blow up memory
// use.
// TODO(sigurdm): Move the try/catch expression to a js_helper function.
// That would also simplify the sync* case, where the error can just be thrown.
@ -601,22 +599,25 @@ class AsyncRewriter extends js.NodeVisitor {
}
switch (async) {
case const js.AsyncModifier.async():
String returnValue =
analysis.hasExplicitReturns ? returnValueName : "null";
addStatement(js.js.statement(
"return #thenHelper($returnValue, #successCode, "
"$completerName, null)", {
"thenHelper": asyncHelper,
"successCode": js.number(error_codes.SUCCESS)}));
"return #runtimeHelper(#returnValue, #successCode, "
"#completer, null)", {
"runtimeHelper": asyncHelper,
"successCode": js.number(error_codes.SUCCESS),
"returnValue": analysis.hasExplicitReturns
? returnValueName
: new js.LiteralNull(),
"completer": completerName}));
break;
case const js.AsyncModifier.syncStar():
addStatement(new js.Return(new js.Call(endOfIteration, [])));
break;
case const js.AsyncModifier.asyncStar():
addStatement(js.js.statement(
"return #streamHelper(null, #successCode, $controllerName)", {
"return #streamHelper(null, #successCode, #controller)", {
"streamHelper": streamHelper,
"successCode": js.number(error_codes.SUCCESS)}));
"successCode": js.number(error_codes.SUCCESS),
"controller": controllerName}));
break;
default:
diagnosticListener.internalError(
@ -625,10 +626,11 @@ class AsyncRewriter extends js.NodeVisitor {
if (isAsync || isAsyncStar) {
beginLabel(rethrowLabel);
addStatement(js.js.statement(
"return #thenHelper($currentErrorName, #errorCode, "
"${isAsync ? completerName : controllerName})", {
"return #thenHelper(#currentError, #errorCode, #controller)", {
"thenHelper": isAsync ? asyncHelper : streamHelper,
"errorCode": js.number(error_codes.ERROR)}));
"errorCode": js.number(error_codes.ERROR),
"currentError": currentErrorName,
"controller": isAsync ? completerName : controllerName}));
} else {
assert(isSyncStar);
beginLabel(rethrowLabel);
@ -647,13 +649,16 @@ class AsyncRewriter extends js.NodeVisitor {
js.Statement generateInitializer() {
if (isAsync) {
return js.js.statement(
"return #asyncHelper(null, $bodyName, $completerName, null);", {
"asyncHelper": asyncHelper
"return #asyncHelper(null, #body, #completer, null);", {
"asyncHelper": asyncHelper,
"body": bodyName,
"completer": completerName,
});
} else if (isAsyncStar) {
return js.js.statement(
"return #streamOfController($controllerName);", {
"streamOfController": streamOfController
"return #streamOfController(#controller);", {
"streamOfController": streamOfController,
"controller": controllerName,
});
} else {
throw diagnosticListener.internalError(
@ -828,15 +833,21 @@ class AsyncRewriter extends js.NodeVisitor {
js.Statement helperBody =
new js.Switch(new js.VariableUse(gotoName), clauses);
if (hasJumpThoughOuterLabel) {
helperBody = js.js.statement("$outerLabelName: #", [helperBody]);
helperBody = new js.LabeledStatement(outerLabelName, helperBody);
}
helperBody = js.js.statement("""
try {
#body
} catch ($errorName){
$currentErrorName = $errorName;
$gotoName = $handlerName;
}""", {"body": helperBody});
} catch (#error){
#currentError = #error;
#goto = #handler;
}""", {
"body": helperBody,
"goto": gotoName,
"error": errorName,
"currentError": currentErrorName,
"handler": handlerName,
});
List<js.VariableInitialization> inits = <js.VariableInitialization>[];
js.VariableInitialization makeInit(String name, js.Expression initValue) {
@ -878,10 +889,10 @@ class AsyncRewriter extends js.NodeVisitor {
return js.js("""
function (#params) {
if (#needsThis)
var $selfName = this;
var #self = this;
return new #newIterable(function () {
#varDecl;
return function $bodyName() {
return function #body() {
while (true)
#helperBody;
};
@ -892,27 +903,29 @@ class AsyncRewriter extends js.NodeVisitor {
"needsThis": analysis.hasThis,
"helperBody": helperBody,
"varDecl": varDecl,
"newIterable": newIterable
"newIterable": newIterable,
"body": bodyName,
"self": selfName,
});
}
return js.js("""
function (#params) {
#varDecl;
function $bodyName($errorCodeName, $resultName) {
function #bodyName(#errorCode, #result) {
if (#hasYield)
switch ($errorCodeName) {
case #streamWasCanceled:
$nextName = $nextWhenCanceledName;
$gotoName = $nextName.pop();
switch (#errorCode) {
case #STREAM_WAS_CANCELED:
#next = #nextWhenCanceled;
#goto = #next.pop();
break;
case #errorCode:
$currentErrorName = $resultName;
$gotoName = $handlerName;
case #ERROR:
#currentError = #result;
#goto = #handler;
}
else
if ($errorCodeName == #errorCode) {
$currentErrorName = $resultName;
$gotoName = $handlerName;
if (#errorCode == #ERROR) {
#currentError = #result;
#goto = #handler;
}
while (true)
#helperBody;
@ -921,11 +934,19 @@ class AsyncRewriter extends js.NodeVisitor {
}""", {
"params": node.params,
"varDecl": varDecl,
"streamWasCanceled": js.number(error_codes.STREAM_WAS_CANCELED),
"errorCode": js.number(error_codes.ERROR),
"STREAM_WAS_CANCELED": js.number(error_codes.STREAM_WAS_CANCELED),
"ERROR": js.number(error_codes.ERROR),
"hasYield": analysis.hasYield,
"helperBody": helperBody,
"init": generateInitializer()
"init": generateInitializer(),
"bodyName": bodyName,
"currentError": currentErrorName,
"goto": gotoName,
"handler": handlerName,
"next": nextName,
"nextWhenCanceled": nextWhenCanceledName,
"errorCode": errorCodeName,
"result": resultName,
});
}
@ -984,11 +1005,13 @@ class AsyncRewriter extends js.NodeVisitor {
addStatement(setGotoVariable(afterAwait));
addStatement(js.js.statement("""
return #asyncHelper(#value,
$bodyName,
${isAsync ? completerName : controllerName});
#body,
#controller);
""", {
"asyncHelper": isAsync ? asyncHelper : streamHelper,
"value": value,
"body": bodyName,
"controller": isAsync ? completerName : controllerName,
}));
}, store: false);
beginLabel(afterAwait);
@ -1165,7 +1188,7 @@ class AsyncRewriter extends js.NodeVisitor {
hasJumpThroughFinally = true;
js.Expression jsJumpStack = new js.ArrayInitializer(
jumpStack.map((int label) => js.number(label)).toList());
addStatement(js.js.statement("$nextName = #", [jsJumpStack]));
addStatement(js.js.statement("# = #", [nextName, jsJumpStack]));
}
addGoto(firstTarget);
}
@ -1337,6 +1360,11 @@ class AsyncRewriter extends js.NodeVisitor {
return unsupported(node);
}
@override
visitInterpolatedDeclaration(js.InterpolatedDeclaration node) {
return unsupported(node);
}
@override
visitInterpolatedLiteral(js.InterpolatedLiteral node) => unsupported(node);
@ -1485,7 +1513,7 @@ class AsyncRewriter extends js.NodeVisitor {
js.Node target = analysis.targets[node];
if (node.value != null) {
withExpression(node.value, (js.Expression value) {
addStatement(js.js.statement("$returnValueName = #", [value]));
addStatement(js.js.statement("# = #", [returnValueName, value]));
}, store: false);
}
translateJump(target, exitLabel);
@ -1666,7 +1694,8 @@ class AsyncRewriter extends js.NodeVisitor {
} else {
// The handler is reset as the first thing in the finally block.
addStatement(
js.js.statement("$nextName = [#];", [js.number(afterFinallyLabel)]));
js.js.statement("# = [#];",
[nextName, js.number(afterFinallyLabel)]));
addGoto(finallyLabel);
}
@ -1692,8 +1721,8 @@ class AsyncRewriter extends js.NodeVisitor {
if (node.finallyPart != null) {
// The error has been caught, so after the finally, continue after the
// try.
addStatement(js.js.statement("$nextName = [#];",
[js.number(afterFinallyLabel)]));
addStatement(js.js.statement("# = [#];",
[nextName, js.number(afterFinallyLabel)]));
addGoto(finallyLabel);
} else {
addGoto(afterFinallyLabel);
@ -1713,8 +1742,8 @@ class AsyncRewriter extends js.NodeVisitor {
// [enclosingFinallies] can be empty if there is no surrounding finally
// blocks. Then [nextLabel] will be [rethrowLabel].
addStatement(
js.js.statement("$nextName = #;", new js.ArrayInitializer(
enclosingFinallies.map(js.number).toList())));
js.js.statement("# = #;", [nextName, new js.ArrayInitializer(
enclosingFinallies.map(js.number).toList())]));
}
if (node.finallyPart == null) {
// The finally-block belonging to [node] will be visited because of
@ -1729,7 +1758,7 @@ class AsyncRewriter extends js.NodeVisitor {
setErrorHandler();
visitStatement(node.finallyPart);
addStatement(new js.Comment("// goto the next finally handler"));
addStatement(js.js.statement("$gotoName = $nextName.pop();"));
addStatement(js.js.statement("# = #.pop();", [gotoName, nextName]));
addBreak();
}
beginLabel(afterFinallyLabel);
@ -1832,15 +1861,17 @@ class AsyncRewriter extends js.NodeVisitor {
enclosingFinallyLabels.addAll(jumpTargets
.where((js.Node node) => finallyLabels[node] != null)
.map((js.Block node) => finallyLabels[node]));
addStatement(js.js.statement("$nextWhenCanceledName = #",
[new js.ArrayInitializer(enclosingFinallyLabels.map(js.number)
.toList())]));
addStatement(js.js.statement("# = #;",
[nextWhenCanceledName, new js.ArrayInitializer(
enclosingFinallyLabels.map(js.number).toList())]));
addStatement(js.js.statement("""
return #streamHelper(#yieldExpression(#expression),
$bodyName, $controllerName);""", {
return #streamHelper(#yieldExpression(#expression), #body,
#controller);""", {
"streamHelper": streamHelper,
"yieldExpression": node.hasStar ? yieldStarExpression : yieldExpression,
"expression": expression,
"body": bodyName,
"controller": controllerName,
}));
}
@ -2088,6 +2119,11 @@ class PreTranslationAnalysis extends js.NodeVisitor<bool> {
return unsupported(node);
}
@override
bool visitInterpolatedDeclaration(js.InterpolatedDeclaration node) {
return unsupported(node);
}
@override
bool visitInterpolatedLiteral(js.InterpolatedLiteral node) {
return unsupported(node);

View file

@ -185,6 +185,11 @@ class InstantiatorGeneratorVisitor implements NodeVisitor<Instantiator> {
return new VariableUse(value);
}
static Expression convertStringToVariableDeclaration(String value) {
assert(identiferRE.hasMatch(value));
return new VariableDeclaration(value);
}
Instantiator visitInterpolatedExpression(InterpolatedExpression node) {
var nameOrPosition = node.nameOrPosition;
return (arguments) {
@ -195,6 +200,16 @@ class InstantiatorGeneratorVisitor implements NodeVisitor<Instantiator> {
};
}
Instantiator visitInterpolatedDeclaration(InterpolatedDeclaration node) {
var nameOrPosition = node.nameOrPosition;
return (arguments) {
var value = arguments[nameOrPosition];
if (value is Declaration) return value;
if (value is String) return convertStringToVariableDeclaration(value);
error('Interpolated value #$nameOrPosition is not a declaration: $value');
};
}
Instantiator visitSplayableExpression(Node node) {
if (node is InterpolatedExpression) {
var nameOrPosition = node.nameOrPosition;

View file

@ -325,5 +325,25 @@ switch (true) {
testStatement('label: while (a) { label2: break label;}', [],
'label:\n while (a) {\n label2:\n break label;\n }'),
testStatement('var # = 3', ['x'], 'var x = 3;'),
testStatement('var # = 3',
[new jsAst.VariableDeclaration('x')],
'var x = 3;'),
testStatement('var # = 3, # = #',
['x', 'y', js.number(2)],
'var x = 3, y = 2;'),
testStatement('var #a = 3, #b = #c',
{"a": 'x', "b": 'y', "c": js.number(2)},
'var x = 3, y = 2;'),
testStatement('function #() {}', ['x'], 'function x() {\n}'),
testStatement('function #() {}',
[new jsAst.VariableDeclaration('x')],
'function x() {\n}'),
testStatement('try {} catch (#) {}', ['x'], 'try {\n} catch (x) {\n}'),
testStatement('try {} catch (#a) {}', {"a": 'x'}, 'try {\n} catch (x) {\n}'),
testStatement('try {} catch (#a) {}',
{"a": new jsAst.VariableDeclaration('x')},
'try {\n} catch (x) {\n}'),
]));
}