about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--src/lua.c41
-rw-r--r--src/tlv.c104
-rw-r--r--src/x.tlv12
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
5dd0b83dabff9'>^
75013dc7 ^
67bb838c ^
a808a661 ^
bba8d293 ^
2a64495f ^
5e449699 ^
fc486c60 ^
c5450bd1 ^
7b04e507 ^
87db0130 ^
dd4a4145 ^
8f2f1767 ^
db1721dd ^
aea5cf92 ^
b624bd94 ^
69d1220f ^
bd8ef764 ^
0128bee7 ^
6f43de0a ^




fca1fc4f ^
f70ee6b2 ^
0db4c9b2 ^
b2d63ef5 ^
291ca616 ^
d994d0d6 ^
01c89bb5 ^
66c5bb93 ^
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85