2015-07-03 22:45:37 +00:00
|
|
|
{listToString, listToVector, pairp, cons, car, cdr, caar, cddr, cdar, cadr, caadr, cadar, caddr, nilp, nil, setcdr, metacadr, setcar} = require "cons-lists/lists"
|
2015-07-01 20:55:22 +00:00
|
|
|
readline = require "readline"
|
|
|
|
{inspect} = require "util"
|
|
|
|
print = require "./print"
|
|
|
|
|
2015-07-03 00:21:50 +00:00
|
|
|
ntype = (node) -> car node
|
|
|
|
nvalu = (node) -> cadr node
|
2015-07-02 00:38:31 +00:00
|
|
|
|
2015-07-03 00:21:50 +00:00
|
|
|
class Value
|
2015-07-02 00:38:31 +00:00
|
|
|
|
2015-07-03 22:45:37 +00:00
|
|
|
# Represents the base class of a continuation. Calls to invoke resume
|
|
|
|
# the contained continuation, which is typecast to one of the specific
|
|
|
|
# continuation needs of conditional, sequence, etc...
|
|
|
|
|
2015-07-02 00:38:31 +00:00
|
|
|
class Continuation
|
|
|
|
constructor: (@k) ->
|
2015-07-03 00:21:50 +00:00
|
|
|
invoke: (v, env, kont) ->
|
|
|
|
if nilp cdr v
|
|
|
|
resume @k, (car v)
|
|
|
|
else
|
|
|
|
throw "Continuations expect one argument", [v, env, kont]
|
|
|
|
|
|
|
|
# Abstract class representing the environment
|
|
|
|
|
|
|
|
class Environment
|
|
|
|
lookup: -> throw "Nonspecific invocation"
|
|
|
|
update: -> throw "Nonspecific invocation"
|
|
|
|
|
|
|
|
# Base of the environment stack. If you hit this, your variable was
|
|
|
|
# never found for lookup/update. Note that at this time in the
|
|
|
|
# class, you have not
|
2015-07-02 00:38:31 +00:00
|
|
|
|
|
|
|
class NullEnv extends Environment
|
|
|
|
lookup: -> throw "Unknown variable"
|
|
|
|
update: -> throw "Unknown variable"
|
2015-07-03 00:21:50 +00:00
|
|
|
|
|
|
|
# This appears to be an easy and vaguely abstract handle to the
|
|
|
|
# environment. The book is not clear on the distinction between the
|
|
|
|
# FullEnv and the VariableEnv.
|
|
|
|
|
2015-07-02 00:38:31 +00:00
|
|
|
class FullEnv extends Environment
|
|
|
|
constructor: (@others, @name) ->
|
|
|
|
lookup: (name, kont) ->
|
|
|
|
@others.lookup name, kont
|
|
|
|
update: (name, kont, value) ->
|
|
|
|
@others.update name, kont, value
|
|
|
|
|
2015-07-03 00:21:50 +00:00
|
|
|
# This is the classic environment pair; either it's *this*
|
|
|
|
# environment, or it's a parent environment, until you hit the
|
2015-07-03 22:45:37 +00:00
|
|
|
# NullEnv. Once the name has been found, the continuation is called
|
|
|
|
# with the found value.
|
2015-07-03 00:21:50 +00:00
|
|
|
|
2015-07-02 00:38:31 +00:00
|
|
|
class VariableEnv extends FullEnv
|
|
|
|
constructor: (@others, @name, @value) ->
|
|
|
|
lookup: (name, kont) ->
|
|
|
|
if name == @name
|
2015-07-03 22:45:37 +00:00
|
|
|
kont.resume @value
|
2015-07-02 00:38:31 +00:00
|
|
|
else
|
|
|
|
@others.lookup name, kont
|
|
|
|
update: (nam, kont, value) ->
|
|
|
|
if name == @name
|
|
|
|
@value = value
|
2015-07-03 22:45:37 +00:00
|
|
|
kont.resume value
|
2015-07-02 00:38:31 +00:00
|
|
|
else
|
|
|
|
@others.update name, kont, value
|
|
|
|
|
2015-07-03 22:45:37 +00:00
|
|
|
# "Renders the quote term to the current continuation"; in a more
|
|
|
|
# familiar parlance, calls resume in the current context with the
|
|
|
|
# quoted term uninterpreted.
|
2015-07-02 00:38:31 +00:00
|
|
|
|
|
|
|
evaluateQuote = (v, env, kont) ->
|
2015-07-03 22:45:37 +00:00
|
|
|
kont.resume v
|
2015-07-02 00:38:31 +00:00
|
|
|
|
2015-07-03 22:45:37 +00:00
|
|
|
# Evaluates the conditional expression, creating a continuation with
|
|
|
|
# the current environment that, when resumed, evaluates either the
|
|
|
|
# true or false branch, again in the current enviornment.
|
2015-07-02 00:38:31 +00:00
|
|
|
|
|
|
|
evaluateIf = (exps, env, kont) ->
|
|
|
|
evaluate (car e), env, new IfCont(kont, (cadr e), (caddr e), env)
|
|
|
|
|
|
|
|
class IfCont extends Continuation
|
|
|
|
constructor: (@k, @ift, @iff, @env) ->
|
|
|
|
resume: (v) -> evaluate (if v then @ift else @iff), @env, @k
|
|
|
|
|
2015-07-03 22:45:37 +00:00
|
|
|
# Sequences: evaluates the current expression with a continuation that
|
|
|
|
# represents "the next expression" in the sequence. Upon resumption,
|
|
|
|
# calls this function with that next expression.
|
2015-07-02 00:38:31 +00:00
|
|
|
|
|
|
|
evaluateBegin = (exps, env, kont) ->
|
|
|
|
if (pairp exps)
|
|
|
|
if pairp (cdr exps)
|
|
|
|
evaluate (car exps), env, (new BeginCont kont, exps, env)
|
|
|
|
else
|
|
|
|
evaluate (car exps), env, kont
|
|
|
|
else
|
2015-07-03 22:45:37 +00:00
|
|
|
kont.resume("Begin empty value")
|
2015-07-02 00:38:31 +00:00
|
|
|
|
|
|
|
class BeginCont extends Continuation
|
|
|
|
constructor: (@k, @exps, @env) ->
|
|
|
|
resume: (v) -> evaluateBegin (cdr @exps), @env, @k
|
|
|
|
|
2015-07-03 22:45:37 +00:00
|
|
|
# In this continuation, we simply pass the continuation and the name
|
|
|
|
# to the environment to look up. The environment knows to call the
|
|
|
|
# continuation with the value.
|
2015-07-02 00:38:31 +00:00
|
|
|
|
|
|
|
evaluateVariable = (name, env, kont) ->
|
|
|
|
env.lookup(name, kont)
|
|
|
|
|
2015-07-03 22:45:37 +00:00
|
|
|
# This is the same dance as lookup, only with the continuation being
|
|
|
|
# called after an update has been performed.
|
2015-07-02 00:38:31 +00:00
|
|
|
|
|
|
|
evaluateSet = (name, exp, env, kont) ->
|
|
|
|
evaluate exp, env, (new setCont(kont, name, env))
|
|
|
|
|
|
|
|
class SetCont extend Continuation
|
|
|
|
constructor: (@k, @name, @env) ->
|
|
|
|
resume: (value) ->
|
|
|
|
update @env, @name, @k, value
|
|
|
|
|
2015-07-03 22:45:37 +00:00
|
|
|
# Calls the current contunation, passing it a new function wrapper.
|
2015-07-02 00:38:31 +00:00
|
|
|
|
2015-07-03 00:21:50 +00:00
|
|
|
evaluateLambda = (names, exp, env, kont) ->
|
2015-07-03 22:45:37 +00:00
|
|
|
kont.resume new Function names, exp, env
|
|
|
|
|
|
|
|
# Upon invocation, evaluates the body with a new environment that
|
|
|
|
# consists of the original names, their current values as called, and
|
|
|
|
# the continuation an the moment of invocation, which will continue
|
|
|
|
# (resume) execution once the function is finished.
|
|
|
|
#
|
|
|
|
# By the way: this is pretty much the whole the point.
|
2015-07-02 00:38:31 +00:00
|
|
|
|
|
|
|
class Function extends Value
|
|
|
|
constructor: (@variables, @body, @env) ->
|
|
|
|
invoke: (values, env, kont) ->
|
|
|
|
evaluateBegin @body, (extend @env, @variables, values), kont
|
|
|
|
|
2015-07-03 22:45:37 +00:00
|
|
|
# Helper function to build name/value pairs for the current execution
|
|
|
|
# context.
|
|
|
|
|
2015-07-02 00:38:31 +00:00
|
|
|
extend = (env, names, values) ->
|
|
|
|
if (pairp names) and (pairp values)
|
|
|
|
new VariableEnv (extend env (cdr names) (cdr values)), (car names), (car values)
|
|
|
|
else if (nilp names)
|
|
|
|
if (nilp values) then env else throw "Arity mismatch"
|
|
|
|
else
|
|
|
|
new VariableEnv env, names, values
|
|
|
|
|
2015-07-03 22:45:37 +00:00
|
|
|
# Now we start the invocation: this is applying the function. Let's
|
|
|
|
# take it stepwise.
|
|
|
|
|
|
|
|
# Create a function environment. Calls the evaluateArguments(), which
|
|
|
|
# in turns goes down the list of arguments and creates a new
|
|
|
|
# environment, and then the continuation is to actually appy the nev
|
|
|
|
# environment to the existing function.
|
2015-07-02 00:38:31 +00:00
|
|
|
|
|
|
|
evaluateApplication = (exp, exps, env, kont) ->
|
|
|
|
evaluate exp, env, (new EvFunCont kont, exps, env)
|
|
|
|
|
|
|
|
class EvFunCont extends Continuation
|
|
|
|
constructor: (@k, @exp, @env) ->
|
|
|
|
resume: (f) ->
|
|
|
|
evaluateArguments (@exp, @k, new ApplyCont @k, f, @env)
|
|
|
|
|
2015-07-03 22:45:37 +00:00
|
|
|
# Evaluate the first list, creating a new list of the arguments. Upon
|
|
|
|
# completion, resume the continuation with the gather phase
|
|
|
|
|
2015-07-02 00:38:31 +00:00
|
|
|
evaluateArguments = (exp, env, kont) ->
|
|
|
|
if (pairp exp)
|
|
|
|
evaluate (car exp), env, (new ArgumentCont kont, exp, env)
|
|
|
|
else
|
2015-07-03 22:45:37 +00:00
|
|
|
kont.resume("No more arguments")
|
2015-07-02 00:38:31 +00:00
|
|
|
|
|
|
|
class ArgumentCont extends Continuation
|
|
|
|
constructor: (@k, @exp, @env) ->
|
|
|
|
resume: (v) ->
|
|
|
|
evaluateArguments (cdr @env, @env, new GatherCont @k, v)
|
|
|
|
|
2015-07-03 22:45:37 +00:00
|
|
|
# Gather the arguments as each ArgumentCont is resumed into a list to
|
|
|
|
# be passed to our next step.
|
|
|
|
|
2015-07-02 00:38:31 +00:00
|
|
|
class GatherCont extends Continuation
|
|
|
|
constructor: (@k, @v) ->
|
|
|
|
resume: (v) ->
|
|
|
|
@k.resume (cons @v, v)
|
2015-07-03 22:45:37 +00:00
|
|
|
|
|
|
|
# Upon resumption, invoke the function.
|
|
|
|
|
|
|
|
class ApplyCont extends Continuation
|
|
|
|
constructor: (@k, @fn, @env) ->
|
|
|
|
resume: (v) ->
|
|
|
|
invoke @fn, v, @env, @k
|
|
|
|
|
|
|
|
# A special continuation that represents what we want the interpreter
|
|
|
|
# to do when it's done processing.
|
2015-07-02 00:38:31 +00:00
|
|
|
|
|
|
|
class BottomCont extends Continuation
|
|
|
|
constructor: (@k, @f) ->
|
|
|
|
resume: (v) ->
|
|
|
|
@f(v)
|
|
|
|
|
|
|
|
class Primitive extends Value
|
2015-07-03 00:21:50 +00:00
|
|
|
constructor: (@name, @nativ) ->
|
|
|
|
invoke: (args, env, kont) ->
|
|
|
|
@nativ.apply null, (listToVector args), env, kont
|
2015-07-02 00:38:31 +00:00
|
|
|
|
|
|
|
evaluate = (e, env, kont) ->
|
|
|
|
[type, exp] = [(ntype e), (nvalu e)]
|
|
|
|
if type == "symbol"
|
2015-07-03 00:21:50 +00:00
|
|
|
return evaluateVariable exp, env, kont
|
2015-07-02 00:38:31 +00:00
|
|
|
if type in ["number", "string", "boolean", "vector"]
|
|
|
|
return exp
|
|
|
|
if type == "list"
|
|
|
|
head = car exp
|
|
|
|
if (ntype head) == 'symbol'
|
|
|
|
switch (nvalu head)
|
2015-07-03 00:21:50 +00:00
|
|
|
when "quote" then evaluateQuote (cdr exp), env, kont
|
2015-07-02 00:38:31 +00:00
|
|
|
when "if" then evaluateIf (cdr exp), env, kont
|
2015-07-03 00:21:50 +00:00
|
|
|
when "begin" then evaluateBegin (cdr exp), env, kont
|
|
|
|
when "set!" then evaluateSet (nvalu cadr exp), (nvalu caddr exp), env, kont
|
|
|
|
when "lambda" then evaluateLambda (astSymbolsToLispSymbols cadr exp), (cddr exp), env, kont
|
|
|
|
evaluateApplication (car exp), (cdr exp), env, cont
|
2015-07-02 00:38:31 +00:00
|
|
|
else
|
2015-07-03 00:21:50 +00:00
|
|
|
evaluateApplication (car exp), (cdr exp), env, cont
|
2015-07-02 00:38:31 +00:00
|
|
|
else
|
|
|
|
throw new Error("Can't handle a #{type}")
|
|
|
|
|
2015-07-01 20:55:22 +00:00
|
|
|
definitial = (name, value = nil) ->
|
2015-07-03 00:21:50 +00:00
|
|
|
env_init = new VariableEnv env_init, name, value
|
2015-07-01 20:55:22 +00:00
|
|
|
name
|
|
|
|
|
|
|
|
defprimitive = (name, nativ, arity) ->
|
2015-07-03 00:21:50 +00:00
|
|
|
definitial name, new Primitive name, (args, env, kont) ->
|
2015-07-01 20:55:22 +00:00
|
|
|
vmargs = listToVector(args)
|
|
|
|
if (vmargs.length == arity)
|
2015-07-03 22:45:37 +00:00
|
|
|
kont.resume (nativ.apply null, vmargs)
|
2015-07-01 20:55:22 +00:00
|
|
|
else
|
|
|
|
throw "Incorrect arity")
|
|
|
|
|
|
|
|
the_false_value = (cons "false", "boolean")
|
|
|
|
|
|
|
|
definitial "#t", true
|
|
|
|
definitial "#f", the_false_value
|
|
|
|
definitial "nil", nil
|
2015-07-03 00:21:50 +00:00
|
|
|
for i in [
|
|
|
|
"x", "y", "z", "a", "b", "c", "foo", "bar", "hux",
|
|
|
|
"fib", "fact", "visit", "primes", "length"]
|
|
|
|
definitial i
|
|
|
|
|
2015-07-01 20:55:22 +00:00
|
|
|
defpredicate = (name, nativ, arity) ->
|
|
|
|
defprimitive name, ((a, b) -> if nativ.call(null, a, b) then true else the_false_value), arity
|
|
|
|
|
|
|
|
defprimitive "cons", cons, 2
|
|
|
|
defprimitive "car", car, 2
|
2015-07-03 22:45:37 +00:00
|
|
|
defprimitive "cdr", cdr, 2
|
2015-07-01 20:55:22 +00:00
|
|
|
defprimitive "set-cdr!", setcdr, 2
|
2015-07-03 22:45:37 +00:00
|
|
|
defprimitive "set-car!", setcar, 2
|
2015-07-01 20:55:22 +00:00
|
|
|
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
|
2015-07-03 22:45:37 +00:00
|
|
|
defpredicate "gt", ((a, b) -> a > b), 2
|
|
|
|
defpredicate "lte", ((a, b) -> a <= b), 2
|
|
|
|
defpredicate "gte", ((a, b) -> a >= b), 2
|
2015-07-01 20:55:22 +00:00
|
|
|
defpredicate "eq?", ((a, b) -> a == b), 2
|
2015-07-03 22:45:37 +00:00
|
|
|
defpredicate "pair?" ((a) -> pairp a), 1
|
|
|
|
defpredicate "nil?" ((a) -> nilp a), 1
|
|
|
|
defpredicate "symbol?" ((a) -> /\-?[0-9]+$/.test(a) == false), 1
|
2015-07-01 20:55:22 +00:00
|
|
|
|
2015-07-03 22:45:37 +00:00
|
|
|
definitial "call/cc", new Primitive "call/cc", (values, env, kont) ->
|
|
|
|
if nilp cdr values
|
|
|
|
(car values).invoke (cons kont), env, kont
|
2015-07-01 20:55:22 +00:00
|
|
|
else
|
2015-07-03 22:45:37 +00:00
|
|
|
throw "Incorrect arity for call/cc", [r, k]
|
2015-07-01 20:55:22 +00:00
|
|
|
|
2015-07-03 22:45:37 +00:00
|
|
|
definitial "apply", new Primitive "apply", (values, env, kont) ->
|
|
|
|
if pairp cdr values
|
|
|
|
f = car values
|
|
|
|
args = (() ->
|
|
|
|
(flat = (args) ->
|
|
|
|
if nilp cdr args then (car args) else (cons (car args), (flat cdr args)))(cdr values))()
|
|
|
|
f.invoke args, env, kont
|
2015-07-01 20:55:22 +00:00
|
|
|
|
2015-07-03 22:45:37 +00:00
|
|
|
definitial "list", new Primitive "list", (values, env, kont) ->
|
|
|
|
(values, env, kont) -> kont.resume(values)
|
2015-07-01 20:55:22 +00:00
|
|
|
|
2015-07-03 22:45:37 +00:00
|
|
|
env_init = new NullEnv()
|
2015-07-02 00:38:31 +00:00
|
|
|
|
2015-07-03 22:45:37 +00:00
|
|
|
interpreter = (ast, kont) ->
|
|
|
|
evaluate ast, env_init, new BottomCont null, kont
|
2015-07-02 00:38:31 +00:00
|
|
|
|
2015-07-03 22:45:37 +00:00
|
|
|
module.exports = intepreter
|