[web] Added REPL support to the library
This commit is contained in:
parent
8ecf309791
commit
6260cc1665
|
@ -6,6 +6,7 @@
|
|||
#include "optimizer.h"
|
||||
#include "nasal_err.h"
|
||||
#include "nasal_lexer.h"
|
||||
#include "repl/repl.h"
|
||||
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
|
@ -14,6 +15,30 @@
|
|||
#include <cstdio>
|
||||
#include <stdexcept>
|
||||
#include <chrono>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
// Helper function implementations inside anonymous namespace
|
||||
std::vector<std::string> split_string(const std::string& str, char delim) {
|
||||
std::vector<std::string> 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<std::string>& 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<nasal::vm> vm_instance;
|
||||
|
@ -25,6 +50,19 @@ struct NasalContext {
|
|||
}
|
||||
};
|
||||
|
||||
struct WebReplContext {
|
||||
std::unique_ptr<nasal::repl::repl> repl_instance;
|
||||
std::vector<std::string> 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<nasal::repl::repl>();
|
||||
}
|
||||
};
|
||||
|
||||
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<nasal::optimizer>();
|
||||
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<double>((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<NasalContext*>(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<WebReplContext*>(context);
|
||||
}
|
||||
|
||||
const char* nasal_repl_eval(void* context, const char* line) {
|
||||
auto* ctx = static_cast<WebReplContext*>(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<WebReplContext*>(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();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue