2015-07-08 22:14:05 +00:00
|
|
|
{listToString, listToVector, pairp, cons, car, cdr, caar, cddr, cdar,
|
|
|
|
cadr, caadr, cadar, caddr, nilp, nil, setcdr, metacadr} = require "cons-lists/lists"
|
2015-07-28 23:51:01 +00:00
|
|
|
{Node} = require "../chapter1/reader_types"
|
2015-07-08 22:14:05 +00:00
|
|
|
|
|
|
|
class LispInterpreterError extends Error
|
|
|
|
name: 'LispInterpreterError'
|
|
|
|
constructor: (@message) ->
|
2015-05-21 20:02:39 +00:00
|
|
|
|
2015-06-11 00:28:47 +00:00
|
|
|
env_init = nil
|
|
|
|
env_global = env_init
|
|
|
|
|
2015-06-17 19:34:31 +00:00
|
|
|
definitial = (name, value = nil) ->
|
|
|
|
env_global = (cons (cons name, value), env_global)
|
|
|
|
name
|
2015-06-01 14:40:15 +00:00
|
|
|
|
2015-05-21 20:02:39 +00:00
|
|
|
defprimitive = (name, nativ, arity) ->
|
2015-06-30 15:05:58 +00:00
|
|
|
definitial name, ((args, callback) ->
|
2015-05-21 20:02:39 +00:00
|
|
|
vmargs = listToVector(args)
|
|
|
|
if (vmargs.length == arity)
|
2015-06-30 15:05:58 +00:00
|
|
|
callback nativ.apply null, vmargs
|
2015-05-21 20:02:39 +00:00
|
|
|
else
|
2015-07-08 22:14:05 +00:00
|
|
|
throw new LispInterpreterError "Incorrect arity")
|
2015-05-21 20:02:39 +00:00
|
|
|
|
2015-07-28 04:27:21 +00:00
|
|
|
defpredicate = (name, nativ, arity) ->
|
|
|
|
defprimitive name, ((a, b) -> if nativ.call(null, a, b) then true else the_false_value), arity
|
|
|
|
|
2015-05-21 20:02:39 +00:00
|
|
|
the_false_value = (cons "false", "boolean")
|
|
|
|
|
|
|
|
definitial "#t", true
|
|
|
|
definitial "#f", the_false_value
|
|
|
|
definitial "nil", nil
|
|
|
|
definitial "foo"
|
|
|
|
definitial "bar"
|
|
|
|
definitial "fib"
|
|
|
|
definitial "fact"
|
|
|
|
|
|
|
|
defprimitive "cons", cons, 2
|
|
|
|
defprimitive "car", car, 2
|
|
|
|
defprimitive "set-cdr!", setcdr, 2
|
|
|
|
defprimitive "+", ((a, b) -> a + b), 2
|
|
|
|
defprimitive "*", ((a, b) -> a * b), 2
|
|
|
|
defprimitive "-", ((a, b) -> a - b), 2
|
|
|
|
defprimitive "/", ((a, b) -> a / b), 2
|
|
|
|
defpredicate "lt", ((a, b) -> a < b), 2
|
|
|
|
defpredicate "eq?", ((a, b) -> a == b), 2
|
|
|
|
|
2015-07-28 04:27:21 +00:00
|
|
|
# MISTAKE: Variables are always of type Symbol. This is probably a
|
|
|
|
# mistake.
|
|
|
|
|
2015-05-21 20:02:39 +00:00
|
|
|
extend = (env, variables, values) ->
|
|
|
|
if (pairp variables)
|
|
|
|
if (pairp values)
|
|
|
|
(cons (cons (car variables), (car values)),
|
|
|
|
(extend env, (cdr variables), (cdr values)))
|
|
|
|
else
|
2015-07-08 22:14:05 +00:00
|
|
|
throw new LispInterpreterError "Too few values"
|
2015-05-21 20:02:39 +00:00
|
|
|
else if (nilp variables)
|
2015-07-08 22:14:05 +00:00
|
|
|
if (nilp values) then env else throw new LispInterpreterError "Too many values"
|
2015-05-21 20:02:39 +00:00
|
|
|
else
|
2015-07-28 23:51:01 +00:00
|
|
|
if (variables.type == 'symbol')
|
|
|
|
(cons (cons variables, values), env)
|
2015-05-21 20:02:39 +00:00
|
|
|
else
|
|
|
|
nil
|
|
|
|
|
2015-06-30 15:05:58 +00:00
|
|
|
make_function = (variables, body, env, callback) ->
|
2015-07-01 20:24:45 +00:00
|
|
|
callback (values, cb) -> eprogn body, (extend env, variables, values), cb
|
2015-06-01 14:40:15 +00:00
|
|
|
|
2015-06-30 15:05:58 +00:00
|
|
|
invoke = (fn, args, callback) ->
|
|
|
|
fn args, callback
|
2015-05-21 20:02:39 +00:00
|
|
|
|
|
|
|
# Takes a list of nodes and calls evaluate on each one, returning the
|
|
|
|
# last one as the value of the total expression. In this example, we
|
2015-06-17 19:34:31 +00:00
|
|
|
# are hard-coding what ought to be a macro, namely the threading
|
|
|
|
# macros, "->"
|
2015-05-21 20:02:39 +00:00
|
|
|
|
2015-06-30 15:05:58 +00:00
|
|
|
eprogn = (exps, env, callback) ->
|
2015-05-21 20:02:39 +00:00
|
|
|
if (pairp exps)
|
|
|
|
if pairp (cdr exps)
|
2015-06-17 19:34:31 +00:00
|
|
|
evaluate (car exps), env, (next) ->
|
2015-06-30 15:05:58 +00:00
|
|
|
eprogn (cdr exps), env, callback
|
2015-05-21 20:02:39 +00:00
|
|
|
else
|
2015-06-30 15:05:58 +00:00
|
|
|
evaluate (car exps), env, callback
|
2015-05-21 20:02:39 +00:00
|
|
|
else
|
2015-06-30 15:05:58 +00:00
|
|
|
callback nil
|
2015-06-01 14:40:15 +00:00
|
|
|
|
2015-06-30 15:05:58 +00:00
|
|
|
evlis = (exps, env, callback) ->
|
2015-06-17 19:34:31 +00:00
|
|
|
if (pairp exps)
|
2015-06-30 15:05:58 +00:00
|
|
|
evlis (cdr exps), env, (rest) ->
|
|
|
|
evaluate (car exps), env, (calc) ->
|
|
|
|
callback cons calc, rest
|
2015-05-21 20:02:39 +00:00
|
|
|
else
|
2015-06-30 15:05:58 +00:00
|
|
|
callback nil
|
2015-07-08 22:14:05 +00:00
|
|
|
|
2015-06-30 15:05:58 +00:00
|
|
|
lookup = (id, env) ->
|
2015-06-17 19:34:31 +00:00
|
|
|
if (pairp env)
|
2015-05-21 20:02:39 +00:00
|
|
|
if (caar env) == id
|
2015-06-30 15:05:58 +00:00
|
|
|
cdar env
|
2015-05-21 20:02:39 +00:00
|
|
|
else
|
2015-06-30 15:05:58 +00:00
|
|
|
lookup id, (cdr env)
|
2015-05-21 20:02:39 +00:00
|
|
|
else
|
2015-06-30 15:05:58 +00:00
|
|
|
nil
|
2015-05-21 20:02:39 +00:00
|
|
|
|
2015-06-17 19:34:31 +00:00
|
|
|
update = (id, env, value, callback) ->
|
|
|
|
if (pairp env)
|
2015-05-21 20:02:39 +00:00
|
|
|
if (caar env) == id
|
|
|
|
setcdr value, (car env)
|
2015-06-17 19:34:31 +00:00
|
|
|
callback value
|
2015-05-21 20:02:39 +00:00
|
|
|
else
|
2015-06-17 19:34:31 +00:00
|
|
|
update id, (cdr env), value, callback
|
2015-05-21 20:02:39 +00:00
|
|
|
else
|
2015-06-30 15:05:58 +00:00
|
|
|
callback nil
|
2015-05-21 20:02:39 +00:00
|
|
|
|
|
|
|
# This really ought to be the only place where the AST meets the
|
|
|
|
# interpreter core. I can't help but think that this design precludes
|
|
|
|
# pluggable interpreter core.
|
|
|
|
|
2015-07-28 04:27:21 +00:00
|
|
|
# TODO: Reengineer this with a call to normalize
|
|
|
|
|
2015-06-17 19:34:31 +00:00
|
|
|
astSymbolsToLispSymbols = (node) ->
|
2015-05-21 20:02:39 +00:00
|
|
|
return nil if nilp node
|
2015-07-28 23:51:01 +00:00
|
|
|
throw (new LispInterpreterError "Not a list of variable names") if not node.type == 'list'
|
|
|
|
handler = (cell) ->
|
|
|
|
return nil if nilp cell
|
|
|
|
cons (car cell).value, (handler cdr cell)
|
|
|
|
handler node.value
|
2015-07-08 22:14:05 +00:00
|
|
|
|
2015-06-17 19:34:31 +00:00
|
|
|
# Takes an AST node and evaluates it and its contents. A node may be
|
|
|
|
# ("list" (... contents ...)) or ("number" 42) or ("symbol" x), etc.
|
|
|
|
|
|
|
|
cadddr = metacadr('cadddr')
|
|
|
|
|
2015-07-28 04:27:21 +00:00
|
|
|
metadata_evaluation =
|
2015-07-28 23:51:01 +00:00
|
|
|
listp: (node) -> node.type == 'list'
|
|
|
|
symbolp: (node) -> node.type == 'symbol'
|
|
|
|
numberp: (node) -> node.type == 'number'
|
|
|
|
stringp: (node) -> node.type == 'string'
|
|
|
|
commentp: (node) -> node.type == 'comment'
|
|
|
|
nvalu: (node) -> node.value
|
|
|
|
mksymbols: (list) -> astSymbolsToLispSymbols(list)
|
2015-07-28 04:27:21 +00:00
|
|
|
|
|
|
|
straight_evaluation =
|
2015-07-28 23:51:01 +00:00
|
|
|
listp: (cell) -> cell.__type == 'list'
|
|
|
|
symbolp: (cell) -> typeof cell == 'string' and cell.length > 0 and cell[0] not in ["\"", ";"]
|
|
|
|
commentp: (cell) -> typeof cell == 'string' and cell.length > 0 and cell[0] == ";"
|
|
|
|
numberp: (cell) -> typeof cell == 'number'
|
|
|
|
stringp: (cell) -> typeof cell == 'string' and cell.length > 0 and cell[0] == "\""
|
|
|
|
boolp: (cell) -> typeof cell == 'boolean'
|
|
|
|
nullp: (cell) -> cell == null
|
|
|
|
vectorp: (cell) -> (not straight_evaluation.listp cell) and toString.call(cell) == '[object Array]'
|
|
|
|
recordp: (cell) -> (not cell._prototype?) and toSTring.call(cell) == '[object Object]'
|
|
|
|
objectp: (cell) -> (cell._prototype?) and toString.call(cell) == '[object Object]'
|
|
|
|
nilp: (cell) -> nilp(cell)
|
|
|
|
nvalu: (cell) -> cell
|
|
|
|
mksymbols: (cell) -> cell
|
2015-07-28 04:27:21 +00:00
|
|
|
|
|
|
|
makeEvaluator = (ix = straight_evaluation, ty="straight") ->
|
|
|
|
(exp, env, callback) ->
|
|
|
|
if ix.symbolp exp
|
2015-07-28 23:51:01 +00:00
|
|
|
return callback lookup (ix.nvalu exp), env
|
2015-07-28 04:27:21 +00:00
|
|
|
else if ([ix.numberp, ix.stringp].filter (i) -> i(exp)).length > 0
|
|
|
|
return callback ix.nvalu exp
|
|
|
|
else if ix.listp exp
|
|
|
|
body = ix.nvalu exp
|
|
|
|
head = car body
|
|
|
|
if ix.symbolp head
|
2015-07-28 23:51:01 +00:00
|
|
|
return switch (ix.nvalu head)
|
2015-07-28 04:27:21 +00:00
|
|
|
when "quote" then callback cdr body
|
|
|
|
when "if"
|
|
|
|
evaluate (cadr body), env, (res) ->
|
|
|
|
w = unless res == the_false_value then caddr else cadddr
|
|
|
|
evaluate (w body), env, callback
|
|
|
|
when "begin" then eprogn (cdr body), env, callback
|
|
|
|
when "set!"
|
|
|
|
evaluate (caddr body), env, (newvalue) ->
|
2015-07-28 23:51:01 +00:00
|
|
|
update (ix.nvalu cadr body), env, newvalue, callback
|
2015-07-28 04:27:21 +00:00
|
|
|
when "lambda"
|
|
|
|
make_function (ix.mksymbols cadr body), (cddr body), env, callback
|
|
|
|
else
|
|
|
|
evaluate (car body), env, (fn) ->
|
|
|
|
evlis (cdr body), env, (args) ->
|
|
|
|
invoke fn, args, callback
|
|
|
|
else
|
|
|
|
evaluate (car body), env, (fn) ->
|
|
|
|
evlis (cdr body), env, (args) ->
|
|
|
|
invoke fn, args, callback
|
2015-06-17 19:34:31 +00:00
|
|
|
else
|
2015-07-28 04:27:21 +00:00
|
|
|
throw new LispInterpreterError ("Can't handle a #{type}")
|
|
|
|
|
|
|
|
nodeEval = makeEvaluator(metadata_evaluation, "node")
|
|
|
|
lispEval = makeEvaluator(straight_evaluation, "lisp")
|
2015-05-21 20:02:39 +00:00
|
|
|
|
2015-07-28 04:27:21 +00:00
|
|
|
evaluate = (exp, env, cb) ->
|
2015-07-28 23:51:01 +00:00
|
|
|
(if exp? and (exp instanceof Node) then nodeEval else lispEval)(exp, env, cb)
|
2015-07-28 04:27:21 +00:00
|
|
|
|
2015-06-30 15:05:58 +00:00
|
|
|
module.exports = (c, cb) -> evaluate c, env_global, cb
|