From 6478e6b149196395e2abb4fcb35cc11978d9b26a Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Thu, 27 Jan 2022 08:30:50 -0800 Subject: [PATCH] incomplete lisp interpreter app I'm not sure what I'm doing here just yet. This is just an experiment of the editing experience. The .tlv app doesn't actually do anything yet. --- lisp.lua | 231 +++++++++++++++++++ lisp.tlv | 676 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 907 insertions(+) create mode 100644 lisp.lua create mode 100644 lisp.tlv diff --git a/lisp.lua b/lisp.lua new file mode 100644 index 0000000..08e0015 --- /dev/null +++ b/lisp.lua @@ -0,0 +1,231 @@ +-- atom types: +-- nil +-- true +-- {num=3.4} +-- {char='a'} +-- {str='bc'} +-- {sym='foo'} +-- non-atom type: +-- {car={num=3.4}, cdr=nil} +-- +-- should {} mean anything special? currently just '(nil) +function atom(x) + return x == nil or x.num or x.char or x.str or x.sym +end + +function car(x) return x.car end +function cdr(x) return x.cdr end +function cons(x, y) return {car=x, cdr=y} end + +function iso(x, y) + if x == nil then return y == nil end + local done={} + 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 + +-- primitives; feel free to add more +-- format: lisp name = lua function that implements it +unary_functions = { + atom=atom, + car=car, + cdr=cdr, +} + +binary_functions = { + cons=cons, + iso=iso, +} + +function lookup(env, s) + if env[s] then return env[s] end + if env.next then return lookup(env.next, s) end +end + +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 + +function eval_unary(x, env) + return unary_functions[x.car.sym](eval(x.cdr.car, env)) +end + +function eval_binary(x, env) + return binary_functions[x.car.sym](eval(x.cdr.car, env), + eval(x.cdr.cdr.car, env)) +end + +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 + +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 + +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 + +function eval_exprs(xs, env) + local result = nil + while xs do + result = eval(xs.car, env) + xs = xs.cdr + end + return result +end + +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 + +-- testing +function num(n) return {num=n} end +function char(c) return {char=c} end +function str(s) return {str=s} end +function sym(s) return {sym=s} end +function list(...) + -- gotcha: no element in arg can be nil; that short-circuits the ipairs below + local result = nil + local curr = nil + for _, x in ipairs({...}) do + if curr == nil then + result = {car=x} + curr = result + else + curr.cdr = {car=x} + curr = curr.cdr + end + end + return result +end + +function p(x) + p2(x) + print() +end + +function p2(x) + if x == nil then + io.write('nil') + elseif x == true then + io.write('true') + elseif x.num then + io.write(x.num) + elseif x.char then + io.write("\\"..x.char) + elseif x.str then + io.write('"'..x.str..'"') + elseif x.sym then + io.write(x.sym) + elseif x.cdr == nil then + io.write('(') + p2(x.car) + io.write(')') + elseif atom(x.cdr) then + io.write('(') + p2(x.car) + io.write(' . ') + p2(x.cdr) + io.write(')') + else + io.write('(') + while true do + p2(x.car) + x = x.cdr + if x == nil then break end + if atom(x) then + io.write(' . ') + p2(x) + break + end + io.write(' ') + end + io.write(')') + end +end + +x = {num=3.4} +p(x) + +p(cons(x, nil)) +p(list(x)) + +p(iso(cons(x, nil), cons(x, nil))) +p(iso(list(x), list(x))) +p(iso(list(x, x), list(x))) +p(iso(list(x, x), list(x, x))) +p(iso(x, cons(x, nil))) + +p (list(sym("cons"), num(42), num(1))) +p(eval(list(sym("cons"), num(42), num(1)), {})) + +-- ((fn () 42)) => 42 +-- can't use list here because of the gotcha above +assert(iso(eval(cons(cons(sym('fn'), cons(nil, cons(num(42))))), {}), num(42))) +-- ((fn (a) (cons a 1)) 42) => '(42 . 1) +assert(iso(eval(cons(cons(sym('fn'), cons(cons(sym('a')), cons(cons(sym('cons'), cons(sym('a'), cons(num(1))))))), cons(num(42)))), cons(num(42), num(1)))) diff --git a/lisp.tlv b/lisp.tlv new file mode 100644 index 0000000..0d211bd --- /dev/null +++ b/lisp.tlv @@ -0,0 +1,676 @@ +# .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 string.sub(str,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 string.sub(str,i,j) + > else + > local t={} + > for k,v in ipairs(i) do + > t[k]=string.sub(str,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 + > + >-- TODO: backport utf-8 support from Lua 5.3 +- __teliva_timestamp: original + debugy: + >debugy = 5 +- __teliva_timestamp: original + dbg: + >-- helper for debug by print; overlay debug information towards the right + >-- reset debugy every time you refresh screen + >function dbg(window, s) + > local oldy = 0 + > local oldx = 0 + > oldy, oldx = window:getyx() + > window:mvaddstr(debugy, 60, s) + > debugy = debugy+1 + > window:mvaddstr(oldy, oldx, '') + >end +- __teliva_timestamp: original + check_eq: + >function check_eq(x, expected, msg) + > if x == expected then + > curses.addch('.') + > else + > print('F - '..msg) + > print(' expected '..tostring(expected)..' but got '..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 + map: + >-- only for arrays + >function map(l, f) + > result = {} + > for _, x in ipairs(l) do + > table.insert(result, f(x)) + > end + > return result + >end +- __teliva_timestamp: original + reduce: + >-- only for arrays + >function reduce(l, f, init) + > result = init + > for _, x in ipairs(l) do + > result = f(result, x) + > end + > return result + >end +- __teliva_timestamp: original + filter: + >-- only for arrays + >function filter(l, f) + > result = {} + > for _, x in ipairs(l) do + > if f(x) then + > table.insert(result, x) + > end + > end + > return result + >end +- __teliva_timestamp: original + find_index: + >function find_index(arr, x) + > for n, y in ipairs(arr) do + > if x == y then + > return n + > end + > end + >end +- __teliva_timestamp: original + trim: + >function trim(s) + > return s:gsub('^%s*', ''):gsub('%s*$', '') + >end +- __teliva_timestamp: original + split: + >function split(s, d) + > result = {} + > for match in (s..d):gmatch("(.-)"..d) do + > table.insert(result, match); + > end + > return result + >end +- __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 + > curses.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 = curses.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 +- main: + >function main() + > init_colors() + > + > while true do + > render(window) + > update(window) + > end + >end + __teliva_timestamp: original +- doc:main: + >foo bar + __teliva_timestamp: + >Thu Jan 27 00:36:56 2022 +- doc:main: + >foo bar baz + __teliva_timestamp: + >Thu Jan 27 00:39:33 2022 +- 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 + __teliva_timestamp: + >Thu Jan 27 00:47:51 2022 +- 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'). + __teliva_timestamp: + >Thu Jan 27 00:55:11 2022 +- 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'). + > + > + __teliva_timestamp: + >Thu Jan 27 00:55:19 2022 +- 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. + __teliva_timestamp: + >Thu Jan 27 00:56:25 2022 +- 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. + __teliva_timestamp: + >Thu Jan 27 00:58:06 2022 +- 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]]. + __teliva_timestamp: + >Thu Jan 27 00:58:46 2022 +- 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]]. + > + >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. + __teliva_timestamp: + >Thu Jan 27 01:01:56 2022 +- 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]]. + > + >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 + __teliva_timestamp: + >Thu Jan 27 01:03:45 2022 +- 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. The tree-walker interpreter [[eval]] is recursive, since trees are self-similar structures. + __teliva_timestamp: + >Thu Jan 27 01:07:24 2022 +- 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. + __teliva_timestamp: + >Thu Jan 27 01:16:25 2022 +- __teliva_timestamp: + >Thu Jan 27 01:17:25 2022 + 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: + >Thu Jan 27 01:17:25 2022 + eval_unary: + >function eval_unary(x, env) + > return unary_functions[x.car.sym](eval(x.cdr.car, env)) + >end +- __teliva_timestamp: + >Thu Jan 27 01:17:25 2022 + eval_binary: + >function eval_binary(x, env) + > return binary_functions[x.car.sym](eval(x.cdr.car, env)) + >end +- __teliva_timestamp: + >Thu Jan 27 01:17:25 2022 + unary_functions: + >-- format: lisp name = lua function that implements it + >unary_functions = { + > atom=atom, + > car=car, + > cdr=cdr, + >} +- __teliva_timestamp: + >Thu Jan 27 01:17:25 2022 + binary_functions: + >-- format: lisp name = lua function that implements it + >binary_functions = { + > cons=cons, + > iso=iso, + >} +- __teliva_timestamp: + >Thu Jan 27 01:17:25 2022 + 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: + >Thu Jan 27 01:17:25 2022 + 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: + >Thu Jan 27 01:17:25 2022 + 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: + >Thu Jan 27 01:17:25 2022 + 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: + >Thu Jan 27 01:17:25 2022 + 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: + >Thu Jan 27 01:17:25 2022 + >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: + >Thu Jan 27 01:24:51 2022 + atom: + >function atom(x) + > return x == nil or x.num or x.char or x.str or x.sym + >end +- car: + >function car(x) return x.car end + __teliva_timestamp: + >Thu Jan 27 01:25:03 2022 +- cdr: + >function cdr(x) return x.cdr end + __teliva_timestamp: + >Thu Jan 27 01:25:10 2022 +- __teliva_timestamp: + >Thu Jan 27 01:25:21 2022 + cons: + >function cons(x, y) return {car=x, cdr=y} end +- __teliva_timestamp: + >Thu Jan 27 01:25:21 2022 + iso: + >function iso(x, y) + > if x == nil then return y == nil end + > local done={} + > 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 +- 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. + > + >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. + > + __teliva_timestamp: + >Thu Jan 27 01:29:01 2022 +- 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. + > + >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: + >Thu Jan 27 01:34:18 2022 +- 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 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: + >Thu Jan 27 01:36:44 2022 +- __teliva_timestamp: + >Thu Jan 27 01:41:06 2022 + 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