clockworkfrogs

Let Over Lambda nlet-tail

Nov 4th, 2021
1,204
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lisp 4.76 KB | None | 0 0
  1. ;; this macro writes a macro which evaluates the arguments, updates the bindings and jumps out to a tagbody label when called.
  2. ;; the code generated by that macro, combined with the tagbody block, essentially forms a recursive call.
  3. ;; when the recursive call ends, we jump out to a block label in order to return the result of the final function call,
  4. ;; rather than letting the tagbody wind down - because tagbody always returns nil.
  5. (defmacro! nlet-tail (n letargs &rest body)
  6.   (let ((gs (loop for i in letargs
  7.                   collect (gensym))))
  8.     `(macrolet ;; this backquote is generated by the macro and hence actually executed
  9.          ((,n ,gs ;; here we don't gensym n, because we want it to be lexically available in the ,@body
  10.             `(progn ;; this backquote represents what is returned by the macro we just defined (n, or fact below)
  11.                (psetq ;; psetq evaluates pairs all at once (psetq a 1 b 2 c 3)
  12.                 ,@(apply #'nconc
  13.                          ;; here, we are doing multi-arg mapcar with list to perform a zip
  14.                          ;; (apply #'nconc (mapcar #'list '(a b c) (list 1 2 3)))
  15.                          ;; => (A 1 B 2 C 3)
  16.                          (mapcar
  17.                           #'list
  18.                           ',(mapcar #'car letargs)
  19.                           (list ,@gs))))
  20.                ;; having generated code to set all the letargs to the values provided by the letargs,
  21.                ;; we now generate the code to GOTO the inner tagbody
  22.                ;; go expects a label, and we're interested in ,g!n. So we have to unquote to execute it, then quote the thing we want to return
  23.                (go ,',g!n)))) ;; go is a GOTO to the named tag inside tagbody
  24.        ;; ok, now the macro is defined we fill out the body
  25.        (block ,g!b ;; this is skipped on first run through, but is generated so we have a nice way to escape. we gensym it to avoid capture
  26.          (let ,letargs
  27.            (tagbody ,g!n
  28.               (return-from ,g!b ;; when this finishes executing, we jump out to block
  29.                 ;; however, arguments are eagerly evaluated! So it has to run the body first.
  30.                 ;; recursive calls in the body will jump us back to the tagbody, meaning the return doesn't happen until
  31.                 ;; body terminates. Much better than having to check a return value every time!
  32.                 (progn ,@body))))))))
  33.  
  34. (defun nlet-tail-fact (n)
  35.   (nlet-tail fact ((n n) (acc 1))
  36.              (if (zerop n)
  37.                  acc
  38.                  (fact (- n 1) (* acc n)))))
  39.  
  40. ;; OTHER BITS
  41.  
  42. ;; probably won't work unless you have a very old lisp
  43. #-sbcl
  44. (defun flatten (x)
  45.   (inspect x)
  46.   (labels ((rec (x acc)
  47.              (cond ((null x) acc)
  48.                    ((atom x) (cons x acc))
  49.                    (t (rec
  50.                        (car x)
  51.                        (rec (cdr x) acc))))))
  52.     (rec x nil)))
  53.  
  54. ;; https://stackoverflow.com/questions/33724300/macros-that-write-macros-compile-error
  55. ;; need to match the sb-impl::comma struct explicitly
  56. #+sbcl
  57. (defun flatten (x)
  58.   (labels ((rec (x acc)
  59.              (cond ((null x) acc)
  60.                    ((eq (type-of x) 'sb-impl::comma)
  61.                     (rec (sb-impl::comma-expr x) acc))
  62.                    ((atom x) (cons x acc))
  63.                    (t (rec
  64.                        (car x)
  65.                        (rec (cdr x) acc))))))
  66.     (rec x nil)))
  67.  
  68.  
  69. (defun g!-symbol-p (s)
  70.   (and (symbolp s)
  71.        (> (length (symbol-name s)) 2)
  72.        (string= (symbol-name s)
  73.                 "G!"
  74.                 :start1 0
  75.                 :end1 2)))
  76.  
  77.  
  78. (defun o!-symbol-p (s)
  79.   (and (symbolp s)
  80.        (> (length (symbol-name s)) 2)
  81.        (string= (symbol-name s)
  82.                 "O!"
  83.                 :start1 0
  84.                 :end1 2)))
  85.  
  86. (defun mkstr (&rest args)
  87.   (with-output-to-string (s)
  88.     (dolist (a  args) (princ a s))))
  89.  
  90. (defun symb (&rest args)
  91.   (values (intern (apply #'mkstr args))))
  92.  
  93. (defun o!-symbol-to-g!-symbol (s)
  94.   (symb "G!"
  95.         (subseq (symbol-name s) 2)))
  96.  
  97. (defmacro defmacro/g! (name args &rest body)
  98.   (let ((syms (remove-duplicates
  99.                (remove-if-not #'g!-symbol-p
  100.                               (flatten body)))))
  101.     `(defmacro ,name ,args
  102.        (let ,(mapcar
  103.               (lambda (s)
  104.                 `(,s (gensym ,(subseq
  105.                                (symbol-name s)
  106.                                2))))
  107.               syms)
  108.          ,@body))))
  109.  
  110.  
  111. (defmacro defmacro! (name args &rest body)
  112.   ;; first, convert all the o!s to g!s, so we can pass it on
  113.   (let* ((os (remove-if-not #'o!-symbol-p args))
  114.          (gs (mapcar #'o!-symbol-to-g!-symbol os)))
  115.     `(defmacro/g! ,name ,args
  116.        `(let ,(mapcar #'list (list ,@gs) (list ,@os))
  117.           ,(progn ,@body)))))
  118.  
  119.  
Advertisement
Add Comment
Please, Sign In to add comment