1 # .tlv file generated by https://github.com/akkartik/teliva
2 # You may edit it if you are careful; however, you may see cryptic errors if you
3 # violate Teliva's assumptions.
5 # .tlv files are representations of Teliva programs. Teliva programs consist of
6 # sequences of definitions. Each definition is a table of key/value pairs. Keys
7 # and values are both strings.
9 # Lines in .tlv files always follow exactly one of the following forms:
10 # - comment lines at the top of the file starting with '#' at column 0
11 # - beginnings of definitions starting with '- ' at column 0, followed by a
13 # - key/value pairs consisting of ' ' at column 0, containing either a
14 # spaceless value on the same line, or a multi-line value
15 # - multiline values indented by more than 2 spaces, starting with a '>'
17 # If these constraints are violated, Teliva may unceremoniously crash. Please
18 # report bugs at http://akkartik.name/contact
19 - __teliva_timestamp: original
21 >-- some string helpers from http://lua-users.org/wiki/StringIndexing
23 >-- index characters using []
24 >getmetatable('').__index = function(str,i)
25 > if type(i) == 'number' then
32 >-- ranges using (), selected bytes using {}
33 >getmetatable('').__call = function(str,i,j)
34 > if type(i)~='table' then
38 > for k,v in ipairs(i) do
41 > return table.concat(t)
45 >-- iterate over an ordered sequence
47 > if type(x) == 'string' then
48 > return x:gmatch('.')
54 >-- insert within string
55 >function string.insert(str1, str2, pos)
56 > return str1:sub(1,pos)..str2..str1:sub(pos+1)
59 >function string.remove(s, pos)
60 > return s:sub(1,pos-1)..s:sub(pos+1)
63 >function string.pos(s, sub)
64 > return string.find(s, sub, 1, true) -- plain=true to disable regular expressions
67 >-- TODO: backport utf-8 support from Lua 5.3
68 - __teliva_timestamp: original
71 - __teliva_timestamp: original
73 >-- helper for debug by print; overlay debug information towards the right
74 >-- reset debugy every time you refresh screen
75 >function dbg(window, s)
78 > oldy, oldx = window:getyx()
79 > window:mvaddstr(debugy, 60, s)
81 > window:mvaddstr(oldy, oldx, '')
83 - __teliva_timestamp: original
85 >function check(x, msg)
90 > print(' '..str(x)..' is false/nil')
91 > teliva_num_test_failures = teliva_num_test_failures + 1
92 > -- overlay first test failure on editors
93 > if teliva_first_failure == nil then
94 > teliva_first_failure = msg
98 - __teliva_timestamp: original
100 >function check_eq(x, expected, msg)
101 > if eq(x, expected) then
105 > print(' expected '..str(expected)..' but got '..str(x))
106 > teliva_num_test_failures = teliva_num_test_failures + 1
107 > -- overlay first test failure on editors
108 > if teliva_first_failure == nil then
109 > teliva_first_failure = msg
113 - __teliva_timestamp: original
116 > if type(a) ~= type(b) then return false end
117 > if type(a) == 'table' then
118 > if #a ~= #b then return false end
119 > for k, v in pairs(a) do
124 > for k, v in pairs(b) do
133 - __teliva_timestamp: original
136 >-- slow; used only for debugging
138 > if type(x) == 'table' then
140 > result = result..#x..'{'
141 > for k, v in pairs(x) do
142 > result = result..str(k)..'='..str(v)..', '
144 > result = result..'}'
146 > elseif type(x) == 'string' then
151 - __teliva_timestamp: original
153 >function find_index(arr, x)
154 > for n, y in ipairs(arr) do
160 - __teliva_timestamp: original
163 > return s:gsub('^%s*', ''):gsub('%s*$', '')
165 - __teliva_timestamp: original
167 >function split(s, d)
169 > for match in (s..d):gmatch("(.-)"..d) do
170 > table.insert(result, match);
174 - __teliva_timestamp: original
179 > for _, x in ipairs(l) do
180 > table.insert(result, f(x))
184 - __teliva_timestamp: original
187 >function reduce(l, f, init)
189 > for _, x in ipairs(l) do
190 > result = f(result, x)
194 - __teliva_timestamp: original
196 >function filter(h, f)
198 > for k, v in pairs(h) do
205 - __teliva_timestamp: original
208 >function ifilter(l, f)
210 > for _, x in ipairs(l) do
212 > table.insert(result, x)
217 - __teliva_timestamp: original
219 >function sort_letters(s)
222 > table.insert(tmp, s[i])
226 > for _, c in pairs(tmp) do
232 >function test_sort_letters(s)
233 > check_eq(sort_letters(''), '', 'test_sort_letters: empty')
234 > check_eq(sort_letters('ba'), 'ab', 'test_sort_letters: non-empty')
235 > check_eq(sort_letters('abba'), 'aabb', 'test_sort_letters: duplicates')
237 - __teliva_timestamp: original
239 >-- TODO: handle unicode
240 >function count_letters(s)
244 > if result[c] == nil then
247 > result[c] = result[c] + 1
252 - __teliva_timestamp: original
254 >-- turn an array of elements into a map from elements to their frequency
255 >-- analogous to count_letters for non-strings
258 > for i, v in ipairs(a) do
259 > if result[v] == nil then
262 > result[v] = result[v] + 1
267 - __teliva_timestamp: original
269 >function union(a, b)
270 > for k, v in pairs(b) do
275 - __teliva_timestamp: original
278 >function subtract(a, b)
279 > for k, v in pairs(b) do
284 - __teliva_timestamp: original
286 >-- universal quantifier on sets
288 > for k, v in pairs(s) do
289 > if not f(k, v) then
295 - __teliva_timestamp: original
297 >-- turn a set into an array
299 >function to_array(h)
301 > for k, _ in pairs(h) do
302 > table.insert(result, k)
306 - __teliva_timestamp: original
308 >-- concatenate list 'elems' into 'l', modifying 'l' in the process
309 >function append(l, elems)
311 > table.insert(l, elems[i])
314 - __teliva_timestamp: original
316 >-- concatenate list 'elems' into the start of 'l', modifying 'l' in the process
317 >function prepend(l, elems)
319 > table.insert(l, i, elems[i])
322 - __teliva_timestamp: original
324 >function all_but(x, idx)
325 > if type(x) == 'table' then
327 > for i, elem in ipairs(x) do
329 > table.insert(result,elem)
333 > elseif type(x) == 'string' then
334 > if idx < 1 then return x:sub(1) end
335 > return x:sub(1, idx-1) .. x:sub(idx+1)
337 > error('all_but: unsupported type '..type(x))
341 >function test_all_but()
342 > check_eq(all_but('', 0), '', 'all_but: empty')
343 > check_eq(all_but('abc', 0), 'abc', 'all_but: invalid low index')
344 > check_eq(all_but('abc', 4), 'abc', 'all_but: invalid high index')
345 > check_eq(all_but('abc', 1), 'bc', 'all_but: first index')
346 > check_eq(all_but('abc', 3), 'ab', 'all_but: final index')
347 > check_eq(all_but('abc', 2), 'ac', 'all_but: middle index')
349 - __teliva_timestamp: original
353 > for i, elem in ipairs(l) do
354 > result[elem] = true
358 - __teliva_timestamp: original
360 >function set_eq(l1, l2)
361 > return eq(set(l1), set(l2))
364 >function test_set_eq()
365 > check(set_eq({1}, {1}), 'set_eq: identical')
366 > check(not set_eq({1, 2}, {1, 3}), 'set_eq: different')
367 > check(set_eq({1, 2}, {2, 1}), 'set_eq: order')
368 > check(set_eq({1, 2, 2}, {2, 1}), 'set_eq: duplicates')
370 - __teliva_timestamp: original
372 >function clear(lines)
373 > while #lines > 0 do
374 > table.remove(lines)
377 - __teliva_timestamp: original
379 >function zap(target, src)
381 > append(target, src)
383 - __teliva_timestamp: original
385 >-- memoized version of factorial
386 >-- doesn't memoize recursive calls, but may be good enough
387 >mfactorial = memo1(factorial)
388 - __teliva_timestamp: original
390 >function factorial(n)
397 - __teliva_timestamp: original
399 >-- a higher-order function that takes a function of a single arg
400 >-- (that never returns nil)
401 >-- and returns a memoized version of it
405 > if memo[x] == nil then
412 >-- mfactorial doesn't seem noticeably faster
413 >function test_memo1()
415 > check_eq(mfactorial(i), factorial(i), 'memo1 over factorial: '..str(i))
418 - __teliva_timestamp: original
420 >-- number of permutations of n distinct objects, taken r at a time
421 >function num_permutations(n, r)
422 > return factorial(n)/factorial(n-r)
425 >-- mfactorial doesn't seem noticeably faster
426 >function test_memo1()
429 > check_eq(num_permutations(i, j), mfactorial(i)/mfactorial(i-j), 'num_permutations memoizes: '..str(i)..'P'..str(j))
433 - __teliva_timestamp: original
435 >-- To show app-specific hotkeys in the menu bar, add hotkey/command
436 >-- arrays of strings to the menu array.
438 - __teliva_timestamp: original
440 >Window = curses.stdscr()
441 - __teliva_timestamp: original
443 >-- constructor for fake screen and window
444 >-- call it like this:
445 >-- local w = window{
447 >-- scr=scr{h=5, w=4},
449 >-- eventually it'll do everything a real ncurses window can
453 > h.__index = function(table, key)
454 > return rawget(h, key)
456 > h.attrset = function(self, x)
459 > h.attron = function(self, x)
460 > -- currently same as attrset since Lua 5.1 doesn't have bitwise operators
461 > -- doesn't support multiple attrs at once
462 >-- local old = self.scr.attrs
463 >-- self.scr.attrs = old|x
466 > h.attroff = function(self, x)
467 > -- currently borked since Lua 5.1 doesn't have bitwise operators
468 > -- doesn't support multiple attrs at once
469 >-- local old = self.scr.attrs
470 >-- self.scr.attrs = old & (~x)
471 > self.scr.attrs = curses.A_NORMAL
473 > h.getch = function(self)
474 > local c = table.remove(h.kbd, 1)
475 > if c == nil then return c end
476 > return string.byte(c) -- for verisimilitude with ncurses
478 > h.addch = function(self, c)
479 > local scr = self.scr
481 > scr.cursy = scr.cursy+1
485 > if scr.cursy <= scr.h then
486 > scr[scr.cursy][scr.cursx] = {data=c, attrs=scr.attrs}
487 > scr.cursx = scr.cursx+1
488 > if scr.cursx > scr.w then
489 > scr.cursy = scr.cursy+1
494 > h.addstr = function(self, s)
499 > h.mvaddch = function(self, y, x, c)
504 > h.mvaddstr = function(self, y, x, s)
509 > h.clear = function(self)
510 > clear_scr(self.scr)
512 > h.refresh = function(self)
517 - __teliva_timestamp: original
521 > for i=1,keys:len() do
522 > table.insert(result, keys[i])
526 - __teliva_timestamp: original
534 - __teliva_timestamp: original
536 >function clear_scr(props)
542 > props[y][x] = {data=' ', attrs=curses.A_NORMAL}
547 - __teliva_timestamp: original
549 >function check_screen(window, contents, message)
551 > for i=1,contents:len() do
552 > check_eq(window.scr[y][x].data, contents[i], message..'/'..y..','..x)
554 > if x > window.scr.w then
561 >-- putting it all together, an example test of both keyboard and screen
562 >function test_check_screen()
575 > local b = w:getch()
576 > if b == nil then break end
577 > w:mvaddstr(y, 1, lines[string.char(b)])
580 > check_screen(w, '345 '..
583 > 'test_check_screen')
585 - __teliva_timestamp: original
587 >function check_reverse(window, contents, message)
589 > for i=1,contents:len() do
590 > if contents[i] ~= ' ' then
591 > -- hacky version while we're without bitwise operators on Lua 5.1
592 >-- check(window.scr[y][x].attrs & curses.A_REVERSE, message..'/'..y..','..x)
593 > check_eq(window.scr[y][x].attrs, curses.A_REVERSE, message..'/'..y..','..x)
595 > -- hacky version while we're without bitwise operators on Lua 5.1
596 >-- check(window.scr[y][x].attrs & (~curses.A_REVERSE), message..'/'..y..','..x)
597 > check(window.scr[y][x].attrs ~= curses.A_REVERSE, message..'/'..y..','..x)
600 > if x > window.scr.w then
606 - __teliva_timestamp: original
608 >function check_bold(window, contents, message)
610 > for i=1,contents:len() do
611 > if contents[i] ~= ' ' then
612 > -- hacky version while we're without bitwise operators on Lua 5.1
613 >-- check(window.scr[y][x].attrs & curses.A_BOLD, message..'/'..y..','..x)
614 > check_eq(window.scr[y][x].attrs, curses.A_BOLD, message..'/'..y..','..x)
616 > -- hacky version while we're without bitwise operators on Lua 5.1
617 >-- check(window.scr[y][x].attrs & (~curses.A_BOLD), message..'/'..y..','..x)
618 > check(window.scr[y][x].attrs ~= curses.A_BOLD, message..'/'..y..','..x)
621 > if x > window.scr.w then
627 - __teliva_timestamp: original
629 >-- check which parts of a screen have the given color_pair
630 >function check_color(window, cp, contents, message)
632 > for i=1,contents:len() do
633 > if contents[i] ~= ' ' then
634 > -- hacky version while we're without bitwise operators on Lua 5.1
635 >-- check(window.scr[y][x].attrs & curses.color_pair(cp), message..'/'..y..','..x)
636 > check_eq(window.scr[y][x].attrs, curses.color_pair(cp), message..'/'..y..','..x)
638 > -- hacky version while we're without bitwise operators on Lua 5.1
639 >-- check(window.scr[y][x].attrs & (~curses.A_BOLD), message..'/'..y..','..x)
640 > check(window.scr[y][x].attrs ~= curses.color_pair(cp), message..'/'..y..','..x)
643 > if x > window.scr.w then
649 - __teliva_timestamp: original
651 >-- horizontal separator
652 >function sep(window)
653 > local y, _ = window:getyx()
654 > window:mvaddstr(y+1, 0, '')
655 > local _, cols = window:getmaxyx()
660 - __teliva_timestamp: original
662 >function render(window)
664 > -- draw stuff to screen here
665 > window:attron(curses.A_BOLD)
666 > window:mvaddstr(1, 5, "example app")
667 > window:attrset(curses.A_NORMAL)
669 > window:attrset(curses.color_pair(i))
670 > window:mvaddstr(3+i, 5, "========================")
674 - __teliva_timestamp: original
676 >function update(window)
677 > local key = window:getch()
678 > -- process key here
680 - __teliva_timestamp: original
682 >A REPL for queries about a graph in a .dot file.
683 - __teliva_timestamp: original
688 > print('restart this app with the name of a .dot file')
690 > while true do Window:getch(); end
692 > Graph = read_dot_file(arg[1])
699 - __teliva_timestamp: original
702 - __teliva_timestamp: original
704 >function read_dot_file(filename)
706 > local infile = start_reading(nil, filename)
708 > local chars = graphviz_buffered_reader(infile)
709 > local tokens = graphviz_tokenizer(chars)
710 > parse_graph(tokens, graph)
714 - __teliva_timestamp: original
715 graphviz_buffered_reader:
716 >-- a stream of characters that can peek up to two characters ahead at a time
717 >-- returns nil when there's nothing left to read
718 >function graphviz_buffered_reader(infile)
721 > peek = infile.read(1),
722 > peek2 = infile.read(1),
723 > read = function(self)
724 > local result = self.peek
725 > self.peek = self.peek2
726 > self.peek2 = self.infile.read(1)
731 - __teliva_timestamp: original
733 >-- a stream of tokens that can peek up to one token at a time
734 >-- returns nil when there's nothing left to read
735 >function graphviz_tokenizer(chars)
738 > peek = function(self)
739 > if not self.buffer then
740 > self.buffer = self:next_token()
744 > read = function(self)
746 > if self.buffer then
747 > result = self.buffer
750 > result = self:next_token()
754 > next_token = function(self)
755 > self:skip_whitespace_and_comments()
756 > local c = self.chars.peek
757 > if c == nil then return nil end
758 > if string.pos('/*;,', c) then
759 > -- should be skipped as comments
760 > error('unexpected character '..c)
761 > elseif string.pos('[]{}():=', c) then
762 > -- single-char tokens
763 > return self.chars:read()
764 > elseif c == '"' then
765 > return self:string()
766 > elseif c == '<' then
767 > error('html strings are not implemented yet')
768 > elseif c == '-' then
769 > if self.chars.peek2 == '-' or self.chars.peek2 == '>' then
770 > return self:edgeop()
772 > return self:numeral()
774 > elseif string.pos('.0123456789', c) then
775 > return self:numeral()
776 > elseif string.match(c, '[%a_]') then
777 > return self:identifier()
779 > error('unexpected character '..str(c))
782 > skip_whitespace_and_comments = function(self)
784 > local c = self.chars.peek
786 > break -- end of chars
787 > elseif string.match(c, '%s') then
789 > elseif string.pos(',;', c) then
791 > elseif c == '#' then
792 > self.chars:read() -- skip
793 > while self.chars:read() ~= '\n' do end
794 > elseif c == '/' then
795 > local c2 = self.chars.peek2
797 > self.chars:read() -- skip '/'
798 > self.chars:read() -- skip '*'
800 > if self.chars.peek == '*' and self.chars.peek2 == '/' then
801 > self.chars:read() -- skip '*'
802 > self.chars:read() -- skip '/'
806 > elseif c2 == '/' then
807 > self.chars:read() -- skip '/'
808 > self.chars:read() -- skip '/'
809 > while self.chars:read() ~= '\n' do end
811 > error('unexpected character after "/": '..c)
818 > string = function(self)
819 > assert(self.chars.peek == '"')
820 > local result = self.chars:read()
822 > local c = self.chars.peek
824 > error('unterminated string literal')
826 > result = result..self.chars:read()
828 > result = result..self.chars:read() -- unconditionally read next char
829 > elseif c == '"' then
835 > numeral = function(self)
838 > local c = self.chars.peek
841 > elseif string.pos('-.0123456789', c) then
842 > result = result..self.chars:read()
847 > if string.match(self.chars.peek, '%w') then
848 > error('invalid character after numeral '..result)
852 > identifier = function(self)
855 > local c = self.chars.peek
857 > error('unterminated string literal')
858 > elseif string.match(c, '[%w_]') then
859 > result = result..self.chars:read()
866 > edgeop = function(self)
867 > return self.chars:read()..self.chars:read()
872 >function check_tokenizer(stream, expected, msg)
873 > local infile = fake_file_stream(stream)
874 > local chars = graphviz_buffered_reader(infile)
875 > local tokens = graphviz_tokenizer(chars)
876 > check_eq(tokens:read(), expected, msg)
879 >function test_graphviz_tokenizer()
880 > check_tokenizer('123', '123', 'tokenizer: numeral')
881 > check_tokenizer(' 123', '123', 'tokenizer: skips whitespace')
882 > check_tokenizer('123 124', '123', 'tokenizer: numeral')
883 > check_tokenizer('-12.3 124', '-12.3', 'tokenizer: numeral')
884 > check_tokenizer('a123 124', 'a123', 'tokenizer: identifier')
885 > check_tokenizer('"abc def" 124', '"abc def"', 'tokenizer: string')
886 > check_tokenizer('', nil, 'tokenizer: eof')
888 - __teliva_timestamp: original
890 >-- https://graphviz.org/doc/info/lang.html
891 >function parse_graph(tokens, graph)
892 > local t = tokens:read()
893 > if t == 'strict' then
896 > if t == 'graph' then
897 > error('undirected graphs not supported just yet')
898 > elseif t == 'digraph' then
899 > return parse_directed_graph(tokens, graph)
901 > error('parse_graph: unexpected token '..t)
904 - __teliva_timestamp:
905 >Fri Mar 18 16:52:39 2022
907 >function skip_attr_list(tokens)
909 > local tok = tokens:read()
911 > error('unterminated attr list; looked for "]" in vain')
913 > if tok == ']' then break end
916 - __teliva_timestamp:
917 >Fri Mar 18 17:01:49 2022
918 parse_directed_graph:
919 >function parse_directed_graph(tokens, graph)
920 > tokens:read() -- skip name
921 > assert(tokens:read() == '{')
923 > if tokens:peek() == nil then
924 > error('file not terminated with "}"')
926 > if tokens:peek() == '}' then break end
927 > local tok1 = tokens:read()
928 > if tok1 == '[' then
929 > skip_attr_list(tokens)
931 > local tok2 = tokens:read()
932 > if tok2 == '[' then
934 > skip_attr_list(tokens)
935 > -- otherwise ignore node declarations;
936 > -- we'll assume the graph is fully connected
937 > -- and we can focus on just edges
938 > elseif tok2 == '->' then
940 > local tok3 = tokens:read()
941 > if graph[tok1] == nil then
944 > graph[tok1][tok3] = true
945 > elseif tok2 == '--' then
946 > error('unexpected token "--" in digraph; edges should be directed using "->"')
947 > elseif tok2 == '=' then
952 > error('unexpected token '..tok2)
956 > assert(tokens:read() == '}')
958 - __teliva_timestamp:
959 >Fri Mar 18 17:21:16 2022
961 >-- The focus is a set of nodes we're constantly running
962 >-- certain queries against.
964 - __teliva_timestamp:
965 >Fri Mar 18 17:21:40 2022
967 >-- The set of edges parsed from the given .dot file.
969 - __teliva_timestamp:
970 >Fri Mar 18 17:29:25 2022
972 >function render_focus(window)
973 > window:attrset(curses.A_BOLD)
974 > window:mvaddstr(5, 1, 'focus: ')
975 > window:attrset(curses.A_NORMAL)
976 > for _, node in ipairs(Focus) do
977 > window:addstr(node)
981 > window:mvaddstr(8, 0, '')
982 > local lines, cols = window:getmaxyx()
987 - __teliva_timestamp:
988 >Fri Mar 18 17:30:11 2022
989 render_queries_on_focus:
990 >function render_queries_on_focus(window)
991 > window:attrset(curses.A_BOLD)
992 > window:mvaddstr(10, 1, '')
995 - __teliva_timestamp:
996 >Fri Mar 18 17:30:39 2022
998 >function render(window)
1000 > render_basic_stats(window)
1001 > render_focus(window)
1002 > render_queries_on_focus(window)
1005 - __teliva_timestamp:
1006 >Fri Mar 18 17:35:20 2022
1008 >function sources(Graph)
1009 > local is_target = {}
1010 > for source, targets in pairs(Graph) do
1011 > for target, _ in pairs(targets) do
1012 > is_target[target] = true
1016 > for source, _ in pairs(Graph) do
1017 > if not is_target[source] then
1018 > table.insert(result, source)
1023 - __teliva_timestamp:
1024 >Fri Mar 18 17:35:57 2022
1026 >function render_basic_stats(window)
1027 > window:attrset(curses.A_BOLD)
1028 > window:mvaddstr(1, 1, 'sources: ')
1029 > window:attrset(curses.A_NORMAL)
1030 > local sources = sources(Graph)
1031 > for _, node in ipairs(sources) do
1032 > window:addstr(node)
1033 > window:addstr(' ')
1035 > window:mvaddstr(3, 0, '')
1036 > local lines, cols = window:getmaxyx()
1038 > window:addstr('_')
1041 - __teliva_timestamp:
1042 >Fri Mar 18 17:51:18 2022
1047 > print('restart this app with the name of a .dot file')
1049 > while true do Window:getch(); end
1051 > for _, filename in ipairs(arg) do
1052 > read_dot_file(filename, Graph)
1060 - __teliva_timestamp:
1061 >Fri Mar 18 17:51:32 2022
1063 >function read_dot_file(filename, graph)
1064 > local infile = start_reading(nil, filename)
1066 > local chars = graphviz_buffered_reader(infile)
1067 > local tokens = graphviz_tokenizer(chars)
1068 > parse_graph(tokens, graph)
1071 - __teliva_timestamp:
1072 >Fri Mar 18 18:59:24 2022
1076 > for k, v in pairs(h) do
1081 - __teliva_timestamp:
1082 >Fri Mar 18 18:59:24 2022
1084 >function num_nodes(Graph)
1086 > for k, v in pairs(Graph) do
1088 > for k, v in pairs(v) do
1093 > for k, v in pairs(nodes) do
1098 - __teliva_timestamp:
1099 >Fri Mar 18 19:00:19 2022
1101 >function render_basic_stats(window)
1102 > window:attrset(curses.A_BOLD)
1103 > window:mvaddstr(1, 1, 'sources: ')
1104 > window:attrset(curses.A_NORMAL)
1105 > local sources = sources(Graph)
1106 > for _, node in ipairs(sources) do
1107 > window:addstr(node)
1108 > window:addstr(' ')
1110 > window:attrset(curses.A_BOLD)
1111 > window:addstr('size: ')
1112 > window:attrset(curses.A_NORMAL)
1113 > window:addstr(tostring(num_nodes(Graph)))
1114 > window:addstr(' nodes')
1115 > window:mvaddstr(3, 0, '')
1116 > local lines, cols = window:getmaxyx()
1118 > window:addstr('_')
1121 - __teliva_timestamp:
1122 >Fri Mar 18 19:01:49 2022
1127 > print('restart this app with the name of a .dot file')
1129 > while true do Window:getch(); end
1131 > for _, filename in ipairs(arg) do
1132 > read_dot_file(filename, Graph)
1134 > Focus = sources(Graph)
1141 - __teliva_timestamp:
1142 >Fri Mar 18 19:09:56 2022
1144 >function reachable(graph, node)
1145 > local reached = {}
1146 > local todo = {node}
1147 > while #todo > 0 do
1148 > local curr = table.remove(todo)
1149 > if reached[curr] == nil then
1150 > reached[curr] = true
1151 > local targets = graph[curr]
1153 > for target, _ in pairs(graph[curr]) do
1154 > table.insert(todo, target)
1161 - __teliva_timestamp:
1162 >Fri Mar 18 20:27:16 2022
1164 >function bold(window, text)
1165 > window:attrset(curses.A_BOLD)
1166 > window:addstr(text)
1167 > window:attrset(curses.A_NORMAL)
1169 - __teliva_timestamp:
1170 >Fri Mar 18 20:30:39 2022
1171 render_queries_on_focus:
1172 >function render_queries_on_focus(window)
1173 > render_reachable_sets(window)
1175 - __teliva_timestamp:
1176 >Fri Mar 18 20:30:39 2022
1177 render_reachable_sets:
1178 >function render_reachable_sets(window)
1180 > local needed_by = {}
1181 > for _, node in ipairs(Focus) do
1182 > deps[node] = reachable(Graph, node)
1183 > for dep, _ in pairs(deps[node]) do
1184 > if needed_by[dep] == nil then
1185 > needed_by[dep] = {}
1187 > append(needed_by[dep], {node})
1190 > for k, v in ipairs(needed_by) do
1193 > window:mvaddstr(10, 0, '')
1194 > local sets = {Focus} -- queue
1196 > while #sets > 0 do
1197 > local from_nodes = table.remove(sets, 1)
1198 > if #from_nodes == 0 then break end
1199 > table.sort(from_nodes)
1200 > local key = table.concat(from_nodes)
1201 > if done[key] == nil then
1203 > local y, x = window:getyx()
1204 > window:mvaddstr(y+2, 0, '')
1205 > window:attrset(curses.A_BOLD)
1206 > render_list(window, from_nodes)
1207 > window:attrset(curses.A_NORMAL)
1208 > window:addstr(' -> ')
1209 > render_set(window, filter(needed_by, function(node, users) return set_eq(users, from_nodes) end))
1210 > for i, elem in ipairs(from_nodes) do
1211 > table.insert(sets, all_but(from_nodes, i))
1216 - __teliva_timestamp:
1217 >Fri Mar 18 20:32:18 2022
1219 >function render_list(window, l)
1220 > window:addstr('{')
1221 > for i, node in ipairs(l) do
1222 > if i > 1 then window:addstr(' ') end
1223 > window:addstr(node)
1225 > window:addstr('}')
1227 - __teliva_timestamp:
1228 >Fri Mar 18 20:32:18 2022
1230 >function render_set(window, h)
1231 > window:addstr('(')
1232 > window:addstr(count(h))
1233 > window:addstr(') ')
1234 > for node, _ in pairs(h) do
1235 > window:addstr(node)
1236 > window:addstr(' ')
1239 - __teliva_timestamp:
1240 >Sat Mar 19 09:19:10 2022
1245 > print('restart this app with the name of a .dot file')
1247 > while true do Window:getch(); end
1249 > for _, filename in ipairs(arg) do
1250 > read_dot_file(filename, Graph)
1252 > Focus = sources(Graph)
1253 > Nodes = toposort(Graph)
1260 - __teliva_timestamp:
1261 >Sat Mar 19 09:32:33 2022
1263 >function nodes(graph)
1265 > for n, deps in pairs(graph) do
1267 > for n, _ in pairs(deps) do
1273 - __teliva_timestamp:
1274 >Sat Mar 19 16:27:32 2022
1276 >-- stable sort of nodes in a graph
1277 >-- nodes always occur before all their dependencies
1278 >-- disconnected nodes are in alphabetical order
1279 >function toposort(graph)
1280 > -- non-map variables are arrays
1281 > -- result = leaves in graph
1282 > -- candidates = non-leaves
1284 > local resultMap = {}
1285 > local candidatesMap = nodes(graph)
1286 > local leavesMap = filter(candidatesMap, function(k, v) return graph[k] == nil end)
1287 > local leaves = to_array(leavesMap)
1288 > table.sort(leaves)
1289 > union(resultMap, leavesMap)
1290 > prepend(result, leaves)
1291 > subtract(candidatesMap, leavesMap)
1293 > function in_result(x, _) return resultMap[x] end
1294 > function all_deps_in_result(k, _) return all(graph[k], in_result) end
1296 > local oldcount = count(candidatesMap)
1297 > if oldcount == 0 then break end
1298 > local inducteesMap = filter(candidatesMap, all_deps_in_result)
1299 > local inductees = to_array(inducteesMap)
1300 > table.sort(inductees)
1301 > union(resultMap, inducteesMap)
1302 > prepend(result, inductees)
1303 > subtract(candidatesMap, inducteesMap)
1304 > if oldcount == count(candidatesMap) then
1305 > error('toposort: graph is not connected')
1310 - __teliva_timestamp:
1311 >Sat Mar 19 16:32:24 2022
1313 >function render_focus(window)
1314 > local y, _ = window:getyx()
1315 > window:mvaddstr(y+1, 0, '')
1316 > bold(window, 'focus: ')
1317 > for _, node in ipairs(Focus) do
1318 > window:addstr(node)
1319 > window:addstr(' ')
1323 - __teliva_timestamp:
1324 >Sat Mar 19 16:33:19 2022
1326 >function render_basic_stats(window)
1327 > bold(window, tostring(#Nodes)..' nodes: ')
1328 > for i, node in ipairs(Nodes) do
1329 > window:attrset(curses.A_REVERSE)
1331 > window:attrset(curses.A_NORMAL)
1332 > window:addstr(' ')
1333 > window:addstr(node)
1334 > window:addstr(' ')
1338 - __teliva_timestamp:
1339 >Sat Mar 19 16:35:34 2022
1340 render_reachable_sets:
1341 >function render_reachable_sets(window)
1343 > local needed_by = {}
1344 > for _, node in ipairs(Focus) do
1345 > deps[node] = reachable(Graph, node)
1346 > for dep, _ in pairs(deps[node]) do
1347 > if needed_by[dep] == nil then
1348 > needed_by[dep] = {}
1350 > append(needed_by[dep], {node})
1353 > for k, v in ipairs(needed_by) do
1356 > local sets = {Focus} -- queue
1358 > while #sets > 0 do
1359 > local from_nodes = table.remove(sets, 1)
1360 > if #from_nodes == 0 then break end
1361 > table.sort(from_nodes)
1362 > local key = table.concat(from_nodes)
1363 > if done[key] == nil then
1365 > local y, x = window:getyx()
1366 > window:mvaddstr(y+2, 0, '')
1367 > window:attrset(curses.A_BOLD)
1368 > render_list(window, from_nodes)
1369 > window:attrset(curses.A_NORMAL)
1370 > window:addstr(' -> ')
1371 > render_set(window, filter(needed_by, function(node, users) return set_eq(users, from_nodes) end))
1372 > for i, elem in ipairs(from_nodes) do
1373 > table.insert(sets, all_but(from_nodes, i))
1378 - __teliva_timestamp:
1379 >Sat Mar 19 21:05:05 2022
1381 >-- stable sort of nodes in a graph
1382 >-- nodes always occur before all their dependencies
1383 >-- disconnected nodes are in alphabetical order
1384 >function toposort(graph)
1385 > -- non-map variables are arrays
1386 > -- result = leaves in graph
1387 > -- candidates = non-leaves
1388 > local inResultMap = {}
1389 > local candidatesMap = nodes(graph)
1390 > local leavesMap = filter(candidatesMap, function(k, v) return graph[k] == nil end)
1391 > local leaves = to_array(leavesMap)
1392 > table.sort(leaves)
1393 > union(inResultMap, leavesMap)
1394 > local result = {leaves}
1395 > subtract(candidatesMap, leavesMap)
1397 > function in_result(x, _) return inResultMap[x] end
1398 > function all_deps_in_result(k, _) return all(graph[k], in_result) end
1400 > local oldcount = count(candidatesMap)
1401 > if oldcount == 0 then break end
1402 > local inducteesMap = filter(candidatesMap, all_deps_in_result)
1403 > local inductees = to_array(inducteesMap)
1404 > table.sort(inductees)
1405 > union(inResultMap, inducteesMap)
1406 > table.insert(result, 1, inductees)
1407 > subtract(candidatesMap, inducteesMap)
1408 > if oldcount == count(candidatesMap) then
1409 > error('toposort: graph is not connected')
1414 - __teliva_timestamp:
1415 >Sat Mar 19 21:05:57 2022
1417 >function render_basic_stats(window)
1418 > bold(window, tostring(#Nodes)..' nodes:')
1420 > for _, stratum in ipairs(Nodes) do
1421 > window:addstr('\n ')
1422 > for _, node in ipairs(stratum) do
1423 > window:attrset(curses.A_REVERSE)
1425 > window:attrset(curses.A_NORMAL)
1426 > window:addstr(' ')
1427 > window:addstr(node)
1428 > window:addstr(' ')