You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
385 lines
13 KiB
385 lines
13 KiB
# .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
|
|
|