summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.dir-locals.el3
-rw-r--r--Makefile5
-rw-r--r--README.org75
-rw-r--r--core.fnl4
-rw-r--r--macros/core.fnl25
-rw-r--r--macros/fn.fnl18
-rw-r--r--test/core.fnl6
-rw-r--r--test/fn.fnl76
-rw-r--r--test/macros.fnl27
-rw-r--r--test/test.fnl2
10 files changed, 154 insertions, 87 deletions
diff --git a/.dir-locals.el b/.dir-locals.el
index 0d0d3cd..2f24cfa 100644
--- a/.dir-locals.el
+++ b/.dir-locals.el
@@ -1,7 +1,8 @@
;;; Directory Local Variables
;;; For more information see (info "(emacs) Directory Variables")
-((fennel-mode . ((eval . (put 'defmethod 'fennel-indent-function 'defun))
+((fennel-mode . ((eval . (put 'when-meta 'fennel-indent-function 'defun))
+ (eval . (put 'defmethod 'fennel-indent-function 'defun))
(eval . (put 'defmulti 'bfennel-indent-function 'defun))
(eval . (put 'deftest 'fennel-indent-function 'defun))
(eval . (put 'testing 'fennel-indent-function 'defun))
diff --git a/Makefile b/Makefile
index 0d69d4e..e044f01 100644
--- a/Makefile
+++ b/Makefile
@@ -10,7 +10,7 @@ all: $(LUASOURCES)
${LUASOURCES}: $(FNLSOURCES)
%.lua: %.fnl
- fennel --lua $(LUA) --metadata --compile $< > $@
+ fennel --lua $(LUA) --compile $< > $@
clean:
rm -f *.lua
@@ -39,13 +39,12 @@ luacov-console: | luacov
luacov-stats: test/core.lua test/macros.lua test/fn.lua
@$(foreach test, $?, $(LUA) -lluarocks.loader -lluacov $(test);)
-
help:
@echo "make -- run tests and create lua library" >&2
@echo "make test -- run tests" >&2
@echo "make clean -- remove lua files" >&2
@echo "make luacov -- build coverage report (requires working tests)" >&2
- @echo "make luacov-console -- build coverage report (requires working tests)" >&2
+ @echo "make luacov-console -- build coverage report for luacov-console (requires working tests)" >&2
@echo "make help -- print this message and exit" >&2
-include .depend.mk
diff --git a/README.org b/README.org
index c16232a..ff2a4cf 100644
--- a/README.org
+++ b/README.org
@@ -15,6 +15,52 @@ Even though it is project is experimental, the goals of this project are:
* Macros
List of macros provided by the library.
+** Metadata macros
+Metadata in Fennel is a pretty tough subject, as there's no such thing as metadata in Lua.
+Therefore, the metadata usage in Fennel is more limited compared to Clojure.
+This library provides some facilities for metadata management, which are experimental and should be used with care.
+
+There are several important gotchas about using metadata.
+
+First, note that this works only when used with Fennel, and only when =(require fennel)= works.
+For compiled Lua library this feature is turned off.
+
+Second, try to avoid using metadata with anything else than tables and functions.
+When storing function or table as a key into metatable, its address is used, while when storing string of number, the value is used.
+This, for example, may cause documentation collision, when you've set some variable holding a number value to have certain docstring, and later you've defined another variable with the same value, but different docstring.
+While this isn't a major breakage, it may confuse if someone will explore your code in the REPL with =doc=.
+
+Lastly, note that prior to Fennel 0.7.1[fn:1] =import-macros= wasn't respecting =--metadata= switch.
+So if you're using Fennel < 0.7.1 this stuff will only work if you use =require-macros= instead of =import-macros=.
+
+*** =when-meta=
+This macros is a 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=.
+
+*** =with-meta=
+Attach metadata to a value.
+
+#+begin_src fennel
+ >> (local foo (with-meta (fn [...] (let [[x y z] [...]] (+ x y z)))
+ {:fnl/arglist [:x :y :z :...]
+ :fnl/docstring "sum first three values"}))
+ >> (doc foo)
+ (foo x y z ...)
+ sum first three values
+#+end_src
+
+When metadata feature is not enabled, returns the value without additional metadata.
+
+*** =meta=
+Get metadata table from object:
+
+#+begin_src fennel
+ >> (meta (with-meta {} {:meta "data"}))
+ {
+ :meta "data"
+ }
+#+end_src
+
** =def= and =defonce=
=def= is wrappers around =local= which can declare variables inside namespace, and as local at the same time:
@@ -32,7 +78,7 @@ List of macros provided by the library.
Both =ns.a= and =a= refer to the same value.
-=defonce= ensures that the binding isn't overriden by another =defonce=:
+=defonce= ensures that the binding isn't overridden by another =defonce=:
#+begin_src fennel
>> (defonce ns {})
@@ -44,7 +90,7 @@ Both =ns.a= and =a= refer to the same value.
42
#+end_src
-Both =def= and =defonce= support literal attribute table as first argument, or a :dynamic keyword, that uses Fennel =var= instead of =local=:
+Both =def= and =defonce= support literal metadata table as first argument, or a :dynamic keyword, that uses Fennel =var= instead of =local=:
#+begin_src fennel
>> (def {:dynamic true} a 10)
@@ -57,6 +103,22 @@ Both =def= and =defonce= support literal attribute table as first argument, or a
42
#+end_src
+Documentation string can be attached to value via =:doc= keyword.
+However it is not recommended to attach metadata to everything except tables and functions:
+
+#+begin_src fennel
+ ;; Bad, may overlap with existing documentation for 299792458, if any
+ >> (def {:doc "The speed of light in m/s"} c 299792458)
+ >> (doc c)
+ c
+ The speed of light in m/s
+
+ ;; OK
+ >> (def {:doc "default connection options"}
+ defaults {:port 1234
+ :host localhost})
+#+end_src
+
** =fn*=
Clojure's =fn= equivalent.
Returns a function of fixed amount of arguments by doing runtime dispatch based on argument count.
@@ -271,6 +333,7 @@ It accepts the multi-fn table as its first argument, the dispatch value as secon
=:default= is a special method which gets called when no other methods were found for given dispatch value.
+
* Functions
Here are some important functions from the library.
Full set can be examined by requiring the module.
@@ -452,4 +515,10 @@ Compose functions into one function.
#+end_src
# LocalWords: Luajit VM arity runtime multi Cljlib fn mapv kv REPL
-# LocalWords: namespaced namespace eq
+# LocalWords: namespaced namespace eq metatable Lua defonce arglist
+# LocalWords: namespaces defmulti defmethod metamethod butlast
+# LocalWords: prepend LocalWords docstring
+
+
+* Footnotes
+[fn:1] https://todo.sr.ht/~technomancy/fennel/18#event-56799
diff --git a/core.fnl b/core.fnl
index 43317ff..057a644 100644
--- a/core.fnl
+++ b/core.fnl
@@ -2,8 +2,8 @@
(local insert table.insert)
(local unpack (or table.unpack _G.unpack))
-(import-macros {: fn* : fn&} :macros.fn)
-(import-macros {: when-some : if-some : when-let} :macros.core)
+(require-macros :macros.fn)
+(require-macros :macros.core)
(fn* core.apply
"Apply `f' to the argument list formed by prepending intervening
diff --git a/macros/core.fnl b/macros/core.fnl
index b073d27..def2f4c 100644
--- a/macros/core.fnl
+++ b/macros/core.fnl
@@ -1,7 +1,8 @@
-(import-macros {: fn* : fn&} :macros.fn)
+(require-macros :macros.fn)
(local core {})
(local unpack (or table.unpack _G.unpack))
(local insert table.insert)
+(local meta-enabled (pcall _SCOPE.specials.doc (list (sym :doc) (sym :doc)) _SCOPE _CHUNK))
(fn multisym->sym [s]
(if (multi-sym? s)
@@ -152,18 +153,22 @@
(fn string? [x]
(= (type x) :string))
+(fn& core.when-meta [...]
+ (when meta-enabled `(do ,...)))
+
(fn* core.with-meta [val meta]
- `(let [val# ,val
- (res# fennel#) (pcall require :fennel)]
- (if res#
- (each [k# v# (pairs ,meta)]
- (fennel#.metadata:set val# k# v#)))
- val#))
+ (if (not meta-enabled) val
+ `(let [val# ,val
+ (res# fennel#) (pcall require :fennel)]
+ (if res#
+ (each [k# v# (pairs ,meta)]
+ (fennel#.metadata:set val# k# v#)))
+ val#)))
(fn* core.meta [v]
- `(let [(res# fennel#) (pcall require :fennel)]
- (if res#
- (. fennel#.metadata ,v))))
+ (when-meta
+ `(let [(res# fennel#) (pcall require :fennel)]
+ (if res# (. fennel#.metadata ,v)))))
(fn* core.defmulti
[name & opts]
diff --git a/macros/fn.fnl b/macros/fn.fnl
index 2d4a753..24f909d 100644
--- a/macros/fn.fnl
+++ b/macros/fn.fnl
@@ -1,14 +1,16 @@
(local unpack (or table.unpack _G.unpack))
(local insert table.insert)
(local sort table.sort)
+(local meta-enabled (pcall _SCOPE.specials.doc (list (sym :doc) (sym :doc)) _SCOPE _CHUNK))
(fn with-meta [val meta]
- `(let [val# ,val
- (res# fennel#) (pcall require :fennel)]
- (if res#
- (each [k# v# (pairs ,meta)]
- (fennel#.metadata:set val# k# v#)))
- val#))
+ (if (not meta-enabled) val
+ `(let [val# ,val
+ (res# fennel#) (pcall require :fennel)]
+ (if res#
+ (each [k# v# (pairs ,meta)]
+ (fennel#.metadata:set val# k# v#)))
+ val#)))
(fn gen-arglist-doc [args]
(if (list? (. args 1))
@@ -252,8 +254,8 @@ namespaced functions. See `fn*' for more info."
(let [docstring (if (string? doc?) doc? nil)
(name-wo-namespace namespaced?) (multisym->sym name)
arg-list (if (sym? name-wo-namespace)
- (if (string? doc?) args doc?)
- name-wo-namespace)
+ (if (string? doc?) args doc?)
+ name-wo-namespace)
arglist-doc (gen-arglist-doc arg-list)
body (if (sym? name)
(if (string? doc?)
diff --git a/test/core.fnl b/test/core.fnl
index c05f2ec..557f042 100644
--- a/test/core.fnl
+++ b/test/core.fnl
@@ -1,6 +1,6 @@
-(import-macros {: fn*} :macros.fn)
-(import-macros {: into : defmethod : defmulti} :macros.core)
-(import-macros {: assert-eq : assert-ne : assert* : testing : deftest} :test.test)
+(require-macros :macros.fn)
+(require-macros :macros.core)
+(require-macros :test.test)
(local
{: apply
diff --git a/test/fn.fnl b/test/fn.fnl
index 7ea7c0a..44e280d 100644
--- a/test/fn.fnl
+++ b/test/fn.fnl
@@ -1,47 +1,49 @@
-(import-macros {: assert-eq : assert-ne : assert* : testing : deftest} :test.test)
-(import-macros {: meta} :macros.core)
+(require-macros :test.test)
+(require-macros :macros.core)
(local {: eq} (require :core)) ;; required for testing
-(import-macros {: fn* : fn&} :macros.fn)
+(require-macros :macros.fn)
(deftest fn*
- (testing fn*-meta
- (fn* f
- "docstring"
- [x] x)
- (assert-eq (meta f) {:fnl/docstring "docstring"
- :fnl/arglist ["x"]})
+ (when-meta
+ (testing fn*-meta
+ (fn* f
+ "docstring"
+ [x] x)
+ (assert-eq (meta f) {:fnl/docstring "docstring"
+ :fnl/arglist ["x"]})
- (fn* f
- "docstring"
- ([x] x))
- (assert-eq (meta f) {:fnl/docstring "docstring"
- :fnl/arglist ["x"]})
+ (fn* f
+ "docstring"
+ ([x] x))
+ (assert-eq (meta f) {:fnl/docstring "docstring"
+ :fnl/arglist ["x"]})
- (fn* f
- "docstring"
- ([x] x)
- ([x y] (+ x y)))
- (assert-eq (meta f) {:fnl/docstring "docstring"
- :fnl/arglist ["\n [x]"
- "\n [x y]"]})
+ (fn* f
+ "docstring"
+ ([x] x)
+ ([x y] (+ x y)))
+ (assert-eq (meta f) {:fnl/docstring "docstring"
+ :fnl/arglist ["\n [x]"
+ "\n [x y]"]})
- (fn* f
- "docstring"
- ([x] x)
- ([x y] (+ x y))
- ([x y & z] (+ x y)))
- (assert-eq (meta f) {:fnl/docstring "docstring"
- :fnl/arglist ["\n [x]"
- "\n [x y]"
- "\n [x y & z]"]})))
+ (fn* f
+ "docstring"
+ ([x] x)
+ ([x y] (+ x y))
+ ([x y & z] (+ x y)))
+ (assert-eq (meta f) {:fnl/docstring "docstring"
+ :fnl/arglist ["\n [x]"
+ "\n [x y]"
+ "\n [x y & z]"]}))))
(deftest fn&
- (testing fn&-meta
- (fn& f "docstring" [x] x)
- (assert-eq (meta f) {:fnl/docstring "docstring"
- :fnl/arglist ["x"]})
+ (when-meta
+ (testing fn&-meta
+ (fn& f "docstring" [x] x)
+ (assert-eq (meta f) {:fnl/docstring "docstring"
+ :fnl/arglist ["x"]})
- (fn& f "docstring" [...] [...])
- (assert-eq (meta f) {:fnl/docstring "docstring"
- :fnl/arglist ["..."]})))
+ (fn& f "docstring" [...] [...])
+ (assert-eq (meta f) {:fnl/docstring "docstring"
+ :fnl/arglist ["..."]}))))
diff --git a/test/macros.fnl b/test/macros.fnl
index 6341433..638a29d 100644
--- a/test/macros.fnl
+++ b/test/macros.fnl
@@ -1,18 +1,7 @@
-(import-macros {: assert-eq : assert-ne : assert* : testing : deftest} :test.test)
+(require-macros :test.test)
(local {: eq : identity} (require :core)) ;; required for testing
-(import-macros
- {: if-let
- : when-let
- : if-some
- : when-some
- : into
- : defmethod
- : defmulti
- : defonce
- : def
- : meta
- : with-meta} :macros.core)
+(require-macros :macros.core)
(deftest into
(testing into
@@ -131,18 +120,18 @@
(deftest meta
(testing with-meta
- (assert-eq (meta (with-meta :a {:k :v})) {:k :v}))
+ (assert-eq (meta (with-meta :a {:k :v})) (when-meta {:k :v})))
(testing def-meta
(def {:doc "x"} x 10)
- (assert-eq (meta x) {:fnl/docstring "x"})
+ (assert-eq (meta x) (when-meta {:fnl/docstring "x"}))
(def {:doc "x" :dynamic true} x 10)
- (assert-eq (meta x) {:fnl/docstring "x"}))
+ (assert-eq (meta x) (when-meta {:fnl/docstring "x"})))
(testing defonce-meta
(defonce {:doc "x"} x 10)
- (assert-eq (meta x) {:fnl/docstring "x"})
+ (assert-eq (meta x) (when-meta {:fnl/docstring "x"}))
(defonce {:doc "y"} x 20)
- (assert-eq (meta x) {:fnl/docstring "x"})
+ (assert-eq (meta x) (when-meta {:fnl/docstring "x"}))
(defonce {:doc "y" :dynamic true} y 20)
- (assert-eq (meta y) {:fnl/docstring "y"})))
+ (assert-eq (meta y) (when-meta {:fnl/docstring "y"}))))
diff --git a/test/test.fnl b/test/test.fnl
index 4b40832..ff7c8bc 100644
--- a/test/test.fnl
+++ b/test/test.fnl
@@ -1,4 +1,4 @@
-(import-macros {: fn*} :macros.fn)
+(require-macros :macros.fn)
;; requires `eq' from core.fnl to be available at runtime
(fn* assert-eq