/** @file VirtualSchematikaMachine.cpp **/ #include "VirtualSchematikaMachine.hpp" #include "VsmInstr.hpp" #include "ExpressionBoxed.hpp" #include "xo/expression/ConstantInterface.hpp" #include "xo/expression/DefineExpr.hpp" #include "xo/expression/AssignExpr.hpp" #include "xo/expression/Variable.hpp" #include "xo/expression/IfExpr.hpp" #include "xo/object/Boolean.hpp" #include "xo/alloc/GC.hpp" /** continue after completing a VSM instruction; * achieve by jumping to continuation. **/ #define VSM_CONTINUE() this->pc_ = this->cont_; return; /** report error and terminate VSM execution **/ #define VSM_ERROR(msg) report_error(msg); return; namespace xo { using xo::gc::GC; using xo::obj::Boolean; namespace scm { struct VsmOps { /** halt virtual scheme machine. * This will cause innermost run() to return to its caller **/ static VsmInstr halt_op; /** evaluate an expression. * - opcode is Opcode::eval * - expression in register @ref expr_ **/ static VsmInstr eval_op; /** assign variable after evaluating rhs of a define-expression or assign-expression * - opcode is Opcode::complete_assign * - top stack frame contains {lhs, cont} **/ static VsmInstr complete_assign_op; /** choose branch of if-expr after evaluating test condition. * - opcode is Opcode::complete_ifexpr * - top stack frame contains {ifexpr, cont} **/ static VsmInstr complete_ifexpr_op; }; VsmInstr VsmOps::halt_op{VsmInstr::Opcode::halt, "halt"}; VsmInstr VsmOps::eval_op{VsmInstr::Opcode::eval, "eval"}; VsmInstr VsmOps::complete_assign_op{VsmInstr::Opcode::complete_assign, "complete-assign"}; VsmInstr VsmOps::complete_ifexpr_op{VsmInstr::Opcode::complete_ifexpr, "complete-ifexpr"}; // ----- VirtualSchematikaMachineFlyweight ----- VirtualSchematikaMachineFlyweight::VirtualSchematikaMachineFlyweight(gc::IAlloc * mm, gp env, log_level ll) : object_mm_{mm}, toplevel_env_{env}, log_level_{ll} {} // ----- VirtualSchematikaMachine ----- VirtualSchematikaMachine::VirtualSchematikaMachine(gc::IAlloc * mm, gp env, log_level ll) : flyweight_{mm, env, ll} { this->env_ = env; // gc roots gc::GC * gc = GC::from(mm); if (gc) { assert((gc->gc_in_progress() == false) && "cannot add roots while GC running"); gc->add_gc_root_dwim(&env_); gc->add_gc_root_dwim(&value_); } else { // Want to support VSM with arena-allocator-only; // if only for unit testing. } // TODO: install builtin primitives here } VirtualSchematikaMachine::~VirtualSchematikaMachine() { gc::GC * gc = GC::from(flyweight_.object_mm_); if (gc) { assert((gc->gc_in_progress() == false) && "cannot remove roots while GC running"); gc->remove_gc_root_dwim(&env_); gc->remove_gc_root_dwim(&value_); } else { // nothing to do in arena-only mode } } std::pair, SchematikaError> VirtualSchematikaMachine::toplevel_eval(bp expr) { return this->eval(expr, this->env_); } std::pair, SchematikaError> VirtualSchematikaMachine::eval(bp expr, gp env) { this->pc_ = &VsmOps::eval_op; this->expr_ = expr.promote(); this->env_ = env; this->stack_ = nullptr; this->value_ = nullptr; this->error_ = SchematikaError(); this->cont_ = &VsmOps::halt_op; this->run(); return std::make_pair(this->value_, this->error_); } void VirtualSchematikaMachine::run() { while(pc_) this->execute_one(); } void VirtualSchematikaMachine::execute_one() { scope log(XO_DEBUG(true)); log && log("stack", stack_); using Opcode = VsmInstr::Opcode; switch (pc_->opcode()) { case Opcode::halt: { this->pc_ = nullptr; this->cont_ = nullptr; break; } case Opcode::eval: { log && log("Opcode::eval"); /* generally speaking: opcode will be 1:1 with extypes */ switch (expr_->extype()) { case exprtype::constant: log && log("eval -> constant"); this->eval_constant_op(); break; case exprtype::define: log && log("eval -> define"); this->eval_define_op(); break; case exprtype::assign: log && log("eval -> assign"); this->eval_assign_op(); break; case exprtype::variable: log && log("eval -> variable"); this->eval_variable_op(); break; case exprtype::ifexpr: log && log("eval -> ifexpr"); this->eval_ifexpr_op(); break; case exprtype::invalid: case exprtype::primitive: case exprtype::apply: case exprtype::lambda: case exprtype::sequence: case exprtype::convert: case exprtype::n_expr: this->pc_ = nullptr; this->value_ = nullptr; this->error_ = SchematikaError(tostr("execute_vsm: not implemented", xtag("extype", expr_->extype()))); this->cont_ = nullptr; break; } } break; case Opcode::complete_assign: this->do_complete_assign_op(); break; case Opcode::complete_ifexpr: this->do_complete_ifexpr_op(); break; case Opcode::N_Opcode: assert(false); break; } } void VirtualSchematikaMachine::report_error(const std::string & err) { /* error short-circuits vsm operation */ this->pc_ = nullptr; this->value_ = nullptr; this->error_ = SchematikaError(err); this->cont_ = nullptr; } void VirtualSchematikaMachine::eval_constant_op() { using xo::scm::ConstantInterface; scope log(XO_DEBUG(true)); bp expr = ConstantInterface::from(expr_); assert(expr); this->value_ = flyweight_.object_converter_.tp_to_object(flyweight_.object_mm_, expr->value_tp(), false); if (this->value_.ptr()) { log && log("got object: ", xtag("value", value_)); VSM_CONTINUE(); } else { /* see ObjectConverter::ctor to add more builtin types */ VSM_ERROR(tostr("constant_op: unable to convert native value to object", xtag("id", expr->value_tp().td()->id()), xtag("short_name", expr->value_tp().td()->short_name()))); } } // placeholder: primitive_op void VirtualSchematikaMachine::eval_define_op() { using xo::scm::DefineExpr; scope log(XO_DEBUG(true)); auto mm = flyweight_.object_mm_; bp expr = DefineExpr::from(expr_); assert(expr); assert(env_.get()); // note: expecting nested define to have expanded iteself into // applying a lambda // note: establish lhs_var first, to allow for recursion, for example: // def fact(n: i64) { if (n == 0) then 1; else n * fact(n-1); } /** remembers promised variable type **/ this->env_->establish_var(expr->lhs_variable()); /** must promote rp -> gp **/ gp lhs_0 = ExpressionBoxed::make(mm, expr->lhs_variable()); this->pc_ = &VsmOps::eval_op; this->expr_ = expr->rhs(); /* when control arrives at .cont_, will have: * .value_ -> result of evaluating expr->rhs() */ this->stack_ = VsmStackFrame::push1(mm, this->stack_, lhs_0, cont_); /* .stack_: * frame * [0] = lhs_0 (boxed lhs Variable) * .. */ this->cont_ = &VsmOps::complete_assign_op; } void VirtualSchematikaMachine::eval_assign_op() { using xo::scm::AssignExpr; scope log(XO_DEBUG(true)); auto mm = flyweight_.object_mm_; bp assign = AssignExpr::from(expr_); assert(assign.get()); assert(env_.get()); assert(assign->lhs().get()); assert(assign->rhs().get()); /* verify slot exists, before we evaluate rhs */ gp * slot = env_->lookup_slot(assign->lhs()->name()); if (slot) { /** must promote rp -> gp **/ gp lhs = ExpressionBoxed::make(mm, assign->lhs()); this->pc_ = &VsmOps::eval_op; this->expr_ = assign->rhs(); /* when control arrives at .cont_, will have: * .value_ -> result of evaluating assign->rhs() */ this->stack_ = VsmStackFrame::push1(mm, this->stack_, lhs, cont_); /* .stack_: * frame * [0] = lhs (boxed lhs Variable) * .. */ this->cont_ = &VsmOps::complete_assign_op; } else { std::string err = tostr("no binding for lhs of assignment", xtag("name", assign->lhs()->name())); this->value_ = nullptr; this->error_ = SchematikaError(err); /* note: poor man's exception */ this->pc_ = nullptr; this->cont_ = nullptr; } } void VirtualSchematikaMachine::do_complete_assign_op() { scope log(XO_DEBUG(true)); /* * - value: contains result of evaluating rhs of define * - stack: top frame has 1 slot, holds variable to receive assignment */ assert(value_.get()); assert(stack_.get()); assert(env_.get()); gp sp0 = this->stack_; bp var = Variable::from(ExpressionBoxed::from((*sp0)[0])->contents()); assert(var.get()); gp * slot = this->env_->establish_var(var); assert(slot); *slot = this->value_; //this->value_ = this->value_; // preserve value from rhs of defexpr this->stack_ = sp0->parent(); this->pc_ = this->cont_ = sp0->continuation(); } void VirtualSchematikaMachine::eval_variable_op() { using xo::scm::Variable; scope log(XO_DEBUG(true)); bp var = Variable::from(expr_); assert(var.get()); assert(env_.get()); const gp * slot = env_->lookup_slot(var->name()); if (slot) { this->value_ = *slot; this->pc_ = cont_; } else { /* Unknown variable error will often be recognized in expression parser, * in such cases this path won't be used. * * In interactive environment will need some kind of support for modifying * code (e.g. replacing top-level functions/variables), and in particular, * replacements may have different type signature. * It's possible that allowing for such replacements winds up giving up * typesafety guarantees. In that case this path may get activated after * all. */ std::string err = tostr("no binding for variable", xtag("name", var->name())); this->value_ = nullptr; this->error_ = SchematikaError(err); /* note: poor man's exception */ this->pc_ = nullptr; this->cont_ = nullptr; } } void VirtualSchematikaMachine::eval_ifexpr_op() { using xo::scm::IfExpr; scope log(XO_DEBUG(true)); gc::IAlloc * mm = flyweight_.object_mm_; /** must promote bp -> gp **/ gp ifexpr_boxed = ExpressionBoxed::make(mm, expr_); bp ifexpr = IfExpr::from(expr_); assert(ifexpr.get()); assert(env_.get()); this->pc_ = &VsmOps::eval_op; this->expr_ = ifexpr->test(); /* when control arrives at .cont_ will have: * .value_ -> result of evaluating ifexpr->test() */ this->stack_ = VsmStackFrame::push1(mm, this->stack_, ifexpr_boxed, cont_); /* .stack_: * frame * [0] = ifexpr (boxed expression) */ this->cont_ = &VsmOps::complete_ifexpr_op; } void VirtualSchematikaMachine::do_complete_ifexpr_op() { using xo::scm::IfExpr; scope log(XO_DEBUG(true)); /* * - value: contains result of evaluating test condition of if-expr * - stack: top frame has 1 slot, holds (boxed) if-expr itself */ assert(value_.get()); assert(stack_.get()); assert(env_.get()); gp test_value = gp::from(value_); if (test_value.get()) { gp sp0 = this->stack_; bp ifexpr = IfExpr::from(ExpressionBoxed::from((*sp0)[0])->contents()); assert(ifexpr.get()); this->pc_ = &VsmOps::eval_op; if (test_value->value()) { this->expr_ = ifexpr->when_true(); } else { this->expr_ = ifexpr->when_false(); } this->stack_ = sp0->parent(); this->cont_ = sp0->continuation(); } else { std::string err = tostr("expect boolean value for result of if-expr test", xtag("value", test_value)); this->value_ = nullptr; this->error_ = SchematikaError(err); /* note: poor man's exception */ this->pc_ = nullptr; this->cont_ = nullptr; } } } /*namespace scm*/ } /*namespace xo*/ /* end VirtualSchematikaMachine.cpp */