6fa813d818
Before: Kernel static errors were represented by throwing a distinguished error or else by calling a distinguished library const constructor. Now: Kernel static errors are represented by InvalidExpression. To support error reporting, InvalidExpression has an optional message and a file offset. A back end can choose to signal these errors at any time; for example, when deserializing the binary, or when compiling the procedure containing the static error, or when the erroneous expression is evaluated at run time. InvalidStatement is removed because it can be encoded as ExpressionStatement of InvalidExpression. Future work: * supporting static errors where an expression cannot appear in the AST * allowing InvalidExpression to contain an Expression for error recovery * adding a top-level list of static errors and warnings to the binary Bug: https://github.com/dart-lang/sdk/issues/29840 Change-Id: Ifdfe9a76cee6cefed28061bf245be70531d2f413 Reviewed-on: https://dart-review.googlesource.com/31320 Commit-Queue: Kevin Millikin <kmillikin@google.com> Reviewed-by: Dan Rubel <danrubel@google.com> Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
2154 lines
73 KiB
Dart
2154 lines
73 KiB
Dart
// Copyright (c) 2017, 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.
|
|
library kernel.interpreter;
|
|
|
|
import '../ast.dart';
|
|
import '../ast.dart' as ast show Class;
|
|
|
|
import '../log.dart';
|
|
export '../log.dart';
|
|
|
|
class NotImplemented {
|
|
String message;
|
|
|
|
NotImplemented(this.message);
|
|
|
|
String toString() => message;
|
|
}
|
|
|
|
class Interpreter {
|
|
// The execution of the program starts with empty main environment.
|
|
static MainEnvironment mainEnvironment =
|
|
new MainEnvironment(<Member, Location>{});
|
|
|
|
final Program program;
|
|
final StatementExecuter visitor = new StatementExecuter();
|
|
|
|
Interpreter(this.program);
|
|
|
|
void run() {
|
|
assert(program.libraries.isEmpty);
|
|
Procedure mainMethod = program.mainMethod;
|
|
|
|
if (mainMethod == null) return;
|
|
|
|
Statement statementBlock = mainMethod.function.body;
|
|
ExecConfiguration configuration = new ExecConfiguration(
|
|
statementBlock, new Environment.empty(), new State.initial());
|
|
visitor.trampolinedExecution(configuration);
|
|
}
|
|
}
|
|
|
|
class Location {
|
|
Value value;
|
|
|
|
Location.empty();
|
|
Location(this.value);
|
|
}
|
|
|
|
class Binding {
|
|
final VariableDeclaration variable;
|
|
final Location location;
|
|
|
|
Binding(this.variable, this.location);
|
|
}
|
|
|
|
/// Represents the top level environment that binds previously accessed or set
|
|
/// static fields to the location that stores their value.
|
|
class MainEnvironment {
|
|
final Map<Member, Location> _staticFields;
|
|
|
|
MainEnvironment(this._staticFields);
|
|
|
|
bool contains(Member member) => _staticFields[member] != null;
|
|
|
|
Value lookup(Member member) {
|
|
assert(contains(member));
|
|
return _staticFields[member].value;
|
|
}
|
|
|
|
void updateStore(Member member, Value value) {
|
|
assert(contains(member));
|
|
_staticFields[member].value = value;
|
|
}
|
|
|
|
MainEnvironment extend(Member member, Value value) {
|
|
var newMap = new Map<Member, Location>.from(_staticFields);
|
|
newMap[member] = new Location(value);
|
|
return new MainEnvironment(newMap);
|
|
}
|
|
}
|
|
|
|
class Environment {
|
|
final List<Binding> bindings = <Binding>[];
|
|
final Environment parent;
|
|
|
|
Value get thisInstance {
|
|
return containsThis()
|
|
? lookupThis().value
|
|
: throw "Invalid reference to 'this' expression";
|
|
}
|
|
|
|
Environment.empty() : parent = null;
|
|
Environment(this.parent);
|
|
|
|
bool contains(VariableDeclaration variable) {
|
|
for (Binding b in bindings) {
|
|
if (identical(b.variable, variable)) return true;
|
|
}
|
|
return parent?.contains(variable) ?? false;
|
|
}
|
|
|
|
bool containsThis() {
|
|
for (Binding b in bindings) {
|
|
if (identical(b.variable.name, 'this')) return true;
|
|
}
|
|
return parent?.containsThis() ?? false;
|
|
}
|
|
|
|
Binding lookupBinding(VariableDeclaration variable) {
|
|
assert(contains(variable));
|
|
for (Binding b in bindings) {
|
|
if (identical(b.variable, variable)) return b;
|
|
}
|
|
return parent.lookupBinding(variable);
|
|
}
|
|
|
|
Location lookupThis() {
|
|
assert(containsThis());
|
|
for (Binding b in bindings) {
|
|
if (identical(b.variable.name, 'this')) return b.location;
|
|
}
|
|
return parent.lookupThis();
|
|
}
|
|
|
|
Value lookup(VariableDeclaration variable) {
|
|
return lookupBinding(variable).location.value;
|
|
}
|
|
|
|
void updateStore(VariableDeclaration variable, Value value) {
|
|
assert(contains(variable));
|
|
lookupBinding(variable).location.value = value;
|
|
}
|
|
|
|
Environment extend(VariableDeclaration variable, Value value) {
|
|
assert(!contains(variable));
|
|
return new Environment(this)
|
|
..bindings.add(new Binding(variable, new Location(value)));
|
|
}
|
|
|
|
Environment extendWithThis(ObjectValue v) {
|
|
assert(!containsThis());
|
|
return extend(new VariableDeclaration('this'), v);
|
|
}
|
|
}
|
|
|
|
/// Evaluate expressions.
|
|
class Evaluator extends ExpressionVisitor1<Configuration, EvalConfiguration> {
|
|
Configuration eval(Expression expr, EvalConfiguration config) =>
|
|
expr.accept1(this, config);
|
|
|
|
Configuration evalList(List<InterpreterExpression> list, Environment env,
|
|
ExceptionComponents exceptionComponents, ApplicationContinuation cont) {
|
|
if (list.isNotEmpty) {
|
|
return new EvalConfiguration(
|
|
list.first.expression,
|
|
env,
|
|
exceptionComponents,
|
|
new ExpressionListEK(
|
|
list.first, list.skip(1), env, exceptionComponents, cont));
|
|
}
|
|
return new ApplicationConfiguration(cont, <InterpreterValue>[]);
|
|
}
|
|
|
|
Configuration defaultExpression(Expression node, EvalConfiguration config) {
|
|
throw new NotImplemented('Evaluation for expressions of type '
|
|
'${node.runtimeType} is not implemented.');
|
|
}
|
|
|
|
Configuration visitInvalidExpression1(
|
|
InvalidExpression node, EvalConfiguration config) {
|
|
throw 'Invalid expression at ${node.location.toString()}';
|
|
}
|
|
|
|
Configuration visitVariableGet(VariableGet node, EvalConfiguration config) {
|
|
Value value = config.environment.lookup(node.variable);
|
|
return new ValuePassingConfiguration(config.continuation, value);
|
|
}
|
|
|
|
Configuration visitVariableSet(VariableSet node, EvalConfiguration config) {
|
|
var cont = new VariableSetEK(
|
|
node.variable, config.environment, config.continuation);
|
|
return new EvalConfiguration(
|
|
node.value, config.environment, config.exceptionComponents, cont);
|
|
}
|
|
|
|
Configuration visitPropertyGet(PropertyGet node, EvalConfiguration config) {
|
|
var cont = new PropertyGetEK(
|
|
node.name, config.exceptionComponents, config.continuation);
|
|
return new EvalConfiguration(
|
|
node.receiver, config.environment, config.exceptionComponents, cont);
|
|
}
|
|
|
|
Configuration visitPropertySet(PropertySet node, EvalConfiguration config) {
|
|
var cont = new PropertySetEK(node.value, node.name, config.environment,
|
|
config.exceptionComponents, config.continuation);
|
|
return new EvalConfiguration(
|
|
node.receiver, config.environment, config.exceptionComponents, cont);
|
|
}
|
|
|
|
Configuration visitStaticGet(StaticGet node, EvalConfiguration config) {
|
|
Member member = node.target;
|
|
|
|
if (member is Procedure && !member.isAccessor) {
|
|
// Create a closure for the method tear off.
|
|
var v = new FunctionValue(member.function, new Environment.empty());
|
|
return new ValuePassingConfiguration(config.continuation, v);
|
|
}
|
|
|
|
if (member is Procedure && member.isGetter) {
|
|
// Execute the body of the getter.
|
|
var state = new State.initial()
|
|
.withReturnContinuation(config.continuation)
|
|
.withContinuation(new ExitSK(config.continuation, Value.nullInstance))
|
|
.withException(config.exceptionComponents);
|
|
return new ExecConfiguration(
|
|
member.function.body, new Environment.empty(), state);
|
|
}
|
|
|
|
assert(member is Field);
|
|
if (Interpreter.mainEnvironment.contains(member)) {
|
|
// Read the value for the member in the main environment.
|
|
return new ValuePassingConfiguration(
|
|
config.continuation, Interpreter.mainEnvironment.lookup(member));
|
|
}
|
|
|
|
// Otherwise, the static field is accessed for the first time.
|
|
// We extend the main environment with a new binding.
|
|
Interpreter.mainEnvironment =
|
|
Interpreter.mainEnvironment.extend(member, Value.nullInstance);
|
|
|
|
if ((member as Field).initializer == null) {
|
|
return new ValuePassingConfiguration(
|
|
config.continuation, Value.nullInstance);
|
|
}
|
|
|
|
// The initializer expression is evaluated otherwise.
|
|
var cont = new StaticSetEK(member, config.continuation);
|
|
return new EvalConfiguration((member as Field).initializer,
|
|
new Environment.empty(), config.exceptionComponents, cont);
|
|
}
|
|
|
|
Configuration visitStaticSet(StaticSet node, EvalConfiguration config) {
|
|
Member member = node.target;
|
|
ExpressionContinuation cont;
|
|
|
|
if (member is Procedure) {
|
|
assert(member.isSetter);
|
|
cont = new StaticSetterEK(
|
|
member.function, config.exceptionComponents, config.continuation);
|
|
} else {
|
|
assert(member is Field);
|
|
cont = new StaticSetEK(member, config.continuation);
|
|
}
|
|
|
|
return new EvalConfiguration(
|
|
node.value, config.environment, config.exceptionComponents, cont);
|
|
}
|
|
|
|
Configuration visitStaticInvocation(
|
|
StaticInvocation node, EvalConfiguration config) {
|
|
if ('print' == node.name.toString()) {
|
|
var cont = new PrintEK(config.continuation);
|
|
return new EvalConfiguration(node.arguments.positional.first,
|
|
config.environment, config.exceptionComponents, cont);
|
|
} else {
|
|
log.info('static-invocation-${node.target.name.toString()}\n');
|
|
|
|
List<InterpreterExpression> args =
|
|
_getArgumentExpressions(node.arguments, node.target.function);
|
|
ApplicationContinuation cont = new StaticInvocationA(node.target.function,
|
|
config.exceptionComponents, config.continuation);
|
|
return new EvalListConfiguration(
|
|
args, config.environment, config.exceptionComponents, cont);
|
|
}
|
|
}
|
|
|
|
Configuration visitMethodInvocation(
|
|
MethodInvocation node, EvalConfiguration config) {
|
|
// Currently supports only method invocation with <2 arguments and is used
|
|
// to evaluate implemented operators for int, double and String values.
|
|
var cont = new MethodInvocationEK(node.arguments, node.name,
|
|
config.environment, config.exceptionComponents, config.continuation);
|
|
|
|
return new EvalConfiguration(
|
|
node.receiver, config.environment, config.exceptionComponents, cont);
|
|
}
|
|
|
|
Configuration visitConstructorInvocation(
|
|
ConstructorInvocation node, EvalConfiguration config) {
|
|
ApplicationContinuation cont = new ConstructorInvocationA(
|
|
node.target, config.exceptionComponents, config.continuation);
|
|
var args = _getArgumentExpressions(node.arguments, node.target.function);
|
|
|
|
return new EvalListConfiguration(
|
|
args, config.environment, config.exceptionComponents, cont);
|
|
}
|
|
|
|
Configuration visitNot(Not node, EvalConfiguration config) {
|
|
return new EvalConfiguration(node.operand, config.environment,
|
|
config.exceptionComponents, new NotEK(config.continuation));
|
|
}
|
|
|
|
Configuration visitLogicalExpression(
|
|
LogicalExpression node, EvalConfiguration config) {
|
|
if ('||' == node.operator) {
|
|
var cont = new OrEK(node.right, config.environment,
|
|
config.exceptionComponents, config.continuation);
|
|
return new EvalConfiguration(
|
|
node.left, config.environment, config.exceptionComponents, cont);
|
|
} else {
|
|
assert('&&' == node.operator);
|
|
var cont = new AndEK(node.right, config.environment,
|
|
config.exceptionComponents, config.continuation);
|
|
return new EvalConfiguration(
|
|
node.left, config.environment, config.exceptionComponents, cont);
|
|
}
|
|
}
|
|
|
|
Configuration visitConditionalExpression(
|
|
ConditionalExpression node, EvalConfiguration config) {
|
|
var cont = new ConditionalEK(node.then, node.otherwise, config.environment,
|
|
config.exceptionComponents, config.continuation);
|
|
return new EvalConfiguration(
|
|
node.condition, config.environment, config.exceptionComponents, cont);
|
|
}
|
|
|
|
Configuration visitStringConcatenation(
|
|
StringConcatenation node, EvalConfiguration config) {
|
|
var cont = new StringConcatenationA(config.continuation);
|
|
var expressions = node.expressions
|
|
.map((Expression e) => new PositionalExpression(e))
|
|
.toList();
|
|
return new EvalListConfiguration(
|
|
expressions, config.environment, config.exceptionComponents, cont);
|
|
}
|
|
|
|
Configuration visitThisExpression(
|
|
ThisExpression node, EvalConfiguration config) {
|
|
return new ValuePassingConfiguration(
|
|
config.continuation, config.environment.thisInstance);
|
|
}
|
|
|
|
Configuration visitThrow(Throw node, EvalConfiguration config) {
|
|
var cont = new ThrowEK(config.exceptionComponents);
|
|
|
|
return new EvalConfiguration(
|
|
node.expression, config.environment, config.exceptionComponents, cont);
|
|
}
|
|
|
|
Configuration visitFunctionExpression(
|
|
FunctionExpression node, EvalConfiguration config) {
|
|
var val = new FunctionValue(node.function, config.environment);
|
|
return new ValuePassingConfiguration(config.continuation, val);
|
|
}
|
|
|
|
// Evaluation of BasicLiterals.
|
|
Configuration visitStringLiteral(
|
|
StringLiteral node, EvalConfiguration config) {
|
|
return new ValuePassingConfiguration(
|
|
config.continuation, new StringValue(node.value));
|
|
}
|
|
|
|
Configuration visitIntLiteral(IntLiteral node, EvalConfiguration config) {
|
|
return new ValuePassingConfiguration(
|
|
config.continuation, new IntValue(node.value));
|
|
}
|
|
|
|
Configuration visitDoubleLiteral(
|
|
DoubleLiteral node, EvalConfiguration config) {
|
|
return new ValuePassingConfiguration(
|
|
config.continuation, new DoubleValue(node.value));
|
|
}
|
|
|
|
Configuration visitBoolLiteral(BoolLiteral node, EvalConfiguration config) {
|
|
Value value = node.value ? Value.trueInstance : Value.falseInstance;
|
|
return new ValuePassingConfiguration(config.continuation, value);
|
|
}
|
|
|
|
Configuration visitNullLiteral(NullLiteral node, EvalConfiguration config) {
|
|
return new ValuePassingConfiguration(
|
|
config.continuation, Value.nullInstance);
|
|
}
|
|
|
|
Configuration visitLet(Let node, EvalConfiguration config) {
|
|
var letCont = new LetEK(node.variable, node.body, config.environment,
|
|
config.exceptionComponents, config.continuation);
|
|
return new EvalConfiguration(node.variable.initializer, config.environment,
|
|
config.exceptionComponents, letCont);
|
|
}
|
|
}
|
|
|
|
/// Represents a state for statement execution.
|
|
class State {
|
|
final Label labels;
|
|
// TODO: Add switch labels.
|
|
final ExceptionComponents exceptionComponents;
|
|
final ExpressionContinuation returnContinuation;
|
|
final StatementContinuation continuation;
|
|
|
|
State(this.labels, this.exceptionComponents, this.returnContinuation,
|
|
this.continuation);
|
|
|
|
State.initial()
|
|
: labels = null,
|
|
exceptionComponents = new ExceptionComponents.initial(),
|
|
returnContinuation = null,
|
|
continuation = null;
|
|
|
|
State withBreak(Statement stmt, Environment env) {
|
|
var cont = new BreakBK(continuation, env);
|
|
Label breakLabels = new Label(stmt, cont, labels);
|
|
return new State(
|
|
breakLabels, exceptionComponents, returnContinuation, continuation);
|
|
}
|
|
|
|
State withReturnContinuation(ExpressionContinuation returnCont) {
|
|
return new State(labels, exceptionComponents, returnCont, continuation);
|
|
}
|
|
|
|
State withContinuation(StatementContinuation cont) {
|
|
return new State(labels, exceptionComponents, returnContinuation, cont);
|
|
}
|
|
|
|
State withException(ExceptionComponents ecs) {
|
|
return new State(labels, ecs, returnContinuation, continuation);
|
|
}
|
|
|
|
Label lookupLabel(LabeledStatement s) {
|
|
assert(labels != null);
|
|
return labels.lookupLabel(s);
|
|
}
|
|
}
|
|
|
|
/// Represents a labeled statement, the corresponding continuation and the
|
|
/// enclosing label.
|
|
class Label {
|
|
final LabeledStatement statement;
|
|
final BreakContinuation continuation;
|
|
final Label enclosingLabel;
|
|
|
|
Label(this.statement, this.continuation, this.enclosingLabel);
|
|
|
|
Label lookupLabel(LabeledStatement s) {
|
|
if (identical(s, statement)) return this;
|
|
assert(enclosingLabel != null);
|
|
return enclosingLabel.lookupLabel(s);
|
|
}
|
|
|
|
// Recursively install finally break to all labels.
|
|
Label withFinalizer(Statement finalizer, Environment env, State state) {
|
|
var label = enclosingLabel?.withFinalizer(finalizer, env, state);
|
|
var finallyCont = new FinallyBK(finalizer, env, state, continuation);
|
|
return new Label(statement, finallyCont, label);
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Configurations
|
|
// ------------------------------------------------------------------------
|
|
|
|
abstract class Configuration {
|
|
/// Executes the current and returns the next configuration.
|
|
Configuration step(StatementExecuter executer);
|
|
}
|
|
|
|
/// Configuration for evaluating an [Expression].
|
|
class EvalConfiguration extends Configuration {
|
|
final Expression expression;
|
|
|
|
/// Environment in which the expression is evaluated.
|
|
final Environment environment;
|
|
|
|
/// Exception components.
|
|
final ExceptionComponents exceptionComponents;
|
|
|
|
/// Next continuation to be applied.
|
|
final Continuation continuation;
|
|
|
|
EvalConfiguration(this.expression, this.environment, this.exceptionComponents,
|
|
this.continuation);
|
|
|
|
Configuration step(StatementExecuter executer) =>
|
|
executer.eval(expression, this);
|
|
}
|
|
|
|
/// Configuration for evaluating a `List<InterpreterExpression>`.
|
|
class EvalListConfiguration extends Configuration {
|
|
final List<InterpreterExpression> expressions;
|
|
final Environment environment;
|
|
final ExceptionComponents exceptionComponents;
|
|
final ApplicationContinuation continuation;
|
|
|
|
EvalListConfiguration(this.expressions, this.environment,
|
|
this.exceptionComponents, this.continuation);
|
|
|
|
Configuration step(StatementExecuter executer) => executer.evalList(
|
|
expressions, environment, exceptionComponents, continuation);
|
|
}
|
|
|
|
/// Configuration for execution of a [Statement].
|
|
class ExecConfiguration extends Configuration {
|
|
final Statement currentStatement;
|
|
final Environment environment;
|
|
final State state;
|
|
|
|
ExecConfiguration(this.currentStatement, this.environment, this.state);
|
|
|
|
Configuration step(StatementExecuter executer) {
|
|
return executer.exec(currentStatement, this);
|
|
}
|
|
}
|
|
|
|
/// Configuration for applying a [StatementContinuation] to an [Environment].
|
|
class ForwardConfiguration extends Configuration {
|
|
final StatementContinuation continuation;
|
|
final Environment environment;
|
|
|
|
ForwardConfiguration(this.continuation, this.environment);
|
|
|
|
Configuration step(StatementExecuter _) => continuation?.call(environment);
|
|
}
|
|
|
|
/// Configuration for applying [ExpressionContinuation] to a [Value].
|
|
class ValuePassingConfiguration extends Configuration {
|
|
final ExpressionContinuation continuation;
|
|
final Value value;
|
|
|
|
ValuePassingConfiguration(this.continuation, this.value);
|
|
|
|
Configuration step(StatementExecuter _) => continuation(value);
|
|
}
|
|
|
|
/// Configuration for applying an [ApplicationContinuation] to a
|
|
/// `List<InterpreterValue>`.
|
|
class ApplicationConfiguration extends Configuration {
|
|
final ApplicationContinuation continuation;
|
|
final List<InterpreterValue> values;
|
|
|
|
ApplicationConfiguration(this.continuation, this.values);
|
|
|
|
Configuration step(StatementExecuter _) => continuation(values);
|
|
}
|
|
|
|
/// Configuration for applying an [ExceptionHandler] to an exception and a
|
|
/// stack trace.
|
|
class ThrowConfiguration extends Configuration {
|
|
final ExceptionHandler handler;
|
|
final Value exception;
|
|
final StackTrace stacktrace;
|
|
|
|
ThrowConfiguration(this.handler, this.exception, this.stacktrace);
|
|
|
|
Configuration step(StatementExecuter _) => handler(exception, stacktrace);
|
|
}
|
|
|
|
class BreakConfiguration extends Configuration {
|
|
final BreakContinuation continuation;
|
|
|
|
BreakConfiguration(this.continuation);
|
|
|
|
Configuration step(StatementExecuter _) => continuation();
|
|
}
|
|
|
|
abstract class BreakContinuation extends Continuation {
|
|
Configuration call();
|
|
}
|
|
|
|
class BreakBK extends BreakContinuation {
|
|
final StatementContinuation continuation;
|
|
final Environment environment;
|
|
|
|
BreakBK(this.continuation, this.environment);
|
|
|
|
Configuration call() => new ForwardConfiguration(continuation, environment);
|
|
}
|
|
|
|
class FinallyBK extends BreakContinuation {
|
|
final Statement finalizer;
|
|
final Environment environment;
|
|
final State state;
|
|
final BreakContinuation continuation;
|
|
|
|
FinallyBK(this.finalizer, this.environment, this.state, this.continuation);
|
|
|
|
Configuration call() {
|
|
var cont = new BreakSK(continuation);
|
|
return new ExecConfiguration(
|
|
finalizer, environment, state.withContinuation(cont));
|
|
}
|
|
}
|
|
|
|
class BreakSK extends StatementContinuation {
|
|
final BreakContinuation continuation;
|
|
|
|
BreakSK(this.continuation);
|
|
|
|
Configuration call(Environment _) => new BreakConfiguration(continuation);
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Interpreter Expressions and Values
|
|
// ------------------------------------------------------------------------
|
|
|
|
abstract class InterpreterExpression {
|
|
Expression get expression;
|
|
|
|
InterpreterValue assignValue(Value v);
|
|
}
|
|
|
|
class PositionalExpression extends InterpreterExpression {
|
|
final Expression expression;
|
|
|
|
PositionalExpression(this.expression);
|
|
|
|
InterpreterValue assignValue(Value v) => new PositionalValue(v);
|
|
}
|
|
|
|
class NamedExpression extends InterpreterExpression {
|
|
final String name;
|
|
final Expression expression;
|
|
|
|
NamedExpression(this.name, this.expression);
|
|
InterpreterValue assignValue(Value v) => new NamedValue(name, v);
|
|
}
|
|
|
|
class FieldInitializerExpression extends InterpreterExpression {
|
|
final Field field;
|
|
final Expression expression;
|
|
|
|
FieldInitializerExpression(this.field, this.expression);
|
|
|
|
InterpreterValue assignValue(Value v) => new FieldInitializerValue(field, v);
|
|
}
|
|
|
|
abstract class InterpreterValue {
|
|
Value get value;
|
|
}
|
|
|
|
class PositionalValue extends InterpreterValue {
|
|
final Value value;
|
|
|
|
PositionalValue(this.value);
|
|
}
|
|
|
|
class NamedValue extends InterpreterValue {
|
|
final String name;
|
|
final Value value;
|
|
|
|
NamedValue(this.name, this.value);
|
|
}
|
|
|
|
class FieldInitializerValue extends InterpreterValue {
|
|
final Field field;
|
|
final Value value;
|
|
|
|
FieldInitializerValue(this.field, this.value);
|
|
}
|
|
|
|
abstract class Continuation {}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Statement Continuations
|
|
// ------------------------------------------------------------------------
|
|
|
|
/// Represents a the continuation for the execution of the next statement of
|
|
/// the program.
|
|
///
|
|
/// There are various kinds of [StatementContinuation]s and their names are
|
|
/// suffixed with "SK".
|
|
abstract class StatementContinuation extends Continuation {
|
|
Configuration call(Environment env);
|
|
}
|
|
|
|
/// Applies the expression continuation to the provided value.
|
|
class ExitSK extends StatementContinuation {
|
|
final ExpressionContinuation continuation;
|
|
final Value value;
|
|
|
|
ExitSK(this.continuation, this.value);
|
|
|
|
Configuration call(Environment _) =>
|
|
new ValuePassingConfiguration(continuation, value);
|
|
}
|
|
|
|
/// Executes the next statement from a block with the corresponding environment
|
|
/// or proceeds with next statement continuation.
|
|
class BlockSK extends StatementContinuation {
|
|
final List<Statement> statements;
|
|
final Environment enclosingEnv;
|
|
final State state;
|
|
|
|
BlockSK(this.statements, this.enclosingEnv, this.state);
|
|
|
|
BlockSK.fromConfig(this.statements, ExecConfiguration conf)
|
|
: enclosingEnv = conf.environment,
|
|
state = conf.state;
|
|
|
|
Configuration call(Environment env) {
|
|
if (statements.isEmpty) {
|
|
return new ForwardConfiguration(state.continuation, enclosingEnv);
|
|
}
|
|
// Proceed with the execution statement when there are some remaining to
|
|
// be executed.
|
|
var cont = new BlockSK(statements.skip(1).toList(), enclosingEnv, state);
|
|
return new ExecConfiguration(
|
|
statements.first, env, state.withContinuation(cont));
|
|
}
|
|
}
|
|
|
|
class WhileConditionSK extends StatementContinuation {
|
|
final Expression condition;
|
|
final Statement body;
|
|
final Environment enclosingEnv;
|
|
final State state;
|
|
|
|
WhileConditionSK(this.condition, this.body, this.enclosingEnv, this.state);
|
|
|
|
Configuration call(Environment _) {
|
|
// Evaluate the condition for the while loop execution.
|
|
var cont = new WhileConditionEK(condition, body, enclosingEnv, state);
|
|
return new EvalConfiguration(
|
|
condition, enclosingEnv, state.exceptionComponents, cont);
|
|
}
|
|
}
|
|
|
|
/// Applies the expression continuation to the provided value.
|
|
class NewSK extends StatementContinuation {
|
|
final ExpressionContinuation continuation;
|
|
final Location location;
|
|
|
|
NewSK(this.continuation, this.location);
|
|
|
|
Configuration call(Environment _) =>
|
|
new ValuePassingConfiguration(continuation, location.value);
|
|
}
|
|
|
|
class ConstructorBodySK extends StatementContinuation {
|
|
final Statement body;
|
|
final Environment environment;
|
|
final ExceptionComponents exceptionComponents;
|
|
final StatementContinuation continuation;
|
|
|
|
ConstructorBodySK(
|
|
this.body, this.environment, this.exceptionComponents, this.continuation);
|
|
|
|
Configuration call(Environment _) {
|
|
return new ExecConfiguration(body, environment,
|
|
new State(null, exceptionComponents, null, continuation));
|
|
}
|
|
}
|
|
|
|
/// Represents the statement continuation for execution of the finalizer
|
|
/// statement.
|
|
class FinallySK extends StatementContinuation {
|
|
final Statement finallyStatement;
|
|
final Environment environment;
|
|
final State state;
|
|
|
|
FinallySK(this.finallyStatement, this.environment, this.state);
|
|
Configuration call(Environment _) {
|
|
return new ExecConfiguration(finallyStatement, environment, state);
|
|
}
|
|
}
|
|
|
|
/// Represents the statement continuation that applies the captured handler to
|
|
/// the current exception.
|
|
///
|
|
/// It is used as next statement continuation for the execution of the finalizer
|
|
/// statement in [TryFinally] to ensure the finalizer is executed before
|
|
/// applying the next handler to the current exception.
|
|
class RethrowSK extends StatementContinuation {
|
|
final ExceptionHandler handler;
|
|
final Value exception;
|
|
final StackTrace stackTrace;
|
|
|
|
RethrowSK(this.handler, this.exception, this.stackTrace);
|
|
|
|
Configuration call(Environment _) {
|
|
return new ThrowConfiguration(handler, exception, stackTrace);
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Application Continuations
|
|
// ------------------------------------------------------------------------
|
|
/// Represents the continuation called after the evaluation of argument
|
|
/// expressions.
|
|
///
|
|
/// There are various kinds of [ApplicationContinuation] and their names are
|
|
/// suffixed with "A".
|
|
abstract class ApplicationContinuation extends Continuation {
|
|
Configuration call(List<InterpreterValue> values);
|
|
|
|
/// Binds actual argument values to formal parameters of the function in a
|
|
/// new environment or in the provided initial environment.
|
|
/// TODO: Add checks for validation of arguments according to spec.
|
|
static Environment createEnvironment(
|
|
FunctionNode function, List<InterpreterValue> args,
|
|
[Environment parentEnv]) {
|
|
Environment newEnv = new Environment(parentEnv ?? new Environment.empty());
|
|
|
|
List<PositionalValue> positional = args.reversed
|
|
.where((InterpreterValue av) => av is PositionalValue)
|
|
.toList();
|
|
|
|
// Add positional parameters.
|
|
for (int i = 0; i < positional.length; ++i) {
|
|
newEnv =
|
|
newEnv.extend(function.positionalParameters[i], positional[i].value);
|
|
}
|
|
|
|
Map<String, Value> named = <String, Value>{};
|
|
for (InterpreterValue argValue in args) {
|
|
if (argValue is NamedValue) {
|
|
named[argValue.name] = argValue.value;
|
|
}
|
|
}
|
|
|
|
// Add named parameters.
|
|
for (VariableDeclaration v in function.namedParameters) {
|
|
newEnv = newEnv.extend(v, named[v.name.toString()]);
|
|
}
|
|
|
|
return newEnv;
|
|
}
|
|
}
|
|
|
|
/// Represents the application continuation called after the evaluation of all
|
|
/// argument expressions for an invocation.
|
|
class ValueA extends ApplicationContinuation {
|
|
final InterpreterValue value;
|
|
final ApplicationContinuation applicationContinuation;
|
|
|
|
ValueA(this.value, this.applicationContinuation);
|
|
|
|
Configuration call(List<InterpreterValue> args) {
|
|
args.add(value);
|
|
return new ApplicationConfiguration(applicationContinuation, args);
|
|
}
|
|
}
|
|
|
|
class StringConcatenationA extends ApplicationContinuation {
|
|
final ExpressionContinuation continuation;
|
|
|
|
StringConcatenationA(this.continuation);
|
|
|
|
Configuration call(List<InterpreterValue> values) {
|
|
StringBuffer result = new StringBuffer();
|
|
for (InterpreterValue v in values.reversed) {
|
|
result.write(v.value.value);
|
|
}
|
|
return new ValuePassingConfiguration(
|
|
continuation, new StringValue(result.toString()));
|
|
}
|
|
}
|
|
|
|
/// Represents the application continuation for static invocation.
|
|
class StaticInvocationA extends ApplicationContinuation {
|
|
final FunctionNode function;
|
|
final ExceptionComponents exceptionComponents;
|
|
final ExpressionContinuation continuation;
|
|
|
|
StaticInvocationA(this.function, this.exceptionComponents, this.continuation);
|
|
|
|
Configuration call(List<InterpreterValue> argValues) {
|
|
Environment functionEnv =
|
|
ApplicationContinuation.createEnvironment(function, argValues);
|
|
State bodyState = new State(null, exceptionComponents, continuation,
|
|
new ExitSK(continuation, Value.nullInstance));
|
|
|
|
return new ExecConfiguration(function.body, functionEnv, bodyState);
|
|
}
|
|
}
|
|
|
|
/// Represents the application continuation for constructor invocation applied
|
|
/// on the list of evaluated arguments when a constructor is invoked with new.
|
|
///
|
|
/// It creates the newly allocated object instance.
|
|
class ConstructorInvocationA extends ApplicationContinuation {
|
|
final Constructor constructor;
|
|
final ExceptionComponents exceptionComponents;
|
|
final ExpressionContinuation continuation;
|
|
|
|
ConstructorInvocationA(
|
|
this.constructor, this.exceptionComponents, this.continuation);
|
|
|
|
Configuration call(List<InterpreterValue> argValues) {
|
|
Environment ctrEnv = ApplicationContinuation.createEnvironment(
|
|
constructor.function, argValues);
|
|
var class_ = new Class(constructor.enclosingClass.reference);
|
|
var newObject = new ObjectValue(class_);
|
|
var cont = new InitializationEK(constructor, ctrEnv, exceptionComponents,
|
|
new NewSK(continuation, new Location(newObject)));
|
|
|
|
return new ValuePassingConfiguration(cont, newObject);
|
|
}
|
|
}
|
|
|
|
/// Represents the application continuation for constructor invocation applied
|
|
/// on the list of evaluated arguments when a constructor is invoked in a
|
|
/// constructor initializer list with a [RedirectingInitializer] or
|
|
/// [SuperInitializer].
|
|
class ConstructorInitializerA extends ApplicationContinuation {
|
|
final Constructor constructor;
|
|
final Location location;
|
|
final ExceptionComponents exceptionComponents;
|
|
final ConstructorBodySK continuation;
|
|
|
|
ConstructorInitializerA(this.constructor, this.location,
|
|
this.exceptionComponents, this.continuation);
|
|
|
|
Configuration call(List<InterpreterValue> vs) {
|
|
Environment ctrEnv =
|
|
ApplicationContinuation.createEnvironment(constructor.function, vs);
|
|
var cont = new InitializationEK(
|
|
constructor, ctrEnv, exceptionComponents, continuation);
|
|
|
|
return new ValuePassingConfiguration(cont, location.value);
|
|
}
|
|
}
|
|
|
|
/// Represents the application continuation applied on the list of evaluated
|
|
/// field initializer expressions.
|
|
class InstanceFieldsA extends ApplicationContinuation {
|
|
final Constructor constructor;
|
|
final Location location;
|
|
final Environment environment;
|
|
final ExceptionComponents exceptionComponents;
|
|
final ConstructorBodySK continuation;
|
|
|
|
final Class _currentClass;
|
|
|
|
InstanceFieldsA(this.constructor, this.location, this.environment,
|
|
this.exceptionComponents, this.continuation)
|
|
: _currentClass = new Class(constructor.enclosingClass.reference);
|
|
|
|
Configuration call(List<InterpreterValue> fieldValues) {
|
|
for (FieldInitializerValue f in fieldValues) {
|
|
// Directly set the field with the corresponding implicit setter.
|
|
_currentClass.implicitSetters[f.field.name](location.value, f.value);
|
|
}
|
|
|
|
if (constructor.initializers.length == 0) {
|
|
// This can happen when initializing fields of a constructor with an empty
|
|
// initializer list.
|
|
return new ForwardConfiguration(continuation, environment);
|
|
}
|
|
|
|
if (constructor.initializers.first is SuperInitializer) {
|
|
// Target constructor is from the superclass `object`.
|
|
if (_currentClass.superclass.superclass == null) {
|
|
// TODO(zhivkag): Execute the constructor when support for
|
|
// native/external functions is added.
|
|
_initializeNullFields(_currentClass, location.value);
|
|
return new ForwardConfiguration(continuation, environment);
|
|
}
|
|
|
|
return _createEvalListConfig(constructor.initializers.first);
|
|
}
|
|
|
|
// Otherwise, the next expression from Field or Local initializers will be
|
|
// evaluated.
|
|
return _createEvalConfig(constructor.initializers.first);
|
|
}
|
|
|
|
Configuration _createEvalListConfig(SuperInitializer initializer) {
|
|
List<InterpreterExpression> args = _getArgumentExpressions(
|
|
initializer.arguments, initializer.target.function);
|
|
var cont = new ConstructorInitializerA(
|
|
initializer.target, location, exceptionComponents, continuation);
|
|
|
|
return new EvalListConfiguration(
|
|
args, environment, exceptionComponents, cont);
|
|
}
|
|
|
|
EvalConfiguration _createEvalConfig(Initializer initializer) {
|
|
Expression expr = (initializer is FieldInitializer)
|
|
? initializer.value
|
|
: (initializer as LocalInitializer).variable.initializer;
|
|
|
|
// We start with index = 0 since we are evaluating the expression for the
|
|
// first initializer in the initializer list.
|
|
var cont = new InitializerListEK(constructor, 0, location, environment,
|
|
exceptionComponents, continuation);
|
|
return new EvalConfiguration(expr, environment, exceptionComponents, cont);
|
|
}
|
|
}
|
|
|
|
class FunctionValueA extends ApplicationContinuation {
|
|
final FunctionValue receiver;
|
|
final ExceptionComponents exceptionComponents;
|
|
final ExpressionContinuation returnContinuation;
|
|
|
|
FunctionValueA(
|
|
this.receiver, this.exceptionComponents, this.returnContinuation);
|
|
|
|
Configuration call(List<InterpreterValue> vs) {
|
|
Environment env = ApplicationContinuation.createEnvironment(
|
|
receiver.function, vs, receiver.environment);
|
|
var scont = new ExitSK(returnContinuation, Value.nullInstance);
|
|
var state = new State(null, exceptionComponents, returnContinuation, scont);
|
|
return new ExecConfiguration(receiver.function.body, env, state);
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Expression Continuations
|
|
// ------------------------------------------------------------------------
|
|
|
|
/// Represents an expression continuation.
|
|
///
|
|
/// There are various kinds of [ExpressionContinuation]s and their names are
|
|
/// suffixed with "EK".
|
|
abstract class ExpressionContinuation extends Continuation {
|
|
Configuration call(Value v);
|
|
}
|
|
|
|
/// Represents a continuation that returns the next [ExecConfiguration]
|
|
/// to be executed.
|
|
class ExpressionEK extends ExpressionContinuation {
|
|
final StatementContinuation continuation;
|
|
final Environment environment;
|
|
|
|
ExpressionEK(this.continuation, this.environment);
|
|
|
|
Configuration call(Value _) {
|
|
return new ForwardConfiguration(continuation, environment);
|
|
}
|
|
}
|
|
|
|
class PrintEK extends ExpressionContinuation {
|
|
final ExpressionContinuation continuation;
|
|
|
|
PrintEK(this.continuation);
|
|
|
|
Configuration call(Value v) {
|
|
log.info('print(${v.value.runtimeType}: ${v.value})\n');
|
|
print(v.value);
|
|
return new ValuePassingConfiguration(continuation, Value.nullInstance);
|
|
}
|
|
}
|
|
|
|
class PropertyGetEK extends ExpressionContinuation {
|
|
final Name name;
|
|
final ExceptionComponents exceptionComponents;
|
|
final ExpressionContinuation continuation;
|
|
|
|
PropertyGetEK(this.name, this.exceptionComponents, this.continuation);
|
|
|
|
Configuration call(Value receiver) {
|
|
if (receiver.class_.isMethod(name)) {
|
|
var method = receiver.class_.lookup(name);
|
|
// Return the function value with this bound to receiver.
|
|
var env = new Environment.empty().extendWithThis(receiver);
|
|
return new ValuePassingConfiguration(
|
|
continuation, new FunctionValue(method, env));
|
|
}
|
|
|
|
if (receiver.class_.isGetter(name)) {
|
|
var getter = receiver.class_.lookup(name);
|
|
var state = new State.initial()
|
|
.withReturnContinuation(continuation)
|
|
.withContinuation(new ExitSK(continuation, Value.nullInstance))
|
|
.withException(exceptionComponents);
|
|
return new ExecConfiguration(
|
|
getter.body, new Environment.empty().extendWithThis(receiver), state);
|
|
}
|
|
|
|
Value propertyValue = receiver.class_.lookupImplicitGetter(name)(receiver);
|
|
return new ValuePassingConfiguration(continuation, propertyValue);
|
|
}
|
|
}
|
|
|
|
class PropertySetEK extends ExpressionContinuation {
|
|
final Expression value;
|
|
final Name setterName;
|
|
final Environment environment;
|
|
final ExceptionComponents exceptionComponents;
|
|
final ExpressionContinuation continuation;
|
|
|
|
PropertySetEK(this.value, this.setterName, this.environment,
|
|
this.exceptionComponents, this.continuation);
|
|
|
|
Configuration call(Value receiver) {
|
|
var cont =
|
|
new SetterEK(receiver, setterName, exceptionComponents, continuation);
|
|
return new EvalConfiguration(value, environment, exceptionComponents, cont);
|
|
}
|
|
}
|
|
|
|
class SetterEK extends ExpressionContinuation {
|
|
final Value receiver;
|
|
final Name name;
|
|
final ExceptionComponents exceptionComponents;
|
|
final ExpressionContinuation continuation;
|
|
|
|
SetterEK(
|
|
this.receiver, this.name, this.exceptionComponents, this.continuation);
|
|
|
|
Configuration call(Value v) {
|
|
if (receiver.class_.isSetter(name)) {
|
|
var setter = receiver.class_.lookup(name);
|
|
var env = new Environment.empty()
|
|
.extendWithThis(receiver)
|
|
.extend(setter.positionalParameters.first, v);
|
|
var state = new State.initial()
|
|
.withReturnContinuation(continuation)
|
|
.withContinuation(new ExitSK(continuation, Value.nullInstance))
|
|
.withException(exceptionComponents);
|
|
return new ExecConfiguration(setter.body, env, state);
|
|
}
|
|
|
|
Setter setter = receiver.class_.lookupImplicitSetter(name);
|
|
setter(receiver, v);
|
|
return new ValuePassingConfiguration(continuation, v);
|
|
}
|
|
}
|
|
|
|
class StaticSetEK extends ExpressionContinuation {
|
|
final Member member;
|
|
final ExpressionContinuation continuation;
|
|
|
|
StaticSetEK(this.member, this.continuation);
|
|
|
|
Configuration call(Value v) {
|
|
if (Interpreter.mainEnvironment.contains(member)) {
|
|
Interpreter.mainEnvironment.updateStore(member, v);
|
|
} else {
|
|
Interpreter.mainEnvironment =
|
|
Interpreter.mainEnvironment.extend(member, v);
|
|
}
|
|
return new ValuePassingConfiguration(continuation, v);
|
|
}
|
|
}
|
|
|
|
class StaticSetterEK extends ExpressionContinuation {
|
|
final FunctionNode setter;
|
|
final ExceptionComponents exceptionComponents;
|
|
final ExpressionContinuation expressionContinuation;
|
|
|
|
StaticSetterEK(
|
|
this.setter, this.exceptionComponents, this.expressionContinuation);
|
|
|
|
Configuration call(Value v) {
|
|
VariableDeclaration arg = setter.positionalParameters.first;
|
|
var env = new Environment.empty().extend(arg, v);
|
|
var state = new State.initial()
|
|
.withException(exceptionComponents)
|
|
.withReturnContinuation(expressionContinuation)
|
|
.withContinuation(
|
|
new ExitSK(expressionContinuation, Value.nullInstance));
|
|
|
|
return new ExecConfiguration(setter.body, env, state);
|
|
}
|
|
}
|
|
|
|
/// Represents a continuation to be called after the evaluation of an actual
|
|
/// argument for function invocation.
|
|
class ExpressionListEK extends ExpressionContinuation {
|
|
final InterpreterExpression currentExpression;
|
|
final List<InterpreterExpression> expressions;
|
|
final Environment environment;
|
|
final ExceptionComponents exceptionComponents;
|
|
final ApplicationContinuation applicationContinuation;
|
|
|
|
ExpressionListEK(this.currentExpression, this.expressions, this.environment,
|
|
this.exceptionComponents, this.applicationContinuation);
|
|
|
|
Configuration call(Value v) {
|
|
ValueA app =
|
|
new ValueA(currentExpression.assignValue(v), applicationContinuation);
|
|
return new EvalListConfiguration(
|
|
expressions, environment, exceptionComponents, app);
|
|
}
|
|
}
|
|
|
|
class MethodInvocationEK extends ExpressionContinuation {
|
|
final Arguments arguments;
|
|
final Name methodName;
|
|
final Environment environment;
|
|
final ExceptionComponents exceptionComponents;
|
|
final ExpressionContinuation continuation;
|
|
|
|
MethodInvocationEK(this.arguments, this.methodName, this.environment,
|
|
this.exceptionComponents, this.continuation);
|
|
|
|
Configuration call(Value receiver) {
|
|
if (receiver is LiteralValue) {
|
|
// TODO(zhivkag): CPS method invocation for literals
|
|
if (arguments.positional.isEmpty) {
|
|
Value returnValue = receiver.invokeMethod(methodName);
|
|
return new ValuePassingConfiguration(continuation, returnValue);
|
|
}
|
|
var cont = new ArgumentsEK(
|
|
receiver, methodName, arguments, environment, continuation);
|
|
|
|
return new EvalConfiguration(
|
|
arguments.positional.first, environment, exceptionComponents, cont);
|
|
}
|
|
|
|
FunctionValue fun;
|
|
if (receiver is FunctionValue) {
|
|
// TODO(zhivkag): Throw an exception when method is not call.
|
|
assert(methodName.toString() == "call");
|
|
fun = receiver;
|
|
} else {
|
|
assert(receiver.class_.isMethod(methodName));
|
|
fun = new FunctionValue(receiver.class_.lookup(methodName),
|
|
new Environment.empty().extendWithThis(receiver));
|
|
}
|
|
|
|
var args = _getArgumentExpressions(arguments, fun.function);
|
|
var acont = new FunctionValueA(receiver, exceptionComponents, continuation);
|
|
return new EvalListConfiguration(
|
|
args, environment, exceptionComponents, acont);
|
|
}
|
|
}
|
|
|
|
class ArgumentsEK extends ExpressionContinuation {
|
|
final Value receiver;
|
|
final Name methodName;
|
|
final Arguments arguments;
|
|
final Environment environment;
|
|
final ExpressionContinuation continuation;
|
|
|
|
ArgumentsEK(this.receiver, this.methodName, this.arguments, this.environment,
|
|
this.continuation);
|
|
|
|
Configuration call(Value value) {
|
|
// Currently evaluates only one argument, for simple method invocations
|
|
// with 1 argument.
|
|
Value returnValue = receiver.invokeMethod(methodName, value);
|
|
return new ValuePassingConfiguration(continuation, returnValue);
|
|
}
|
|
}
|
|
|
|
class VariableSetEK extends ExpressionContinuation {
|
|
final VariableDeclaration variable;
|
|
final Environment environment;
|
|
final ExpressionContinuation continuation;
|
|
|
|
VariableSetEK(this.variable, this.environment, this.continuation);
|
|
|
|
Configuration call(Value value) {
|
|
environment.updateStore(variable, value);
|
|
return new ValuePassingConfiguration(continuation, value);
|
|
}
|
|
}
|
|
|
|
class NotEK extends ExpressionContinuation {
|
|
final ExpressionContinuation continuation;
|
|
|
|
NotEK(this.continuation);
|
|
|
|
Configuration call(Value value) {
|
|
Value notValue = identical(Value.trueInstance, value)
|
|
? Value.falseInstance
|
|
: Value.trueInstance;
|
|
return new ValuePassingConfiguration(continuation, notValue);
|
|
}
|
|
}
|
|
|
|
class OrEK extends ExpressionContinuation {
|
|
final Expression right;
|
|
final Environment environment;
|
|
final ExceptionComponents exceptionComponents;
|
|
final ExpressionContinuation continuation;
|
|
|
|
OrEK(this.right, this.environment, this.exceptionComponents,
|
|
this.continuation);
|
|
|
|
Configuration call(Value left) {
|
|
return identical(Value.trueInstance, left)
|
|
? new ValuePassingConfiguration(continuation, Value.trueInstance)
|
|
: new EvalConfiguration(
|
|
right, environment, exceptionComponents, continuation);
|
|
}
|
|
}
|
|
|
|
class AndEK extends ExpressionContinuation {
|
|
final Expression right;
|
|
final Environment environment;
|
|
final ExceptionComponents exceptionComponents;
|
|
final ExpressionContinuation continuation;
|
|
|
|
AndEK(this.right, this.environment, this.exceptionComponents,
|
|
this.continuation);
|
|
|
|
Configuration call(Value left) {
|
|
return identical(Value.falseInstance, left)
|
|
? new ValuePassingConfiguration(continuation, Value.falseInstance)
|
|
: new EvalConfiguration(
|
|
right, environment, exceptionComponents, continuation);
|
|
}
|
|
}
|
|
|
|
class ConditionalEK extends ExpressionContinuation {
|
|
final Expression then;
|
|
final Expression otherwise;
|
|
final Environment environment;
|
|
final ExceptionComponents exceptionComponents;
|
|
final ExpressionContinuation continuation;
|
|
|
|
ConditionalEK(this.then, this.otherwise, this.environment,
|
|
this.exceptionComponents, this.continuation);
|
|
|
|
Configuration call(Value value) {
|
|
return identical(Value.trueInstance, value)
|
|
? new EvalConfiguration(
|
|
then, environment, exceptionComponents, continuation)
|
|
: new EvalConfiguration(
|
|
otherwise, environment, exceptionComponents, continuation);
|
|
}
|
|
}
|
|
|
|
class LetEK extends ExpressionContinuation {
|
|
final VariableDeclaration variable;
|
|
final Expression letBody;
|
|
final Environment environment;
|
|
final ExceptionComponents exceptionComponents;
|
|
final ExpressionContinuation continuation;
|
|
|
|
LetEK(this.variable, this.letBody, this.environment, this.exceptionComponents,
|
|
this.continuation);
|
|
|
|
Configuration call(Value value) {
|
|
var letEnv = environment.extend(variable, value);
|
|
return new EvalConfiguration(
|
|
letBody, letEnv, exceptionComponents, continuation);
|
|
}
|
|
}
|
|
|
|
/// Represents the continuation for the condition expression in [WhileStatement].
|
|
class WhileConditionEK extends ExpressionContinuation {
|
|
final Expression condition;
|
|
final Statement body;
|
|
final Environment enclosingEnv;
|
|
final State state;
|
|
|
|
WhileConditionEK(this.condition, this.body, this.enclosingEnv, this.state);
|
|
|
|
Configuration call(Value v) {
|
|
if (identical(v, Value.falseInstance)) {
|
|
return new ForwardConfiguration(state.continuation, enclosingEnv);
|
|
}
|
|
var cont = new WhileConditionSK(condition, body, enclosingEnv, state);
|
|
return new ExecConfiguration(
|
|
body, enclosingEnv, state.withContinuation(cont));
|
|
}
|
|
}
|
|
|
|
/// Represents the continuation for the condition expression in [IfStatement].
|
|
class IfConditionEK extends ExpressionContinuation {
|
|
final Statement then;
|
|
final Statement otherwise;
|
|
final Environment environment;
|
|
final State state;
|
|
|
|
IfConditionEK(this.then, this.otherwise, this.environment, this.state);
|
|
|
|
Configuration call(Value v) {
|
|
if (identical(v, Value.trueInstance)) {
|
|
log.info("if-then\n");
|
|
return new ExecConfiguration(then, environment, state);
|
|
} else if (otherwise != null) {
|
|
log.info("if-otherwise\n");
|
|
return new ExecConfiguration(otherwise, environment, state);
|
|
}
|
|
return new ForwardConfiguration(state.continuation, environment);
|
|
}
|
|
}
|
|
|
|
/// Represents the continuation for the initializer expression in
|
|
/// [VariableDeclaration].
|
|
class VariableInitializerEK extends ExpressionContinuation {
|
|
final VariableDeclaration variable;
|
|
final Environment environment;
|
|
final StatementContinuation continuation;
|
|
VariableInitializerEK(this.variable, this.environment, this.continuation);
|
|
|
|
Configuration call(Value v) {
|
|
return new ForwardConfiguration(
|
|
continuation, environment.extend(variable, v));
|
|
}
|
|
}
|
|
|
|
/// Expression continuation that further initializes the newly allocated object
|
|
/// instance with running the constructor.
|
|
class InitializationEK extends ExpressionContinuation {
|
|
final Constructor constructor;
|
|
final Environment environment;
|
|
final ExceptionComponents exceptionComponents;
|
|
final StatementContinuation continuation;
|
|
|
|
InitializationEK(this.constructor, this.environment, this.exceptionComponents,
|
|
this.continuation);
|
|
|
|
Configuration call(Value value) {
|
|
Location location = new Location(value);
|
|
|
|
if (constructor.initializers.isNotEmpty &&
|
|
constructor.initializers.last is RedirectingInitializer) {
|
|
return _createRedirectingInitializerConfig(
|
|
constructor.initializers.first, location);
|
|
}
|
|
// The statement body is captured by the next statement continuation and
|
|
// expressions for field initialization are evaluated.
|
|
var ctrEnv = environment.extendWithThis(value);
|
|
var bodyCont = new ConstructorBodySK(
|
|
constructor.function.body, ctrEnv, exceptionComponents, continuation);
|
|
var initializers = _getFieldInitializers(constructor.enclosingClass);
|
|
var fieldsCont = new InstanceFieldsA(
|
|
constructor, location, ctrEnv, exceptionComponents, bodyCont);
|
|
return new EvalListConfiguration(
|
|
initializers, new Environment.empty(), exceptionComponents, fieldsCont);
|
|
}
|
|
|
|
/// Creates the next configuration to further initializer the value for
|
|
/// redirecting constructors.
|
|
Configuration _createRedirectingInitializerConfig(
|
|
Initializer initializer, Location location) {
|
|
Initializer current = constructor.initializers.first;
|
|
if (current is RedirectingInitializer) {
|
|
// Evaluate the list of arguments for invoking the target constructor in
|
|
// the current environment.
|
|
List<InterpreterExpression> exprs =
|
|
_getArgumentExpressions(current.arguments, current.target.function);
|
|
var cont = new ConstructorInitializerA(
|
|
current.target, location, exceptionComponents, continuation);
|
|
return new EvalListConfiguration(
|
|
exprs, environment, exceptionComponents, cont);
|
|
}
|
|
Expression expr = (current is FieldInitializer)
|
|
? current.value
|
|
: (current as LocalInitializer).variable.initializer;
|
|
|
|
// The index is set to 0 since we are evaluating the expression for the
|
|
// first initializer in the initializer list.
|
|
var cont = new InitializerListEK(constructor, 0, location, environment,
|
|
exceptionComponents, continuation);
|
|
return new EvalConfiguration(expr, environment, exceptionComponents, cont);
|
|
}
|
|
}
|
|
|
|
class InitializerListEK extends ExpressionContinuation {
|
|
final Constructor constructor;
|
|
final int initializerIndex;
|
|
final Location location;
|
|
final Environment environment;
|
|
final ExceptionComponents exceptionComponents;
|
|
final ConstructorBodySK continuation;
|
|
|
|
final Class _currentClass;
|
|
|
|
InitializerListEK(this.constructor, this.initializerIndex, this.location,
|
|
this.environment, this.exceptionComponents, this.continuation)
|
|
: _currentClass = new Class(constructor.enclosingClass.reference);
|
|
|
|
/// Creates a continuation for the evaluation of the initializer at position
|
|
/// [index].
|
|
InitializerListEK withInitializerIndex(int index) {
|
|
return new InitializerListEK(constructor, index, location, environment,
|
|
exceptionComponents, continuation);
|
|
}
|
|
|
|
Configuration call(Value value) {
|
|
Initializer current = constructor.initializers[initializerIndex];
|
|
if (current is FieldInitializer) {
|
|
_currentClass.lookupImplicitSetter(current.field.name)(
|
|
location.value, value);
|
|
return _createNextConfiguration(environment);
|
|
}
|
|
if (current is LocalInitializer) {
|
|
Environment newEnv = environment.extend(current.variable, value);
|
|
return _createNextConfiguration(newEnv);
|
|
}
|
|
throw "Value can't be applied to initalizer of type ${current.runtimeType}";
|
|
}
|
|
|
|
Configuration _createNextConfiguration(Environment env) {
|
|
assert(initializerIndex + 1 < constructor.initializers.length);
|
|
Initializer next = constructor.initializers[initializerIndex + 1];
|
|
if (next is SuperInitializer) {
|
|
// TODO(zhivkag): Execute constructor of "object" class when support for
|
|
// native/external functions is added.
|
|
if (_currentClass.superclass.superclass == null) {
|
|
_initializeNullFields(_currentClass, location.value);
|
|
return new ForwardConfiguration(continuation, environment);
|
|
}
|
|
return _createEvalListConfig(next.arguments, next.target, env);
|
|
}
|
|
|
|
if (next is RedirectingInitializer) {
|
|
return _createEvalListConfig(next.arguments, next.target, env);
|
|
}
|
|
|
|
Expression nextExpr = (next is FieldInitializer)
|
|
? next.value
|
|
: (next as LocalInitializer).variable.initializer;
|
|
|
|
var cont = withInitializerIndex(initializerIndex + 1);
|
|
return new EvalConfiguration(nextExpr, env, exceptionComponents, cont);
|
|
}
|
|
|
|
Configuration _createEvalListConfig(
|
|
Arguments args, Constructor ctr, Environment env) {
|
|
List<InterpreterExpression> exprs =
|
|
_getArgumentExpressions(args, ctr.function);
|
|
var cont = new ConstructorInitializerA(
|
|
ctr, location, exceptionComponents, continuation);
|
|
return new EvalListConfiguration(exprs, env, exceptionComponents, cont);
|
|
}
|
|
}
|
|
|
|
class ThrowEK extends ExpressionContinuation {
|
|
final ExceptionComponents exceptionComponents;
|
|
|
|
ThrowEK(this.exceptionComponents);
|
|
|
|
Configuration call(Value value) {
|
|
return new ThrowConfiguration(
|
|
exceptionComponents.handler, value, exceptionComponents.stackTrace);
|
|
}
|
|
}
|
|
|
|
/// Represents the expression continuation that ensures the finalizer of a
|
|
/// [TryFinally] node is executed before applying the return continuation to
|
|
/// the given value.
|
|
///
|
|
/// It executes the captured finalizer statement and adds a statement
|
|
/// continuation that will apply the return continuation to the given value
|
|
/// when/if reached.
|
|
class FinallyReturnEK extends ExpressionContinuation {
|
|
final Statement statement;
|
|
final Environment environment;
|
|
final State state;
|
|
|
|
FinallyReturnEK(this.statement, this.environment, this.state);
|
|
|
|
Configuration call(Value value) {
|
|
return new ExecConfiguration(statement, environment,
|
|
state.withContinuation(new ExitSK(state.returnContinuation, value)));
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Exceptions Handlers
|
|
// ------------------------------------------------------------------------
|
|
|
|
abstract class ExceptionHandler extends Continuation {
|
|
Configuration call(Value exception, StackTrace stacktrace);
|
|
|
|
static String errorMessage(Value exception, StackTrace stacktrace) {
|
|
return 'Uncaught exception '
|
|
'"${exception.value.runtimeType} : ${exception.value}"\n'
|
|
'${stacktrace.toString()}';
|
|
}
|
|
}
|
|
|
|
/// Handler for showing an exception to the user and returning a halting the
|
|
/// execution of the program when an exception is not handled.
|
|
class MainHandler extends ExceptionHandler {
|
|
Configuration call(Value exception, StackTrace stacktrace) {
|
|
var message = ExceptionHandler.errorMessage(exception, stacktrace);
|
|
log.info(message);
|
|
print(message);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// Represents the handler that either executes a matching catch clause or
|
|
/// applies the next handler to the given exception.
|
|
class CatchHandler extends ExceptionHandler {
|
|
final List<Catch> catches;
|
|
final Environment environment;
|
|
final State state;
|
|
|
|
CatchHandler(this.catches, this.environment, this.state);
|
|
|
|
Configuration call(Value exception, StackTrace stackTrace) {
|
|
if (catches.isEmpty) {
|
|
return new ThrowConfiguration(
|
|
state.exceptionComponents.handler, exception, stackTrace);
|
|
}
|
|
|
|
// TODO(zhivkag): Check if there is a matching catch clause instead.
|
|
var currentCatch = catches.first;
|
|
if (currentCatch.guard is DynamicType) {
|
|
// Exception is caught, execute the body.
|
|
|
|
var env = environment;
|
|
if (currentCatch.exception != null) {
|
|
env = env.extend(currentCatch.exception, exception);
|
|
}
|
|
if (currentCatch.stackTrace != null) {
|
|
env = env.extend(
|
|
currentCatch.stackTrace, new StringValue(stackTrace.toString()));
|
|
}
|
|
var ecs = new ExceptionComponents(state.exceptionComponents.handler,
|
|
state.exceptionComponents.stackTrace, stackTrace, exception);
|
|
var newState = new State.initial()
|
|
.withContinuation(state.continuation)
|
|
.withReturnContinuation(state.returnContinuation)
|
|
.withException(ecs);
|
|
return new ExecConfiguration(currentCatch.body, env, newState);
|
|
}
|
|
|
|
var catchHandler =
|
|
new CatchHandler(catches.skip(1).toList(), environment, state);
|
|
return new ThrowConfiguration(catchHandler, exception, stackTrace);
|
|
}
|
|
}
|
|
|
|
/// Represents the handler that executes the corresponding finalizer before
|
|
/// applying the next handler to the given exception.
|
|
///
|
|
/// Applying the next handler to the given exception is supported with adding
|
|
/// [RethrowSK] as next statement continuation.
|
|
class FinallyHandler extends ExceptionHandler {
|
|
final Statement finallyStatement;
|
|
final Environment environment;
|
|
final State state;
|
|
|
|
FinallyHandler(this.finallyStatement, this.environment, this.state);
|
|
Configuration call(Value exception, StackTrace stackTrace) {
|
|
// A finally handler can't handle an exception, only execute the
|
|
// corresponding finally statement and rethrow.
|
|
var cont =
|
|
new RethrowSK(state.exceptionComponents.handler, exception, stackTrace);
|
|
var newState = state.withContinuation(cont);
|
|
return new ExecConfiguration(finallyStatement, environment, newState);
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Exceptions
|
|
// ------------------------------------------------------------------------
|
|
/// Represents the components for Exception handling.
|
|
///
|
|
/// It contains the current of exception handler, a stack trace and optional
|
|
/// components, current stacktrace and exception.
|
|
class ExceptionComponents {
|
|
final ExceptionHandler handler;
|
|
final StackTrace stackTrace;
|
|
|
|
/// Current exception and stack trace.
|
|
///
|
|
/// Components enabling support for `rethrow` expressions and set only in
|
|
/// catch clauses.
|
|
final StackTrace currentStackTrace;
|
|
final Value currentException;
|
|
|
|
ExceptionComponents(this.handler, this.stackTrace, this.currentStackTrace,
|
|
this.currentException);
|
|
|
|
ExceptionComponents.initial()
|
|
: handler = new MainHandler(),
|
|
stackTrace = new StackTrace(null, null),
|
|
currentStackTrace = null,
|
|
currentException = null;
|
|
}
|
|
|
|
class StackTrace {
|
|
final Expression expression;
|
|
final StackTrace stackTrace;
|
|
|
|
StackTrace(this.expression, this.stackTrace);
|
|
|
|
String toString() {
|
|
var buffer = new StringBuffer('in main()');
|
|
var current = this;
|
|
while (current.expression != null) {
|
|
buffer.write('at ${current.expression.toString()}\n');
|
|
current = current.stackTrace;
|
|
}
|
|
return buffer.toString();
|
|
}
|
|
}
|
|
|
|
/// Executes statements.
|
|
///
|
|
/// Execution of a statement completes in one of the following ways:
|
|
/// - It completes normally, in which case the execution proceeds to applying
|
|
/// the next continuation.
|
|
/// - It breaks with a label, in which case the corresponding continuation is
|
|
/// returned and applied.
|
|
/// - It returns with or without value, in which case the return continuation is
|
|
/// returned and applied accordingly.
|
|
/// - It throws, in which case the handler is returned and applied accordingly.
|
|
class StatementExecuter
|
|
extends StatementVisitor1<Configuration, ExecConfiguration> {
|
|
final Evaluator evaluator = new Evaluator();
|
|
|
|
void trampolinedExecution(Configuration configuration) {
|
|
while (configuration != null) {
|
|
configuration = configuration.step(this);
|
|
}
|
|
}
|
|
|
|
Configuration exec(Statement statement, ExecConfiguration conf) =>
|
|
statement.accept1(this, conf);
|
|
Configuration eval(Expression expression, EvalConfiguration config) =>
|
|
evaluator.eval(expression, config);
|
|
Configuration evalList(List<InterpreterExpression> es, Environment env,
|
|
ExceptionComponents ecs, Continuation cont) =>
|
|
evaluator.evalList(es, env, ecs, cont);
|
|
|
|
Configuration defaultStatement(Statement node, ExecConfiguration conf) {
|
|
throw notImplemented(
|
|
m: "Execution is not implemented for statement:\n$node ");
|
|
}
|
|
|
|
Configuration visitExpressionStatement(
|
|
ExpressionStatement node, ExecConfiguration conf) {
|
|
var cont = new ExpressionEK(conf.state.continuation, conf.environment);
|
|
return new EvalConfiguration(node.expression, conf.environment,
|
|
conf.state.exceptionComponents, cont);
|
|
}
|
|
|
|
Configuration visitBlock(Block node, ExecConfiguration conf) {
|
|
if (node.statements.isEmpty) {
|
|
return new ForwardConfiguration(
|
|
conf.state.continuation, conf.environment);
|
|
}
|
|
|
|
var cont = new BlockSK.fromConfig(node.statements.skip(1).toList(), conf);
|
|
return new ExecConfiguration(node.statements.first, conf.environment,
|
|
conf.state.withContinuation(cont));
|
|
}
|
|
|
|
Configuration visitEmptyStatement(
|
|
EmptyStatement node, ExecConfiguration conf) {
|
|
return new ForwardConfiguration(conf.state.continuation, conf.environment);
|
|
}
|
|
|
|
Configuration visitIfStatement(IfStatement node, ExecConfiguration conf) {
|
|
var cont = new IfConditionEK(
|
|
node.then, node.otherwise, conf.environment, conf.state);
|
|
|
|
return new EvalConfiguration(
|
|
node.condition, conf.environment, conf.state.exceptionComponents, cont);
|
|
}
|
|
|
|
Configuration visitLabeledStatement(
|
|
LabeledStatement node, ExecConfiguration conf) {
|
|
return new ExecConfiguration(node.body, conf.environment,
|
|
conf.state.withBreak(node, conf.environment));
|
|
}
|
|
|
|
Configuration visitBreakStatement(
|
|
BreakStatement node, ExecConfiguration conf) {
|
|
Label l = conf.state.lookupLabel(node.target);
|
|
return new BreakConfiguration(l.continuation);
|
|
}
|
|
|
|
Configuration visitWhileStatement(
|
|
WhileStatement node, ExecConfiguration conf) {
|
|
var cont = new WhileConditionEK(
|
|
node.condition, node.body, conf.environment, conf.state);
|
|
|
|
return new EvalConfiguration(
|
|
node.condition, conf.environment, conf.state.exceptionComponents, cont);
|
|
}
|
|
|
|
Configuration visitDoStatement(DoStatement node, ExecConfiguration conf) {
|
|
var cont = new WhileConditionSK(
|
|
node.condition, node.body, conf.environment, conf.state);
|
|
|
|
return new ExecConfiguration(
|
|
node.body, conf.environment, conf.state.withContinuation(cont));
|
|
}
|
|
|
|
Configuration visitReturnStatement(
|
|
ReturnStatement node, ExecConfiguration conf) {
|
|
assert(conf.state.returnContinuation != null);
|
|
log.info('return\n');
|
|
if (node.expression == null) {
|
|
return new ValuePassingConfiguration(
|
|
conf.state.returnContinuation, Value.nullInstance);
|
|
}
|
|
return new EvalConfiguration(node.expression, conf.environment,
|
|
conf.state.exceptionComponents, conf.state.returnContinuation);
|
|
}
|
|
|
|
Configuration visitTryCatch(TryCatch node, ExecConfiguration conf) {
|
|
var handler = new CatchHandler(node.catches, conf.environment, conf.state);
|
|
var exceptionComponents = new ExceptionComponents(
|
|
handler,
|
|
conf.state.exceptionComponents.stackTrace,
|
|
conf.state.exceptionComponents.currentStackTrace,
|
|
conf.state.exceptionComponents.currentException);
|
|
var state = conf.state.withException(exceptionComponents);
|
|
return new ExecConfiguration(node.body, conf.environment, state);
|
|
}
|
|
|
|
Configuration visitTryFinally(TryFinally node, ExecConfiguration conf) {
|
|
var cont = new FinallySK(node.finalizer, conf.environment, conf.state);
|
|
var returnCont =
|
|
new FinallyReturnEK(node.finalizer, conf.environment, conf.state);
|
|
var labels = conf.state.labels
|
|
?.withFinalizer(node.finalizer, conf.environment, conf.state);
|
|
var handler =
|
|
new FinallyHandler(node.finalizer, conf.environment, conf.state);
|
|
var exceptionComponents = new ExceptionComponents(
|
|
handler,
|
|
conf.state.exceptionComponents.stackTrace,
|
|
conf.state.exceptionComponents.currentStackTrace,
|
|
conf.state.exceptionComponents.currentException);
|
|
var state = new State(labels, exceptionComponents, returnCont, cont);
|
|
return new ExecConfiguration(node.body, conf.environment, state);
|
|
}
|
|
|
|
Configuration visitVariableDeclaration(
|
|
VariableDeclaration node, ExecConfiguration conf) {
|
|
if (node.initializer != null) {
|
|
var cont = new VariableInitializerEK(
|
|
node, conf.environment, conf.state.continuation);
|
|
return new EvalConfiguration(node.initializer, conf.environment,
|
|
conf.state.exceptionComponents, cont);
|
|
}
|
|
return new ForwardConfiguration(conf.state.continuation,
|
|
conf.environment.extend(node, Value.nullInstance));
|
|
}
|
|
|
|
Configuration visitFunctionDeclaration(
|
|
FunctionDeclaration node, ExecConfiguration conf) {
|
|
var newEnv = conf.environment.extend(node.variable, Value.nullInstance);
|
|
var fun = new FunctionValue(node.function, newEnv);
|
|
newEnv.updateStore(node.variable, fun);
|
|
return new ForwardConfiguration(conf.state.continuation, newEnv);
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// VALUES
|
|
// ------------------------------------------------------------------------
|
|
|
|
typedef Value Getter(Value receiver);
|
|
typedef void Setter(Value receiver, Value value);
|
|
|
|
class Class {
|
|
static final Map<Reference, Class> _classes = <Reference, Class>{};
|
|
|
|
/// The immediate superclass, or `null` if this is the root class object.
|
|
Class superclass;
|
|
|
|
/// The class definitions from the `implements` clause.
|
|
final List<Supertype> interfaces = <Supertype>[];
|
|
|
|
/// Implicit getters for instance fields.
|
|
Map<Name, Getter> implicitGetters = <Name, Getter>{};
|
|
|
|
/// Implicit setters for non final instance fields.
|
|
Map<Name, Setter> implicitSetters = <Name, Setter>{};
|
|
|
|
/// Instance methods, explicit getters and setters.
|
|
Map<Name, Procedure> methods = <Name, Procedure>{};
|
|
|
|
int get instanceSize => implicitGetters.length;
|
|
|
|
factory Class(Reference classRef) {
|
|
return _classes.putIfAbsent(
|
|
classRef, () => new Class._internal(classRef.asClass));
|
|
}
|
|
|
|
Class._internal(ast.Class currentClass) {
|
|
if (currentClass.superclass != null) {
|
|
superclass = new Class(currentClass.superclass.reference);
|
|
}
|
|
|
|
_populateImplicitGettersAndSetters(currentClass);
|
|
_populateInstanceMethods(currentClass);
|
|
}
|
|
|
|
Getter lookupImplicitGetter(Name name) {
|
|
Getter getter = implicitGetters[name];
|
|
if (getter != null) return getter;
|
|
if (superclass != null) return superclass.lookupImplicitGetter(name);
|
|
return (Value receiver) => notImplemented(obj: name);
|
|
}
|
|
|
|
Setter lookupImplicitSetter(Name name) {
|
|
Setter setter = implicitSetters[name];
|
|
if (setter != null) return setter;
|
|
if (superclass != null) return superclass.lookupImplicitSetter(name);
|
|
// TODO(zhivkag): Throw NoSuchInstance error instead.
|
|
return (Value receiver, Value value) => notImplemented(obj: name);
|
|
}
|
|
|
|
FunctionNode lookup(Name name) => methods[name]?.function;
|
|
|
|
bool isGetter(Name name) => methods[name]?.isGetter ?? false;
|
|
bool isSetter(Name name) => methods[name]?.isSetter ?? false;
|
|
bool isMethod(Name name) => !(methods[name]?.isAccessor ?? true);
|
|
|
|
/// Populates implicit getters and setters for the current class and its
|
|
/// superclass recursively.
|
|
_populateImplicitGettersAndSetters(ast.Class class_) {
|
|
if (class_.superclass != null) {
|
|
_populateImplicitGettersAndSetters(class_.superclass);
|
|
}
|
|
|
|
for (Field f in class_.fields) {
|
|
if (f.isStatic) continue;
|
|
assert(f.hasImplicitGetter);
|
|
|
|
int currentFieldIndex = implicitGetters.length;
|
|
// Shadowing an inherited getter with the same name.
|
|
implicitGetters[f.name] =
|
|
(Value receiver) => receiver.fields[currentFieldIndex].value;
|
|
if (f.hasImplicitSetter) {
|
|
// Shadowing an inherited setter with the same name.
|
|
implicitSetters[f.name] = (Value receiver, Value value) =>
|
|
receiver.fields[currentFieldIndex].value = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Populates instance methods, getters and setters for the current class and
|
|
/// its super class recursively.
|
|
_populateInstanceMethods(ast.Class class_) {
|
|
if (class_.superclass != null) {
|
|
_populateInstanceMethods(class_.superclass);
|
|
}
|
|
|
|
for (Member m in class_.members) {
|
|
if (m is Procedure) {
|
|
// Shadowing an inherited method, getter or setter with the same name.
|
|
methods[m.name] = m;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
abstract class Value {
|
|
Class get class_;
|
|
List<Location> get fields;
|
|
Object get value;
|
|
|
|
static final NullValue nullInstance = const NullValue();
|
|
static final BoolValue trueInstance = const BoolValue(true);
|
|
static final BoolValue falseInstance = const BoolValue(false);
|
|
|
|
const Value();
|
|
|
|
BoolValue toBoolean() {
|
|
return identical(this, Value.trueInstance)
|
|
? Value.trueInstance
|
|
: Value.falseInstance;
|
|
}
|
|
|
|
BoolValue equals(Value other) =>
|
|
value == other?.value ? Value.trueInstance : Value.falseInstance;
|
|
|
|
Value invokeMethod(Name name, [Value arg]) {
|
|
if (name.toString() == "==") return equals(arg);
|
|
throw notImplemented(obj: name);
|
|
}
|
|
}
|
|
|
|
class ObjectValue extends Value {
|
|
final Class class_;
|
|
final List<Location> fields;
|
|
Object get value => this;
|
|
|
|
ObjectValue(this.class_) : fields = new List<Location>(class_.instanceSize) {
|
|
for (int i = 0; i < fields.length; i++) {
|
|
// Create fresh locations for each field.
|
|
fields[i] = new Location.empty();
|
|
}
|
|
}
|
|
}
|
|
|
|
class FunctionValue extends Value {
|
|
Class get class_ => throw 'Class for FunctionValue is not defined';
|
|
List<Location> get fields => throw 'FunctionValue has no fields.';
|
|
|
|
FunctionValue get value => this;
|
|
|
|
final FunctionNode function;
|
|
final Environment environment;
|
|
|
|
FunctionValue(this.function, this.environment);
|
|
}
|
|
|
|
abstract class LiteralValue extends Value {
|
|
Class get class_ =>
|
|
notImplemented(m: "Loading class for literal is not implemented.");
|
|
List<Location> get fields =>
|
|
notImplemented(m: "Literal value does not have fields");
|
|
|
|
const LiteralValue();
|
|
}
|
|
|
|
class StringValue extends LiteralValue {
|
|
final String value;
|
|
|
|
static final operators = <String, Function>{
|
|
'[]': (StringValue v1, Value v2) => v1[v2],
|
|
'==': (StringValue v1, Value v2) => v1.equals(v2)
|
|
};
|
|
|
|
StringValue(this.value);
|
|
|
|
Value invokeMethod(Name name, [Value arg]) {
|
|
if (!operators.containsKey(name.name)) {
|
|
return notImplemented(obj: name);
|
|
}
|
|
return operators[name.name](this, arg);
|
|
}
|
|
|
|
// Operators
|
|
Value operator [](Value index) => new StringValue(value[index.value]);
|
|
}
|
|
|
|
abstract class NumValue extends LiteralValue {
|
|
num get value;
|
|
|
|
NumValue();
|
|
|
|
factory NumValue.fromValue(num value) {
|
|
if (value is int) {
|
|
return new IntValue(value);
|
|
} else {
|
|
assert(value is double);
|
|
return new DoubleValue(value);
|
|
}
|
|
}
|
|
|
|
static final operators = <String, Function>{
|
|
'+': (NumValue v1, Value v2) => v1 + v2,
|
|
'-': (NumValue v1, Value v2) => v1 - v2,
|
|
'>': (NumValue v1, Value v2) => v1 > v2,
|
|
'<': (NumValue v1, Value v2) => v1 < v2,
|
|
'==': (NumValue v1, Value v2) => v1.equals(v2),
|
|
'unary-': (NumValue v1) => -v1,
|
|
};
|
|
|
|
Value invokeMethod(Name name, [Value arg]) {
|
|
if (!operators.containsKey(name.name)) return notImplemented(obj: name);
|
|
if (arg == null) return operators[name.name](this);
|
|
return operators[name.name](this, arg);
|
|
}
|
|
|
|
// Operators
|
|
NumValue operator +(Value other) =>
|
|
new NumValue.fromValue(value + other.value);
|
|
NumValue operator -(Value other) =>
|
|
new NumValue.fromValue(value - other.value);
|
|
NumValue operator -() => new NumValue.fromValue(-value);
|
|
|
|
BoolValue operator >(Value other) =>
|
|
value > other.value ? Value.trueInstance : Value.falseInstance;
|
|
BoolValue operator <(Value other) =>
|
|
value < other.value ? Value.trueInstance : Value.falseInstance;
|
|
}
|
|
|
|
class IntValue extends NumValue {
|
|
final int value;
|
|
|
|
IntValue(this.value);
|
|
}
|
|
|
|
class DoubleValue extends NumValue {
|
|
final double value;
|
|
|
|
DoubleValue(this.value);
|
|
}
|
|
|
|
class BoolValue extends LiteralValue {
|
|
final bool value;
|
|
|
|
const BoolValue(this.value);
|
|
}
|
|
|
|
class NullValue extends LiteralValue {
|
|
Object get value => null;
|
|
|
|
const NullValue();
|
|
}
|
|
|
|
notImplemented({String m, Object obj}) {
|
|
throw new NotImplemented(m ?? 'Evaluation for $obj is not implemented');
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// INTERNAL FUNCTIONS
|
|
// ------------------------------------------------------------------------
|
|
|
|
/// Creates a list of all argument expressions to be evaluated for the
|
|
/// invocation of the provided [FunctionNode] containing the actual arguments
|
|
/// and the optional argument initializers.
|
|
List<InterpreterExpression> _getArgumentExpressions(
|
|
Arguments providedArgs, FunctionNode fun) {
|
|
List<InterpreterExpression> args = <InterpreterExpression>[];
|
|
// Add positional arguments expressions.
|
|
args.addAll(providedArgs.positional
|
|
.map((Expression e) => new PositionalExpression(e)));
|
|
|
|
// Add optional positional argument initializers.
|
|
for (int i = providedArgs.positional.length;
|
|
i < fun.positionalParameters.length;
|
|
i++) {
|
|
args.add(new PositionalExpression(fun.positionalParameters[i].initializer));
|
|
}
|
|
|
|
Map<String, NamedExpression> namedFormals = <String, NamedExpression>{};
|
|
for (VariableDeclaration vd in fun.namedParameters) {
|
|
if (vd is NamedValue) {
|
|
namedFormals[vd.name] = new NamedExpression(vd.name, vd.initializer);
|
|
}
|
|
}
|
|
|
|
// Add named expressions.
|
|
for (int i = 0; i < providedArgs.named.length; i++) {
|
|
var current = providedArgs.named[i];
|
|
args.add(new NamedExpression(current.name, current.value));
|
|
namedFormals.remove(current.name);
|
|
}
|
|
|
|
// Add missing optional named initializers.
|
|
args.addAll(namedFormals.values);
|
|
|
|
return args;
|
|
}
|
|
|
|
/// Creates a list of all field expressions to be evaluated.
|
|
///
|
|
/// A field expression is an initializer expression for a given field defined
|
|
/// when the field was created.
|
|
List<InterpreterExpression> _getFieldInitializers(ast.Class class_) {
|
|
var fieldInitializers = new List<InterpreterExpression>();
|
|
|
|
for (Field f in class_.fields) {
|
|
if (!f.isStatic && f.initializer != null) {
|
|
fieldInitializers.add(new FieldInitializerExpression(f, f.initializer));
|
|
}
|
|
}
|
|
|
|
return fieldInitializers;
|
|
}
|
|
|
|
/// Initializes all non initialized fields from the provided class to
|
|
/// `Value.nullInstance` in the provided value.
|
|
void _initializeNullFields(Class class_, Value value) {
|
|
int startIndex = class_.superclass?.instanceSize ?? 0;
|
|
for (int i = startIndex; i < class_.instanceSize; i++) {
|
|
if (value.fields[i].value == null) {
|
|
value.fields[i].value = Value.nullInstance;
|
|
}
|
|
}
|
|
}
|