#include "pxl8_repl.h" #include #include #include #include #include #include #include #include #include #define PXL8_MAX_REPL_COMMAND_SIZE 4096 #define PXL8_REPL_QUEUE_SIZE 8 struct pxl8_repl_command { char buffer[PXL8_MAX_REPL_COMMAND_SIZE]; }; struct pxl8_repl { char commands[PXL8_REPL_QUEUE_SIZE][PXL8_MAX_REPL_COMMAND_SIZE]; atomic_uint cmd_write_idx; atomic_uint cmd_read_idx; char logs[PXL8_REPL_QUEUE_SIZE][PXL8_MAX_REPL_COMMAND_SIZE]; atomic_uint log_write_idx; atomic_uint log_read_idx; atomic_bool should_quit; pthread_t thread; char accumulator[PXL8_MAX_REPL_COMMAND_SIZE]; pxl8_repl_command command; }; static pxl8_repl* g_repl = NULL; static void pxl8_repl_completion(const char* buf, linenoiseCompletions* lc) { const char* fennel_keywords[] = { "fn", "let", "var", "set", "global", "local", "if", "when", "do", "while", "for", "each", "lambda", "λ", "partial", "macro", "macros", "require", "include", "import-macros", "values", "select", "table", "length", ".", "..", ":", "->", "->>", "-?>", "-?>>", "doto", "match", "case", "pick-values", "collect", "icollect", "accumulate" }; const char* pxl8_functions[] = { "pxl8.clr", "pxl8.pixel", "pxl8.get_pixel", "pxl8.line", "pxl8.rect", "pxl8.rect_fill", "pxl8.circle", "pxl8.circle_fill", "pxl8.text", "pxl8.get_screen", "pxl8.info", "pxl8.warn", "pxl8.error", "pxl8.debug", "pxl8.trace" }; size_t buf_len = strlen(buf); for (size_t i = 0; i < sizeof(fennel_keywords) / sizeof(fennel_keywords[0]); i++) { if (strncmp(buf, fennel_keywords[i], buf_len) == 0) { linenoiseAddCompletion(lc, fennel_keywords[i]); } } for (size_t i = 0; i < sizeof(pxl8_functions) / sizeof(pxl8_functions[0]); i++) { if (strncmp(buf, pxl8_functions[i], buf_len) == 0) { linenoiseAddCompletion(lc, pxl8_functions[i]); } } } static char* pxl8_repl_hints(const char* buf, int* color, int* bold) { if (strncmp(buf, "pxl8.", 5) == 0 && strlen(buf) == 5) { *color = 35; *bold = 0; return "clr|pixel|line|rect|circle|text|get_screen"; } if (strcmp(buf, "(fn") == 0) { *color = 36; *bold = 0; return " [args] body)"; } if (strcmp(buf, "(let") == 0) { *color = 36; *bold = 0; return " [bindings] body)"; } return NULL; } static void pxl8_repl_flush_logs(pxl8_repl* repl) { u32 log_read_idx = atomic_load(&repl->log_read_idx); u32 log_write_idx = atomic_load(&repl->log_write_idx); while (log_read_idx != log_write_idx) { printf("%s", repl->logs[log_read_idx]); atomic_store(&repl->log_read_idx, (log_read_idx + 1) % PXL8_REPL_QUEUE_SIZE); log_read_idx = atomic_load(&repl->log_read_idx); log_write_idx = atomic_load(&repl->log_write_idx); } fflush(stdout); } static void* pxl8_repl_thread(void* arg) { pxl8_repl* repl = (pxl8_repl*)arg; printf("[pxl8 REPL] Fennel 1.6.0 - Tab for completion, Ctrl-D to exit\n"); fflush(stdout); struct linenoiseState ls; char input_buf[PXL8_MAX_REPL_COMMAND_SIZE]; bool editing = false; bool stopped_for_logs = false; struct pollfd pfd = { .fd = STDIN_FILENO, .events = POLLIN }; while (!atomic_load(&repl->should_quit)) { u32 log_read_idx = atomic_load(&repl->log_read_idx); u32 log_write_idx = atomic_load(&repl->log_write_idx); if (log_read_idx != log_write_idx) { if (editing) { linenoiseEditStop(&ls); editing = false; stopped_for_logs = true; } while (log_read_idx != log_write_idx) { printf("%s", repl->logs[log_read_idx]); fflush(stdout); atomic_store(&repl->log_read_idx, (log_read_idx + 1) % PXL8_REPL_QUEUE_SIZE); log_read_idx = atomic_load(&repl->log_read_idx); log_write_idx = atomic_load(&repl->log_write_idx); } continue; } if (!editing && !atomic_load(&repl->should_quit)) { if (stopped_for_logs) { struct timespec ts = {.tv_sec = 0, .tv_nsec = 5000000}; nanosleep(&ts, NULL); stopped_for_logs = false; continue; } const char* prompt = (repl->accumulator[0] != '\0') ? ".. " : ">> "; if (linenoiseEditStart(&ls, STDIN_FILENO, STDOUT_FILENO, input_buf, sizeof(input_buf), prompt) == -1) { atomic_store(&repl->should_quit, true); break; } editing = true; } if (poll(&pfd, 1, 1) <= 0) continue; char* line = linenoiseEditFeed(&ls); if (line == NULL) { atomic_store(&repl->should_quit, true); break; } if (line == linenoiseEditMore) continue; linenoiseEditStop(&ls); editing = false; bool in_multiline = (repl->accumulator[0] != '\0'); if (strlen(line) > 0 || in_multiline) { if (!in_multiline) { linenoiseHistoryAdd(line); linenoiseHistorySave(".pxl8_history"); } if (repl->accumulator[0] != '\0') { strncat(repl->accumulator, "\n", PXL8_MAX_REPL_COMMAND_SIZE - strlen(repl->accumulator) - 1); } strncat(repl->accumulator, line, PXL8_MAX_REPL_COMMAND_SIZE - strlen(repl->accumulator) - 1); u32 write_idx = atomic_load(&repl->cmd_write_idx); u32 read_idx = atomic_load(&repl->cmd_read_idx); u32 next_write = (write_idx + 1) % PXL8_REPL_QUEUE_SIZE; if (next_write != read_idx) { strncpy(repl->commands[write_idx], repl->accumulator, PXL8_MAX_REPL_COMMAND_SIZE - 1); repl->commands[write_idx][PXL8_MAX_REPL_COMMAND_SIZE - 1] = '\0'; atomic_store(&repl->cmd_write_idx, next_write); } } linenoiseFree(line); while (atomic_load(&repl->cmd_write_idx) != atomic_load(&repl->cmd_read_idx)) { struct timespec ts = {.tv_sec = 0, .tv_nsec = 1000000}; nanosleep(&ts, NULL); } } if (editing) linenoiseEditStop(&ls); pxl8_repl_flush_logs(repl); return NULL; } pxl8_repl* pxl8_repl_create(void) { pxl8_repl* repl = (pxl8_repl*)calloc(1, sizeof(pxl8_repl)); if (!repl) return NULL; repl->accumulator[0] = '\0'; atomic_store(&repl->cmd_write_idx, 0); atomic_store(&repl->cmd_read_idx, 0); atomic_store(&repl->log_write_idx, 0); atomic_store(&repl->log_read_idx, 0); atomic_store(&repl->should_quit, false); linenoiseHistorySetMaxLen(100); linenoiseSetMultiLine(1); linenoiseSetCompletionCallback(pxl8_repl_completion); linenoiseSetHintsCallback(pxl8_repl_hints); linenoiseHistoryLoad(".pxl8_history"); g_repl = repl; if (pthread_create(&repl->thread, NULL, pxl8_repl_thread, repl) != 0) { free(repl); g_repl = NULL; return NULL; } return repl; } void pxl8_repl_destroy(pxl8_repl* repl) { if (!repl) return; atomic_store(&repl->should_quit, true); struct timespec ts = {.tv_sec = 0, .tv_nsec = 2000000}; nanosleep(&ts, NULL); printf("\r\033[K"); fflush(stdout); pthread_join(repl->thread, NULL); pxl8_repl_flush_logs(repl); g_repl = NULL; system("stty sane 2>/dev/null"); free(repl); } pxl8_repl_command* pxl8_repl_pop_command(pxl8_repl* repl) { if (!repl) return NULL; u32 read_idx = atomic_load(&repl->cmd_read_idx); u32 write_idx = atomic_load(&repl->cmd_write_idx); if (read_idx == write_idx) return NULL; strncpy(repl->command.buffer, repl->commands[read_idx], PXL8_MAX_REPL_COMMAND_SIZE - 1); repl->command.buffer[PXL8_MAX_REPL_COMMAND_SIZE - 1] = '\0'; atomic_store(&repl->cmd_read_idx, (read_idx + 1) % PXL8_REPL_QUEUE_SIZE); return &repl->command; } const char* pxl8_repl_command_buffer(pxl8_repl_command* cmd) { return cmd ? cmd->buffer : NULL; } bool pxl8_repl_should_quit(pxl8_repl* repl) { return repl ? atomic_load(&repl->should_quit) : false; } bool pxl8_repl_push_log(const char* message) { if (!g_repl || !message) return false; u32 write_idx = atomic_load(&g_repl->log_write_idx); u32 read_idx = atomic_load(&g_repl->log_read_idx); u32 next_write = (write_idx + 1) % PXL8_REPL_QUEUE_SIZE; if (next_write != read_idx) { strncpy(g_repl->logs[write_idx], message, PXL8_MAX_REPL_COMMAND_SIZE - 1); g_repl->logs[write_idx][PXL8_MAX_REPL_COMMAND_SIZE - 1] = '\0'; atomic_store(&g_repl->log_write_idx, next_write); } return true; } void pxl8_repl_clear_accumulator(pxl8_repl* repl) { if (!repl) return; repl->accumulator[0] = '\0'; }