use markdown syntax for images
[teliva.git] / lisp.tlv
bloba064adf1249cb9530085e1f8efba6327b2c709dd
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
12 #   key/value pair
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
20   str_helpers:
21     >-- some string helpers from http://lua-users.org/wiki/StringIndexing
22     >
23     >-- index characters using []
24     >getmetatable('').__index = function(str,i)
25     >  if type(i) == 'number' then
26     >    return str:sub(i,i)
27     >  else
28     >    return string[i]
29     >  end
30     >end
31     >
32     >-- ranges using (), selected bytes using {}
33     >getmetatable('').__call = function(str,i,j)
34     >  if type(i)~='table' then
35     >    return str:sub(i,j)
36     >  else
37     >    local t={}
38     >    for k,v in ipairs(i) do
39     >      t[k]=str:sub(v,v)
40     >    end
41     >    return table.concat(t)
42     >  end
43     >end
44     >
45     >-- iterate over an ordered sequence
46     >function q(x)
47     >  if type(x) == 'string' then
48     >    return x:gmatch('.')
49     >  else
50     >    return ipairs(x)
51     >  end
52     >end
53     >
54     >-- insert within string
55     >function string.insert(str1, str2, pos)
56     >  return str1:sub(1,pos)..str2..str1:sub(pos+1)
57     >end
58     >
59     >function string.remove(s, pos)
60     >  return s:sub(1,pos-1)..s:sub(pos+1)
61     >end
62     >
63     >function string.pos(s, sub)
64     >  return string.find(s, sub, 1, true)  -- plain=true to disable regular expressions
65     >end
66     >
67     >-- TODO: backport utf-8 support from Lua 5.3
68 - __teliva_timestamp: original
69   check:
70     >function check(x, msg)
71     >  if x then
72     >    Window:addch('.')
73     >  else
74     >    print('F - '..msg)
75     >    print('  '..str(x)..' is false/nil')
76     >    teliva_num_test_failures = teliva_num_test_failures + 1
77     >    -- overlay first test failure on editors
78     >    if teliva_first_failure == nil then
79     >      teliva_first_failure = msg
80     >    end
81     >  end
82     >end
83 - __teliva_timestamp: original
84   check_eq:
85     >function check_eq(x, expected, msg)
86     >  if eq(x, expected) then
87     >    Window:addch('.')
88     >  else
89     >    print('F - '..msg)
90     >    print('  expected '..str(expected)..' but got '..str(x))
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
95     >    end
96     >  end
97     >end
98 - __teliva_timestamp: original
99   eq:
100     >function eq(a, b)
101     >  if type(a) ~= type(b) then return false end
102     >  if type(a) == 'table' then
103     >    if #a ~= #b then return false end
104     >    for k, v in pairs(a) do
105     >      if b[k] ~= v then
106     >        return false
107     >      end
108     >    end
109     >    for k, v in pairs(b) do
110     >      if a[k] ~= v then
111     >        return false
112     >      end
113     >    end
114     >    return true
115     >  end
116     >  return a == b
117     >end
118 - __teliva_timestamp: original
119   str:
120     >-- smarter tostring
121     >-- slow; used only for debugging
122     >function str(x)
123     >  if type(x) == 'table' then
124     >    local result = ''
125     >    result = result..#x..'{'
126     >    for k, v in pairs(x) do
127     >      result = result..str(k)..'='..str(v)..', '
128     >    end
129     >    result = result..'}'
130     >    return result
131     >  elseif type(x) == 'string' then
132     >    return '"'..x..'"'
133     >  end
134     >  return tostring(x)
135     >end
136 - __teliva_timestamp: original
137   menu:
138     >-- To show app-specific hotkeys in the menu bar, add hotkey/command
139     >-- arrays of strings to the menu array.
140     >menu = {}
141 - __teliva_timestamp: original
142   Window:
143     >Window = curses.stdscr()
144 - __teliva_timestamp: original
145   render:
146     >function render(window)
147     >  window:clear()
148     >  -- draw stuff to screen here
149     >  window:attron(curses.A_BOLD)
150     >  window:mvaddstr(1, 5, "example app")
151     >  window:attrset(curses.A_NORMAL)
152     >  for i=0,15 do
153     >    window:attrset(curses.color_pair(i))
154     >    window:mvaddstr(3+i, 5, "========================")
155     >  end
156     >  window:refresh()
157     >end
158 - __teliva_timestamp: original
159   menu:
160     >-- To show app-specific hotkeys in the menu bar, add hotkey/command
161     >-- arrays of strings to the menu array.
162     >menu = {}
163 - __teliva_timestamp: original
164   update:
165     >function update(window)
166     >  local key = window:getch()
167     >  -- process key here
168     >end
169 - __teliva_timestamp: original
170   init_colors:
171     >function init_colors()
172     >  for i=0,7 do
173     >    curses.init_pair(i, i, -1)
174     >  end
175     >  curses.init_pair(8, 7, 0)
176     >  curses.init_pair(9, 7, 1)
177     >  curses.init_pair(10, 7, 2)
178     >  curses.init_pair(11, 7, 3)
179     >  curses.init_pair(12, 7, 4)
180     >  curses.init_pair(13, 7, 5)
181     >  curses.init_pair(14, 7, 6)
182     >  curses.init_pair(15, -1, 15)
183     >end
184 - __teliva_timestamp: original
185   main:
186     >function main()
187     >  init_colors()
188     >
189     >  while true do
190     >    render(Window)
191     >    update(Window)
192     >  end
193     >end
194 - __teliva_timestamp: original
195   eval:
196     >function eval(x, env)
197     >  function symeq(x, s)
198     >    return x and x.sym == s
199     >  end
200     >  if x.sym then
201     >    return lookup(env, x.sym)
202     >  elseif atom(x) then
203     >    return x
204     >  -- otherwise x is a pair
205     >  elseif symeq(x.car, 'quote') then
206     >    return x.cdr
207     >  elseif unary_functions[x.car.sym] then
208     >    return eval_unary(x, env)
209     >  elseif binary_functions[x.car.sym] then
210     >    return eval_binary(x, env)
211     >  -- special forms that don't always eval all their args
212     >  elseif symeq(x.car, 'if') then
213     >    return eval_if(x, env)
214     >  elseif symeq(x.car.car, 'fn') then
215     >    return eval_fn(x, env)
216     >  elseif symeq(x.car.car, 'label') then
217     >    return eval_label(x, env)
218     >  end
219     >end
220 - __teliva_timestamp: original
221   eval_unary:
222     >function eval_unary(x, env)
223     >  return unary_functions[x.car.sym](eval(x.cdr.car, env))
224     >end
225 - __teliva_timestamp: original
226   eval_binary:
227     >function eval_binary(x, env)
228     >  return binary_functions[x.car.sym](eval(x.cdr.car, env))
229     >end
230 - __teliva_timestamp: original
231   unary_functions:
232     >-- format: lisp name = lua function that implements it
233     >unary_functions = {
234     >  atom=atom,
235     >  car=car,
236     >  cdr=cdr,
237     >}
238 - __teliva_timestamp: original
239   binary_functions:
240     >-- format: lisp name = lua function that implements it
241     >binary_functions = {
242     >  cons=cons,
243     >  iso=iso,
244     >}
245 - __teliva_timestamp: original
246   lookup:
247     >function lookup(env, s)
248     >  if env[s] then return env[s] end
249     >  if env.next then return lookup(env.next, s) end
250     >end
251 - __teliva_timestamp: original
252   eval_if:
253     >function eval_if(x, env)
254     >  -- syntax: (if check b1 b2)
255     >  local check = x.cdr.car
256     >  local b1    = x.cdr.cdr.car
257     >  local b2    = x.cdr.cdr.cdr.car
258     >  if eval(check, env) then
259     >    return eval(b1, env)
260     >  else
261     >    return eval(b2, env)
262     >  end
263     >end
264 - __teliva_timestamp: original
265   eval_fn:
266     >function eval_fn(x, env)
267     >  -- syntax: ((fn params body*) args*)
268     >  local callee = x.car
269     >  local args = x.cdr
270     >  local params = callee.cdr.car
271     >  local body = callee.cdr.cdr
272     >  return eval_exprs(body,
273     >                    bind_env(params, args, env))
274     >end
275 - __teliva_timestamp: original
276   bind_env:
277     >function bind_env(params, args, env)
278     >  if params == nil then return env end
279     >  local result = {next=env}
280     >  while true do
281     >    result[params.car.sym] = eval(args.car, env)
282     >    params = params.cdr
283     >    args = args.cdr
284     >    if params == nil then break end
285     >  end
286     >  return result
287     >end
288 - __teliva_timestamp: original
289   eval_exprs:
290     >function eval_exprs(xs, env)
291     >  local result = nil
292     >  while xs do
293     >    result = eval(xs.car, env)
294     >    xs = xs.cdr
295     >  end
296     >  return result
297     >end
298 - __teliva_timestamp: original
299   eval_label:
300     >function eval_label(x, env)
301     >  -- syntax: ((label f (fn params body*)) args*)
302     >  local callee = x.car
303     >  local args = x.cdr
304     >  local f = callee.cdr.car
305     >  local fn = callee.cdr.cdr.car
306     >  return eval({car=fn, cdr=args},
307     >              bind_env({f}, {callee}, env))
308     >end
309 - __teliva_timestamp: original
310   atom:
311     >function atom(x)
312     >  return x == nil or x.num or x.char or x.str or x.sym
313     >end
314 - __teliva_timestamp: original
315   car:
316     >function car(x) return x.car end
317 - __teliva_timestamp: original
318   cdr:
319     >function cdr(x) return x.cdr end
320 - __teliva_timestamp: original
321   cons:
322     >function cons(x, y) return {car=x, cdr=y} end
323 - __teliva_timestamp: original
324   doc:main:
325     >John McCarthy's Lisp -- without the metacircularity
326     >If you know Lua, this version might be easier to understand.
327     >
328     >Words highlighted like [[this]] are suggestions for places to jump to using ctrl-g (see the menu below).
329     >You can always jump back here using ctrl-b (for 'big picture').
330     >
331     >Lisp is a programming language that manipulates objects of a few different types.
332     >There are a few _atomic_ types, and one type that can combine them.
333     >The atomic types are what you would expect: numbers, characters, strings, symbols (variables). You can add others.
334     >
335     >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.
336     >
337     >We'll now build an interpreter that can run programs constructed out of cons tables.
338     >
339     >One thing we'll need for an interpreter is a symbol table (env) that maps symbols to values (objects).
340     >We'll just use a Lua table for this purpose, but with one tweak: a _next_ pointer that allows us to combine tables together.
341     >See [[lookup]] now to get a sense for how we'll use envs.
342     >
343     >Lisp programs are just cons tables and atoms nested to arbitrary depths, constructing trees. A Lisp interpreter walks the tree of code,
344     >performing computations. Since cons tables can point to other cons tables, the tree-walker interpreter [[eval]] is recursive.
345     >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.
346     >The helper functions contain recursive calls to [[eval]], so that [[eval]] becomes indirectly recursive, and [[eval]] together with its helpers
347     >is mutually recursive. I sometimes find it helpful to think of them all as just one big function.
348     >
349     >All these mutually recursive functions take the same arguments: a current expression 'x' and the symbol table 'env'.
350     >But really, most of the interpreter is just walking the tree of expressions. Only two functions care about the internals of 'env':
351     >  - [[lookup]] which reads within env as we saw before
352     >  - [[bind_env]] which creates a new _scope_ of symbols for each new function call.
353     >More complex Lisps add even more arguments to every. single. helper. Each arg will still only really matter to a couple of functions.
354     >But we still have to pass them around all over the place.
355     >
356     >Hopefully this quick overview will help you get a sense for this codebase.
357     >
358     >Here's a reference list of eval helpers: [[eval_unary]], [[eval_binary]], [[eval_if]], [[eval_fn]], [[eval_exprs]], [[eval_label]]
359     >More complex Lisps with more features will likely add helpers for lumpy bits of the language.
360     >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)
361     >Here's a list of _constructors_ for creating objects of different types: [[num]], [[char]], [[str]], [[sym]] (and of course [[cons]])
362     >I should probably add more primitives for operating on numbers, characters and strings..
363 - __teliva_timestamp: original
364   iso:
365     >function iso(x, y)
366     >  if x == nil then return y == nil end
367     >  local done={}
368     >  -- watch out for the rare cyclical expression
369     >  if done[x] then return done[x] == y end
370     >  done[x] = y
371     >  if atom(x) then
372     >    if not atom(y) then return nil end
373     >    for k, v in pairs(x) do
374     >      if y[k] ~= v then return nil end
375     >    end
376     >    return true
377     >  end
378     >  for k, v in pairs(x) do
379     >    if not iso(y[k], v) then return nil end
380     >  end
381     >  for k, v in pairs(y) do
382     >    if not iso(x[k], v) then return nil end
383     >  end
384     >  return true
385     >end