diff options
| author | Andrey Orst <andreyorst@gmail.com> | 2021-01-24 16:22:57 +0300 |
|---|---|---|
| committer | Andrey Orst <andreyorst@gmail.com> | 2021-01-24 18:25:42 +0300 |
| commit | b22f270b596881630fb1dbd6a721c1fe6312f00d (patch) | |
| tree | fc9ac927f79039c67d263b40c6ec73de4a1161a2 | |
| parent | 996b6b2b199610682d32028e02e5c07f781e5373 (diff) | |
feature: include documentation testing in pipeline
| -rw-r--r-- | .dir-locals.el | 5 | ||||
| -rw-r--r-- | .fenneldoc | 9 | ||||
| -rw-r--r-- | .gitlab-ci.yml | 15 | ||||
| -rw-r--r-- | Makefile | 13 | ||||
| -rw-r--r-- | README.md | 4 | ||||
| -rw-r--r-- | doc/cljlib.md | 77 | ||||
| -rw-r--r-- | doc/macros.md | 207 | ||||
| -rw-r--r-- | doc/tests/test.md | 41 | ||||
| -rw-r--r-- | init.fnl | 107 | ||||
| -rw-r--r-- | macros.fnl | 291 | ||||
| -rw-r--r-- | tests/test.fnl | 23 |
11 files changed, 437 insertions, 355 deletions
diff --git a/.dir-locals.el b/.dir-locals.el index bf3fb90..abb24bb 100644 --- a/.dir-locals.el +++ b/.dir-locals.el @@ -31,5 +31,6 @@ (eval . (put 'if-some 'fennel-indent-function 1)) (eval . (put 'when-let 'fennel-indent-function 1)) (eval . (put 'if-let 'fennel-indent-function 1)) - (eval . (put 'fn+ 'fennel-indent-function 'defun)) - (eval . (put 'fn* 'fennel-indent-function 'defun))))) + (eval . (put 'fn* 'fennel-indent-function 'defun)) + (eval . (put 'fn* 'fennel-doc-string-elt 2)) + (eval . (put 'defmulti 'fennel-doc-string-elt 2))))) diff --git a/.fenneldoc b/.fenneldoc new file mode 100644 index 0000000..6af9e5e --- /dev/null +++ b/.fenneldoc @@ -0,0 +1,9 @@ +;; -*- mode: fennel; -*- +;; Configuration file for Fenneldoc. +;; https://gitlab.com/andreyorst/fenneldoc + +{:test-requirements {:init.fnl "(import-macros {: assert-eq : assert-ne : assert-is : assert-not} :tests.test)" + :macros.fnl "(require-macros :macros) + (import-macros {: assert-eq} :tests.test) + (local {: eq : vector : hash-map} (require :init))" + :tests/test.fnl "(require-macros :tests.test)"}} diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 701cff8..72ec9b2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -8,9 +8,17 @@ Lua: image: alpine:edge stage: test before_script: - - apk add -q lua5.2 lua5.3 lua5.4 git make - - git clone https://git.sr.ht/~technomancy/fennel + - > + apk add -q + lua5.2 lua5.3 lua5.3-dev lua5.4 luarocks5.3 + git make gcc musl-dev + - git clone --depth 1 https://git.sr.ht/~technomancy/fennel - (cd fennel; LUA=lua5.3 make install) + - luarocks-5.3 install luafilesystem + - > + git clone --depth 1 --branch v0.1.0 --recursive + https://gitlab.com/andreyorst/fenneldoc.git + - (cd fenneldoc; LUA=lua5.3 make install) script: - LUA_EXECUTABLES="lua5.2 lua5.3 lua5.4" make testall >/dev/null @@ -28,13 +36,14 @@ Luajit: script: - LUA=luajit make test + # We install fennel via luarocks because I don't want to figure out # how to install luacov without luarocks Coverage: image: alpine:3.12.1 stage: coverage before_script: - - apk add -q lua5.3 lua5.3-dev gcc musl-dev luarocks5.3 make + - apk add -q lua5.3 lua5.3-dev luarocks5.3 make gcc musl-dev - luarocks-5.3 install fennel - luarocks-5.3 install luacov - luarocks-5.3 install luacov-console @@ -3,8 +3,10 @@ FENNEL ?= fennel FNLSOURCES = init.fnl LUASOURCES = $(FNLSOURCES:.fnl=.lua) FNLTESTS = tests/fn.fnl tests/macros.fnl tests/core.fnl +FNLDOCS = init.fnl macros.fnl tests/test.fnl LUATESTS = $(FNLTESTS:.fnl=.lua) LUA_EXECUTABLES ?= lua luajit +FENNELDOC := $(shell command -v fenneldoc) .PHONY: build clean distclean help test luacov luacov-console fenneldoc $(LUA_EXECUTABLES) @@ -23,6 +25,15 @@ distclean: clean test: $(FNLTESTS) @echo "Testing on" $$($(LUA) -v) >&2 +ifdef FENNELDOC + @fenneldoc --mode check $(FNLDOCS) || exit +else + @echo "" + @echo "fenneldoc is not installed" >&2 + @echo "Please install fenneldoc to check documentation during testing" >&2 + @echo "https://gitlab.com/andreyorst/fenneldoc" >&2 + @echo "" +endif @$(foreach test,$?,$(FENNEL) --lua $(LUA) --metadata $(test) || exit;) testall: $(LUA_EXECUTABLES) @@ -38,7 +49,7 @@ luacov-console: luacov @$(foreach test, $(LUATESTS), mv $(test).tmp $(test);) fenneldoc: - fenneldoc init.fnl macros.fnl tests/test.fnl + fenneldoc $(FNLDOCS) help: @echo "make -- run tests and create lua library" >&2 @@ -36,5 +36,7 @@ Documentation is auto-generated with [Fenneldoc](https://gitlab.com/andreyorst/f Please make sure you've read [contribution guidelines](https://gitlab.com/andreyorst/fennel-cljlib/-/tree/master/CONTRIBUTING.md). -<!-- LocalWords: Lua submodule precompile cljlib +<!-- LocalWords: Lua submodule precompile cljlib docstring config +<!-- LocalWords: namespace destructure + --> --> diff --git a/doc/cljlib.md b/doc/cljlib.md index e54bcae..4634fb9 100644 --- a/doc/cljlib.md +++ b/doc/cljlib.md @@ -138,15 +138,12 @@ arguments to `args`, and `f` must support variadic amount of arguments. ### Examples -Applying `print` to different arguments: +Applying [`add`](#add) to different amount of arguments: ``` fennel -(apply print [1 2 3 4]) -;; prints 1 2 3 4 -(apply print 1 [2 3 4]) -;; => 1 2 3 4 -(apply print 1 2 3 4 5 6 [7 8 9]) -;; => 1 2 3 4 5 6 7 8 9 +(assert-eq (apply add [1 2 3 4]) 10) +(assert-eq (apply add 1 [2 3 4]) 10) +(assert-eq (apply add 1 2 3 4 5 6 [7 8 9]) 45) ``` ## `add` @@ -228,7 +225,7 @@ Function signature: (inc ([x])) ``` -Increase number by one +Increase number `x` by one ## `dec` Function signature: @@ -237,7 +234,7 @@ Function signature: (dec ([x])) ``` -Decrease number by one +Decrease number `x` by one ## `eq` Function signature: @@ -270,24 +267,24 @@ metadata attached for this test to work. Non empty tables: ``` fennel -(assert (map? {:a 1 :b 2})) +(assert-is (map? {:a 1 :b 2})) (local some-table {:key :value}) -(assert (map? some-table)) +(assert-is (map? some-table)) ``` Empty tables: ``` fennel (local some-table {}) -(assert (not (map? some-table))) +(assert-not (map? some-table)) ``` Empty tables created with [`hash-map`](#hash-map) will pass the test: ``` fennel (local some-table (hash-map)) -(assert (map? some-table)) +(assert-is (map? some-table)) ``` ## `vector?` @@ -312,24 +309,24 @@ metadata attached for this test to work. Non empty vector: ``` fennel -(assert (vector? [1 2 3 4])) +(assert-is (vector? [1 2 3 4])) (local some-table [1 2 3]) -(assert (vector? some-table)) +(assert-is (vector? some-table)) ``` Empty tables: ``` fennel (local some-table []) -(assert (not (vector? some-table))) +(assert-not (vector? some-table)) ``` Empty tables created with [`vector`](#vector) will pass the test: ``` fennel (local some-table (vector)) -(assert (vector? some-table)) +(assert-is (vector? some-table)) ``` ## `multifn?` @@ -360,7 +357,7 @@ Function signature: (nil? ([]) ([x])) ``` -Test if value is nil. +Test if `x` is nil. ## `zero?` Function signature: @@ -369,7 +366,7 @@ Function signature: (zero? ([x])) ``` -Test if value is equal to zero. +Test if `x` is equal to zero. ## `pos?` Function signature: @@ -396,7 +393,7 @@ Function signature: (even? ([x])) ``` -Test if value is even. +Test if `x` is even. ## `odd?` Function signature: @@ -405,7 +402,7 @@ Function signature: (odd? ([x])) ``` -Test if value is odd. +Test if `x` is odd. ## `string?` Function signature: @@ -514,7 +511,7 @@ Sets additional metadata for function [`vector?`](#vector?) to work. ``` fennel (local v (vector 1 2 3 4)) -(assert (eq v [1 2 3 4])) +(assert-eq v [1 2 3 4]) ``` ## `seq` @@ -564,7 +561,7 @@ Function signature: (kvseq ([col])) ``` -Transforms any table to key-value sequence. +Transforms any table `col` to key-value sequence. ## `first` Function signature: @@ -953,7 +950,8 @@ Function signature: (hash-map ([]) ([& kvs])) ``` -Create associative table from keys and values +Create associative table from `kvs` represented as sequence of keys +and values ## `get` Function signature: @@ -1002,7 +1000,7 @@ Function signature: (find ([tbl key])) ``` -Returns the map entry for `key`, or `nil` if key not present. +Returns the map entry for `key`, or `nil` if key not present in `tbl`. ## `dissoc` Function signature: @@ -1011,44 +1009,45 @@ Function signature: (dissoc ([tbl]) ([tbl key]) ([tbl key & keys])) ``` -Remove `key` from table `tbl`. +Remove `key` from table `tbl`. Optionally takes more `keys`. ## `remove-method` Function signature: ``` -(remove-method ([multifn dispatch-val])) +(remove-method ([multimethod dispatch-value])) ``` -Remove method from `multifn` for given `dispatch-val`. +Remove method from `multimethod` for given `dispatch-value`. ## `remove-all-methods` Function signature: ``` -(remove-all-methods ([multifn])) +(remove-all-methods ([multimethod])) ``` -Removes all of the methods of multimethod +Removes all of the methods of `multimethod` ## `methods` Function signature: ``` -(methods ([multifn])) +(methods ([multimethod])) ``` -Given a multimethod, returns a map of dispatch values -> dispatch fns +Given a `multimethod`, returns a map of dispatch values -> dispatch fns ## `get-method` Function signature: ``` -(get-method ([multifn dispatch-val])) +(get-method ([multimethod dispatch-value])) ``` -Given a multimethod and a dispatch value, returns the dispatch `fn` -that would apply to that value, or `nil` if none apply and no default. +Given a `multimethod` and a `dispatch-value`, returns the dispatch +`fn` that would apply to that value, or `nil` if none apply and no +default. ## `ordered-set` Function signature: @@ -1133,9 +1132,9 @@ and are compared for having the same keys without particular order and same size: ``` fennel -(assert (= (ordered-set :a :b) (ordered-set :b :a))) -(assert (not= (ordered-set :a :b) (ordered-set :b :a :c))) -(assert (= (ordered-set :a :b) (hash-set :a :b))) +(assert-eq (ordered-set :a :b) (ordered-set :b :a)) +(assert-ne (ordered-set :a :b) (ordered-set :b :a :c)) +(assert-eq (ordered-set :a :b) (hash-set :a :b)) ``` ## `hash-set` @@ -1168,5 +1167,5 @@ Copyright (C) 2020 Andrey Orst License: [MIT](https://gitlab.com/andreyorst/fennel-cljlib/-/raw/master/LICENSE) -<!-- Generated with Fenneldoc 0.0.7 +<!-- Generated with Fenneldoc 0.1.0 https://gitlab.com/andreyorst/fenneldoc --> diff --git a/doc/macros.md b/doc/macros.md index 1841b14..f1efa48 100644 --- a/doc/macros.md +++ b/doc/macros.md @@ -1,4 +1,4 @@ -# Macros.fnl (0.3.0) +# Macros.fnl (0.4.0) Macros for Cljlib that implement various facilities from Clojure. **Table of contents** @@ -27,7 +27,10 @@ Function signature: ``` Create (anonymous) function of fixed arity. -Supports multiple arities by defining bodies as lists. +Accepts optional `name` and `docstring?` as first two arguments, +followed by single or multiple arity bodies defined as lists. Each +list starts with `arglist*` vector, which supports destructuring, and +is followed by `body*` wrapped in implicit `do`. ### Examples Named function of fixed arity 2: @@ -136,12 +139,12 @@ namespace tables: ([t1 t2 & tables] (join (join t1 t2) ((or table.unpack _G.unpack) tables)))) ;; call to `join` resolves to ns.tables.join -(ns.strings.join "a" "b" "c") -;; => abc -(join ["a"] ["b"] ["c"] ["d" "e"]) -;; => ["a" "b" "c" "d" "e"] -(join "a" "b" "c") -;; {} +(assert-eq (ns.strings.join "a" "b" "c") "abc") + +(assert-eq (join ["a"] ["b"] ["c"] ["d" "e"]) + ["a" "b" "c" "d" "e"]) +(assert-eq (join "a" "b" "c") + []) ``` Note that this creates a collision and local `join` overrides `join` @@ -159,10 +162,12 @@ 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. +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`. Finally clause is optional, and written as (finally body*). If present, it must be the last clause in the `try` form, and the only @@ -184,36 +189,36 @@ Catch all errors, ignore those and return fallback value: (+ x y) (catch _ 0))) -(add nil 1) ;; => 0 +(assert-eq (add nil 1) 0) ``` Catch error and do cleanup: ``` fennel -(let [tbl []] - (try - (table.insert tbl "a") - (table.insert tbl "b" "c") - (catch _ - (each [k _ (pairs tbl)] - (tset tbl k nil)))) - tbl) -;; => {} +(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 res (try 10 (finally (print "side-effect!")))) -;; => side-effect! -;; => nil -res -;; => 10 -(local res (try (error 10) (catch 10 nil) (finally (print "side-effect!")))) -;; => side-effect! -;; => nil -res -;; => nil +(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) ``` @@ -224,15 +229,22 @@ Function signature: (def attr-map? name expr) ``` -Wrapper around `local` which can -declare variables inside namespace, and as local at the same time -similarly to [`fn*`](#fn*): +Wrapper around `local` which can declare variables inside namespace, +and as local `name` at the same time similarly to +[`fn*`](#fn*). Accepts optional `attr-map?` which can contain a +docstring, and whether variable should be mutable or not. Sets +variable to the result of `expr`. ``` fennel (def ns {}) (def a 10) ;; binds `a` to `10` +(assert-eq a 10) + (def ns.b 20) ;; binds `ns.b` and `b` to `20` + +(assert-eq b 20) +(assert-eq ns.b 20) ``` `a` is a `local`, and both `ns.b` and `b` refer to the same value. @@ -263,42 +275,48 @@ Function signature: ``` Works the same as [`def`](#def), but ensures that later `defonce` -calls will not override existing bindings: +calls will not override existing bindings. Accepts same `attr-map?` as +`def`, and sets `name` to the result of `expr`: ``` fennel (defonce a 10) (defonce a 20) -(print a) ;; => prints 10 +(assert-eq a 10) ``` ## `defmulti` Function signature: ``` -(defmulti name docstring? dispatch-fn attr-map?) +(defmulti name docstring? dispatch-fn options*) ``` -Create multifunction with -runtime dispatching based on results from `dispatch-fn`. Returns an -empty 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`. +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 -[`multimethod`](#multimethod) on how to add one. +[`defmethod`](#defmethod) on how to add one. ## `defmethod` Function signature: ``` -(defmethod multifn dispatch-val fnspec) +(defmethod multi-fn dispatch-value fnspec) ``` -Attach new method to multi-function dispatch value. accepts the `multi-fn` -as its first argument, the dispatch value as second, and function tail -starting from argument list, followed by function body as in -[`fn*`](#fn). +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*`](#fn). ### Examples Here are some examples how multimethods can be used. @@ -315,7 +333,7 @@ to another multimethod: (defmethod fac 0 [_] 1) (defmethod fac :default [x] (* x (fac (- x 1)))) -(fac 4) ;; => 24 +(assert-eq (fac 4) 24) ``` `:default` is a special method which gets called when no other methods @@ -348,22 +366,30 @@ tables to Lua's one: (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 :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)) -(print (to-lua-str {:a {:b 10}})) -;; => {["a"] = {["b"] = 10}} +(assert-eq (to-lua-str {:a {:b 10}}) "{[\"a\"] = {[\"b\"] = 10}}") -(print (to-lua-str [:a :b :c [:d {:e :f}]])) -;; => {[1] = "a", [2] = "b", [3] = "c", [4] = {[1] = "d", [2] = {["e"] = "f"}}} +(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 if we want. +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. ## `into` Function signature: @@ -372,7 +398,7 @@ Function signature: (into to from) ``` -Transform one table into another. Mutates first table. +Transform table `from` into another table `to`. Mutates first table. Transformation happens in runtime, but type deduction happens in compile time if possible. This means, that if literal values passed @@ -380,15 +406,15 @@ to `into` this will have different effects for associative tables and vectors: ``` fennel -(into [1 2 3] [4 5 6]) ;; => [1 2 3 4 5 6] -(into {:a 1 :c 2} {:a 0 :b 1}) ;; => {:a 0 :b 1 :c 2} +(assert-eq (into [1 2 3] [4 5 6]) [1 2 3 4 5 6]) +(assert-eq (into {:a 1 :c 2} {:a 0 :b 1}) {:a 0 :b 1 :c 2}) ``` Conversion between different table types is also supported: ``` fennel -(into [] {:a 1 :b 2 :c 3}) ;; => [[:a 1] [:b 2] [:c 3]] -(into {} [[:a 1] [:b 2]]) ;; => {:a 1 :b 2} +(assert-eq (into [] {:a 1}) [[:a 1]]) +(assert-eq (into {} [[:a 1] [:b 2]]) {:a 1 :b 2}) ``` Same rules apply to runtime detection of table type, except that this @@ -396,7 +422,7 @@ will not work for empty tables: ``` fennel (local empty-table {}) -(into empty-table {:a 1 :b 2}) ;; => [[:a 1] [:b 2]] +(assert-eq (into empty-table {:a 1}) [[:a 1]]) ``` fennel If table is empty, `into` defaults to sequential table, because it @@ -408,8 +434,8 @@ runtime, and this works as expected: ``` fennel (local t1 [1 2 3]) (local t2 {:a 10 :c 3}) -(into t1 {:a 1 :b 2}) ;; => [1 2 3 [:a 1] [:b 2]] -(into t2 {:a 1 :b 2}) ;; => {:a 1 :b 2 :c 3} +(assert-eq (into t1 {:a 1}) [1 2 3 [:a 1]]) +(assert-eq (into t2 {:a 1}) {:a 1 :c 3}) ``` `cljlib.fnl` module provides two additional functions `vector` and @@ -417,8 +443,8 @@ runtime, and this works as expected: at runtime: ``` fennel -(into (vector) {:a 1 :b 2}) ;; => [[:a 1] [:b 2]] -(into (hash-map) [[:a 1 :b 2]]) ;; => {:a 1 :b 2} +(assert-eq (into (vector) {:a 1}) [[:a 1]]) +(assert-eq (into (hash-map) [[:a 1] [:b 2]]) {:a 1 :b 2}) ``` ## `empty` @@ -442,10 +468,10 @@ and return result of the same type: (table.insert res (f v))) (into (empty tbl) res))) -(map (fn [[k v]] [(string.upper k) v]) {:a 1 :b 2 :c 3}) -;; => {:A 1 :B 2 :C 3} -(map #(* $ $) [1 2 3 4]) -;; [1 4 9 16] +(assert-eq (map (fn [[k v]] [(string.upper k) v]) {:a 1 :b 2 :c 3}) + {:A 1 :B 2 :C 3}) +(assert-eq (map #(* $ $) [1 2 3 4]) + [1 4 9 16]) ``` See [`into`](#into) for more info on how conversion is done. @@ -456,11 +482,11 @@ Function signature: (when-meta [& body]) ``` -Wrapper that compiles away if metadata support was not enabled. What -this effectively means, is that everything that is wrapped with this -macro will disappear from the resulting Lua code if metadata is not -enabled when compiling with `fennel --compile` without `--metadata` -switch. +Wrapper that compiles away if metadata support was not enabled. +What this effectively means, is that everything that is wrapped with +this macro and its `body` will disappear from the resulting Lua code +if metadata is not enabled when compiling with `fennel --compile` +without `--metadata` switch. ## `with-meta` Function signature: @@ -469,7 +495,7 @@ Function signature: (with-meta value meta) ``` -Attach metadata to a value. When metadata feature is not enabled, +Attach `meta` to a `value`. When metadata feature is not enabled, returns the value without additional metadata. ``` fennel @@ -526,9 +552,9 @@ Function signature: (if-let [binding test] then-branch else-branch) ``` -If test is logical true, -evaluates `then-branch` with binding-form bound to the value of test, -if not, yields `else-branch`. +If `binding` is set by `test` to logical true, evaluates `then-branch` +with binding-form bound to the value of test, if not, yields +`else-branch`. ## `when-let` Function signature: @@ -537,8 +563,8 @@ Function signature: (when-let [binding test] & body) ``` -If test is logical true, -evaluates `body` in implicit `do`. +If `binding` was bound by `test` to logical true, evaluates `body` in +implicit `do`. ## `if-some` Function signature: @@ -547,9 +573,8 @@ Function signature: (if-some [binding test] then-branch else-branch) ``` -If test is non-`nil`, evaluates -`then-branch` with binding-form bound to the value of test, if not, -yields `else-branch`. +If `test` is non-`nil`, evaluates `then-branch` with `binding`-form bound +to the value of test, if not, yields `else-branch`. ## `when-some` Function signature: @@ -558,8 +583,8 @@ Function signature: (when-some [binding test] & body) ``` -If test is non-`nil`, -evaluates `body` in implicit `do`. +If `test` sets `binding` to non-`nil`, evaluates `body` in implicit +`do`. --- @@ -569,5 +594,5 @@ Copyright (C) 2020 Andrey Orst License: [MIT](https://gitlab.com/andreyorst/fennel-cljlib/-/raw/master/LICENSE) -<!-- Generated with Fenneldoc 0.0.7 +<!-- Generated with Fenneldoc 0.1.0 https://gitlab.com/andreyorst/fenneldoc --> diff --git a/doc/tests/test.md b/doc/tests/test.md index 8a6c7e5..f9d8ae9 100644 --- a/doc/tests/test.md +++ b/doc/tests/test.md @@ -16,7 +16,7 @@ Function signature: (deftest name ...) ``` -Simple way of grouping tests. +Simple way of grouping tests with `name`. ## `testing` Function signature: @@ -25,7 +25,7 @@ Function signature: (testing description ...) ``` -Print test description and run it. +Print test `description` and run it. ## `assert-eq` Function signature: @@ -34,30 +34,26 @@ Function signature: (assert-eq expr1 expr2 msg) ``` -Like `assert`, except compares results of two expressions on equality. +Like `assert`, except compares results of `expr1` and `expr2` for equality. Generates formatted message if `msg` is not set to other message. ### Example Compare two expressions: ``` fennel ->> (assert-eq 1 (+1 1)) -runtime error: equality assertion failed - Left: 1 - Right: 3 +;; (assert-eq 1 (+1 1)) +;; => runtime error: equality assertion failed +;; => Left: 1 +;; => Right: 3 ``` Deep compare values: ``` fennel ->> (assert-eq [1 {[2 3] [4 5 6]}] [1 {[2 3] [4 5]}]) -runtime error: equality assertion failed - Left: [1 { - [2 3] [4 5 6] - }] - Right: [1 { - [2 3] [4 5] - }] +;; (assert-eq [1 {[2 3] [4 5 6]}] [1 {[2 3] [4 5]}]) +;; => runtime error: equality assertion failed +;; => Left: [1 {[2 3] [4 5 6]}] +;; => Right: [1 {[2 3] [4 5]}] ``` ## `assert-ne` @@ -67,7 +63,9 @@ Function signature: (assert-ne expr1 expr2 msg) ``` -Assert for unequality. Same as [`assert-eq`](#assert-eq). +Assert for unequality. Like `assert`, except compares results of +`expr1` and `expr2` for equality. Generates formatted message if +`msg` is not set to other message. Same as [`assert-eq`](#assert-eq). ## `assert-is` Function signature: @@ -76,12 +74,12 @@ Function signature: (assert-is expr msg) ``` -Assert for truth. Same as inbuilt `assert`, except generates more +Assert `expr` for truth. Same as inbuilt `assert`, except generates more verbose message if `msg` is not set. ``` fennel ->> (assert-is (= 1 2 3)) -runtime error: assertion failed for (= 1 2 3) +;; (assert-is (= 1 2 3)) +;; => runtime error: assertion failed for (= 1 2 3) ``` ## `assert-not` @@ -91,8 +89,9 @@ Function signature: (assert-not expr msg) ``` -Assert for not truth. Works the same as [`assert-is`](#assert-is). +Assert `expr` for not truth. Generates more verbose message if + `msg` is not set. Works the same as [`assert-is`](#assert-is). -<!-- Generated with Fenneldoc 0.0.7 +<!-- Generated with Fenneldoc 0.1.0 https://gitlab.com/andreyorst/fenneldoc --> @@ -64,15 +64,12 @@ arguments to `args`, and `f` must support variadic amount of arguments. # Examples -Applying `print` to different arguments: +Applying [`add`](#add) to different amount of arguments: ``` fennel -(apply print [1 2 3 4]) -;; prints 1 2 3 4 -(apply print 1 [2 3 4]) -;; => 1 2 3 4 -(apply print 1 2 3 4 5 6 [7 8 9]) -;; => 1 2 3 4 5 6 7 8 9 +(assert-eq (apply add [1 2 3 4]) 10) +(assert-eq (apply add 1 [2 3 4]) 10) +(assert-eq (apply add 1 2 3 4 5 6 [7 8 9]) 45) ```" ([f args] (f (_unpack args))) ([f a args] (f a (_unpack args))) @@ -165,8 +162,8 @@ Applying `print` to different arguments: (> y (. more 1))) false))) -(fn* core.inc "Increase number by one" [x] (+ x 1)) -(fn* core.dec "Decrease number by one" [x] (- x 1)) +(fn* core.inc "Increase number `x` by one" [x] (+ x 1)) +(fn* core.dec "Decrease number `x` by one" [x] (- x 1)) (local utility-doc-order [:apply :add :sub :mul :div :le :lt :ge :gt :inc :dec]) @@ -195,24 +192,24 @@ metadata attached for this test to work. Non empty tables: ``` fennel -(assert (map? {:a 1 :b 2})) +(assert-is (map? {:a 1 :b 2})) (local some-table {:key :value}) -(assert (map? some-table)) +(assert-is (map? some-table)) ``` Empty tables: ``` fennel (local some-table {}) -(assert (not (map? some-table))) +(assert-not (map? some-table)) ``` Empty tables created with [`hash-map`](#hash-map) will pass the test: ``` fennel (local some-table (hash-map)) -(assert (map? some-table)) +(assert-is (map? some-table)) ```" [tbl] (if (= (type tbl) :table) @@ -238,24 +235,24 @@ metadata attached for this test to work. Non empty vector: ``` fennel -(assert (vector? [1 2 3 4])) +(assert-is (vector? [1 2 3 4])) (local some-table [1 2 3]) -(assert (vector? some-table)) +(assert-is (vector? some-table)) ``` Empty tables: ``` fennel (local some-table []) -(assert (not (vector? some-table))) +(assert-not (vector? some-table)) ``` Empty tables created with [`vector`](#vector) will pass the test: ``` fennel (local some-table (vector)) -(assert (vector? some-table)) +(assert-is (vector? some-table)) ```" [tbl] (if (= (type tbl) :table) @@ -281,12 +278,12 @@ from `cljlib-macros.fnl`." _ false)) (fn* core.nil? - "Test if value is nil." + "Test if `x` is nil." ([] true) ([x] (= x nil))) (fn* core.zero? - "Test if value is equal to zero." + "Test if `x` is equal to zero." [x] (= x 0)) @@ -301,12 +298,12 @@ from `cljlib-macros.fnl`." (< x 0)) (fn* core.even? - "Test if value is even." + "Test if `x` is even." [x] (= (% x 2) 0)) (fn* core.odd? - "Test if value is odd." + "Test if `x` is odd." [x] (not (even? x))) @@ -387,7 +384,7 @@ Sets additional metadata for function [`vector?`](#vector?) to work. ``` fennel (local v (vector 1 2 3 4)) -(assert (eq v [1 2 3 4])) +(assert-eq v [1 2 3 4]) ```" [& args] (setmetatable args {:cljlib/type :seq})) @@ -453,7 +450,7 @@ Additionally you can use [`conj`](#conj) and [`apply`](#apply) with _ (error (.. "expected table, string or nil, got " (type col)) 2)))) (fn* core.kvseq - "Transforms any table to key-value sequence." + "Transforms any table `col` to key-value sequence." [col] (let [res (empty [])] (match (type col) @@ -977,7 +974,8 @@ use." (setmetatable tbl {:cljlib/type :table}))) (fn* core.hash-map - "Create associative table from keys and values" + "Create associative table from `kvs` represented as sequence of keys +and values" ([] (empty {})) ([& kvs] (apply assoc {} kvs))) @@ -1022,13 +1020,13 @@ found in the table." res)) (fn* core.find - "Returns the map entry for `key`, or `nil` if key not present." + "Returns the map entry for `key`, or `nil` if key not present in `tbl`." [tbl key] (when-some [v (. tbl key)] [key v])) (fn* core.dissoc - "Remove `key` from table `tbl`." + "Remove `key` from table `tbl`. Optionally takes more `keys`." ([tbl] tbl) ([tbl key] (doto tbl (tset key nil))) @@ -1042,40 +1040,41 @@ found in the table." ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Multimethods ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (fn* core.remove-method - "Remove method from `multifn` for given `dispatch-val`." - [multifn dispatch-val] - (if (multifn? multifn) - (tset multifn dispatch-val nil) - (error (.. (tostring multifn) " is not a multifn") 2)) - multifn) + "Remove method from `multimethod` for given `dispatch-value`." + [multimethod dispatch-value] + (if (multifn? multimethod) + (tset multimethod dispatch-value nil) + (error (.. (tostring multimethod) " is not a multifn") 2)) + multimethod) (fn* core.remove-all-methods - "Removes all of the methods of multimethod" - [multifn] - (if (multifn? multifn) - (each [k _ (pairs multifn)] - (tset multifn k nil)) - (error (.. (tostring multifn) " is not a multifn") 2)) - multifn) + "Removes all of the methods of `multimethod`" + [multimethod] + (if (multifn? multimethod) + (each [k _ (pairs multimethod)] + (tset multimethod k nil)) + (error (.. (tostring multimethod) " is not a multifn") 2)) + multimethod) (fn* core.methods - "Given a multimethod, returns a map of dispatch values -> dispatch fns" - [multifn] - (if (multifn? multifn) + "Given a `multimethod`, returns a map of dispatch values -> dispatch fns" + [multimethod] + (if (multifn? multimethod) (let [m {}] - (each [k v (pairs multifn)] + (each [k v (pairs multimethod)] (tset m k v)) m) - (error (.. (tostring multifn) " is not a multifn") 2))) + (error (.. (tostring multimethod) " is not a multifn") 2))) (fn* core.get-method - "Given a multimethod and a dispatch value, returns the dispatch `fn` -that would apply to that value, or `nil` if none apply and no default." - [multifn dispatch-val] - (if (multifn? multifn) - (or (. multifn dispatch-val) - (. multifn :default)) - (error (.. (tostring multifn) " is not a multifn") 2))) + "Given a `multimethod` and a `dispatch-value`, returns the dispatch +`fn` that would apply to that value, or `nil` if none apply and no +default." + [multimethod dispatch-value] + (if (multifn? multimethod) + (or (. multimethod dispatch-value) + (. multimethod :default)) + (error (.. (tostring multimethod) " is not a multifn") 2))) (local multimethods-doc-order [:remove-method :remove-all-methods :methods :get-method]) @@ -1247,9 +1246,9 @@ and are compared for having the same keys without particular order and same size: ``` fennel -(assert (= (ordered-set :a :b) (ordered-set :b :a))) -(assert (not= (ordered-set :a :b) (ordered-set :b :a :c))) -(assert (= (ordered-set :a :b) (hash-set :a :b))) +(assert-eq (ordered-set :a :b) (ordered-set :b :a)) +(assert-ne (ordered-set :a :b) (ordered-set :b :a :c)) +(assert-eq (ordered-set :a :b) (hash-set :a :b)) ```" [& xs] (let [Set (setmetatable {} {:__index deep-index}) @@ -66,12 +66,12 @@ ;; `(eq {[1 2 3] {:a [1 2 3]}} {[1 2 3] {:a [1 2 3]}})` ;; we have to do even deeper search (setmetatable right# {:__index (fn [tbl# key#] - (var res# nil) - (each [k# v# (pairs tbl#)] - (when (eq# k# key#) - (set res# v#) - (lua :break))) - res#)}) + (var res# nil) + (each [k# v# (pairs tbl#)] + (when (eq# k# key#) + (set res# v#) + (lua :break))) + res#)}) (var [res# count-a# count-b#] [true 0 0]) (each [k# v# (pairs left#)] (set res# (eq# v# (. right# k#))) @@ -146,11 +146,11 @@ _SCOPE _CHUNK)) (fn when-meta [...] - "Wrapper that compiles away if metadata support was not enabled. What -this effectively means, is that everything that is wrapped with this -macro will disappear from the resulting Lua code if metadata is not -enabled when compiling with `fennel --compile` without `--metadata` -switch." + "Wrapper that compiles away if metadata support was not enabled. +What this effectively means, is that everything that is wrapped with +this macro and its `body` will disappear from the resulting Lua code +if metadata is not enabled when compiling with `fennel --compile` +without `--metadata` switch." (when meta-enabled `(do ,...))) @@ -192,7 +192,7 @@ this stuff will only work if you use `require-macros` instead of (if res# (. fennel#.metadata ,value))))) (fn with-meta [value meta] - "Attach metadata to a value. When metadata feature is not enabled, + "Attach `meta` to a `value`. When metadata feature is not enabled, returns the value without additional metadata. ``` fennel @@ -347,7 +347,8 @@ returns the value without additional metadata. (table.insert bodies body)) (when amp-body (let [[more-len body arity] amp-body] - (assert-compile (not (and max (<= more-len max))) "fn*: arity with `&' must have more arguments than maximum arity without `&'. + (assert-compile (not (and max (<= more-len max))) + "fn*: arity with `&' must have more arguments than maximum arity without `&'. * Try adding more arguments before `&'" arity) (table.insert lengths (- more-len 1)) @@ -357,8 +358,8 @@ returns the value without additional metadata. (contains? lengths 0) amp-body)) (table.insert bodies (list 'error - (.. "wrong argument amount" - (if name (.. " for " name) "")) 2))) + (.. "wrong argument amount" + (if name (.. " for " name) "")) 2))) bodies)) (fn single-arity-body [args fname] @@ -396,7 +397,10 @@ returns the value without additional metadata. (fn fn* [name doc? ...] "Create (anonymous) function of fixed arity. -Supports multiple arities by defining bodies as lists. +Accepts optional `name` and `docstring?` as first two arguments, +followed by single or multiple arity bodies defined as lists. Each +list starts with `arglist*` vector, which supports destructuring, and +is followed by `body*` wrapped in implicit `do`. # Examples Named function of fixed arity 2: @@ -505,12 +509,12 @@ namespace tables: ([t1 t2 & tables] (join (join t1 t2) ((or table.unpack _G.unpack) tables)))) ;; call to `join` resolves to ns.tables.join -(ns.strings.join \"a\" \"b\" \"c\") -;; => abc -(join [\"a\"] [\"b\"] [\"c\"] [\"d\" \"e\"]) -;; => [\"a\" \"b\" \"c\" \"d\" \"e\"] -(join \"a\" \"b\" \"c\") -;; {} +(assert-eq (ns.strings.join \"a\" \"b\" \"c\") \"abc\") + +(assert-eq (join [\"a\"] [\"b\"] [\"c\"] [\"d\" \"e\"]) + [\"a\" \"b\" \"c\" \"d\" \"e\"]) +(assert-eq (join \"a\" \"b\" \"c\") + []) ``` Note that this creates a collision and local `join` overrides `join` @@ -534,13 +538,12 @@ from `ns.strings`, so the latter must be fully qualified (if (sym? name-wo-namespace) (if namespaced? `(local ,name-wo-namespace - (do (fn ,name-wo-namespace [...] ,docstring ,body) - (set ,name ,name-wo-namespace) ;; set function into module table, e.g. (set foo.bar bar) + (do (set ,name (fn ,name-wo-namespace [...] ,docstring ,body)) ;; set function into module table, e.g. (set foo.bar bar) ,(with-meta name-wo-namespace `{:fnl/arglist ,arglist-doc}))) `(local ,name ,(with-meta `(fn ,name [...] ,docstring ,body) `{:fnl/arglist ,arglist-doc}))) (with-meta `(fn [...] ,docstring ,body) `{:fnl/arglist ,arglist-doc})))) -(attach-meta fn* {:fnl/arglist ["name docstring? ([arglist*] body)*"]}) +(attach-meta fn* {:fnl/arglist ["name" "docstring?" "([arglist*] body)*"]}) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; let variants ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -553,6 +556,9 @@ from `ns.strings`, so the latter must be fully qualified ;; such intention (fn if-let [...] + "If `binding` is set by `test` to logical true, evaluates `then-branch` +with binding-form bound to the value of test, if not, yields +`else-branch`." (let [[bindings then else] (match (select :# ...) 2 [...] 3 [...] @@ -565,13 +571,12 @@ from `ns.strings`, so the latter must be fully qualified ,then) ,else))))) -(attach-meta if-let {:fnl/arglist ["[binding test]" "then-branch" "else-branch"] - :fnl/docstring "If test is logical true, -evaluates `then-branch` with binding-form bound to the value of test, -if not, yields `else-branch`."}) +(attach-meta if-let {:fnl/arglist ["[binding test]" "then-branch" "else-branch"]}) (fn when-let [...] + "If `binding` was bound by `test` to logical true, evaluates `body` in +implicit `do`." (let [[bindings & body] (if (> (select :# ...) 0) [...] (error "wrong argument amount for when-let" 2))] (check-two-binding-vec bindings) @@ -581,12 +586,12 @@ if not, yields `else-branch`."}) (let [,form tmp#] ,((or table.unpack _G.unpack) body))))))) -(attach-meta when-let {:fnl/arglist ["[binding test]" "& body"] - :fnl/docstring "If test is logical true, -evaluates `body` in implicit `do`."}) +(attach-meta when-let {:fnl/arglist ["[binding test]" "&" "body"]}) (fn if-some [...] + "If `test` is non-`nil`, evaluates `then-branch` with `binding`-form bound +to the value of test, if not, yields `else-branch`." (let [[bindings then else] (match (select :# ...) 2 [...] 3 [...] @@ -599,13 +604,12 @@ evaluates `body` in implicit `do`."}) (let [,form tmp#] ,then)))))) -(attach-meta if-some {:fnl/arglist ["[binding test]" "then-branch" "else-branch"] - :fnl/docstring "If test is non-`nil`, evaluates -`then-branch` with binding-form bound to the value of test, if not, -yields `else-branch`."}) +(attach-meta if-some {:fnl/arglist ["[binding test]" "then-branch" "else-branch"]}) (fn when-some [...] + "If `test` sets `binding` to non-`nil`, evaluates `body` in implicit +`do`." (let [[bindings & body] (if (> (select :# ...) 0) [...] (error "wrong argument amount for when-some" 2))] (check-two-binding-vec bindings) @@ -616,9 +620,7 @@ yields `else-branch`."}) (let [,form tmp#] ,((or table.unpack _G.unpack) body))))))) -(attach-meta when-some {:fnl/arglist ["[binding test]" "& body"] - :fnl/docstring "If test is non-`nil`, -evaluates `body` in implicit `do`."}) +(attach-meta when-some {:fnl/arglist ["[binding test]" "&" "body"]}) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; into ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -628,7 +630,7 @@ evaluates `body` in implicit `do`."}) :else)) (fn into [to from] - "Transform one table into another. Mutates first table. + "Transform table `from` into another table `to`. Mutates first table. Transformation happens in runtime, but type deduction happens in compile time if possible. This means, that if literal values passed @@ -636,15 +638,15 @@ to `into` this will have different effects for associative tables and vectors: ``` fennel -(into [1 2 3] [4 5 6]) ;; => [1 2 3 4 5 6] -(into {:a 1 :c 2} {:a 0 :b 1}) ;; => {:a 0 :b 1 :c 2} +(assert-eq (into [1 2 3] [4 5 6]) [1 2 3 4 5 6]) +(assert-eq (into {:a 1 :c 2} {:a 0 :b 1}) {:a 0 :b 1 :c 2}) ``` Conversion between different table types is also supported: ``` fennel -(into [] {:a 1 :b 2 :c 3}) ;; => [[:a 1] [:b 2] [:c 3]] -(into {} [[:a 1] [:b 2]]) ;; => {:a 1 :b 2} +(assert-eq (into [] {:a 1}) [[:a 1]]) +(assert-eq (into {} [[:a 1] [:b 2]]) {:a 1 :b 2}) ``` Same rules apply to runtime detection of table type, except that this @@ -652,7 +654,7 @@ will not work for empty tables: ``` fennel (local empty-table {}) -(into empty-table {:a 1 :b 2}) ;; => [[:a 1] [:b 2]] +(assert-eq (into empty-table {:a 1}) [[:a 1]]) ``` fennel If table is empty, `into` defaults to sequential table, because it @@ -664,8 +666,8 @@ runtime, and this works as expected: ``` fennel (local t1 [1 2 3]) (local t2 {:a 10 :c 3}) -(into t1 {:a 1 :b 2}) ;; => [1 2 3 [:a 1] [:b 2]] -(into t2 {:a 1 :b 2}) ;; => {:a 1 :b 2 :c 3} +(assert-eq (into t1 {:a 1}) [1 2 3 [:a 1]]) +(assert-eq (into t2 {:a 1}) {:a 1 :c 3}) ``` `cljlib.fnl` module provides two additional functions `vector` and @@ -673,8 +675,8 @@ runtime, and this works as expected: at runtime: ``` fennel -(into (vector) {:a 1 :b 2}) ;; => [[:a 1] [:b 2]] -(into (hash-map) [[:a 1 :b 2]]) ;; => {:a 1 :b 2} +(assert-eq (into (vector) {:a 1}) [[:a 1]]) +(assert-eq (into (hash-map) [[:a 1] [:b 2]]) {:a 1 :b 2}) ```" (assert-compile (and to from) "into: expected two arguments") (let [to-type (table-type to) @@ -787,10 +789,10 @@ and return result of the same type: (table.insert res (f v))) (into (empty tbl) res))) -(map (fn [[k v]] [(string.upper k) v]) {:a 1 :b 2 :c 3}) -;; => {:A 1 :B 2 :C 3} -(map #(* $ $) [1 2 3 4]) -;; [1 4 9 16] +(assert-eq (map (fn [[k v]] [(string.upper k) v]) {:a 1 :b 2 :c 3}) + {:A 1 :B 2 :C 3}) +(assert-eq (map #(* $ $) [1 2 3 4]) + [1 4 9 16]) ``` See [`into`](#into) for more info on how conversion is done." (match (table-type x) @@ -817,7 +819,7 @@ See [`into`](#into) for more info on how conversion is done." (fn defmulti [...] (let [[name & options] (if (> (select :# ...) 0) [...] - (error "wrong argument amount for defmulti")) + (error "wrong argument amount for defmulti")) docstring (if (string? (first options)) (first options)) options (if docstring (rest options) options) dispatch-fn (first options) @@ -855,27 +857,32 @@ See [`into`](#into) for more info on how conversion is done." :__fennelview tostring :cljlib/type :multifn})))))) -(attach-meta defmulti {:fnl/arglist [:name :docstring? :dispatch-fn :attr-map?] - :fnl/docstring "Create multifunction with -runtime dispatching based on results from `dispatch-fn`. Returns an -empty 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`. +(attach-meta 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 -[`multimethod`](#multimethod) on how to add one."}) +[`defmethod`](#defmethod) on how to add one."}) (fn defmethod [multifn dispatch-val ...] (when (= (select :# ...) 0) (error "wrong argument amount for defmethod")) `(doto ,multifn (tset ,dispatch-val (do (fn* f# ,...) f#)))) -(attach-meta defmethod {:fnl/arglist [:multifn :dispatch-val :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 function tail -starting from argument list, followed by function body as in -[`fn*`](#fn). +(attach-meta defmethod {: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*`](#fn). # Examples Here are some examples how multimethods can be used. @@ -892,7 +899,7 @@ to another multimethod: (defmethod fac 0 [_] 1) (defmethod fac :default [x] (* x (fac (- x 1)))) -(fac 4) ;; => 24 +(assert-eq (fac 4) 24) ``` `:default` is a special method which gets called when no other methods @@ -925,54 +932,51 @@ tables to Lua's one: (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 :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)) -(print (to-lua-str {:a {:b 10}})) -;; => {[\"a\"] = {[\"b\"] = 10}} +(assert-eq (to-lua-str {:a {:b 10}}) \"{[\\\"a\\\"] = {[\\\"b\\\"] = 10}}\") -(print (to-lua-str [:a :b :c [:d {:e :f}]])) -;; => {[1] = \"a\", [2] = \"b\", [3] = \"c\", [4] = {[1] = \"d\", [2] = {[\"e\"] = \"f\"}}} +(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 if we want."}) +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."}) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; def and defonce ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (fn def [...] - (let [[attr-map name expr] (match (select :# ...) - 2 [{} ...] - 3 [...] - _ (error "wrong argument amount for def" 2)) - attr-map (if (table? attr-map) attr-map - (string? attr-map) {attr-map true} - (error "def: expected keyword or literal table as first argument" 2)) - (s multi) (multisym->sym name) - docstring (or (. attr-map :doc) - (. attr-map :fnl/docstring)) - f (if (. attr-map :mutable) 'var 'local)] - (if multi - `(,f ,s (do (,f ,s ,expr) - (set ,name ,s) - ,(with-meta s {:fnl/docstring docstring}))) - `(,f ,name ,(with-meta expr {:fnl/docstring docstring}))))) - -(attach-meta def {:fnl/arglist [:attr-map? :name :expr] - :fnl/docstring "Wrapper around `local` which can -declare variables inside namespace, and as local at the same time -similarly to [`fn*`](#fn*): + "Wrapper around `local` which can declare variables inside namespace, +and as local `name` at the same time similarly to +[`fn*`](#fn*). Accepts optional `attr-map?` which can contain a +docstring, and whether variable should be mutable or not. Sets +variable to the result of `expr`. ``` fennel (def ns {}) (def a 10) ;; binds `a` to `10` +(assert-eq a 10) + (def ns.b 20) ;; binds `ns.b` and `b` to `20` + +(assert-eq b 20) +(assert-eq ns.b 20) ``` `a` is a `local`, and both `ns.b` and `b` refer to the same value. @@ -993,9 +997,36 @@ supported, which is `:mutable`, which allows mutating variable with However, attaching documentation metadata to anything other than tables and functions considered bad practice, due to how Lua works. More info can be found in [`with-meta`](#with-meta) -description."}) +description." + (let [[attr-map name expr] (match (select :# ...) + 2 [{} ...] + 3 [...] + _ (error "wrong argument amount for def" 2)) + attr-map (if (table? attr-map) attr-map + (string? attr-map) {attr-map true} + (error "def: expected keyword or literal table as first argument" 2)) + (s multi) (multisym->sym name) + docstring (or (. attr-map :doc) + (. attr-map :fnl/docstring)) + f (if (. attr-map :mutable) 'var 'local)] + (if multi + `(,f ,s (do (,f ,s ,expr) + (set ,name ,s) + ,(with-meta s {:fnl/docstring docstring}))) + `(,f ,name ,(with-meta expr {:fnl/docstring docstring}))))) + +(attach-meta def {:fnl/arglist [:attr-map? :name :expr]}) (fn defonce [...] + "Works the same as [`def`](#def), but ensures that later `defonce` +calls will not override existing bindings. Accepts same `attr-map?` as +`def`, and sets `name` to the result of `expr`: + +``` fennel +(defonce a 10) +(defonce a 20) +(assert-eq a 10) +```" (let [[attr-map name expr] (match (select :# ...) 2 [{} ...] 3 [...] @@ -1004,15 +1035,7 @@ description."}) nil (def attr-map name expr)))) -(attach-meta defonce {:fnl/arglist [:attr-map? :name :expr] - :fnl/docstring "Works the same as [`def`](#def), but ensures that later `defonce` -calls will not override existing bindings: - -``` fennel -(defonce a 10) -(defonce a 20) -(print a) ;; => prints 10 -```"}) +(attach-meta defonce {:fnl/arglist [:attr-map? :name :expr]}) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; try ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -1092,10 +1115,12 @@ clauses when we push body epression." 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. +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`. Finally clause is optional, and written as (finally body*). If present, it must be the last clause in the `try` form, and the only @@ -1117,36 +1142,36 @@ Catch all errors, ignore those and return fallback value: (+ x y) (catch _ 0))) -(add nil 1) ;; => 0 +(assert-eq (add nil 1) 0) ``` Catch error and do cleanup: ``` fennel -(let [tbl []] - (try - (table.insert tbl \"a\") - (table.insert tbl \"b\" \"c\") - (catch _ - (each [k _ (pairs tbl)] - (tset tbl k nil)))) - tbl) -;; => {} +(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 res (try 10 (finally (print \"side-effect!\")))) -;; => side-effect! -;; => nil -res -;; => 10 -(local res (try (error 10) (catch 10 nil) (finally (print \"side-effect!\")))) -;; => side-effect! -;; => nil -res -;; => nil +(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) ``` "}) @@ -1166,7 +1191,7 @@ res : defmethod : def : defonce - :_VERSION #"0.3.0" + :_VERSION #"0.4.0" :_LICENSE #"[MIT](https://gitlab.com/andreyorst/fennel-cljlib/-/raw/master/LICENSE)" :_COPYRIGHT #"Copyright (C) 2020 Andrey Orst" :_DOC_ORDER #[:fn* diff --git a/tests/test.fnl b/tests/test.fnl index f1d6371..05da7be 100644 --- a/tests/test.fnl +++ b/tests/test.fnl @@ -35,7 +35,7 @@ the tables uses tables as keys." (fn test.assert-eq [expr1 expr2 msg] - "Like `assert`, except compares results of two expressions on equality. + "Like `assert`, except compares results of `expr1` and `expr2` for equality. Generates formatted message if `msg` is not set to other message. # Example @@ -62,26 +62,28 @@ Deep compare values: fennel# (require :fennel)] (assert (eq# left# right#) (or ,msg (.. "assertion failed for expression: -(= " ,(view expr1 {:one-line? true}) " " ,(view expr2 {:one-line? true}) " +(= " ,(view expr1 {:one-line? true}) " " ,(view expr2 {:one-line? true}) ") Left: " (fennel#.view left# {:one-line? true}) " Right: " (fennel#.view right# {:one-line? true}) "\n"))))) (fn test.assert-ne [expr1 expr2 msg] - "Assert for unequality. Same as [`assert-eq`](#assert-eq)." + "Assert for unequality. Like `assert`, except compares results of +`expr1` and `expr2` for equality. Generates formatted message if +`msg` is not set to other message. Same as [`assert-eq`](#assert-eq)." `(let [left# ,expr1 right# ,expr2 eq# ,(eq-fn) fennel# (require :fennel)] (assert (not (eq# left# right#)) (or ,msg (.. "assertion failed for expression: -(not= " ,(view expr1 {:one-line? true}) " " ,(view expr2 {:one-line? true}) " +(not= " ,(view expr1 {:one-line? true}) " " ,(view expr2 {:one-line? true}) ") Left: " (fennel#.view left# {:one-line? true}) " Right: " (fennel#.view right# {:one-line? true}) "\n"))))) (fn test.assert-is [expr msg] - "Assert for truth. Same as inbuilt `assert`, except generates more + "Assert `expr` for truth. Same as inbuilt `assert`, except generates more verbose message if `msg` is not set. ``` fennel @@ -89,23 +91,24 @@ Deep compare values: ;; => runtime error: assertion failed for (= 1 2 3) ```" `(assert ,expr - (.. "assertion failed for " + (.. "assertion failed: " (or ,msg ,(view expr {:one-line? true}))))) (fn test.assert-not [expr msg] - "Assert for not truth. Works the same as [`assert-is`](#assert-is)." + "Assert `expr` for not truth. Generates more verbose message if + `msg` is not set. Works the same as [`assert-is`](#assert-is)." `(assert (not ,expr) - (.. "assertion failed for " + (.. "assertion failed: " (or ,msg ,(view expr {:one-line? true}))))) (fn test.deftest [name ...] - "Simple way of grouping tests." + "Simple way of grouping tests with `name`." `(do ,...)) (fn test.testing [description ...] - "Print test description and run it." + "Print test `description` and run it." `(do (io.stdout:write (.. "testing: " ,description "\n")) ,...)) |