Add optional docstring to def* macros - macros

I want to add optional docstrings to my def* macros. For example:
(defmacro defhtml
"Macro to avoid backtick unquote[splicing] in html vectors.
TODO: Add optional docstring."
[name args & body]
`(defn ~name ~args (html ~#body)))
;; Working defhtml
(defhtml include-css [href]
[:link {:href href :rel "stylesheet"}])
I would like:
(defhtml include-css
"My optional docstring here."
[:link {:href href :rel "stylesheet"}])
I figure there should be some common idiom for this.

You'll need to decision off of whether or not the second argument to your macro is a doc-string (you can test if it's a string). Clojure macros are Clojure, so you can perform any logic or manipulations on the forms passed to the macro that you want to. This should be close to if not exactly what you're after:
(defmacro defhtml [name & args]
(cond
;; doc-string?
(string? (first args))
(let [[doc-string args-list & body] args]
`(defn ~name ~doc-string ~args-list (html ~#body)))
:no-doc-string
(let [[args-list & body] args]
`(defn ~name ~(format "HTML Generator %s" name) ~args-list (html ~#body)))))
That should produce the macro expansions you're after:
(defhtml include-css [href]
[:link {:href href :rel "stylesheet"}])
produces:
(defn include-css
"HTML Generator include-css"
[href]
(html [:link {:href href, :rel "stylesheet"}]))
while:
(defhtml include-css
"Standard css includes fory my site"
[href]
[:link {:href href :rel "stylesheet"}])
produces:
(defn include-css
"Standard css includes fory my site"
[href]
(html [:link {:href href, :rel "stylesheet"}]))

defn and defmacro already support optional docstrings, so if your def* macro expands to a call to one of these, you may not need to include any string? checks in your own code. This is the case with defhtml, which can be implemented thus:
;; also adding an optional attribute map parameter, because why not?
(defmacro defhtml [name doc? attr-map? params & body]
`(defn ~name ~doc? ~attr-map? ~params (html ~#body)))
;; equivalent, perhaps prettier:
(defmacro defhtml [name doc? attr-map? params & body]
`(defn ~#[name doc? attr-map? params] (html ~#body)))

Related

Macro argument not being substituted in

I'm trying to fully understand the limitations of compile-time macros.
Here is a macro (I'm fully aware that this is not a best-practice macro):
(defmacro emit (language file &body body)
(print language)
(print file)
(print body)
(with-open-file (str file :direction :output :if-exists :supersede)
(princ (cond ((eq language 'html)
(cl-who:with-html-output-to-string (s nil :prologue t :indent t) body))
((eq language 'javascript)
(parenscript:ps body))
((eq language 'json)
(remove #\; (parenscript:ps body))))
str)))
I compile the macro:
; processing (DEFMACRO EMIT ...)
PROGRAM>
I compile this form:
PROGRAM> (compile nil (lambda () (emit json "~/file" (ps:create "hi" "hello") (ps:create "yo" "howdy"))))
JSON
"~/file"
((PARENSCRIPT:CREATE "hi" "hello") (PARENSCRIPT:CREATE "yo" "howdy"))
#<FUNCTION (LAMBDA ()) {5367482B}>
NIL
NIL
PROGRAM>
The compile-time print output is what I expect.
However, if I look at ~/file:
body
It appears that ((PARENSCRIPT:CREATE "hi" "hello") (PARENSCRIPT:CREATE "yo" "howdy")) was never substituted in for the parameter body, and thus never processed.
Why is this?
& what would be the best literature to read on this subject?
Why should it substitute? You never substituted anything.
A macro defines a macro substitution function, which is applied to the actual form in the code to produce another form which is then compiled. When you apply your macro definition to those parameters, it will at macroexpansion time do all kinds of things (write a file etc.) before returning what princ returned, which is exactly its first argument, and this returned form is then compiled. I don't think that is what you want.
It seems that what you actually want to do is to expand to a form that interprets the body in one of a variety of ways, indicated by the first argument.
What you need to do is to return the new form, so that
(emit 'html "foo.html"
(:html (:head) (:body "whatever")))
expands to
(with-open-file (str "foo.html" :direction :output :etc :etc)
(cl-who:with-html-output (str)
(:html (:head) (:body "whatever")))
For that, we have a template syntax: the backtick.
`(foo ,bar baz)
means the same as
(list 'foo bar 'baz)
but makes the structure of transformed code a bit clearer. There is also ,# to splice things into a list.
`(foo ,#bar)
means the same as
(list* 'foo bar)
i. e. the contents of bar, when they are a list, are spliced into the list. This is especially useful for bodies such as in your macro.
(defmacro emit (language file &body body)
`(with-open-file (str ,file :direction :output :if-exists :supersede)
(princ (cond ((eq ,language 'html)
(cl-who:with-html-output-to-string (s nil :prologue t :indent t)
,#body))
((eq ,language 'javascript)
(parenscript:ps ,#body))
((eq ,language 'json)
(remove #\; (parenscript:ps ,#body))))
str)))
Note where I introduced the backtick to create a template and commata to put outer arguments into it. Note also that the arguments are forms.
This has a few problems: there are hardcoded symbols that the user of the macro has no way of knowing. In one case (str) they have to pay attention not to shadow it, in the other (s) they have to know it in order to write to it. For this, we use either generated symbols (for str so that there is no conflict possible) or let the user say what they want to name it (for s). Also, this cond can be simplified to a case:
(defmacro emit (language file var &body body)
(let ((str (gensym "str")))
`(with-open-file (,str ,file
:direction :output
:if-exists :supersede)
(princ (case ,language
('html
(cl-who:with-html-output-to-string (,var nil
:prologue t
:indent t)
,#body))
('javascript
(parenscript:ps ,#body))
('json
(remove #\; (parenscript:ps ,#body))))
,str)))
However, you might want to determine the output code already at macro expansion time.
(defmacro emit (language file var &body body)
(let ((str (gensym "str")))
`(with-open-file (,str ,file
:direction :output
:if-exists :supersede)
(princ ,(case language
('html
`(cl-who:with-html-output-to-string (,var nil
:prologue t
:indent t)
,#body))
('javascript
`(parenscript:ps ,#body))
('json
`(remove #\; (parenscript:ps ,#body))))
,str)))
Here, you can see that the case form is already evaluated at macro expansion time, and an inner template is then used to create the inner form.
This is all completely untested, so removing the little errors is left as an exercise ^^.
One book that has a lot of things to say about macro writing is »On Lisp« by Paul Graham. The freely available »Practical Common Lisp« by Peter Seibel also has a chapter about it, and there are also some recipes in »Common Lisp Recipes« by Edi Weitz.
parenscript:ps is a macro, not a function: its body is literal parenscript and is not evaluated but compiled, from Parenscript to JavaSctipt. This is easy to check:
> (parenscript:ps body)
"body;"
I don't have any advice on what you should read: this macro looks so utterly confused I can't really understand what the underlying intent was. A macro in CL is a function which whose argument is source code in some language L1 and which returns source code in some language L2, where L2 is usually a subset of L1. I can't work out, though, if this is just the normal case of someone thinking they need a macro when they need a function, or if it's some other confusion.

Evaluating a macro when form is read from a string

I have a macro with-voice:
(defmacro with-voice (tag body)
`(format nil "<span class=\"~a\">~%~a~%</span>" ',tag ,body))
which emits some text surrounded by a tag with some class. I know there are great libraries like CL-WHO - but I just need something tiny...
CL-USER> (with-voice narrator "foo")
"<span class=\"NARRATOR\">
foo
</span>"
which is is the desired outcome
I would like to be able to do this from a string
(let ((s (read-from-string "(with-voice narrator \"foo\")")))
(print (eval s)))
This works:
CL-USER> (let ((s (read-from-string "(with-voice narrator \"foo\")")))
(print (eval s)))
"<span class=\"NARRATOR\">
foo
</span>"
"<span class=\"NARRATOR\">
foo
</span>
but it has the dreaded eval. I've tried to get this working using macros and lambdas, but I can't get it working.
I'd be grateful for some help
thanks!
You could funcall the symbol you expect in a certain position of the read form:
(destructuring-bind (operator class text)
(read-from-string "(with-voice narrator \"foo\")")
(funcall operator class text))
Or even just expect those forms to be funcallable:
(apply #'funcall (read-from-string "(with-voice narrator \"foo\")"))
If you have more different shapes of data, you might want to match those, e. g. using optima, or using dispatch by eql specializer. This can also help with validation if the read input might contain harmful intent.

Substitute symbol name in macro

How can I substitute a symbol name into a function created in a macro? I think I am missing something obvious here. For example, I am trying to make a macro that defines some variables and functions similar to the following,
(cl-defmacro mac (pkg)
(let (
;; Define some variables
(sym (intern (concat pkg "-file")))
(sym-def "default-file.el")
(sym-doc (concat "A custom var from `" pkg "'."))
;; Define some functions
(symfn (intern (concat pkg "-fn")))
(symfn-doc (concat "A function for `" pkg "'.")))
`(list
(defcustom ,sym ,sym-def ,sym-doc
:group (quote ,(make-symbol pkg))
:type '(choice (const :tag "None" nil)
file))
(defun ,symfn ()
,symfn-doc
(interactive)
(fn ,sym)))))
The function returned makes a call out to another function (fn) with a signature like
(defun fn (var) (symbol-value var))
So, it is expecting a call like (fn 'some-var). And, I want to be able to use the macro like
(mac "pack")
And have the following work,
pack-file ; works: "default-file.el"
(pack-fn) ; error: not a symbol
I have tried things like (quote ,sym), symbol-name, and others... But can't seem to get it right.
You want the call to fn to be (fn ',sym) (which you mention you tried in the question, but I suspect got wrong somehow).
You probably also want the expansion of the macro to be (progn ...) instead of (list ...).
(This was originally a comment: I'm putting it here just so there's an answer.)

Clojure macro inside another macro: how to generate string from symbol

Probably the title is not 100% correct, but let me show you the issue:
(defmacro nvp!
[n width height]
`(q/defsketch (symbol (str (name '~n) "-viewport" ))
:title (name '~n))
In short: there's a macro called defsketch (it's part of the lib quil, but that does not matter). It's signature is basically defsketch [applet-name & options], and it creates something and binds it to a var called applet-name. For some reason, I want to wrap this into another macro that -- among other things -- takes an applet-name parameter, extends that name into applet-name-viewport and passes that to defsketch. I, however, am unable to figure out how to do that correctly (using macroexpand on the code above ((pprint (macroexpand(nvp test-name 500 500))), I get
(def(clojure.core/symbol (clojure.core/str (clojure.core/name 'my-namespace.core/test-name) "-viewport"))
(quil.applet/applet
:title (clojure.core/name 'my-namespace.core/test-name)))
(clojure.core/symbol (clojure.core/str (clojure.core/name 'my-namespace.core/test-name) "-viewport") -- this part looks good, but it should be evaluated somehow before passing it to the inner macro...
You need to unquote the form that generates the new symbol instead of the original symbol itself.
Here is a small example of how to accomplish this using a defn like macro for the inner macro:
(defmacro mydefn
[name & body]
`(defn ~name ~#body))
(defmacro defnview
[n & body]
`(mydefn ~(symbol (str (name n) "-viewport")) ~#body))
;; OR
(defmacro defnview
[n & body]
(let [n (symbol (str (name n) "-viewport"))]
`(mydefn ~n ~#body)))
Example:
(defnview foo [a] a)
;; => #'user/foo-viewport
(foo-viewport 1)
;; => 1
(macroexpand '(defnview foo [a] a))
;; => (def foo-viewport (clojure.core/fn ([a] a)))

Clojure defmacro loses metadata

I am trying to create a little Clojure macro that defs a String with a type hint:
(defmacro def-string [name value]
`(def ^String ~name ~value))
(def-string db-host-option "db-host")
When I macroexpand it, the type hint is lost:
(macroexpand '(def-string db-host-option "db-host"))
;=> (def db-host-option "db-host")
Never mind the wisdom of type hinting this.
Why is the macro losing the metadata? How do I write this macro, or any that includes metadata?
^ is a reader macro. defmacro never gets to see it. The hint is put on the list (unquote name). Compare for example (meta ^String 'x) with (meta ' ^String x) to see the effect.
You need to put the hint on the symbol.
(defmacro def-string
[name value]
`(def ~(vary-meta name assoc :tag `String) ~value))
And the usage:
user=> (def-string foo "bar")
#'user/foo
user=> (meta #'foo)
{:ns #<Namespace user>, :name foo, :file "NO_SOURCE_PATH", :line 5, :tag java.lang.String}
Metadata doesn't show up in a macroexpand since it's supposed to be "invisible".
If the macro is correct (which it isn't) you should be able to call (meta #'db-host-option) to inspect the meta data on the var.
Note that (def sym ...) inserts metadata on the var that it receives from the symbol. But ^Tag ~name sets the meta data on ~name (unquote name), not on the passed in symbol bound to name. It can't do anything else since ^Tag ... processing is done by the reader, which is already finished once macro expansion starts.
You want something like
(defmacro def-string [name value]
`(def ~(with-meta name {:tag String}) ~value))
user> (def-string bar 1)
#'user/bar
user> (meta #'bar)
{:ns #<Namespace user>, :name bar, :file "NO_SOURCE_FILE", :line 1, :tag java.lang.String}