/** @file VirtualSchematikaMachine.cpp
 *
 *  @author Roland Conybeare, Jan 2026
 **/

#include "VirtualSchematikaMachine.hpp"
#include "DPrimitive_gco_3_dict_string_gco.hpp"
#include "DPrimitive_gco_2_gco_gco.hpp"
#include "VsmDefContFrame.hpp"
#include "VsmApplyFrame.hpp"
#include "VsmEvalArgsFrame.hpp"
#include "VsmApplyClosureFrame.hpp"
#include "VsmIfElseContFrame.hpp"
#include "VsmSeqContFrame.hpp"
#include "VsmRcx.hpp"
#include "Closure.hpp"
#include <xo/object2/Sequence.hpp>
#include <xo/object2/List.hpp>
#include <xo/numeric/NumericDispatch.hpp>
#include <xo/expression2/DefineExpr.hpp>
#include <xo/expression2/ApplyExpr.hpp>
#include <xo/expression2/LambdaExpr.hpp>
#include <xo/expression2/Constant.hpp>
#include <xo/expression2/SequenceExpr.hpp>
#include <xo/object2/Dictionary.hpp>
#include <xo/object2/Integer.hpp>
#include <xo/object2/Boolean.hpp>
#include <xo/procedure2/RuntimeContext.hpp>
#include <xo/procedure2/Primitive_gco_0.hpp>
#include <xo/procedure2/Primitive_gco_1_gco.hpp>
#include <xo/procedure2/Primitive_gco_2_gco_gco.hpp>
#include <xo/procedure2/Primitive_gco_3_dict_string_gco.hpp>
#include <xo/gc/X1Collector.hpp>
#include <xo/reflect/Reflect.hpp>
#include <xo/stringtable2/UniqueString.hpp>
#include <xo/alloc2/CollectorTypeRegistry.hpp>
#include <xo/alloc2/Arena.hpp>
#include <xo/printable2/Printable.hpp>
#include <xo/facet/FacetRegistry.hpp>
#include <cassert>

namespace xo {
    using xo::scm::DDictionary;
    using xo::print::APrintable;
    using xo::print::ppconfig;
    using xo::print::ppstate_standalone;
    using xo::reflect::Reflect;
    using xo::mm::AGCObject;
    using xo::mm::MemorySizeInfo;
    using xo::mm::CollectorTypeRegistry;
    using xo::mm::AAllocator;
    using xo::mm::ACollector;
    using xo::mm::DX1Collector;
    using xo::mm::X1CollectorConfig;
    using xo::mm::DArena;
    using xo::facet::FacetRegistry;
    using xo::facet::TypeRegistry;
    using std::cout;

    namespace scm {

        bool
        VsmResult::is_error() const
        {
            return (*this->value() && obj<AGCObject,DRuntimeError>::from(*(this->value())));
        }

        namespace {
            /** helper function to create X1 collector instance **/
            abox<AAllocator,DX1Collector>
            vsm_make_gc(obj<AAllocator> aux_mm,
                        const X1CollectorConfig & config)
            {
                auto retval = abox<AAllocator,DX1Collector>::make(aux_mm, config);

                // establish the set of types that mm_ will be able to collect

                CollectorTypeRegistry::instance().install_types
                    (retval.to_op().to_facet<ACollector>());

                return retval;
            }
        }

        // NOTE: using heap here for {DX1Collector, DArena, DVsmRcx} instances
        //       (though DX1Collector allocations will be from explictly mmap'd memory)
        //
        DVirtualSchematikaMachine::DVirtualSchematikaMachine(const VsmConfig & config,
                                                             obj<AAllocator> aux_mm)
        : config_{config},
          self_vroot_{obj<AGCObject,DVirtualSchematikaMachine>(this)},
          aux_mm_{aux_mm},
          mm_(vsm_make_gc(aux_mm_, config.x1_config_)),
          rcx_(abox<ARuntimeContext,DVsmRcx>::make(aux_mm_, this)),
          reader_{config.rdr_config_, mm_.to_op(), aux_mm_}
        {
            {
                DArena * arena = new DArena(config_.error_config_);
                assert(arena);

                this->error_mm_.adopt(obj<AAllocator,DArena>(arena));
            }

            this->global_env_
                = obj<AGCObject,DGlobalEnv>(reader_.global_env());

            // TODO:
            // annoying to have to create self_vroot_ to appease
            // add_gc_root_poly() signature.
            // In practice gc won't modify the pointer
            // (instead traverses it to find +update children)
            //
            mm_.to_op().to_facet<ACollector>().add_gc_root_poly(&self_vroot_);
        }

        DVirtualSchematikaMachine *
        DVirtualSchematikaMachine::_make(obj<AAllocator> mm,
                                        const VsmConfig & config,
                                        obj<AAllocator> aux_mm)
        {
            void * mem = mm.alloc_for<DVirtualSchematikaMachine>();

            return new (mem) DVirtualSchematikaMachine(config, aux_mm);
        }

        obj<AGCObject,DVirtualSchematikaMachine>
        DVirtualSchematikaMachine::make(obj<AAllocator> mm,
                                       const VsmConfig & config,
                                       obj<AAllocator> aux_mm)
        {
            return obj<AGCObject,DVirtualSchematikaMachine>(
                                                           _make(mm, config, aux_mm));
        }

        obj<AAllocator>
        DVirtualSchematikaMachine::allocator() const noexcept
        {
            return mm_.to_op();
        }

        obj<AAllocator>
        DVirtualSchematikaMachine::error_allocator() const noexcept
        {
            return error_mm_.to_op();
        }

        StringTable *
        DVirtualSchematikaMachine::stringtable() noexcept
        {
            return reader_.stringtable();
        }

        bool
        DVirtualSchematikaMachine::is_at_toplevel() const noexcept
        {
            return reader_.is_at_toplevel();
        }

        void
        DVirtualSchematikaMachine::visit_pools(const MemorySizeVisitor & visitor) const
        {
            aux_mm_.visit_pools(visitor);
            mm_.visit_pools(visitor);
            error_mm_.visit_pools(visitor);
            reader_.visit_pools(visitor);
        }

        void
        DVirtualSchematikaMachine::begin_interactive_session()
        {
            reader_.begin_interactive_session();
        }

        void
        DVirtualSchematikaMachine::begin_batch_session()
        {
            reader_.begin_batch_session();
        }

        VsmResultExt
        DVirtualSchematikaMachine::read_eval_print(span_type input, bool eof)
        {
            if (input.empty()) {
                return VsmResultExt();
            }

            reader_.reset_result();

            auto [expr, remaining, tk_error]
                = reader_.read_expr(input, eof);

            if (!expr) {
                if (tk_error.is_error()) {
                    // tokenizer error -> convert to runtime error

                    DString * src = DString::from_view(mm_.to_op(), tk_error.src_function());
                    DString * msg = tk_error.report_to_string(mm_.to_op());

                    auto error = obj<AGCObject,DRuntimeError>(DRuntimeError::_make(mm_.to_op(), src, msg));

                    this->value_ = VsmResult(error);

                    {
                        obj<APrintable> error_pr
                            = FacetRegistry::instance().variant<APrintable,AGCObject>(error);

                        ppconfig ppc;
                        ppstate_standalone pps(&cout, 0, &ppc);
                        pps.prettyn(error_pr);
                    }

                    return VsmResultExt(value_, remaining);
                } else {
                    // incomplete input
                    return VsmResultExt(VsmResult(), remaining);
                }
            }

            // here: have obtained complete input expression

            const VsmResult & evalresult = this->start_eval(expr);

            if (evalresult.is_error()) {
                // TODO: print error here

                return VsmResultExt(evalresult, remaining);
            }

            assert(evalresult.is_value());

            obj<AGCObject> value = evalresult.result_;

            assert(value);

            {
                obj<APrintable> value_pr
                    = FacetRegistry::instance().variant<APrintable,AGCObject>(value);

                // pretty_toplevel(value_pr, &cout, ppconfig());
                ppconfig ppc;
                ppstate_standalone pps(&cout, 0, &ppc);
                pps.prettyn(value_pr);
            }

            return VsmResultExt(evalresult, remaining);
        }

        const VsmResult &
        DVirtualSchematikaMachine::start_eval(obj<AExpression> expr)
        {
            this->pc_ = VsmInstr::c_eval;
            this->expr_ = expr;
            this->value_ = VsmResult(obj<AGCObject>());
            this->cont_ = VsmInstr::c_halt;

            this->run();

            return value_;
        }

        void
        DVirtualSchematikaMachine::run()
        {
            while (this->execute_one())
                ;
        }

        bool
        DVirtualSchematikaMachine::execute_one()
        {
            scope log(XO_DEBUG(config_.debug_flag_));

            log && log(xtag("pc", pc_),
                       xtag("cont", cont_));

            auto expr_pr = expr_.to_facet<APrintable>();
            if (expr_pr)
                log && log(xtag("expr", expr_pr));

            if (value_.value()) {
                auto value_pr
                    = const_cast<obj<AGCObject> *>(value_.value())->to_facet<APrintable>();
                if (value_pr)
                    log && log(xtag("value", value_pr));
            } else {
                log && log("value not present or tk error");
            }

            auto stack_pr = stack_.to_facet<APrintable>();
            if (stack_pr)
                log && log(xtag("stack", stack_pr));

            switch (pc_.opcode()) {
            case vsm_opcode::sentinel:
            case vsm_opcode::halt:
            case vsm_opcode::N:
                return false;
            case vsm_opcode::eval:
                _do_eval_op();
                break;
            case vsm_opcode::apply:
                _do_apply_op();
                break;
            case vsm_opcode::evalargs:
                _do_evalargs_op();
                break;
            case vsm_opcode::def_cont:
                _do_def_cont_op();
                break;
            case vsm_opcode::apply_cont:
                _do_apply_cont_op();
                break;
            case vsm_opcode::ifelse_cont:
                _do_ifelse_cont_op();
                break;
            case vsm_opcode::seq_cont:
                _do_seq_cont_op();
                break;
            }

            return true;
        }

        void
        DVirtualSchematikaMachine::_do_eval_op()
        {
            switch(expr_.extype()) {
            case exprtype::invalid:
            case exprtype::N:
                break;
            case exprtype::constant:
                _do_eval_constant_op();
                break;
            case exprtype::define:
                _do_eval_define_op();
                break;
            case exprtype::lambda:
                _do_eval_lambda_op();
                break;
            case exprtype::variable:
                _do_eval_variable_op();
                break;
            case exprtype::varref:
                _do_eval_varref_op();
                break;
            case exprtype::apply:
                _do_eval_apply_op();
                break;
            case exprtype::ifexpr:
                _do_eval_if_else_op();
                break;
            case exprtype::sequence:
                _do_eval_sequence_op();
                break;
            }
        }

        void
        DVirtualSchematikaMachine::_do_eval_constant_op()
        {
            auto expr
                = obj<AExpression,DConstant>::from(expr_);

            this->value_ = VsmResult(expr.data()->value());

            this->pc_ = this->cont_;
            this->cont_ = VsmInstr::c_sentinel;
        }

        void
        DVirtualSchematikaMachine::_do_eval_define_op()
        {
            scope log(XO_DEBUG(true));

            auto def_expr
                = obj<AExpression,DDefineExpr>::from(expr_);

            if (local_env_) {
                // nested defines implemented by rewriting,
                // so this branch should be unreachable

                assert(false);
            } else {
                // top-level define

                // .stack_ --+
                //           |
                //           v
                //           +------DVsmDefContFrame------+
                //           | .parent x | .cont | .def x |
                //           +---------|-+-------+------|-+
                //                     |                |
                //  ParserStack* <-----/                |
                //                                      |
                //                                      v
                //                                 DDefineExpr

                /* stack frame for nested continuation
                 * (to perform assignment)
                 */
                auto defcont_frame
                    = obj<AGCObject,DVsmDefContFrame>
                          (DVsmDefContFrame::make(mm_.to_op(),
                                                  this->stack_ /*saved stack*/,
                                                  this->cont_ /*saved cont*/,
                                                  def_expr.data() /*saved expr*/));

                this->stack_ = defcont_frame;

                // setup evaluation of rhs

                this->expr_ = def_expr->rhs();
                this->pc_ = VsmInstr::c_eval;
                this->cont_ = VsmInstr::c_def_cont;
            }
        }

        void
        DVirtualSchematikaMachine::_do_def_cont_op()
        {
            // see DVsmDefContFrame

            auto frame = obj<AGCObject,DVsmDefContFrame>::from(stack_);

            assert(frame);
            assert(value_.is_value());

            // TODO: verify that value satisfies expected type ?

            DVariable * lhs = frame->def_expr()->lhs();
            obj<AGCObject> rhs = *value_.value();

            assert(lhs->path().is_global());

            global_env_->assign_value(mm_.to_op(), lhs->path(), rhs);

            // TODO: unfortunate const_cast here, because obj<> doesn't support const DRepr yet
            this->value_ = VsmResult(obj<AGCObject,DUniqueString>(const_cast<DUniqueString*>(lhs->name())));

            this->stack_ = frame->parent();
            this->pc_ = frame->cont();
            this->cont_ = VsmInstr::c_sentinel;
        }

        void
        DVirtualSchematikaMachine::_do_eval_lambda_op()
        {
            // assuming bump allocator
            //
            //   +----------- DArray---------+ +-------------DLocalEnv-----------+ +-----DClosure-------+
            //   | .cap |.size | .elts_[]... |h| .parent x | .symtab x | .args x |h| .lambda x | .env x |
            //   +------+------+-------------+ +---------|-+---------|-+-------|-+ +---------|-+------|-+
            //   ^                             ^         |           |         |             |        |
            //   \-----------------------------|---------|-----------|---------/             |        |
            //                                 |         |           |                       |        |
            //                                 \---------|-----------|-----------------------|--------/
            //                                           |           |                       |
            //    <--------------------------------------/           |                       |
            //                                                       |                       |
            //                                                       v                       v
            //                                                 DLocalSymtab             DLambdaExpr
            //
            //    DClosure      runtime procedure (created below)
            //    DArray        bound non-local variables (established by VSM)
            //    DLocalEnv     local environment (copy ref from VSM state)
            //    h             alloc header
            //    DLocalSymtab  local symbol table (created by parser)
            //    DLambdaExpr   lambda expression (created by parser)
            //

            // will create DClosure with local_env_

            // local_env_
            // global_env_

            auto lambda
                = obj<AExpression,DLambdaExpr>::from(expr_);

            DClosure * closure = DClosure::make(mm_.to_op(),
                                                lambda.data(),
                                                local_env_.data());

            this->value_
                = VsmResult(obj<AGCObject>(obj<AGCObject,DClosure>(closure)));

            this->pc_ = this->cont_;
            this->cont_ = VsmInstr::c_sentinel;
        }

        void
        DVirtualSchematikaMachine::_do_eval_variable_op()
        {
            // not implemented
            assert(false);
        }

        void
        DVirtualSchematikaMachine::_do_eval_varref_op()
        {
            auto var = obj<AExpression,DVarRef>::from(expr_);

            Binding b = var->path();

            obj<AGCObject> value;

            if (b.is_local() && local_env_) {
                value = local_env_->lookup_value(b);
            } else if (b.is_global()) {
                value = global_env_->lookup_value(b);
            }

            if (value) {
                this->value_ = VsmResult(value);

                this->pc_ = this->cont_;
                this->cont_ = VsmInstr::c_sentinel;
                return;
            }

            // no local or global binding

            auto error = DRuntimeError::make(mm_.to_op(),
                                             "_do_eval_varref_op",
                                             "no binding for variable");
            this->value_ = VsmResult(error);

            // for now: halt VSM execution
            // TODO: some combination of
            // 1. emit stack trace
            // 2. go to debugger
            // 3. have every vsm instruction check inputs for errors

            this->pc_ = VsmInstr::c_halt;
            this->cont_ = VsmInstr::c_sentinel;
        }

        void
        DVirtualSchematikaMachine::_do_eval_apply_op()
        {
            // ApplyExpr in expr_ register

            // assuming bump allocator:
            //
            //   DArray                 VsmApplyFrame                 VsmEvalArgsFrame
            //   v                      v                             v
            //   +----------------------+-------+-------+----+--------+-------+-------+-------+
            //   | argument expressions | par x | cont1 | fn | args x | par x | cont2 | i_arg |
            //   +----------------------+-----|-+-------+----+------|-+-----|-+-------+-------+
            //   ^                      ^     |                     |       |
            //   |                      \-----------------------------------/
            //   \                            |                     /
            //    \------------------------------------------------/
            //                                /
            //   <---------------------------/
            //
            // - VsmEvalArgsFrame: owned by VSM, state for evalargs loop
            // - VsmApplyFrame:    owned by VSM, state for transferring control to called function
            // - DArray:           contains evaluated args; owned by called primitive
            // - cont2:            always c_apply
            //

            auto apply = obj<AExpression,DApplyExpr>::from(expr_);

            // accumulate evaluated arguments here
            DArray * args = DArray::_empty(mm_.to_op(),
                                           apply->n_args());

            // TODO: check function signature

            DVsmApplyFrame * apply_frame
                = DVsmApplyFrame::make(mm_.to_op(), stack_, cont_, args);

            auto evalargs_frame
                = obj<AGCObject,DVsmEvalArgsFrame>
                      (DVsmEvalArgsFrame::make(mm_.to_op(),
                                               apply_frame, VsmInstr::c_apply, apply.data()));

            this->stack_ = evalargs_frame;

            // Setup evaluation of first argument.  No new stack for this.

            this->expr_ = apply->fn();
            this->pc_   = VsmInstr::c_eval;
            this->cont_ = VsmInstr::c_evalargs;
        }

        void
        DVirtualSchematikaMachine::_do_eval_if_else_op()
        {
            // control:
            //   self -> eval(test) -> ifelse_cont -> eval(when_true)
            //                                     -> eval(when_false)

            auto ifelse_expr = obj<AExpression,DIfElseExpr>::from(expr_);

            obj<AGCObject,DVsmIfElseContFrame> ifelse_frame
                (DVsmIfElseContFrame::make(mm_.to_op(),
                                           stack_, cont_, ifelse_expr.data()));

            this->stack_ = ifelse_frame;
            this->expr_ = ifelse_expr->test();
            this->pc_ = VsmInstr::c_eval;
            this->cont_ = VsmInstr::c_ifelse_cont;
        }

        void
        DVirtualSchematikaMachine::_do_eval_sequence_op()
        {
            // stack:
            //
            //   VsmEvalSequence
            //   v
            //   +-------+------+-------+-------+
            //   | par x | cont |  seq  | i_elt |
            //   +-----|-+------+-------+-------+
            //         |
            //   <-----/
            //

            auto seq_expr = obj<AExpression,DSequenceExpr>::from(expr_);

            if (seq_expr->size() == 0) {
                /* empty sequence expression does not produce a value */

                this->value_ = VsmResult(obj<AGCObject>());
                this->pc_ = this->cont_;
                return;
            }

            auto seqexpr_frame
                = obj<AGCObject,DVsmSeqContFrame>
                      (DVsmSeqContFrame::make(mm_.to_op(),
                                              this->stack_ /*saved stack*/,
                                              this->cont_ /*saved cont*/,
                                              seq_expr.data() /*saved expr*/,
                                              0 /*index of seq element*/));

            this->stack_ = seqexpr_frame;

            // Setup evaluation of first sequence element

            this->expr_ = (*seq_expr.data())[0];
            this->pc_ = VsmInstr::c_eval;
            this->cont_ = VsmInstr::c_seq_cont;
        }

        void
        DVirtualSchematikaMachine::_do_apply_op()
        {
            // rcx_  : runtime context
            // fn_   : function to call
            // args_ : array of arguments

            // TODO: check argument types

            auto closure = obj<AGCObject,DClosure>::from(fn_);

            if (closure) {
                _do_call_closure_op();
                return;
            } else {
                _do_call_primitive_op();
                return;
            }
        }

        void
        DVirtualSchematikaMachine::_do_call_closure_op()
        {
            // We need to preserve registers while evaluating
            // lambda body

            auto closure = obj<AGCObject,DClosure>::from(fn_);

            assert(closure);

            // TODO: for tail recursion:
            //       check whether stack_ already refers to a
            //       DVsmApplyClosureFrame instance, in which case
            //       we can just refer to it instead of pushing a new one

            if (cont_ == VsmInstr::c_apply_cont) {
                // we are making a tail call.
                // No need to preserve (stack, cont, local_env),
                // since continuation will restore on top of them
                // frame top stackframe anyway
            } else {
                obj<AGCObject,
                    DVsmApplyClosureFrame> frame(
                        DVsmApplyClosureFrame::make(mm_.to_op(),
                                                    stack_,
                                                    cont_,
                                                    local_env_.data()));

                // push frame w/ saved vsm registers
                this->stack_ = frame;
                this->cont_ = VsmInstr::c_apply_cont;
            }

            auto lambda = closure->lambda();

            auto local_env
                = DLocalEnv::_make(mm_.to_op(),
                                   local_env_.data(),
                                   lambda->local_symtab(),
                                   args_.data());

            this->local_env_ = obj<AGCObject,DLocalEnv>(local_env);
            this->expr_ = lambda->body_expr();
            this->pc_ = VsmInstr::c_eval;
            // cont_ already established
        }

        void
        DVirtualSchematikaMachine::_do_call_primitive_op()
        {
            auto fn = fn_.to_facet<AProcedure>();

            this->value_ = VsmResult(fn.apply_nocheck(rcx_.to_op(), args_.data()));
            this->pc_ = cont_;
            this->cont_ = VsmInstr::c_sentinel;
        }

        void
        DVirtualSchematikaMachine::_do_evalargs_op()
        {
            scope log(XO_DEBUG(false));

            if (!value_.is_value()) {
                // error while evaluating function arg

                log.retroactively_enable();
                log && log("error in apply -> terminating app");

                this->pc_ = VsmInstr::c_halt;
                this->cont_ = VsmInstr::c_sentinel;
                return;
            }

            // here: nested evaluation succeeded

            // value of one of {fn, arg(i), ..} in fn(arg0 .. arg(n-1))
            //
            obj<AGCObject> value = *(value_.value());

            //            value_ in [i_arg]              value_
            //            .   (if i_arg >= 0)            .  (if i_arg = -1)
            //            .                              .
            //   DArray   .             VsmApplyFrame    .            VsmEvalArgsFrame
            //   v        v             v                v            v
            //   +----------------------+-------+-------+----+--------+-------+-------+--------+-------+
            //   | argument expressions | par o | cont1 | fn | args x | par o | cont2 | applyx | i_arg |
            //   +----------------------+-----|-+-------+----+------|-+-----|-+-------+--------+-------+
            //   ^                      ^     |                     |       |
            //   |                      \-----------------------------------/
            //   \                            |                     /
            //    \------------------------------------------------/
            //                                /
            //   <---------------------------/
            //
            // - VsmEvalArgsFrame: owned by VSM, state for evalargs loop
            // - VsmApplyFrame:    owned by VSM, state for transferring control to called function
            // - DArray:           contains evaluated args; owned by called primitive

            // - i_arg
            //     if -1:  value_ register holds function
            //     if >=0: value_ register holds i'th function argument
            //

            auto evalargs_frame
                = obj<AGCObject,DVsmEvalArgsFrame>::from(stack_);

            assert(evalargs_frame);

            int32_t i_arg = evalargs_frame->i_arg();

            DVsmApplyFrame * apply_frame = evalargs_frame->parent();

            const DApplyExpr * apply_expr
                = evalargs_frame->apply_expr();

            if (i_arg == -1) {
                bool is_closure = obj<AGCObject,DClosure>::from(value);
                bool is_native_fn = value.try_to_facet<AProcedure>();

                if (is_native_fn || is_closure) {
                    apply_frame->assign_fn(value);

                    i_arg = evalargs_frame->increment_arg();

                    // now i_arg is 0 -> evaluate that argument

                    if (i_arg >= static_cast<int32_t>(apply_expr->n_args())) {
                        // corner case: function with 0 arguments

                        this->fn_ = apply_frame->fn(); // = value;
                        this->args_ = obj<AGCObject,DArray>(apply_frame->args()); // empty

                        this->stack_ = apply_frame->parent();
                        this->pc_ = VsmInstr::c_apply;
                        this->cont_ = apply_frame->cont();

                        return;
                    } else {
                        this->expr_ = apply_expr->arg(i_arg);
                        this->pc_ = VsmInstr::c_eval;
                        this->cont_ = VsmInstr::c_evalargs;

                        return;
                    }
                } else {
                    // error - function position must deliver something with AProcedure?
                    // or DClosure, but we'll get to that.

                    log.retroactively_enable();
                    log("expected procedure in function position -> terminate");

                    assert(false);
                }
            } else {
                DArray * args = apply_frame->args();

                log && log(xtag("i_arg", i_arg), xtag("n_arg", args->size()), xtag("cap", args->capacity()));

                args->push_back(mm_.to_op(), value);

                i_arg = evalargs_frame->increment_arg();

                if (i_arg == static_cast<int32_t>(apply_expr->n_args())) {
                    // all apply-arguments have been evaluated
                    //  -> done with VsmEvalArgsFrame
                    //

                    this->fn_ = apply_frame->fn();
                    this->args_ = obj<AGCObject,DArray>(apply_frame->args());

                    this->stack_ = apply_frame->parent();
                    this->pc_ = VsmInstr::c_apply;
                    this->cont_ = apply_frame->cont();

                    return;

                } else {
                    this->expr_ = apply_expr->arg(i_arg);
                    this->pc_ = VsmInstr::c_eval;
                    this->cont_ = VsmInstr::c_evalargs;

                    return;
                }
            }

            // not implemented
            assert(false);
        }

        void
        DVirtualSchematikaMachine::_do_apply_cont_op()
        {
            // see DVsmApplyClosureFrame

            auto frame = obj<AGCObject,DVsmApplyClosureFrame>::from(stack_);

            assert(frame);

            this->stack_ = frame->parent();
            this->local_env_ = obj<AGCObject,DLocalEnv>(frame->local_env());
            this->pc_ = frame->cont();
            this->cont_ = VsmInstr::c_sentinel;
        }

        void
        DVirtualSchematikaMachine::_do_ifelse_cont_op()
        {
            // pre: result of evaluating test condition in value_ register

            auto frame = obj<AGCObject,DVsmIfElseContFrame>::from(stack_);

            assert(frame);
            assert(value_.is_value());

            auto flag = obj<AGCObject,DBoolean>::from(*value_.value());

            if (flag.data()) {
                obj<AExpression> next_expr;
                {
                    if (flag->value()) {
                        // proceed with if-branch
                        next_expr = frame->ifelse_expr()->when_true();
                    } else {
                        // proceed with else-branch
                        next_expr = frame->ifelse_expr()->when_false();
                    }
                }

                this->stack_ = frame->parent();
                this->expr_ = next_expr;
                this->pc_ = VsmInstr::c_eval;
                this->cont_ = frame->cont();
            } else {
                auto error = DRuntimeError::make(mm_.to_op(),
                                                 "_do_ifelse_cont_op",
                                                 "expected boolean for test condition");
                this->value_ = VsmResult(error);

                // for now: halt VSM execution
                // TODO: some combination of
                // 1. emit stack trace
                // 2. go to debugger
                // 3. have every vsm instruction check inputs for errors

                this->pc_ = VsmInstr::c_halt;
                this->cont_ = VsmInstr::c_sentinel;
            }
        }

        void
        DVirtualSchematikaMachine::_do_seq_cont_op()
        {
            auto frame = obj<AGCObject,DVsmSeqContFrame>::from(stack_);

            assert(frame);

            uint32_t i_seq = 1 + frame->i_seq();

            auto seq_expr = frame->seq_expr();

            assert(seq_expr);

            if (i_seq == seq_expr->size()) {
                /* done with sequence
                 * value of sequence-expr is the value of the last expression in that sequence,
                 * which is already in the value_ register
                 */

                this->stack_ = frame->parent();
                this->pc_ = frame->cont();
                this->cont_ = VsmInstr::c_sentinel;

                return;
            } else {
                frame->incr_i_seq();

                this->expr_ = (*seq_expr)[i_seq];
                this->pc_ = VsmInstr::c_eval;
                this->cont_ = VsmInstr::c_seq_cont;

                return;
            }
         }

        std::size_t
        DVirtualSchematikaMachine::shallow_size() const noexcept
        {
            return sizeof(DVirtualSchematikaMachine);
        }

        DVirtualSchematikaMachine *
        DVirtualSchematikaMachine::gco_shallow_move(obj<AGCObjectVisitor> gc) noexcept
        {
            // need move-ctor on abox<..>
            //return gc.std_move_for(this);

            (void)gc;

            /** TODO: should be able to use gc.std_move_for(this) now
             *  that shallow_move uses move construction.
             *  DVirtualSchematikaMachine is (or can be made) moveable.
             **/

            assert(false);

            return nullptr;
        }

        void
        DVirtualSchematikaMachine::visit_gco_children(VisitReason reason,
                                                      obj<AGCObjectVisitor> gc) noexcept
        {
            reader_.visit_gco_children(reason, gc);

            gc.visit_child(reason, &stack_);
            gc.visit_poly_child(reason, &expr_);
            gc.visit_child(reason, &global_env_);
            gc.visit_child(reason, &local_env_);
            gc.visit_child(reason, &fn_);
            gc.visit_child(reason, &args_);
            if (value_.is_value()) {
                gc.visit_child(reason, const_cast<obj<AGCObject> *>(&value_.value_ref()));
            }
        }

    } /*namespace scm*/
} /*namespace xo*/

/* end DVirtualSchematikaMachine.cpp */
