summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrey Orst <andreyorst@gmail.com>2021-01-24 16:22:57 +0300
committerAndrey Orst <andreyorst@gmail.com>2021-01-24 18:25:42 +0300
commitb22f270b596881630fb1dbd6a721c1fe6312f00d (patch)
treefc9ac927f79039c67d263b40c6ec73de4a1161a2
parent996b6b2b199610682d32028e02e5c07f781e5373 (diff)
feature: include documentation testing in pipeline
-rw-r--r--.dir-locals.el5
-rw-r--r--.fenneldoc9
-rw-r--r--.gitlab-ci.yml15
-rw-r--r--Makefile13
-rw-r--r--README.md4
-rw-r--r--doc/cljlib.md77
-rw-r--r--doc/macros.md207
-rw-r--r--doc/tests/test.md41
-rw-r--r--init.fnl107
-rw-r--r--macros.fnl291
-rw-r--r--tests/test.fnl23
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
diff --git a/Makefile b/Makefile
index 83f78f7..2e03b9b 100644
--- a/Makefile
+++ b/Makefile
@@ -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
diff --git a/README.md b/README.md
index 6b213a5..ea3f040 100644
--- a/README.md
+++ b/README.md
@@ -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 -->
diff --git a/init.fnl b/init.fnl
index ee98456..c7f6fa8 100644
--- a/init.fnl
+++ b/init.fnl
@@ -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})
diff --git a/macros.fnl b/macros.fnl
index b045828..d53e54b 100644
--- a/macros.fnl
+++ b/macros.fnl
@@ -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"))
,...))