aboutsummaryrefslogtreecommitdiff
path: root/src/eval.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/eval.c')
-rw-r--r--src/eval.c217
1 files changed, 208 insertions, 9 deletions
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 <unistd.h>
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 : "<eval>",
+ 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 : "<eval>");
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);