378 lines
12 KiB
C++
378 lines
12 KiB
C++
#include "nasal_web.h"
|
|
#include "nasal_vm.h"
|
|
#include "nasal_parse.h"
|
|
#include "nasal_codegen.h"
|
|
#include "nasal_import.h"
|
|
#include "optimizer.h"
|
|
#include "nasal_err.h"
|
|
#include "nasal_lexer.h"
|
|
#include "repl/repl.h"
|
|
|
|
#include <string>
|
|
#include <sstream>
|
|
#include <fstream>
|
|
#include <cstdlib>
|
|
#include <cstdio>
|
|
#include <stdexcept>
|
|
#include <chrono>
|
|
#include <vector>
|
|
#include <future>
|
|
#include <atomic>
|
|
|
|
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;
|
|
std::string last_result;
|
|
std::string last_error;
|
|
std::chrono::seconds timeout{5}; // Default 5 second timeout
|
|
std::atomic<bool> interrupted{false};
|
|
|
|
NasalContext() {
|
|
vm_instance = std::make_unique<nasal::vm>();
|
|
vm_instance->set_interrupt_ptr(&interrupted);
|
|
}
|
|
|
|
~NasalContext() {
|
|
vm_instance.reset(); // Reset explicitly
|
|
}
|
|
};
|
|
|
|
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;
|
|
std::chrono::seconds timeout{1}; // Default 1 second timeout
|
|
|
|
WebReplContext() : allow_output(false), initialized(false) {
|
|
repl_instance = std::make_unique<nasal::repl::repl>();
|
|
}
|
|
};
|
|
|
|
void* nasal_init() {
|
|
return new NasalContext();
|
|
}
|
|
|
|
void nasal_cleanup(void* context) {
|
|
auto* ctx = static_cast<NasalContext*>(context);
|
|
ctx->vm_instance.reset();
|
|
delete ctx;
|
|
}
|
|
|
|
// Add new function to set timeout
|
|
void nasal_set_timeout(void* context, int seconds) {
|
|
auto* ctx = static_cast<NasalContext*>(context);
|
|
ctx->timeout = std::chrono::seconds(seconds);
|
|
}
|
|
|
|
const char* nasal_eval(void* context, const char* code, int show_time) {
|
|
using clk = std::chrono::high_resolution_clock;
|
|
const auto den = clk::duration::period::den;
|
|
|
|
auto* ctx = static_cast<NasalContext*>(context);
|
|
|
|
try {
|
|
nasal::lexer lex;
|
|
nasal::parse parse;
|
|
nasal::linker ld;
|
|
nasal::codegen gen;
|
|
|
|
// Create a unique temporary file
|
|
char temp_filename[256];
|
|
snprintf(temp_filename, sizeof(temp_filename), "/tmp/nasal_eval_%ld_XXXXXX", std::time(nullptr));
|
|
int fd = mkstemp(temp_filename);
|
|
if (fd == -1) {
|
|
throw std::runtime_error("Failed to create temporary file");
|
|
}
|
|
|
|
// Write the code to the temporary file
|
|
std::ofstream temp_file(temp_filename);
|
|
if (!temp_file.is_open()) {
|
|
close(fd);
|
|
throw std::runtime_error("Failed to open temporary file for writing");
|
|
}
|
|
temp_file << code;
|
|
temp_file.close();
|
|
close(fd);
|
|
|
|
// Capture stdout and stderr
|
|
std::stringstream output;
|
|
std::stringstream error_output;
|
|
auto old_cout = std::cout.rdbuf(output.rdbuf());
|
|
auto old_cerr = std::cerr.rdbuf(error_output.rdbuf());
|
|
|
|
// 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);
|
|
std::remove(temp_filename);
|
|
return ctx->last_error.c_str();
|
|
}
|
|
|
|
if (parse.compile(lex).geterr()) {
|
|
ctx->last_error = error_output.str();
|
|
std::cout.rdbuf(old_cout);
|
|
std::cerr.rdbuf(old_cerr);
|
|
std::remove(temp_filename);
|
|
return ctx->last_error.c_str();
|
|
}
|
|
|
|
ld.link(parse, false).chkerr();
|
|
auto opt = std::make_unique<nasal::optimizer>();
|
|
opt->do_optimization(parse.tree());
|
|
gen.compile(parse, ld, false, true).chkerr();
|
|
|
|
const auto start = show_time ? clk::now() : clk::time_point();
|
|
|
|
// Create a future for the VM execution
|
|
auto future = std::async(std::launch::async, [&]() {
|
|
ctx->vm_instance->run(gen, ld, {});
|
|
});
|
|
|
|
// Wait for completion or timeout
|
|
auto status = future.wait_for(ctx->timeout);
|
|
if (status == std::future_status::timeout) {
|
|
ctx->interrupted.store(true);
|
|
std::remove(temp_filename);
|
|
throw std::runtime_error("Execution timed out after " +
|
|
std::to_string(ctx->timeout.count()) + " seconds");
|
|
}
|
|
|
|
const auto end = show_time ? clk::now() : clk::time_point();
|
|
|
|
std::cout.rdbuf(old_cout);
|
|
std::cerr.rdbuf(old_cerr);
|
|
|
|
std::stringstream result;
|
|
result << output.str();
|
|
if (!error_output.str().empty()) {
|
|
result << error_output.str();
|
|
}
|
|
if (result.str().empty()) {
|
|
result << "Execution completed successfully.\n";
|
|
}
|
|
|
|
if (show_time) {
|
|
double execution_time = static_cast<double>((end-start).count())/den;
|
|
result << "\nExecution time: " << execution_time << "s";
|
|
}
|
|
|
|
ctx->last_result = result.str();
|
|
std::remove(temp_filename);
|
|
|
|
return ctx->last_result.c_str();
|
|
} catch (const std::exception& e) {
|
|
ctx->last_error = e.what();
|
|
return ctx->last_error.c_str();
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
// Add new function to set REPL timeout
|
|
void nasal_repl_set_timeout(void* context, int seconds) {
|
|
auto* ctx = static_cast<WebReplContext*>(context);
|
|
ctx->timeout = std::chrono::seconds(seconds);
|
|
}
|
|
|
|
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());
|
|
|
|
// Create a copy of the source for the async task
|
|
auto source_copy = ctx->source;
|
|
|
|
// Create a future for the REPL execution using the existing instance
|
|
auto future = std::async(std::launch::async, [repl = ctx->repl_instance.get(), source_copy]() {
|
|
repl->get_runtime().set_repl_mode_flag(true);
|
|
repl->get_runtime().set_allow_repl_output_flag(true);
|
|
repl->set_source(source_copy);
|
|
return repl->run();
|
|
});
|
|
|
|
// Wait for completion or timeout
|
|
auto status = future.wait_for(ctx->timeout);
|
|
|
|
// Restore output streams first
|
|
std::cout.rdbuf(old_cout);
|
|
std::cerr.rdbuf(old_cerr);
|
|
|
|
if (status == std::future_status::timeout) {
|
|
ctx->source.pop_back(); // Remove the line that caused timeout
|
|
|
|
// Reset the REPL instance state
|
|
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);
|
|
|
|
throw std::runtime_error("Execution timed out after " +
|
|
std::to_string(ctx->timeout.count()) + " seconds");
|
|
}
|
|
|
|
bool success = future.get();
|
|
std::string result = output.str();
|
|
|
|
if (!success) {
|
|
ctx->last_error = result;
|
|
ctx->source.pop_back(); // Remove failed line
|
|
return ctx->last_error.c_str();
|
|
}
|
|
|
|
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);
|
|
|
|
if (!ctx->initialized) {
|
|
return -1; // Error state
|
|
}
|
|
|
|
// Handle empty input
|
|
if (!line || strlen(line) == 0) {
|
|
return 0; // Complete
|
|
}
|
|
|
|
// Handle REPL commands
|
|
if (line[0] == '.') {
|
|
return 0; // Commands are always complete
|
|
}
|
|
|
|
// Create a temporary source vector with existing source plus new line
|
|
std::vector<std::string> temp_source = ctx->source;
|
|
temp_source.push_back(line);
|
|
|
|
// Use the REPL's check_need_more_input method
|
|
int result = ctx->repl_instance->check_need_more_input(temp_source);
|
|
return result; // Ensure a return value is provided
|
|
}
|
|
|
|
// 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();
|
|
}
|