diff --git a/default.nix b/default.nix index e801f99f..37d7cc10 100644 --- a/default.nix +++ b/default.nix @@ -170,14 +170,18 @@ pkgs.mkShell { pkgs.graphviz pkgs.doxygen + pkgs.llvmPackages_18.llvm.dev pkgs.libwebsockets + pkgs.replxx pkgs.jsoncpp pkgs.eigen - pkgs.cmake pkgs.catch2 pkgs.zlib pkgs.unzip + + pkgs.cmake + pkgs.pkg-config ]; shellHook = '' diff --git a/xo-reader/examples/CMakeLists.txt b/xo-reader/examples/CMakeLists.txt index 220b7a89..faab007d 100644 --- a/xo-reader/examples/CMakeLists.txt +++ b/xo-reader/examples/CMakeLists.txt @@ -1 +1,2 @@ add_subdirectory(exprrepl) +add_subdirectory(exprreplxx) diff --git a/xo-reader/examples/exprrepl/expreplxx.cpp b/xo-reader/examples/exprrepl/expreplxx.cpp new file mode 100644 index 00000000..e69de29b diff --git a/xo-reader/examples/exprrepl/exprrepl.cpp b/xo-reader/examples/exprrepl/exprrepl.cpp index dbc9f587..42bf4baa 100644 --- a/xo-reader/examples/exprrepl/exprrepl.cpp +++ b/xo-reader/examples/exprrepl/exprrepl.cpp @@ -4,18 +4,35 @@ #include #include // for isatty -bool repl_getline(bool interactive, std::istream& in, std::ostream& out, std::string& input) +bool repl_getline(bool interactive, std::size_t parser_stack_size, std::istream& in, std::ostream& out, std::string& input) { + using namespace std; + if (interactive) { - out << "> "; + if (parser_stack_size <= 1) + out << "> "; + else + out << ". "; std::flush(out); } - bool retval = static_cast(std::getline(in, input)); + bool retval = static_cast(getline(in, input)); if (retval) { + cerr << "got reval->true" << endl; + + // interactive only: treat ^@ (C-RET) as ;RET + if ((input.size() > 0) && ((*input.rbegin()) == '\0')) + { + cerr << "got ^@" << endl; + + *input.rbegin() = ';'; + } + // want reader to see newline, it's syntax input.push_back('\n'); + } else { + cerr << "got retval->false" << endl; } return retval; @@ -38,22 +55,24 @@ main() { bool eof = false; span_type input; + std::size_t parser_stack_size = 0; - while (repl_getline(interactive, cin, cout, input_str)) { + while (repl_getline(interactive, parser_stack_size, cin, cout, input_str)) { input = span_type::from_string(input_str); while (!input.empty()) { - auto [expr, consumed] = rdr.read_expr(input, eof); + auto [expr, consumed, psz] = rdr.read_expr(input, eof); if (expr) { cout << expr << endl; } input = input.after_prefix(consumed); + parser_stack_size = psz; } } - auto [expr, _] = rdr.read_expr(input, true /*eof*/); + auto [expr, _1, _2] = rdr.read_expr(input, true /*eof*/); if (expr) { cout << expr << endl; diff --git a/xo-reader/examples/exprreplxx/CMakeLists.txt b/xo-reader/examples/exprreplxx/CMakeLists.txt new file mode 100644 index 00000000..da33e668 --- /dev/null +++ b/xo-reader/examples/exprreplxx/CMakeLists.txt @@ -0,0 +1,16 @@ +# xo-reader/example/exprreplxx/CMakeLists.txt + +set(SELF_EXE xo_expression_replxx) +set(SELF_SRCS exprreplxx.cpp) + +if (XO_ENABLE_EXAMPLES) + xo_add_executable(${SELF_EXE} ${SELF_SRCS}) + 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) + #xo_external_target_dependency(${SELF_EXE} Threads Threads::Threads) +endif() + +# end CMakeLists.txt diff --git a/xo-reader/examples/exprreplxx/exprreplxx.cpp b/xo-reader/examples/exprreplxx/exprreplxx.cpp new file mode 100644 index 00000000..574f3f4b --- /dev/null +++ b/xo-reader/examples/exprreplxx/exprreplxx.cpp @@ -0,0 +1,108 @@ +/** @file exprreplxx.cpp **/ + +#include "xo/reader/reader.hpp" +#include +#include +#include // for isatty + +// presumeably replxx assumes input is a tty +// +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 = ". "; + } + + const char * input_cstr = rx.input(prompt); + + bool retval = (input_cstr != nullptr); + + if (retval) { + //cerr << "got reval->true" << endl; + + input = input_cstr; + + } else { + //cerr << "got retval->false" << endl; + } + + rx.history_add(input); + + 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 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"); + + reader rdr; + 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] = rdr.read_expr(input, eof); + + if (expr) { + cout << expr << endl; + } + + input = input.after_prefix(consumed); + parser_stack_size = psz; + } + } + + auto [expr, _1, _2] = rdr.read_expr(input, true /*eof*/); + + if (expr) { + cout << expr << endl; + } + + rx.history_save("repl_history.txt"); +} diff --git a/xo-reader/include/xo/reader/parser.hpp b/xo-reader/include/xo/reader/parser.hpp index 850a5513..4e89e886 100644 --- a/xo-reader/include/xo/reader/parser.hpp +++ b/xo-reader/include/xo/reader/parser.hpp @@ -156,6 +156,9 @@ namespace xo { **/ parser(); + /** true if parser is at top-level, i.e. ready for next top-level expression **/ + bool is_at_toplevel() const { return stack_size() == 0; } + /** for diagnostics: number of entries in parser stack **/ std::size_t stack_size() const { return xs_stack_.size(); } /** for diagnostics: exprstatetype at level @p i diff --git a/xo-reader/include/xo/reader/reader.hpp b/xo-reader/include/xo/reader/reader.hpp index 59e600c1..b155f4db 100644 --- a/xo-reader/include/xo/reader/reader.hpp +++ b/xo-reader/include/xo/reader/reader.hpp @@ -18,8 +18,11 @@ namespace xo { using Expression = xo::ast::Expression; using span_type = span; - reader_result(rp expr, span_type rem) - : expr_{std::move(expr)}, rem_{rem} {} + reader_result(rp expr, span_type rem, std::size_t psz) + : expr_{std::move(expr)}, rem_{rem}, parser_stack_size_{psz} {} + + /** true if reader parsed a complete expression **/ + bool expr_complete() const { return expr_.get(); } /** parsed schematica expression **/ rp expr_; @@ -28,6 +31,11 @@ namespace xo { * This is the span returned in result of tokenizer::scan() **/ span_type rem_; + + /** parser nesting level when this result delivered + * will be zero whenever @ref expr_ is non-null + **/ + std::size_t parser_stack_size_ = 0; }; /** diff --git a/xo-reader/src/reader/reader.cpp b/xo-reader/src/reader/reader.cpp index bc9de1ce..5a51c6fa 100644 --- a/xo-reader/src/reader/reader.cpp +++ b/xo-reader/src/reader/reader.cpp @@ -63,7 +63,7 @@ namespace xo { xtag("expr", expr)); /* token completes an expression -> victory */ - return reader_result(expr, expr_span); + return reader_result(expr, expr_span, parser_.stack_size()); } else { /* token did not complete an expression * (e.g. token for '[') @@ -99,7 +99,7 @@ namespace xo { log && log(xtag("outcome", "noop")); - return reader_result(nullptr, expr_span); + return reader_result(nullptr, expr_span, parser_.stack_size()); } } /*namespace scm*/