wip repl
This commit is contained in:
parent
e862b02019
commit
0ed7fc4496
21 changed files with 1267 additions and 148 deletions
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue