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 (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 + 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 (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 + 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); + 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);