From 1d40b5da55b8bf05d5332a7d82a744eb1856dda6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Nov 2025 09:38:32 -0500 Subject: [PATCH] xo-interpreter: repl example [wip] --- xo-interpreter/example/CMakeLists.txt | 1 + xo-interpreter/example/replxx/CMakeLists.txt | 17 +++ xo-interpreter/example/replxx/replxx.cpp | 140 ++++++++++++++++++ .../include/xo/interpreter/Schematika.hpp | 40 +++++ 4 files changed, 198 insertions(+) create mode 100644 xo-interpreter/example/CMakeLists.txt create mode 100644 xo-interpreter/example/replxx/CMakeLists.txt create mode 100644 xo-interpreter/example/replxx/replxx.cpp create mode 100644 xo-interpreter/include/xo/interpreter/Schematika.hpp diff --git a/xo-interpreter/example/CMakeLists.txt b/xo-interpreter/example/CMakeLists.txt new file mode 100644 index 00000000..ce7aaf20 --- /dev/null +++ b/xo-interpreter/example/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(replxx) diff --git a/xo-interpreter/example/replxx/CMakeLists.txt b/xo-interpreter/example/replxx/CMakeLists.txt new file mode 100644 index 00000000..96f11ae4 --- /dev/null +++ b/xo-interpreter/example/replxx/CMakeLists.txt @@ -0,0 +1,17 @@ +# xo-interpreter/example/replxx/CMakeLists.txt + +set(SELF_EXE xo_interpreter_replxx) +set(SELF_SRCS replxx.cpp) + +if (XO_ENABLE_EXAMPLES) + xo_add_executable(${SELF_EXE} ${SELF_SRCS}) + xo_self_dependency(${SELF_EXE} xo_interpreter) + # TODO: consider promoting to regular app + xo_dependency(${SELF_EXE} xo_reader) + xo_external_target_dependency(${SELF_EXE} replxx replxx::replxx) + + find_package(Threads REQUIRED) + target_link_libraries(${SELF_EXE} PUBLIC Threads::Threads) +endif() + +# end CMakeLists.txt diff --git a/xo-interpreter/example/replxx/replxx.cpp b/xo-interpreter/example/replxx/replxx.cpp new file mode 100644 index 00000000..ad735b1d --- /dev/null +++ b/xo-interpreter/example/replxx/replxx.cpp @@ -0,0 +1,140 @@ +/** @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; +} + +int +main() +{ + 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 span_type = xo::scm::span; + + 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"); +} + +/* end replxx.cpp */ diff --git a/xo-interpreter/include/xo/interpreter/Schematika.hpp b/xo-interpreter/include/xo/interpreter/Schematika.hpp new file mode 100644 index 00000000..7a19869b --- /dev/null +++ b/xo-interpreter/include/xo/interpreter/Schematika.hpp @@ -0,0 +1,40 @@ +/** @file Schematika.hpp + * + * @author Roland Conybeare, Nov 2025 + **/ + +#pramga once + + + +namespace xo { + namespace scm { + + /** @class Schematika + * @brief schematika interpreter state + **/ + class Schematika { + public: + /** interactive read-eval-print loop. + * Uses replxx to read from stdin. + * If stdin is interactive, accepts line editing commands: + * - ctrl-a goto beginning of line + * - ctrl-e goto end of line + * - ctrl-k delete to end of line + * - meta- backwards delete word + * - meta-p| retrieve previous command from history + * - meta-n| retrieve next command from history + * - / page through history faster + * - ctrl-s forward history search + * - ctrl-r backward history search + **/ + void interactive_repl(); + + private: + class Impl; + std::unique_ptr p_impl_; + }; + } +} + +/* end Schematika.hpp */