Andrey Listopadov: 1 Allow metadata tables after docstring 5 files changed, 73 insertions(+), 20 deletions(-)
Copy & paste the following snippet into your terminal to import this patchset into git:
curl -s https://lists.sr.ht/~technomancy/fennel/patches/40988/mbox | git am -3Learn more about email & git
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 [...]] + ;; ... + )) +```
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.
+ +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"} +```
This belongs in api.md rather than reference.md.
+ 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)
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
+ (. 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:
builds.sr.ht <builds@sr.ht>fennel/patches/.build.yml: SUCCESS in 55s [Allow metadata tables after docstring][0] from [Andrey Listopadov][1] [0]: https://lists.sr.ht/~technomancy/fennel/patches/40988 [1]: mailto:andreyorst@gmail.com ✓ #987202 SUCCESS fennel/patches/.build.yml https://builds.sr.ht/~technomancy/job/987202