Browse code
feature: add patmatch system
Ed Langley authored on 27/10/2019 04:39:55
Showing 3 changed files
Showing 3 changed files
0 | 9 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,45 @@ |
1 |
+(in-package :patmatch) |
|
2 |
+ |
|
3 |
+(define-condition no-pattern (error) |
|
4 |
+ ((%pattern-obj :initarg :pattern-obj :reader pattern-obj) |
|
5 |
+ (%pattern-form :initarg :pattern-form :reader pattern-form) |
|
6 |
+ (%pattern-args :initarg :pattern-args :reader pattern-args))) |
|
7 |
+ |
|
8 |
+(eval-when (:compile-toplevel :load-toplevel :execute) |
|
9 |
+ (defgeneric handle-pattern (pattern form &rest args) |
|
10 |
+ (:method-combination append) |
|
11 |
+ |
|
12 |
+ (:method append ((pattern (eql 'cons)) form &rest args) |
|
13 |
+ (let ((val-sym (gensym "VAL"))) |
|
14 |
+ (destructuring-bind (car cdr) args |
|
15 |
+ `((,val-sym ,form) |
|
16 |
+ (,car (car ,val-sym)) |
|
17 |
+ (,cdr (cdr ,val-sym)))))) |
|
18 |
+ |
|
19 |
+ (:method append ((pattern (eql 'vector)) form &rest args) |
|
20 |
+ (let ((val-sym (gensym "VAL"))) |
|
21 |
+ `((,val-sym ,form) |
|
22 |
+ ,@ (loop for arg in args |
|
23 |
+ for idx from 0 |
|
24 |
+ collect `(,arg (aref ,val-sym ,idx)))))) |
|
25 |
+ |
|
26 |
+ (:method append ((pattern (eql 'hash-table)) form &rest args) |
|
27 |
+ (let* ((val-sym (gensym "VAL")) |
|
28 |
+ (binding-forms (loop for (key sym) in args |
|
29 |
+ append `((,sym (gethash ',key ,val-sym)))))) |
|
30 |
+ `((,val-sym ,form) |
|
31 |
+ ,@binding-forms))) |
|
32 |
+ |
|
33 |
+ (:method append ((pattern symbol) form &rest args) |
|
34 |
+ (when (closer-mop:subclassp pattern 'standard-object) |
|
35 |
+ (apply #'handle-pattern |
|
36 |
+ (closer-mop:class-prototype |
|
37 |
+ (closer-mop:ensure-finalized (find-class pattern))) |
|
38 |
+ form |
|
39 |
+ args))))) |
|
40 |
+ |
|
41 |
+(defmacro let-pat* ((&rest clauses) &body body) |
|
42 |
+ `(let* (,@ (loop for ((discriminator . args) val-form) in clauses |
|
43 |
+ append (apply 'handle-pattern discriminator val-form args))) |
|
44 |
+ ,@body)) |
|
45 |
+ |
0 | 46 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,118 @@ |
1 |
+(defpackage :patmatch/test |
|
2 |
+ (:use :cl) |
|
3 |
+ (:import-from :parachute |
|
4 |
+ #:true #:false #:fail #:is #:isnt #:is-values #:isnt-values |
|
5 |
+ #:of-type #:finish #:define-test) |
|
6 |
+ (:import-from :patmatch :let-pat*) |
|
7 |
+ (:export )) |
|
8 |
+(in-package :patmatch/test) |
|
9 |
+ |
|
10 |
+(define-test add-stuff |
|
11 |
+ (is = |
|
12 |
+ (progn (sleep 2) |
|
13 |
+ 2) |
|
14 |
+ 2)) |
|
15 |
+ |
|
16 |
+(define-test let-pat*-handles-cons |
|
17 |
+ (is = |
|
18 |
+ 2 |
|
19 |
+ (let-pat* (((cons a b) '(2 . 3))) |
|
20 |
+ (declare (ignore b)) |
|
21 |
+ a))) |
|
22 |
+ |
|
23 |
+#| |
|
24 |
+(deftest let-pat*-handles-cons () |
|
25 |
+"" |
|
26 |
+(should be eql |
|
27 |
+2 |
|
28 |
+(let-pat* (((cons a b) '(2 . 3))) |
|
29 |
+(declare (ignore b)) |
|
30 |
+a)) |
|
31 |
+(should be eql |
|
32 |
+3 |
|
33 |
+(let-pat* (((cons a b) '(2 . 3))) |
|
34 |
+(declare (ignore a)) |
|
35 |
+b))) |
|
36 |
+ |
|
37 |
+(deftest let-pat*-handles-vector () |
|
38 |
+ "" |
|
39 |
+ (should be eql |
|
40 |
+ 2 |
|
41 |
+ (let-pat* (((vector a b) #(2 3))) |
|
42 |
+ (declare (ignore b)) |
|
43 |
+ a)) |
|
44 |
+ (should be eql |
|
45 |
+ 3 |
|
46 |
+ (let-pat* (((vector a b) #(2 3))) |
|
47 |
+ (declare (ignore a)) |
|
48 |
+ b))) |
|
49 |
+ |
|
50 |
+(deftest let-pat*-handles-hash-table () |
|
51 |
+ "" |
|
52 |
+ (should be eql |
|
53 |
+ 2 |
|
54 |
+ (let-pat* (((hash-table (:a a) (:b b)) (alexandria:plist-hash-table '(:a 2 :b 3)))) |
|
55 |
+ (declare (ignore b)) |
|
56 |
+ a)) |
|
57 |
+ (should be eql |
|
58 |
+ 3 |
|
59 |
+ (let-pat* (((hash-table (:a a) (:b b)) (alexandria:plist-hash-table '(:a 2 :b 3)))) |
|
60 |
+ (declare (ignore a)) |
|
61 |
+ b))) |
|
62 |
+ |
|
63 |
+(eval-when (:compile-toplevel :load-toplevel :execute) |
|
64 |
+ (defclass test-base () |
|
65 |
+ ((a :initform 1))) |
|
66 |
+ (defclass test-sub1 (test-base) |
|
67 |
+ ()) |
|
68 |
+ (defclass test-sub2 (test-base) |
|
69 |
+ ((b :initform 2))) |
|
70 |
+ (closer-mop:ensure-class 'test-base) |
|
71 |
+ (closer-mop:ensure-class 'test-sub1) |
|
72 |
+ (closer-mop:ensure-class 'test-sub2) |
|
73 |
+ ) |
|
74 |
+ |
|
75 |
+(defmethod patmatch:handle-pattern append ((pattern test-base) form &rest args) |
|
76 |
+ (alexandria:when-let ((arg (getf args :a))) |
|
77 |
+ (let ((val-sym (gensym "test-base-"))) |
|
78 |
+ `((,val-sym ,form) |
|
79 |
+ ,@(serapeum:unsplice |
|
80 |
+ `(,arg (slot-value ,val-sym 'a))))))) |
|
81 |
+ |
|
82 |
+(defmethod patmatch:handle-pattern append ((pattern test-sub2) form &rest args) |
|
83 |
+ (alexandria:when-let ((arg (getf args :b))) |
|
84 |
+ (let ((val-sym (gensym "test-base-"))) |
|
85 |
+ `((,val-sym ,form) |
|
86 |
+ ,@(serapeum:unsplice |
|
87 |
+ `(,arg (slot-value ,val-sym 'b))))))) |
|
88 |
+ |
|
89 |
+ |
|
90 |
+(deftest let-pat*-handles-object-destructuring () |
|
91 |
+ "" |
|
92 |
+ (should be eql |
|
93 |
+ 1 |
|
94 |
+ (let-pat* (((test-base :a a) (make-instance 'test-base))) |
|
95 |
+ a))) |
|
96 |
+ |
|
97 |
+(deftest let-pat*-handles-inheritance () |
|
98 |
+"" |
|
99 |
+(should be eql |
|
100 |
+1 |
|
101 |
+(let-pat* (((test-base :a a) (make-instance 'test-sub1))) |
|
102 |
+a)) |
|
103 |
+ |
|
104 |
+(should be eql |
|
105 |
+1 |
|
106 |
+(let-pat* (((test-sub1 :a a) (make-instance 'test-sub1))) |
|
107 |
+a)) |
|
108 |
+ |
|
109 |
+(should be eql |
|
110 |
+1 |
|
111 |
+(let-pat* (((test-sub2 :a a) (make-instance 'test-sub2))) |
|
112 |
+a)) |
|
113 |
+ |
|
114 |
+(should be equal |
|
115 |
+'(1 2) |
|
116 |
+(let-pat* (((test-sub2 :a a :b b) (make-instance 'test-sub2))) |
|
117 |
+(list a b)))) |
|
118 |
+|# |