• 0 Posts
  • 2 Comments
Joined 1 year ago
cake
Cake day: October 21st, 2023

help-circle
  • Macros are expanded recursively, so if the result of expanding a macro is itself a macro then the evaluator or compiler will immediately expand it again. This is why we have MACROEXPAND-1 which expands it once, and MACROEXPAND which expands a form until it is no longer a macro form. I would suggest you MACROEXPAND-1 my answer, then repeat with MACROEXPAND to see the non-macro code that actually ends up being compiled/evaluated.

    You may find Section 3 of the HyperSpec helpful. The spec goes into a fair amount of detail about the semantics of both evaluation and compilation which should help.


  • I will preface this by saying in general I don’t think the approach of interning symbols and binding them at runtime is a good idea. If you’ve referenced these symbols by name in the body of the form, you already knew you needed them, so why not just just use LET to create the binding? Without knowing the details of your use case it sounds like it could be an XY problem.

    However, this being Common Lisp, there is a way to do most things and the special operator PROGV is used to create dynamic variable bindings. This is not a tool I reach for often, but it does occasionally come in handy. I am not proud of this:

    (in-package #:cl-user)
    (ql:quickload "alexandria")
    
    (defmacro with-free-ports ((start end &key (prefix "PORT-") limit) &body body)
      (check-type limit (or null (integer 1 *)))
      (alexandria:with-gensyms (first-port last-port index vars values symbol-prefix)
        `(let ((,first-port ,start)
               (,last-port ,end)
               (,symbol-prefix ,prefix))
           (check-type ,first-port (integer 1 *))
           (check-type ,last-port (integer 1 *))
           (assert (>= ,last-port ,first-port) (,first-port ,last-port) "port range not ordered")
           (check-type ,symbol-prefix (or string symbol character))
           ,(when limit
              `(when (>= (- ,last-port ,first-port) ,limit)
                 (error ,(format nil "WITH-FREE-PORTS limit (~A) exceeded" limit))))
           (loop named with-free-ports
                 for ,index from ,first-port to ,last-port               
                 collecting (alexandria:symbolicate ,symbol-prefix (princ-to-string ,index)) into ,vars
                 collecting (format nil "127.0.0.1:~A" ,index) into ,values
                 finally (return-from with-free-ports (progv ,vars ,values ,@body))))))
    

    To use it:

    (let ((start 80))
      (with-free-ports (start (+ start 5) :limit 10)
        (setf port-80 "http")
        (values port-80 port-85))) ;; => "http", "127.0.0.1:85"
    

    I have added what I consider the bare minimum of safety checks, such as a limit on the number of symbols it will create. You also lose help from the compiler which can’t warn you about unused variables and you may get warned that some of your variables have been assumed special (at least on LispWorks). Overall, I think its a bit of hack and not something I would put in production code.

    It is also worth noting the following loop achieves the same thing, without the hackery and is trivial to change to a vector if you’re worried about efficiency:

    (let ((start 80))
      (loop for port from start to (+ start 5)
            collecting (format nil "127.0.0.1:~A" port) into ports
            finally (progn
                      (setf (first ports) "http")
                      (return (values (first ports) (sixth ports))))))