#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 #include #include #include 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 : "", 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 : ""); 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 : ""); 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 : "", &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, "", 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, "", 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 : ""); 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; }