[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 bin/cake
test-reports.xml test-reports.xml
LisperatorLanguage LisperatorLanguage
chapter?/test.coffee

View File

@ -1,8 +1,10 @@
{listToString, listToVector, pairp, cons, car, cdr, caar, cddr, cdar, {listToString, listToVector, pairp, cons, car, cdr, caar, cddr, cdar,
cadr, caadr, cadar, caddr, nilp, nil, setcdr, cadr, caadr, cadar, caddr, nilp, nil, setcdr,
metacadr, setcar} = require "cons-lists/lists" metacadr, setcar} = require "cons-lists/lists"
{normalizeForm} = require "../chapter1/astToList"
readline = require "readline" readline = require "readline"
{inspect} = require "util" {inspect} = require "util"
minspect = (obj) -> inspect obj, false, null, true
class LispInterpreterError extends Error class LispInterpreterError extends Error
name: 'LispInterpreterError' name: 'LispInterpreterError'
@ -28,6 +30,8 @@ class Continuation
throw new LispInterpreterError "Continuations expect one argument" throw new LispInterpreterError "Continuations expect one argument"
unwind: (value, ktarget) -> unwind: (value, ktarget) ->
if (@kont == ktarget) then (@kont.resume value) else (@kont.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 # Abstract class representing the environment
@ -93,6 +97,7 @@ evaluateQuote = (v, env, kont) ->
evaluateIf = (exps, env, kont) -> evaluateIf = (exps, env, kont) ->
evaluate (car exps), env, new IfCont(kont, (cadr exps), (caddr exps), env) evaluate (car exps), env, new IfCont(kont, (cadr exps), (caddr exps), env)
class IfCont extends Continuation class IfCont extends Continuation
constructor: (@kont, @ift, @iff, @env) -> constructor: (@kont, @ift, @iff, @env) ->
@_type = "IfCont" @_type = "IfCont"
@ -224,6 +229,8 @@ class BottomCont extends Continuation
@func(value) @func(value)
unwind: (value, ktarget) -> unwind: (value, ktarget) ->
throw new LispInterpreterError "Obsolete continuation" throw new LispInterpreterError "Obsolete continuation"
catchLookup: (tag, kk) ->
throw new LispInterpreterError "No associated catch"
evaluateBlock = (label, body, env, kont) -> evaluateBlock = (label, body, env, kont) ->
k = new BlockCont(kont, label) k = new BlockCont(kont, label)
@ -251,6 +258,47 @@ class ReturnFromCont extends Continuation
resume: (v) -> resume: (v) ->
@env.blockLookup @label, @kont, 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 class Primitive extends Value
constructor: (@name, @nativ) -> constructor: (@name, @nativ) ->
@_type = "Primitive" @_type = "Primitive"
@ -303,7 +351,7 @@ defprimitive = (name, nativ, arity) ->
if (vmargs.length == arity) if (vmargs.length == arity)
kont.resume (nativ.apply null, vmargs) kont.resume (nativ.apply null, vmargs)
else else
throw new LispInterpreterError "Incorrect arity" throw new LispInterpreterError "Incorrect arity"
defpredicate = (name, nativ, arity) -> defpredicate = (name, nativ, arity) ->
defprimitive name, ((a, b) -> if nativ.call(null, a, b) then true else the_false_value), 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))() if nilp (cdr args) then (car args) else (cons (car args), (flat cdr args)))(cdr values))()
f.invoke args, env, kont 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) -> definitial "list", new Primitive "list", (values, env, kont) ->
(values, env, kont) -> kont.resume(values) (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 )" blockb = "((block a (* 2 (block b (return-from a (lambda (x) (return-from a x))))) ) 3 )"
expect(-> lisp read blockb).to.throw("Obsolete continuation") 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)
#
#
#
#