| // 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. |
| |
| part of js; |
| |
| abstract class NodeVisitor<T> { |
| T visitProgram(Program node); |
| |
| T visitBlock(Block node); |
| T visitExpressionStatement(ExpressionStatement node); |
| T visitEmptyStatement(EmptyStatement node); |
| T visitIf(If node); |
| T visitFor(For node); |
| T visitForIn(ForIn node); |
| T visitWhile(While node); |
| T visitDo(Do node); |
| T visitContinue(Continue node); |
| T visitBreak(Break node); |
| T visitReturn(Return node); |
| T visitThrow(Throw node); |
| T visitTry(Try node); |
| T visitCatch(Catch node); |
| T visitSwitch(Switch node); |
| T visitCase(Case node); |
| T visitDefault(Default node); |
| T visitFunctionDeclaration(FunctionDeclaration node); |
| T visitLabeledStatement(LabeledStatement node); |
| T visitLiteralStatement(LiteralStatement node); |
| |
| T visitLiteralExpression(LiteralExpression node); |
| T visitVariableDeclarationList(VariableDeclarationList node); |
| T visitSequence(Sequence node); |
| T visitAssignment(Assignment node); |
| T visitVariableInitialization(VariableInitialization node); |
| T visitConditional(Conditional cond); |
| T visitNew(New node); |
| T visitCall(Call node); |
| T visitBinary(Binary node); |
| T visitPrefix(Prefix node); |
| T visitPostfix(Postfix node); |
| |
| T visitVariableUse(VariableUse node); |
| T visitThis(This node); |
| T visitVariableDeclaration(VariableDeclaration node); |
| T visitParameter(Parameter node); |
| T visitAccess(PropertyAccess node); |
| |
| T visitNamedFunction(NamedFunction node); |
| T visitFun(Fun node); |
| |
| T visitLiteralBool(LiteralBool node); |
| T visitLiteralString(LiteralString node); |
| T visitLiteralNumber(LiteralNumber node); |
| T visitLiteralNull(LiteralNull node); |
| |
| T visitArrayInitializer(ArrayInitializer node); |
| T visitArrayElement(ArrayElement node); |
| T visitObjectInitializer(ObjectInitializer node); |
| T visitProperty(Property node); |
| T visitRegExpLiteral(RegExpLiteral node); |
| |
| T visitComment(Comment node); |
| } |
| |
| class BaseVisitor<T> implements NodeVisitor<T> { |
| T visitNode(Node node) { |
| node.visitChildren(this); |
| return null; |
| } |
| |
| T visitProgram(Program node) => visitNode(node); |
| |
| T visitStatement(Statement node) => visitNode(node); |
| T visitLoop(Loop node) => visitStatement(node); |
| T visitJump(Statement node) => visitStatement(node); |
| |
| T visitBlock(Block node) => visitStatement(node); |
| T visitExpressionStatement(ExpressionStatement node) |
| => visitStatement(node); |
| T visitEmptyStatement(EmptyStatement node) => visitStatement(node); |
| T visitIf(If node) => visitStatement(node); |
| T visitFor(For node) => visitLoop(node); |
| T visitForIn(ForIn node) => visitLoop(node); |
| T visitWhile(While node) => visitLoop(node); |
| T visitDo(Do node) => visitLoop(node); |
| T visitContinue(Continue node) => visitJump(node); |
| T visitBreak(Break node) => visitJump(node); |
| T visitReturn(Return node) => visitJump(node); |
| T visitThrow(Throw node) => visitJump(node); |
| T visitTry(Try node) => visitStatement(node); |
| T visitSwitch(Switch node) => visitStatement(node); |
| T visitFunctionDeclaration(FunctionDeclaration node) |
| => visitStatement(node); |
| T visitLabeledStatement(LabeledStatement node) => visitStatement(node); |
| T visitLiteralStatement(LiteralStatement node) => visitStatement(node); |
| |
| T visitCatch(Catch node) => visitNode(node); |
| T visitCase(Case node) => visitNode(node); |
| T visitDefault(Default node) => visitNode(node); |
| |
| T visitExpression(Expression node) => visitNode(node); |
| T visitVariableReference(VariableReference node) => visitExpression(node); |
| |
| T visitLiteralExpression(LiteralExpression node) => visitExpression(node); |
| T visitVariableDeclarationList(VariableDeclarationList node) |
| => visitExpression(node); |
| T visitSequence(Sequence node) => visitExpression(node); |
| T visitAssignment(Assignment node) => visitExpression(node); |
| T visitVariableInitialization(VariableInitialization node) { |
| if (node.value != null) { |
| visitAssignment(node); |
| } else { |
| visitExpression(node); |
| } |
| } |
| T visitConditional(Conditional node) => visitExpression(node); |
| T visitNew(New node) => visitExpression(node); |
| T visitCall(Call node) => visitExpression(node); |
| T visitBinary(Binary node) => visitCall(node); |
| T visitPrefix(Prefix node) => visitCall(node); |
| T visitPostfix(Postfix node) => visitCall(node); |
| T visitAccess(PropertyAccess node) => visitExpression(node); |
| |
| T visitVariableUse(VariableUse node) => visitVariableReference(node); |
| T visitVariableDeclaration(VariableDeclaration node) |
| => visitVariableReference(node); |
| T visitParameter(Parameter node) => visitVariableDeclaration(node); |
| T visitThis(This node) => visitParameter(node); |
| |
| T visitNamedFunction(NamedFunction node) => visitExpression(node); |
| T visitFun(Fun node) => visitExpression(node); |
| |
| T visitLiteral(Literal node) => visitExpression(node); |
| |
| T visitLiteralBool(LiteralBool node) => visitLiteral(node); |
| T visitLiteralString(LiteralString node) => visitLiteral(node); |
| T visitLiteralNumber(LiteralNumber node) => visitLiteral(node); |
| T visitLiteralNull(LiteralNull node) => visitLiteral(node); |
| |
| T visitArrayInitializer(ArrayInitializer node) => visitExpression(node); |
| T visitArrayElement(ArrayElement node) => visitNode(node); |
| T visitObjectInitializer(ObjectInitializer node) => visitExpression(node); |
| T visitProperty(Property node) => visitNode(node); |
| T visitRegExpLiteral(RegExpLiteral node) => visitExpression(node); |
| |
| // Ignore comments by default. |
| T visitComment(Comment node) {} |
| } |
| |
| abstract class Node { |
| var sourcePosition; |
| var endSourcePosition; |
| |
| accept(NodeVisitor visitor); |
| void visitChildren(NodeVisitor visitor); |
| |
| VariableUse asVariableUse() => null; |
| |
| Statement toStatement() { |
| throw new UnsupportedError('toStatement'); |
| } |
| } |
| |
| class Program extends Node { |
| final List<Statement> body; |
| Program(this.body); |
| |
| accept(NodeVisitor visitor) => visitor.visitProgram(this); |
| void visitChildren(NodeVisitor visitor) { |
| for (Statement statement in body) statement.accept(visitor); |
| } |
| } |
| |
| abstract class Statement extends Node { |
| Statement toStatement() => this; |
| } |
| |
| class Block extends Statement { |
| final List<Statement> statements; |
| Block(this.statements); |
| Block.empty() : this.statements = <Statement>[]; |
| |
| accept(NodeVisitor visitor) => visitor.visitBlock(this); |
| void visitChildren(NodeVisitor visitor) { |
| for (Statement statement in statements) statement.accept(visitor); |
| } |
| } |
| |
| class ExpressionStatement extends Statement { |
| final Expression expression; |
| ExpressionStatement(this.expression); |
| |
| accept(NodeVisitor visitor) => visitor.visitExpressionStatement(this); |
| void visitChildren(NodeVisitor visitor) { expression.accept(visitor); } |
| } |
| |
| class EmptyStatement extends Statement { |
| EmptyStatement(); |
| |
| accept(NodeVisitor visitor) => visitor.visitEmptyStatement(this); |
| void visitChildren(NodeVisitor visitor) {} |
| } |
| |
| class If extends Statement { |
| final Expression condition; |
| final Node then; |
| final Node otherwise; |
| |
| If(this.condition, this.then, this.otherwise); |
| If.noElse(this.condition, this.then) : this.otherwise = new EmptyStatement(); |
| |
| bool get hasElse => otherwise is !EmptyStatement; |
| |
| accept(NodeVisitor visitor) => visitor.visitIf(this); |
| |
| void visitChildren(NodeVisitor visitor) { |
| condition.accept(visitor); |
| then.accept(visitor); |
| otherwise.accept(visitor); |
| } |
| } |
| |
| abstract class Loop extends Statement { |
| final Statement body; |
| Loop(this.body); |
| } |
| |
| class For extends Loop { |
| final Expression init; |
| final Expression condition; |
| final Expression update; |
| |
| For(this.init, this.condition, this.update, Statement body) : super(body); |
| |
| accept(NodeVisitor visitor) => visitor.visitFor(this); |
| |
| void visitChildren(NodeVisitor visitor) { |
| if (init != null) init.accept(visitor); |
| if (condition != null) condition.accept(visitor); |
| if (update != null) update.accept(visitor); |
| body.accept(visitor); |
| } |
| } |
| |
| class ForIn extends Loop { |
| // Note that [VariableDeclarationList] is a subclass of [Expression]. |
| // Therefore we can type the leftHandSide as [Expression]. |
| final Expression leftHandSide; |
| final Expression object; |
| |
| ForIn(this.leftHandSide, this.object, Statement body) : super(body); |
| |
| accept(NodeVisitor visitor) => visitor.visitForIn(this); |
| |
| void visitChildren(NodeVisitor visitor) { |
| leftHandSide.accept(visitor); |
| object.accept(visitor); |
| body.accept(visitor); |
| } |
| } |
| |
| class While extends Loop { |
| final Node condition; |
| |
| While(this.condition, Statement body) : super(body); |
| |
| accept(NodeVisitor visitor) => visitor.visitWhile(this); |
| |
| void visitChildren(NodeVisitor visitor) { |
| condition.accept(visitor); |
| body.accept(visitor); |
| } |
| } |
| |
| class Do extends Loop { |
| final Expression condition; |
| |
| Do(Statement body, this.condition) : super(body); |
| |
| accept(NodeVisitor visitor) => visitor.visitDo(this); |
| |
| void visitChildren(NodeVisitor visitor) { |
| body.accept(visitor); |
| condition.accept(visitor); |
| } |
| } |
| |
| class Continue extends Statement { |
| final String targetLabel; // Can be null. |
| |
| Continue(this.targetLabel); |
| |
| accept(NodeVisitor visitor) => visitor.visitContinue(this); |
| void visitChildren(NodeVisitor visitor) {} |
| } |
| |
| class Break extends Statement { |
| final String targetLabel; // Can be null. |
| |
| Break(this.targetLabel); |
| |
| accept(NodeVisitor visitor) => visitor.visitBreak(this); |
| void visitChildren(NodeVisitor visitor) {} |
| } |
| |
| class Return extends Statement { |
| final Expression value; // Can be null. |
| |
| Return([this.value = null]); |
| |
| accept(NodeVisitor visitor) => visitor.visitReturn(this); |
| |
| void visitChildren(NodeVisitor visitor) { |
| if (value != null) value.accept(visitor); |
| } |
| } |
| |
| class Throw extends Statement { |
| final Expression expression; |
| |
| Throw(this.expression); |
| |
| accept(NodeVisitor visitor) => visitor.visitThrow(this); |
| |
| void visitChildren(NodeVisitor visitor) { |
| expression.accept(visitor); |
| } |
| } |
| |
| class Try extends Statement { |
| final Block body; |
| final Catch catchPart; // Can be null if [finallyPart] is non-null. |
| final Block finallyPart; // Can be null if [catchPart] is non-null. |
| |
| Try(this.body, this.catchPart, this.finallyPart) { |
| assert(catchPart != null || finallyPart != null); |
| } |
| |
| accept(NodeVisitor visitor) => visitor.visitTry(this); |
| |
| void visitChildren(NodeVisitor visitor) { |
| body.accept(visitor); |
| if (catchPart != null) catchPart.accept(visitor); |
| if (finallyPart != null) finallyPart.accept(visitor); |
| } |
| } |
| |
| class Catch extends Node { |
| final VariableDeclaration declaration; |
| final Block body; |
| |
| Catch(this.declaration, this.body); |
| |
| accept(NodeVisitor visitor) => visitor.visitCatch(this); |
| |
| void visitChildren(NodeVisitor visitor) { |
| declaration.accept(visitor); |
| body.accept(visitor); |
| } |
| } |
| |
| class Switch extends Statement { |
| final Expression key; |
| final List<SwitchClause> cases; |
| |
| Switch(this.key, this.cases); |
| |
| accept(NodeVisitor visitor) => visitor.visitSwitch(this); |
| |
| void visitChildren(NodeVisitor visitor) { |
| key.accept(visitor); |
| for (SwitchClause clause in cases) clause.accept(visitor); |
| } |
| } |
| |
| abstract class SwitchClause extends Node { |
| final Block body; |
| |
| SwitchClause(this.body); |
| } |
| |
| class Case extends SwitchClause { |
| final Expression expression; |
| |
| Case(this.expression, Block body) : super(body); |
| |
| accept(NodeVisitor visitor) => visitor.visitCase(this); |
| |
| void visitChildren(NodeVisitor visitor) { |
| expression.accept(visitor); |
| body.accept(visitor); |
| } |
| } |
| |
| class Default extends SwitchClause { |
| Default(Block body) : super(body); |
| |
| accept(NodeVisitor visitor) => visitor.visitDefault(this); |
| |
| void visitChildren(NodeVisitor visitor) { |
| body.accept(visitor); |
| } |
| } |
| |
| class FunctionDeclaration extends Statement { |
| final VariableDeclaration name; |
| final Fun function; |
| |
| FunctionDeclaration(this.name, this.function); |
| |
| accept(NodeVisitor visitor) => visitor.visitFunctionDeclaration(this); |
| |
| void visitChildren(NodeVisitor visitor) { |
| name.accept(visitor); |
| function.accept(visitor); |
| } |
| } |
| |
| class LabeledStatement extends Statement { |
| final String label; |
| final Statement body; |
| |
| LabeledStatement(this.label, this.body); |
| |
| accept(NodeVisitor visitor) => visitor.visitLabeledStatement(this); |
| |
| void visitChildren(NodeVisitor visitor) { |
| body.accept(visitor); |
| } |
| } |
| |
| class LiteralStatement extends Statement { |
| final String code; |
| |
| LiteralStatement(this.code); |
| |
| accept(NodeVisitor visitor) => visitor.visitLiteralStatement(this); |
| void visitChildren(NodeVisitor visitor) { } |
| } |
| |
| abstract class Expression extends Node { |
| int get precedenceLevel; |
| |
| Call callWith(List<Expression> arguments) => new Call(this, arguments); |
| |
| New newWith(List<Expression> arguments) => new New(this, arguments); |
| |
| PropertyAccess operator [](expression) { |
| if (expression is Expression) { |
| return new PropertyAccess(this, expression); |
| } else if (expression is int) { |
| return new PropertyAccess.indexed(this, expression); |
| } else if (expression is String) { |
| return new PropertyAccess.field(this, expression); |
| } else { |
| throw new ArgumentError('Expected an int, String, or Expression'); |
| } |
| } |
| |
| Statement toStatement() => new ExpressionStatement(this); |
| |
| Call call([expression]) { |
| List<Expression> arguments; |
| if (expression == null) { |
| arguments = <Expression>[]; |
| } else if (expression is List) { |
| arguments = expression.map(js.toExpression).toList(); |
| } else { |
| arguments = <Expression>[js.toExpression(expression)]; |
| } |
| return callWith(arguments); |
| } |
| |
| Expression equals(expression) => binary('==', expression); |
| |
| Expression strictEquals(expression) => binary('===', expression); |
| |
| Expression notEquals(expression) => binary('!=', expression); |
| |
| Expression operator +(expression) => binary('+', expression); |
| |
| Expression operator -(expression) => binary('-', expression); |
| |
| Expression operator &(expression) => binary('&', expression); |
| |
| Expression operator <(expression) => binary('<', expression); |
| |
| Expression operator >(expression) => binary('>', expression); |
| |
| Expression operator >=(expression) => binary('>=', expression); |
| |
| Expression binary(String operator, expression) { |
| return new Binary(operator, this, js.toExpression(expression)); |
| } |
| |
| Expression assign(expression) { |
| return new Assignment(this, js.toExpression(expression)); |
| } |
| |
| Expression update(String operator, expression) { |
| return new Assignment.compound(this, operator, js.toExpression(expression)); |
| } |
| |
| Expression get plusPlus => new Postfix('++', this); |
| |
| Prefix get typeof => new Prefix('typeof', this); |
| |
| Prefix get not => new Prefix('!', this); |
| } |
| |
| class LiteralExpression extends Expression { |
| final String template; |
| final List<Expression> inputs; |
| |
| LiteralExpression(this.template) : inputs = const []; |
| LiteralExpression.withData(this.template, this.inputs); |
| |
| accept(NodeVisitor visitor) => visitor.visitLiteralExpression(this); |
| |
| void visitChildren(NodeVisitor visitor) { |
| for (Expression expr in inputs) expr.accept(visitor); |
| } |
| |
| // Code that uses JS must take care of operator precedences, and |
| // put parenthesis if needed. |
| int get precedenceLevel => PRIMARY; |
| } |
| |
| /** |
| * [VariableDeclarationList] is a subclass of [Expression] to simplify the |
| * AST. |
| */ |
| class VariableDeclarationList extends Expression { |
| final List<VariableInitialization> declarations; |
| |
| VariableDeclarationList(this.declarations); |
| |
| accept(NodeVisitor visitor) => visitor.visitVariableDeclarationList(this); |
| |
| void visitChildren(NodeVisitor visitor) { |
| for (VariableInitialization declaration in declarations) { |
| declaration.accept(visitor); |
| } |
| } |
| |
| int get precedenceLevel => EXPRESSION; |
| } |
| |
| class Sequence extends Expression { |
| final List<Expression> expressions; |
| |
| Sequence(this.expressions); |
| |
| accept(NodeVisitor visitor) => visitor.visitSequence(this); |
| |
| void visitChildren(NodeVisitor visitor) { |
| for (Expression expr in expressions) expr.accept(visitor); |
| } |
| |
| int get precedenceLevel => EXPRESSION; |
| } |
| |
| class Assignment extends Expression { |
| final Expression leftHandSide; |
| // Null, if the assignment is not compound. |
| final VariableReference compoundTarget; |
| final Expression value; // May be null, for [VariableInitialization]s. |
| |
| Assignment(this.leftHandSide, this.value) : compoundTarget = null; |
| Assignment.compound(this.leftHandSide, String op, this.value) |
| : compoundTarget = new VariableUse(op); |
| |
| int get precedenceLevel => ASSIGNMENT; |
| |
| bool get isCompound => compoundTarget != null; |
| String get op => compoundTarget == null ? null : compoundTarget.name; |
| |
| accept(NodeVisitor visitor) => visitor.visitAssignment(this); |
| |
| void visitChildren(NodeVisitor visitor) { |
| leftHandSide.accept(visitor); |
| if (compoundTarget != null) compoundTarget.accept(visitor); |
| if (value != null) value.accept(visitor); |
| } |
| } |
| |
| class VariableInitialization extends Assignment { |
| /** [value] may be null. */ |
| VariableInitialization(VariableDeclaration declaration, Expression value) |
| : super(declaration, value); |
| |
| VariableDeclaration get declaration => leftHandSide; |
| |
| accept(NodeVisitor visitor) => visitor.visitVariableInitialization(this); |
| } |
| |
| class Conditional extends Expression { |
| final Expression condition; |
| final Expression then; |
| final Expression otherwise; |
| |
| Conditional(this.condition, this.then, this.otherwise); |
| |
| accept(NodeVisitor visitor) => visitor.visitConditional(this); |
| |
| void visitChildren(NodeVisitor visitor) { |
| condition.accept(visitor); |
| then.accept(visitor); |
| otherwise.accept(visitor); |
| } |
| |
| int get precedenceLevel => ASSIGNMENT; |
| } |
| |
| class Call extends Expression { |
| Expression target; |
| List<Expression> arguments; |
| |
| Call(this.target, this.arguments); |
| |
| accept(NodeVisitor visitor) => visitor.visitCall(this); |
| |
| void visitChildren(NodeVisitor visitor) { |
| target.accept(visitor); |
| for (Expression arg in arguments) arg.accept(visitor); |
| } |
| |
| int get precedenceLevel => CALL; |
| } |
| |
| class New extends Call { |
| New(Expression cls, List<Expression> arguments) : super(cls, arguments); |
| |
| accept(NodeVisitor visitor) => visitor.visitNew(this); |
| } |
| |
| class Binary extends Call { |
| Binary(String op, Expression left, Expression right) |
| : super(new VariableUse(op), <Expression>[left, right]); |
| |
| String get op { |
| VariableUse use = target; |
| return use.name; |
| } |
| |
| Expression get left => arguments[0]; |
| Expression get right => arguments[1]; |
| |
| accept(NodeVisitor visitor) => visitor.visitBinary(this); |
| |
| int get precedenceLevel { |
| // TODO(floitsch): switch to constant map. |
| switch (op) { |
| case "*": |
| case "/": |
| case "%": |
| return MULTIPLICATIVE; |
| case "+": |
| case "-": |
| return ADDITIVE; |
| case "<<": |
| case ">>": |
| case ">>>": |
| return SHIFT; |
| case "<": |
| case ">": |
| case "<=": |
| case ">=": |
| case "instanceof": |
| case "in": |
| return RELATIONAL; |
| case "==": |
| case "===": |
| case "!=": |
| case "!==": |
| return EQUALITY; |
| case "&": |
| return BIT_AND; |
| case "^": |
| return BIT_XOR; |
| case "|": |
| return BIT_OR; |
| case "&&": |
| return LOGICAL_AND; |
| case "||": |
| return LOGICAL_OR; |
| default: |
| throw new leg.CompilerCancelledException( |
| "Internal Error: Unhandled binary operator: $op"); |
| } |
| } |
| } |
| |
| class Prefix extends Call { |
| Prefix(String op, Expression arg) |
| : super(new VariableUse(op), <Expression>[arg]); |
| |
| String get op => (target as VariableUse).name; |
| Expression get argument => arguments[0]; |
| |
| accept(NodeVisitor visitor) => visitor.visitPrefix(this); |
| |
| int get precedenceLevel => UNARY; |
| } |
| |
| class Postfix extends Call { |
| Postfix(String op, Expression arg) |
| : super(new VariableUse(op), <Expression>[arg]); |
| |
| String get op => (target as VariableUse).name; |
| Expression get argument => arguments[0]; |
| |
| accept(NodeVisitor visitor) => visitor.visitPostfix(this); |
| |
| int get precedenceLevel => UNARY; |
| } |
| |
| abstract class VariableReference extends Expression { |
| final String name; |
| |
| // We treat operators as if they were special functions. They can thus be |
| // referenced like other variables. |
| VariableReference(this.name); |
| |
| accept(NodeVisitor visitor); |
| int get precedenceLevel => PRIMARY; |
| void visitChildren(NodeVisitor visitor) {} |
| } |
| |
| class VariableUse extends VariableReference { |
| VariableUse(String name) : super(name); |
| |
| accept(NodeVisitor visitor) => visitor.visitVariableUse(this); |
| |
| VariableUse asVariableUse() => this; |
| } |
| |
| class VariableDeclaration extends VariableReference { |
| VariableDeclaration(String name) : super(name); |
| |
| accept(NodeVisitor visitor) => visitor.visitVariableDeclaration(this); |
| } |
| |
| class Parameter extends VariableDeclaration { |
| Parameter(String id) : super(id); |
| |
| accept(NodeVisitor visitor) => visitor.visitParameter(this); |
| } |
| |
| class This extends Parameter { |
| This() : super("this"); |
| |
| accept(NodeVisitor visitor) => visitor.visitThis(this); |
| } |
| |
| class NamedFunction extends Expression { |
| final VariableDeclaration name; |
| final Fun function; |
| |
| NamedFunction(this.name, this.function); |
| |
| accept(NodeVisitor visitor) => visitor.visitNamedFunction(this); |
| |
| void visitChildren(NodeVisitor visitor) { |
| name.accept(visitor); |
| function.accept(visitor); |
| } |
| |
| int get precedenceLevel => CALL; |
| } |
| |
| class Fun extends Expression { |
| final List<Parameter> params; |
| final Block body; |
| |
| Fun(this.params, this.body); |
| |
| accept(NodeVisitor visitor) => visitor.visitFun(this); |
| |
| void visitChildren(NodeVisitor visitor) { |
| for (Parameter param in params) param.accept(visitor); |
| body.accept(visitor); |
| } |
| |
| int get precedenceLevel => CALL; |
| } |
| |
| class PropertyAccess extends Expression { |
| final Expression receiver; |
| final Expression selector; |
| |
| PropertyAccess(this.receiver, this.selector); |
| PropertyAccess.field(this.receiver, String fieldName) |
| : selector = new LiteralString("'$fieldName'"); |
| PropertyAccess.indexed(this.receiver, int index) |
| : selector = new LiteralNumber('$index'); |
| |
| accept(NodeVisitor visitor) => visitor.visitAccess(this); |
| |
| void visitChildren(NodeVisitor visitor) { |
| receiver.accept(visitor); |
| selector.accept(visitor); |
| } |
| |
| int get precedenceLevel => CALL; |
| } |
| |
| abstract class Literal extends Expression { |
| void visitChildren(NodeVisitor visitor) {} |
| |
| int get precedenceLevel => PRIMARY; |
| } |
| |
| class LiteralBool extends Literal { |
| final bool value; |
| |
| LiteralBool(this.value); |
| |
| accept(NodeVisitor visitor) => visitor.visitLiteralBool(this); |
| // [visitChildren] inherited from [Literal]. |
| } |
| |
| class LiteralNull extends Literal { |
| LiteralNull(); |
| |
| accept(NodeVisitor visitor) => visitor.visitLiteralNull(this); |
| } |
| |
| class LiteralString extends Literal { |
| final String value; |
| |
| LiteralString(this.value); |
| |
| accept(NodeVisitor visitor) => visitor.visitLiteralString(this); |
| } |
| |
| class LiteralNumber extends Literal { |
| final String value; |
| |
| LiteralNumber(this.value); |
| |
| accept(NodeVisitor visitor) => visitor.visitLiteralNumber(this); |
| } |
| |
| class ArrayInitializer extends Expression { |
| final int length; |
| // We represent the array as sparse list of elements. Each element knows its |
| // position in the array. |
| final List<ArrayElement> elements; |
| |
| ArrayInitializer(this.length, this.elements); |
| |
| factory ArrayInitializer.from(Iterable<Expression> expressions) => |
| new ArrayInitializer(expressions.length, _convert(expressions)); |
| |
| accept(NodeVisitor visitor) => visitor.visitArrayInitializer(this); |
| |
| void visitChildren(NodeVisitor visitor) { |
| for (ArrayElement element in elements) element.accept(visitor); |
| } |
| |
| int get precedenceLevel => PRIMARY; |
| |
| static List<ArrayElement> _convert(Iterable<Expression> expressions) { |
| int index = 0; |
| return expressions.map( |
| (expression) => new ArrayElement(index++, expression)) |
| .toList(); |
| } |
| } |
| |
| /** |
| * An expression inside an [ArrayInitializer]. An [ArrayElement] knows |
| * its position in the containing [ArrayInitializer]. |
| */ |
| class ArrayElement extends Node { |
| int index; |
| Expression value; |
| |
| ArrayElement(this.index, this.value); |
| |
| accept(NodeVisitor visitor) => visitor.visitArrayElement(this); |
| |
| void visitChildren(NodeVisitor visitor) { |
| value.accept(visitor); |
| } |
| } |
| |
| class ObjectInitializer extends Expression { |
| List<Property> properties; |
| |
| ObjectInitializer(this.properties); |
| |
| accept(NodeVisitor visitor) => visitor.visitObjectInitializer(this); |
| |
| void visitChildren(NodeVisitor visitor) { |
| for (Property init in properties) init.accept(visitor); |
| } |
| |
| int get precedenceLevel => PRIMARY; |
| } |
| |
| class Property extends Node { |
| Literal name; |
| Expression value; |
| |
| Property(this.name, this.value); |
| |
| accept(NodeVisitor visitor) => visitor.visitProperty(this); |
| |
| void visitChildren(NodeVisitor visitor) { |
| name.accept(visitor); |
| value.accept(visitor); |
| } |
| } |
| |
| /** |
| * [RegExpLiteral]s, despite being called "Literal", are not inheriting from |
| * [Literal]. Indeed, regular expressions in JavaScript have a side-effect and |
| * are thus not in the same category as numbers or strings. |
| */ |
| class RegExpLiteral extends Expression { |
| /** Contains the pattern and the flags.*/ |
| String pattern; |
| |
| RegExpLiteral(this.pattern); |
| |
| accept(NodeVisitor visitor) => visitor.visitRegExpLiteral(this); |
| void visitChildren(NodeVisitor visitor) {} |
| |
| int get precedenceLevel => PRIMARY; |
| } |
| |
| /** |
| * A comment. |
| * |
| * Extends [Statement] so we can add comments before statements in |
| * [Block] and [Program]. |
| */ |
| class Comment extends Statement { |
| final String comment; |
| |
| Comment(this.comment); |
| |
| accept(NodeVisitor visitor) => visitor.visitComment(this); |
| |
| void visitChildren(NodeVisitor visitor) {} |
| } |
| |
| class JsBuilder { |
| const JsBuilder(); |
| |
| Expression operator [](String source) { |
| return new MiniJsParser(source).expression(); |
| } |
| |
| // TODO(ahe): Remove this method. |
| Binary equals(Expression left, Expression right) { |
| return new Binary('==', left, right); |
| } |
| |
| // TODO(ahe): Remove this method. |
| Binary strictEquals(Expression left, Expression right) { |
| return new Binary('===', left, right); |
| } |
| |
| LiteralString string(String value) => new LiteralString('"$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) { |
| return new Block(statement.map(toStatement).toList()); |
| } 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)); |
| } |
| |
| Assignment assign(Expression leftHandSide, Expression value) { |
| return new Assignment(leftHandSide, value); |
| } |
| |
| Expression undefined() => new Prefix('void', new LiteralNumber('0')); |
| |
| 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 new Block(statement.map(toStatement).toList()); |
| } else if (statement is Node) { |
| return statement.toStatement(); |
| } else { |
| throw new ArgumentError('statement'); |
| } |
| } |
| |
| Expression toExpression(expression) { |
| 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 { |
| throw new ArgumentError('expression should be an Expression, ' |
| 'a String, a num, a bool, or a Map'); |
| } |
| } |
| |
| 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)); |
| } |
| |
| Try try_(body, {catchPart, finallyPart}) { |
| if (catchPart != null) catchPart = toStatement(catchPart); |
| if (finallyPart != null) finallyPart = toStatement(finallyPart); |
| return new Try(toStatement(body), catchPart, finallyPart); |
| } |
| |
| Comment comment(String text) => new Comment(text); |
| } |
| |
| const JsBuilder js = const JsBuilder(); |
| |
| LiteralString string(String value) => js.string(value); |
| |
| class MiniJsParserError { |
| MiniJsParserError(this.parser, this.message) { } |
| |
| MiniJsParser parser; |
| String message; |
| |
| String toString() { |
| var codes = |
| new List.fixedLength(parser.lastPosition, fill: charCodes.$SPACE); |
| var spaces = new String.fromCharCodes(codes); |
| return "Error in MiniJsParser:\n${parser.src}\n$spaces^\n$spaces$message\n"; |
| } |
| } |
| |
| /// Mini JavaScript parser for tiny snippets of code that we want to make into |
| /// AST nodes. Handles: |
| /// * identifiers. |
| /// * dot access. |
| /// * method calls. |
| /// * [] access. |
| /// * array, string, boolean, null and numeric literals (no hex). |
| /// * most operators. |
| /// * brackets. |
| /// * var declarations. |
| /// Notable things it can't do yet include: |
| /// * operator precedence. |
| /// * non-empty object literals. |
| /// * throw, return. |
| /// * statements, including any flow control (if, while, for, etc.) |
| /// * the 'in' keyword. |
| /// |
| /// It's a fairly standard recursive descent parser. |
| /// |
| /// Literal strings are passed through to the final JS source code unchanged, |
| /// including the choice of surrounding quotes, so if you parse |
| /// r'var x = "foo\n\"bar\""' you will end up with |
| /// var x = "foo\n\"bar\"" in the final program. String literals are |
| /// restricted to a small subset of the full set of allowed JS escapes in order |
| /// to get early errors for unintentional escape sequences without complicating |
| /// this parser unneccessarily. |
| class MiniJsParser { |
| MiniJsParser(this.src) |
| : lastCategory = NONE, |
| lastToken = null, |
| lastPosition = 0, |
| position = 0 { |
| getSymbol(); |
| } |
| |
| int lastCategory; |
| String lastToken; |
| int lastPosition; |
| int position; |
| String src; |
| |
| static const NONE = -1; |
| static const ALPHA = 0; |
| static const NUMERIC = 1; |
| static const STRING = 2; |
| static const SYMBOL = 3; |
| static const RELATION = 4; |
| static const DOT = 5; |
| static const LPAREN = 6; |
| static const RPAREN = 7; |
| static const LBRACE = 8; |
| static const RBRACE = 9; |
| static const LSQUARE = 10; |
| static const RSQUARE = 11; |
| static const COMMA = 12; |
| static const QUERY = 13; |
| static const COLON = 14; |
| static const OTHER = 15; |
| |
| // Make sure that ]] is two symbols. |
| bool singleCharCategory(int category) => category >= DOT; |
| |
| static String categoryToString(int cat) { |
| switch (cat) { |
| case NONE: return "NONE"; |
| case ALPHA: return "ALPHA"; |
| case NUMERIC: return "NUMERIC"; |
| case SYMBOL: return "SYMBOL"; |
| case RELATION: return "RELATION"; |
| case DOT: return "DOT"; |
| case LPAREN: return "LPAREN"; |
| case RPAREN: return "RPAREN"; |
| case LBRACE: return "LBRACE"; |
| case RBRACE: return "RBRACE"; |
| case RSQUARE: return "RSQUARE"; |
| case STRING: return "STRING"; |
| case COMMA: return "COMMA"; |
| case QUERY: return "QUERY"; |
| case COLON: return "COLON"; |
| case OTHER: return "OTHER"; |
| } |
| return "Unknown: $cat"; |
| } |
| |
| static const CATEGORIES = const <int>[ |
| OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, // 0-7 |
| OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, // 8-15 |
| OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, // 16-23 |
| OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, OTHER, // 24-31 |
| OTHER, RELATION, OTHER, OTHER, ALPHA, SYMBOL, SYMBOL, OTHER, // !"#$%&´ |
| LPAREN, RPAREN, SYMBOL, SYMBOL, COMMA, SYMBOL, DOT, SYMBOL, // ()*+,-./ |
| NUMERIC, NUMERIC, NUMERIC, NUMERIC, NUMERIC, // 01234 |
| NUMERIC, NUMERIC, NUMERIC, NUMERIC, NUMERIC, // 56789 |
| COLON, OTHER, RELATION, RELATION, RELATION, QUERY, OTHER, // :;<=>?@ |
| ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, // ABCDEFGH |
| ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, // IJKLMNOP |
| ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, // QRSTUVWX |
| ALPHA, ALPHA, LSQUARE, OTHER, RSQUARE, SYMBOL, ALPHA, OTHER, // YZ[\]^_' |
| ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, // abcdefgh |
| ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, // ijklmnop |
| ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, ALPHA, // qrstuvwx |
| ALPHA, ALPHA, LBRACE, SYMBOL, RBRACE, SYMBOL]; // yz{|}~ |
| |
| static final BINARY_OPERATORS = [ |
| '+', '-', '*', '/', '%', '^', '|', '&', '||', '&&', |
| '<<', '>>', '+=', '-=', '*=', '/=', '^=', '|=', '&=', '<<=', '>>=', |
| '=', '!=', '==', '!==', '===', '<', '<=', '>=', '>'].toSet(); |
| static final UNARY_OPERATORS = ['++', '--', '+', '-', '~', '!'].toSet(); |
| |
| // For sanity we only allow \\, \', \" and \n in string literals. |
| static final STRING_LITERAL_PATTERN = |
| new RegExp('^[\'"](?:[^\\\\]|\\\\[\\\\n\'"])*[\'"]\$'); |
| |
| static int category(int code) { |
| if (code >= CATEGORIES.length) return OTHER; |
| return CATEGORIES[code]; |
| } |
| |
| void getSymbol() { |
| while (position < src.length && |
| src.codeUnitAt(position) == charCodes.$SPACE) { |
| position++; |
| } |
| if (position == src.length) { |
| lastCategory = NONE; |
| lastToken = null; |
| lastPosition = position; |
| return; |
| } |
| int code = src.codeUnitAt(position); |
| lastPosition = position; |
| if (code == charCodes.$SQ || code == charCodes.$DQ) { |
| int currentCode; |
| do { |
| position++; |
| if (position >= src.length) { |
| throw new MiniJsParserError(this, "Unterminated string"); |
| } |
| currentCode = src.codeUnitAt(position); |
| if (currentCode == charCodes.$BACKSLASH) { |
| if (++position >= src.length) { |
| throw new MiniJsParserError(this, "Unterminated string"); |
| } |
| } |
| } while (currentCode != code); |
| lastCategory = STRING; |
| position++; |
| lastToken = src.substring(lastPosition, position); |
| if (!STRING_LITERAL_PATTERN.hasMatch(lastToken)) { |
| throw new MiniJsParserError( |
| this, |
| "Only escapes allowed in string literals are \\, \', \" and \n"); |
| } |
| } else { |
| int cat = category(src.codeUnitAt(position)); |
| int newCat; |
| do { |
| position++; |
| if (position == src.length) break; |
| newCat = category(src.codeUnitAt(position)); |
| } while (!singleCharCategory(cat) && |
| (cat == newCat || |
| (cat == ALPHA && newCat == NUMERIC) || // eg. level42. |
| (cat == NUMERIC && newCat == DOT) || // eg. 3.1415 |
| (cat == SYMBOL && newCat == RELATION))); // eg. +=. |
| lastCategory = cat; |
| lastToken = src.substring(lastPosition, position); |
| if (cat == NUMERIC) { |
| double.parse(lastToken, (_) { |
| throw new MiniJsParserError(this, "Unparseable number"); |
| }); |
| } else if (cat == SYMBOL || cat == RELATION) { |
| if (!BINARY_OPERATORS.contains(lastToken) && |
| !UNARY_OPERATORS.contains(lastToken)) { |
| throw new MiniJsParserError(this, "Unknown operator"); |
| } |
| } |
| } |
| } |
| |
| void expectCategory(int cat) { |
| if (cat != lastCategory) { |
| throw new MiniJsParserError(this, "Expected ${categoryToString(cat)}"); |
| } |
| getSymbol(); |
| } |
| |
| bool acceptCategory(int cat) { |
| if (cat == lastCategory) { |
| getSymbol(); |
| return true; |
| } |
| return false; |
| } |
| |
| bool acceptString(String string) { |
| if (lastToken == string) { |
| getSymbol(); |
| return true; |
| } |
| return false; |
| } |
| |
| Expression parsePrimary() { |
| String last = lastToken; |
| if (acceptCategory(ALPHA)) { |
| if (last == "true") { |
| return new LiteralBool(true); |
| } else if (last == "false") { |
| return new LiteralBool(false); |
| } else if (last == "null") { |
| return new LiteralNull(); |
| } else { |
| return new VariableUse(last); |
| } |
| } else if (acceptCategory(LPAREN)) { |
| Expression expression = parseExpression(); |
| expectCategory(RPAREN); |
| return expression; |
| } else if (acceptCategory(STRING)) { |
| return new LiteralString(last); |
| } else if (acceptCategory(NUMERIC)) { |
| return new LiteralNumber(last); |
| } else if (acceptCategory(LBRACE)) { |
| expectCategory(RBRACE); |
| return new ObjectInitializer([]); |
| } else if (acceptCategory(LSQUARE)) { |
| var values = <ArrayElement>[]; |
| if (!acceptCategory(RSQUARE)) { |
| do { |
| values.add(new ArrayElement(values.length, parseExpression())); |
| } while (acceptCategory(COMMA)); |
| expectCategory(RSQUARE); |
| } |
| return new ArrayInitializer(values.length, values); |
| } else { |
| throw new MiniJsParserError(this, "Expected primary expression"); |
| } |
| } |
| |
| Expression parseMember() { |
| Expression receiver = parsePrimary(); |
| while (true) { |
| if (acceptCategory(DOT)) { |
| String identifier = lastToken; |
| expectCategory(ALPHA); |
| receiver = new PropertyAccess.field(receiver, identifier); |
| } else if (acceptCategory(LSQUARE)) { |
| Expression inBraces = parseExpression(); |
| expectCategory(RSQUARE); |
| receiver = new PropertyAccess(receiver, inBraces); |
| } else { |
| return receiver; |
| } |
| } |
| } |
| |
| Expression parseCall() { |
| bool constructor = acceptString("new"); |
| Expression receiver = parseMember(); |
| if (acceptCategory(LPAREN)) { |
| final arguments = <Expression>[]; |
| if (!acceptCategory(RPAREN)) { |
| while (true) { |
| Expression argument = parseExpression(); |
| arguments.add(argument); |
| if (acceptCategory(RPAREN)) break; |
| expectCategory(COMMA); |
| } |
| } |
| return constructor ? |
| new New(receiver, arguments) : |
| new Call(receiver, arguments); |
| } else { |
| if (constructor) { |
| // JS allows new without (), but we don't. |
| throw new MiniJsParserError(this, "Parentheses are required for new"); |
| } |
| return receiver; |
| } |
| } |
| |
| Expression parsePostfix() { |
| Expression expression = parseCall(); |
| String operator = lastToken; |
| if (lastCategory == SYMBOL && (acceptString("++") || acceptString("--"))) { |
| return new Postfix(operator, expression); |
| } |
| return expression; |
| } |
| |
| Expression parseUnary() { |
| String operator = lastToken; |
| if (lastCategory == ALPHA) { |
| if (acceptString("typeof") || acceptString("void") || |
| acceptString("delete")) { |
| return new Prefix(operator, parsePostfix()); |
| } |
| } else if (lastCategory == SYMBOL) { |
| if (acceptString("~") || acceptString("-") || acceptString("++") || |
| acceptString("--") || acceptString("+")) { |
| return new Prefix(operator, parsePostfix()); |
| } |
| } else if (acceptString("!")) { |
| return new Prefix(operator, parsePostfix()); |
| } |
| return parsePostfix(); |
| } |
| |
| Expression parseBinary() { |
| // Since we don't handle precedence we don't allow two different symbols |
| // without parentheses. |
| Expression lhs = parseUnary(); |
| String firstSymbol = lastToken; |
| while (true) { |
| String symbol = lastToken; |
| if (!acceptCategory(SYMBOL)) return lhs; |
| if (!BINARY_OPERATORS.contains(symbol)) { |
| throw new MiniJsParserError(this, "Unknown binary operator"); |
| } |
| if (symbol != firstSymbol) { |
| throw new MiniJsParserError( |
| this, "Mixed $firstSymbol and $symbol operators without ()"); |
| } |
| Expression rhs = parseUnary(); |
| if (symbol.endsWith("=")) { |
| // +=, -=, *= etc. |
| lhs = new Assignment.compound(lhs, |
| symbol.substring(0, symbol.length - 1), |
| rhs); |
| } else { |
| lhs = new Binary(symbol, lhs, rhs); |
| } |
| } |
| } |
| |
| Expression parseRelation() { |
| Expression lhs = parseBinary(); |
| String relation = lastToken; |
| // The lexer returns "=" as a relational operator because it looks a bit |
| // like ==, <=, etc. But we don't want to handle it here (that would give |
| // it the wrong prescedence), so we just return if we see it. |
| if (relation == "=" || !acceptCategory(RELATION)) return lhs; |
| Expression rhs = parseBinary(); |
| if (relation == "<<=" || relation == ">>=") { |
| return new Assignment.compound(lhs, |
| relation.substring(0, relation.length - 1), |
| rhs); |
| } else { |
| // Regular binary operation. |
| return new Binary(relation, lhs, rhs); |
| } |
| } |
| |
| Expression parseConditional() { |
| Expression lhs = parseRelation(); |
| if (!acceptCategory(QUERY)) return lhs; |
| Expression ifTrue = parseAssignment(); |
| expectCategory(COLON); |
| Expression ifFalse = parseAssignment(); |
| return new Conditional(lhs, ifTrue, ifFalse); |
| } |
| |
| |
| Expression parseAssignment() { |
| Expression lhs = parseConditional(); |
| if (acceptString("=")) { |
| return new Assignment(lhs, parseAssignment()); |
| } |
| return lhs; |
| } |
| |
| Expression parseExpression() => parseAssignment(); |
| |
| Expression parseVarDeclarationOrExpression() { |
| if (acceptString("var")) { |
| var initialization = []; |
| do { |
| String variable = lastToken; |
| expectCategory(ALPHA); |
| Expression initializer = null; |
| if (acceptString("=")) { |
| initializer = parseExpression(); |
| } |
| var declaration = new VariableDeclaration(variable); |
| initialization.add( |
| new VariableInitialization(declaration, initializer)); |
| } while (acceptCategory(COMMA)); |
| return new VariableDeclarationList(initialization); |
| } else { |
| return parseExpression(); |
| } |
| } |
| |
| Expression expression() { |
| Expression expression = parseVarDeclarationOrExpression(); |
| if (lastCategory != NONE || position != src.length) { |
| throw new MiniJsParserError( |
| this, "Unparsed junk: ${categoryToString(lastCategory)}"); |
| } |
| return expression; |
| } |
| } |