diff --git a/src/nasal_web.cpp b/src/nasal_web.cpp index 4925f8e..771e325 100644 --- a/src/nasal_web.cpp +++ b/src/nasal_web.cpp @@ -6,6 +6,7 @@ #include "optimizer.h" #include "nasal_err.h" #include "nasal_lexer.h" +#include "repl/repl.h" #include #include @@ -14,6 +15,30 @@ #include #include #include +#include + +namespace { + // Helper function implementations inside anonymous namespace + std::vector split_string(const std::string& str, char delim) { + std::vector result; + std::stringstream ss(str); + std::string item; + while (std::getline(ss, item, delim)) { + result.push_back(item); + } + return result; + } + + std::string join_string(const std::vector& vec, const std::string& delim) { + if (vec.empty()) return ""; + std::stringstream ss; + ss << vec[0]; + for (size_t i = 1; i < vec.size(); ++i) { + ss << delim << vec[i]; + } + return ss.str(); + } +} struct NasalContext { std::unique_ptr vm_instance; @@ -25,6 +50,19 @@ struct NasalContext { } }; +struct WebReplContext { + std::unique_ptr repl_instance; + std::vector source; + std::string last_result; + std::string last_error; + bool allow_output; + bool initialized; + + WebReplContext() : allow_output(false), initialized(false) { + repl_instance = std::make_unique(); + } +}; + void* nasal_init() { return new NasalContext(); } @@ -60,20 +98,19 @@ const char* nasal_eval(void* context, const char* code, int show_time) { } temp_file << code; temp_file.close(); - close(fd); // Close the file descriptor + close(fd); // Capture stdout and stderr std::stringstream output; std::stringstream error_output; - std::streambuf* old_cout = std::cout.rdbuf(output.rdbuf()); - std::streambuf* old_cerr = std::cerr.rdbuf(error_output.rdbuf()); + auto old_cout = std::cout.rdbuf(output.rdbuf()); + auto old_cerr = std::cerr.rdbuf(error_output.rdbuf()); - // Process the code by scanning the temporary file + // Process the code if (lex.scan(std::string(temp_filename)).geterr()) { ctx->last_error = error_output.str(); std::cout.rdbuf(old_cout); std::cerr.rdbuf(old_cerr); - // Remove the temporary file std::remove(temp_filename); return ctx->last_error.c_str(); } @@ -82,23 +119,19 @@ const char* nasal_eval(void* context, const char* code, int show_time) { ctx->last_error = error_output.str(); std::cout.rdbuf(old_cout); std::cerr.rdbuf(old_cerr); - // Remove the temporary file std::remove(temp_filename); return ctx->last_error.c_str(); } ld.link(parse, false).chkerr(); - // optimizer does simple optimization on ast auto opt = std::make_unique(); opt->do_optimization(parse.tree()); - gen.compile(parse, ld, false, true).chkerr(); // enable limit_mode for safety + gen.compile(parse, ld, false, true).chkerr(); - // Run the code with optional timing const auto start = show_time ? clk::now() : clk::time_point(); ctx->vm_instance->run(gen, ld, {}); const auto end = show_time ? clk::now() : clk::time_point(); - // Restore stdout and stderr and get the outputs std::cout.rdbuf(old_cout); std::cerr.rdbuf(old_cerr); @@ -111,15 +144,12 @@ const char* nasal_eval(void* context, const char* code, int show_time) { result << "Execution completed successfully.\n"; } - // Add execution time if requested if (show_time) { double execution_time = static_cast((end-start).count())/den; result << "\nExecution time: " << execution_time << "s"; } ctx->last_result = result.str(); - - // Remove the temporary file std::remove(temp_filename); return ctx->last_result.c_str(); @@ -133,3 +163,159 @@ const char* nasal_get_error(void* context) { auto* ctx = static_cast(context); return ctx->last_error.c_str(); } + +void* nasal_repl_init() { + auto* ctx = new WebReplContext(); + + try { + // Initialize environment silently + nasal::repl::info::instance()->in_repl_mode = true; + ctx->repl_instance->get_runtime().set_repl_mode_flag(true); + ctx->repl_instance->get_runtime().set_detail_report_info(false); + + // Run initial setup + ctx->repl_instance->set_source({}); + if (!ctx->repl_instance->run()) { + ctx->last_error = "Failed to initialize REPL environment"; + return ctx; + } + + // Enable output after initialization + ctx->allow_output = true; + ctx->repl_instance->get_runtime().set_allow_repl_output_flag(true); + ctx->initialized = true; + } catch (const std::exception& e) { + ctx->last_error = std::string("Initialization error: ") + e.what(); + } + + return ctx; +} + +void nasal_repl_cleanup(void* context) { + delete static_cast(context); +} + +const char* nasal_repl_eval(void* context, const char* line) { + auto* ctx = static_cast(context); + + if (!ctx->initialized) { + ctx->last_error = "REPL not properly initialized"; + return ctx->last_error.c_str(); + } + + try { + std::string input_line(line); + + // Handle empty input + if (input_line.empty()) { + ctx->last_result = ""; + return ctx->last_result.c_str(); + } + + // Handle REPL commands + if (input_line[0] == '.') { + if (input_line == ".help" || input_line == ".h") { + ctx->last_result = + "Nasal REPL commands:\n" + " .help .h show this help message\n" + " .clear .c clear screen\n" + " .exit .e exit repl\n" + " .quit .q exit repl\n" + " .source .s show source\n"; + return ctx->last_result.c_str(); + } + else if (input_line == ".clear" || input_line == ".c") { + ctx->last_result = "\033c"; // Special marker for clear screen + return ctx->last_result.c_str(); + } + else if (input_line == ".exit" || input_line == ".e" || + input_line == ".quit" || input_line == ".q") { + ctx->last_result = "__EXIT__"; // Special marker for exit + return ctx->last_result.c_str(); + } + else if (input_line == ".source" || input_line == ".s") { + // Return accumulated source + ctx->last_result = ctx->source.empty() ? + "(no source)" : + join_string(ctx->source, "\n"); + return ctx->last_result.c_str(); + } + else { + ctx->last_error = "no such command \"" + input_line + "\", input \".help\" for help"; + return ctx->last_error.c_str(); + } + } + + // Add the line to source + ctx->source.push_back(input_line); + + // Capture output + std::stringstream output; + auto old_cout = std::cout.rdbuf(output.rdbuf()); + auto old_cerr = std::cerr.rdbuf(output.rdbuf()); + + // Update source in repl instance and run + ctx->repl_instance->get_runtime().set_repl_mode_flag(true); + ctx->repl_instance->get_runtime().set_allow_repl_output_flag(true); + ctx->repl_instance->set_source(ctx->source); + bool success = ctx->repl_instance->run(); + + // Restore output streams + std::cout.rdbuf(old_cout); + std::cerr.rdbuf(old_cerr); + + // Get the output + std::string result = output.str(); + + if (!success) { + ctx->last_error = result; + ctx->source.pop_back(); // Remove failed line + return ctx->last_error.c_str(); + } + + // // Process output + // auto lines = split_string(result, '\n'); + // if (!lines.empty()) { + // // Remove empty lines from the end + // while (!lines.empty() && lines.back().empty()) { + // lines.pop_back(); + // } + // result = join_string(lines, "\n"); + // } + + ctx->last_result = result; + return ctx->last_result.c_str(); + + } catch (const std::exception& e) { + ctx->last_error = std::string("Error: ") + e.what(); + ctx->source.pop_back(); // Remove failed line + return ctx->last_error.c_str(); + } +} + +int nasal_repl_is_complete(void* context, const char* line) { + auto* ctx = static_cast(context); + std::string input_line(line); + + // Handle empty input or single semicolon + if (input_line.empty() || input_line == ";") { + return 1; // Input is complete + } + + // Add the new line to source + ctx->source.push_back(input_line); + + // Use existing REPL check_need_more_input functionality + bool needs_more = ctx->repl_instance->check_need_more_input(ctx->source); + + ctx->source.pop_back(); + return needs_more; +} + +// Add this function to expose version info +const char* nasal_repl_get_version() { + static std::string version_info = + std::string("version ") + __nasver__ + + " (" + __DATE__ + " " + __TIME__ + ")"; + return version_info.c_str(); +}