Something went wrong. Try again.
MIRROR: javascript for 🐜's, a tiny runtime with big ambitions ant.themackabu.dev
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811#include <compat.h> // IWYU pragma: keep#include <stdio.h>#include <stdlib.h>#include <string.h>#include <ctype.h>#include <pthread.h>#include <signal.h>#include <crprintf.h>
#include "ant.h"#include "repl.h"#include "readline.h"#include "reactor.h"#include "internal.h"#include "descriptors.h"#include "silver/call.h"#include "modules/io.h"#include "highlight.h"#include "highlight/regex.h"#include "inspector.h"#include "esm/commonjs.h"
typedef enum { CMD_OK, CMD_EXIT, CMD_NOT_FOUND} cmd_result_t;
typedef struct { const char *name; const char *description; bool has_arg; cmd_result_t (*handler)( ant_t *js, ant_history_t *history, const char *arg );} repl_command_t;
static void repl_read_wake_cb(uv_async_t *handle) { (void)handle; }static cmd_result_t cmd_help(ant_t *js, ant_history_t *history, const char *arg);
typedef struct { pthread_mutex_t mutex; uv_async_t async; ant_history_t *history; const char *prompt; highlight_state prefix_state; repl_preview_snapshot_t *preview_snapshot; ant_readline_result_t status; char *line; pthread_cond_t preview_cond; char preview_request_line[REPL_PREVIEW_EXPR_MAX]; char preview_response_suffix[REPL_PREVIEW_TEXT_MAX]; char preview_response_text[REPL_PREVIEW_TEXT_MAX]; size_t preview_request_len; bool done; bool async_initialized; bool preview_enabled; bool preview_cond_initialized; bool preview_request_pending; bool preview_response_ready;} repl_read_job_t;
static bool repl_read_preview_cb( void *ctx, const char *line, size_t len, char *suffix_out, size_t suffix_len, char *preview_out, size_t preview_len) { repl_read_job_t *job = (repl_read_job_t *)ctx; if ( !job || !line || !suffix_out || suffix_len == 0 || !preview_out || preview_len == 0 ) return false; suffix_out[0] = '\0'; preview_out[0] = '\0'; if (!job->preview_enabled || !job->preview_cond_initialized) return false; if (len >= REPL_PREVIEW_EXPR_MAX) len = REPL_PREVIEW_EXPR_MAX - 1;
pthread_mutex_lock(&job->mutex); if (!job->async_initialized || job->done) { pthread_mutex_unlock(&job->mutex); return false; }
while (job->preview_request_pending && !job->done) pthread_cond_wait(&job->preview_cond, &job->mutex); if (job->done) { pthread_mutex_unlock(&job->mutex); return false; }
memcpy(job->preview_request_line, line, len); job->preview_request_line[len] = '\0'; job->preview_request_len = len; job->preview_response_suffix[0] = '\0'; job->preview_response_text[0] = '\0'; job->preview_response_ready = false; job->preview_request_pending = true; if (uv_async_send(&job->async) != 0) { job->preview_request_pending = false; pthread_cond_broadcast(&job->preview_cond); pthread_mutex_unlock(&job->mutex); return false; }
while ( job->preview_request_pending && !job->preview_response_ready && !job->done ) pthread_cond_wait(&job->preview_cond, &job->mutex);
if (job->preview_response_ready) { strncpy(suffix_out, job->preview_response_suffix, suffix_len - 1); suffix_out[suffix_len - 1] = '\0'; strncpy(preview_out, job->preview_response_text, preview_len - 1); preview_out[preview_len - 1] = '\0'; } pthread_mutex_unlock(&job->mutex); return suffix_out[0] != '\0' || preview_out[0] != '\0';}
static void repl_read_job_handle_preview(ant_t *js, repl_read_job_t *job) { if (!js || !job || !job->preview_cond_initialized) return;
for (;;) { char line[REPL_PREVIEW_EXPR_MAX]; size_t len = 0;
pthread_mutex_lock(&job->mutex); if (!job->preview_request_pending || job->preview_response_ready) { pthread_mutex_unlock(&job->mutex); return; } len = job->preview_request_len; memcpy(line, job->preview_request_line, len + 1); pthread_mutex_unlock(&job->mutex);
char suffix[REPL_PREVIEW_TEXT_MAX]; char preview[REPL_PREVIEW_TEXT_MAX]; repl_preview_compute( js, job->preview_snapshot, line, len, suffix, sizeof(suffix), preview, sizeof(preview) );
pthread_mutex_lock(&job->mutex); strncpy(job->preview_response_suffix, suffix, sizeof(job->preview_response_suffix) - 1); job->preview_response_suffix[sizeof(job->preview_response_suffix) - 1] = '\0'; strncpy(job->preview_response_text, preview, sizeof(job->preview_response_text) - 1); job->preview_response_text[sizeof(job->preview_response_text) - 1] = '\0'; job->preview_response_ready = true; job->preview_request_pending = false; pthread_cond_broadcast(&job->preview_cond); pthread_mutex_unlock(&job->mutex); }}
static void repl_read_async_close_cb(uv_handle_t *handle) { repl_read_job_t *job = (repl_read_job_t *)handle->data; if (job) job->async_initialized = false;}
static void *repl_read_thread_main(void *data) { repl_read_job_t *job = (repl_read_job_t *)data; char *line = NULL;
#ifndef _WIN32 sigset_t sigint_set; sigemptyset(&sigint_set); sigaddset(&sigint_set, SIGINT); pthread_sigmask(SIG_UNBLOCK, &sigint_set, NULL);#endif
ant_readline_result_t status = ant_readline_with_preview( job->history, job->prompt, job->prefix_state, job->preview_enabled ? repl_read_preview_cb : NULL, job, &line );
pthread_mutex_lock(&job->mutex); job->status = status; job->line = line; job->done = true; if (job->preview_cond_initialized) pthread_cond_broadcast(&job->preview_cond); pthread_mutex_unlock(&job->mutex);
if (job->async_initialized) uv_async_send(&job->async); return NULL;}
static bool repl_read_job_is_done(repl_read_job_t *job) { bool done; pthread_mutex_lock(&job->mutex); done = job->done; pthread_mutex_unlock(&job->mutex); return done;}
static ant_readline_result_t repl_readline_async( ant_t *js, ant_history_t *history, const char *prompt, highlight_state prefix_state, char **out_line) { repl_read_job_t job = { .history = history, .prompt = prompt, .prefix_state = prefix_state, .preview_snapshot = NULL, .status = ANT_READLINE_EOF, .line = NULL, .preview_request_len = 0, .done = false, .async_initialized = false, .preview_enabled = true, .preview_cond_initialized = false, .preview_request_pending = false, .preview_response_ready = false, }; repl_preview_snapshot_t preview_snapshot = {0};
pthread_t thread;#ifndef _WIN32 sigset_t sigint_set; sigset_t old_sigmask; bool sigint_blocked = false;#endif
if (out_line) *out_line = NULL; if (repl_preview_snapshot_build(js, &preview_snapshot)) job.preview_snapshot = &preview_snapshot; else job.preview_enabled = false;
if (pthread_mutex_init(&job.mutex, NULL) != 0) { repl_preview_snapshot_free(&preview_snapshot); return ant_readline(history, prompt, prefix_state, out_line); }
if (pthread_cond_init(&job.preview_cond, NULL) != 0) { pthread_mutex_destroy(&job.mutex); repl_preview_snapshot_free(&preview_snapshot); return ant_readline(history, prompt, prefix_state, out_line); } job.preview_cond_initialized = true;
if (uv_async_init(js->loop, &job.async, repl_read_wake_cb) != 0) { pthread_cond_destroy(&job.preview_cond); pthread_mutex_destroy(&job.mutex); repl_preview_snapshot_free(&preview_snapshot); return ant_readline(history, prompt, prefix_state, out_line); } job.async.data = &job; job.async_initialized = true;
#ifndef _WIN32 sigemptyset(&sigint_set); sigaddset(&sigint_set, SIGINT); sigint_blocked = pthread_sigmask(SIG_BLOCK, &sigint_set, &old_sigmask) == 0;#endif
if (pthread_create(&thread, NULL, repl_read_thread_main, &job) != 0) {#ifndef _WIN32 if (sigint_blocked) pthread_sigmask(SIG_SETMASK, &old_sigmask, NULL);#endif if (job.async_initialized) uv_close((uv_handle_t *)&job.async, repl_read_async_close_cb); while (job.async_initialized) ant_uv_run(js->loop, UV_RUN_ONCE); pthread_cond_destroy(&job.preview_cond); pthread_mutex_destroy(&job.mutex); repl_preview_snapshot_free(&preview_snapshot); return ant_readline(history, prompt, prefix_state, out_line); }
while (!repl_read_job_is_done(&job)) { repl_read_job_handle_preview(js, &job); js_reactor_pump_repl_nowait(js); ant_uv_run(js->loop, UV_RUN_ONCE); repl_read_job_handle_preview(js, &job); } repl_read_job_handle_preview(js, &job);
pthread_join(thread, NULL);#ifndef _WIN32 if (sigint_blocked) pthread_sigmask(SIG_SETMASK, &old_sigmask, NULL);#endif
if (job.async_initialized) uv_close((uv_handle_t *)&job.async, repl_read_async_close_cb); while (job.async_initialized) ant_uv_run(js->loop, UV_RUN_ONCE);
pthread_mutex_lock(&job.mutex); ant_readline_result_t status = job.status; char *line = job.line; pthread_mutex_unlock(&job.mutex); pthread_cond_destroy(&job.preview_cond); pthread_mutex_destroy(&job.mutex); repl_preview_snapshot_free(&preview_snapshot);
if (out_line) *out_line = line; else free(line); return status;}
typedef enum { REPL_PRINT_INTERACTIVE, REPL_PRINT_LOAD, REPL_PRINT_STARTUP,} repl_print_mode_t;
typedef enum { REPL_EVAL_COMPLETED, REPL_EVAL_INTERRUPTED,} repl_eval_status_t;
static bool repl_eval_interrupt_pending(void *ctx) { (void)ctx; return ant_readline_interrupt_pending();}
static repl_eval_status_t repl_evaluate( ant_t *js, const char *code, size_t len, ant_value_t *result_out) { js_eval_result_t evaluation = js_eval_bytecode_repl(js, code, len); ant_value_t result = evaluation.value;
if (evaluation.kind == JS_EVAL_ASYNC_ENTRY && !Ant_Exception_Pending(js)) { js_reactor_await_status_t await_status = js_reactor_blocking_await_promise( js, result, &result, repl_eval_interrupt_pending, NULL ); if (await_status == JS_REACTOR_AWAIT_INTERRUPTED) { coroutine_cancel(evaluation.async_coro); coroutine_release(evaluation.async_coro); ant_readline_clear_interrupt(); return REPL_EVAL_INTERRUPTED; } coroutine_release(evaluation.async_coro); if (await_status == JS_REACTOR_AWAIT_REJECTED) js_throw(js, result); else if (await_status == JS_REACTOR_AWAIT_INVALID) result = js_mkerr(js, "invalid top-level await completion"); } else { coroutine_release(evaluation.async_coro); if (!Ant_Exception_Pending(js)) js_reactor_pump_repl_nowait(js); }
if (result_out) *result_out = result; return REPL_EVAL_COMPLETED;}
static void repl_eval_chunk( ant_t *js, const char *code, size_t len, repl_print_mode_t print_mode) { js_take_thrown(js, js_mkundef()); ant_value_t result = js_mkundef(); if (repl_evaluate(js, code, len, &result) == REPL_EVAL_INTERRUPTED) { fputs("^C\n", stdout); return; }
if (Ant_Exception_Pending(js)) { js_set(js, js_glob(js), "_error", Ant_Exception_Value(js, Ant_Exception_Peek(js))); if (print_uncaught_throw(js)) return; }
if (print_mode == REPL_PRINT_INTERACTIVE) { js_set(js, js_glob(js), "_", result); print_repl_value(js, result, stdout); return; }
if (print_mode == REPL_PRINT_LOAD) { if (vtype(result) == kTypeError) fprintf(stderr, "%s\n", js_str(js, result)); else if (vtype(result) != kTypeUndefined) printf("%s\n", js_str(js, result)); }}
static const char *repl_command_usage(const repl_command_t *cmd) { if (!cmd || !cmd->name) return ""; if (strcmp(cmd->name, "copy") == 0) return ".copy [expr]"; if (strcmp(cmd->name, "load") == 0) return ".load <file>"; if (strcmp(cmd->name, "save") == 0) return ".save <file>"; if (strcmp(cmd->name, "history") == 0) return ".history"; if (strcmp(cmd->name, "clear") == 0) return ".clear"; if (strcmp(cmd->name, "stats") == 0) return ".stats"; if (strcmp(cmd->name, "exit") == 0) return ".exit"; if (strcmp(cmd->name, "help") == 0) return ".help"; return cmd->name;}
static cmd_result_t cmd_exit(ant_t *js, ant_history_t *history, const char *arg) { return CMD_EXIT;}
static cmd_result_t cmd_load(ant_t *js, ant_history_t *history, const char *arg) { if (!arg || *arg == '\0') { fprintf(stderr, "Usage: .load <filename>\n"); return CMD_OK; }
FILE *fp = fopen(arg, "r"); if (fp == NULL) { fprintf(stderr, "Failed to open file: %s\n", arg); return CMD_OK; }
fseek(fp, 0, SEEK_END); long file_size = ftell(fp); fseek(fp, 0, SEEK_SET);
char *file_buffer = malloc(file_size + 1); if (file_buffer) { size_t len = fread(file_buffer, 1, file_size, fp); file_buffer[len] = '\0'; repl_eval_chunk(js, file_buffer, len, REPL_PRINT_LOAD); free(file_buffer); }
fclose(fp); return CMD_OK;}
static cmd_result_t cmd_save(ant_t *js, ant_history_t *history, const char *arg) { if (!arg || *arg == '\0') { fprintf(stderr, "Usage: .save <filename>\n"); return CMD_OK; }
FILE *fp = fopen(arg, "w"); if (fp == NULL) { fprintf(stderr, "Failed to open file for writing: %s\n", arg); return CMD_OK; }
for (int i = 0; i < history->count; i++) fprintf(fp, "%s\n", history->lines[i]);
fclose(fp); printf("Session saved to %s\n", arg);
return CMD_OK;}
static cmd_result_t cmd_stats(ant_t *js, ant_history_t *history, const char *arg) { ant_value_t stats_fn = js_get(js, js->Ant, "stats"); ant_value_t result = sv_vm_call(js->vm, js, stats_fn, js_mkundef(), NULL, 0, NULL, js_mkundef()); console_emit(js, false, NULL, &result, 1); return CMD_OK;}
static cmd_result_t cmd_clear(ant_t *js, ant_history_t *history, const char *arg) { fputs("\033[2J\033[H", stdout); fflush(stdout); return CMD_OK;}
static cmd_result_t cmd_history(ant_t *js, ant_history_t *history, const char *arg) { for (int i = 0; i < history->count; i++) printf("%4d %s\n", i + 1, history->lines[i]); return CMD_OK;}
#ifdef _WIN32static bool repl_copy_with_command(const char *data, size_t len) { FILE *pipe = _popen("clip", "wb"); if (!pipe) return false;
size_t written = fwrite(data, 1, len, pipe); int close_rc = _pclose(pipe); return written == len && close_rc == 0;}#elsestatic bool repl_copy_with_single_command(const char *cmd, const char *data, size_t len) { FILE *pipe = popen(cmd, "w"); if (!pipe) return false;
size_t written = fwrite(data, 1, len, pipe); int close_rc = pclose(pipe); return written == len && close_rc == 0;}
static bool repl_copy_with_command(const char *data, size_t len) { static const char *cmds[] = { "pbcopy", "wl-copy", "xclip -selection clipboard", "xsel --clipboard --input", }; for (size_t i = 0; i < sizeof(cmds) / sizeof(cmds[0]); i++) if (repl_copy_with_single_command(cmds[i], data, len)) return true; return false;}#endif
static cmd_result_t cmd_copy(ant_t *js, ant_history_t *history, const char *arg) { if (!arg || *arg == '\0') { fprintf(stderr, "Usage: .copy <expression>\n"); return CMD_OK; }
js_take_thrown(js, js_mkundef()); ant_value_t result = js_mkundef();
if (repl_evaluate(js, arg, strlen(arg), &result) == REPL_EVAL_INTERRUPTED) { fputs("^C\n", stdout); return CMD_OK; }
if (Ant_Exception_Pending(js)) { js_set(js, js_glob(js), "_error", Ant_Exception_Value(js, Ant_Exception_Peek(js))); if (print_uncaught_throw(js)) return CMD_OK; }
js_set(js, js_glob(js), "_", result); char cbuf[512]; js_cstr_t cstr = js_to_cstr(js, result, cbuf, sizeof(cbuf));
bool copied_command = repl_copy_with_command(cstr.ptr, cstr.len); if (cstr.needs_free) free((void *)cstr.ptr);
if (!copied_command) { fprintf(stderr, "Failed to copy to clipboard (no clipboard command available).\n"); return CMD_OK; }
printf("Copied to clipboard.\n"); return CMD_OK;}
static const repl_command_t commands[] = { { "help", "Show this help message", false, cmd_help }, { "exit", "Exit the REPL", false, cmd_exit }, { "clear", "Clear the screen", false, cmd_clear }, { "history", "Show command history", false, cmd_history }, { "load", "Load JS from a file into the REPL session", true, cmd_load }, { "save", "Save all evaluated commands in this REPL session to a file", true, cmd_save }, { "stats", "Show memory statistics", false, cmd_stats }, { "copy", "Evaluate expression and copy its value", true, cmd_copy }, { NULL, NULL, false, NULL }};
static cmd_result_t cmd_help(ant_t *js, ant_history_t *history, const char *arg) { printf("\n%sREPL Commands:%s\n", C_BOLD, C_RESET); for (const repl_command_t *cmd = commands; cmd->name; cmd++) { const char *usage = repl_command_usage(cmd); printf(" %s%-12s%s %s\n", C_CYAN, usage, C_RESET, cmd->description); } printf("\n%sKeybindings:%s\n", C_BOLD, C_RESET); printf(" Ctrl+C Abort current expression (press twice to exit)\n"); printf(" Left/Right Move backward/forward one character\n"); printf(" Home/End Jump to start/end of line\n"); printf(" Up/Down Navigate history\n"); printf(" Backspace Delete character backward\n"); printf(" Delete Delete character under cursor\n"); printf(" Enter Submit input\n"); printf("\n%sSpecial Variables:%s\n", C_BOLD, C_RESET); printf(" %s_%s Last expression result\n", C_CYAN, C_RESET); printf(" %s_error%s Last error\n\n", C_CYAN, C_RESET); return CMD_OK;}
static cmd_result_t execute_command(ant_t *js, ant_history_t *history, const char *line) { const char *cmd_start = line + 1;
for (const repl_command_t *cmd = commands; cmd->name; cmd++) { size_t n = strlen(cmd->name); if (strncmp(cmd_start, cmd->name, n) != 0) continue;
char next = cmd_start[n]; if (cmd->has_arg && (next == ' ' || next == '\0')) { const char *arg = cmd_start + n; while (*arg == ' ') arg++; return cmd->handler(js, history, arg); } if (!cmd->has_arg && next == '\0') return cmd->handler(js, history, NULL); }
return CMD_NOT_FOUND;}
typedef struct { int paren, bracket, brace; int *templates; int template_count, template_cap; char string_char; bool in_string, escaped; char last_code_char;} parse_state_t;
static void push_template(parse_state_t *s) { if (s->template_count >= s->template_cap) { s->template_cap = s->template_cap ? s->template_cap * 2 : 8; int *new_templates = realloc(s->templates, s->template_cap * sizeof(int)); if (!new_templates) { return; } s->templates = new_templates; } s->templates[s->template_count++] = s->brace;}
static bool in_template_text(parse_state_t *s) { return s->template_count > 0 && s->brace == s->templates[s->template_count - 1];}
static bool is_incomplete_input(const char *code, size_t len) { parse_state_t s = {0};
for (size_t i = 0; i < len; i++) { char c = code[i];
if (s.escaped) { s.escaped = false; continue; } if (c == '\\' && (s.in_string || s.template_count > 0)) { s.escaped = true; continue; } if (s.in_string) { if (c == s.string_char) s.in_string = false; continue; }
if (in_template_text(&s)) { if (c == '`') s.template_count--; else if (c == '$' && i + 1 < len && code[i + 1] == '{') { s.brace++; i++; } continue; }
if (c == '/' && i + 1 < len) { if (code[i + 1] == '/') { while (i < len && code[i] != '\n') i++; continue; } if (code[i + 1] == '*') { for (i += 2; i + 1 < len && !(code[i] == '*' && code[i + 1] == '/'); i++); if (i + 1 >= len) { free(s.templates); return true; } i++; continue; } if (js_regex_can_start(code, i)) { size_t regex_end = 0; if (!js_scan_regex_literal(code, len, i, ®ex_end)) { free(s.templates); return true; } i = regex_end - 1; continue; } }
switch (c) { case '"': case '\'': s.in_string = true; s.string_char = c; break; case '`': push_template(&s); break; case '(': s.paren++; break; case ')': s.paren--; break; case '[': s.bracket++; break; case ']': s.bracket--; break; case '{': s.brace++; break; case '}': s.brace--; break; } if (!isspace((unsigned char)c)) s.last_code_char = c; }
bool incomplete = s.in_string || s.template_count > 0 || s.paren > 0 || s.bracket > 0 || s.brace > 0 || s.last_code_char == ',';
free(s.templates); return incomplete;}
void ant_repl_run(ant_t *js, const char *startup_code) { ant_readline_install_signal_handler(); js->uncaught_nonfatal = true;
js_set_filename(js, "[repl]"); js_setup_import_meta(js, "[repl]");
if (!startup_code) crprintf( "Welcome to <red+bold>Ant JavaScript</> v%s\n" "Type <cyan>.copy [code]</cyan> to copy, <cyan>.help</cyan> for more information.\n\n", ANT_VERSION );
ant_history_t history; ant_history_init(&history, 512); ant_history_load(&history);
js_set_global_builtin(js, "__dirname", js_mkstr(js, ".", 1)); js_set_global_builtin(js, "__filename", js_mkstr(js, "[repl]", 6));
char cwd[PATH_MAX]; char repl_path[PATH_MAX + 8]; if (getcwd(cwd, sizeof(cwd))) { snprintf(repl_path, sizeof(repl_path), "%s/[repl]", cwd); js_set_global_builtin(js, "require", esm_create_require_from_path(js, repl_path)); }
js_set(js, js_glob(js), "_", js_mkundef()); js_set(js, js_glob(js), "_error", js_mkundef());
js_set_descriptor(js, js_as_obj(js_glob(js)), "_", 1, JS_DESC_W | JS_DESC_C); js_set_descriptor(js, js_as_obj(js_glob(js)), "_error", 6, JS_DESC_W | JS_DESC_C);
if (startup_code) repl_eval_chunk(js, startup_code, strlen(startup_code), REPL_PRINT_STARTUP);
int prev_ctrl_c_count = 0; char *multiline_buf = NULL; size_t multiline_len = 0; size_t multiline_cap = 0;
while (1) { const char *prompt = multiline_buf ? "\x1b[2m|\x1b[0m " : "\x1b[2m❯\x1b[0m "; highlight_state prefix_state = HL_STATE_INIT; if (multiline_buf && multiline_len > 0) { char scratch[8192]; ant_highlight_stateful(multiline_buf, multiline_len, scratch, sizeof(scratch), &prefix_state); }
fputs(prompt, stdout); fflush(stdout);
char *line = NULL; ant_readline_result_t readline_status = repl_readline_async(js, &history, prompt, prefix_state, &line);
if (readline_status == ANT_READLINE_INTERRUPT) { if (multiline_buf) { free(multiline_buf); multiline_buf = NULL; multiline_len = 0; multiline_cap = 0; prev_ctrl_c_count = 0; if (line) free(line); continue; } if (prev_ctrl_c_count > 0) { if (line) free(line); break; } printf("(To exit, press Ctrl+C again or type .exit)\n"); prev_ctrl_c_count++; if (line) free(line); continue; }
if (readline_status == ANT_READLINE_EOF || line == NULL) { if (multiline_buf) { free(multiline_buf); multiline_buf = NULL; multiline_len = 0; multiline_cap = 0; continue; } break; }
prev_ctrl_c_count = 0; size_t line_len = strlen(line);
if (line_len == 0 && multiline_buf) { if (multiline_len + 1 >= multiline_cap) { multiline_cap = multiline_cap ? multiline_cap * 2 : 256; multiline_buf = realloc(multiline_buf, multiline_cap); } multiline_buf[multiline_len++] = '\n'; multiline_buf[multiline_len] = '\0'; free(line); continue; }
if (line_len == 0) { free(line); continue; }
if (!multiline_buf && line[0] == '.') { cmd_result_t result = execute_command(js, &history, line); if (result == CMD_EXIT) { free(line); break; } else if (result == CMD_NOT_FOUND) { printf("Unknown command: %s\n", line); printf("Type \".help\" for more information.\n"); } free(line); continue; }
size_t new_len = multiline_len + line_len + 1; if (new_len >= multiline_cap || !multiline_buf) { multiline_cap = multiline_cap ? multiline_cap * 2 : 256; if (multiline_cap < new_len + 1) multiline_cap = new_len + 1; multiline_buf = realloc(multiline_buf, multiline_cap); }
if (multiline_len > 0) multiline_buf[multiline_len++] = '\n';
memcpy(multiline_buf + multiline_len, line, line_len); multiline_len += line_len; multiline_buf[multiline_len] = '\0'; free(line);
if (is_incomplete_input(multiline_buf, multiline_len)) continue; ant_history_add(&history, multiline_buf); repl_eval_chunk(js, multiline_buf, multiline_len, REPL_PRINT_INTERACTIVE);
free(multiline_buf); multiline_buf = NULL; multiline_len = 0; multiline_cap = 0; }
if (multiline_buf) free(multiline_buf); ant_readline_shutdown(); ant_history_save(&history); ant_history_free(&history);}