Download src/cl_macros/knowledge_base.py from j14i/cl-ds: direct link, hf CLI and curl.
- Browser
- Download file 33.4 kB
-
https://huggingface.co/j14i/cl-ds/resolve/main/src/cl_macros/knowledge_base.py
- Command line
-
hf download hf://j14i/cl-ds/src/cl_macros/knowledge_base.py
-
curl -L -o knowledge_base.py https://huggingface.co/j14i/cl-ds/resolve/main/src/cl_macros/knowledge_base.py
33.4 kB
| """Curated macro transformation examples β exact macro definitions from | |
| the Let Over Lambda source repository (thephoeron/let-over-lambda). | |
| Each entry has the verified defmacro form from the production repo. | |
| Uses raw triple-quoted strings (r\"\"\") for CL code to avoid escaping issues. | |
| """ | |
| from cl_macros.schema import ( | |
| Complexity, | |
| MacroCategory, | |
| MacroTechnique, | |
| Source, | |
| TransformationExample, | |
| ) | |
| # ============================================================ | |
| # 1. defmacro/g! β Automatic gensym generation | |
| # ============================================================ | |
| _gbang_macro = r"""(defun g!-symbol-p (s) | |
| (and (symbolp s) | |
| (> (length (symbol-name s)) 2) | |
| (string= (symbol-name s) | |
| "G!" | |
| :start1 0 | |
| :end1 2))) | |
| (defmacro defmacro/g! (name args &rest body) | |
| (let ((syms (remove-duplicates | |
| (remove-if-not #'g!-symbol-p | |
| (flatten body))))) | |
| (multiple-value-bind (body declarations docstring) | |
| (parse-body body :documentation t) | |
| `(defmacro ,name ,args | |
| ,@(when docstring | |
| (list docstring)) | |
| ,@declarations | |
| (let ,(mapcar | |
| (lambda (s) | |
| `(,s (gensym ,(subseq | |
| (symbol-name s) | |
| 2)))) | |
| syms) | |
| ,@body)))))""" | |
| _gbang_before = r""";; Manual gensym creation is boilerplate-heavy | |
| (defmacro with-temp (var &body body) | |
| (let ((g (gensym))) | |
| `(let ((,g ,var)) | |
| ,@body))) | |
| ;; Every macro needs this pattern - repetitive and error-prone""" | |
| _gbang_after = r""";; Usage: | |
| ;; (defmacro/g! with-temp (g!var &body body) | |
| ;; `(let ((,g!var ,g!var)) | |
| ;; ,@body)) | |
| ;; | |
| ;; g!var is automatically gensym'd to #:VAR1234 at expansion time | |
| ;; No capture risk - gensym is uninterned""" | |
| G_BANG = TransformationExample( | |
| id="lol-defmacro-g", | |
| before_code=_gbang_before, | |
| problem_pattern="Repeated gensym boilerplate in macro definitions β want automatic gensym creation from naming convention", | |
| macro_definition=_gbang_macro, | |
| after_expansion=_gbang_after, | |
| macro_category=MacroCategory.CAPTURE_MANAGEMENT, | |
| technique=[MacroTechnique.GENSYM, MacroTechnique.CODE_WALKING], | |
| source=Source.LET_OVER_LAMBDA, | |
| source_chapter="ch3", | |
| complexity=Complexity.ADVANCED, | |
| has_capture_risk=False, | |
| requires_gensyms=True, | |
| commentary="Scans macro body for G!-prefixed symbols, creates gensym bindings at expansion time. Uses alexandria:parse-body for proper docstring/declaration handling. This is NOT hygienic macros β it's automatic gensym generation in full Lisp.", | |
| ) | |
| # ============================================================ | |
| # 2. defmacro! β Automatic once-only + gensym | |
| # ============================================================ | |
| _defmacrobang_macro = r"""(defun o!-symbol-p (s) | |
| (and (symbolp s) | |
| (> (length (symbol-name s)) 2) | |
| (string= (symbol-name s) | |
| "O!" | |
| :start1 0 | |
| :end1 2))) | |
| (defun o!-symbol-to-g!-symbol (s) | |
| (symb "G!" | |
| (subseq (symbol-name s) 2))) | |
| (defmacro defmacro! (name args &rest body) | |
| (let* ((os (remove-if-not #'o!-symbol-p (flatten args))) | |
| (gs (mapcar #'o!-symbol-to-g!-symbol os))) | |
| (multiple-value-bind (body declarations docstring) | |
| (parse-body body :documentation t) | |
| `(defmacro/g! ,name ,args | |
| ,@(when docstring | |
| (list docstring)) | |
| ,@declarations | |
| `(let ,(mapcar #'list (list ,@gs) (list ,@os)) | |
| ,(progn ,@body))))))""" | |
| _defmacrobang_before = r""";; Macro with double-evaluation bug | |
| (defmacro square (x) | |
| `(* ,x ,x)) | |
| ;; (square (incf y)) evaluates (incf y) TWICE | |
| ;; Manual fix requires gensym + let boilerplate every time | |
| (defmacro square (x) | |
| (let ((g (gensym))) | |
| `(let ((,g ,x)) | |
| (* ,g ,g))))""" | |
| _defmacrobang_after = r""";; (defmacro! square (o!x) | |
| ;; `(* ,g!x ,g!x)) | |
| ;; | |
| ;; (macroexpand '(square (incf y))) | |
| ;; => (LET ((#:X1633 (INCF Y))) | |
| ;; (* #:X1633 #:X1633)) | |
| ;; | |
| ;; o!x = original form (still accessible via ',o!x) | |
| ;; g!x = evaluated value, bound once""" | |
| DEFMACRO_BANG = TransformationExample( | |
| id="lol-defmacro-bang", | |
| before_code=_defmacrobang_before, | |
| problem_pattern="Macro arguments evaluated multiple times (double evaluation) β need automatic once-only that also provides both original form and evaluated value", | |
| macro_definition=_defmacrobang_macro, | |
| after_expansion=_defmacrobang_after, | |
| macro_category=MacroCategory.CAPTURE_MANAGEMENT, | |
| technique=[MacroTechnique.ONCE_ONLY, MacroTechnique.GENSYM], | |
| source=Source.LET_OVER_LAMBDA, | |
| source_chapter="ch3", | |
| complexity=Complexity.ADVANCED, | |
| has_capture_risk=False, | |
| requires_gensyms=True, | |
| commentary="O!-prefixed args trigger once-only: argument form evaluated exactly once, result bound to corresponding G!-prefixed gensym. The O! symbol still names the original unevaluated form (accessible via quote). Builds on top of defmacro/g!.", | |
| ) | |
| # ============================================================ | |
| # 3. aif β Anaphoric if (Graham) | |
| # ============================================================ | |
| _aif_macro = r"""(defmacro aif (test then &optional else) | |
| `(let ((it ,test)) | |
| (if it ,then ,else)))""" | |
| _aif_before = r""";; Common pattern: test a value, use result in then-branch | |
| (let ((result (compute-something))) | |
| (if result | |
| (format t "Got: ~a~%" result) | |
| :nothing)) | |
| ;; Want: implicit 'it' bound to test result""" | |
| _aif_after = r""";; (aif (compute-something) | |
| ;; (format t "Got: ~a~%" it) | |
| ;; :nothing) | |
| ;; Expands to: | |
| ;; (LET ((IT (COMPUTE-SOMETHING))) | |
| ;; (IF IT | |
| ;; (FORMAT T "Got: ~a~%" IT) | |
| ;; :NOTHING))""" | |
| AIFF = TransformationExample( | |
| id="lol-aif", | |
| before_code=_aif_before, | |
| problem_pattern="Repeated pattern of binding test result then branching on it β want implicit 'it' anaphor", | |
| macro_definition=_aif_macro, | |
| after_expansion=_aif_after, | |
| macro_category=MacroCategory.ANAPHORIC, | |
| technique=[MacroTechnique.ANAPHOR], | |
| source=Source.LET_OVER_LAMBDA, | |
| source_chapter="ch6", | |
| complexity=Complexity.BASIC, | |
| has_capture_risk=True, | |
| requires_gensyms=False, | |
| commentary="Paul Graham's classic anaphoric if. Deliberately captures 'it' so the then-clause can reference the test result. This is intentional free variable injection β the user must know 'it' exists.", | |
| ) | |
| # ============================================================ | |
| # 4. alambda β Anaphoric lambda (Graham) | |
| # ============================================================ | |
| _alambda_macro = r"""(defmacro alambda (parms &body body) | |
| `(labels ((self ,parms ,@body)) | |
| #'self))""" | |
| _alambda_before = r""";; Anonymous recursion requires explicit labels every time | |
| (labels ((self (n) | |
| (if (> n 0) | |
| (cons n (self (- n 1))) | |
| nil))) | |
| #'self) | |
| ;; labels boilerplate obscures the recursive logic""" | |
| _alambda_after = r""";; (alambda (n) | |
| ;; (if (> n 0) | |
| ;; (cons n (self (- n 1))) | |
| ;; nil)) | |
| ;; Expands to: | |
| ;; (LABELS ((SELF (N) | |
| ;; (IF (> N 0) | |
| ;; (CONS N (SELF (- N 1))) | |
| ;; NIL))) | |
| ;; #'SELF)""" | |
| ALAMBDA = TransformationExample( | |
| id="lol-alambda", | |
| before_code=_alambda_before, | |
| problem_pattern="Need anonymous recursion without writing labels boilerplate β want implicit 'self' for recursion", | |
| macro_definition=_alambda_macro, | |
| after_expansion=_alambda_after, | |
| macro_category=MacroCategory.ANAPHORIC, | |
| technique=[MacroTechnique.ANAPHOR], | |
| source=Source.LET_OVER_LAMBDA, | |
| source_chapter="ch6", | |
| complexity=Complexity.BASIC, | |
| has_capture_risk=True, | |
| requires_gensyms=False, | |
| commentary="Anaphoric lambda: captures 'self' to enable recursion in anonymous functions. The function body can call (self ...) to recurse. Used as a building block for more complex anaphoric macros.", | |
| ) | |
| # ============================================================ | |
| # 5. dlambda β Dispatching lambda | |
| # ============================================================ | |
| _dlambda_macro = r"""(defmacro! dlambda (&rest ds) | |
| `(lambda (&rest ,g!args) | |
| (case (car ,g!args) | |
| ,@(mapcar | |
| (lambda (d) | |
| `(,(if (eq t (car d)) | |
| t | |
| (list (car d))) | |
| (apply (lambda ,@(cdr d)) | |
| ,(if (eq t (car d)) | |
| g!args | |
| `(cdr ,g!args))))) | |
| ds))))""" | |
| _dlambda_before = r""";; Manual dispatch by keyword with multiple lambdas is repetitive | |
| (let ((count 0)) | |
| (setf (symbol-function 'counter) | |
| (lambda (&rest args) | |
| (case (car args) | |
| (:inc (apply (lambda (n) (incf count n)) (cdr args))) | |
| (:dec (apply (lambda (n) (decf count n)) (cdr args))) | |
| (:reset (progn (setf count 0) nil)) | |
| (t (error "Unknown: ~a" (car args))))))) | |
| ;; Every method: keyword, lambda wrapper, arg destructuring - repetitive""" | |
| _dlambda_after = r""";; (dlambda | |
| ;; (:inc (n) (incf count n)) | |
| ;; (:dec (n) (decf count n)) | |
| ;; (:reset () (setf count 0)) | |
| ;; (t (&rest args) (format t "Unknown: ~a~%" args))) | |
| ;; | |
| ;; Expands to a single lambda with case dispatch. | |
| ;; Each method has its own destructured argument list. | |
| ;; t as the key provides a default/catch-all.""" | |
| DLAMBDA = TransformationExample( | |
| id="lol-dlambda", | |
| before_code=_dlambda_before, | |
| problem_pattern="Repeated keyword-dispatch with automatic argument destructuring β each keyword maps to a lambda with its own parameter list", | |
| macro_definition=_dlambda_macro, | |
| after_expansion=_dlambda_after, | |
| macro_category=MacroCategory.DISPATCH, | |
| technique=[MacroTechnique.DLAMBDA, MacroTechnique.GENSYM], | |
| source=Source.LET_OVER_LAMBDA, | |
| source_chapter="ch5", | |
| complexity=Complexity.INTERMEDIATE, | |
| has_capture_risk=False, | |
| requires_gensyms=True, | |
| commentary="Dispatching lambda: compact syntax for building closures with keyword-dispatch. Each clause is (keyword arglist body...). Uses defmacro! for hygiene. Combines case, lambda, and apply into a closure-building DSL.", | |
| ) | |
| # ============================================================ | |
| # 6. alet β Anaphoric let with indirection | |
| # ============================================================ | |
| _alet_macro = r"""(defmacro alet (letargs &rest body) | |
| `(let ((this) ,@letargs) | |
| (setq this ,@(last body)) | |
| ,@(butlast body) | |
| (lambda (&rest params) | |
| (apply this params))))""" | |
| _alet_before = r""";; Building a closure with mutable behavior requires manual forwarding | |
| (let ((counter 0) | |
| (impl (lambda (n) (incf counter n)))) | |
| (lambda (&rest args) | |
| (apply impl args))) | |
| ;; To change behavior: build an entirely new closure""" | |
| _alet_after = r""";; (alet ((acc 0)) | |
| ;; (format t "Initialized~%") | |
| ;; (lambda (n) (incf acc n))) | |
| ;; | |
| ;; Returns a forwarding closure where 'this' can be swapped. | |
| ;; The lambda inside can (setq this #'new-fn) to hotpatch.""" | |
| ALET = TransformationExample( | |
| id="lol-alet", | |
| before_code=_alet_before, | |
| problem_pattern="Need mutable closure behavior changeable at runtime, with implicit 'this' pointing to current implementation", | |
| macro_definition=_alet_macro, | |
| after_expansion=_alet_after, | |
| macro_category=MacroCategory.ANAPHORIC, | |
| technique=[MacroTechnique.ANAPHOR], | |
| source=Source.LET_OVER_LAMBDA, | |
| source_chapter="ch6", | |
| complexity=Complexity.INTERMEDIATE, | |
| has_capture_risk=True, | |
| requires_gensyms=False, | |
| commentary="Anaphoric let: introduces 'this' bound to last body form, returns a forwarding closure. Enables runtime behavior swapping (hotpatching) without rebuilding the closure. The indirection through apply enables dynamic dispatch.", | |
| ) | |
| # ============================================================ | |
| # 7. pandoriclet β Pandora's box closure | |
| # ============================================================ | |
| _pandoriclet_macro = r"""(defun pandoriclet-get (letargs) | |
| `(case sym | |
| ,@(mapcar #`((,(car a1)) ,(car a1)) | |
| letargs) | |
| (t (error | |
| "Unknown pandoric get: ~a" | |
| sym)))) | |
| (defun pandoriclet-set (letargs) | |
| `(case sym | |
| ,@(mapcar #`((,(car a1)) | |
| (setq ,(car a1) val)) | |
| letargs) | |
| (t (error | |
| "Unknown pandoric set: ~a" | |
| sym val)))) | |
| (defmacro pandoriclet (letargs &rest body) | |
| (let ((letargs (cons | |
| '(this) | |
| (let-binding-transform | |
| letargs)))) | |
| `(let (,@letargs) | |
| (setq this ,@(last body)) | |
| ,@(butlast body) | |
| (dlambda | |
| (:pandoric-get (sym) | |
| ,(pandoriclet-get letargs)) | |
| (:pandoric-set (sym val) | |
| ,(pandoriclet-set letargs)) | |
| (t (&rest args) | |
| (apply this args))))))""" | |
| _pandoriclet_before = r""";; Closure variables are opaque - can't inspect or modify them | |
| (let ((acc 0) (mul 1)) | |
| (lambda (n) | |
| (incf acc n) | |
| (setf mul (* mul n)))) | |
| ;; To access acc/mul: must add specific getter/setter methods | |
| ;; This doesn't scale - every variable needs its own accessor""" | |
| _pandoriclet_after = r""";; (pandoriclet ((acc 0) (mul 1)) | |
| ;; (lambda (n) (incf acc n) (setf mul (* mul n)))) | |
| ;; | |
| ;; Now: | |
| ;; (closure :pandoric-get 'acc) => value of acc | |
| ;; (closure :pandoric-set 'acc 100) => sets acc to 100 | |
| ;; (closure 5) => normal call | |
| ;; | |
| ;; with-pandoric makes them look like regular variables: | |
| ;; (with-pandoric (acc mul) closure | |
| ;; (setf acc (+ acc 10)))""" | |
| PANDORICLET = TransformationExample( | |
| id="lol-pandoriclet", | |
| before_code=_pandoriclet_before, | |
| problem_pattern="Closure internals are opaque β want a general protocol for reading/writing any closed-over variable", | |
| macro_definition=_pandoriclet_macro, | |
| after_expansion=_pandoriclet_after, | |
| macro_category=MacroCategory.DISPATCH, | |
| technique=[MacroTechnique.DLAMBDA, MacroTechnique.NESTED_BACKQUOTE, MacroTechnique.ANAPHOR], | |
| source=Source.LET_OVER_LAMBDA, | |
| source_chapter="ch6", | |
| complexity=Complexity.ADVANCED, | |
| has_capture_risk=False, | |
| requires_gensyms=False, | |
| commentary="Pandoric macros: combine dlambda dispatch with a protocol for reading/writing closure internals. Named after Pandora's box β opening the closure. The #` (sharp-backquote) read macro provides compact lambda syntax in the helper functions.", | |
| ) | |
| # ============================================================ | |
| # 8. with-pandoric β Transparent pandoric variable access | |
| # ============================================================ | |
| _with_pandoric_macro = r"""(defun get-pandoric (box sym) | |
| (funcall box :pandoric-get sym)) | |
| (defsetf get-pandoric (box sym) (val) | |
| `(progn | |
| (funcall ,box :pandoric-set ,sym ,val) | |
| ,val)) | |
| (defmacro with-pandoric (syms box &rest body) | |
| (let ((g!box (gensym "box"))) | |
| `(let ((,g!box ,box)) | |
| (declare (ignorable ,g!box)) | |
| (symbol-macrolet | |
| (,@(mapcar #`(,a1 (get-pandoric ,g!box ',a1)) | |
| syms)) | |
| ,@body))))""" | |
| _with_pandoric_before = r""";; Accessing pandoric variables requires verbose funcall | |
| (let ((sum (funcall box :pandoric-get 'sum)) | |
| (count (funcall box :pandoric-get 'count))) | |
| (format t "sum=~a count=~a~%" sum count)) | |
| ;; Setting is even worse: | |
| (progn | |
| (funcall box :pandoric-set 'sum new-value) | |
| new-value) | |
| ;; Want: regular variable syntax for pandoric access""" | |
| _with_pandoric_after = r""";; (with-pandoric (sum count) #'counter | |
| ;; (format t "sum=~a count=~a~%" sum count) | |
| ;; (incf sum 10) | |
| ;; (setf count 0)) | |
| ;; | |
| ;; Expands to: | |
| ;; (SYMBOL-MACROLET | |
| ;; ((SUM (GET-PANDORIC #:BOX123 'SUM)) | |
| ;; (COUNT (GET-PANDORIC #:BOX123 'COUNT))) | |
| ;; (FORMAT T "sum=~a count=~a~%" SUM COUNT) | |
| ;; (INCF SUM 10) | |
| ;; (SETF COUNT 0)) | |
| ;; | |
| ;; Reading SUM expands to (GET-PANDORIC ... 'SUM) | |
| ;; Writing SUM expands to setf -> get-pandoric setf expander""" | |
| WITH_PANDORIC = TransformationExample( | |
| id="lol-with-pandoric", | |
| before_code=_with_pandoric_before, | |
| problem_pattern="Pandoric get/set requires verbose keyword funcalls β want transparent variable syntax using symbol-macrolet", | |
| macro_definition=_with_pandoric_macro, | |
| after_expansion=_with_pandoric_after, | |
| macro_category=MacroCategory.DISPATCH, | |
| technique=[MacroTechnique.SYMBOL_MACROLET, MacroTechnique.DEFSETF, MacroTechnique.GENSYM], | |
| source=Source.LET_OVER_LAMBDA, | |
| source_chapter="ch6", | |
| complexity=Complexity.ADVANCED, | |
| has_capture_risk=False, | |
| requires_gensyms=True, | |
| commentary="Uses symbol-macrolet to make pandoric variables look like regular lexical variables. The duality of syntax β same variable syntax for two completely different mechanisms. defsetf enables setf and incf/decf to work transparently.", | |
| ) | |
| # ============================================================ | |
| # 9. nlet-tail β Guaranteed tail-call via tagbody/go | |
| # ============================================================ | |
| _nlet_tail_macro = r"""(defmacro! nlet-tail (n letargs &body body) | |
| (let ((gs (loop for i in letargs | |
| collect (gensym)))) | |
| `(macrolet | |
| ((,n ,gs | |
| `(progn | |
| (psetq | |
| ,@(apply #'nconc | |
| (mapcar | |
| #'list | |
| ',(mapcar #'car letargs) | |
| (list ,@gs)))) | |
| (go ,',g!n)))) | |
| (block ,g!b | |
| (let ,letargs | |
| (tagbody | |
| ,g!n (return-from | |
| ,g!b (progn ,@body))))))))""" | |
| _nlet_tail_before = r""";; Recursive factorial - stack overflow on large N in CL | |
| ;; (CL does not guarantee tail-call optimization) | |
| (defun fact (n) | |
| (labels ((iter (n acc) | |
| (if (zerop n) | |
| acc | |
| (iter (- n 1) (* n acc))))) | |
| (iter n 1))) | |
| ;; (fact 1000000) => stack overflow on most implementations""" | |
| _nlet_tail_after = r""";; (defun fact (n) | |
| ;; (nlet-tail fact ((n n) (acc 1)) | |
| ;; (if (zerop n) | |
| ;; acc | |
| ;; (fact (- n 1) (* acc n))))) | |
| ;; | |
| ;; "Calling" (fact ...) expands via macrolet to: | |
| ;; (progn (psetq n (- n 1) acc (* acc n)) (go #:TAG)) | |
| ;; = a jump, not a function call. Zero stack consumption.""" | |
| NLET_TAIL = TransformationExample( | |
| id="lol-nlet-tail", | |
| before_code=_nlet_tail_before, | |
| problem_pattern="Need guaranteed tail-call elimination for recursive algorithms β CL doesn't mandate TCO", | |
| macro_definition=_nlet_tail_macro, | |
| after_expansion=_nlet_tail_after, | |
| macro_category=MacroCategory.CONTROL_FLOW, | |
| technique=[MacroTechnique.MACROLET, MacroTechnique.TAGBODY, MacroTechnique.GENSYM], | |
| source=Source.LET_OVER_LAMBDA, | |
| source_chapter="ch5", | |
| complexity=Complexity.ADVANCED, | |
| has_capture_risk=False, | |
| requires_gensyms=True, | |
| commentary="Guaranteed tail calls via code-walking. The local macrolet redefines the function name to expand into psetq + go β a jump into tagbody, not a function call. No stack frame allocated. The 'call site' looks like a function call but compiles to a goto.", | |
| ) | |
| # ============================================================ | |
| # 10. sortf β Sorting network macro | |
| # ============================================================ | |
| _sortf_macro = r"""(defun build-batcher-sn (n) | |
| (let* (network | |
| (tee (ceiling (log n 2))) | |
| (p (ash 1 (- tee 1)))) | |
| (loop while (> p 0) do | |
| (let ((q (ash 1 (- tee 1))) | |
| (r 0) | |
| (d p)) | |
| (loop while (> d 0) do | |
| (loop for i from 0 to (- n d 1) do | |
| (if (= (logand i p) r) | |
| (push (list i (+ i d)) | |
| network))) | |
| (setf d (- q p) | |
| q (ash q -1) | |
| r p))) | |
| (setf p (ash p -1))) | |
| (nreverse network))) | |
| (defmacro! sortf (comparator &rest places) | |
| (if places | |
| `(tagbody | |
| ,@(mapcar | |
| #`(let ((,g!a #1=,(nth (car a1) places)) | |
| (,g!b #2=,(nth (cadr a1) places))) | |
| (if (,comparator ,g!b ,g!a) | |
| (setf #1# ,g!b | |
| #2# ,g!a))) | |
| (build-batcher-sn (length places))))))""" | |
| _sortf_before = r""";; Sorting multiple places requires manual compare-and-swap | |
| (when (> b a) (rotatef a b)) | |
| (when (> c a) (rotatef a c)) | |
| (when (> c b) (rotatef b c)) | |
| ;; This is a sorting network but manually written - error-prone | |
| ;; For more than 3 elements, the network is complex""" | |
| _sortf_after = r""";; (sortf string< a b c d e) | |
| ;; Generates a Batcher sorting network of compare-and-swap operations | |
| ;; Each pair is: (let ((#:ga (nth 0 places)) (#:gb (nth 3 places))) | |
| ;; (if (string< #:gb #:ga) (setf #1# #:gb #2# #:ga))) | |
| ;; All within a tagbody for compiler optimization""" | |
| SORTF = TransformationExample( | |
| id="lol-sortf", | |
| before_code=_sortf_before, | |
| problem_pattern="Sorting multiple values in-place requires compare-and-swap networks β want automatic sorting network generation", | |
| macro_definition=_sortf_macro, | |
| after_expansion=_sortf_after, | |
| macro_category=MacroCategory.EFFICIENCY, | |
| technique=[MacroTechnique.GENSYM, MacroTechnique.TAGBODY], | |
| source=Source.LET_OVER_LAMBDA, | |
| source_chapter="ch7", | |
| complexity=Complexity.ADVANCED, | |
| has_capture_risk=False, | |
| requires_gensyms=True, | |
| commentary="Sorting network macro: generates optimal compare-and-swap networks using Batcher's merge-exchange algorithm. The network is generated at macro-expansion time from the number of places.", | |
| ) | |
| # ============================================================ | |
| # 11. Compiler macro: fformat | |
| # ============================================================ | |
| _fformat_macro = r"""(defun fformat (&rest all) | |
| (apply #'format all)) | |
| (define-compiler-macro fformat | |
| (&whole form | |
| stream fmt &rest args) | |
| (if (constantp fmt) | |
| (if stream | |
| `(funcall (formatter ,fmt) | |
| ,stream ,@args) | |
| (let ((g!stream (gensym "stream"))) | |
| `(with-output-to-string (,g!stream) | |
| (funcall (formatter ,fmt) | |
| ,g!stream ,@args)))) | |
| form))""" | |
| _fformat_before = r""";; format with constant control strings interprets directives at runtime | |
| (format nil "Value: ~a, Name: ~a" val name) | |
| ;; Every call: parse the ~a directives, resolve them, dispatch formatting | |
| ;; The format string is known at compile time but still interpreted at runtime""" | |
| _fformat_after = r""";; (fformat t "Value: ~a, Name: ~a" val name) | |
| ;; Compiler macro sees constant fmt, expands to: | |
| ;; (FUNCALL (FORMATTER "Value: ~a, Name: ~a") T VAL NAME) | |
| ;; | |
| ;; formatter macro-expands the control string into direct | |
| ;; write-string, princ, etc. calls - no runtime parsing. | |
| ;; | |
| ;; When fmt is NOT constant: returns original form unchanged. | |
| ;; Graceful degradation - always correct, sometimes faster.""" | |
| FFORMAT = TransformationExample( | |
| id="lol-fformat", | |
| before_code=_fformat_before, | |
| problem_pattern="format with compile-time-constant control strings wastes time interpreting directives at runtime β want compile-time compilation of format strings", | |
| macro_definition=_fformat_macro, | |
| after_expansion=_fformat_after, | |
| macro_category=MacroCategory.COMPILER_MACRO, | |
| technique=[MacroTechnique.COMPILER_MACRO, MacroTechnique.GENSYM], | |
| source=Source.LET_OVER_LAMBDA, | |
| source_chapter="ch7", | |
| complexity=Complexity.INTERMEDIATE, | |
| has_capture_risk=False, | |
| requires_gensyms=True, | |
| commentary="Compiler macro with &whole + constantp guard. When fmt is constant, delegates to formatter which macro-expands directives into direct function calls. &whole captures the original form β returning it unchanged declines optimization.", | |
| ) | |
| # ============================================================ | |
| # 12. defun! β defun with automatic gensyms | |
| # ============================================================ | |
| _defun_bang_macro = r"""(defmacro defun! (name args &body body) | |
| (let ((syms (remove-duplicates | |
| (remove-if-not #'g!-symbol-p | |
| (flatten body))))) | |
| (multiple-value-bind (body declarations docstring) | |
| (parse-body body :documentation t) | |
| `(defun ,name ,args | |
| ,@(when docstring | |
| (list docstring)) | |
| ,@declarations | |
| (let ,(mapcar (lambda (s) | |
| `(,s (gensym ,(subseq (symbol-name s) | |
| 2)))) | |
| syms) | |
| ,@body)))))""" | |
| _defun_bang_before = r""";; Functions that build code also need gensyms for hygiene | |
| (defun build-setter (var) | |
| (let ((g (gensym))) | |
| `(lambda (,g) | |
| (setf ,var ,g)))) | |
| ;; Gensym boilerplate in code-generating functions""" | |
| _defun_bang_after = r""";; (defun! build-setter (var) | |
| ;; `(lambda (,g!param) | |
| ;; (setf ,var ,g!param))) | |
| ;; | |
| ;; g!param is automatically gensym'd when the function runs | |
| ;; Each call produces a fresh uninterned symbol""" | |
| DEFUN_BANG = TransformationExample( | |
| id="lol-defun-bang", | |
| before_code=_defun_bang_before, | |
| problem_pattern="Functions that generate code also need gensyms β want same G! prefix convention extended to defun", | |
| macro_definition=_defun_bang_macro, | |
| after_expansion=_defun_bang_after, | |
| macro_category=MacroCategory.CAPTURE_MANAGEMENT, | |
| technique=[MacroTechnique.GENSYM, MacroTechnique.CODE_WALKING], | |
| source=Source.LET_OVER_LAMBDA, | |
| source_chapter="ch3", | |
| complexity=Complexity.INTERMEDIATE, | |
| has_capture_risk=False, | |
| requires_gensyms=True, | |
| commentary="defun! extends the G! prefix convention to functions that generate code. Scans the body for G!-prefixed symbols and gensyms them. Verified in test suite: (defun! fn! () `(let ((,g!test 123)) ,g!test)).", | |
| ) | |
| # ============================================================ | |
| # 13. plambda β Pandoric lambda (exporting closure vars) | |
| # ============================================================ | |
| _plambda_macro = r"""(defmacro plambda (largs pargs &rest body) | |
| (let ((pargs (mapcar #'list pargs))) | |
| `(let (this self) | |
| (setq | |
| this (lambda ,largs ,@body) | |
| self (dlambda | |
| (:pandoric-get (sym) | |
| ,(pandoriclet-get pargs)) | |
| (:pandoric-set (sym val) | |
| ,(pandoriclet-set pargs)) | |
| (t (&rest args) | |
| (apply this args)))))))""" | |
| _plambda_before = r""";; Exporting closure variables requires building pandoric protocol manually | |
| (let ((a 0)) | |
| (let ((b 1)) | |
| (let (this self) | |
| (setq | |
| this (lambda (n) (incf a n) (setq b (* b n))) | |
| self (dlambda | |
| (:pandoric-get (sym) | |
| (case sym | |
| ((a) a) | |
| ((b) b) | |
| (t (error "Unknown: ~a" sym)))) | |
| (:pandoric-set (sym val) | |
| (case sym | |
| ((a) (setq a val)) | |
| ((b) (setq b val)))) | |
| (t (&rest args) (apply this args))))))) | |
| ;; Manual self construction for every closure is painful""" | |
| _plambda_after = r""";; (let ((a 0)) | |
| ;; (let ((b 1)) | |
| ;; (plambda (n) (a b) | |
| ;; (incf a n) | |
| ;; (setq b (* b n))))) | |
| ;; | |
| ;; Now: | |
| ;; (with-pandoric (a b) closure | |
| ;; (format t "a=~a b=~a~%" a b)) | |
| ;; a and b are accessible through the pandoric protocol""" | |
| PLAMBDA = TransformationExample( | |
| id="lol-plambda", | |
| before_code=_plambda_before, | |
| problem_pattern="Need to export closure variables through pandoric protocol, but manually building the self dlambda for each set of variables is repetitive", | |
| macro_definition=_plambda_macro, | |
| after_expansion=_plambda_after, | |
| macro_category=MacroCategory.DISPATCH, | |
| technique=[MacroTechnique.DLAMBDA, MacroTechnique.NESTED_BACKQUOTE], | |
| source=Source.LET_OVER_LAMBDA, | |
| source_chapter="ch6", | |
| complexity=Complexity.ADVANCED, | |
| has_capture_risk=True, | |
| requires_gensyms=False, | |
| commentary="Pandoric lambda: like pandoriclet but explicitly lists which variables to export. Useful for exposing variables from nested lets. The self closure handles :pandoric-get/:pandoric-set while forwarding normal calls to this.", | |
| ) | |
| # ============================================================ | |
| # 14. if-match β Pattern matching with regex captures | |
| # ============================================================ | |
| _if_match_macro = r"""#+cl-ppcre | |
| (defun dollar-symbol-p (s) | |
| (and (symbolp s) | |
| (> (length (symbol-name s)) 1) | |
| (string= (symbol-name s) | |
| "$" | |
| :start1 0 | |
| :end1 1) | |
| (ignore-errors (parse-integer (subseq (symbol-name s) 1))))) | |
| (defmacro! if-match ((match-regex str) then &optional else) | |
| (let* ((dollars (remove-duplicates | |
| (remove-if-not #'dollar-symbol-p | |
| (flatten then)))) | |
| (top (or (car (sort (mapcar #'dollar-symbol-p dollars) #'>)) | |
| 0))) | |
| `(multiple-value-bind (,g!matches ,g!captures) (,match-regex ,str) | |
| (declare (ignorable ,g!matches ,g!captures)) | |
| (let ((,g!captures-len (length ,g!captures))) | |
| (declare (ignorable ,g!captures-len)) | |
| (symbol-macrolet ,(mapcar #`(,(symb "$" a1) | |
| (if (< ,g!captures-len ,a1) | |
| (error "Too few matchs: ~a unbound." ,(mkstr "$" a1)) | |
| (aref ,g!captures ,(1- a1)))) | |
| (loop for i from 1 to top collect i)) | |
| (if ,g!matches | |
| ,then | |
| ,else))))))""" | |
| _if_match_before = r""";; Regex matching with captures requires manual multiple-value-bind | |
| (multiple-value-bind (matchp captures) | |
| (cl-ppcre:scan-to-strings "_(\\w+)@(\\w+)\\.com_" email) | |
| (if matchp | |
| (let ((user (aref captures 0)) | |
| (domain (aref captures 1))) | |
| (format t "~a at ~a~%" user domain)) | |
| :invalid)) | |
| ;; Verbose: bind result, extract captures by index, branch - repetitive""" | |
| _if_match_after = r""";; (if-match (#~m_(\\w+)@(\\w+)\\.com_ email) | |
| ;; (format t "~a at ~a~%" $1 $2) | |
| ;; :invalid) | |
| ;; | |
| ;; $1, $2 bound to capture groups via symbol-macrolet | |
| ;; Out-of-bounds $N signals a clear error at runtime | |
| ;; Nested if-match works - each has its own capture bindings""" | |
| IF_MATCH = TransformationExample( | |
| id="lol-if-match", | |
| before_code=_if_match_before, | |
| problem_pattern="Regex matching with named captures requires verbose destructuring β want $1, $2, ... anaphors bound to capture groups", | |
| macro_definition=_if_match_macro, | |
| after_expansion=_if_match_after, | |
| macro_category=MacroCategory.CONTROL_FLOW, | |
| technique=[MacroTechnique.SYMBOL_MACROLET, MacroTechnique.GENSYM, MacroTechnique.CODE_WALKING], | |
| source=Source.LET_OVER_LAMBDA, | |
| source_chapter="ch6", | |
| complexity=Complexity.ADVANCED, | |
| has_capture_risk=False, | |
| requires_gensyms=True, | |
| commentary="Combines regex matching with anaphoric capture variables ($1, $2, ...). Scans the then-branch for $N symbols and creates symbol-macrolet bindings. Uses defmacro! for hygiene. Requires cl-ppcre.", | |
| ) | |
| # ============================================================ | |
| # 15. pandoric-eval β Tunneling lexical scope through eval | |
| # ============================================================ | |
| _pandoric_eval_macro = r"""(defvar pandoric-eval-tunnel) | |
| (defmacro pandoric-eval (vars expr) | |
| `(let ((pandoric-eval-tunnel | |
| (plambda () ,vars t))) | |
| (eval `(with-pandoric | |
| ,',vars pandoric-eval-tunnel | |
| ,,expr))))""" | |
| _pandoric_eval_before = r""";; eval loses lexical environment - can't access surrounding bindings | |
| (let ((x 1)) | |
| (eval '(+ 1 x))) ;; Error: X is unbound! | |
| ;; Need: way to tunnel lexical variables into eval""" | |
| _pandoric_eval_after = r""";; (let ((x 1)) | |
| ;; (pandoric-eval (x) | |
| ;; '(+ 1 x))) | |
| ;; => 2 | |
| ;; | |
| ;; (let ((x 1)) | |
| ;; (pandoric-eval (x) | |
| ;; '(incf x)) | |
| ;; x) | |
| ;; => 2 ;; modifications persist! | |
| ;; | |
| ;; Builds a plambda capturing x, then eval-with-pandoric | |
| ;; reconstructs x as symbol-macrolet inside eval""" | |
| PANDORIC_EVAL = TransformationExample( | |
| id="lol-pandoric-eval", | |
| before_code=_pandoric_eval_before, | |
| problem_pattern="eval cannot access lexical environment β want a 'tunnel' that bridges lexical scope into eval's dynamic world", | |
| macro_definition=_pandoric_eval_macro, | |
| after_expansion=_pandoric_eval_after, | |
| macro_category=MacroCategory.SCOPE, | |
| technique=[MacroTechnique.DLAMBDA, MacroTechnique.SYMBOL_MACROLET], | |
| source=Source.LET_OVER_LAMBDA, | |
| source_chapter="ch6", | |
| complexity=Complexity.ADVANCED, | |
| has_capture_risk=True, | |
| requires_gensyms=False, | |
| commentary="Two-way tunnel between lexical scope and eval. Uses plambda to capture the lexical bindings, then with-pandoric inside eval to make them accessible. Modifications flow back because symbol-macrolet changes go through setf -> pandoric-set.", | |
| ) | |
| # ============================================================ | |
| # Aggregate all examples | |
| # ============================================================ | |
| from cl_macros.on_lisp import ON_LISP_EXAMPLES | |
| ALL_EXAMPLES: list[TransformationExample] = [ | |
| G_BANG, | |
| DEFMACRO_BANG, | |
| AIFF, | |
| ALAMBDA, | |
| DLAMBDA, | |
| ALET, | |
| PANDORICLET, | |
| WITH_PANDORIC, | |
| NLET_TAIL, | |
| SORTF, | |
| FFORMAT, | |
| DEFUN_BANG, | |
| PLAMBDA, | |
| IF_MATCH, | |
| PANDORIC_EVAL, | |
| *ON_LISP_EXAMPLES, | |
| ] | |