This commit is contained in:
asrael 2025-11-01 12:39:59 -05:00
parent e862b02019
commit 0ed7fc4496
No known key found for this signature in database
GPG key ID: 2786557804DFAE24
21 changed files with 1267 additions and 148 deletions

View file

@ -1,4 +1,5 @@
#include <dirent.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
@ -23,6 +24,49 @@ struct pxl8_script {
time_t latest_mod_time;
};
#define PXL8_MAX_REPL_COMMAND_SIZE 4096
static void pxl8_script_repl_promote_locals(const char* input, char* output, size_t output_size) {
size_t i = 0;
size_t j = 0;
bool in_string = false;
bool in_comment = false;
while (input[i] && j < output_size - 1) {
if (in_comment) {
output[j++] = input[i];
if (input[i] == '\n') {
in_comment = false;
}
i++;
continue;
}
if (input[i] == ';' && !in_string) {
in_comment = true;
output[j++] = input[i++];
continue;
}
if (input[i] == '"' && (i == 0 || input[i-1] != '\\')) {
in_string = !in_string;
output[j++] = input[i++];
continue;
}
if (!in_string && input[i] == '(' &&
strncmp(&input[i], "(local ", 7) == 0) {
strncpy(&output[j], "(global ", 8);
j += 8;
i += 7;
continue;
}
output[j++] = input[i++];
}
output[j] = '\0';
}
static const char* pxl8_ffi_cdefs =
"typedef uint8_t u8;\n"
"typedef uint16_t u16;\n"
@ -70,10 +114,17 @@ static const char* pxl8_ffi_cdefs =
"void pxl8_lua_trace(const char* msg);\n"
"void pxl8_lua_warn(const char* msg);\n"
"\n"
"typedef u32 pxl8_tile;\n"
"typedef struct pxl8_tilemap pxl8_tilemap;\n"
"typedef struct pxl8_tilesheet pxl8_tilesheet;\n"
"pxl8_tilemap* pxl8_tilemap_create(u32 width, u32 height, u32 tile_size);\n"
"void pxl8_tilemap_destroy(pxl8_tilemap* tilemap);\n"
"u32 pxl8_tilemap_get_height(const pxl8_tilemap* tilemap);\n"
"pxl8_tile pxl8_tilemap_get_tile(const pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y);\n"
"u32 pxl8_tilemap_get_tile_size(const pxl8_tilemap* tilemap);\n"
"void* pxl8_tilemap_get_tile_user_data(const pxl8_tilemap* tilemap, u16 tile_id);\n"
"u32 pxl8_tilemap_get_width(const pxl8_tilemap* tilemap);\n"
"void pxl8_tilemap_set_tile_user_data(pxl8_tilemap* tilemap, u16 tile_id, void* user_data);\n"
"bool pxl8_tilemap_check_collision(const pxl8_tilemap* tilemap, i32 x, i32 y, i32 w, i32 h);\n"
"u16 pxl8_tilemap_get_tile_id(const pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y);\n"
"bool pxl8_tilemap_is_solid(const pxl8_tilemap* tilemap, u32 x, u32 y);\n"
@ -82,6 +133,7 @@ static const char* pxl8_ffi_cdefs =
"void pxl8_tilemap_set_camera(pxl8_tilemap* tilemap, i32 x, i32 y);\n"
"void pxl8_tilemap_set_tile(pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y, u16 tile_id, u8 flags);\n"
"i32 pxl8_tilemap_set_tilesheet(pxl8_tilemap* tilemap, pxl8_tilesheet* tilesheet);\n"
"i32 pxl8_tilemap_load_ase(pxl8_tilemap* tilemap, const char* filepath, u32 layer);\n"
"pxl8_tilesheet* pxl8_tilesheet_create(u32 tile_size);\n"
"void pxl8_tilesheet_destroy(pxl8_tilesheet* tilesheet);\n"
"i32 pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath, pxl8_gfx* gfx);\n"
@ -314,33 +366,40 @@ void pxl8_script_set_ui(pxl8_script* script, pxl8_ui* ui) {
}
}
static pxl8_result pxl8_script_prepare_path(pxl8_script* script, const char* filename, char** out_basename) {
char* filename_copy = strdup(filename);
static pxl8_result pxl8_script_prepare_path(pxl8_script* script, const char* filename, char* out_basename, size_t basename_size) {
char filename_copy[PATH_MAX];
strncpy(filename_copy, filename, sizeof(filename_copy) - 1);
filename_copy[sizeof(filename_copy) - 1] = '\0';
char* last_slash = strrchr(filename_copy, '/');
*out_basename = (char*)filename;
if (last_slash) {
*last_slash = '\0';
char* script_dir = realpath(filename_copy, NULL);
char* original_cwd = getcwd(NULL, 0);
if (script_dir && original_cwd) {
char script_dir[PATH_MAX];
char original_cwd[PATH_MAX];
if (realpath(filename_copy, script_dir) && getcwd(original_cwd, sizeof(original_cwd))) {
chdir(script_dir);
pxl8_script_set_cart_path(script, script_dir, original_cwd);
*out_basename = strdup(last_slash + 1);
strncpy(out_basename, last_slash + 1, basename_size - 1);
out_basename[basename_size - 1] = '\0';
} else {
strncpy(out_basename, filename, basename_size - 1);
out_basename[basename_size - 1] = '\0';
}
free(script_dir);
free(original_cwd);
} else {
strncpy(out_basename, filename, basename_size - 1);
out_basename[basename_size - 1] = '\0';
}
free(filename_copy);
return PXL8_OK;
}
pxl8_result pxl8_script_run_file(pxl8_script* script, const char* filename) {
if (!script || !script->L || !filename) return PXL8_ERROR_NULL_POINTER;
char* basename;
pxl8_script_prepare_path(script, filename, &basename);
char basename[PATH_MAX];
pxl8_script_prepare_path(script, filename, basename, sizeof(basename));
pxl8_result result = PXL8_OK;
if (luaL_dofile(script->L, basename) != 0) {
@ -351,7 +410,6 @@ pxl8_result pxl8_script_run_file(pxl8_script* script, const char* filename) {
script->last_error[0] = '\0';
}
if (basename != filename) free(basename);
return result;
}
@ -392,14 +450,13 @@ void pxl8_script_set_cart_path(pxl8_script* script, const char* cart_path, const
pxl8_result pxl8_script_run_fennel_file(pxl8_script* script, const char* filename) {
if (!script || !script->L || !filename) return PXL8_ERROR_NULL_POINTER;
char* basename;
pxl8_script_prepare_path(script, filename, &basename);
char basename[PATH_MAX];
pxl8_script_prepare_path(script, filename, basename, sizeof(basename));
lua_getglobal(script->L, "fennel");
if (lua_isnil(script->L, -1)) {
pxl8_script_set_error(script, "Fennel not loaded");
lua_pop(script->L, 1);
if (basename != filename) free(basename);
return PXL8_ERROR_SCRIPT_ERROR;
}
@ -416,14 +473,19 @@ pxl8_result pxl8_script_run_fennel_file(pxl8_script* script, const char* filenam
}
lua_pop(script->L, 1);
if (basename != filename) free(basename);
return result;
}
pxl8_result pxl8_script_eval(pxl8_script* script, const char* code) {
static pxl8_result pxl8_script_eval_internal(pxl8_script* script, const char* code, bool repl_mode) {
if (!script || !script->L || !code) return PXL8_ERROR_NULL_POINTER;
char transformed_code[PXL8_MAX_REPL_COMMAND_SIZE];
if (repl_mode) {
pxl8_script_repl_promote_locals(code, transformed_code, sizeof(transformed_code));
code = transformed_code;
}
lua_getglobal(script->L, "fennel");
if (lua_isnil(script->L, -1)) {
pxl8_script_set_error(script, "Fennel not loaded");
@ -434,17 +496,61 @@ pxl8_result pxl8_script_eval(pxl8_script* script, const char* code) {
lua_getfield(script->L, -1, "eval");
lua_pushstring(script->L, code);
if (lua_pcall(script->L, 1, 1, 0) != 0) {
pxl8_script_set_error(script, lua_tostring(script->L, -1));
lua_remove(script->L, -2);
lua_newtable(script->L);
lua_pushstring(script->L, "useMetadata");
lua_pushboolean(script->L, true);
lua_settable(script->L, -3);
if (lua_pcall(script->L, 2, 1, 0) != 0) {
const char* error = lua_tostring(script->L, -1);
if (error) {
char cleaned_error[2048];
size_t j = 0;
for (size_t i = 0; error[i] && j < sizeof(cleaned_error) - 1; i++) {
if (error[i] == '\t') {
cleaned_error[j++] = ' ';
} else {
cleaned_error[j++] = error[i];
}
}
cleaned_error[j] = '\0';
pxl8_script_set_error(script, cleaned_error);
} else {
pxl8_script_set_error(script, "Unknown error");
}
lua_pop(script->L, 2);
return PXL8_ERROR_SCRIPT_ERROR;
}
lua_remove(script->L, -2);
if (!lua_isnil(script->L, -1)) {
lua_pushvalue(script->L, -1);
lua_setglobal(script->L, "_");
lua_getglobal(script->L, "tostring");
lua_pushvalue(script->L, -2);
if (lua_pcall(script->L, 1, 1, 0) == 0) {
const char* result = lua_tostring(script->L, -1);
if (result && strlen(result) > 0) {
printf("=> %s\n", result);
fflush(stdout);
}
lua_pop(script->L, 1);
}
}
lua_pop(script->L, 2);
script->last_error[0] = '\0';
return PXL8_OK;
}
pxl8_result pxl8_script_eval(pxl8_script* script, const char* code) {
return pxl8_script_eval_internal(script, code, false);
}
pxl8_result pxl8_script_eval_repl(pxl8_script* script, const char* code) {
return pxl8_script_eval_internal(script, code, true);
}
pxl8_result pxl8_script_load_module(pxl8_script* script, const char* module_name) {
if (!script || !script->L || !module_name) return PXL8_ERROR_NULL_POINTER;
@ -567,7 +673,6 @@ pxl8_result pxl8_script_load_main(pxl8_script* script, const char* path) {
if (result == PXL8_OK) {
pxl8_info("Loaded script: %s", path);
pxl8_script_call_function(script, "init");
} else {
pxl8_warn("Failed to load script: %s", script->last_error);
}
@ -604,21 +709,33 @@ bool pxl8_script_check_reload(pxl8_script* script) {
#include <string.h>
#include <linenoise.h>
#define PXL8_MAX_REPL_COMMANDS 4096
#define PXL8_REPL_RING_BUFFER_SIZE 64
struct pxl8_script_repl_command {
char buffer[PXL8_MAX_REPL_COMMANDS];
struct pxl8_script_repl_command* next;
char buffer[PXL8_MAX_REPL_COMMAND_SIZE];
};
struct pxl8_script_repl {
pthread_t thread;
pthread_mutex_t mutex;
pxl8_script_repl_command* queue_head;
pxl8_script_repl_command* queue_tail;
pthread_cond_t cond;
pxl8_script_repl_command ring_buffer[PXL8_REPL_RING_BUFFER_SIZE];
u32 head;
u32 tail;
bool running;
bool should_quit;
bool waiting_for_eval;
char accumulator[PXL8_MAX_REPL_COMMAND_SIZE];
};
static bool pxl8_script_is_incomplete_error(const char* error) {
if (!error) return false;
return strstr(error, "expected closing delimiter") != NULL ||
strstr(error, "unexpected end of source") != NULL ||
strstr(error, "expected whitespace before") != NULL ||
strstr(error, "unexpected end") != NULL;
}
static void pxl8_script_repl_completion(const char* buf, linenoiseCompletions* lc) {
const char* fennel_keywords[] = {
"fn", "let", "var", "set", "global", "local",
@ -687,30 +804,49 @@ static void* pxl8_script_repl_stdin_thread(void* user_data) {
linenoiseSetHintsCallback(pxl8_script_repl_hints);
linenoiseHistoryLoad(history_file);
while (repl->running && (line = linenoise(">> "))) {
if (strlen(line) > 0) {
linenoiseHistoryAdd(line);
linenoiseHistorySave(history_file);
while (repl->running) {
pthread_mutex_lock(&repl->mutex);
bool in_multiline = (repl->accumulator[0] != '\0');
pthread_mutex_unlock(&repl->mutex);
pxl8_script_repl_command* cmd = (pxl8_script_repl_command*)malloc(sizeof(pxl8_script_repl_command));
if (cmd) {
strncpy(cmd->buffer, line, PXL8_MAX_REPL_COMMANDS - 1);
cmd->buffer[PXL8_MAX_REPL_COMMANDS - 1] = '\0';
cmd->next = NULL;
const char* prompt = in_multiline ? ".. " : ">> ";
line = linenoise(prompt);
pthread_mutex_lock(&repl->mutex);
if (repl->queue_tail) {
repl->queue_tail->next = cmd;
repl->queue_tail = cmd;
} else {
repl->queue_head = repl->queue_tail = cmd;
}
pthread_mutex_unlock(&repl->mutex);
if (!line) break;
if (strlen(line) > 0 || in_multiline) {
if (!in_multiline) {
linenoiseHistoryAdd(line);
linenoiseHistorySave(history_file);
}
pthread_mutex_lock(&repl->mutex);
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 next_tail = (repl->tail + 1) % PXL8_REPL_RING_BUFFER_SIZE;
if (next_tail != repl->head) {
strncpy(repl->ring_buffer[repl->tail].buffer, repl->accumulator, PXL8_MAX_REPL_COMMAND_SIZE - 1);
repl->ring_buffer[repl->tail].buffer[PXL8_MAX_REPL_COMMAND_SIZE - 1] = '\0';
repl->tail = next_tail;
repl->waiting_for_eval = true;
while (repl->waiting_for_eval && repl->running) {
pthread_cond_wait(&repl->cond, &repl->mutex);
}
}
pthread_mutex_unlock(&repl->mutex);
}
linenoiseFree(line);
}
pthread_mutex_lock(&repl->mutex);
repl->should_quit = true;
pthread_mutex_unlock(&repl->mutex);
return NULL;
}
@ -727,38 +863,41 @@ void pxl8_script_repl_destroy(pxl8_script_repl* repl) {
void pxl8_script_repl_init(pxl8_script_repl* repl) {
if (!repl) return;
repl->queue_head = NULL;
repl->queue_tail = NULL;
repl->head = 0;
repl->tail = 0;
repl->running = true;
repl->waiting_for_eval = false;
repl->accumulator[0] = '\0';
pthread_mutex_init(&repl->mutex, NULL);
pthread_cond_init(&repl->cond, NULL);
pthread_create(&repl->thread, NULL, pxl8_script_repl_stdin_thread, repl);
}
void pxl8_script_repl_shutdown(pxl8_script_repl* repl) {
if (!repl) return;
pthread_mutex_lock(&repl->mutex);
repl->running = false;
pthread_join(repl->thread, NULL);
pthread_mutex_destroy(&repl->mutex);
pthread_cond_signal(&repl->cond);
pthread_mutex_unlock(&repl->mutex);
pxl8_script_repl_command* cmd = repl->queue_head;
while (cmd) {
pxl8_script_repl_command* next = cmd->next;
free(cmd);
cmd = next;
}
pthread_cancel(repl->thread);
pthread_join(repl->thread, NULL);
system("stty sane 2>/dev/null");
pthread_cond_destroy(&repl->cond);
pthread_mutex_destroy(&repl->mutex);
}
pxl8_script_repl_command* pxl8_script_repl_pop_command(pxl8_script_repl* repl) {
if (!repl) return NULL;
pthread_mutex_lock(&repl->mutex);
pxl8_script_repl_command* cmd = repl->queue_head;
if (cmd) {
repl->queue_head = cmd->next;
if (!repl->queue_head) {
repl->queue_tail = NULL;
}
pxl8_script_repl_command* cmd = NULL;
if (repl->head != repl->tail) {
cmd = &repl->ring_buffer[repl->head];
repl->head = (repl->head + 1) % PXL8_REPL_RING_BUFFER_SIZE;
}
pthread_mutex_unlock(&repl->mutex);
return cmd;
@ -768,6 +907,34 @@ const char* pxl8_script_repl_command_buffer(pxl8_script_repl_command* cmd) {
return cmd ? cmd->buffer : NULL;
}
void pxl8_script_repl_command_free(pxl8_script_repl_command* cmd) {
free(cmd);
bool pxl8_script_repl_should_quit(pxl8_script_repl* repl) {
if (!repl) return false;
pthread_mutex_lock(&repl->mutex);
bool should_quit = repl->should_quit;
pthread_mutex_unlock(&repl->mutex);
return should_quit;
}
void pxl8_script_repl_eval_complete(pxl8_script_repl* repl) {
if (!repl) return;
pthread_mutex_lock(&repl->mutex);
repl->waiting_for_eval = false;
pthread_cond_signal(&repl->cond);
pthread_mutex_unlock(&repl->mutex);
}
void pxl8_script_repl_clear_accumulator(pxl8_script_repl* repl) {
if (!repl) return;
pthread_mutex_lock(&repl->mutex);
repl->accumulator[0] = '\0';
pthread_mutex_unlock(&repl->mutex);
}
bool pxl8_script_is_incomplete_input(pxl8_script* script) {
if (!script) return false;
return pxl8_script_is_incomplete_error(script->last_error);
}