[web] Added REPL support to the library
This commit is contained in:
parent
8ecf309791
commit
6260cc1665
|
@ -6,6 +6,7 @@
|
||||||
#include "optimizer.h"
|
#include "optimizer.h"
|
||||||
#include "nasal_err.h"
|
#include "nasal_err.h"
|
||||||
#include "nasal_lexer.h"
|
#include "nasal_lexer.h"
|
||||||
|
#include "repl/repl.h"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
@ -14,6 +15,30 @@
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <chrono>
|
#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 {
|
struct NasalContext {
|
||||||
std::unique_ptr<nasal::vm> vm_instance;
|
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() {
|
void* nasal_init() {
|
||||||
return new NasalContext();
|
return new NasalContext();
|
||||||
}
|
}
|
||||||
|
@ -60,20 +98,19 @@ const char* nasal_eval(void* context, const char* code, int show_time) {
|
||||||
}
|
}
|
||||||
temp_file << code;
|
temp_file << code;
|
||||||
temp_file.close();
|
temp_file.close();
|
||||||
close(fd); // Close the file descriptor
|
close(fd);
|
||||||
|
|
||||||
// Capture stdout and stderr
|
// Capture stdout and stderr
|
||||||
std::stringstream output;
|
std::stringstream output;
|
||||||
std::stringstream error_output;
|
std::stringstream error_output;
|
||||||
std::streambuf* old_cout = std::cout.rdbuf(output.rdbuf());
|
auto old_cout = std::cout.rdbuf(output.rdbuf());
|
||||||
std::streambuf* old_cerr = std::cerr.rdbuf(error_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()) {
|
if (lex.scan(std::string(temp_filename)).geterr()) {
|
||||||
ctx->last_error = error_output.str();
|
ctx->last_error = error_output.str();
|
||||||
std::cout.rdbuf(old_cout);
|
std::cout.rdbuf(old_cout);
|
||||||
std::cerr.rdbuf(old_cerr);
|
std::cerr.rdbuf(old_cerr);
|
||||||
// Remove the temporary file
|
|
||||||
std::remove(temp_filename);
|
std::remove(temp_filename);
|
||||||
return ctx->last_error.c_str();
|
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();
|
ctx->last_error = error_output.str();
|
||||||
std::cout.rdbuf(old_cout);
|
std::cout.rdbuf(old_cout);
|
||||||
std::cerr.rdbuf(old_cerr);
|
std::cerr.rdbuf(old_cerr);
|
||||||
// Remove the temporary file
|
|
||||||
std::remove(temp_filename);
|
std::remove(temp_filename);
|
||||||
return ctx->last_error.c_str();
|
return ctx->last_error.c_str();
|
||||||
}
|
}
|
||||||
|
|
||||||
ld.link(parse, false).chkerr();
|
ld.link(parse, false).chkerr();
|
||||||
// optimizer does simple optimization on ast
|
|
||||||
auto opt = std::make_unique<nasal::optimizer>();
|
auto opt = std::make_unique<nasal::optimizer>();
|
||||||
opt->do_optimization(parse.tree());
|
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();
|
const auto start = show_time ? clk::now() : clk::time_point();
|
||||||
ctx->vm_instance->run(gen, ld, {});
|
ctx->vm_instance->run(gen, ld, {});
|
||||||
const auto end = show_time ? clk::now() : clk::time_point();
|
const auto end = show_time ? clk::now() : clk::time_point();
|
||||||
|
|
||||||
// Restore stdout and stderr and get the outputs
|
|
||||||
std::cout.rdbuf(old_cout);
|
std::cout.rdbuf(old_cout);
|
||||||
std::cerr.rdbuf(old_cerr);
|
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";
|
result << "Execution completed successfully.\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add execution time if requested
|
|
||||||
if (show_time) {
|
if (show_time) {
|
||||||
double execution_time = static_cast<double>((end-start).count())/den;
|
double execution_time = static_cast<double>((end-start).count())/den;
|
||||||
result << "\nExecution time: " << execution_time << "s";
|
result << "\nExecution time: " << execution_time << "s";
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx->last_result = result.str();
|
ctx->last_result = result.str();
|
||||||
|
|
||||||
// Remove the temporary file
|
|
||||||
std::remove(temp_filename);
|
std::remove(temp_filename);
|
||||||
|
|
||||||
return ctx->last_result.c_str();
|
return ctx->last_result.c_str();
|
||||||
|
@ -133,3 +163,159 @@ const char* nasal_get_error(void* context) {
|
||||||
auto* ctx = static_cast<NasalContext*>(context);
|
auto* ctx = static_cast<NasalContext*>(context);
|
||||||
return ctx->last_error.c_str();
|
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