LispInSmallPieces/chapter1/interpreter.coffee

188 lines
5.8 KiB
CoffeeScript
Raw Normal View History

{listToString, listToVector, pairp, cons, car, cdr, caar, cddr,
cdar, cadr, caadr, cadar, caddr, nilp, nil, setcdr, metacadr} = require "cons-lists/lists"
2015-05-14 05:28:55 +00:00
readline = require "readline"
{inspect} = require "util"
{Symbol} = require "./reader_types"
class LispInterpreterError extends Error
name: 'LispInterpreterError'
constructor: (@message, position = null) ->
2015-05-14 05:28:55 +00:00
env_init = nil
env_global = env_init
definitial = (name, value = nil) ->
env_global = (cons (cons name, value), env_global)
name
defprimitive = (name, nativ, arity) ->
definitial name, ((args) ->
vmargs = listToVector(args)
if (vmargs.length == arity)
nativ.apply null, vmargs
2015-05-14 05:28:55 +00:00
else
throw (new LispInterpreterError "Incorrect arity"))
2015-05-14 05:28:55 +00:00
defpredicate = (name, nativ, arity) ->
defprimitive name, ((a, b) -> if nativ.call(null, a, b) then true else the_false_value), arity
the_false_value = (cons "false", "boolean")
2015-05-14 05:28:55 +00:00
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
extend = (env, variables, values) ->
if (pairp variables)
if (pairp values)
(cons (cons (car variables), (car values)),
(extend env, (cdr variables), (cdr values)))
else
throw new LispInterpreterError "Too few values"
else if (nilp variables)
if (nilp values) then env else throw new LispInterpreterError "Too many values"
else
if (variables instanceof Symbol)
(cons (cons variables.v, values), env)
else
nil
make_function = (variables, body, env) ->
(values) -> eprogn body, (extend env, variables, values)
invoke = (fn, args) ->
(fn args)
# 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
# are hard-coding what ought to be a macro, namely the threading
# macros, "->"
eprogn = (exps, env) ->
if (pairp exps)
if pairp (cdr exps)
evaluate (car exps), env
eprogn (cdr exps), env
else
evaluate (car exps), env
else
nil
evlis = (exps, env) ->
if (pairp exps)
(cons (evaluate (car exps), env), (evlis (cdr exps), env))
else
nil
lookup = (id, env) ->
if (pairp env)
if (caar env) == id
cdar env
else
lookup id, (cdr env)
else
nil
update = (id, env, value) ->
if (pairp env)
if (caar env) == id
setcdr value, (car env)
value
else
update id, (cdr env), value
else
nil
# TODO: Reengineer this with a call to normalize
astSymbolsToLispSymbols = (node) ->
nvalu = (node) -> cadr node
return nil if nilp node
throw (new LispInterpreterError "Not a list of variable names") if not ((car node) is 'list')
handler = (node) ->
return nil if nilp node
cons (nvalu car node).v, (handler cdr node)
handler(nvalu node)
cadddr = metacadr('cadddr')
2015-05-14 05:28:55 +00:00
# This is really the only thing that changes behavior between "reader
# nodes" (nodes loaded with debugging metadata) and a standard cons
# object. TODO: astSymbolsToLispSymbols should be deprecated in
# favor of normalizeForm (s?) and Symbol extraction
metadata_evaluation =
listp: (node) -> (car node) == 'list'
symbolp: (node) -> (car node) == 'symbol'
numberp: (node) -> (car node) == 'number'
stringp: (node) -> (car node) == 'string'
nvalu: (node) -> cadr node
mksymbols: (node) -> astSymbolsToLispSymbols(node)
straight_evaluation =
listp: (node) -> node.__type == 'list'
symbolp: (node) -> node instanceof Symbol
commentp: (node) -> node instanceof Comment
numberp: (node) -> typeof node == 'number'
stringp: (node) -> typeof node == 'string'
boolp: (node) -> typeof node == 'boolean'
nullp: (node) -> node == null
vectorp: (node) -> (not straight_evaluation.listp node) and toString.call(node) == '[object Array]'
recordp: (node) -> (not node._prototype?) and toSTring.call(node) == '[object Object]'
objectp: (node) -> (node._prototype?) and toString.call(node) == '[object Object]'
nilp: (node) -> nilp(node)
nvalu: (node) -> node
mksymbols: (node) -> node
makeEvaluator = (ix = straight_evaluation, ty="straight") ->
(exp, env) ->
# Takes an AST node and evaluates it and its contents. A node may be
# ("list" (... contents ...)) or ("number" 42) or ("symbol" x), etc.
2015-05-14 05:28:55 +00:00
if ix.symbolp(exp)
return lookup (ix.nvalu exp).v, env
else if ([ix.numberp, ix.stringp].filter (i) -> i(exp)).length > 0
return ix.nvalu exp
else if ix.listp(exp)
body = ix.nvalu exp
head = car body
if ix.symbolp(head)
switch (ix.nvalu head).v
when "quote" then cdr body
when "if"
unless (evaluate (cadr body), env) == the_false_value
evaluate (caddr body), env
else
evaluate (cadddr body), env
when "begin" then eprogn (cdr body), env
when "set!" then update (ix.nvalu cadr body).v, env, (evaluate (caddr body), env)
when "lambda" then make_function (ix.mksymbols cadr body), (cddr body), env
else invoke (evaluate (car body), env), (evlis (cdr body), env)
else
invoke (evaluate (car body), env), (evlis (cdr body), env)
else
throw new LispInterpreterError "Can't handle a #{type}"
nodeEval = makeEvaluator(metadata_evaluation, "node")
lispEval = makeEvaluator(straight_evaluation, "lisp")
evaluate = (exp, env) ->
(if exp? and exp.__node then nodeEval else lispEval)(exp, env)
2015-05-14 05:28:55 +00:00
module.exports = (c) -> evaluate c, env_global