diff --git a/xo-interpreter/example/replxx/replxx.cpp b/xo-interpreter/example/replxx/replxx.cpp index ad735b1d..d1d134c0 100644 --- a/xo-interpreter/example/replxx/replxx.cpp +++ b/xo-interpreter/example/replxx/replxx.cpp @@ -1,140 +1,16 @@ /** @file replxx.cpp **/ -#include "xo/reader/reader.hpp" -#include -#include -#include // for isatty - -// similar helper in exprreplxx.cpp -// -bool replxx_getline(bool interactive, - std::size_t parser_stack_size, - replxx::Replxx & rx, - std::string & input) -{ - using namespace std; - - char const * prompt = ""; - - if (interactive) { - if (parser_stack_size <= 1) - prompt = "> "; - else - prompt = ". "; - } - - /* input_cstr: next line of input from replxx library */ - const char * input_cstr = rx.input(prompt); - - bool retval = (input_cstr != nullptr); - - if (retval) { - /* got new input */ - input = input_cstr; - } - - rx.history_add(input); - - input.push_back('\n'); - - return retval; -} - -void -welcome(std::ostream & os) -{ - using namespace std; - - os << "read-eval-print loop for schematika expressions" << 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; -} +#include "xo/interpreter/Schematika.hpp" int -main() +main(int argc, char ** argv) { - using namespace replxx; - using namespace xo::scm; - using xo::scm::Expression; - using xo::print::ppconfig; - using xo::print::ppstate_standalone; - using xo::rp; - using namespace std; + using xo::scm::Schematika; - using span_type = xo::scm::span; + Schematika::Config cfg; + Schematika scm = Schematika::make(cfg); - bool interactive = isatty(STDIN_FILENO); - - Replxx rx; - rx.set_max_history_size(1000); - rx.history_load("repl_history.txt"); -// rx.bind_key_internal(Replxx::KEY::control('p'), "history_previous"); -// rx.bind_key_internal(Replxx::KEY::control('n'), "history_next"); - - constexpr bool c_debug_flag = false; - - reader rdr(c_debug_flag); - rdr.begin_interactive_session(); - - string input_str; - - bool eof = false; - - span_type input; - std::size_t parser_stack_size = 0; - - welcome(cerr); - - while (replxx_getline(interactive, parser_stack_size, rx, input_str)) { - input = span_type::from_string(input_str); - - while (!input.empty()) { - auto [expr, consumed, psz, error] = rdr.read_expr(input, eof); - - if (expr) { - ppconfig ppc; - ppstate_standalone pps(&cout, 0, &ppc); - - pps.prettyn(expr); - } else if (error.is_error()) { - cout << "parsing error (detected in " << error.src_function() << "): " << endl; - error.report(cout); - - /* discard stashed remainder of input line - * (for nicely-formatted errors) - */ - rdr.reset_to_idle_toplevel(); - break; - } - - input = input.after_prefix(consumed); - parser_stack_size = psz; - } - - /* here: input.empty() or error encountered */ - - } - - auto [expr, _1, _2, error] = rdr.read_expr(input, true /*eof*/); - - if (expr) { - ppconfig ppc; - ppstate_standalone pps(&cout, 0, &ppc); - - pps.prettyn>(rp(expr)); - } else if (error.is_error()) { - cout << "parsing error (detected in " << error.src_function() << "): " << endl; - error.report(cout); - } - - rx.history_save("repl_history.txt"); + scm.interactive_repl(); } /* end replxx.cpp */ diff --git a/xo-interpreter/include/xo/interpreter/Schematika.hpp b/xo-interpreter/include/xo/interpreter/Schematika.hpp index 7a19869b..f79167bf 100644 --- a/xo-interpreter/include/xo/interpreter/Schematika.hpp +++ b/xo-interpreter/include/xo/interpreter/Schematika.hpp @@ -3,18 +3,38 @@ * @author Roland Conybeare, Nov 2025 **/ -#pramga once - +#pragma once +#include "xo/alloc/IAlloc.hpp" namespace xo { namespace scm { - /** @class Schematika * @brief schematika interpreter state **/ class Schematika { public: + class Impl; + + struct Config { + /** true to enable welcome message **/ + bool welcome_flag_ = true; + /** number of command history items to preserve **/ + std::size_t history_size = 100; + /** on startup: load command history from this file; + persist last @ref history_size commands to the same file + **/ + std::string history_file = "scm_history.txt"; + /** when true enable console logging for repl internals **/ + bool debug_flag = false; + }; + + public: + ~Schematika(); + + /** create instance with configuration @p cfg **/ + static Schematika make(const Config & cfg); + /** interactive read-eval-print loop. * Uses replxx to read from stdin. * If stdin is interactive, accepts line editing commands: @@ -31,8 +51,10 @@ namespace xo { void interactive_repl(); private: - class Impl; - std::unique_ptr p_impl_; + Schematika(const Config & cfg); + + private: + up p_impl_; }; } } diff --git a/xo-interpreter/src/interpreter/CMakeLists.txt b/xo-interpreter/src/interpreter/CMakeLists.txt index d0824e34..e9fb9317 100644 --- a/xo-interpreter/src/interpreter/CMakeLists.txt +++ b/xo-interpreter/src/interpreter/CMakeLists.txt @@ -4,10 +4,16 @@ set(SELF_LIB xo_interpreter) set(SELF_SRCS init_interpreter.cpp StackFrame.cpp + Schematika.cpp VirtualSchematikaMachine.cpp ) +find_package(Threads REQUIRED) + xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) xo_dependency(${SELF_LIB} xo_alloc) -#xo_dependency(${SELF_LIB} xo_reader) +xo_dependency(${SELF_LIB} xo_expression) +xo_dependency(${SELF_LIB} xo_reader) +xo_external_target_dependency(${SELF_LIB} replxx replxx::replxx) +target_link_libraries(${SELF_LIB} PUBLIC Threads::Threads) xo_headeronly_dependency(${SELF_LIB} subsys) diff --git a/xo-interpreter/src/interpreter/Schematika.cpp b/xo-interpreter/src/interpreter/Schematika.cpp new file mode 100644 index 00000000..1c5b4361 --- /dev/null +++ b/xo-interpreter/src/interpreter/Schematika.cpp @@ -0,0 +1,212 @@ +/** @file Schematika.cpp + * + * @author Roland Conybeare, Nov 2025 + **/ + +#include "Schematika.hpp" +#include "xo/reader/reader.hpp" +#include +#include + +namespace xo { + using xo::print::ppconfig; + using xo::print::ppstate_standalone; + using replxx::Replxx; + using namespace std; + + namespace scm { + + class Schematika::Impl { + public: + Impl(const Config & config) : config_{config} {} + + /** borrow calling thread to run interactive read-eval-print loop; + * input from stdin, output to stdout. + **/ + void interactive_repl(); + + void welcome(std::ostream & os); + + bool replxx_getline(bool interactive, + std::size_t parser_stack_size, + replxx::Replxx & rx, + std::string & input); + + private: + /** configuration **/ + Config config_; + }; + + void + Schematika::Impl::welcome(std::ostream & os) + { + using namespace std; + + os << "read-eval-print loop for schematika expressions" << 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; + } + + // similar helper in exprreplxx.cpp + // + bool + Schematika::Impl::replxx_getline(bool interactive, + std::size_t parser_stack_size, + replxx::Replxx & rx, + std::string & input) + { + using namespace std; + + char const * prompt = ""; + + if (interactive) { + if (parser_stack_size <= 1) + prompt = "> "; + else + prompt = ". "; + } + + /* input_cstr: next line of input from replxx library */ + const char * input_cstr = rx.input(prompt); + + bool retval = (input_cstr != nullptr); + + if (retval) { + /* got new input */ + input = input_cstr; + } + + rx.history_add(input); + + input.push_back('\n'); + + return retval; + } + + void + Schematika::Impl::interactive_repl() + { + using span_type = xo::scm::span; + + bool interactive = isatty(STDIN_FILENO); + + Replxx rx; + rx.set_max_history_size(config_.history_size); + rx.history_load(config_.history_file); + // rx.bind_key_internal(Replxx::KEY::control('p'), "history_previous"); + // rx.bind_key_internal(Replxx::KEY::control('n'), "history_next"); + + reader rdr(config_.debug_flag); + rdr.begin_interactive_session(); + + string input_str; + + bool eof = false; + + span_type input; + std::size_t parser_stack_size = 0; + + if (config_.welcome_flag_) + welcome(cerr); + + while (replxx_getline(interactive, parser_stack_size, rx, input_str)) { + input = span_type::from_string(input_str); + + while (!input.empty()) { + /** + * Three cases here: + * 1. available input is invalid (does not conform to schematika syntax). + * 1a. expr=nullptr + * 1b. consumed reads all available input + * 1c. psz=0 + * 1d. error.is_error(); details including exact location where parsing failed. + * 1e. parser reset to top level. + * 2. available input represents prefix of a possibly-valid expression + * 2a. expr=nullptr; + * 2b. consumed reads all available input + * 2c. psz reflects nesting level after reading available input. + * 2d. error.is_not_an_error() + * 3. available input completes at least one expression + * 3a. expr contains first completed top-level expression + * 3b. consumed reports portion of input up to end of expr + * 3c. psz=0 + * 3d. error.is_not_an_error() + * + * expr :: rp if non-null: the next expression from input + * consumed :: span extent of input read up to next Expression + * psz :: size_t parser stack size + * error :: reader_error error details on parsing failure + **/ + auto [expr, consumed, psz, error] = rdr.read_expr(input, eof); + + if (expr) { + /** configuration for pretty-printing **/ + ppconfig ppc; + ppstate_standalone pps(&cout, 0, &ppc); + + pps.prettyn(expr); + } else if (error.is_error()) { + cout << "parsing error (detected in " << error.src_function() << "): " << endl << endl; + error.report(cout); + + /* discard stashed remainder of input line + * (for nicely-formatted errors) + */ + rdr.reset_to_idle_toplevel(); + break; + } + + input = input.after_prefix(consumed); + parser_stack_size = psz; + } + + /* here: input.empty() or error encountered */ + + } + + auto [expr, _1, _2, error] = rdr.read_expr(input, true /*eof*/); + + if (expr) { + ppconfig ppc; + ppstate_standalone pps(&cout, 0, &ppc); + + pps.prettyn>(rp(expr)); + } else if (error.is_error()) { + cout << "parsing error (detected in " << error.src_function() << "): " << endl; + error.report(cout); + } + + rx.history_save("repl_history.txt"); + } + + // ----- Schematika ----- + + Schematika::Schematika(const Config & cfg) : + p_impl_{new Impl(cfg)} + {} + + Schematika::~Schematika() + {} + + Schematika + Schematika::make(const Config & cfg) + { + return Schematika(cfg); + } + + void + Schematika::interactive_repl() + { + p_impl_->interactive_repl(); + } + } +} + +/* end Schematika.cpp */