// 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 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<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;
    }
  }
}
