diff options
| author | David Moc <personal@cdatgoose.org> | 2026-05-30 21:53:05 +0200 |
|---|---|---|
| committer | David Moc <personal@cdatgoose.org> | 2026-05-30 21:53:05 +0200 |
| commit | e930cc6bdc7f62befac063d7d9d016ffb0a64f1a (patch) | |
| tree | 52118a1e990ae88f5f0410c8caea129609e22e19 /src/eval.c | |
Added the old repo, refactored it, added the C jit.
Diffstat (limited to 'src/eval.c')
| -rw-r--r-- | src/eval.c | 482 |
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; +} |
