// 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({}); final Component component; final StatementExecuter visitor = new StatementExecuter(); Interpreter(this.component); void run() { assert(component.libraries.isEmpty); Procedure mainMethod = component.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 _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.from(_staticFields); newMap[member] = new Location(value); return new MainEnvironment(newMap); } } class Environment { final List bindings = []; 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 eval(Expression expr, EvalConfiguration config) => expr.accept1(this, config); Configuration evalList(List 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, []); } 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 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`. class EvalListConfiguration extends Configuration { final List 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`. class ApplicationConfiguration extends Configuration { final ApplicationContinuation continuation; final List 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 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 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 args, [Environment parentEnv]) { Environment newEnv = new Environment(parentEnv ?? new Environment.empty()); List 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 named = {}; 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 args) { args.add(value); return new ApplicationConfiguration(applicationContinuation, args); } } class StringConcatenationA extends ApplicationContinuation { final ExpressionContinuation continuation; StringConcatenationA(this.continuation); Configuration call(List 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 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 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 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 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 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 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 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 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 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 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 { 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 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 _classes = {}; /// The immediate superclass, or `null` if this is the root class object. Class superclass; /// The class definitions from the `implements` clause. final List interfaces = []; /// Implicit getters for instance fields. Map implicitGetters = {}; /// Implicit setters for non final instance fields. Map implicitSetters = {}; /// Instance methods, explicit getters and setters. Map methods = {}; 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 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 fields; Object get value => this; ObjectValue(this.class_) : fields = new List(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 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 get fields => notImplemented(m: "Literal value does not have fields"); const LiteralValue(); } class StringValue extends LiteralValue { final String value; static final operators = { '[]': (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 = { '+': (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 _getArgumentExpressions( Arguments providedArgs, FunctionNode fun) { List args = []; // 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 namedFormals = {}; 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 _getFieldInitializers(ast.Class class_) { var fieldInitializers = new List(); 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; } } }