From f26c05d64f1c45539225f6a3110422eb63ee80ac Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 18 Feb 2026 01:46:45 -0500 Subject: [PATCH] xo-interpreter2: + skrepl (read eval print loop) --- xo-interpreter2/CMakeLists.txt | 1 + .../interpreter2/VirtualSchematikaMachine.hpp | 5 + .../interpreter2/VirtualSchematikaMachine.cpp | 6 + xo-interpreter2/src/skrepl/CMakeLists.txt | 13 + xo-interpreter2/src/skrepl/skreplxx.cpp | 242 ++++++++++++++++++ .../utest/VirtualSchematikaMachine.test.cpp | 13 +- 6 files changed, 268 insertions(+), 12 deletions(-) create mode 100644 xo-interpreter2/src/skrepl/CMakeLists.txt create mode 100644 xo-interpreter2/src/skrepl/skreplxx.cpp diff --git a/xo-interpreter2/CMakeLists.txt b/xo-interpreter2/CMakeLists.txt index 2d1a6db8..426e4112 100644 --- a/xo-interpreter2/CMakeLists.txt +++ b/xo-interpreter2/CMakeLists.txt @@ -21,6 +21,7 @@ add_definitions(${PROJECT_CXX_FLAGS}) # output targets add_subdirectory(src/interpreter2) +add_subdirectory(src/skrepl) add_subdirectory(utest) # ---------------------------------------------------------------- diff --git a/xo-interpreter2/include/xo/interpreter2/VirtualSchematikaMachine.hpp b/xo-interpreter2/include/xo/interpreter2/VirtualSchematikaMachine.hpp index 724e1c33..75fb968f 100644 --- a/xo-interpreter2/include/xo/interpreter2/VirtualSchematikaMachine.hpp +++ b/xo-interpreter2/include/xo/interpreter2/VirtualSchematikaMachine.hpp @@ -91,6 +91,11 @@ namespace xo { /** allocator for runtime errors **/ obj error_allocator() const noexcept; + /** true iff parser is at top-level -> does not contain + * state for a incomplete/partial expression + **/ + bool is_at_toplevel() const noexcept; + /** visit vsm-owned memory pools; call visitor(info) for each **/ void visit_pools(const MemorySizeVisitor & visitor) const; diff --git a/xo-interpreter2/src/interpreter2/VirtualSchematikaMachine.cpp b/xo-interpreter2/src/interpreter2/VirtualSchematikaMachine.cpp index dc8844e2..9104fa2f 100644 --- a/xo-interpreter2/src/interpreter2/VirtualSchematikaMachine.cpp +++ b/xo-interpreter2/src/interpreter2/VirtualSchematikaMachine.cpp @@ -86,6 +86,12 @@ namespace xo { return error_mm_.to_op(); } + bool + VirtualSchematikaMachine::is_at_toplevel() const noexcept + { + return reader_.is_at_toplevel(); + } + void VirtualSchematikaMachine::visit_pools(const MemorySizeVisitor & visitor) const { diff --git a/xo-interpreter2/src/skrepl/CMakeLists.txt b/xo-interpreter2/src/skrepl/CMakeLists.txt new file mode 100644 index 00000000..b5511114 --- /dev/null +++ b/xo-interpreter2/src/skrepl/CMakeLists.txt @@ -0,0 +1,13 @@ +# xo-interpreter2/src/repl/CMakeLists.txt + +set(SELF_EXE skrepl) +set(SELF_SRCS skreplxx.cpp) + +xo_add_executable(${SELF_EXE} ${SELF_SRCS}) +xo_self_dependency(${SELF_EXE} xo_interpreter2) +xo_external_target_dependency(${SELF_EXE} replxx replxx::replxx) + +# replxx requires this +find_package(Threads REQUIRED) +target_link_libraries(${SELF_EXE} PUBLIC Threads::Threads) + diff --git a/xo-interpreter2/src/skrepl/skreplxx.cpp b/xo-interpreter2/src/skrepl/skreplxx.cpp new file mode 100644 index 00000000..df6be29d --- /dev/null +++ b/xo-interpreter2/src/skrepl/skreplxx.cpp @@ -0,0 +1,242 @@ +/** @file skreplxx.cpp + * + * @author Roland Conybeare, Feb 2026 + **/ + +#include +#include +#include +#include +#include +#include + +namespace xo { + using xo::scm::VirtualSchematikaMachine; + using xo::scm::VsmResultExt; + using xo::mm::AAllocator; + using xo::mm::ArenaConfig; + using xo::mm::DArena; + using span_type = xo::mm::span; + using xo::facet::FacetRegistry; + using xo::facet::TypeRegistry; + using std::cerr; + + // presumeably replxx assumes input is a tty anyway? + // + bool replxx_getline(bool interactive, + bool is_at_toplevel, + replxx::Replxx & rx, + span_type * p_input + //const char ** p_input + ) + { + using namespace std; + + char const * prompt = ""; + + if (interactive) { + prompt = ((is_at_toplevel) ? "> " : ". "); + } + + const char * input_cstr = rx.input(prompt); + + bool retval = (input_cstr != nullptr); + + if (retval) + *p_input = span_type::from_cstr(input_cstr); + + if (input_cstr) + rx.history_add(input_cstr); + + return retval; + } + + void + welcome(std::ostream & os) + { + using namespace std; + + os << "schematika repl" << endl; + os << " ctrl-a/ctrl-e beginning/end of line" << endl; + os << " ctrl-u delete entire line" << endl; + os << " ctrl-k delete to end of line" << endl; + os << " meta- backward delete word" << endl; + os << " |meta-p previous command from history" << endl; + os << " |meta-n next command from history" << endl; + os << " / page through history faster" << endl; + os << " ctrl-s/ctrl-r forward/backward history search" << endl; + os << endl; + } + + struct ReplConfig { + ReplConfig() = default; + + std::size_t max_history_size_ = 1000; + std::string repl_history_fname_ = "skrepl_history.txt"; + bool debug_flag_ = false; + }; + + struct AppConfig { + using VsmConfig = xo::scm::VsmConfig; + + //using ReaderConfig = xo::scm::ReaderConfig; + //using X1CollectorConfig = xo::mm::X1CollectorConfig; + //using ArenaConfig = xo::mm::ArenaConfig; + + AppConfig(const ReplConfig & repl_cfg = ReplConfig(), + const ArenaConfig & app_arena_cfg = ArenaConfig().with_name("skreplxx").with_size(16 * 1024), + const VsmConfig & vsm_cfg = VsmConfig()) + : repl_config_{repl_cfg}, app_arena_config_{app_arena_cfg}, vsm_config_{vsm_cfg} + { + //rdr_config_.reader_debug_flag_ = true; + //rdr_config.parser_debug_flag_ = true; + //rdr_config.tk_debug_flag_ = true; + } + + ReplConfig repl_config_; + ArenaConfig app_arena_config_; + VsmConfig vsm_config_; + //ReaderConfig rdr_config_; + //X1CollectorConfig x1_config_ = (X1CollectorConfig().with_name("gc").with_size(4*1024*1024)); + //ArenaConfig fixed_config_ = (ArenaConfig().with_name("fixed").with_size(4*1024)); + }; + + struct App { + //using AAllocator = xo::mm::AAllocator; + //using DX1Collector = xo::mm::DX1Collector; + //using X1CollectorConfig = xo::mm::X1CollectorConfig; + //using DArena = xo::mm::DArena; + //using ArenaConfig = xo::mm::ArenaConfig; + using Replxx = replxx::Replxx; + using span_type = VirtualSchematikaMachine::span_type; + + App(const AppConfig & cfg = AppConfig()) + : repl_config_{cfg.repl_config_}, + app_arena_{cfg.app_arena_config_}, + vsm_{cfg.vsm_config_, obj(&app_arena_)} + { + this->interactive_ = isatty(STDIN_FILENO); + + rx_.set_max_history_size(repl_config_.max_history_size_); + rx_.history_load(repl_config_.repl_history_fname_); + // rx.bind_key_internal(Replxx::KEY::control('p'), "history_previous"); + // rx.bind_key_internal(Replxx::KEY::control('n'), "history_next"); + } + + /** borrows calling thread to run application **/ + void run(); + + private: + void _init(); + void _start(); + void _repl(); + bool _read_eval_print(span_type * p_input, bool eof); + + private: + InitEvidence init_evidence_ = 0; + ReplConfig repl_config_; + bool interactive_ = false; + Replxx rx_; + ///** collector/allocator for schematika expressions **/ + //DX1Collector x1_; + ///** e.g. for DArenaHashMap within global symtab **/ + //DArena fixed_; + + /** arena with same lifetime as this application **/ + DArena app_arena_; + /** schematika virtual machine **/ + VirtualSchematikaMachine vsm_; + }; + + void + App::run() + { + this->_init(); + this->_start(); + this->_repl(); + } + + void + App::_init() + { + // window to contorl size of registries ends as soon as we init other subsystems + TypeRegistry::instance(1024); + FacetRegistry::instance(1024); + + InitEvidence init_evidence_ = (InitSubsys::require()); + + Subsystem::initialize_all(); + } + + void + App::_start() + { + welcome(cerr); + + vsm_.begin_interactive_session(); + } + + void + App::_repl() + { + bool eof = false; + span_type input; + + // outer loop: fetch one line of interactive input + while (replxx_getline(interactive_, vsm_.is_at_toplevel(), rx_, &input)) { + + // inner lo9op: consume up to one expression at a time. + while (!input.empty() && this->_read_eval_print(&input, false /*eof*/)) + { + ; + } + + /* here: either: + * 1. input.empty() or + * 2. error encountered + */ + } + + /* reminder: eof can complete at most one token */ + this->_read_eval_print(&input, true /*eof*/); + } + + /** body of read-parse-print loop + * + * true -> no errors; + * false -> reader encountered error + **/ + bool + App::_read_eval_print(span_type * p_input, + bool eof) + { + scope log(XO_DEBUG(repl_config_.debug_flag_)); + + if (!p_input || p_input->empty()) + return true; + + VsmResultExt res = vsm_.read_eval_print(*p_input, eof); + + *p_input = res.remaining_; + + return !res.is_tk_error() && !res.is_eval_error(); + } + +} /*namespace xo*/ + +int +main (int argc, char * argv[]) +{ + using xo::AppConfig; + using xo::App; + + AppConfig cfg; + // [cmdline options here] + + App app(cfg); + + app.run(); +} /*main*/ + +/* end skreplxx.cpp */ + diff --git a/xo-interpreter2/utest/VirtualSchematikaMachine.test.cpp b/xo-interpreter2/utest/VirtualSchematikaMachine.test.cpp index 8d4188a6..77b3a30b 100644 --- a/xo-interpreter2/utest/VirtualSchematikaMachine.test.cpp +++ b/xo-interpreter2/utest/VirtualSchematikaMachine.test.cpp @@ -3,6 +3,7 @@ * @author Roland Conybeare, Jan 2026 **/ +#include #include #include #include @@ -12,18 +13,6 @@ #include #include #include - -#ifdef NOT_YET -#include -#include -#include -#include -#include -#endif -#include -#ifdef NOT_YET -#include -#endif #include #include