Search code examples
macroslispcommon-lisp

Why isn't my macro functioning in the same way as handwritten code?


So I have 2 router functions for learning to work with clack in common lisp. One is written without macros, and the other is written with macros that expand to match the first function, but only the non-macro version is working. The code below has (:use :trivia) from (ql:quickload 'trivia).

This one is written without any macros, and works:

(defun router (env)
  (match env
    ((guard (property :path-info path)
        (equalp "/" path))
     (home env))
    ((guard (property :path-info path)
        (equalp "/live/clicked" path))
     (live-clicked env))
    ((property :path-info path)
     `(404 nil (,(format nil "404 page not found"))))))

I decided I didn't like those guard clauses taking up so much space in the function definition, so I rewrote the function:

(defun router (env)
  (match env
    (route "/" (home env))
    (route "/live/clicked" (live-clicked env))
    ((property :path-info path)
     `(404 nil (,(format nil "404 page not found"))))))

route is defined as so:

(defmacro route (valid-path &body body)
  (let ((path (gensym)))
    `((guard (property :path-info ,path)
         (equalp ,valid-path ,path))
      ,@body)))

With this new router function and macro, the function is always short-circuiting on the first clause. When macroexpanding the 2 (route ...) clauses, I receive this output, matching the function I wrote:

* `(defun router (env)
      (match env
        ,(macroexpand '(route "/" (home env))) 
        ,(macroexpand '(route "/live/clicked" (live-clicked env)))
        ((property :path-info path)
         `(404 nil (,(format nil "404")))))))
(DEFUN ROUTER (ENV)
  (MATCH ENV
   ((GUARD (PROPERTY :PATH-INFO #:G120) (EQUALP "/" #:G120)) (HOME ENV))
   ((GUARD (PROPERTY :PATH-INFO #:G121) (EQUALP "/live/clicked" #:G121)) (LIVE-CLICKED ENV))
   ((PROPERTY :PATH-INFO PATH) `(404 NIL (,(FORMAT NIL "404")))))

(home env) and (live-clicked env) are functions that return something similar to (backquote (200 nil (,(*form generating html*)))). env is the state of the web request, but in this instance it only needs to be (list :path-info "/live/clicked")


Solution

  • The pattern matcher you are using, Trivia, has a defpattern macro. That's what you must use to define a macro against the pattern language itself, rather than defmacro.