/** @file VirtualSchematikaMachine.hpp **/ #pragma once #include "VsmInstr.hpp" #include "VsmStackFrame.hpp" #include "SchematikaError.hpp" #include "GlobalEnv.hpp" #include "xo/expression/Expression.hpp" #include "xo/object/ObjectConverter.hpp" #include "xo/alloc/Object.hpp" namespace xo { namespace scm { /** @brief state that may be shared across VirtualSchematikaMachine instances **/ struct VirtualSchematikaMachineFlyweight { explicit VirtualSchematikaMachineFlyweight(gc::IAlloc * mm, gp env, log_level log_level); /** memory allocator for interpreter operation. **/ gc::IAlloc * object_mm_ = nullptr; /** global environment **/ gp toplevel_env_; /** convert TaggedPtr->Object **/ xo::obj::ObjectConverter object_converter_; /** control logging level. higher values -> more logging **/ log_level log_level_; }; /** @class VirtualSchematikaMachine * @brief Virtual machine implementing a Schematika interpreter * **/ class VirtualSchematikaMachine { public: using IAlloc = xo::gc::IAlloc; public: VirtualSchematikaMachine(IAlloc * mm, gp toplevel_env, log_level log_level); ~VirtualSchematikaMachine(); gp toplevel_env() const { return flyweight_.toplevel_env_; } /** evaluate expression @p expr. * borrows calling thread until completion * return [value, error]. error ignored unless value is nullptr. * conversely when value is nullptr, error gives details of original * error. * * Evaluate schematika expression @p expr in environment @p env **/ std::pair, SchematikaError> eval(bp expr, gp env); /** evaluate expression @p expr in toplevel environment **/ std::pair, SchematikaError> toplevel_eval(bp expr); private: /** Not moveable or copyable. * One constraint is member variables added to flyweight_.object_mm_ * as GC roots, with no provision for unwinding later. **/ VirtualSchematikaMachine(const VirtualSchematikaMachine &) = delete; VirtualSchematikaMachine(VirtualSchematikaMachine &&) = delete; /** borrow calling thread to run schematika machine * indefinitely, or until null continuation **/ void run(); /** execute vsm instruction in program counter. * Note: may possibly be able to replace with just opcode * * Registers: * - expr_ input, caller saves * - env_ input, caller saves * - cont_ input, caller saves * - stack_ input, caller saves * - value_ output * - error_ output * **/ void execute_one(); /* design note: * - eval_xxx_op() methods are primary VSM transitions, * in the sense that they begin a sequence of transitions to interpret a * particular kind of expression * - do_xxx_op() methods represent secondary VSM transitions, * that continue an instruction sequence that was initiated by a preceding * eval_xxx_op() method */ /** interpret literal constant expression **/ void eval_constant_op(); /** interpreter literal primitive expression * (these appear implicitly as result of builtin operators like {+, ==, ..}) **/ void eval_primitive_op(); /** execute define expression (finished in do_complete_assign_op()) **/ void eval_define_op(); /** execute assign expression (finishes in do_complete_assign_op()) **/ void eval_assign_op(); /** continue after establishing value fo rhs of define exprsssion **/ void do_complete_assign_op(); /** interpret variable expression **/ void eval_variable_op(); /** interpret if-expression **/ void eval_ifexpr_op(); /** continue after establish value of test expression **/ void do_complete_ifexpr_op(); /** interpret sequence **/ void eval_sequence_op(); /** continue after establishing value for a sequence element **/ void do_complete_sequence_op(); /** interpret apply-expression (i.e. function call) **/ void eval_apply_op(); /** continue assembling args for a function call; * transition to (interpretation of) called function once all arguments * are evaluated. **/ void do_complete_evalargs_op(); /** execute function application, given actuals in top stack frame **/ void apply_op(); /** goto error state with message @p err **/ void report_error(const std::string & err); /** implementation class; contains instruction implementations **/ friend struct VsmOps; private: /** program counter. * (Perhaps replace with VsmInstr::Opcode ?) **/ const VsmInstr * pc_ = nullptr; /** register to hold Schematika expression to drive @ref execute_one. * * caller saves! **/ rp expr_; /** holds bindings for all schematika variables, to drive @ref execute_one. * execute_one will not save this * * caller saves! **/ gp env_; /** vsm stack. callee saves! **/ gp stack_; /** non-error result value from eval() / apply() * * output register: caller must save **/ gp value_; /** error result value from eval() / apply() * * output regisetr: caller must save **/ SchematikaError error_; /** continuation * (Perhaps replace with VsmInstr::Opcode ?) * * input register: callee saves! **/ const VsmInstr * cont_ = nullptr; /** possibly-shared data **/ VirtualSchematikaMachineFlyweight flyweight_; }; } /*namespace scm*/ } /*namespace xo*/ /* end VirtualSchematikaMachine.hpp */