diff --git a/CMakeLists.txt b/CMakeLists.txt index 6fcf5f8..d8fcb0c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,7 +34,7 @@ if(NOT CMAKE_C_COMPILER AND NOT CMAKE_CXX_COMPILER) endif() project(zelph VERSION 0.9.6 LANGUAGES C CXX) -set(ZELPH_VERSION_SUFFIX "-dev") +set(ZELPH_VERSION_SUFFIX "-dev" CACHE STRING "Version suffix appended to the reported project version") include(GNUInstallDirs) diff --git a/src/lib/command_executor.cpp b/src/lib/command_executor.cpp index 2b569b3..38dc22a 100644 --- a/src/lib/command_executor.cpp +++ b/src/lib/command_executor.cpp @@ -48,6 +48,7 @@ along with zelph. If not, see . #include #include #include +#include #include #include @@ -416,6 +417,55 @@ class console::CommandExecutor::Impl throw std::runtime_error("Command .index-file: Failed to open output file '" + output_filename + "'"); } + auto escape_json_string = [](const std::string& value) + { + std::ostringstream escaped; + for (unsigned char ch : value) + { + switch (ch) + { + case '\"': + escaped << "\\\""; + break; + case '\\': + escaped << "\\\\"; + break; + case '\b': + escaped << "\\b"; + break; + case '\f': + escaped << "\\f"; + break; + case '\n': + escaped << "\\n"; + break; + case '\r': + escaped << "\\r"; + break; + case '\t': + escaped << "\\t"; + break; + default: + if (ch < 0x20) + { + escaped << "\\u" + << std::hex + << std::setw(4) + << std::setfill('0') + << static_cast(ch) + << std::dec + << std::setfill(' '); + } + else + { + escaped << static_cast(ch); + } + break; + } + } + return escaped.str(); + }; + auto write_chunk_array = [&](const char* key, const std::vector& refs) { out << " \"" << key << "\": [\n"; @@ -427,11 +477,11 @@ class console::CommandExecutor::Impl << ",\"length\":" << ref.length; if (!ref.which.empty()) { - out << ",\"which\":\"" << ref.which << "\""; + out << ",\"which\":\"" << escape_json_string(ref.which) << "\""; } if (!ref.lang.empty()) { - out << ",\"lang\":\"" << ref.lang << "\""; + out << ",\"lang\":\"" << escape_json_string(ref.lang) << "\""; } out << "}"; if (i + 1 < refs.size()) @@ -444,7 +494,7 @@ class console::CommandExecutor::Impl }; out << "{\n"; - out << " \"file\":\"" << data.filename << "\",\n"; + out << " \"file\":\"" << escape_json_string(data.filename) << "\",\n"; out << " \"header\":{\"offset\":0,\"length\":" << data.header_length_bytes << "},\n"; write_chunk_array("left", data.left_chunks); out << ",\n"; @@ -490,11 +540,20 @@ class console::CommandExecutor::Impl try { size_t pos = 0; - uint32_t index = static_cast(std::stoul(token, &pos, 10)); + if (token[0] == '-') + { + throw std::runtime_error(""); + } + uint64_t parsed = std::stoull(token, &pos, 10); if (pos != token.size()) { throw std::runtime_error(""); } + if (parsed > std::numeric_limits::max()) + { + throw std::runtime_error(""); + } + uint32_t index = static_cast(parsed); indices.push_back(index); } catch (...) @@ -540,6 +599,10 @@ class console::CommandExecutor::Impl try { size_t pos = 0; + if (token[0] == '-') + { + throw std::runtime_error(""); + } uint64_t id = std::stoull(token, &pos, 10); if (pos != token.size()) { @@ -1846,6 +1909,11 @@ class console::CommandExecutor::Impl } } + if (use_manifest && source_bin_override.empty() && first_arg.ends_with(".bin")) + { + source_bin_override = first_arg; + } + if ((selection.route_nodes_explicit || selection.route_name_explicit) && !use_manifest) { throw std::runtime_error("Command .load-partial: route selectors require manifest mode"); diff --git a/src/lib/command_executor.hpp b/src/lib/command_executor.hpp index 0449a06..ef055a3 100644 --- a/src/lib/command_executor.hpp +++ b/src/lib/command_executor.hpp @@ -64,11 +64,11 @@ namespace zelph::console * * @param reasoning Pointer to the reasoning network (must remain valid). * @param script_engine Pointer to the script engine (must remain valid). - * @param data_manager Reference to the shared_ptr holding the data manager (allows replacement). + * @param repl_state Shared REPL state used for mode flags and buffers. * @param line_processor Callback to process a raw line (used for .import recursion). */ CommandExecutor(zelph::network::Reasoning* reasoning, - zelph::ScriptEngine* script_engine, + zelph::ScriptEngine* script_engine, std::shared_ptr repl_state, LineProcessor line_processor); diff --git a/src/lib/interactive.cpp b/src/lib/interactive.cpp index 0b863ee..a6424c0 100644 --- a/src/lib/interactive.cpp +++ b/src/lib/interactive.cpp @@ -74,16 +74,25 @@ class console::Interactive::Impl { _interactive->process(line); }); } - void reset_reasoning() - { - _command_executor.reset(); // destroy first (depends on both) - _script_engine.reset(); // destroy second (depends on _n) - auto output = _n->get_output_handler(); // save what's needed - _n.reset(); // destroy last - - init(output); - _n->out("Cleared network and re-initialized core nodes."); - } + void reset_reasoning() + { + _command_executor.reset(); // destroy first (depends on both) + _script_engine.reset(); // destroy second (depends on _n) + auto output = _n->get_output_handler(); // save what's needed + _n.reset(); // destroy last + + _repl_state->partial_load_mode = false; + _repl_state->partial_load_source.clear(); + _repl_state->janet_buffer.clear(); + _repl_state->zelph_buffer.clear(); + _repl_state->accumulating_inline_janet = false; + _repl_state->accumulating_zelph = false; + _repl_state->script_mode = ScriptMode::Zelph; + _repl_state->reset_requested = false; + + init(output); + _n->out("Cleared network and re-initialized core nodes."); + } // Member function to delegate to CommandExecutor void process_command(const std::vector& cmd); diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index d8dc819..f11eb4f 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -23,7 +23,7 @@ include(FetchContent) -# doctest v2.4.11 declares cmake_minimum_required(VERSION 3.1), +# doctest v2.4.12 declares cmake_minimum_required(VERSION 3.1), # which newer CMake rejects. Allow it to configure anyway. set(CMAKE_POLICY_VERSION_MINIMUM 3.5 CACHE STRING "")