about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--chesstv.tlv37
-rw-r--r--counter.tlv27
-rw-r--r--hanoi.tlv30
-rw-r--r--life.tlv42
-rw-r--r--src/lua.c208
5 files changed, 259 insertions, 85 deletions
diff --git a/chesstv.tlv b/chesstv.tlv
index b13c1b1..87ef1d4 100644
--- a/chesstv.tlv
+++ b/chesstv.tlv
@@ -1,10 +1,16 @@
 teliva_program = {
+  {
     window = [==[
 window = curses.stdscr()
 -- animation-based app
 window:nodelay(true)
 lines, cols = window:getmaxyx()]==],
-    current_game = [==[current_game = {}]==],
+  },
+  {
+    current_game = [==[
+current_game = {}]==],
+  },
+  {
     piece_glyph = [==[
 piece_glyph = {
   -- for legibility, white pieces also use unicode glyphs for black pieces
@@ -22,6 +28,8 @@ piece_glyph = {
   n = 0x265e,
   p = 0x265f,
 }]==],
+  },
+  {
     top_player = [==[
 function top_player(current_game)
   if current_game.players[1].color == "black" then
@@ -29,6 +37,8 @@ function top_player(current_game)
   end
   return current_game.players[2]
 end]==],
+  },
+  {
     bottom_player = [==[
 function bottom_player(current_game)
   if current_game.players[1].color == "white" then
@@ -36,12 +46,16 @@ function bottom_player(current_game)
   end
   return current_game.players[2]
 end]==],
+  },
+  {
     render_player = [==[
 function render_player(y, x, player)
   curses.mvaddstr(y, x, player.user.name)
   curses.addstr(" ยท ")
   curses.addstr(tostring(player.rating))
 end]==],
+  },
+  {
     render_square = [==[
 function render_square(current_game, rank, file, highlighted_squares)
   -- decide whether to highlight
@@ -62,6 +76,8 @@ function render_square(current_game, rank, file, highlighted_squares)
   curses.mvaddstr((8 - rank + 1)*3+2, file*5, "     ")
   curses.attrset(curses.A_NORMAL)
 end]==],
+  },
+  {
     render_fen_rank = [==[
 function render_fen_rank(rank, fen_rank, highlighted_squares)
   local file = 1
@@ -98,12 +114,16 @@ function render_fen_rank(rank, fen_rank, highlighted_squares)
     end
   end
 end]==],
+  },
+  {
     render_time = [==[
 function render_time(y, x, seconds)
   if seconds == nil then return end
   curses.mvaddstr(y, x, tostring(math.floor(seconds/60)))
   curses.addstr(string.format(":%02d", seconds%60))
 end]==],
+  },
+  {
     render_board = [==[
 function render_board(current_game)
 --?   curses.mvaddstr(1, 50, dump(current_game.fen))
@@ -119,6 +139,8 @@ function render_board(current_game)
   render_player(27, 5, bottom_player(current_game))
   render_time(27, 35, current_game.wc)
 end]==],
+  },
+  {
     parse_lm = [==[
 function parse_lm(move)
 --?   curses.mvaddstr(4, 50, move)
@@ -129,6 +151,8 @@ function parse_lm(move)
 --?   curses.mvaddstr(5, 50, dump({{rank1, file1}, {rank2, file2}}))
   return {from={rank=rank1, file=file1}, to={rank=rank2, file=file2}}
 end]==],
+  },
+  {
     render = [==[
 function render(chunk)
   local o = json.decode(chunk)
@@ -149,6 +173,8 @@ function render(chunk)
   render_board(current_game)
   curses.refresh()
 end]==],
+  },
+  {
     init_colors = [==[
 function init_colors()
   -- colors
@@ -168,6 +194,8 @@ function init_colors()
   curses.init_pair(7, light_piece, dark_last_moved_square)
   curses.init_pair(8, dark_piece, dark_last_moved_square)
 end]==],
+  },
+  {
     main = [==[
 function main()
   init_colors()
@@ -184,6 +212,8 @@ function main()
   }
   http.request(request)
 end]==],
+  },
+  {
     utf8 = [==[
 -- https://stackoverflow.com/questions/7983574/how-to-write-a-unicode-symbol-in-lua
 function utf8(decimal)
@@ -203,6 +233,8 @@ function utf8(decimal)
   end
   return table.concat(charbytes)
 end]==],
+  },
+  {
     split = [==[
 function split(s, pat)
   result = {}
@@ -211,6 +243,8 @@ function split(s, pat)
   end
   return result
 end]==],
+  },
+  {
     dump = [==[
 -- https://stackoverflow.com/questions/9168058/how-to-dump-a-table-to-console
 function dump(o)
@@ -225,4 +259,5 @@ function dump(o)
     return tostring(o)
   end
 end]==],
+  },
 }
diff --git a/counter.tlv b/counter.tlv
index d8b396f..566314e 100644
--- a/counter.tlv
+++ b/counter.tlv
@@ -1,7 +1,14 @@
 teliva_program = {
-    window = [==[window = curses.stdscr()]==],
-    n = [==[n = 0]==],
-    render = [==[
+  {
+  window = [==[
+window = curses.stdscr()]==],
+  },
+  {
+  n = [==[
+n = 0]==],
+  },
+  {
+  render = [==[
 function render(window)
   window:clear()
   window:attron(curses.A_BOLD)
@@ -12,15 +19,22 @@ function render(window)
   window:attroff(curses.A_BOLD)
   curses.refresh()
 end]==],
-    menu = [==[menu = {Enter="increment"}]==],
-    update = [==[
+  },
+  {
+  menu = [==[
+menu = {Enter="increment"}]==],
+  },
+  {
+  update = [==[
 function update(window)
   local key = curses.getch()
   if key == 10 then
     n = n+1
   end
 end]==],
-    main = [==[
+  },
+  {
+  main = [==[
 function main()
   for i=1,7 do
     curses.init_pair(i, 0, i)
@@ -31,4 +45,5 @@ function main()
     update(window)
   end
 end]==],
+  },
 }
diff --git a/hanoi.tlv b/hanoi.tlv
index b08b200..cc1d33d 100644
--- a/hanoi.tlv
+++ b/hanoi.tlv
@@ -1,4 +1,5 @@
 teliva_program = {
+  {
     render = [==[
 function render(window)
   window:clear()
@@ -10,16 +11,25 @@ function render(window)
   end
   curses.refresh()
 end]==],
+  },
+  {
     lines = [==[
 function lines(window)
   local lines, cols = window:getmaxyx()
   return lines
 end]==],
+  },
+  {
     pop = [==[
 function pop(array)
   return table.remove(array)
 end]==],
-    window = [==[window = curses.stdscr()]==],
+  },
+  {
+    window = [==[
+window = curses.stdscr()]==],
+  },
+  {
     render_tower = [==[
 function render_tower(window, line, col, tower_index, tower)
   window:attron(curses.A_BOLD)
@@ -39,7 +49,12 @@ function render_tower(window, line, col, tower_index, tower)
     line = line - 1
   end
 end]==],
-    tower = [==[tower = {{6, 5, 4, 3, 2}, {}, {}}]==],
+  },
+  {
+    tower = [==[
+tower = {{6, 5, 4, 3, 2}, {}, {}}]==],
+  },
+  {
     render_disk = [==[
 function render_disk(window, line, col, size)
   col = col-size+1
@@ -50,6 +65,8 @@ function render_disk(window, line, col, size)
     col = col+2
   end
 end]==],
+  },
+  {
     main = [==[
 function main()
   for i=1,7 do
@@ -62,6 +79,8 @@ function main()
   end
 end
 ]==],
+  },
+  {
     len = [==[
 function len(array)
   local result = 0
@@ -70,6 +89,8 @@ function len(array)
   end
   return result
 end]==],
+  },
+  {
     update = [==[
 function update(window)
   window:mvaddstr(lines(window)-2, 5, "tower to remove top disk from? ")
@@ -78,14 +99,19 @@ function update(window)
   local to = curses.getch() - 96
   make_move(from, to)
 end]==],
+  },
+  {
     make_move = [==[
 function make_move(from, to)
   local disk = pop(tower[from])
   table.insert(tower[to], disk)
 end]==],
+  },
+  {
     cols = [==[
 function cols(window)
   local lines, cols = window:getmaxyx()
   return cols
 end]==],
+  },
 }
diff --git a/life.tlv b/life.tlv
index 8a3f9d4..b0e5500 100644
--- a/life.tlv
+++ b/life.tlv
@@ -1,9 +1,5 @@
 teliva_program = {
-    window = [==[
-window = curses.stdscr()
--- animation-based app
-window:nodelay(true)
-lines, cols = window:getmaxyx()]==],
+  {
     grid = [==[
 -- main data structure
 grid = {}
@@ -14,6 +10,15 @@ for i=1,lines*4 do
   end
 end
 ]==],
+  },
+  {
+    window = [==[
+window = curses.stdscr()
+-- animation-based app
+window:nodelay(true)
+lines, cols = window:getmaxyx()]==],
+  },
+  {
     grid_char = [==[
 -- grab a 4x2 chunk of grid
 function grid_char(line, col)
@@ -23,6 +28,8 @@ function grid_char(line, col)
   end
   return result
 end]==],
+  },
+  {
     print_grid_char = [==[
 function print_grid_char(window, x)
   result = {}
@@ -33,6 +40,8 @@ function print_grid_char(window, x)
   end
   return result
 end]==],
+  },
+  {
     glyph = [==[
 -- look up the braille pattern corresponding to a 4x2 chunk of grid
 -- https://en.wikipedia.org/wiki/Braille_Patterns
@@ -56,6 +65,8 @@ glyph = {
   0x28b0, 0x28b1, 0x28b2, 0x28b3, 0x28b4, 0x28b5, 0x28b6, 0x28b7,   0x28f0, 0x28f1, 0x28f2, 0x28f3, 0x28f4, 0x28f5, 0x28f6, 0x28f7,
   0x28b8, 0x28b9, 0x28ba, 0x28bb, 0x28bc, 0x28bd, 0x28be, 0x28bf,   0x28f8, 0x28f9, 0x28fa, 0x28fb, 0x28fc, 0x28fd, 0x28fe, 0x28ff,
 }]==],
+  },
+  {
     utf8 = [==[
 -- https://stackoverflow.com/questions/7983574/how-to-write-a-unicode-symbol-in-lua
 function utf8(decimal)
@@ -75,6 +86,8 @@ function utf8(decimal)
   end
   return table.concat(charbytes)
 end]==],
+  },
+  {
     grid_char_to_glyph_index = [==[
 -- convert a chunk of grid into a number
 function grid_char_to_glyph_index(g)
@@ -82,6 +95,8 @@ function grid_char_to_glyph_index(g)
          g[1][2]*16 + g[2][2]*32 + g[3][2]*64 + g[4][2]*128 +
          1  -- 1-indexing
 end]==],
+  },
+  {
     render = [==[
 function render(window)
   window:clear()
@@ -93,6 +108,8 @@ function render(window)
   curses.refresh()
 end
 ]==],
+  },
+  {
     state = [==[
 function state(line, col)
   if line < 1 or line > table.getn(grid) or col < 1 or col > table.getn(grid[1]) then
@@ -100,12 +117,16 @@ function state(line, col)
   end
   return grid[line][col]
 end]==],
+  },
+  {
     num_live_neighbors = [==[
 function num_live_neighbors(line, col)
   return state(line-1, col-1) + state(line-1, col) + state(line-1, col+1) +
          state(line,   col-1) +                      state(line,   col+1) +
          state(line+1, col-1) + state(line+1, col) + state(line+1, col+1)
 end]==],
+  },
+  {
     step = [==[
 function step()
   local new_grid = {}
@@ -124,12 +145,16 @@ function step()
   end
   grid = new_grid
 end]==],
+  },
+  {
     sleep = [==[
 function sleep(a)
     local sec = tonumber(os.clock() + a);
     while (os.clock() < sec) do
     end
 end]==],
+  },
+  {
     file_exists = [==[
 function file_exists(filename)
   local f = io.open(filename, "r")
@@ -140,6 +165,8 @@ function file_exists(filename)
     return false
   end
 end]==],
+  },
+  {
     load_file = [==[
 function load_file(window, filename)
   io.input(filename)
@@ -160,6 +187,8 @@ function load_file(window, filename)
     end
   end
 end]==],
+  },
+  {
     update = [==[
 menu = {arrow="pan"}
 
@@ -198,6 +227,8 @@ function update(window, c)
     end
   end
 end]==],
+  },
+  {
     main = [==[
 function main()
   for i=1,7 do
@@ -274,4 +305,5 @@ function main()
     step()
   end
 end]==],
+  },
 }
diff --git a/src/lua.c b/src/lua.c
index 1e88b8b..460436d 100644
--- a/src/lua.c
+++ b/src/lua.c
@@ -280,27 +280,64 @@ void stack_dump (lua_State *L) {
 }
 
 
+static int binding_exists (lua_State *L, const char *name) {
+  int result = 0;
+  lua_getglobal(L, name);
+  result = !lua_isnil(L, -1);
+  lua_pop(L, 1);
+  return result;
+}
+
+
+static const char *look_up_definition (lua_State *L, const char *name) {
+  lua_getglobal(L, "teliva_program");
+  int history_array = lua_gettop(L);
+  /* iterate over mutations in teliva_program history in reverse order */
+  int history_array_size = luaL_getn(L, history_array);
+  for (int i = history_array_size; i > 0; --i) {
+    lua_rawgeti(L, history_array, i);
+    int table = lua_gettop(L);
+    /* iterate over bindings */
+    /* really we expect only one */
+    for (lua_pushnil(L); lua_next(L, table) != 0; lua_pop(L, 1)) {
+      const char* key = lua_tostring(L, -2);
+      if (strcmp(key, name) == 0)
+        return lua_tostring(L, -1);
+    }
+  }
+  lua_pop(L, 1);
+  return NULL;
+}
+
+
 char *Image_name = NULL;
 static int handle_image (lua_State *L, char **argv, int n) {
   int status;
   int narg = getargs(L, argv, n);  /* collect arguments */
   lua_setglobal(L, "arg");
-  /* parse and load file contents (teliva_program table) */
+  /* parse and load file contents (teliva_program array) */
   Image_name = argv[n];
   status = luaL_loadfile(L, Image_name);
   lua_insert(L, -(narg+1));
-  if (status != 0) {
-    return status;
-  }
+  if (status != 0) return status;
   status = docall(L, narg, 0);
   lua_getglobal(L, "teliva_program");
-  int table = lua_gettop(L);
-  /* parse and load each binding in teliva_program */
-  for (lua_pushnil(L); lua_next(L, table) != 0; lua_pop(L, 1)) {
-    const char* key = lua_tostring(L, -2);
-    const char* value = lua_tostring(L, -1);
-    status = dostring(L, value, key);
-    if (status != 0) return report(L, status);
+  int history_array = lua_gettop(L);
+  /* iterate over mutations in teliva_program history in reverse order */
+  int history_array_size = luaL_getn(L, history_array);
+  for (int i = history_array_size; i > 0; --i) {
+    lua_rawgeti(L, history_array, i);
+    int table = lua_gettop(L);
+    /* iterate over bindings */
+    /* really we expect only one */
+    for (lua_pushnil(L); lua_next(L, table) != 0; lua_pop(L, 1)) {
+      const char* key = lua_tostring(L, -2);
+      if (binding_exists(L, key))
+        continue;  // most recent binding trumps older ones
+      const char* value = lua_tostring(L, -1);
+      status = dostring(L, value, key);
+      if (status != 0) return report(L, status);
+    }
   }
   /* call main() */
   lua_getglobal(L, "main");
@@ -314,10 +351,7 @@ static int handle_image (lua_State *L, char **argv, int n) {
 char Current_definition[CURRENT_DEFINITION_LEN+1] = {0};
 void save_to_current_definition_and_editor_buffer (lua_State *L, const char *definition) {
   strncpy(Current_definition, definition, CURRENT_DEFINITION_LEN);
-  lua_getglobal(L, "teliva_program");
-  lua_getfield(L, -1, Current_definition);
-  const char *contents = lua_tostring(L, -1);
-  lua_pop(L, 1);
+  const char *contents = look_up_definition(L, Current_definition);
   FILE *out = fopen("teliva_editbuffer", "w");
   if (contents != NULL)
     fprintf(out, "%s", contents);
@@ -333,27 +367,42 @@ static void read_editor_buffer (char *out) {
 }
 
 
-/* table to update is at top of stack */
-static void update_definition (lua_State *L, const char *name, char *out) {
+static void update_definition (lua_State *L, const char *name, char *new_contents) {
+  assert(lua_gettop(L) == 0);
   lua_getglobal(L, "teliva_program");
-  lua_pushstring(L, out);
+  int history_array = 1;
+  /* create a new table containing a single binding */
+  lua_createtable(L, /*number of fields per mutation*/2, 0);
+  lua_pushstring(L, new_contents);
   assert(strlen(name) > 0);
   lua_setfield(L, -2, name);
+  /* append the new table to the history of mutations */
+  int history_array_size = luaL_getn(L, history_array);
+  ++history_array_size;
+  lua_rawseti(L, history_array, history_array_size);
   lua_settop(L, 0);
 }
 
 
 static void save_image (lua_State *L) {
   lua_getglobal(L, "teliva_program");
-  int table = lua_gettop(L);
-  FILE *out = fopen(Image_name, "w");
+  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 (lua_pushnil(L); lua_next(L, table) != 0; lua_pop(L, 1)) {
-    const char *key = lua_tostring(L, -2);
-    const char *value = lua_tostring(L, -1);
-    fprintf(out, "  %s = [==[", key);
-    fprintf(out, "%s", value);
-    fprintf(out, "]==],\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);
+      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);
@@ -434,46 +483,55 @@ int browse_image (lua_State *L) {
   clear();
   luaL_newmetatable(L, "__teliva_call_graph_depth");
   int cgt = lua_gettop(L);
-  // special-case: we don't instrument the call to main, but it's always 1
+  // special-case: we don't instrument the call to main, but it's always at depth 1
   lua_pushinteger(L, 1);
   lua_setfield(L, cgt, "main");
   // segment definitions by depth
   lua_getglobal(L, "teliva_program");
-  int t = lua_gettop(L);
+  int history_array = lua_gettop(L);
+  int history_array_size = luaL_getn(L, history_array);
 
   int y = 2;
   mvaddstr(y, 0, "data:           ");
   // first: data (non-functions) that's not the Teliva menu or curses variables
-  for (lua_pushnil(L); lua_next(L, t) != 0;) {
-    const char *definition_name = lua_tostring(L, -2);
-    lua_getglobal(L, definition_name);
-    int is_userdata = lua_isuserdata(L, -1);
-    int is_function = lua_isfunction(L, -1);
-    lua_pop(L, 1);
-    if (strcmp(definition_name, "menu") != 0  // required by all Teliva programs
-        && !is_function  // functions are not data
-        && !is_userdata  // including curses window objects
-                         // (unlikely to have an interesting definition)
-    ) {
-      browse_definition(definition_name);
+  for (int i = history_array_size; i > 0; --i) {
+    lua_rawgeti(L, history_array, i);
+    int t = lua_gettop(L);
+    for (lua_pushnil(L); lua_next(L, t) != 0;) {
+      const char *definition_name = lua_tostring(L, -2);
+      lua_getglobal(L, definition_name);
+      int is_userdata = lua_isuserdata(L, -1);
+      int is_function = lua_isfunction(L, -1);
+      lua_pop(L, 1);
+      if (strcmp(definition_name, "menu") != 0  // required by all Teliva programs
+          && !is_function  // functions are not data
+          && !is_userdata  // including curses window objects
+                           // (unlikely to have an interesting definition)
+      ) {
+        browse_definition(definition_name);
+      }
+      lua_pop(L, 1);  // value
+      // leave key on stack for next iteration
     }
-    lua_pop(L, 1);  // value
-    // leave key on stack for next iteration
   }
 
   // second: menu and other userdata
-  for (lua_pushnil(L); lua_next(L, t) != 0;) {
-    const char* definition_name = lua_tostring(L, -2);
-    lua_getglobal(L, definition_name);
-    int is_userdata = lua_isuserdata(L, -1);
-    lua_pop(L, 1);
-    if (strcmp(definition_name, "menu") == 0
-        || is_userdata  // including curses window objects
-    ) {
-      browse_definition(definition_name);
+  for (int i = history_array_size; i > 0; --i) {
+    lua_rawgeti(L, history_array, i);
+    int t = lua_gettop(L);
+    for (lua_pushnil(L); lua_next(L, t) != 0;) {
+      const char* definition_name = lua_tostring(L, -2);
+      lua_getglobal(L, definition_name);
+      int is_userdata = lua_isuserdata(L, -1);
+      lua_pop(L, 1);
+      if (strcmp(definition_name, "menu") == 0
+          || is_userdata  // including curses window objects
+      ) {
+        browse_definition(definition_name);
+      }
+      lua_pop(L, 1);  // value
+      // leave key on stack for next iteration
     }
-    lua_pop(L, 1);  // value
-    // leave key on stack for next iteration
   }
 
   // functions by level
@@ -482,32 +540,40 @@ int browse_image (lua_State *L) {
   y++;
   for (int level = 1; level < 5; ++level) {
     mvaddstr(y, 0, "                ");
+    for (int i = history_array_size; i > 0; --i) {
+      lua_rawgeti(L, history_array, i);
+      int t = lua_gettop(L);
+      for (lua_pushnil(L); lua_next(L, t) != 0;) {
+        const char* definition_name = lua_tostring(L, -2);
+        lua_getfield(L, cgt, definition_name);
+        int depth = lua_tointeger(L, -1);
+        if (depth == level)
+          browse_definition(definition_name);
+        lua_pop(L, 1);  // depth of value
+        lua_pop(L, 1);  // value
+        // leave key on stack for next iteration
+      }
+    }
+    y += 2;
+  }
+
+  // unused functions
+  mvaddstr(y, 0, "                ");
+  for (int i = history_array_size; i > 0; --i) {
+    lua_rawgeti(L, history_array, i);
+    int t = lua_gettop(L);
     for (lua_pushnil(L); lua_next(L, t) != 0;) {
       const char* definition_name = lua_tostring(L, -2);
+      lua_getglobal(L, definition_name);
+      int is_function = lua_isfunction(L, -1);
+      lua_pop(L, 1);
       lua_getfield(L, cgt, definition_name);
-      int depth = lua_tointeger(L, -1);
-      if (depth == level)
+      if (is_function && lua_isnoneornil(L, -1))
         browse_definition(definition_name);
       lua_pop(L, 1);  // depth of value
       lua_pop(L, 1);  // value
       // leave key on stack for next iteration
     }
-    y += 2;
-  }
-
-  // unused functions
-  mvaddstr(y, 0, "                ");
-  for (lua_pushnil(L); lua_next(L, t) != 0;) {
-    const char* definition_name = lua_tostring(L, -2);
-    lua_getglobal(L, definition_name);
-    int is_function = lua_isfunction(L, -1);
-    lua_pop(L, 1);
-    lua_getfield(L, cgt, definition_name);
-    if (is_function && lua_isnoneornil(L, -1))
-      browse_definition(definition_name);
-    lua_pop(L, 1);  // depth of value
-    lua_pop(L, 1);  // value
-    // leave key on stack for next iteration
   }
 
   lua_settop(L, 0);