From d5038fe51424971c0d7cd81336de19be5f85b7c6 Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Sat, 11 Dec 2021 09:37:23 -0800 Subject: snapshot: writing working? This is a complete mess. I want to abstract reading multiline strings behind a function, but the lookahead requirements for that are quite stringent. What's a reasonable abstraction here? --- src/lua.c | 41 +++++-------------------- src/tlv.c | 104 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- src/x.tlv | 12 ++++---- 3 files changed, 112 insertions(+), 45 deletions(-) diff --git a/src/lua.c b/src/lua.c index eb16b51..62441f7 100644 --- a/src/lua.c +++ b/src/lua.c @@ -307,13 +307,15 @@ int load_definitions(lua_State *L) { char *Image_name = NULL; -void load_tlv (lua_State *L, char *filename); +extern void load_tlv (lua_State *L, char *filename); static int handle_image (lua_State *L, char **argv, int n) { int status; /* TODO: pass args in */ /* parse and load file contents (teliva_program array) */ Image_name = argv[n]; load_tlv(L, Image_name); +//? save_tlv(L, Image_name); // manual test; should always return identical result, modulo key order +//? exit(1); status = load_definitions(L); if (status != 0) return 0; /* call main() */ @@ -379,41 +381,12 @@ static void update_definition (lua_State *L, const char *name, char *new_content } -static void save_image (lua_State *L) { - lua_getglobal(L, "teliva_program"); - int history_array = lua_gettop(L); - int history_array_size = luaL_getn(L, history_array); - FILE *out = fopen(Image_name, "w"); - fprintf(out, "teliva_program = {\n"); - for (int i = 1; i <= history_array_size; ++i) { - lua_rawgeti(L, history_array, i); - int table = lua_gettop(L); - fprintf(out, " {\n"); - for (lua_pushnil(L); lua_next(L, table) != 0; lua_pop(L, 1)) { - const char* key = lua_tostring(L, -2); - if (strcmp(key, "__teliva_undo") == 0) { - fprintf(out, " %s = %ld,\n", key, lua_tointeger(L, -1)); - continue; - } - const char* value = lua_tostring(L, -1); - fprintf(out, " %s = [==[\n", key); - fprintf(out, "%s", value); - fprintf(out, "]==],\n"); - } - fprintf(out, " },\n"); - lua_pop(L, 1); - } - fprintf(out, "}\n"); - fclose(out); - lua_pop(L, 1); -} - - +extern void save_tlv (lua_State *L, char *filename); int load_editor_buffer_to_current_definition_in_image(lua_State *L) { char new_contents[8192] = {0}; read_editor_buffer(new_contents); update_definition(L, Current_definition, new_contents); - save_image(L); + save_tlv(L, Image_name); /* reload binding */ return luaL_loadbuffer(L, new_contents, strlen(new_contents), Current_definition) || docall(L, 0, 1); @@ -651,12 +624,12 @@ void recent_changes_view (lua_State *L) { /* TODO: go hotkey is misleading. edits will not be persisted until you return to recent changes */ edit(L, "teliva_editor_buffer"); load_note_from_editor_buffer(L, cursor); - save_image(L); + save_tlv(L, Image_name); break; case CTRL_U: if (cursor < history_array_size) { add_undo_event(L, cursor); - save_image(L); + save_tlv(L, Image_name); } break; } diff --git a/src/tlv.c b/src/tlv.c index 00966b9..adf21c3 100644 --- a/src/tlv.c +++ b/src/tlv.c @@ -7,22 +7,37 @@ #include "lauxlib.h" /* If you encounter assertion failures in this file and _didn't_ manually edit - * it, lease report the .tlv file that caused them: http://akkartik.name/contact. */ + * it, lease report the .tlv file that caused them: http://akkartik.name/contact. + * + * Manually edited files can have cryptic errors. Teliva's first priority is + * to be secure, so it requires a fairly rigid file format and errors out if + * things are even slightly amiss. */ -void teliva_load_multiline_string(lua_State* L, FILE* in, char* line, int capacity) { +/* This code is surprisingly hairy. Estimate of buffer overflows: 2. */ + +static void teliva_load_multiline_string(lua_State* L, FILE* in, char* line, int capacity) { luaL_Buffer b; luaL_buffinit(L, &b); int expected_indent = -1; while (1) { if (feof(in)) break; + char c = fgetc(in); +//? printf("^%d$\n", (int)c); + ungetc(c, in); + if (c != ' ') { + /* new definition; signal end to caller without reading a new line */ + strcpy(line, "-\n"); + break; + } memset(line, '\0', capacity); if (fgets(line, capacity, in) == NULL) break; /* eof */ +//? printf("ml: %s", line); int max = strlen(line); assert(line[max-1] == '\n'); int indent = 0; while (indent < max-1 && line[indent] == ' ') ++indent; - if (line[indent] != '>') break; + if (line[indent] != '>') break; /* new key/value pair in definition */ if (expected_indent == -1) expected_indent = indent; else @@ -35,7 +50,8 @@ void teliva_load_multiline_string(lua_State* L, FILE* in, char* line, int capaci } /* leave a single table on stack containing the next top-level definition from the file */ -void teliva_load_definition(lua_State* L, FILE* in) { +static void teliva_load_definition(lua_State* L, FILE* in) { +//? printf("== new definition\n"); lua_newtable(L); int def_idx = lua_gettop(L); char line[1024] = {'\0'}; @@ -49,6 +65,7 @@ void teliva_load_definition(lua_State* L, FILE* in) { do { assert(line[0] == '-' || line[0] == ' '); assert(line[1] == ' '); +//? printf("l: %s", line); /* key/value pair always indented at 0, never empty, unambiguously not a multiline string */ char key[512] = {'\0'}; char value[1024] = {'\0'}; @@ -62,18 +79,33 @@ void teliva_load_definition(lua_State* L, FILE* in) { assert(key[strlen(key)-1] == ':'); key[strlen(key)-1] = '\0'; lua_pushstring(L, key); +//? printf("value: %s$\n", value); if (value[0] != '\0') { +//? printf("single-line\n"); lua_pushstring(L, value); /* value string on same line */ - assert(fgets(line, 1024, in)); + char c = fgetc(in); + ungetc(c, in); + if (c == '-') { + strcpy(line, "-\n"); + } + else { + assert(fgets(line, 1024, in)); +//? printf("new line: %s", line); + } } else { +//? printf("multi-line\n"); teliva_load_multiline_string(L, in, line, 1024); /* load from later lines */ +//? printf("new line: %s", line); } +//? printf("adding key: %s$\n", lua_tostring(L, -2)); +//? printf("adding value: %s$\n", lua_tostring(L, -1)); lua_settable(L, def_idx); } while (line[0] == ' '); } void load_tlv(lua_State* L, char* filename) { + endwin(); lua_newtable(L); int history_array = lua_gettop(L); FILE* in = fopen(filename, "r"); @@ -85,3 +117,65 @@ void load_tlv(lua_State* L, char* filename) { fclose(in); lua_setglobal(L, "teliva_program"); } + +static void emit_multiline_string(FILE* out, const char* value) { + fprintf(out, " >"); + for (const char* curr = value; *curr != '\0'; ++curr) { + if (*curr == '\n' && *(curr+1) != '\0') + fprintf(out, "\n >"); + else + fprintf(out, "%c", *curr); + } +} + +void save_tlv(lua_State* L, char* filename) { +//? printf("SAVE\n"); + lua_getglobal(L, "teliva_program"); + int history_array = lua_gettop(L); + int history_array_size = luaL_getn(L, history_array); + FILE *out = fopen(filename, "w"); + fprintf(out, "# .tlv file generated by https://github.com/akkartik/teliva\n"); + fprintf(out, "# You may edit it if you are careful; however, you may see cryptic errors if you\n"); + fprintf(out, "# violate Teliva's assumptions.\n"); + fprintf(out, "#\n"); + fprintf(out, "# .tlv files are representations of Teliva programs. Teliva programs consist of\n"); + fprintf(out, "# sequences of definitions. Each definition is a table of key/value pairs. Keys\n"); + fprintf(out, "# and values are both strings.\n"); + fprintf(out, "#\n"); + fprintf(out, "# Lines in .tlv files always follow exactly one of the following forms:\n"); + fprintf(out, "# - comment lines at the top of the file starting with '#' at column 0\n"); + fprintf(out, "# - beginnings of definitions starting with '- ' at column 0, followed by a\n"); + fprintf(out, "# key/value pair\n"); + fprintf(out, "# - key/value pairs consisting of ' ' at column 0, containing either a\n"); + fprintf(out, "# spaceless value on the same line, or a multi-line value\n"); + fprintf(out, "# - multiline values indented by more than 2 spaces, starting with a '>'\n"); + fprintf(out, "#\n"); + fprintf(out, "# If these constraints are violated, Teliva may unceremoniously crash. Please\n"); + fprintf(out, "# report bugs at http://akkartik.name/contact\n"); + for (int i = 1; i <= history_array_size; ++i) { + lua_rawgeti(L, history_array, i); + int table = lua_gettop(L); + int first = 1; + for (lua_pushnil(L); lua_next(L, table) != 0; lua_pop(L, 1)) { + if (first) fprintf(out, "- "); + else fprintf(out, " "); + first = 0; + const char* key = lua_tostring(L, -2); + if (strcmp(key, "__teliva_undo") == 0) { + fprintf(out, "%s: %ld\n", key, lua_tointeger(L, -1)); + continue; + } + const char* value = lua_tostring(L, -1); + if (strchr(value, ' ') || strchr(value, '\n')) { + fprintf(out, "%s:\n", key); + emit_multiline_string(out, value); + } + else { + fprintf(out, "%s: %s\n", key, value); + } + } + lua_pop(L, 1); + } + fclose(out); + lua_pop(L, 1); +} diff --git a/src/x.tlv b/src/x.tlv index a2b8334..c7f5247 100644 --- a/src/x.tlv +++ b/src/x.tlv @@ -1,10 +1,10 @@ -- __teliva_timestamp: original +- __teliva_timestamp: foo1 window: >window = curses.stdscr() -- __teliva_timestamp: original +- __teliva_timestamp: foo2 n: >n = 0 -- __teliva_timestamp: original +- __teliva_timestamp: foo3 render: >function render(window) > window:clear() @@ -17,10 +17,10 @@ > curses.refresh() >end __teliva_note: foo -- __teliva_timestamp: original +- __teliva_timestamp: foo4 menu: >menu = {Enter="increment"} -- __teliva_timestamp: original +- __teliva_timestamp: foo5 update: >function update(window) > local key = curses.getch() @@ -28,7 +28,7 @@ > n = n+1 > end >end -- __teliva_timestamp: original +- __teliva_timestamp: foo6 main: >function main() > for i=1,7 do -- cgit 1.4.1-2-gfad0