diff options
author | Kartik K. Agaram <vc@akkartik.com> | 2021-12-11 09:37:23 -0800 |
---|---|---|
committer | Kartik K. Agaram <vc@akkartik.com> | 2021-12-11 09:37:23 -0800 |
commit | d5038fe51424971c0d7cd81336de19be5f85b7c6 (patch) | |
tree | d592871d79cfa7bf52a0a2fc8d7e420a0dbb8c80 /src/tlv.c | |
parent | 052c5501ac8b27cab747c16fd10a20aa44ac57c9 (diff) | |
download | teliva-d5038fe51424971c0d7cd81336de19be5f85b7c6.tar.gz |
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?
Diffstat (limited to 'src/tlv.c')
-rw-r--r-- | src/tlv.c | 104 |
1 files changed, 99 insertions, 5 deletions
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); +} |