aboutsummaryrefslogtreecommitdiff
path: root/src/eval.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/eval.c')
-rw-r--r--src/eval.c482
1 files changed, 482 insertions, 0 deletions
diff --git a/src/eval.c b/src/eval.c
new file mode 100644
index 0000000..316d6d8
--- /dev/null
+++ b/src/eval.c
@@ -0,0 +1,482 @@
+#define _POSIX_C_SOURCE 200809L
+
+#include "eval.h"
+
+#include "buffers.h"
+#include "ccdjit.h"
+#include "common.h"
+#include "config.h"
+#include "ecex.h"
+#include "util.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static ecex_t *g_eval_editor = NULL;
+
+static buffer_t *eval_output_buffer(ecex_t *ed) {
+ if (!ed) return NULL;
+
+ buffer_t *out = ecex_create_interactive_buffer(ed, "*eval-output*");
+ if (out) {
+ ecex_buffer_set_major_mode_by_name(ed, out, "eval-output-mode");
+ }
+
+ return out;
+}
+
+static void eval_append_diag(buffer_t *out, ccdjit_context *ctx) {
+ if (!out || !ctx) return;
+
+ const ccdjit_diagnostic *diag = ccdjit_context_last_error(ctx);
+ if (!diag) {
+ buffer_append(out, "ccdjit: unknown error\n");
+ return;
+ }
+
+ char line[1024];
+ snprintf(line,
+ sizeof(line),
+ "ccdjit error [%s] %s:%d:%d: %s\n",
+ diag->phase ? diag->phase : "?",
+ diag->filename ? diag->filename : "<input>",
+ diag->line,
+ diag->column,
+ diag->message ? diag->message : "(no message)");
+ buffer_append(out, line);
+
+ if (diag->source_line) {
+ buffer_append(out, diag->source_line);
+ buffer_append(out, "\n");
+ }
+
+ if (diag->caret_line) {
+ buffer_append(out, diag->caret_line);
+ buffer_append(out, "\n");
+ }
+}
+
+typedef struct eval_capture {
+ FILE *file;
+ int saved_stdout;
+ int saved_stderr;
+ int active;
+} eval_capture_t;
+
+static void eval_capture_init(eval_capture_t *capture) {
+ if (!capture) return;
+
+ capture->file = NULL;
+ capture->saved_stdout = -1;
+ capture->saved_stderr = -1;
+ capture->active = 0;
+}
+
+static int eval_capture_begin(eval_capture_t *capture, buffer_t *out) {
+ if (!capture) return ECEX_ERR;
+
+ eval_capture_init(capture);
+
+ capture->file = tmpfile();
+ if (!capture->file) {
+ if (out) buffer_append(out, "warning: failed to create stdout capture file\n");
+ return ECEX_ERR;
+ }
+
+ int capture_fd = fileno(capture->file);
+ if (capture_fd < 0) {
+ if (out) buffer_append(out, "warning: failed to get stdout capture fd\n");
+ fclose(capture->file);
+ eval_capture_init(capture);
+ return ECEX_ERR;
+ }
+
+ fflush(NULL);
+
+ capture->saved_stdout = dup(STDOUT_FILENO);
+ capture->saved_stderr = dup(STDERR_FILENO);
+
+ if (capture->saved_stdout < 0 || capture->saved_stderr < 0) {
+ if (out) buffer_append(out, "warning: failed to save stdout/stderr\n");
+
+ if (capture->saved_stdout >= 0) close(capture->saved_stdout);
+ if (capture->saved_stderr >= 0) close(capture->saved_stderr);
+ fclose(capture->file);
+ eval_capture_init(capture);
+ return ECEX_ERR;
+ }
+
+ if (dup2(capture_fd, STDOUT_FILENO) < 0 ||
+ dup2(capture_fd, STDERR_FILENO) < 0) {
+ if (out) buffer_append(out, "warning: failed to redirect stdout/stderr\n");
+
+ dup2(capture->saved_stdout, STDOUT_FILENO);
+ dup2(capture->saved_stderr, STDERR_FILENO);
+ close(capture->saved_stdout);
+ close(capture->saved_stderr);
+ fclose(capture->file);
+ eval_capture_init(capture);
+ return ECEX_ERR;
+ }
+
+ capture->active = 1;
+ return ECEX_OK;
+}
+
+static void eval_capture_restore(eval_capture_t *capture) {
+ if (!capture || !capture->active) return;
+
+ fflush(NULL);
+
+ if (capture->saved_stdout >= 0) {
+ dup2(capture->saved_stdout, STDOUT_FILENO);
+ close(capture->saved_stdout);
+ capture->saved_stdout = -1;
+ }
+
+ if (capture->saved_stderr >= 0) {
+ dup2(capture->saved_stderr, STDERR_FILENO);
+ close(capture->saved_stderr);
+ capture->saved_stderr = -1;
+ }
+
+ capture->active = 0;
+}
+
+static void eval_capture_append_output(eval_capture_t *capture, buffer_t *out) {
+ if (!capture || !capture->file || !out) return;
+
+ if (fseek(capture->file, 0, SEEK_END) != 0) {
+ return;
+ }
+
+ long size = ftell(capture->file);
+ if (size <= 0) {
+ return;
+ }
+
+ if (fseek(capture->file, 0, SEEK_SET) != 0) {
+ return;
+ }
+
+ buffer_append(out, "stdout/stderr:\n");
+
+ char chunk[4096];
+ size_t n = 0;
+
+ while ((n = fread(chunk, 1, sizeof(chunk), capture->file)) > 0) {
+ size_t old_len = out->len;
+
+ if (buffer_reserve(out, out->len + n + 1) != ECEX_OK) {
+ buffer_append(out, "\n[output truncated: allocation failed]\n");
+ return;
+ }
+
+ memcpy(out->data + old_len, chunk, n);
+ out->len += n;
+ out->data[out->len] = '\0';
+ out->point = out->len;
+ }
+
+ if (out->len > 0 && out->data[out->len - 1] != '\n') {
+ buffer_append(out, "\n");
+ }
+
+ buffer_append(out, "\n");
+}
+
+static void eval_capture_finish(eval_capture_t *capture, buffer_t *out) {
+ if (!capture) return;
+
+ eval_capture_restore(capture);
+ eval_capture_append_output(capture, out);
+
+ if (capture->file) {
+ fclose(capture->file);
+ }
+
+ eval_capture_init(capture);
+}
+
+static int source_has_main(const char *source) {
+ if (!source) return 0;
+
+ return strstr(source, "main(") != NULL ||
+ strstr(source, "main (") != NULL;
+}
+
+static char *make_eval_source(const char *source, int wrap_as_statements) {
+ if (!source) return NULL;
+
+ const char *prefix =
+ "#include \"ecex.h\"\n"
+ "#include \"buffers.h\"\n"
+ "extern ecex_t *__ecex_eval_editor;\n"
+ "#define ECEX_ED (__ecex_eval_editor)\n"
+ "\n";
+
+ const char *wrapper_open =
+ "int main(int argc, char **argv) {\n"
+ " (void)argc;\n"
+ " (void)argv;\n"
+ " ecex_t *ed = __ecex_eval_editor;\n"
+ " (void)ed;\n";
+
+ const char *wrapper_close =
+ "\n"
+ " return 0;\n"
+ "}\n";
+
+ int do_wrap = wrap_as_statements || !source_has_main(source);
+
+ size_t prefix_len = strlen(prefix);
+ size_t source_len = strlen(source);
+ size_t open_len = do_wrap ? strlen(wrapper_open) : 0;
+ size_t close_len = do_wrap ? strlen(wrapper_close) : 0;
+
+ char *combined = malloc(prefix_len + open_len + source_len + close_len + 2);
+ if (!combined) return NULL;
+
+ char *p = combined;
+ memcpy(p, prefix, prefix_len);
+ p += prefix_len;
+
+ if (do_wrap) {
+ memcpy(p, wrapper_open, open_len);
+ p += open_len;
+ }
+
+ memcpy(p, source, source_len);
+ p += source_len;
+
+ if (do_wrap) {
+ if (source_len == 0 || source[source_len - 1] != '\n') {
+ *p++ = '\n';
+ }
+ memcpy(p, wrapper_close, close_len);
+ p += close_len;
+ }
+
+ *p = '\0';
+ return combined;
+}
+
+static int remember_last_eval(ecex_t *ed,
+ const char *source,
+ const char *filename,
+ int wrap_as_statements) {
+ if (!ed || !source) return ECEX_ERR;
+
+ char *source_copy = ecex_strdup(source);
+ char *filename_copy = ecex_strdup(filename ? filename : "<eval>");
+ if (!source_copy || !filename_copy) {
+ free(source_copy);
+ free(filename_copy);
+ return ECEX_ERR;
+ }
+
+ free(ed->last_eval_source);
+ free(ed->last_eval_filename);
+ ed->last_eval_source = source_copy;
+ ed->last_eval_filename = filename_copy;
+ ed->last_eval_wrap_as_statements = wrap_as_statements;
+ return ECEX_OK;
+}
+
+int ecex_eval_source(ecex_t *ed,
+ const char *source,
+ const char *filename,
+ int wrap_as_statements) {
+ if (!ed || !source) return ECEX_ERR;
+
+ remember_last_eval(ed, source, filename, wrap_as_statements);
+
+ buffer_t *out = eval_output_buffer(ed);
+ if (!out) return ECEX_ERR;
+
+ buffer_clear(out);
+ buffer_set_interactive(out, 1);
+ ecex_buffer_set_major_mode_by_name(ed, out, "eval-output-mode");
+ buffer_append(out, "Eval output (g: re-eval, q: quit window, ENTER: follow line if available)\n");
+ buffer_append(out, "────────────────────────────────────────────────────────────────\n\n");
+ buffer_append(out, "Eval: ");
+ buffer_append(out, filename ? filename : "<eval>");
+ buffer_append(out, "\n\n");
+
+ char *eval_source = make_eval_source(source, wrap_as_statements);
+ if (!eval_source) {
+ buffer_append(out, "failed to allocate eval source\n");
+ ecex_switch_buffer(ed, "*eval-output*");
+ return ECEX_ERR;
+ }
+
+ ccdjit_context *ctx = ccdjit_context_new(NULL);
+ if (!ctx) {
+ buffer_append(out, "failed to create ccdjit context\n");
+ free(eval_source);
+ ecex_switch_buffer(ed, "*eval-output*");
+ return ECEX_ERR;
+ }
+
+ if (ecex_add_ccdjit_include_paths(ctx) != ECEX_OK ||
+ ecex_register_host_symbols(ctx) != ECEX_OK ||
+ ccdjit_context_register_symbol(ctx,
+ "__ecex_eval_editor",
+ (void *)&g_eval_editor) != 0) {
+ buffer_append(out, "failed to prepare eval context\n");
+ eval_append_diag(out, ctx);
+ ccdjit_context_free(ctx);
+ free(eval_source);
+ ecex_switch_buffer(ed, "*eval-output*");
+ return ECEX_ERR;
+ }
+
+ g_eval_editor = ed;
+
+ ccdjit_module *module = NULL;
+ if (ccdjit_compile_string(ctx,
+ eval_source,
+ filename ? filename : "<eval>",
+ &module) != 0) {
+ buffer_append(out, "compile failed\n\n");
+ eval_append_diag(out, ctx);
+ g_eval_editor = NULL;
+ ccdjit_context_free(ctx);
+ free(eval_source);
+ ecex_switch_buffer(ed, "*eval-output*");
+ return ECEX_ERR;
+ }
+
+ int result = 0;
+ eval_capture_t capture;
+ int capture_enabled = (eval_capture_begin(&capture, out) == ECEX_OK);
+ int runtime_status = ccdjit_module_call_main(module, 0, NULL, &result);
+
+ if (capture_enabled) {
+ eval_capture_finish(&capture, out);
+ }
+
+ if (runtime_status != 0) {
+ buffer_append(out, "runtime failed\n\n");
+ eval_append_diag(out, ctx);
+ ccdjit_module_free(module);
+ g_eval_editor = NULL;
+ ccdjit_context_free(ctx);
+ free(eval_source);
+ ecex_switch_buffer(ed, "*eval-output*");
+ return ECEX_ERR;
+ }
+
+ char line[128];
+ snprintf(line, sizeof(line), "ok, result = %d\n", result);
+ buffer_append(out, line);
+
+ /*
+ * Keep successful eval modules alive. This makes eval useful for live
+ * customization: code evaluated from a buffer may register commands whose
+ * function pointers remain valid after eval returns.
+ */
+ if (ecex_keep_jit_module(ed, module) != ECEX_OK) {
+ buffer_append(out, "failed to keep eval module alive\n");
+ ccdjit_module_free(module);
+ g_eval_editor = NULL;
+ ccdjit_context_free(ctx);
+ free(eval_source);
+ ecex_switch_buffer(ed, "*eval-output*");
+ return ECEX_ERR;
+ }
+
+ g_eval_editor = NULL;
+ ccdjit_context_free(ctx);
+ free(eval_source);
+
+ out->modified = 0;
+ out->point = 0;
+ ecex_switch_buffer(ed, "*eval-output*");
+ return ECEX_OK;
+}
+
+int ecex_eval_current_buffer(ecex_t *ed) {
+ buffer_t *buf = ecex_current_buffer(ed);
+ if (!buf || !buf->data) return ECEX_ERR;
+
+ return ecex_eval_source(ed,
+ buf->data,
+ buf->path ? buf->path : buf->name,
+ 0);
+}
+
+int ecex_eval_current_line(ecex_t *ed) {
+ buffer_t *buf = ecex_current_buffer(ed);
+ if (!buf) return ECEX_ERR;
+
+ char *line = buffer_current_line_copy(buf);
+ if (!line) return ECEX_ERR;
+
+ int result = ecex_eval_source(ed, line, "<current-line>", 1);
+ free(line);
+ return result;
+}
+
+
+int ecex_eval_current_region(ecex_t *ed) {
+ buffer_t *buf = ecex_current_buffer(ed);
+ if (!buf || !buffer_has_selection(buf)) return ECEX_ERR;
+
+ size_t start = 0;
+ size_t end = 0;
+ buffer_selection_range(buf, &start, &end);
+
+ char *region = buffer_substring(buf, start, end);
+ if (!region) return ECEX_ERR;
+
+ int result = ecex_eval_source(ed, region, "<region>", 1);
+ free(region);
+ return result;
+}
+
+int ecex_eval_file(ecex_t *ed, const char *path) {
+ if (!ed || !path || !path[0]) return ECEX_ERR;
+
+ size_t size = 0;
+ char *source = ecex_read_entire_file(path, &size);
+ (void)size;
+
+ if (!source) {
+ buffer_t *out = eval_output_buffer(ed);
+ if (out) {
+ buffer_clear(out);
+ buffer_append(out, "failed to read eval file: ");
+ buffer_append(out, path);
+ buffer_append(out, "\n");
+ out->modified = 0;
+ ecex_switch_buffer(ed, "*eval-output*");
+ }
+ return ECEX_ERR;
+ }
+
+ int result = ecex_eval_source(ed, source, path, 0);
+ free(source);
+ return result;
+}
+
+int ecex_eval_rerun_last(ecex_t *ed) {
+ if (!ed || !ed->last_eval_source) return ECEX_ERR;
+
+ char *source = ecex_strdup(ed->last_eval_source);
+ char *filename = ecex_strdup(ed->last_eval_filename ? ed->last_eval_filename : "<eval>");
+ int wrap = ed->last_eval_wrap_as_statements;
+
+ if (!source || !filename) {
+ free(source);
+ free(filename);
+ return ECEX_ERR;
+ }
+
+ int result = ecex_eval_source(ed, source, filename, wrap);
+ free(source);
+ free(filename);
+ return result;
+}