File size: 33,392 Bytes
a383597
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
"""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,
]