-
Notifications
You must be signed in to change notification settings - Fork 126
Block return
Common Lisp has a notion of named blocks of code, that one may quickly jump out.
It is achieved by using block
and return-from
forms.
Fennel doesn't have a return
statement, and its control flow options are limited when compared to Lua, but we can achieve a functionality similar to Common Lisp's named blocks by using pcall
:
(macro return-from [name ...]
`(error {:target ,name
:data (#(doto [$...] (tset :n (select "#" $...))) ,...)}))
(macro block [name ...]
`(let [,name (setmetatable {} {:__name "block"
:__fennelview #(.. "#<" (_G.tostring $) ">")})
pack# #(doto [$...] (tset :n (select "#" $...)))
unpack# (or _G.table.unpack _G.unpack)
(ok# res#) ,(if (. (get-scope) :vararg)
`(pcall (fn [...] (pack# (do ,...))) ...)
`(pcall (fn [] (pack# (do ,...)))))]
(if ok#
(unpack# res# 1 res#.n)
(match res#
{:target ,name
:data data#}
(unpack# data# 1 data#.n)
_# (error _#)))))
Blocks are lexically scoped values, representing a point to which it's possible to return with the return-from
function.
Blocks can be passed around as closures, but can't be returned to if execution left the "dynamic" scope of the block.
For example:
>> (block foo
(print "entered foo")
(block bar
(print "entered bar")
(block baz
(print "entered baz")
(return-from bar)
(print "exit baz"))
(print "exit bar"))
(print "exit foo"))
entered foo
entered bar
entered baz
exit foo
In the example above, we've preemptively left the blocks baz
and bar
, landing in the foo
block.
As was mentioned, blocks can be lexically stored, or passed to other functions:
>> (fn baz [block-name]
(print "entered baz")
(return-from block-name)
(print "exited baz"))
#<function: 0x55ada310e0c0>
>> (fn bar [block-name]
(print "entered bar")
(baz block-name)
(print "exited bar"))
#<function: 0x55ada312a3f0>
>> (fn foo []
(print "entered foo")
(block foo
(bar foo))
(print "exited foo"))
#<function: 0x55ada31e2ec0>
>> (foo)
entered foo
entered bar
entered baz
exited foo