166 lines
5.6 KiB
CoffeeScript
166 lines
5.6 KiB
CoffeeScript
{listToString, listToVector, pairp, cons, car, cdr, caar, cddr, cdar,
|
|
cadr, caadr, cadar, caddr, nilp, nil, setcdr,
|
|
metacadr, setcar} = require "cons-lists/lists"
|
|
{normalizeForms, normalizeForm} = require "../chapter1/astToList"
|
|
{Node, Symbol} = require '../chapter1/reader_types'
|
|
|
|
class LispInterpreterError extends Error
|
|
name: 'LispInterpreterError'
|
|
constructor: (@message) ->
|
|
|
|
the_false_value = (cons "false", "boolean")
|
|
|
|
eq = (id1, id2) ->
|
|
if id1 instanceof Symbol and id2 instanceof Symbol
|
|
return id1.name == id2.name
|
|
id1 == id2
|
|
|
|
# Only called in rich node mode...
|
|
|
|
astSymbolsToLispSymbols = (node) ->
|
|
return nil if nilp node
|
|
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
|
|
|
|
cadddr = metacadr('cadddr')
|
|
|
|
metadata_evaluation =
|
|
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)
|
|
|
|
# The hairness of this makes me doubt the wisdom of using Javascript.
|
|
|
|
straight_evaluation =
|
|
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
|
|
|
|
sType = new Symbol 'type'
|
|
sBehavior = new Symbol 'behavior'
|
|
sFunction = new Symbol 'function'
|
|
|
|
# Page 129
|
|
env_init = (id) -> throw LispInterpreterError "No binding for #{id}"
|
|
|
|
# Page 129
|
|
# We don't have an initial value for mem yet?
|
|
update = (mem, addr, value) ->
|
|
(addra) -> if (addra == addr) then value else mem(addra)
|
|
|
|
# Page 130
|
|
updates = (mem, addrs, values) ->
|
|
if (pairp addrs)
|
|
updates (update mem, (car addrs), (car values)), (cdr addrs), (cdr values)
|
|
else
|
|
mem
|
|
|
|
# Page 130
|
|
evaluateVariable = (name, env, mem, kont) ->
|
|
kont mem, (env name), mem
|
|
|
|
# Page 130
|
|
evaluateSet = (name, exp, env, mem, kont) ->
|
|
evaluate exp, env, mem, (value, newmem) ->
|
|
kont value, (update newmem, (env name), value)
|
|
|
|
# Page 131
|
|
# TODO: I don't know that I trust this.
|
|
evaluateApplication = (exp, exprs, env, mem, kont) ->
|
|
|
|
evaluateArguments = (exprs, env, mem, kont) ->
|
|
if (pairp exprs)
|
|
evaluate (car exprs), env, mem, (value, mem2) ->
|
|
evaluateArguments (cdr exprs), env, mem2, (value2, mems3) ->
|
|
kont (cons value, value2), mems3
|
|
else
|
|
kont cons(), mem
|
|
|
|
evaluate exp, env, mem, (fun, mems) ->
|
|
evaluateArguments exprs, env, mems, (value2, mem3) ->
|
|
if eq (fun sType), sFunction
|
|
(fun sBehavior) value2, mem3, kont
|
|
else
|
|
throw new LispInterpreterError "Not a function #{(car value2)}"
|
|
|
|
evaluateLambda = (names, exprs, env, mem, kont) ->
|
|
allocate 1, mem, (addrs, mem2) ->
|
|
kont (createFunction (car addrs), (values, mem, kont) ->
|
|
if eq (length names), (length values)
|
|
allocate (length names), mem, (addrs, mem2) ->
|
|
evaluateBegin exprs, (updates env, names, addrs), (updates mem2, addrs, values), kont
|
|
else
|
|
throw new LispInterpreterError "Incorrect Arrity"), mem2
|
|
|
|
allocate = (num, mem, q) ->
|
|
if (num > 0)
|
|
do ->
|
|
addr = newLocation s
|
|
allocate (num - 1), (expandStore addr, mem), (addrs, mem2) ->
|
|
q (cons addr, addrs), mem2
|
|
else
|
|
q cons(), mem
|
|
|
|
expandStore = (highLocation, mem) ->
|
|
update mem, 0, highLocation
|
|
|
|
newLocation = (mem) ->
|
|
(mem 0) + 1
|
|
|
|
|
|
|
|
# Page 128
|
|
evaluateIf = (expc, expt, expf, env, mem, kont) ->
|
|
evaluate expc, env, mem, (env, mems) ->
|
|
evaluate ((env "boolify") expt, expf), env, mems, kont
|
|
|
|
# Page 129
|
|
# I like how, in this version, we explicitly throw away the meaning of
|
|
# all but the last statement in evaluateBegin.
|
|
evaluateBegin = (exps, env, mem, kont) ->
|
|
if pairp (cdr exps)
|
|
evaluate (car exps), env, mem, (_, mems) ->
|
|
evaluateBegin (cdr exps), env, mems, kont
|
|
else
|
|
evaluate (car exps), env, mem, kont
|
|
|
|
|
|
prox =
|
|
"quote": (body, env, mem, kont, ix) -> evaluateQuote (cadr body), env, mem, kont
|
|
"if": (body, env, mem, kont, ix) -> evaluateIf (cadr body), (caddr body), (cadddr body), env, mem, kont
|
|
"begin": (body, env, mem, kont, ix) -> evaluateBegin (cdr body), env, mem, kont
|
|
"set!": (body, env, mem, kont, ix) -> evaluateSet (ix.nvalu cadr body), (caddr body), env, mem, kont
|
|
"lambda": (body, env, mem, kont, ix) -> evaluateLambda (ix.mksymbols cadr body), (cddr body), env, mem, kont
|
|
|
|
makeEvaluator = (ix = straight_evaluation) ->
|
|
(exp, env, mem, kont) ->
|
|
if ix.atomp exp
|
|
if ix.symbolp exp
|
|
evaluateVariable exp, env, mem, kont
|
|
else
|
|
evaluateQuote exp, env, mem, kont
|
|
else
|
|
body = ix.nvalu exp
|
|
head = car body
|
|
if prox[(ix.nvalu head)]?
|
|
prox[(ix.nvalue head)](body, env, mem, kont, ix)
|
|
else
|
|
evaluateApplication body, (cadr body), env, mem, kont
|