386 lines
13 KiB
Lua
386 lines
13 KiB
Lua
# .tlv file generated by https://github.com/akkartik/teliva
|
|
# You may edit it if you are careful; however, you may see cryptic errors if you
|
|
# violate Teliva's assumptions.
|
|
#
|
|
# .tlv files are representations of Teliva programs. Teliva programs consist of
|
|
# sequences of definitions. Each definition is a table of key/value pairs. Keys
|
|
# and values are both strings.
|
|
#
|
|
# Lines in .tlv files always follow exactly one of the following forms:
|
|
# - comment lines at the top of the file starting with '#' at column 0
|
|
# - beginnings of definitions starting with '- ' at column 0, followed by a
|
|
# key/value pair
|
|
# - key/value pairs consisting of ' ' at column 0, containing either a
|
|
# spaceless value on the same line, or a multi-line value
|
|
# - multiline values indented by more than 2 spaces, starting with a '>'
|
|
#
|
|
# If these constraints are violated, Teliva may unceremoniously crash. Please
|
|
# report bugs at http://akkartik.name/contact
|
|
- __teliva_timestamp: original
|
|
str_helpers:
|
|
>-- some string helpers from http://lua-users.org/wiki/StringIndexing
|
|
>
|
|
>-- index characters using []
|
|
>getmetatable('').__index = function(str,i)
|
|
> if type(i) == 'number' then
|
|
> return str:sub(i,i)
|
|
> else
|
|
> return string[i]
|
|
> end
|
|
>end
|
|
>
|
|
>-- ranges using (), selected bytes using {}
|
|
>getmetatable('').__call = function(str,i,j)
|
|
> if type(i)~='table' then
|
|
> return str:sub(i,j)
|
|
> else
|
|
> local t={}
|
|
> for k,v in ipairs(i) do
|
|
> t[k]=str:sub(v,v)
|
|
> end
|
|
> return table.concat(t)
|
|
> end
|
|
>end
|
|
>
|
|
>-- iterate over an ordered sequence
|
|
>function q(x)
|
|
> if type(x) == 'string' then
|
|
> return x:gmatch('.')
|
|
> else
|
|
> return ipairs(x)
|
|
> end
|
|
>end
|
|
>
|
|
>-- insert within string
|
|
>function string.insert(str1, str2, pos)
|
|
> return str1:sub(1,pos)..str2..str1:sub(pos+1)
|
|
>end
|
|
>
|
|
>function string.remove(s, pos)
|
|
> return s:sub(1,pos-1)..s:sub(pos+1)
|
|
>end
|
|
>
|
|
>function string.pos(s, sub)
|
|
> return string.find(s, sub, 1, true) -- plain=true to disable regular expressions
|
|
>end
|
|
>
|
|
>-- TODO: backport utf-8 support from Lua 5.3
|
|
- __teliva_timestamp: original
|
|
check:
|
|
>function check(x, msg)
|
|
> if x then
|
|
> Window:addch('.')
|
|
> else
|
|
> print('F - '..msg)
|
|
> print(' '..str(x)..' is false/nil')
|
|
> teliva_num_test_failures = teliva_num_test_failures + 1
|
|
> -- overlay first test failure on editors
|
|
> if teliva_first_failure == nil then
|
|
> teliva_first_failure = msg
|
|
> end
|
|
> end
|
|
>end
|
|
- __teliva_timestamp: original
|
|
check_eq:
|
|
>function check_eq(x, expected, msg)
|
|
> if eq(x, expected) then
|
|
> Window:addch('.')
|
|
> else
|
|
> print('F - '..msg)
|
|
> print(' expected '..str(expected)..' but got '..str(x))
|
|
> teliva_num_test_failures = teliva_num_test_failures + 1
|
|
> -- overlay first test failure on editors
|
|
> if teliva_first_failure == nil then
|
|
> teliva_first_failure = msg
|
|
> end
|
|
> end
|
|
>end
|
|
- __teliva_timestamp: original
|
|
eq:
|
|
>function eq(a, b)
|
|
> if type(a) ~= type(b) then return false end
|
|
> if type(a) == 'table' then
|
|
> if #a ~= #b then return false end
|
|
> for k, v in pairs(a) do
|
|
> if b[k] ~= v then
|
|
> return false
|
|
> end
|
|
> end
|
|
> for k, v in pairs(b) do
|
|
> if a[k] ~= v then
|
|
> return false
|
|
> end
|
|
> end
|
|
> return true
|
|
> end
|
|
> return a == b
|
|
>end
|
|
- __teliva_timestamp: original
|
|
str:
|
|
>-- smarter tostring
|
|
>-- slow; used only for debugging
|
|
>function str(x)
|
|
> if type(x) == 'table' then
|
|
> local result = ''
|
|
> result = result..#x..'{'
|
|
> for k, v in pairs(x) do
|
|
> result = result..str(k)..'='..str(v)..', '
|
|
> end
|
|
> result = result..'}'
|
|
> return result
|
|
> elseif type(x) == 'string' then
|
|
> return '"'..x..'"'
|
|
> end
|
|
> return tostring(x)
|
|
>end
|
|
- __teliva_timestamp: original
|
|
menu:
|
|
>-- To show app-specific hotkeys in the menu bar, add hotkey/command
|
|
>-- arrays of strings to the menu array.
|
|
>menu = {}
|
|
- __teliva_timestamp: original
|
|
Window:
|
|
>Window = curses.stdscr()
|
|
- __teliva_timestamp: original
|
|
render:
|
|
>function render(window)
|
|
> window:clear()
|
|
> -- draw stuff to screen here
|
|
> window:attron(curses.A_BOLD)
|
|
> window:mvaddstr(1, 5, "example app")
|
|
> window:attrset(curses.A_NORMAL)
|
|
> for i=0,15 do
|
|
> window:attrset(curses.color_pair(i))
|
|
> window:mvaddstr(3+i, 5, "========================")
|
|
> end
|
|
> window:refresh()
|
|
>end
|
|
- __teliva_timestamp: original
|
|
menu:
|
|
>-- To show app-specific hotkeys in the menu bar, add hotkey/command
|
|
>-- arrays of strings to the menu array.
|
|
>menu = {}
|
|
- __teliva_timestamp: original
|
|
update:
|
|
>function update(window)
|
|
> local key = window:getch()
|
|
> -- process key here
|
|
>end
|
|
- __teliva_timestamp: original
|
|
init_colors:
|
|
>function init_colors()
|
|
> for i=0,7 do
|
|
> curses.init_pair(i, i, -1)
|
|
> end
|
|
> curses.init_pair(8, 7, 0)
|
|
> curses.init_pair(9, 7, 1)
|
|
> curses.init_pair(10, 7, 2)
|
|
> curses.init_pair(11, 7, 3)
|
|
> curses.init_pair(12, 7, 4)
|
|
> curses.init_pair(13, 7, 5)
|
|
> curses.init_pair(14, 7, 6)
|
|
> curses.init_pair(15, -1, 15)
|
|
>end
|
|
- __teliva_timestamp: original
|
|
main:
|
|
>function main()
|
|
> init_colors()
|
|
>
|
|
> while true do
|
|
> render(Window)
|
|
> update(Window)
|
|
> end
|
|
>end
|
|
- __teliva_timestamp: original
|
|
eval:
|
|
>function eval(x, env)
|
|
> function symeq(x, s)
|
|
> return x and x.sym == s
|
|
> end
|
|
> if x.sym then
|
|
> return lookup(env, x.sym)
|
|
> elseif atom(x) then
|
|
> return x
|
|
> -- otherwise x is a pair
|
|
> elseif symeq(x.car, 'quote') then
|
|
> return x.cdr
|
|
> elseif unary_functions[x.car.sym] then
|
|
> return eval_unary(x, env)
|
|
> elseif binary_functions[x.car.sym] then
|
|
> return eval_binary(x, env)
|
|
> -- special forms that don't always eval all their args
|
|
> elseif symeq(x.car, 'if') then
|
|
> return eval_if(x, env)
|
|
> elseif symeq(x.car.car, 'fn') then
|
|
> return eval_fn(x, env)
|
|
> elseif symeq(x.car.car, 'label') then
|
|
> return eval_label(x, env)
|
|
> end
|
|
>end
|
|
- __teliva_timestamp: original
|
|
eval_unary:
|
|
>function eval_unary(x, env)
|
|
> return unary_functions[x.car.sym](eval(x.cdr.car, env))
|
|
>end
|
|
- __teliva_timestamp: original
|
|
eval_binary:
|
|
>function eval_binary(x, env)
|
|
> return binary_functions[x.car.sym](eval(x.cdr.car, env))
|
|
>end
|
|
- __teliva_timestamp: original
|
|
unary_functions:
|
|
>-- format: lisp name = lua function that implements it
|
|
>unary_functions = {
|
|
> atom=atom,
|
|
> car=car,
|
|
> cdr=cdr,
|
|
>}
|
|
- __teliva_timestamp: original
|
|
binary_functions:
|
|
>-- format: lisp name = lua function that implements it
|
|
>binary_functions = {
|
|
> cons=cons,
|
|
> iso=iso,
|
|
>}
|
|
- __teliva_timestamp: original
|
|
lookup:
|
|
>function lookup(env, s)
|
|
> if env[s] then return env[s] end
|
|
> if env.next then return lookup(env.next, s) end
|
|
>end
|
|
- __teliva_timestamp: original
|
|
eval_if:
|
|
>function eval_if(x, env)
|
|
> -- syntax: (if check b1 b2)
|
|
> local check = x.cdr.car
|
|
> local b1 = x.cdr.cdr.car
|
|
> local b2 = x.cdr.cdr.cdr.car
|
|
> if eval(check, env) then
|
|
> return eval(b1, env)
|
|
> else
|
|
> return eval(b2, env)
|
|
> end
|
|
>end
|
|
- __teliva_timestamp: original
|
|
eval_fn:
|
|
>function eval_fn(x, env)
|
|
> -- syntax: ((fn params body*) args*)
|
|
> local callee = x.car
|
|
> local args = x.cdr
|
|
> local params = callee.cdr.car
|
|
> local body = callee.cdr.cdr
|
|
> return eval_exprs(body,
|
|
> bind_env(params, args, env))
|
|
>end
|
|
- __teliva_timestamp: original
|
|
bind_env:
|
|
>function bind_env(params, args, env)
|
|
> if params == nil then return env end
|
|
> local result = {next=env}
|
|
> while true do
|
|
> result[params.car.sym] = eval(args.car, env)
|
|
> params = params.cdr
|
|
> args = args.cdr
|
|
> if params == nil then break end
|
|
> end
|
|
> return result
|
|
>end
|
|
- __teliva_timestamp: original
|
|
eval_exprs:
|
|
>function eval_exprs(xs, env)
|
|
> local result = nil
|
|
> while xs do
|
|
> result = eval(xs.car, env)
|
|
> xs = xs.cdr
|
|
> end
|
|
> return result
|
|
>end
|
|
- __teliva_timestamp: original
|
|
eval_label:
|
|
>function eval_label(x, env)
|
|
> -- syntax: ((label f (fn params body*)) args*)
|
|
> local callee = x.car
|
|
> local args = x.cdr
|
|
> local f = callee.cdr.car
|
|
> local fn = callee.cdr.cdr.car
|
|
> return eval({car=fn, cdr=args},
|
|
> bind_env({f}, {callee}, env))
|
|
>end
|
|
- __teliva_timestamp: original
|
|
atom:
|
|
>function atom(x)
|
|
> return x == nil or x.num or x.char or x.str or x.sym
|
|
>end
|
|
- __teliva_timestamp: original
|
|
car:
|
|
>function car(x) return x.car end
|
|
- __teliva_timestamp: original
|
|
cdr:
|
|
>function cdr(x) return x.cdr end
|
|
- __teliva_timestamp: original
|
|
cons:
|
|
>function cons(x, y) return {car=x, cdr=y} end
|
|
- __teliva_timestamp: original
|
|
doc:main:
|
|
>John McCarthy's Lisp -- without the metacircularity
|
|
>If you know Lua, this version might be easier to understand.
|
|
>
|
|
>Words highlighted like [[this]] are suggestions for places to jump to using ctrl-g (see the menu below).
|
|
>You can always jump back here using ctrl-b (for 'big picture').
|
|
>
|
|
>Lisp is a programming language that manipulates objects of a few different types.
|
|
>There are a few _atomic_ types, and one type that can combine them.
|
|
>The atomic types are what you would expect: numbers, characters, strings, symbols (variables). You can add others.
|
|
>
|
|
>The way to combine them is the [[cons]] table which has just two keys: a [[car]] and a [[cdr]]. Both can hold objects, either atoms or other cons tables.
|
|
>
|
|
>We'll now build an interpreter that can run programs constructed out of cons tables.
|
|
>
|
|
>One thing we'll need for an interpreter is a symbol table (env) that maps symbols to values (objects).
|
|
>We'll just use a Lua table for this purpose, but with one tweak: a _next_ pointer that allows us to combine tables together.
|
|
>See [[lookup]] now to get a sense for how we'll use envs.
|
|
>
|
|
>Lisp programs are just cons tables and atoms nested to arbitrary depths, constructing trees. A Lisp interpreter walks the tree of code,
|
|
>performing computations. Since cons tables can point to other cons tables, the tree-walker interpreter [[eval]] is recursive.
|
|
>As the interpreter gets complex, we'll extract parts of it into their own helper functions: [[eval_unary]], [[eval_binary]], [[eval_if]], and so on.
|
|
>The helper functions contain recursive calls to [[eval]], so that [[eval]] becomes indirectly recursive, and [[eval]] together with its helpers
|
|
>is mutually recursive. I sometimes find it helpful to think of them all as just one big function.
|
|
>
|
|
>All these mutually recursive functions take the same arguments: a current expression 'x' and the symbol table 'env'.
|
|
>But really, most of the interpreter is just walking the tree of expressions. Only two functions care about the internals of 'env':
|
|
> - [[lookup]] which reads within env as we saw before
|
|
> - [[bind_env]] which creates a new _scope_ of symbols for each new function call.
|
|
>More complex Lisps add even more arguments to every. single. helper. Each arg will still only really matter to a couple of functions.
|
|
>But we still have to pass them around all over the place.
|
|
>
|
|
>Hopefully this quick overview will help you get a sense for this codebase.
|
|
>
|
|
>Here's a reference list of eval helpers: [[eval_unary]], [[eval_binary]], [[eval_if]], [[eval_fn]], [[eval_exprs]], [[eval_label]]
|
|
>More complex Lisps with more features will likely add helpers for lumpy bits of the language.
|
|
>Here's a list of primitives implemented in Lua: [[atom]], [[car]], [[cdr]], [[cons]], [[iso]] (for 'isomorphic'; comparing trees all the way down to the leaves)
|
|
>Here's a list of _constructors_ for creating objects of different types: [[num]], [[char]], [[str]], [[sym]] (and of course [[cons]])
|
|
>I should probably add more primitives for operating on numbers, characters and strings..
|
|
- __teliva_timestamp: original
|
|
iso:
|
|
>function iso(x, y)
|
|
> if x == nil then return y == nil end
|
|
> local done={}
|
|
> -- watch out for the rare cyclical expression
|
|
> if done[x] then return done[x] == y end
|
|
> done[x] = y
|
|
> if atom(x) then
|
|
> if not atom(y) then return nil end
|
|
> for k, v in pairs(x) do
|
|
> if y[k] ~= v then return nil end
|
|
> end
|
|
> return true
|
|
> end
|
|
> for k, v in pairs(x) do
|
|
> if not iso(y[k], v) then return nil end
|
|
> end
|
|
> for k, v in pairs(y) do
|
|
> if not iso(x[k], v) then return nil end
|
|
> end
|
|
> return true
|
|
>end
|