424 lines
13 KiB
C
424 lines
13 KiB
C
#include "pxl8_repl.h"
|
|
|
|
#include <linenoise.h>
|
|
#include <SDL3/SDL.h>
|
|
#include <stdatomic.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#ifndef _WIN32
|
|
#include <poll.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#include "pxl8_log.h"
|
|
#include "pxl8_mem.h"
|
|
|
|
#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;
|
|
atomic_bool cmd_complete;
|
|
|
|
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;
|
|
SDL_Thread* thread;
|
|
char accumulator[PXL8_MAX_REPL_COMMAND_SIZE];
|
|
pxl8_repl_command command;
|
|
};
|
|
|
|
static pxl8_repl* g_repl = NULL;
|
|
|
|
static 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"
|
|
};
|
|
|
|
static 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"
|
|
};
|
|
|
|
#ifdef PXL8_WIN32_LINENOISE
|
|
|
|
static void pxl8_repl_completion(const char* buf, linenoiseCompletions* lc, void* userdata) {
|
|
(void)userdata;
|
|
usize buf_len = strlen(buf);
|
|
for (usize 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 (usize 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, void* userdata) {
|
|
(void)userdata;
|
|
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;
|
|
}
|
|
|
|
#else
|
|
|
|
static void pxl8_repl_completion(const char* buf, linenoiseCompletions* lc) {
|
|
usize buf_len = strlen(buf);
|
|
for (usize 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 (usize 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;
|
|
}
|
|
|
|
#endif
|
|
|
|
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);
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
|
|
static int pxl8_repl_thread(void* arg) {
|
|
pxl8_repl* repl = (pxl8_repl*)arg;
|
|
|
|
printf("[pxl8 REPL] Fennel 1.6.0 - Tab for completion, Ctrl-Z to exit\n");
|
|
fflush(stdout);
|
|
|
|
while (!atomic_load(&repl->should_quit)) {
|
|
pxl8_repl_flush_logs(repl);
|
|
|
|
const char* prompt = (repl->accumulator[0] != '\0') ? ".. " : ">> ";
|
|
char* line = linenoise(prompt);
|
|
|
|
if (line == NULL) {
|
|
atomic_store(&repl->should_quit, true);
|
|
break;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
free(line);
|
|
|
|
while (!atomic_load(&repl->should_quit) &&
|
|
(atomic_load(&repl->cmd_write_idx) != atomic_load(&repl->cmd_read_idx) ||
|
|
!atomic_load(&repl->cmd_complete))) {
|
|
pxl8_repl_flush_logs(repl);
|
|
SDL_Delay(1);
|
|
}
|
|
atomic_store(&repl->cmd_complete, false);
|
|
}
|
|
|
|
pxl8_repl_flush_logs(repl);
|
|
return 0;
|
|
}
|
|
|
|
#else
|
|
|
|
static int 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;
|
|
|
|
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) {
|
|
printf("\r\033[K");
|
|
if (editing) {
|
|
linenoiseEditStop(&ls);
|
|
editing = false;
|
|
printf("\033[A\r\033[K");
|
|
}
|
|
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);
|
|
continue;
|
|
}
|
|
|
|
if (!editing && !atomic_load(&repl->should_quit)) {
|
|
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->should_quit) &&
|
|
(atomic_load(&repl->cmd_write_idx) != atomic_load(&repl->cmd_read_idx) ||
|
|
!atomic_load(&repl->cmd_complete))) {
|
|
u32 lr = atomic_load(&repl->log_read_idx);
|
|
u32 lw = atomic_load(&repl->log_write_idx);
|
|
while (lr != lw) {
|
|
printf("%s", repl->logs[lr]);
|
|
atomic_store(&repl->log_read_idx, (lr + 1) % PXL8_REPL_QUEUE_SIZE);
|
|
lr = atomic_load(&repl->log_read_idx);
|
|
lw = atomic_load(&repl->log_write_idx);
|
|
}
|
|
fflush(stdout);
|
|
SDL_Delay(1);
|
|
}
|
|
atomic_store(&repl->cmd_complete, false);
|
|
}
|
|
|
|
if (editing) linenoiseEditStop(&ls);
|
|
|
|
pxl8_repl_flush_logs(repl);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
pxl8_repl* pxl8_repl_create(void) {
|
|
pxl8_repl* repl = (pxl8_repl*)pxl8_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->cmd_complete, true);
|
|
atomic_store(&repl->log_write_idx, 0);
|
|
atomic_store(&repl->log_read_idx, 0);
|
|
atomic_store(&repl->should_quit, false);
|
|
|
|
linenoiseHistorySetMaxLen(100);
|
|
linenoiseSetMultiLine(1);
|
|
#ifdef PXL8_WIN32_LINENOISE
|
|
linenoiseSetCompletionCallback(pxl8_repl_completion, NULL);
|
|
linenoiseSetHintsCallback(pxl8_repl_hints, NULL);
|
|
#else
|
|
linenoiseSetCompletionCallback(pxl8_repl_completion);
|
|
linenoiseSetHintsCallback(pxl8_repl_hints);
|
|
#endif
|
|
linenoiseHistoryLoad(".pxl8_history");
|
|
|
|
g_repl = repl;
|
|
pxl8_log_set_handler(pxl8_repl_push_log);
|
|
|
|
repl->thread = SDL_CreateThread(pxl8_repl_thread, "pxl8-repl", repl);
|
|
if (!repl->thread) {
|
|
pxl8_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);
|
|
|
|
SDL_Delay(2);
|
|
|
|
printf("\r\033[K");
|
|
fflush(stdout);
|
|
|
|
SDL_WaitThread(repl->thread, NULL);
|
|
pxl8_repl_flush_logs(repl);
|
|
|
|
g_repl = NULL;
|
|
pxl8_log_set_handler(NULL);
|
|
|
|
#ifndef _WIN32
|
|
system("stty sane 2>/dev/null");
|
|
#endif
|
|
pxl8_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';
|
|
}
|
|
|
|
void pxl8_repl_signal_complete(pxl8_repl* repl) {
|
|
if (!repl) return;
|
|
atomic_store(&repl->cmd_complete, true);
|
|
}
|