about summary refs log tree commit diff stats
path: root/src/tlv.c
diff options
context:
space:
mode:
authorKartik K. Agaram <vc@akkartik.com>2021-12-11 09:37:23 -0800
committerKartik K. Agaram <vc@akkartik.com>2021-12-11 09:37:23 -0800
commitd5038fe51424971c0d7cd81336de19be5f85b7c6 (patch)
treed592871d79cfa7bf52a0a2fc8d7e420a0dbb8c80 /src/tlv.c
parent052c5501ac8b27cab747c16fd10a20aa44ac57c9 (diff)
downloadteliva-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.c104
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);
+}