Skip to content
Phil Hagelberg edited this page Mar 18, 2022 · 1 revision

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
Clone this wiki locally