-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
with.lisp
177 lines (150 loc) · 5.89 KB
/
with.lisp
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
;; cl-facts
;; Copyright 2011, 2023 Thomas de Grivel <thodg@kmx.io>
;;
;; Permission is hereby granted to use this software granted
;; the above copyright notice and this permission paragraph
;; are included in all copies and substantial portions of this
;; software.
;;
;; THIS SOFTWARE IS PROVIDED "AS-IS" WITHOUT ANY GUARANTEE OF
;; PURPOSE AND PERFORMANCE. IN NO EVENT WHATSOEVER SHALL THE
;; AUTHOR BE CONSIDERED LIABLE FOR THE USE AND PERFORMANCE OF
;; THIS SOFTWARE.
(in-package :facts)
;; Tools
(defun nor (&rest forms)
(declare (dynamic-extent forms))
(every #'null forms))
;; WITH
(defun with/3 (form-s form-p form-o body)
`(when (db-get ,form-s ,form-p ,form-o)
,@body
(values)))
(defun with/0 (var-s var-p var-o body)
`(db-each (,var-s ,var-p ,var-o) (db-index-spo)
,@body))
(defun ignorable-bindings (&rest vars)
(let ((ignorable (mapcan (lambda (x)
(when (char= #\- (char (symbol-name x) 0))
(list x)))
vars)))
(when ignorable
`((declare (ignorable ,@ignorable))))))
(defun with/1-2 (s p o var-s var-p var-o tree body)
(let* ((value-s (unless var-s (gensym "VALUE-S-")))
(value-p (unless var-p (gensym "VALUE-P-")))
(value-o (unless var-o (gensym "VALUE-O-")))
(fact-s (or var-s (gensym "FACT-S-")))
(fact-p (or var-p (gensym "FACT-P-")))
(fact-o (or var-o (gensym "FACT-O-")))
(block-name (gensym "BLOCK-")))
`(block ,block-name
(let (,@(when value-s `((,value-s ,s)))
,@(when value-p `((,value-p ,p)))
,@(when value-o `((,value-o ,o))))
(db-each (,fact-s ,fact-p ,fact-o)
(,tree :start (make-fact/v ,value-s ,value-p ,value-o))
,@(ignorable-bindings fact-s fact-p fact-o)
(unless (and ,@(unless var-s `((equal ,value-s ,fact-s)))
,@(unless var-p `((equal ,value-p ,fact-p)))
,@(unless var-o `((equal ,value-o ,fact-o))))
(return-from ,block-name (values)))
,@body)))))
(eval-when (:compile-toplevel :load-toplevel)
(defun with/dispatch (s p o binding-vars body)
(let ((var-s (when (binding-p s) (cdr (assoc s binding-vars))))
(var-p (when (binding-p p) (cdr (assoc p binding-vars))))
(var-o (when (binding-p o) (cdr (assoc o binding-vars)))))
(cond ((and var-s var-p var-o) (with/0 var-s var-p var-o body))
((nor var-s var-p var-o) (with/3 s p o body))
(t (with/1-2 s p o var-s var-p var-o
(cond ((and (null var-s) var-o) 'db-index-spo)
((null var-p) 'db-index-pos)
(t 'db-index-osp))
body)))))
(defun with/iter (spec binding-vars body)
(ecase (length spec)
((3) (destructuring-bind (s p o) spec
(with/dispatch s p o binding-vars body)))
((4) (destructuring-bind (not s p o) spec
(if (eq :not not)
`(without ((,s ,p ,o)) ,@body)
(with/dispatch s p o binding-vars body)))))))
(defmacro with/rec ((spec &rest more-specs) &body body)
(let* ((bindings (collect-bindings spec))
(binding-vars (gensym-bindings bindings))
(body-subst (sublis binding-vars body)))
(with/iter spec binding-vars
(if more-specs
`((with/rec ,(sublis binding-vars more-specs)
,@body-subst))
body-subst))))
(defmacro with/expanded (binding-specs &body body)
`(block nil
(with/rec ,binding-specs
,@body)))
(defmacro with (binding-specs &body body)
`(with/expanded ,(expand-specs binding-specs)
,@body))
(defmacro bound-p (binding-specs)
`(with ,binding-specs
(return (values t ,@(collect-bindings binding-specs)))))
(defmacro collect (binding-specs &body body)
(let ((g!collect (gensym "COLLECT-")))
`(let ((,g!collect ()))
(with ,binding-specs
(push (progn ,@body) ,g!collect))
,g!collect)))
(defmacro collect-facts (fact-specs)
(let ((g!facts (gensym "FACTS-"))
(specs (expand-specs fact-specs)))
`(let (,g!facts)
(with/expanded ,specs
,@(mapcar (lambda (fact)
`(push (make-fact/v ,@fact) ,g!facts))
specs))
(remove-duplicates ,g!facts :test #'fact-equal))))
(defmacro first-bound (binding-specs)
;; FIXME: detect multiple bindings
(let* ((bindings (collect-bindings binding-specs)))
(assert (= 1 (length bindings)) ()
"Invalid BINDING-SPEC: ~S
You should provide exactly one unbound variable."
binding-specs)
`(with ,binding-specs
(return ,(first bindings)))))
(defmacro let-with (let-spec &body body)
`(let* (,@(mapcar
(lambda (b)
(if (third b)
`(,(first b) (or (first-bound ,(second b)) ,(third b)))
`(,(first b) (first-bound ,(second b)))))
let-spec))
,@body))
(defmacro push-tail (tail &rest values)
`(setf ,@(mapcan (lambda (v)
`((cdr ,tail) (cons ,v nil)
,tail (cdr ,tail)))
values)))
;; ADD
(defmacro add (&rest specs)
(let ((bindings (collect-bindings specs)))
`(with-transaction
(let ,(mapcar (lambda (b)
`(,b (anon ,(subseq (symbol-name b) 1))))
bindings)
,@(mapcar (lambda (fact)
`(db-insert ,@fact))
(expand-specs specs))))))
(defun add* (&rest specs)
(with-transaction
(dolist (fact (expand-specs specs))
(apply #'db-insert fact))))
;; RM
(defmacro rm (specs)
`(with-transaction
(mapc #'db-delete (collect-facts ,specs))))
;; Without
(defmacro without (binding-specs &body body)
`(unless (with ,binding-specs (return t))
,@body))