summaryrefslogtreecommitdiff
path: root/init-macros.fnl
diff options
context:
space:
mode:
Diffstat (limited to 'init-macros.fnl')
-rw-r--r--init-macros.fnl1074
1 files changed, 0 insertions, 1074 deletions
diff --git a/init-macros.fnl b/init-macros.fnl
deleted file mode 100644
index 3c54fec..0000000
--- a/init-macros.fnl
+++ /dev/null
@@ -1,1074 +0,0 @@
-(comment
- "MIT License
-
-Copyright (c) 2022 Andrey Listopadov
-
-Permission is hereby granted‚ free of charge‚ to any person obtaining a copy
-of this software and associated documentation files (the “Software”)‚ to deal
-in the Software without restriction‚ including without limitation the rights
-to use‚ copy‚ modify‚ merge‚ publish‚ distribute‚ sublicense‚ and/or sell
-copies of the Software‚ and to permit persons to whom the Software is
-furnished to do so‚ subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED “AS IS”‚ WITHOUT WARRANTY OF ANY KIND‚ EXPRESS OR
-IMPLIED‚ INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY‚
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM‚ DAMAGES OR OTHER
-LIABILITY‚ WHETHER IN AN ACTION OF CONTRACT‚ TORT OR OTHERWISE‚ ARISING FROM‚
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.")
-
-(local core
- (if (and ... (string.match ... "init%-macros$"))
- (string.gsub ... "init%-macros$" "init")
- (or ... :init)))
-
-(fn string? [x]
- (= :string (type x)))
-
-(fn has? [tbl sym]
- ;; searches for the given symbol in a table.
- (var has false)
- (each [_ elt (ipairs tbl) :until has]
- (set has (= sym elt)))
- has)
-
-;;; ns
-
-(local cljlib-namespaces
- {}
- ;; A map of files and their respective namespaces. Each entry is a
- ;; filename followed by a table with two keys: `:current` and
- ;; `:known`. The second one holds all namespaces that were defined
- ;; for the file via the `ns` macro, and thus are available to switch
- ;; with the `in-ns` macro. The `:current` key represents currently
- ;; active namespace that is used for binding via the `def` macro and
- ;; its derivatives.
- )
-
-(fn current-file [ast]
- (. (ast-source ast) :filename))
-
-(fn create-ns [name]
- (let [file (current-file name)]
- (when (not (. cljlib-namespaces file))
- (tset cljlib-namespaces file {:known {}}))
- (tset cljlib-namespaces file :current name)
- (tset cljlib-namespaces file :known (tostring name) true))
- `(setmetatable
- {}
- {:__name "namespace"
- :__fennelview #(do ,(: "#<namespace: %s>" :format (tostring name)))}))
-
-(fn known-ns? [name]
- (let [file (current-file name)]
- (?. cljlib-namespaces file :known (tostring name))))
-
-(fn current-ns [ast]
- (?. cljlib-namespaces (current-file ast) :current))
-
-(fn in-ns [name]
- "Sets the compile-time variable `cljlib-namespaces` to the given `name`.
-Affects such macros as `def`, `defn`, which will bind names to the
-specified namespace.
-
-# Examples
-Creating several namespaces in the file, and defining functions in each:
-
-``` fennel
-(ns a)
-(defn f [] \"f from a\")
-(ns b)
-(defn f [] \"f from b\")
-(in-ns a)
-(defn g [] \"g from a\")
-(in-ns b)
-(defn g [] \"g from b\")
-
-(assert-eq (a.f) \"f from a\")
-(assert-eq (b.f) \"f from b\")
-(assert-eq (a.g) \"g from a\")
-(assert-eq (b.g) \"g from b\")
-```
-
-Note, switching namespaces in the REPL doesn't affect non-namespaced
-local bindings. In other words, when defining a local with `def`, a
-bot a local binding and a namespaced binding are created, and
-switching current namespace won't change the local binding:
-
-``` fennel :skip-test
->> (ns foo)
-nil
->> (def x 42)
-nil
->> (ns bar)
-nil
->> (def x 1337)
-nil
->> (in-ns foo)
-#<namespace: foo>
->> x ; user might have expected to see 42 here
-1337
->> foo.x
-42
->> bar.x
-1337
-```
-
-Sadly, Fennel itself has no support for namespace switching in REPL,
-so this feature can be only partially emulated by the cljlib library.
-"
- (assert-compile (known-ns? name)
- (: "no such namespace: %s" :format (tostring name))
- name)
- (tset cljlib-namespaces (current-file name) :current name)
- name)
-
-(fn ns [name commentary requirements]
- "Namespace declaration macro.
-Accepts the `name` of the generated namespace, and creates a local
-variable with this name holding a table. Optionally accepts
-`commentary` describing what namespace is about and a `requirements`
-spec, specifying what libraries should be required.
-
-The `requirements` spec is a list that consists of vectors, specifying
-library name and a possible alias or a vector of names to refer to
-without a prefix:
-
-``` fennel :skip-test
-(ns some-namespace
- \"Description of the some-namespace.\"
- (:require [some.lib]
- [some.other.lib :as lib2]
- [another.lib :refer [foo bar baz]]))
-
-(defn inc [x] (+ x 1))
-```
-
-Which is equivalent to:
-
-``` fennel :skip-test
-(local some-namespace {})
-(local lib (require :some.lib))
-(local lib2 (require :some.other.lib))
-(local {:bar bar :baz baz :foo foo} (require :another.lib))
-(comment \"Description of the some-namespace.\")
-```
-
-Note that when no `:as` alias is given, the library will be named
-after the innermost part of the require path, i.e. `some.lib` is
-transformed to `lib`.
-
-See `in-ns` on how to switch namespaces."
- (let [bind-table [name]
- require-table [(create-ns name)]
- requirements (if (string? commentary)
- requirements
- commentary)]
- (match requirements
- [:require & requires]
- (each [_ spec (ipairs requires)]
- (match spec
- (where (or [module :as alias :refer names]
- [module :refer names :as alias]))
- (do (table.insert bind-table (collect [_ name (ipairs names) :into {'&as alias}]
- (values (tostring name) name)))
- (table.insert require-table `(require ,(tostring module))))
- [module :as alias]
- (do (table.insert bind-table alias)
- (table.insert require-table `(require ,(tostring module))))
- [module :refer names]
- (do (table.insert bind-table (collect [_ name (ipairs names)]
- (values (tostring name) name)))
- (table.insert require-table `(require ,(tostring module))))
- [module]
- (do (->> (string.gsub (tostring module) ".+%.(.-)$" "%1")
- (pick-values 1)
- sym
- (table.insert bind-table))
- (table.insert require-table `(require ,(tostring module))))
- _ (assert-compile false "wrong require syntax" name)))
- nil nil
- _ (assert-compile false "wrong require syntax" name))
- (if (string? commentary)
- `(local ,bind-table
- (values ,require-table (comment ,commentary)))
- `(local ,bind-table ,require-table))))
-
-;;; def
-
-(fn def [...]
- {:fnl/docstring "Name binding macro similar to `local` but acts in terms of current
-namespace set with the `ns` macro, unless `:private` was passed before
-the binding name. Accepts the `name` to be bound and the `initializer`
-expression. `meta` can be either an associative table where keys are
-strings, or a string representing a key from the table. If a sole
-string is given, its value is set to `true` in the meta table."
- :fnl/arglist [([name initializer]) ([meta name initializer])]}
- (match [...]
- (where (or [:private name val]
- [{:private true} name val]))
- `(local ,name ,val)
- [name val]
- (let [namespace (current-ns name)]
- (if (in-scope? namespace)
- `(local ,name
- (let [v# ,val]
- (tset ,namespace ,(tostring name) v#)
- v#))
- `(local ,name ,val)))))
-
-;;; defn
-
-(local errors
- {:vararg "... is't allowed in the arglist, use & destructuring"
- :same-arity "Can't have 2 overloads with same arity"
- :arity-order "Overloads must be sorted by arities"
- :amp-arity "Variadic overload must be the last overload"
- :extra-rest-args "Only one argument allowed after &"
- :wrong-arg-amount "Wrong number of args (%s) passed to %s"
- :extra-amp "Can't have more than 1 variadic overload"})
-
-(fn first [[x]] x)
-(fn rest [[_ & xs]] xs)
-(fn vfirst [x] x)
-(fn vrest [_ ...] ...)
-
-(fn length* [arglist]
- ;; Gets "length" of variadic arglist, stopping at first & plus 1 arg.
- ;; Additionally checks whether there are more than one arg after &.
- (var (l amp? n) (values 0 false nil))
- (each [i arg (ipairs arglist) :until amp?]
- (if (= arg '&)
- (set (amp? n) (values true i))
- (set l (+ l 1))))
- (when n
- (assert-compile (= (length arglist) (+ n 1))
- errors.extra-rest-args
- (. arglist (length arglist))))
- (if amp? (+ l 1) l))
-
-(fn check-arglists [arglists]
- ;; performs a check that arglists are ordered correctly, and that
- ;; only one of multiarity arglists has the & symbol, additionally
- ;; checking for a presence of the multiple-values symbol.
- (var (size amp?) (values -1 false))
- (each [_ [arglist] (ipairs arglists)]
- (assert-compile (not (has? arglist '...)) errors.vararg arglist)
- (let [len (length* arglist)]
- (assert-compile (not= size len) errors.same-arity arglist)
- (assert-compile (< size len) errors.arity-order arglist)
- (assert-compile (not amp?) (if (has? arglist '&)
- errors.extra-amp
- errors.amp-arity) arglist)
- (set size len)
- (set amp? (has? arglist '&)))))
-
-(fn with-immutable-rest [arglist body]
- `(let [core# (require ,core)
- ,arglist (core#.list ...)]
- ,(unpack body)))
-
-(fn add-missing-arities! [arglists name]
- "Adds missing arity overloads for given `arglists`.
-For example, given the [[[a] body] [[a b c] body]], will generate
-[[[] error]
- [[a] body]
- [[arg_1_ arg_2_] error]
- [[a b c] body]]
-
-Because inital arglist didn't specify arities of 0 and 2."
- (for [i (- (length* arglists) 1) 1 -1]
- (let [current-args (first (. arglists i))
- current-len (length* current-args)
- next-args (first (. arglists (+ i 1)))
- next-len (length* next-args)
- next-len (if (has? next-args '&) (- next-len 1) next-len)]
- (when (not= (+ current-len 1) next-len)
- (for [len (- next-len 1) (+ current-len 1) -1]
- (table.insert arglists (+ i 1) [(fcollect [i 1 len :into {:fake true}] (gensym :arg))
- `(error (: ,errors.wrong-arg-amount :format ,len ,(tostring name)))])))))
- (while (not= 0 (length* (first (first arglists))))
- (let [len (- (length* (first (first arglists))) 1)]
- (table.insert arglists 1 [(fcollect [i 1 len :into {:fake true}] (gensym :arg))
- `(error (: ,errors.wrong-arg-amount :format ,len ,(tostring name)))]))))
-
-;; TODO: implement pre-post conditions
-(fn gen-match-fn [name doc arglists]
- ;; automated multi-arity dispatch generator
- (check-arglists arglists)
- (add-missing-arities! arglists name)
- (let [match-body `(match (select :# ...))]
- (var variadic? false)
- (each [_ [arglist & body] (ipairs arglists)]
- (table.insert match-body (if (has? arglist '&)
- (do (set variadic? true) (sym :_))
- (length arglist)))
- (table.insert match-body (if variadic?
- (with-immutable-rest arglist body)
- (if (and (> (length arglist) 0) (not arglist.fake))
- `(let [(,(unpack arglist)) (values ...)]
- ,(if (> (length body) 0)
- (unpack body)
- 'nil))
- `(do ,(unpack body))))))
- (when (not variadic?)
- (table.insert match-body (sym :_))
- (table.insert match-body
- `(error (: ,errors.wrong-arg-amount :format ,(sym :_) ,(tostring name)))))
- `(fn ,name [...]
- {:fnl/docstring ,doc
- :fnl/arglist ,(icollect [_ [arglist] (ipairs arglists)]
- (when (not arglist.fake)
- (list (sequence (unpack arglist)))))}
- ,match-body)))
-
-;; TODO: implement pre-post conditions
-(fn gen-fn [name doc arglist _pre-post body]
- (check-arglists [[arglist]])
- `(fn ,name [...]
- {:fnl/docstring ,doc
- :fnl/arglist ,(sequence arglist)}
- ,(if (has? arglist '&)
- (with-immutable-rest arglist [body])
- `(let ,(if (> (length arglist) 0)
- `[(,(unpack arglist)) (values ...)]
- `[])
- (let [cnt# (select "#" ...)]
- (when (not= ,(length arglist) cnt#)
- (error (: ,errors.wrong-arg-amount :format cnt# ,(tostring name)))))
- ,body))))
-
-(fn fn* [...]
- {:fnl/docstring
- "Clojure-inspired `fn' macro for defining functions.
-Accepts an optional `name` and `docstring?`, followed by the binding
-list containing function's `params*`. The `body` is wrapped in an
-implicit `do`. The `doc-string?` argument specifies an optional
-documentation for the function. Supports multi-arity dispatching via
-the following syntax:
-
-(fn* optional-name
- optional-docstring
- ([arity1] body1)
- ([other arity2] body2))
-
-Accepts `pre-post?` conditions in a form of a table after argument
-list:
-
-(fn* optional-name
- optional-docstring
- [arg1 arg2]
- {:pre [(check1 arg1 arg2) (check2 arg1)]
- :post [(check1 $) ... (checkN $)]}
- body)
-
-The same syntax applies to multi-arity version.
-
-(pre- and post-checks are not yet implemented)"
- :fnl/arglist [([name doc-string? [params*] pre-post? body])
- ([name doc-string? ([params*] pre-post? body)+])]}
- (let [{: name? : doc? : args : pre-post? : body : multi-arity?}
- ;; descent into maddness
- (match (values ...)
- (where (name docstring [[] &as arity])
- (and (sym? name)
- (string? docstring)
- (list? arity)))
- {:pat '(fn* foo "bar" ([baz]) ...)
- :name? name
- :doc? docstring
- :args [arity (select 4 ...)]
- :multi-arity? true}
- (where (name [[] &as arity])
- (and (sym? name)
- (list? arity)))
- {:pat '(fn* foo ([baz]) ...)
- :name? name
- :args [arity (select 3 ...)]
- :multi-arity? true}
- (where (docstring [[] &as arity])
- (and (string? docstring)
- (list? arity)))
- {:pat '(fn* "bar" ([baz]) ...)
- :name? (gensym :fn)
- :doc? docstring
- :args [arity (select 3 ...)]
- :multi-arity? true}
- (where ([[] &as arity])
- (list? arity))
- {:pat '(fn* ([baz]) ...)
- :name? (gensym :fn)
- :args [arity (select 2 ...)]
- :multi-arity? true}
- (where (name docstring args {&as pre-post})
- (and (sym? name)
- (string? docstring)
- (sequence? args)
- (or (not= nil pre-post.pre)
- (not= nil pre-post.post))))
- {:pat '(fn* foo "foo" [baz] {:pre qux :post quux} ...)
- :name? name
- :doc? docstring
- :args args
- :pre-post? pre-post
- :body [(select 5 ...)]}
- (where (name docstring args)
- (and (sym? name)
- (string? docstring)
- (sequence? args)))
- {:pat '(fn* foo "foo" [baz] ...)
- :name? name
- :doc? docstring
- :args args
- :body [(select 4 ...)]}
- (where (name args {&as pre-post})
- (and (sym? name)
- (sequence? args)
- (or (not= nil pre-post.pre)
- (not= nil pre-post.post))))
- {:pat '(fn* foo [baz] {:pre qux :post quux} ...)
- :name? name
- :args args
- :pre-post? pre-post
- :body [(select 4 ...)]}
- (where (name args)
- (and (sym? name) (sequence? args)))
- {:pat '(fn* foo [baz] ...)
- :name? name
- :args args
- :body [(select 3 ...)]}
- (where (docstring args {&as pre-post})
- (and (string? docstring)
- (sequence? args)
- (or (not= nil pre-post.pre)
- (not= nil pre-post.post))))
- {:pat '(fn* "bar" [baz] {:pre qux :post quux} ...)
- :name? (gensym :fn)
- :doc? docstring
- :args args
- :pre-post? pre-post
- :body [(select 4 ...)]}
- (where (docstring args)
- (and (string? docstring)
- (sequence? args)))
- {:pat '(fn* "bar" [baz] ...)
- :name? (gensym :fn)
- :doc? docstring
- :args args
- :body [(select 3 ...)]}
- (where (args {&as pre-post})
- (and (sequence? args)
- (or (not= nil pre-post.pre)
- (not= nil pre-post.post))))
- {:pat '(fn* [baz] {:pre qux :post quux} ...)
- :name? (gensym :fn)
- :args args
- :pre-post? pre-post
- :body [(select 3 ...)]}
- (where (args)
- (sequence? args))
- {:pat '(fn* [baz] ...)
- :name? (gensym :fn)
- :args args
- :body [(select 2 ...)]}
- _ (assert-compile (string.format
- "Expression %s didn't match any pattern."
- (view `(fn* ,...)))))]
- (if multi-arity?
- (gen-match-fn name? doc? args)
- (gen-fn name? doc? args pre-post? `(do ,(unpack body))))))
-
-(fn defn [name ...]
- {:fnl/docstring
- "Same as `(def name (fn* name docstring? [params*] pre-post? exprs*))`
-or `(def name (fn* name docstring? ([params*] pre-post? exprs*)+))`
-with any doc-string or attrs added to the function metadata. Accepts
-`name` which will be used to refer to a function in the current
-namespace, and optional `doc-string?`, a vector of function's
-`params*`, `pre-post?` conditions, and the `body` of the function.
-The body is wrapped in an implicit do. See `fn*` for more info."
- :fnl/arglist [([name doc-string? [params*] pre-post? body])
- ([name doc-string? ([params*] pre-post? body)+])]}
- (assert-compile (sym? name) "expected a function name, use `fn*` for anonymous functions" name)
- (def name (fn* name ...)))
-
-(fn defn- [name ...]
- {:fnl/docstring
- "Same as `(def :private name (fn* name docstring? [params*] pre-post?
-exprs*))` or `(def :private name (fn* name docstring? ([params*]
-pre-post? exprs*)+))` with any doc-string or attrs added to the
-function metadata. Accepts `name` which will be used to refer to a
-function, and optional `doc-string?`, a vector of function's
-`params*`, `pre-post?` conditions, and the `body` of the function.
-The body is wrapped in an implicit do. See `fn*` for more info."
- :fnl/arglist [([name doc-string? [params*] pre-post? body])
- ([name doc-string? ([params*] pre-post? body)+])]}
- (assert-compile (sym? name) "expected a function name, use `fn*` for anonymous functions" name)
- (def :private name (fn* name ...)))
-
-;;; Time
-
-(fn time [expr]
- "Measure the CPU time spent executing `expr`."
- `(let [c# os.clock
- pack# #(doto [$...] (tset :n (select "#" $...)))
- s# (c#)
- res# (pack# ,expr)
- e# (c#)]
- (print (.. "Elapsed time: " (* (- e# s#) 1000) " msecs"))
- ((or table.unpack _G.unpack) res# 1 res#.n)))
-
-;;; let variants
-
-(fn when-let [[name test] ...]
- {:fnl/docstring "When `test` is logical `true`, evaluates the `body` with `name` bound
-to the value of `test`."
- :fnl/arglist [[name test] & body]}
- `(let [val# ,test]
- (if val#
- (let [,name val#]
- ,...))))
-
-(fn if-let [[name test] if-branch else-branch ...]
- {:fnl/docstring "When `test` is logical `true`, evaluates the `if-branch` with `name`
-bound to the value of `test`. Otherwise, evaluates the `else-branch`"
- :fnl/arglist [[name test] if-branch else-branch]}
- (assert-compile (= 0 (select "#" ...)) "too many arguments to if-let" ...)
- `(let [val# ,test]
- (if val#
- (let [,name val#]
- ,if-branch)
- ,else-branch)))
-
-(fn when-some [[name test] ...]
- {:fnl/docstring "When `test` is not `nil`, evaluates the `body` with `name` bound to
-the value of `test`."
- :fnl/arglist [[name test] & body]}
- `(let [val# ,test]
- (if (not= nil val#)
- (let [,name val#]
- ,...))))
-
-(fn if-some [[name test] if-branch else-branch ...]
- {:fnl/docstring "When `test` is not `nil`, evaluates the `if-branch` with `name`
-bound to the value of `test`. Otherwise, evaluates the `else-branch`"
- :fnl/arglist [[name test] if-branch else-branch]}
- (assert-compile (= 0 (select "#" ...)) "too many arguments to if-some" ...)
- `(let [val# ,test]
- (if (not= nil val#)
- (let [,name val#]
- ,if-branch)
- ,else-branch)))
-
-;;; Multimethods
-
-(fn defmulti [...]
- {:fnl/arglist [name docstring? dispatch-fn options*]
- :fnl/docstring "Create multifunction `name' with runtime dispatching based on results
-from `dispatch-fn'. Returns a proxy table with `__call` metamethod,
-that calls `dispatch-fn' on its arguments. Amount of arguments
-passed, should be the same as accepted by `dispatch-fn'. Looks for
-multimethod based on result from `dispatch-fn'.
-
-Accepts optional `docstring?', and `options*' arguments, where
-`options*' is a sequence of key value pairs representing additional
-attributes. Supported options:
-
-`:default` - the default dispatch value, defaults to `:default`.
-
-By default, multifunction has no multimethods, see
-`defmethod' on how to add one."}
- (let [[name & options] (if (> (select :# ...) 0) [...]
- (error "wrong argument amount for defmulti"))
- docstring (if (string? (first options)) (first options))
- options (if docstring (rest options) options)
- dispatch-fn (first options)
- options* (rest options)]
- (assert (= (% (length options*) 2) 0) "wrong argument amount for defmulti")
- (let [options {}]
- (for [i 1 (length options*) 2]
- (tset options (. options* i) (. options* (+ i 1))))
- (def name
- `(let [pairs# (fn [t#]
- (match (getmetatable t#)
- {:__pairs p#} (p# t#)
- ,(sym :_) (pairs t#)))
- {:eq eq#} (require ,core)]
- (setmetatable
- {}
- {:__index (fn [t# key#]
- (accumulate [res# nil
- k# v# (pairs# t#)
- :until res#]
- (when (eq# k# key#)
- v#)))
- :__call
- (fn [t# ...]
- ,docstring
- (let [dispatch-value# (,dispatch-fn ...)
- view# (match (pcall require :fennel)
- (true fennel#) #(fennel#.view $ {:one-line true})
- ,(sym :_) tostring)]
- ((or (. t# dispatch-value#)
- (. t# (or (. ,options :default) :default))
- (error (.. "No method in multimethod '"
- ,(tostring name)
- "' for dispatch value: "
- (view# dispatch-value#))
- 2)) ...)))
- :__name (.. "multifn " ,(tostring name))
- :__fennelview tostring
- :cljlib/type :multifn}))))))
-
-(fn defmethod [multifn dispatch-val ...]
- {:fnl/arglist [multi-fn dispatch-value fnspec]
- :fnl/docstring "Attach new method to multi-function dispatch value. Accepts the
-`multi-fn' as its first argument, the `dispatch-value' as second, and
-`fnspec' - a function tail starting from argument list, followed by
-function body as in `fn*'.
-
-# Examples
-Here are some examples how multimethods can be used.
-
-## Factorial example
-Key idea here is that multimethods can call itself with different
-values, and will dispatch correctly. Here, `fac' recursively calls
-itself with less and less number until it reaches `0` and dispatches
-to another multimethod:
-
-``` fennel
-(ns test)
-
-(defmulti fac (fn [x] x))
-
-(defmethod fac 0 [_] 1)
-(defmethod fac :default [x] (* x (fac (- x 1))))
-
-(assert-eq (fac 4) 24)
-```
-
-`:default` is a special method which gets called when no other methods
-were found for given dispatch value.
-
-## Multi-arity dispatching
-Multi-arity function tails are also supported:
-
-``` fennel
-(ns test)
-
-(defmulti foo (fn* ([x] [x]) ([x y] [x y])))
-
-(defmethod foo [10] [_] (print \"I knew I'll get 10\"))
-(defmethod foo [10 20] [_ _] (print \"I knew I'll get both 10 and 20\"))
-(defmethod foo :default ([x] (print (.. \"Umm, got\" x)))
- ([x y] (print (.. \"Umm, got both \" x \" and \" y))))
-```
-
-Calling `(foo 10)` will print `\"I knew I'll get 10\"`, and calling
-`(foo 10 20)` will print `\"I knew I'll get both 10 and 20\"`.
-However, calling `foo' with any other numbers will default either to
-`\"Umm, got x\"` message, when called with single value, and `\"Umm, got
-both x and y\"` when calling with two values.
-
-## Dispatching on object's type
-We can dispatch based on types the same way we dispatch on values.
-For example, here's a naive conversion from Fennel's notation for
-tables to Lua's one:
-
-``` fennel
-(ns test)
-
-(defmulti to-lua-str (fn [x] (type x)))
-
-(defmethod to-lua-str :number [x] (tostring x))
-(defmethod to-lua-str :table [x]
- (let [res []]
- (each [k v (pairs x)]
- (table.insert res (.. \"[\" (to-lua-str k) \"] = \" (to-lua-str v))))
- (.. \"{\" (table.concat res \", \") \"}\")))
-(defmethod to-lua-str :string [x] (.. \"\\\"\" x \"\\\"\"))
-(defmethod to-lua-str :default [x] (tostring x))
-
-(assert-eq (to-lua-str {:a {:b 10}}) \"{[\\\"a\\\"] = {[\\\"b\\\"] = 10}}\")
-
-(assert-eq (to-lua-str [:a :b :c [:d {:e :f}]])
- \"{[1] = \\\"a\\\", [2] = \\\"b\\\", [3] = \\\"c\\\", [4] = {[1] = \\\"d\\\", [2] = {[\\\"e\\\"] = \\\"f\\\"}}}\")
-```
-
-And if we call it on some table, we'll get a valid Lua table, which we
-can then reformat as we want and use in Lua.
-
-All of this can be done with functions, and single entry point
-function, that uses if statement and branches on the type, however one
-of the additional features of multimethods, is that separate libraries
-can extend such multimethod by adding additional claues to it without
-needing to patch the source of the function. For example later on
-support for userdata or coroutines can be added to `to-lua-str'
-function as a separate multimethods for respective types."}
- (when (= (select :# ...) 0) (error "wrong argument amount for defmethod"))
- `(let [dispatch# ,dispatch-val
- multifn# ,multifn]
- (and (not (. multifn# dispatch#))
- (doto multifn#
- (tset dispatch# ,(fn* ...))))))
-
-;;; loop
-
-(fn assert-tail [tail-sym body]
- "Asserts that the passed in tail-sym function is a tail-call position of the
-passed-in body.
-
-Throws an error if it is in a position to be returned or if the function is
-situated to be called from a position other than the tail of the passed-in
-body."
- (fn last-arg? [form i]
- (= (- (length form) 1) i))
-
- ;; Tail in special forms are (After macroexpanding):
- ;;
- ;; - Every second form in an if, or the last form
- ;; (if ... (sym ...) (sym ...))
- ;;
- ;; - Last form in a let
- ;; (let [] (sym ...))
- ;;
- ;; - Last form in a do
- ;; (do ... (sym ...))
- ;;
- ;; Anything else fails the assert
- (fn path-tail? [op i form]
- (if (= op 'if) (and (not= 1 i) (or (last-arg? form i) (= 0 (% i 2))))
- (= op 'let) (last-arg? form i)
- (= op 'do) (last-arg? form i)
- false))
-
- ;; Check the current form for the tail-sym, and if it's in a bad
- ;; place, error out. If we run into other forms, we recurse with the
- ;; comprehension if this is the tail form or not
- (fn walk [body ok]
- (let [[op & operands] body]
- (if (list? op) (walk op true)
- (assert-compile (not (and (= tail-sym op) (not ok)))
- (.. (tostring tail-sym) " must be in tail position")
- op)
- (each [i v (ipairs operands)]
- (if (list? v) (walk v (and ok (path-tail? op i body)))
- (assert-compile (not= tail-sym v)
- (.. (tostring tail-sym) " must not be passed")
- v))))))
-
- (walk `(do ,(macroexpand body)) true))
-
-
-(fn loop [binding-vec ...]
- {:fnl/arglist [binding-vec body*]
- :fnl/docstring "Recursive loop macro.
-
-Similar to `let`, but binds a special `recur` call that will reassign
-the values of the `binding-vec` and restart the loop `body*`. Unlike
-`let`, doesn't support multiple-value destructuring.
-
-The first argument is a binding table with alternating symbols (or destructure
-forms), and the values to bind to them.
-
-For example:
-
-``` fennel
-(loop [[first & rest] [1 2 3 4 5]
- i 0]
- (if (= nil first)
- i
- (recur rest (+ 1 i))))
-```
-
-This would destructure the first table argument, with the first value inside it
-being assigned to `first` and the remainder of the table being assigned to
-`rest`. `i` simply gets bound to 0.
-
-The body of the form executes for every item in the table, calling `recur` each
-time with the table lacking its head element (thus consuming one element per
-iteration), and with `i` being called with one value greater than the previous.
-
-When the loop terminates (When the user doesn't call `recur`) it will return the
-number of elements in the passed in table. (In this case, 5)
-
-# Limitations
-
-In order to only evaluate expressions once and support sequential
-bindings, the binding table has to be transformed like this:
-
-``` fennel :skip-test
-(loop [[x & xs] (foo)
- y (+ x 1)]
- ...)
-
-(let [_1_ (foo)
- [x & xs] _1_
- _2_ (+ x 1)
- y _2_]
- ((fn recur [[x & xs] y] ...) _1_ _2_)
-```
-
-This ensures that `foo` is called only once, its result is cached in a
-`sym1#` binding, and that `y` can use the destructured value, obtained
-from that binding. The value of this binding is later passed to the
-function to begin the first iteration.
-
-This has two unfortunate consequences. One is that the initial
-destructuring happens twice - first, to make sure that later bindings
-can be properly initialized, and second, when the first looping
-function call happens. Another one is that as a result, `loop` macro
-can't work with multiple-value destructuring, because these can't be
-cached as described above. E.g. this will not work:
-
-``` fennel :skip-test
-(loop [(x y) (foo)] ...)
-```
-
-Because it would be transformed to:
-
-``` fennel :skip-test
-(let [_1_ (foo)
- (x y) _1_]
- ((fn recur [(x y)] ...) _1_)
-```
-
-`x` is correctly set, but `y` is completely lost. Therefore, this
-macro checks for lists in bindings."}
- (let [recur (sym :recur)
- keys []
- gensyms []
- bindings []]
- (assert-tail recur ...)
- (each [i v (ipairs binding-vec)]
- (when (= 0 (% i 2))
- (let [key (. binding-vec (- i 1))
- gs (gensym (tostring i))]
- (assert-compile (not (list? key)) "loop macro doesn't support multiple-value destructuring" key)
- ;; [sym1# sym2# etc...], for the function application below
- (table.insert gensyms gs)
-
- ;; let bindings
- (table.insert bindings gs) ;; sym1#
- (table.insert bindings v) ;; (expression)
- (table.insert bindings key) ;; [first & rest]
- (table.insert bindings gs) ;; sym1#
-
- ;; The gensyms we use for function application
- (table.insert keys key))))
- `(let ,bindings
- ((fn ,recur ,keys
- ,...)
- ,(table.unpack gensyms)))))
-
-;;; Try catch finally
-
-(fn catch? [[fun]]
- "Test if expression is a catch clause."
- (= (tostring fun) :catch))
-
-(fn finally? [[fun]]
- "Test if expression is a finally clause."
- (= (tostring fun) :finally))
-
-(fn add-finally [finally form]
- "Stores `form' as body of `finally', which will be injected into
-`match' branches at places appropriate for it to run.
-
-Checks if there already was `finally' clause met, which can be only
-one."
- (assert-compile (= (length finally) 0)
- "Only one finally clause can exist in try expression"
- [])
- (table.insert finally (list 'do ((or table.unpack _G.unpack) form 2))))
-
-(fn add-catch [finally catches form]
- "Appends `catch' body to a sequence of catch bodies that will later
-be used in `make-catch-clauses' to produce AST.
-
-Checks if there already was `finally' clause met."
- (assert-compile (= (length finally) 0)
- "finally clause must be last in try expression"
- [])
- (table.insert catches (list 'do ((or table.unpack _G.unpack) form 2))))
-
-(fn make-catch-clauses [catches finally]
- "Generates AST of error branches for `match' macro."
- (let [clauses []]
- (var add-catchall? true)
- (each [_ [_ binding-or-val & body] (ipairs catches)]
- (when (sym? binding-or-val)
- (set add-catchall? false))
- (table.insert clauses `(false ,binding-or-val))
- (table.insert clauses `(let [res# ((or table.pack #(doto [$...] (tset :n (select :# $...))))
- (do ,((or table.unpack _G.unpack) body)))]
- ,(. finally 1)
- (table.unpack res# 1 res#.n))))
- (when add-catchall?
- ;; implicit catchall which retrows error further is added only
- ;; if there were no catch clause that used symbol as catch value
- (table.insert clauses `(false _#))
- (table.insert clauses `(do ,(. finally 1) (error _#))))
- ((or table.unpack _G.unpack) clauses)))
-
-(fn add-to-try [finally catches try form]
- "Append form to the try body. There must be no `catch' of `finally'
-clauses when we push body epression."
- (assert-compile (and (= (length finally) 0)
- (= (length catches) 0))
- "Only catch or finally clause can follow catch in try expression"
- [])
- (table.insert try form))
-
-(fn try [...]
- {:fnl/arglist [body* catch-clause* finally-clause?]
- :fnl/docstring "General purpose try/catch/finally macro.
-Wraps its body in `pcall' and checks the return value with `match'
-macro.
-
-Catch clause is written either as `(catch symbol body*)`, thus acting
-as catch-all, or `(catch value body*)` for catching specific errors.
-It is possible to have several `catch' clauses. If no `catch' clauses
-specified, an implicit catch-all clause is created. `body*', and
-inner expressions of `catch-clause*', and `finally-clause?' are
-wrapped in implicit `do'.
-
-The `finally` clause is optional, and written as (finally body*). If
-present, it must be the last clause in the `try' form, and the only
-`finally' clause. Note that `finally' clause is for side effects
-only, and runs either after succesful run of `try' body, or after any
-`catch' clause body, before returning the result. If no `catch'
-clause is provided `finally' runs in implicit catch-all clause, and
-trows error to upper scope using `error' function.
-
-To throw error from `try' to catch it with `catch' clause use `error'
-or `assert' functions.
-
-# Examples
-Catch all errors, ignore those and return fallback value:
-
-``` fennel
-(fn add [x y]
- (try
- (+ x y)
- (catch _ 0)))
-
-(assert-eq (add nil 1) 0)
-```
-
-Catch error and do cleanup:
-
-``` fennel
-(local tbl [])
-
-(try
- (table.insert tbl \"a\")
- (table.insert tbl \"b\" \"c\")
- (catch _
- (each [k _ (pairs tbl)]
- (tset tbl k nil))))
-
-(assert-eq (length tbl) 0)
-
-```
-
-Always run some side effect action:
-
-``` fennel
-(local t [])
-(local res (try 10 (finally (table.insert t :finally))))
-(assert-eq (. t 1) :finally)
-(assert-eq res 10)
-
-(local res (try (error 10) (catch 10 nil) (finally (table.insert t :again))))
-(assert-eq (. t 2) :again)
-(assert-eq res nil)
-```"}
- (let [try '(do)
- catches []
- finally []]
- (each [_ form (ipairs [...])]
- (if (list? form)
- (if (catch? form) (add-catch finally catches form)
- (finally? form) (add-finally finally form)
- (add-to-try finally catches try form))
- (add-to-try finally catches try form)))
- `(match (pcall (fn [] ((or table.pack #(doto [$...] (tset :n (select :# $...)))) ,try)))
- (true _#) (do ,(. finally 1) ((or table.unpack _G.unpack) _# 1 _#.n))
- ,(make-catch-clauses catches finally))))
-
-;;; Misc
-
-(fn cond [...]
- "Takes a set of test expression pairs. It evaluates each test one at a
-time. If a test returns logical true, `cond` evaluates and returns
-the value of the corresponding expression and doesn't evaluate any of
-the other tests or exprs. `(cond)` returns nil."
- (assert-compile (= 0 (% (select "#" ...) 2))
- "cond requires an even number of forms"
- ...)
- (if (= 0 (select "#" ...))
- `nil
- `(if ,...)))
-
-;;; Lazy seq
-
-(local {:lazy-seq lazy-seq* :lazy-cat lazy-cat*}
- (require (if (and ... (string.match ... "init%-macros$"))
- (string.gsub ... "init%-macros$" "lazy-seq.init-macros")
- ... (.. ... ".lazy-seq.init-macros")
- "lazy-seq.init-macros")))
-
-(fn lazy-seq [...]
- {:fnl/docstring "Takes a `body` of expressions that returns a sequence, table or nil,
-and yields a lazy sequence that will invoke the body only the first
-time `seq` is called, and will cache the result and return it on all
-subsequent `seq` calls. See also - `realized?`"
- :fnl/arglist [& body]}
- `(let [core# (require ,core)
- res# ,(lazy-seq* ...)]
- (match (getmetatable res#)
- mt# (doto mt#
- (tset :cljlib/type :seq)
- (tset :cljlib/conj
- (fn [s# v#] (core#.cons v# s#)))
- (tset :cljlib/empty #(core#.list))))
- res#))
-
-(fn lazy-cat [...]
- {:fnl/docstring "Expands to code which yields a lazy sequence of the concatenation of
-`colls` - expressions returning collections. Each expression is not
-evaluated until it is needed."
- :fnl/arglist [& colls]}
- `(let [core# (require ,core)
- res# ,(lazy-cat* ...)]
- (match (getmetatable res#)
- mt# (doto mt#
- (tset :cljlib/type :seq)
- (tset :cljlib/conj
- (fn [s# v#] (core#.cons v# s#)))
- (tset :cljlib/empty #(core#.list))))
- res#))
-
-{: fn*
- : defn
- : defn-
- : in-ns
- : ns
- : def
- : time
- : when-let
- : when-some
- : if-let
- : if-some
- : defmulti
- : defmethod
- : cond
- : loop
- : try
- : lazy-seq
- : lazy-cat}