I'm writing a macro which calls a helper function. I need to return a list like this:
`(s -- (cons 'commentary s))
I have tried many combinations, but I can't seem to get what I need without an extra comma:
(let ((rule-name 'commentary))
(car `(`(s -- (cons ',,rule-name s)))))
;; => `(s -- (cons ',commentary s))
(let ((rule-name 'commentary))
(car `(`(s -- (cons ,',rule-name s)))))
;; => `(s -- (cons ,'commentary s))
I don't understand why the second comma is not being consumed.
The only possibly related discussion I have found is here, but I'm not sure it's related, because it's about splicing, which I'm not doing here.
What I'm actually trying to do is rewrite PEGs for peg-parse
in peg.el
to automatically capture rules that use the substring
action. To do this, I need to add this to the end of each rule that contains substring
:
`(s -- (cons 'rule-name s))
For example, I'm trying to write a PEG to parse Emacs packages. Here's the call to my macro with the PEG:
(peg-auto-capture-substring
(package package-header-line headers commentary-heading commentary code-heading rest)
(package-header-line heading-1 package-name triple-hyphen package-desc (opt file-local-variables) newline)
(headers (substring (+ (not commentary-heading) (any))))
(commentary-heading heading-1 "Commentary:" newline)
(commentary (substring (+ (not code-heading) (any))))
(code-heading heading-1 "Code:" newline)
(rest (substring (+ (any))))
(heading-1 (bol) ";;;" space)
(package-name (substring (+ (not space) (any))))
(triple-hyphen space "---" space)
(package-desc (substring (+ (not file-local-variables "\n") (any))))
(file-local-variables "-*-" space (substring (+ (not "-*-") (any))) "-*-")
(space (+ (not "\n") (or " " "\t")))
(newline (opt space) (+ "\n")))
Here's the macro:
(cl-defmacro peg-auto-capture-substring (&rest body)
`(peg-parse ,@(apply #'peg-auto-capture-substring-1 body)))
And here's the helper function:
(defun peg-auto-capture-substring-1 (&rest grammar)
;; This should work but it leaves an extra comma!
(cl-labels ((has-substring
(list)
(cl-loop for elem in list
if (and (atom elem)
(eq elem 'substring))
return t
else if (and (listp elem)
(has-substring elem))
return t)))
(cl-loop for rule in grammar
for rule-name = (car rule)
for action = `(`(s -- (cons ,',rule-name s)))
when (has-substring rule)
do (setq rule (append rule action))
collect rule)))
And here's the expansion of my macro:
(peg-parse
(package package-header-line headers commentary-heading commentary code-heading rest)
(package-header-line heading-1 package-name triple-hyphen package-desc (opt file-local-variables) newline)
(headers (substring (+ (not commentary-heading) (any)))
`(s -- (cons ,'headers s)))
(commentary-heading heading-1 "Commentary:" newline)
(commentary (substring (+ (not code-heading) (any)))
`(s -- (cons ,'commentary s)))
(code-heading heading-1 "Code:" newline)
(rest (substring (+ (any)))
`(s -- (cons ,'rest s)))
(heading-1 (bol) ";;;" space)
(package-name (substring (+ (not space) (any)))
`(s -- (cons ,'package-name s)))
(triple-hyphen space "---" space)
(package-desc (substring (+ (not file-local-variables "\n") (any)))
`(s -- (cons ,'package-desc s)))
(file-local-variables "-*-" space (substring (+ (not "-*-") (any))) "-*-"
`(s -- (cons ,'file-local-variables s)))
(space (+ (not "\n") (or " " " ")))
(newline (opt space) (+ "\n")))
As you can see, the only problem is the unconsumed comma in each action expression.
Any help would be appreciated!
Workaround found
I discovered that there is an undocumented, alternative syntax for "stack actions" in peg.el
, which allows me to do this instead of a backquoted list:
(stack-action (s -- (cons 'commentary s)))
I suppose it's like peg.el
's equivalent of (backquote)
. So I'm able to use this helper function for my macro:
(defun peg-auto-capture-substring-1 (&rest grammar)
`,@(cl-labels ((has-substring (list) (cl-loop for elem in list
if (and (atom elem)
(eq elem 'substring))
return t
else if (and (listp elem)
(has-substring elem))
return t)))
(cl-loop for rule in grammar
for rule-name = (car rule)
for action = (list (list 'stack-action `(s -- (cons ',rule-name s))))
when (has-substring rule)
do (setq rule (append rule action))
collect rule)))
Which expands to:
(peg-parse
(package package-header-line headers commentary-heading commentary code-heading rest)
(package-header-line heading-1 package-name triple-hyphen package-desc (opt file-local-variables) newline)
(headers (substring (+ (not commentary-heading) (any)))
(stack-action (s -- (cons 'headers s))))
(commentary-heading heading-1 "Commentary:" newline)
(commentary (substring (+ (not code-heading) (any)))
(stack-action (s -- (cons 'commentary s))))
(code-heading heading-1 "Code:" newline)
(rest (substring (+ (any)))
(stack-action (s -- (cons 'rest s))))
(heading-1 (bol) ";;;" space)
(package-name (substring (+ (not space) (any)))
(stack-action (s -- (cons 'package-name s))))
(triple-hyphen space "---" space)
(package-desc (substring (+ (not file-local-variables "\n") (any)))
(stack-action (s -- (cons 'package-desc s))))
(file-local-variables "-*-" space (substring (+ (not "-*-") (any))) "-*-"
(stack-action (s -- (cons 'file-local-variables s))))
(space (+ (not "\n") (or " " " ")))
(newline (opt space) (+ "\n")))
And this works, so now it parses an Emacs package file into, e.g.:
((package-name . "peg.el")
(package-desc . "Parsing Expression Grammars in Emacs Lisp ")
(file-local-variables . "lexical-binding: t; ")
...)
I'd still like to learn more about how backquotes work. I using escaped backquotes as well, like:
(let ((rule-name 'commentary))
(list '\` `(s -- (cons ',rule-name s))))
Which almost worked, except that expanded to an escaped backquote rather than an actual backquote.
I'd also like to know why the double-commas were not completely consumed by the double-backquote, because that seemed to be the real problem I was facing. Is that a bug in Emacs's backquoting, or do I just not understand how it's working internally?
Thanks for your help.
list
andcons
to create the code you are trying to create using backquotes and commas. That will give you a hint of what you need in terms of backquote syntax. IOW, to figure out what's wrong with your syntactic sugar, try coding without it and then working toward using it, bit by bit. (BTW, if what you want to end up with is the list(cons 'foo s)
then it is perfectly OK to get there via `(cons ,'foo s).)(let ((rule-name 'commentary)) (list 'backquote `(s -- (cons ',rule-name s))))
produces the result you've stated that you want (without nesting backquotes), but I'm confused as to why you want the outer backquote at all, if you are not evaluating anything in that form?peg-parse
macro requires action forms to be backquoted. See github.com/ellerh/peg.el/blob/master/peg.el#L69backquote
instead of an actual (?) backquote, which PEG doesn't accept--it gives the error(error "Reference to undefined PEG rule: backquote")
.(let ((rule-name 'commentary)) (list '\` `(s -- (cons ',rule-name s))))
(edit: Ah, I see from your edit that you've already tried that in the interim.)