git.fiddlerwoaroof.com
Browse code

feature: add patmatch system

Ed Langley authored on 27/10/2019 04:39:55
Showing 3 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,8 @@
1
+(defpackage :fwoar-lisputils.patmatch.package
2
+  (:use :cl)
3
+  (:export))
4
+(in-package :fwoar-lisputils.patmatch.package)
5
+
6
+(defpackage :patmatch
7
+  (:use :cl)
8
+  (:export :let-pat* :handle-pattern :no-pattern))
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
+|#