With this change, it will be possible to follow the usual docstring
with metadata table literal, avoiding the need to nest the docstring
in the table, making it more convenient for both reading and writing
the code.
Additionally, metadata:get call will now return the whole metadata
table if no key is passed to it.
---
changelog.md | 4 +++-
reference.md | 30 +++++++++++++++++++++++++++++-
src/fennel/compiler.fnl | 6 ++++--
src/fennel/specials.fnl | 39 ++++++++++++++++++++++++---------------
test/repl.fnl | 14 +++++++++++++-
5 files changed, 73 insertions(+), 20 deletions(-)
diff --git a/changelog.md b/changelog.md
index 841a214..c9e439b 100644
--- a/changelog.md
+++ b/changelog.md
@@ -11,7 +11,7 @@ deprecated forms.
### Bug Fixes
* Fix a bug where tables like `{:true 1 true 2}` emit with unstable key order
* Fix a bug where table literals mutated by a macro emit with unstable key
- order when AOT-compiled in Lua > 5.2
+ order when AOT-compiled in Lua > 5.2
* Fix a bug where very long individual lines in the repl would be truncated
* Fix an edge case where `{:__metatable true}` (as in pandoc-lua) breaks fennel.view
* Fix a 1.3.0 bug where `macros` only accepts table literals, not table-returning exprs
@@ -25,6 +25,8 @@ deprecated forms.
if given optional argument
* Expose REPL's methods in the `___repl___` table, allowing method
redefinition at runtime.
+* Allow following docstring with a metadata table syntax.
+* Return whole metadata table when `metadata.get` is called without a key.
## 1.3.0 / 2023-02-13
diff --git a/reference.md b/reference.md
index 5751c3c..3ad0b52 100644
--- a/reference.md
+++ b/reference.md
@@ -122,7 +122,7 @@ recursion:
```fennel
(fn pxy [x y]
(print (+ x y)))
-
+
(local pxy (fn [x y]
(print (+ x y))))
```
@@ -270,6 +270,34 @@ Such metadata can be any data literal, including tables, with the only
restriction that there are no side effects. Fennel's lists are
disallowed as metadata values.
+*(Since 1.3.1)*
+
+For editing convenience, the metadata table literals are allowed after docstrings:
+
+``` fennel
+(fn some-function [x ...]
+ "Docstring for some-function."
+ {:fnl/arglist [x & xs]
+ :other :metadata}
+ (let [xs [...]]
+ ;; ...
+ ))
+```
+
+In this case, the documentation string is automatically inserted to
+the metadata table by the compiler.
+
+The whole metadata table can be obtained by calling `metadata:get`
+without the `key` argument:
+
+```
+>> (local {: metadata} (require :fennel))
+>> (metadata:get some-function)
+{:fnl/arglist ["x" "&" "xs"]
+ :fnl/docstring "Docstring for some-function."
+ :other "metadata"}
+```
+
Fennel itself only uses the `fnl/docstring` and `fnl/arglist` metadata
keys but third-party code can make use of arbitrary keys.
diff --git a/src/fennel/compiler.fnl b/src/fennel/compiler.fnl
index 94af299..e582cce 100644
--- a/src/fennel/compiler.fnl
+++ b/src/fennel/compiler.fnl
@@ -329,9 +329,11 @@
(fn make-metadata []
"Make module-wide state table for metadata."
- (setmetatable [] {:__index {:get (fn [self tgt key]
+ (setmetatable [] {:__index {:get (fn [self tgt ?key]
(when (. self tgt)
- (. (. self tgt) key)))
+ (if (not= nil ?key)
+ (. (. self tgt) ?key)
+ (. self tgt))))
:set (fn [self tgt key value]
(tset self tgt (or (. self tgt) []))
(tset (. self tgt) key value)
diff --git a/src/fennel/specials.fnl b/src/fennel/specials.fnl
index ff2925f..fa96487 100644
--- a/src/fennel/specials.fnl
+++ b/src/fennel/specials.fnl
@@ -274,23 +274,32 @@
(compile-named-fn ast f-scope f-chunk parent index fn-name true
arg-name-list f-metadata)))
-(fn get-function-metadata [ast arg-list index]
- ;; Get function metadata from ast and put it in a table. Detects if
- ;; the next expression after a argument list is either a string or a
- ;; table, and copies values into function metadata table.
- (let [f-metadata {:fnl/arglist arg-list}
- index* (+ index 1)
+(fn maybe-metadata [ast pred handler mt index]
+ ;; check if conditions for metadata literal are met. The index must
+ ;; not be the last in the ast, and the expression at the index must
+ ;; conform to pred. If conditions are met the handler is called
+ ;; with metadata table and expression. Returns metadata table and
+ ;; an index.
+ (let [index* (+ index 1)
index*-before-ast-end? (< index* (length ast))
expr (. ast index*)]
- (if (and index*-before-ast-end? (utils.string? expr))
- (values (doto f-metadata
- (tset :fnl/docstring expr))
- index*)
- (and index*-before-ast-end? (utils.kv-table? expr))
- (values (collect [k v (pairs expr) :into f-metadata]
- (values k v))
- index*)
- (values f-metadata index))))
+ (if (and index*-before-ast-end? (pred expr))
+ (values (handler mt expr) index*)
+ (values mt index))))
+
+(fn get-function-metadata [ast arg-list index]
+ ;; Get function metadata from ast and put it in a table. Detects if
+ ;; the next expression after the argument list is either a string or
+ ;; a table, and copies values into function metadata table. If it
+ ;; is a string, checks if the next one is a table and combines them.
+ (->> (values {:fnl/arglist arg-list} index)
+ (maybe-metadata
+ ast utils.string?
+ #(doto $1 (tset :fnl/docstring $2)))
+ (maybe-metadata
+ ast utils.kv-table?
+ #(collect [k v (pairs $2) :into $1]
+ (values k v)))))
(fn SPECIALS.fn [ast scope parent]
(let [f-scope (doto (compiler.make-scope scope)
diff --git a/test/repl.fnl b/test/repl.fnl
index 701d3a4..054eda0 100644
--- a/test/repl.fnl
+++ b/test/repl.fnl
@@ -304,10 +304,22 @@
(view (metadata:get qux :qux))"
"{:compound [\"seq\" {:table \"table\"}]}"
"expected compound metadata to work"]
+ ["(fn quux [] \"docs\" {:foo :some-data} nil)
+ (view [(metadata:get quux :foo) (metadata:get quux :fnl/docstring)])"
+ "[\"some-data\" \"docs\"]"
+ "expected combined docstring and ordinary string metadata to work"]
["(λ a-lambda [x ...] {:fnl/arglist [x y z]} nil)
(view (metadata:get a-lambda :fnl/arglist))"
"[\"x\" \"y\" \"z\"]"
- "expected lambda metadata literal to work"]]
+ "expected lambda metadata literal to work"]
+ ["(λ b-lambda [] \"docs\" {:fnl/arglist [x y z]} nil)
+ (view [(metadata:get b-lambda :fnl/arglist) (metadata:get b-lambda :fnl/docstring)])"
+ "[[\"x\" \"y\" \"z\"] \"docs\"]"
+ "expected combined docstring and ordinary string metadata to work"]
+ ["(fn whole [x] nil)
+ (view (metadata:get whole))"
+ "{:fnl/arglist [\"x\"]}"
+ "expected whole metadata table when no key is asked"]]
err-cases [["(fn foo [] {:foo (fn [] nil)} nil)"
"expected literal value in metadata table, got: \"foo\" (fn [] nil)"
"lists are not allowed as metadata fields"]
--
2.40.1
Andrey Listopadov <andreyorst@gmail.com> writes:
> +For editing convenience, the metadata table literals are allowed after docstrings:
> +
> +``` fennel
> +(fn some-function [x ...]
> + "Docstring for some-function."
> + {:fnl/arglist [x & xs]
> + :other :metadata}
> + (let [xs [...]]
> + ;; ...
> + ))
> +```
Overall it would be better to introduce this feature showing this style
first rather than last, because it's the more natural way to do it. Then
maybe a note that prior to 1.3.1 metadata tables and docstrings were
mutually exclusive.
> +The whole metadata table can be obtained by calling `metadata:get`
> +without the `key` argument:
> +
> +```
> +>> (local {: metadata} (require :fennel))
> +>> (metadata:get some-function)
> +{:fnl/arglist ["x" "&" "xs"]
> + :fnl/docstring "Docstring for some-function."
> + :other "metadata"}
> +```
This belongs in api.md rather than reference.md.
> +++ b/src/fennel/compiler.fnl
> @@ -329,9 +329,11 @@
>
> (fn make-metadata []
> "Make module-wide state table for metadata."
> - (setmetatable [] {:__index {:get (fn [self tgt key]
> + (setmetatable [] {:__index {:get (fn [self tgt ?key]
> (when (. self tgt)
> - (. (. self tgt) key)))
> + (if (not= nil ?key)
> + (. (. self tgt) ?key)
FYI a single . call would suffice here: (. self tgt ?key)
I'll go ahead and apply this since these are pretty minor but if you
have a chance, it'd be cool if you could rearrange the documentation for
this; thanks!
-Phil