blob: db8b5e0f76d5949bbcdce82da46f8e8e91f96286 [file] [log] [blame] [edit]
// Copyright (c) 2014, 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.
// ignore_for_file: omit_local_variable_types
import 'nodes.dart';
class TemplateManager {
Map<String, Template> expressionTemplates = {};
Map<String, Template> statementTemplates = {};
TemplateManager();
Template? lookupExpressionTemplate(String source) {
return expressionTemplates[source];
}
Template defineExpressionTemplate(String source, Node ast) {
Template template = Template(
source,
ast,
isExpression: true,
forceCopy: false,
);
expressionTemplates[source] = template;
return template;
}
Template? lookupStatementTemplate(String source) {
return statementTemplates[source];
}
Template defineStatementTemplate(String source, Node ast) {
Template template = Template(
source,
ast,
isExpression: false,
forceCopy: false,
);
statementTemplates[source] = template;
return template;
}
}
/// A Template is created with JavaScript AST containing placeholders (interface
/// InterpolatedNode).
///
/// The [instantiate] method creates an AST that looks like the original with
/// the placeholders replaced by the arguments to [instantiate].
class Template {
final String? source;
final bool isExpression;
final bool forceCopy;
final Node ast;
final Instantiator instantiator;
final int positionalArgumentCount;
// Names of named holes, empty if there are no named holes.
final List<String> holeNames;
bool get isPositional => holeNames.isEmpty;
Template._(
this.source,
this.ast, {
required this.instantiator,
required this.isExpression,
required this.forceCopy,
required this.positionalArgumentCount,
this.holeNames = const [],
});
factory Template(
String? source,
Node ast, {
bool isExpression = true,
bool forceCopy = false,
}) {
assert(isExpression ? ast is Expression : ast is Statement);
final generator = InstantiatorGeneratorVisitor(forceCopy);
final instantiator = generator.compile(ast);
final positionalArgumentCount = generator.analysis.count;
final names = generator.analysis.holeNames;
final holeNames = names.toList(growable: false);
return Template._(
source,
ast,
instantiator: instantiator,
isExpression: isExpression,
forceCopy: forceCopy,
positionalArgumentCount: positionalArgumentCount,
holeNames: holeNames,
);
}
factory Template.withExpressionResult(Expression ast) {
assert(_checkNoPlaceholders(ast));
return Template._(
null,
ast,
instantiator: (arguments) => ast,
isExpression: true,
forceCopy: false,
positionalArgumentCount: 0,
);
}
factory Template.withStatementResult(Statement ast) {
assert(_checkNoPlaceholders(ast));
return Template._(
null,
ast,
instantiator: (arguments) => ast,
isExpression: false,
forceCopy: false,
positionalArgumentCount: 0,
);
}
static bool _checkNoPlaceholders(Node ast) {
var generator = InstantiatorGeneratorVisitor(false);
generator.compile(ast);
return generator.analysis.count == 0;
}
/// Instantiates the template with the given [arguments].
///
/// This method fills in the holes with the given arguments. The [arguments]
/// must be either a [List] or a [Map].
Node instantiate(Object arguments) {
if (arguments is List) {
if (arguments.length != positionalArgumentCount) {
throw 'Wrong number of template arguments, given ${arguments.length}, '
'expected $positionalArgumentCount'
', source: "$source"';
}
return instantiator(arguments) as Node;
}
if (arguments is Map) {
if (holeNames.length < arguments.length) {
// This search is in O(n), but we only do it in case of an error, and
// the number of holes should be quite limited.
String unusedNames = arguments.keys
.where((name) => !holeNames.contains(name))
.join(', ');
throw 'Template arguments has unused mappings: $unusedNames';
}
if (!holeNames.every((String name) => arguments.containsKey(name))) {
String notFound = holeNames
.where((name) => !arguments.containsKey(name))
.join(', ');
throw 'Template arguments is missing mappings for: $notFound';
}
return instantiator(arguments) as Node;
}
throw ArgumentError.value(arguments, 'arguments', 'Must be a List or Map');
}
}
/// An Instantiator is a Function that generates a JS AST tree or List of
/// trees.
///
/// [arguments] is a List for positional templates, or Map for named templates.
typedef Instantiator<T> = T Function(dynamic);
/// InstantiatorGeneratorVisitor compiles a template. This class compiles a
/// tree containing [InterpolatedNode]s into a function that will create a copy
/// of the tree with the interpolated nodes substituted with provided values.
class InstantiatorGeneratorVisitor implements NodeVisitor<Instantiator> {
final bool forceCopy;
final analysis = InterpolatedNodeAnalysis();
/// The entire tree is cloned if [forceCopy] is true.
InstantiatorGeneratorVisitor(this.forceCopy);
Instantiator compile(Node node) {
analysis.visit(node);
return visit(node);
}
static Instantiator<T> same<T extends Node>(T node) =>
(arguments) => node;
static Null makeNull(dynamic arguments) => null;
Instantiator visit<T extends Node>(T node) {
if (forceCopy || analysis.containsInterpolatedNodes(node)) {
return node.accept(this);
}
return same<T>(node);
}
Instantiator visitNullable<T extends Node>(T? node) {
return node == null ? makeNull : visit(node);
}
Instantiator visitSplayable(Node node) {
// TODO(jmesserly): parameters and methods always support splaying because
// they appear in lists. So this method is equivalent to
// `visitSplayableExpression`.
return visitSplayableExpression(node);
}
Instantiator visitNode(Node node) {
throw UnimplementedError('visit${node.runtimeType}');
}
@override
Instantiator<Expression> visitInterpolatedExpression(
InterpolatedExpression node,
) {
var nameOrPosition = node.nameOrPosition;
return (arguments) {
var value = arguments[nameOrPosition];
if (value is Expression) return value;
if (value is String) return Identifier(value);
throw StateError(
'Interpolated value #$nameOrPosition is not an Expression: $value',
);
};
}
Instantiator visitSplayableExpression(Node node) {
if (node is InterpolatedExpression) {
var nameOrPosition = node.nameOrPosition;
return (arguments) {
var value = arguments[nameOrPosition];
Expression toExpression(item) {
if (item is Expression) return item;
if (item is String) return Identifier(item);
throw StateError(
'Interpolated value #$nameOrPosition is not '
'an Expression or List of Expressions: $value',
);
}
if (value is Iterable) return value.map(toExpression);
return toExpression(value);
};
}
return visit(node);
}
List<T> splayNodes<T extends Node>(List<Instantiator> makers, args) {
var exprs = <T>[];
for (var instantiator in makers) {
var result = instantiator(args);
if (result is Iterable) {
for (var e in result) {
exprs.add(e as T);
}
} else {
exprs.add(result as T);
}
}
return exprs;
}
@override
Instantiator<Literal> visitInterpolatedLiteral(InterpolatedLiteral node) {
var nameOrPosition = node.nameOrPosition;
return (arguments) {
var value = arguments[nameOrPosition];
if (value is Literal) return value;
throw StateError(
'Interpolated value #$nameOrPosition is not a Literal: $value',
);
};
}
@override
Instantiator visitInterpolatedParameter(InterpolatedParameter node) {
var nameOrPosition = node.nameOrPosition;
return (arguments) {
var value = arguments[nameOrPosition];
Parameter toIdentifier(item) {
if (item is Parameter) return item;
if (item is String) return Identifier(item);
throw StateError(
'Interpolated value #$nameOrPosition is not an Identifier'
' or List of Identifiers: $value',
);
}
if (value is Iterable) return value.map(toIdentifier);
return toIdentifier(value);
};
}
@override
Instantiator<Expression> visitInterpolatedSelector(
InterpolatedSelector node,
) {
// A selector is an expression, as in `a[selector]`.
// A String argument converted into a LiteralString, so `a.#` with argument
// 'foo' generates `a["foo"]` which prints as `a.foo`.
var nameOrPosition = node.nameOrPosition;
return (arguments) {
var value = arguments[nameOrPosition];
if (value is Expression) return value;
if (value is String) return LiteralString('"$value"');
throw StateError(
'Interpolated value #$nameOrPosition is not a selector: $value',
);
};
}
@override
Instantiator<Statement> visitInterpolatedStatement(
InterpolatedStatement node,
) {
var nameOrPosition = node.nameOrPosition;
return (arguments) {
var value = arguments[nameOrPosition];
if (value is Node) return value.toStatement();
throw StateError(
'Interpolated value #$nameOrPosition is not a Statement: $value',
);
};
}
@override
Instantiator visitInterpolatedMethod(InterpolatedMethod node) {
var nameOrPosition = node.nameOrPosition;
return (arguments) {
var value = arguments[nameOrPosition];
Method toMethod(item) {
if (item is Method) return item;
throw StateError(
'Interpolated value #$nameOrPosition is not a Method '
'or List of Methods: $value',
);
}
if (value is Iterable) return value.map(toMethod);
return toMethod(value);
};
}
@override
Instantiator<Identifier> visitInterpolatedIdentifier(
InterpolatedIdentifier node,
) {
var nameOrPosition = node.nameOrPosition;
return (arguments) {
var item = arguments[nameOrPosition];
if (item is Identifier) return item;
if (item is String) return Identifier(item);
throw StateError(
'Interpolated value #$nameOrPosition is not a '
'Identifier or String: $item',
);
};
}
Instantiator visitSplayableStatement(Node node) {
if (node is InterpolatedStatement) {
var nameOrPosition = node.nameOrPosition;
return (arguments) {
var value = arguments[nameOrPosition];
Statement toStatement(item) {
if (item is Statement) return item;
if (item is Expression) return item.toStatement();
throw StateError(
'Interpolated value #$nameOrPosition is not '
'a Statement or List of Statements: $value',
);
}
if (value is Iterable) return value.map(toStatement);
return toStatement(value);
};
}
return visit(node);
}
@override
Instantiator<Program> visitProgram(Program node) {
var instantiators = node.body.map(visitSplayableStatement).toList();
return (arguments) => Program(splayStatements(instantiators, arguments));
}
List<Statement> splayStatements(List<Instantiator> instantiators, arguments) {
var statements = <Statement>[];
for (var instantiator in instantiators) {
var node = instantiator(arguments);
if (node is EmptyStatement) continue;
if (node is Iterable) {
statements.addAll(node as Iterable<Statement>);
} else if (node is Block && !node.isScope) {
statements.addAll(node.statements);
} else {
statements.add((node as Node).toStatement());
}
}
return statements;
}
@override
Instantiator<Block> visitBlock(Block node) {
var instantiators = node.statements.map(visitSplayableStatement).toList();
return (arguments) => Block(splayStatements(instantiators, arguments));
}
@override
Instantiator<Statement> visitExpressionStatement(ExpressionStatement node) {
var makeExpression = visit(node.expression) as Instantiator<Expression>;
return (arguments) => makeExpression(arguments).toStatement();
}
@override
Instantiator<DebuggerStatement> visitDebuggerStatement(node) =>
(arguments) => DebuggerStatement();
@override
Instantiator<EmptyStatement> visitEmptyStatement(EmptyStatement node) =>
(arguments) => EmptyStatement();
@override
Instantiator<Statement> visitIf(If node) {
final condition = node.condition;
if (condition is InterpolatedExpression) {
return visitIfConditionalCompilation(node, condition);
} else {
return visitIfNormal(node);
}
}
Instantiator<Statement> visitIfConditionalCompilation(
If node,
InterpolatedExpression condition,
) {
var makeThen = visit(node.then) as Instantiator<Statement>;
var makeOtherwise = visit(node.otherwise) as Instantiator<Statement>;
return (arguments) {
// Allow booleans to be used for conditional compilation.
var nameOrPosition = condition.nameOrPosition;
var value = arguments[nameOrPosition];
if (value is bool) {
return value ? makeThen(arguments) : makeOtherwise(arguments);
}
var newCondition = value is String
? Identifier(value)
: value as Expression;
return If(newCondition, makeThen(arguments), makeOtherwise(arguments));
};
}
Instantiator<Statement> visitIfNormal(If node) {
var makeCondition = visit(node.condition) as Instantiator<Expression>;
var makeThen = visit(node.then) as Instantiator<Statement>;
var makeOtherwise = visit(node.otherwise) as Instantiator<Statement>;
return (arguments) => If(
makeCondition(arguments),
makeThen(arguments),
makeOtherwise(arguments),
);
}
@override
Instantiator<Statement> visitFor(For node) {
var makeInit = visitNullable(node.init) as Instantiator<Expression>;
var makeCondition =
visitNullable(node.condition) as Instantiator<Expression>;
var makeUpdate = visitNullable(node.update) as Instantiator<Expression>;
var makeBody = visit(node.body) as Instantiator<Statement>;
return (arguments) => For(
makeInit(arguments),
makeCondition(arguments),
makeUpdate(arguments).toVoidExpression(),
makeBody(arguments),
);
}
@override
Instantiator<ForIn> visitForIn(ForIn node) {
var makeLeftHandSide = visit(node.leftHandSide) as Instantiator<Expression>;
var makeObject = visit(node.object) as Instantiator<Expression>;
var makeBody = visit(node.body) as Instantiator<Statement>;
return (arguments) => ForIn(
makeLeftHandSide(arguments),
makeObject(arguments),
makeBody(arguments),
);
}
@override
Instantiator<ForOf> visitForOf(ForOf node) {
var makeLeftHandSide = visit(node.leftHandSide) as Instantiator<Expression>;
var makeObject = visit(node.iterable) as Instantiator<Expression>;
var makeBody = visit(node.body) as Instantiator<Statement>;
return (arguments) => ForOf(
makeLeftHandSide(arguments),
makeObject(arguments),
makeBody(arguments),
);
}
@override
Instantiator<While> visitWhile(While node) {
var makeCondition = visit(node.condition) as Instantiator<Expression>;
var makeBody = visit(node.body) as Instantiator<Statement>;
return (arguments) => While(makeCondition(arguments), makeBody(arguments));
}
@override
Instantiator<Do> visitDo(Do node) {
var makeBody = visit(node.body) as Instantiator<Statement>;
var makeCondition = visit(node.condition) as Instantiator<Expression>;
return (arguments) => Do(makeBody(arguments), makeCondition(arguments));
}
@override
Instantiator<Continue> visitContinue(Continue node) =>
(arguments) => Continue(node.targetLabel);
@override
Instantiator<Break> visitBreak(Break node) =>
(arguments) => Break(node.targetLabel);
@override
Instantiator<Statement> visitReturn(Return node) {
if (node.value == null) return (args) => Return();
var makeExpression = visit(node.value!) as Instantiator<Expression>;
return (arguments) => makeExpression(arguments).toReturn();
}
@override
Instantiator<DartYield> visitDartYield(DartYield node) {
var makeExpression = visit(node.expression) as Instantiator<Expression>;
return (arguments) => DartYield(makeExpression(arguments), node.hasStar);
}
@override
Instantiator<Throw> visitThrow(Throw node) {
var makeExpression = visit(node.expression) as Instantiator<Expression>;
return (arguments) => Throw(makeExpression(arguments));
}
@override
Instantiator<Try> visitTry(Try node) {
var makeBody = visit(node.body) as Instantiator<Block>;
var makeCatch = visitNullable(node.catchPart) as Instantiator<Catch?>;
var makeFinally = visitNullable(node.finallyPart) as Instantiator<Block?>;
return (arguments) =>
Try(makeBody(arguments), makeCatch(arguments), makeFinally(arguments));
}
@override
Instantiator<Catch> visitCatch(Catch node) {
var makeDeclaration = visit(node.declaration) as Instantiator<Identifier>;
var makeBody = visit(node.body) as Instantiator<Block>;
return (arguments) =>
Catch(makeDeclaration(arguments), makeBody(arguments));
}
@override
Instantiator<Switch> visitSwitch(Switch node) {
var makeKey = visit(node.key) as Instantiator<Expression>;
var makeCases = node.cases.map(
(c) => visit(c) as Instantiator<SwitchClause>,
);
return (arguments) => Switch(
makeKey(arguments),
makeCases.map((makeCase) => makeCase(arguments)).toList(),
);
}
@override
Instantiator<Case> visitCase(Case node) {
var makeExpression = visit(node.expression) as Instantiator<Expression>;
var makeBody = visit(node.body) as Instantiator<Block>;
return (arguments) {
return Case(makeExpression(arguments), makeBody(arguments));
};
}
@override
Instantiator<Default> visitDefault(Default node) {
var makeBody = visit(node.body) as Instantiator<Block>;
return (arguments) {
return Default(makeBody(arguments));
};
}
@override
Instantiator<FunctionDeclaration> visitFunctionDeclaration(
FunctionDeclaration node,
) {
var makeName = visit(node.name) as Instantiator<Identifier>;
var makeFunction = visit(node.function) as Instantiator<Fun>;
return (arguments) =>
FunctionDeclaration(makeName(arguments), makeFunction(arguments));
}
@override
Instantiator<LabeledStatement> visitLabeledStatement(LabeledStatement node) {
var makeBody = visit(node.body) as Instantiator<Statement>;
return (arguments) => LabeledStatement(node.label, makeBody(arguments));
}
@override
Instantiator visitLiteralStatement(LiteralStatement node) => visitNode(node);
@override
Instantiator visitLiteralExpression(LiteralExpression node) =>
visitNode(node);
@override
Instantiator<VariableDeclarationList> visitVariableDeclarationList(
VariableDeclarationList node,
) {
var declarationMakers = node.declarations
.map(visitVariableInitialization)
.toList();
return (arguments) => VariableDeclarationList(
node.keyword,
declarationMakers.map((m) => m(arguments)).toList(),
);
}
@override
Instantiator<Expression> visitAssignment(Assignment node) {
Instantiator makeLeftHandSide = visit(node.leftHandSide);
String? op = node.op;
Instantiator makeValue = visit(node.value);
return (arguments) {
return makeValue(
arguments,
).toAssignExpression(makeLeftHandSide(arguments), op)
as Expression;
};
}
@override
Instantiator<VariableInitialization> visitVariableInitialization(
VariableInitialization node,
) {
var makeDeclaration =
visit(node.declaration) as Instantiator<VariableBinding>;
var makeValue = visitNullable(node.value) as Instantiator<Expression?>;
return (arguments) => VariableInitialization(
makeDeclaration(arguments),
makeValue(arguments),
);
}
@override
Instantiator<Conditional> visitConditional(Conditional cond) {
var makeCondition = visit(cond.condition) as Instantiator<Expression>;
var makeThen = visit(cond.then) as Instantiator<Expression>;
var makeOtherwise = visit(cond.otherwise) as Instantiator<Expression>;
return (arguments) => Conditional(
makeCondition(arguments),
makeThen(arguments),
makeOtherwise(arguments),
);
}
@override
Instantiator<Call> visitNew(New node) => handleCallOrNew(node, true);
@override
Instantiator<Call> visitCall(Call node) => handleCallOrNew(node, false);
Instantiator<Call> handleCallOrNew(Call node, bool isNew) {
var makeTarget = visit(node.target) as Instantiator<Expression>;
var argumentMakers = node.arguments.map(visitSplayableExpression).toList();
// TODO(sra): Avoid copying call arguments if no interpolation or forced
// copying.
return (arguments) {
var target = makeTarget(arguments);
var callArgs = splayNodes<Expression>(argumentMakers, arguments);
return isNew ? New(target, callArgs) : Call(target, callArgs);
};
}
@override
Instantiator<Binary> visitBinary(Binary node) {
var makeLeft = visit(node.left) as Instantiator<Expression>;
var makeRight = visit(node.right) as Instantiator<Expression>;
String op = node.op;
return (arguments) => Binary(op, makeLeft(arguments), makeRight(arguments));
}
@override
Instantiator<Prefix> visitPrefix(Prefix node) {
var makeOperand = visit(node.argument) as Instantiator<Expression>;
String op = node.op;
return (arguments) => Prefix(op, makeOperand(arguments));
}
@override
Instantiator<Postfix> visitPostfix(Postfix node) {
var makeOperand = visit(node.argument) as Instantiator<Expression>;
String op = node.op;
return (arguments) => Postfix(op, makeOperand(arguments));
}
@override
Instantiator<This> visitThis(This node) =>
(arguments) => This();
@override
Instantiator<Super> visitSuper(Super node) =>
(arguments) => Super();
@override
Instantiator<Identifier> visitIdentifier(Identifier node) =>
(arguments) => Identifier(node.name);
@override
Instantiator<Spread> visitSpread(Spread node) {
var maker = visit(node.argument);
return (arguments) => Spread(maker(arguments) as Expression);
}
@override
Instantiator<Yield> visitYield(Yield node) {
var maker = visitNullable(node.value);
return (arguments) =>
Yield(maker(arguments) as Expression, star: node.star);
}
@override
Instantiator<RestParameter> visitRestParameter(RestParameter node) {
var maker = visit(node.parameter);
return (arguments) => RestParameter(maker(arguments) as Identifier);
}
@override
Instantiator<PropertyAccess> visitAccess(PropertyAccess node) {
var makeReceiver = visit(node.receiver) as Instantiator<Expression>;
var makeSelector = visit(node.selector) as Instantiator<Expression>;
return (arguments) =>
PropertyAccess(makeReceiver(arguments), makeSelector(arguments));
}
@override
Instantiator<NamedFunction> visitNamedFunction(NamedFunction node) {
var makeDeclaration = visit(node.name) as Instantiator<Identifier>;
var makeFunction = visit(node.function) as Instantiator<Fun>;
return (arguments) =>
NamedFunction(makeDeclaration(arguments), makeFunction(arguments));
}
@override
Instantiator<Fun> visitFun(Fun node) {
var paramMakers = node.params.map(visitSplayable).toList();
var makeBody = visit(node.body) as Instantiator<Block>;
return (arguments) => Fun(
splayNodes(paramMakers, arguments),
makeBody(arguments),
isGenerator: node.isGenerator,
asyncModifier: node.asyncModifier,
);
}
@override
Instantiator<ArrowFun> visitArrowFun(ArrowFun node) {
var paramMakers = node.params.map(visitSplayable).toList();
Instantiator makeBody = visit(node.body);
return (arguments) => ArrowFun(
splayNodes(paramMakers, arguments),
makeBody(arguments) as Node,
);
}
@override
Instantiator<LiteralBool> visitLiteralBool(LiteralBool node) =>
(arguments) => LiteralBool(node.value);
@override
Instantiator<LiteralString> visitLiteralString(LiteralString node) =>
(arguments) => LiteralString(node.value);
@override
Instantiator<LiteralNumber> visitLiteralNumber(LiteralNumber node) =>
(arguments) => LiteralNumber(node.value);
@override
Instantiator<LiteralNull> visitLiteralNull(LiteralNull node) =>
(arguments) => LiteralNull();
@override
Instantiator<ArrayInitializer> visitArrayInitializer(ArrayInitializer node) {
var makers = node.elements.map(visitSplayableExpression).toList();
return (arguments) => ArrayInitializer(splayNodes(makers, arguments));
}
@override
Instantiator<ArrayHole> visitArrayHole(ArrayHole node) {
return (arguments) => ArrayHole();
}
@override
Instantiator<ObjectInitializer> visitObjectInitializer(
ObjectInitializer node,
) {
var propertyMakers = node.properties.map(visitSplayable).toList();
return (arguments) =>
ObjectInitializer(splayNodes(propertyMakers, arguments));
}
@override
Instantiator<Property> visitProperty(Property node) {
var makeName = visit(node.name) as Instantiator<Expression>;
var makeValue = visit(node.value) as Instantiator<Expression>;
return (arguments) => Property(
makeName(arguments),
makeValue(arguments),
isStatic: node.isStatic,
isClassProperty: node.isClassProperty,
);
}
@override
Instantiator<RegExpLiteral> visitRegExpLiteral(RegExpLiteral node) =>
(arguments) => RegExpLiteral(node.pattern);
@override
Instantiator<TemplateString> visitTemplateString(TemplateString node) {
var makeElements = node.interpolations.map(visit).toList();
return (arguments) =>
TemplateString(node.strings, splayNodes(makeElements, arguments));
}
@override
Instantiator<TaggedTemplate> visitTaggedTemplate(TaggedTemplate node) {
var makeTag = visit(node.tag) as Instantiator<Expression>;
var makeTemplate = visitTemplateString(node.template);
return (arguments) =>
TaggedTemplate(makeTag(arguments), makeTemplate(arguments));
}
@override
Instantiator<ClassDeclaration> visitClassDeclaration(ClassDeclaration node) {
var makeClass = visitClassExpression(node.classExpr);
return (arguments) => ClassDeclaration(makeClass(arguments));
}
@override
Instantiator<ClassExpression> visitClassExpression(ClassExpression node) {
var makeProperties = node.properties.map(visitSplayableExpression).toList();
var makeName = visit(node.name) as Instantiator<Identifier>;
var makeHeritage =
visitNullable(node.heritage) as Instantiator<Expression?>;
return (arguments) => ClassExpression(
makeName(arguments),
makeHeritage(arguments),
splayNodes(makeProperties, arguments),
);
}
@override
Instantiator<Method> visitMethod(Method node) {
var makeName = visit(node.name) as Instantiator<Expression>;
var makeFunction = visit(node.function) as Instantiator<Fun>;
return (arguments) => Method(
makeName(arguments),
makeFunction(arguments),
isGetter: node.isGetter,
isSetter: node.isSetter,
isStatic: node.isStatic,
);
}
@override
Instantiator<Comment> visitComment(Comment node) =>
(arguments) => Comment(node.comment);
@override
Instantiator<CommentExpression> visitCommentExpression(
CommentExpression node,
) {
var makeExpr = visit(node.expression) as Instantiator<Expression>;
return (arguments) => CommentExpression(node.comment, makeExpr(arguments));
}
@override
Instantiator<Await> visitAwait(Await node) {
var makeExpr = visit(node.expression) as Instantiator<Expression>;
return (arguments) => Await(makeExpr(arguments));
}
@override
Instantiator visitNameSpecifier(NameSpecifier node) =>
throw UnimplementedError();
@override
Instantiator visitImportDeclaration(ImportDeclaration node) =>
throw UnimplementedError();
@override
Instantiator visitExportDeclaration(ExportDeclaration node) =>
throw UnimplementedError();
@override
Instantiator visitExportClause(ExportClause node) =>
throw UnimplementedError();
@override
Instantiator<DestructuredVariable> visitDestructuredVariable(
DestructuredVariable node,
) {
var makeName = visit(node.name) as Instantiator<Identifier>;
var makeProperty =
visitNullable(node.property) as Instantiator<Expression?>;
var makeStructure =
visitNullable(node.structure) as Instantiator<BindingPattern?>;
var makeDefaultValue =
visitNullable(node.defaultValue) as Instantiator<Expression?>;
return (arguments) => DestructuredVariable(
name: makeName(arguments),
property: makeProperty(arguments),
structure: makeStructure(arguments),
defaultValue: makeDefaultValue(arguments),
);
}
@override
Instantiator<ArrayBindingPattern> visitArrayBindingPattern(
ArrayBindingPattern node,
) {
List<Instantiator> makeVars = node.variables.map(visit).toList();
return (arguments) => ArrayBindingPattern(splayNodes(makeVars, arguments));
}
@override
Instantiator<ObjectBindingPattern> visitObjectBindingPattern(
ObjectBindingPattern node,
) {
List<Instantiator> makeVars = node.variables.map(visit).toList();
return (arguments) => ObjectBindingPattern(splayNodes(makeVars, arguments));
}
@override
Instantiator<SimpleBindingPattern> visitSimpleBindingPattern(
SimpleBindingPattern node,
) =>
(arguments) => SimpleBindingPattern(Identifier(node.name.name));
}
/// InterpolatedNodeAnalysis determines which AST trees contain
/// [InterpolatedNode]s, and the names of the named interpolated nodes.
class InterpolatedNodeAnalysis extends BaseVisitorVoid {
final Set<Node> containsInterpolatedNode = {};
final Set<String> holeNames = {};
int count = 0;
InterpolatedNodeAnalysis();
bool containsInterpolatedNodes(Node node) =>
containsInterpolatedNode.contains(node);
void visit(Node node) {
node.accept(this);
}
@override
void visitNode(Node node) {
int before = count;
node.visitChildren(this);
if (count != before) containsInterpolatedNode.add(node);
}
@override
void visitInterpolatedNode(InterpolatedNode node) {
containsInterpolatedNode.add(node);
if (node.isNamed) holeNames.add(node.nameOrPosition as String);
++count;
}
}