blob: fe89432e088c8bd0599b362e0d15eb1d643a392a [file] [log] [blame]
// 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.filled(parser.lastPosition, 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;
}
}