#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 unsigned long g_eval_serial = 0; typedef int (*ecex_eval_entry_fn)(int argc, char **argv); 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 int eval_trace_enabled(void) { const char *log = getenv("ECEX_LOG"); const char *trace_eval = getenv("ECEX_TRACE_EVAL"); return (log && log[0] && log[0] != '0') || (trace_eval && trace_eval[0] && trace_eval[0] != '0'); } static size_t eval_source_line_count(const char *source, size_t source_len) { if (!source || source_len == 0) return 0; size_t lines = 1; for (size_t i = 0; i < source_len; i++) { if (source[i] == '\n' && i + 1 < source_len) lines++; } return lines; } static size_t eval_append_escaped_char(char *out, size_t len, size_t cap, unsigned char c) { static const char hex[] = "0123456789abcdef"; if (!out || cap == 0 || len >= cap) return len; if (c == '\t') { for (int i = 0; i < 4 && len + 1 < cap; i++) out[len++] = ' '; } else if (c == '\\') { if (len + 2 < cap) { out[len++] = '\\'; out[len++] = '\\'; } } else if (c < 32 || c == 127) { if (len + 4 < cap) { out[len++] = '\\'; out[len++] = 'x'; out[len++] = hex[(c >> 4) & 0x0f]; out[len++] = hex[c & 0x0f]; } } else { if (len + 1 < cap) out[len++] = (char)c; } out[len < cap ? len : cap - 1] = '\0'; return len; } static void eval_log_source_line(size_t line_no, const char *line, size_t line_len) { size_t pos = 0; int continued = 0; if (line_len == 0) { ecex_logf("%4lu |", (unsigned long)line_no); return; } while (pos < line_len) { char out[1024]; size_t len = 0; int prefix_len = snprintf(out, sizeof(out), "%4lu %c ", (unsigned long)line_no, continued ? '>' : '|'); if (prefix_len < 0) return; if ((size_t)prefix_len >= sizeof(out)) prefix_len = (int)sizeof(out) - 1; len = (size_t)prefix_len; size_t before = pos; while (pos < line_len && len + 8 < sizeof(out)) { len = eval_append_escaped_char(out, len, sizeof(out), (unsigned char)line[pos]); pos++; } if (pos == before) { len = eval_append_escaped_char(out, len, sizeof(out), (unsigned char)line[pos]); pos++; } ecex_log(out); continued = 1; } } static void eval_log_source(const char *source, const char *filename, int wrap_source, unsigned long serial) { if (!source || !eval_trace_enabled()) return; size_t source_len = strlen(source); size_t line_count = eval_source_line_count(source, source_len); char header[768]; snprintf(header, sizeof(header), "eval source start serial=%lu file=%s mode=%s bytes=%lu lines=%lu", serial, filename ? filename : "", wrap_source ? "wrapped-statements" : "translation-unit", (unsigned long)source_len, (unsigned long)line_count); ecex_log_group_begin(header); if (source_len == 0) { ecex_log("(empty)"); } else { size_t line_start = 0; size_t line_no = 1; for (size_t i = 0; i <= source_len; i++) { if (source[i] != '\n' && source[i] != '\0') continue; if (source[i] == '\0' && line_start == source_len && source_len > 0 && source[source_len - 1] == '\n') { break; } size_t line_len = i - line_start; if (line_len > 0 && source[line_start + line_len - 1] == '\r') line_len--; eval_log_source_line(line_no, source + line_start, line_len); if (source[i] == '\0') break; line_start = i + 1; line_no++; } } ecex_log_group_end("eval source end"); } static char *make_eval_source(const char *source, int wrap_as_statements, unsigned long serial) { if (!source) return NULL; /* Keep eval-defined command/helper symbols private. Successful eval * modules stay loaded, and default-visible ELF symbols can otherwise be * interposed by the first eval that defined the same names. */ char prefix[512]; int prefix_len = snprintf(prefix, sizeof(prefix), "#include \"ecex.h\"\n" "#include \"buffers.h\"\n" "extern ecex_t *__ecex_eval_editor;\n" "#define ECEX_ED (__ecex_eval_editor)\n" "#define main __ecex_eval_user_main_%lu\n" "#pragma GCC visibility push(hidden)\n" "\n", serial); if (prefix_len < 0 || (size_t)prefix_len >= sizeof(prefix)) return NULL; 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 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 entry_wrapper[384]; int entry_wrapper_len = 0; const char *suffix = "\n" "#pragma GCC visibility pop\n" "#undef main\n"; entry_wrapper_len = snprintf(entry_wrapper, sizeof(entry_wrapper), "int ecex_eval_entry(int argc, char **argv) {\n" " return ((int (*)(int, char **))__ecex_eval_user_main_%lu)(argc, argv);\n" "}\n" "int main(int argc, char **argv) {\n" " return ecex_eval_entry(argc, argv);\n" "}\n", serial); if (entry_wrapper_len < 0 || (size_t)entry_wrapper_len >= sizeof(entry_wrapper)) return NULL; size_t suffix_len = strlen(suffix); char *combined = malloc((size_t)prefix_len + open_len + source_len + close_len + suffix_len + (size_t)entry_wrapper_len + 2); if (!combined) return NULL; char *p = combined; memcpy(p, prefix, (size_t)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; } memcpy(p, suffix, suffix_len); p += suffix_len; memcpy(p, entry_wrapper, (size_t)entry_wrapper_len); p += entry_wrapper_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"); unsigned long eval_serial = ++g_eval_serial; int wrap_source = wrap_as_statements || !source_has_main(source); eval_log_source(source, filename, wrap_source, eval_serial); char *eval_source = make_eval_source(source, wrap_as_statements, eval_serial); 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; } char entry_name[128]; snprintf(entry_name, sizeof(entry_name), "ecex_eval_entry"); ecex_eval_entry_fn entry = (ecex_eval_entry_fn)ccdjit_module_symbol(module, entry_name); if (!entry) { buffer_append(out, "failed to resolve eval entry: "); buffer_append(out, entry_name); buffer_append(out, "\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; } int result = 0; eval_capture_t capture; int capture_enabled = (eval_capture_begin(&capture, out) == ECEX_OK); int runtime_status = 0; result = entry(0, NULL); if (capture_enabled) { eval_capture_finish(&capture, out); } /* * Eval code can mutate public editor fields directly, bypassing the * setter functions that normally bump UI revisions. Treat a completed * eval as a possible UI mutation so the app layer always gets a repaint * opportunity. */ ed->ui_revision++; if (eval_trace_enabled()) { ecex_logf("eval result serial=%lu entry=%p result=%d font_size=%.3f font_revision=%lu ui_revision=%lu", eval_serial, (void *)entry, result, (double)ecex_get_font_size(ed), ed->font_revision, ed->ui_revision); } 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; }