[feat] Most throw/catch conditions work.

I've hit a snag with respect to self-evaluating objects, and the ad-hoc evaluation of
program labels is messed up because of it.  I'm going to have to refactor.
Oddly enough, the strategy I hit upon appears to be the same one found
in Wisp, rather than Clojurescript.

This may actually be an internal detail; the version rendered for the user may actually
not care.  I hope not; the performance could become hairy pretty quickly.
This commit is contained in:
Elf M. Sternberg 2015-07-21 16:16:13 -07:00
parent c816fa9eb8
commit 39f6a09d51
3 changed files with 114 additions and 3 deletions

1
.gitignore vendored
View File

@ -12,3 +12,4 @@ bin/coffee
bin/cake
test-reports.xml
LisperatorLanguage
chapter?/test.coffee

View File

@ -1,8 +1,10 @@
{listToString, listToVector, pairp, cons, car, cdr, caar, cddr, cdar,
cadr, caadr, cadar, caddr, nilp, nil, setcdr,
metacadr, setcar} = require "cons-lists/lists"
{normalizeForm} = require "../chapter1/astToList"
readline = require "readline"
{inspect} = require "util"
minspect = (obj) -> inspect obj, false, null, true
class LispInterpreterError extends Error
name: 'LispInterpreterError'
@ -28,6 +30,8 @@ class Continuation
throw new LispInterpreterError "Continuations expect one argument"
unwind: (value, ktarget) ->
if (@kont == ktarget) then (@kont.resume value) else (@kont.unwind value, ktarget)
catchLookup: (tag, kk) ->
@kont.catchLookup tag, kk
# Abstract class representing the environment
@ -93,6 +97,7 @@ evaluateQuote = (v, env, kont) ->
evaluateIf = (exps, env, kont) ->
evaluate (car exps), env, new IfCont(kont, (cadr exps), (caddr exps), env)
class IfCont extends Continuation
constructor: (@kont, @ift, @iff, @env) ->
@_type = "IfCont"
@ -224,6 +229,8 @@ class BottomCont extends Continuation
@func(value)
unwind: (value, ktarget) ->
throw new LispInterpreterError "Obsolete continuation"
catchLookup: (tag, kk) ->
throw new LispInterpreterError "No associated catch"
evaluateBlock = (label, body, env, kont) ->
k = new BlockCont(kont, label)
@ -251,6 +258,47 @@ class ReturnFromCont extends Continuation
resume: (v) ->
@env.blockLookup @label, @kont, v
evaluateCatch = (tag, body, env, kont) ->
evaluate tag, env, (new CatchCont kont, body, env)
class CatchCont extends Continuation
constructor: (@kont, @body, @env) ->
resume: (value) ->
console.log(value)
evaluateBegin @body, @env, (new LabeledCont @kont, normalizeForm car value)
class LabeledCont extends Continuation
constructor: (@kont, @tag) ->
resume: (value) ->
@kont.resume value
catchLookup: (tag, kk) ->
if tag == @tag
console.log tag, @tag
evaluate kk.form, kk.env, (new ThrowingCont kk, tag, this)
else
@kont.catchLookup tag, kk
class ThrowCont extends Continuation
constructor: (@kont, @form, @env) ->
resume: (value) ->
@catchLookup (normalizeForm car value), @
evaluateThrow = (tag, form, env, kont) ->
evaluate tag, env, (new ThrowCont kont, form, env)
class ThrowingCont extends Continuation
constructor: (@kont, @tag, @resumecont) ->
resume: (value) ->
@resumecont.resume value
evaluateUnwindProtect = (form, cleanup, env, kont) ->
evaluate form, env, (new UnwindProtectCont kont, cleanup, env)
class UnwindProtectCont extends Continuation
constructor: (@kont, @cleanup, @env) ->
resume: (value) ->
evaluateBegin @cleanup, @env, (new ProtectReturnCont @kont, value)
class Primitive extends Value
constructor: (@name, @nativ) ->
@_type = "Primitive"
@ -302,8 +350,8 @@ defprimitive = (name, nativ, arity) ->
vmargs = listToVector(args)
if (vmargs.length == arity)
kont.resume (nativ.apply null, vmargs)
else
throw new LispInterpreterError "Incorrect arity"
else
throw new LispInterpreterError "Incorrect arity"
defpredicate = (name, nativ, arity) ->
defprimitive name, ((a, b) -> if nativ.call(null, a, b) then true else the_false_value), arity
@ -348,6 +396,12 @@ definitial "apply", new Primitive "apply", (values, env, kont) ->
if nilp (cdr args) then (car args) else (cons (car args), (flat cdr args)))(cdr values))()
f.invoke args, env, kont
definitial "funcall", new Primitive "funcall", (args, env, kont) ->
if not nilp cdr args
@kont.invoke (car args), (cdr args)
else
throw new LispInterpreterError "Invoke requires a function name and arguments"
definitial "list", new Primitive "list", (values, env, kont) ->
(values, env, kont) -> kont.resume(values)

View File

@ -37,4 +37,60 @@ describe "Core interpreter #3: Blocks", ->
blockb = "((block a (* 2 (block b (return-from a (lambda (x) (return-from a x))))) ) 3 )"
expect(-> lisp read blockb).to.throw("Obsolete continuation")
describe "Core interpreter #3: Try/Catch", ->
it "doesn't change a simple value", ->
expect(lisp read "(catch 'bar 1)").to.equal(1)
it "doesn't interfere with standard behavior", ->
expect(lisp read "(catch 'bar 1 2 3)").to.equal(3)
it "bails at the top level when no catch", ->
expect(-> lisp read "(throw 'bar 33)").to.throw("No associated catch")
it "catches the right thing", ->
expect(lisp read "(catch 'bar (throw 'bar 11))").to.equal(11)
it "catches before the evaluation happens", ->
expect(lisp read "(catch 'bar (* 2 (throw 'bar 5)))").to.equal(5)
it "unrolls through multiple layers of the stack", ->
expect(lisp read "((lambda (f) (catch 'bar (* 2 (f 5))) ) (lambda (x) (throw 'bar x)))").to.equal(5)
it "continues at the right location", ->
expect(lisp read "((lambda (f) (catch 'bar (* 2 (catch 'bar (* 3 (f 5))))) ) (lambda (x) (throw 'bar x)))").to.equal(10)
it "throw/catch happens with unlabled catches", ->
expect(lisp read "(catch 2 (* 7 (catch 1 (* 3 (catch 2 (throw 1 (throw 2 5)) )) )))").to.equal(105)
it "bails at top level when there aren't enough catches", ->
expect(-> lisp read "(catch 2 (* 7 (throw 1 (throw 2 3))))").to.throw("no test")
# describe "Core interpreter #3: Unwind-Protect", ->
# it "protects the value correctly", ->
# expect(lisp read "(unwind-protect 1 2").to.equal(1)
# it "", ->
# expect(lisp read "((lambda (c) (unwind-protect 1 (set! c 2)) c ) 0 ").to.equal(2)
# it "", ->
# expect(lisp read "((lambda (c) (catch 111 (* 2 (unwind-protect (* 3 (throw 111 5)) (set! c 1) ))) ) 0 ").to.equal(5)
# it "", ->
# expect(lisp read "((lambda (c) (catch 111 (* 2 (unwind-protect (* 3 (throw 111 5)) (set! c 1) ))) c ) 0 ").to.equal(1)
# it "", ->
# expect(lisp read "((lambda (c) (block A (* 2 (unwind-protect (* 3 (return-from A 5)) (set! c 1) ))) ) 0 ").to.equal(5)
# it "", ->
# expect(lisp read "((lambda (c) (block A (* 2 (unwind-protect (* 3 (return-from A 5)) (set! c 1) ))) c ) 0 ").to.equal(1)
#
#
# describe "Core interpreter #3: Try/Catch with Throw as a function", ->
# contain = (fcall) ->
# return "(begin ((lambda () (begin (set! funcall (lambda (g . args) (apply g args))) #{fcall}))))"
#
# it "", ->
# expect(-> lisp read "(funcall throw 'bar 33").to.throw("bar")
# it "", ->
# expect(lisp read "(catch 'bar (funcall throw 'bar 11))").to.equal(11)
# it "", ->
# expect(lisp read "(catch 'bar (* 2 (funcall throw 'bar 5)))").to.equal(5)
# it "", ->in
# expect(lisp read "((lambda (f) (catch 'bar (* 2 (f 5))) ) (lambda (x) (funcall throw 'bar x))) ").to.equal(5)
# it "", ->
# expect(lisp read "((lambda (f) (catch 'bar (* 2 (catch 'bar (* 3 (f 5))))) ) (lambda (x) (funcall throw 'bar x)))) ").to.equal(10)
# it "", ->
# expect(lisp read "(catch 2 (* 7 (catch 1 (* 3 (catch 2 (funcall throw 1 (funcall throw 2 5)) )) ))) ").to.equal(105)
# it "", ->
# expect(lisp read "(catch 2 (* 7 (funcall throw 1 (funcall throw 2 3))))").to.equal(3)
#
#
#
#