From a15cb041654ae307add0b998b526c87c3f42bf5f Mon Sep 17 00:00:00 2001 From: David Moc Date: Tue, 2 Jun 2026 13:50:21 +0200 Subject: Add plugin hooks and mode plugins --- src/eval.c | 217 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 208 insertions(+), 9 deletions(-) (limited to 'src/eval.c') diff --git a/src/eval.c b/src/eval.c index e5f0e44..34259eb 100644 --- a/src/eval.c +++ b/src/eval.c @@ -15,6 +15,9 @@ #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; @@ -207,15 +210,148 @@ static int source_has_main(const char *source) { strstr(source, "main (") != NULL; } -static char *make_eval_source(const char *source, int wrap_as_statements) { +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; - const char *prefix = + /* 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" - "\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" @@ -231,16 +367,35 @@ static char *make_eval_source(const char *source, int wrap_as_statements) { 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); + 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, prefix_len); + memcpy(p, prefix, (size_t)prefix_len); p += prefix_len; if (do_wrap) { @@ -259,6 +414,12 @@ static char *make_eval_source(const char *source, int wrap_as_statements) { 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; } @@ -305,7 +466,11 @@ int ecex_eval_source(ecex_t *ed, buffer_append(out, filename ? filename : ""); buffer_append(out, "\n\n"); - char *eval_source = make_eval_source(source, wrap_as_statements); + 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*"); @@ -349,15 +514,49 @@ int ecex_eval_source(ecex_t *ed, 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 = ccdjit_module_call_main(module, 0, NULL, &result); + 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); -- cgit v1.2.3