-
Notifications
You must be signed in to change notification settings - Fork 12
/
access.lisp
817 lines (729 loc) · 28.6 KB
/
access.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
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
(cl:defpackage #:access
(:use #:cl #:iterate)
(:import-from #:alexandria #:ensure-list #:if-let #:when-let)
(:export
#:access-warning
;; utils to make this work
#:has-reader?
#:has-writer?
#:has-slot?
#:get-slot-value
#:ensure-slot-name
#:class-of-object
#:class-slot-by-name
#:class-slots
#:class-direct-slots
#:class-direct-slot-names
#:class-direct-slot-readers
#:class-direct-slot-writers
#:class-slot-names
#:class-slot-definitions
#:class-slot-readers
#:class-slot-writers
#:equalper
#:plist-val
#:rem-plist-val
#:rem-plist-val!
#:set-plist-val
#:set-plist-val!
#:call-if-applicable
#:call-applicable-fns
;; main stuff
#:access
#:accesses
#:do-access
#:set-access
#:set-accesses
#:do-set-access
#:access-copy
#:mutate-access
#:with-access
#:with-all-slot-accessors
#:with-access-values
#:with-all-slot-access-values
;; dot syntax stuff
#:with-dot
#:enable-dot-syntax
#:disable-dot-syntax
#:split-dot-sym
;; arg-list-manip
#:arg-list-key-value
#:set-arg-list-key-value
#:set-arg-list-key-value!
#:rem-arg-list-key-value
#:rem-arg-list-key-value!
#:ensure-arg-list-key-value
#:ensure-arg-list-key-value!
))
(in-package :access)
(define-condition access-condition (simple-condition)
((format-control :accessor format-control :initarg :format-control :initform nil)
(format-args :accessor format-args :initarg :format-args :initform nil)
(original-error :accessor original-error :initarg :original-error :initform nil))
(:report (lambda (c s)
(apply #'format
s
(format-control c)
(format-args c)))))
(define-condition access-warning (warning access-condition) ())
(defun access-warn (message &rest args)
(warn (make-condition 'access-warning
:format-control message
:format-args args)))
(defun equalper (x y)
"compares symbols by equalp symbol-name"
(flet ((cast (it)
(typecase it
(symbol (string it))
(t it))))
(or (eql x y)
(equalp (cast x) (cast y)))))
(defvar *default-test* #'access:equalper)
(defvar *default-key* #'identity)
(defun default-test () (or *default-test* #'access:equalper))
(defun default-key () (or *default-key* #'identity))
(defgeneric plist-val (id list &key test key)
(:documentation "get a value out of a plist based on its key")
(:method (id list &key (test (default-test)) (key (default-key)))
(iter (for (k v) on list by #'cddr)
(for found = (funcall (or test (default-test))
(funcall (or key (default-key)) k)
id))
(when found
(return-from plist-val (values v found))))))
(defgeneric rem-plist-val (id list &key test key)
(:documentation
"removes key & its value from plist returning
(values plist (list-of-values-removed))")
(:method (id list &key (test (default-test)) (key (default-key)))
(iter
(for (k v) on list by #'cddr)
(cond ((funcall (or test (default-test))
(funcall (or key (default-key)) k)
id)
(collect v into removed))
(t (collect k into plist)
(collect v into plist)))
(finally (return (values plist removed))))))
(defmacro rem-plist-val! (id place &key (test '(default-test)) (key '(default-key)))
`(setf ,place
(rem-plist-val ,id ,place :test ,test :key ,key)))
(defgeneric set-plist-val (new id list &key test key)
(:documentation "If a key exists in the plist, set its value, otherwise add
this key to the dictionary")
(:method (new id list &key (test (default-test)) (key (default-key)))
(iter
(with collected)
(for (k v) on list by #'cddr)
(collect k into res)
(if (funcall (or test (default-test))
(funcall (or key (default-key)) k)
id)
(progn (setf collected t)
(collect new into res))
(collect v into res))
(finally
(unless collected
(setf res (list* id new res)))
(return res)))))
(defmacro set-plist-val! (new id place
&key (test '(default-test)) (key '(default-key)))
`(setf ,place
(set-plist-val ,new ,id ,place :test ,test :key ,key)))
(defun %slot-readers (slots)
(iter (for slot in (ensure-list slots))
(for reader-name =
(or
;; ESD's might not have this slot apparently
(and
(typep slot 'closer-mop:direct-slot-definition)
(first (closer-mop::slot-definition-readers slot)))
;; NB: We should probably check for a reader fn here before assuming
;; the slot name is a reader-fn, but I couldnt find a cross platform way
;; of doing this
(closer-mop:slot-definition-name slot)))
(collecting reader-name into names)
;; some valid slot names are not valid function names (see type)
(collecting (ignore-errors
(symbol-function reader-name)) into readers)
(finally (return (values readers names)))))
(defun %slot-writers (slots)
(iter (for slot in (ensure-list slots))
(for sn = (closer-mop::slot-definition-name slot))
;; effective slots dont have readers or writers
;; but direct slots do, no idea why, I asked and its in the spec
(for wn = (or (and (typep slot 'closer-mop:direct-slot-definition)
(first (closer-mop::slot-definition-writers slot)))
`(setf ,sn)))
(collecting wn into writer-names)
(collecting sn into slot-names)
;; some valid slot names are not valid function names (see type)
(collecting (ignore-errors (fdefinition wn)) into writers)
(finally (return (values writers writer-names slot-names)))))
(defun class-of-object ( o )
"returns the class of the object/symbol (or itself if it is a class),
if passed a list returns a list of these results"
(typecase o
(keyword nil)
(symbol (find-class o))
(standard-class o)
((or condition standard-object structure-object)
(class-of o))))
(defun appended (fn lst)
"Mapcan caused all sorts of trouble with its NCONCing"
(iter (for o in lst)
(appending (funcall fn o))))
(defgeneric class-slots (o)
(:documentation "returns the slots for the class/obj or list of class/obj passed in")
(:method (o)
(typecase o
(list (appended #'access:class-slots o))
(t
(when-let (c (class-of-object o))
(closer-mop:ensure-finalized c)
(closer-mop:class-slots c))))))
(defun class-slot-definitions (o)
(class-slots o))
(defun class-direct-slot-readers ( o )
"ensures o is a class (or list thereof) and returns all the direct slot reader functions)"
(typecase o
(list (appended #'access:class-direct-slot-readers o))
(t
(when-let (c (class-of-object o))
(%slot-readers (closer-mop:class-direct-slots c))))))
(defun class-slot-readers ( o )
(typecase o
(list (appended #'access:class-slot-readers o))
(t
(when-let (c (class-of-object o))
(%slot-readers (closer-mop:class-slots c))))))
(defun class-direct-slot-writers (o)
(typecase o
(list (appended #'access:class-direct-slot-writers o))
(t
(when-let (c (class-of-object o))
(%slot-writers (closer-mop:class-direct-slots c))))))
(defun class-slot-writers (o)
(typecase o
(list (appended #'access:class-slot-writers o))
(t
(when-let (c (class-of-object o))
(%slot-writers (closer-mop:class-slots c))))))
(defun class-direct-slots (o)
(typecase o
(list (appended #'access:class-direct-slots o))
(t
(when-let (c (class-of-object o))
(closer-mop:ensure-finalized c)
(closer-mop:class-direct-slots c)))))
(defun class-direct-slot-names (o)
(mapcar
#'closer-mop:slot-definition-name
(access:class-direct-slots o)))
(defun class-slot-names (o)
(mapcar
#'closer-mop:slot-definition-name
(access:class-slots o)))
(defun class-slot-by-name (o k &key (test (default-test)) )
(iter (for s in (access:class-slots o))
(for name = (ensure-slot-name s))
(when (funcall (or test (default-test))
k name)
(return (values s name)))))
(defun ensure-slot-name (sn)
(typecase sn
(list (mapcar #'ensure-slot-name sn))
((or symbol string) sn)
(closer-mop:slot-definition
(closer-mop:slot-definition-name sn))))
(defun has-reader? (o reader-name
&aux (match (ensure-slot-name reader-name)))
"For o, does a reader function exist for it"
(when (and o reader-name (typep o 'standard-object))
(multiple-value-bind (readers names) (class-slot-readers o)
(iter (for reader in readers)
(for name in names)
(when (typecase reader-name
(function (eql reader reader-name))
((or symbol keyword string closer-mop:slot-definition)
(equalper name match))
((or list number) nil) ;; compound-keys , array indexes
(t (access-warn "Not sure how to ~S maps to a function" reader-name)))
(return (values reader name)))))))
(defun has-writer? (o writer-name
&aux (match (ensure-slot-name writer-name)))
"For o, does a writer function exist for it?"
(when (and o writer-name (typep o 'standard-object))
(multiple-value-bind (writers wns sns) (class-slot-writers o)
(or
(iter (for writer in writers)
(for wn in wns)
(for sn in sns)
(when (typecase writer-name
(function (eql writer writer-name))
(string
(or (equalper sn match) ;; handle "slot-name" matches
;; handle "(setf slot-name)" matches
(equalper (princ-to-string wn) match)))
((or symbol keyword closer-mop:slot-definition)
(or (equalper sn match) ;; handle slot-name matches
(equalper wn match) ;; handle (setf slot-name) matches
))
(list ;; exact list match
(equal wn writer-name))
(t (access-warn "Not sure how to ~S maps to a function" writer-name)))
(return (values writer wn sn))))
;; setf-form ;; try again with just the slotname
(when (listp writer-name)
(has-writer? o (second writer-name)))))))
(defun get-slot-value (o sn)
"like slot-value but without boundedness errors and works with slot definitions"
(setf sn (ensure-slot-name sn))
(and (slot-boundp o sn) (slot-value o sn)))
(defun has-slot? (o slot-name &key (lax? t))
"Does o have a slot named slot-name
if lax? we will ignore packages to find the slot we will always return a
slot-name from the specified package if it exists, otherwise we return the
slot-name we found if its in a different package"
;; The accessing of structure objects slots is undefined behaviour by
;; the language specification, but most Lisp implementations define
;; slot-value on structure-objects in a meaningful and expected way.
;; This was tested to work for SBCL, CCL, ECL, ABCL, CLISP, Clasp, LW, ACL.
(unless (and o (typep o '(or standard-object structure-object condition)))
(return-from has-slot? nil))
(let ((match (ensure-slot-name slot-name))
(slot-names (class-slot-names o))
lax)
(or
(iter (for sn in slot-names)
(cond
;; exact match - always return this first if we find it
((eql sn match)
(return sn))
;; save the lax matches
((and lax? (equalper sn match))
(push sn lax))))
(cond
((< 1 (length lax))
(access-warn "Multiple slots inexactly matched for ~a on ~a" slot-name o)
(first (reverse lax)))
(t (first lax))))))
(defun setf-if-applicable (new o fn)
"If we find a setf function named (setf fn) that can operate on o then call
that with value new "
(handler-bind ((undefined-function
(lambda (c) (declare (ignore c))
(return-from setf-if-applicable nil))))
(setf fn
(typecase fn
((or keyword string symbol closer-mop:slot-definition)
(has-writer? o fn))
(function fn)
((or number list) nil);; compound-keys and indexes
(t (access-warn "Not sure how to call a ~A" fn) ))))
(etypecase fn
(null nil)
(standard-generic-function
(when (compute-applicable-methods fn (list new o))
(values (funcall fn new o) t)))
(function (values (funcall fn new o) t))))
(defun call-if-applicable (o fn &key (warn-if-not-a-fn? t))
"See if there is a method named fn specialized on o, or a function named fn
and call it if so
TODO: dont call macro functions/special forms, they are not applicable
"
(handler-bind ((undefined-function
(lambda (c) (declare (ignore c))
(return-from call-if-applicable nil))))
(setf fn
(typecase fn
((or keyword string closer-mop:slot-definition) (has-reader? o fn))
(symbol (symbol-function fn))
(function fn)
((or number list) nil);; compound-keys and indexes
(t (when warn-if-not-a-fn?
(access-warn "Not sure how to call a ~A" fn))))))
(handler-case
(etypecase fn
(null nil)
(standard-generic-function
(when (compute-applicable-methods fn (list o))
(values (funcall fn o) t)))
(function (values (funcall fn o) t)))
(unbound-slot (c) (declare (ignore c)))))
(defun call-applicable-fns (o &rest fns)
"For an object and a list of fn/fn names, call-if-applicable repeatedly"
(iter (for fn in fns)
(setf o (call-if-applicable o fn)))
o)
(defgeneric do-access (o k &key test key type skip-call?)
(:method ((o list) k &key (test (default-test)) (key (default-key))
type skip-call?)
(declare (ignore skip-call?))
(if (or (eql type :alist)
(and (null type) (consp (first o))))
;;alist
(let ((assoc (assoc k o :test (or test (default-test))
:key (or key (default-key)))))
(values (cdr assoc) (and assoc t)))
;;plist
(plist-val k o :test test :key key)))
(:method ((o array) k &key type test key skip-call?)
(declare (ignore type test key skip-call?))
(when (< k (length o))
(values (aref o k) t)))
(:method ((o hash-table) k &key type test key skip-call?)
(declare (ignore type test key skip-call?))
(multiple-value-bind (res found) (gethash k o)
(if found
(values res found)
(when-let (skey (ignore-errors (string k)))
(gethash skey o)))))
(:method (o k &key (test (default-test)) (key (default-key))
type skip-call?)
;; not specializing on standard-object here
;; allows this same code path to work with conditions (in sbcl)
(let ((actual-slot-name (has-slot? o k)))
(cond
;; same package as requested, must be no accessor so handle slots
((eql actual-slot-name k)
(when (slot-boundp o k)
(values (slot-value o k) t)))
;; lets recheck for an accessor in the correct package
(actual-slot-name
(access o actual-slot-name
:test (or test (default-test))
:key (or key (default-key))
:type type :skip-call? skip-call?))))))
(defun access (o k &key type (test (default-test)) (key (default-key))
skip-call?)
"Access plists, alists, arrays, hashtables and clos objects
all through the same interface
skip-call, skips trying to call "
(multiple-value-bind (res called)
(unless skip-call?
;; lets suppress the warning if it is just being called through access
(call-if-applicable o k :warn-if-not-a-fn? nil))
(if called
(values res t)
(do-access o k :test test :key key :type type))))
(defun %initialize-null-container (k type test)
(flet ((create-array ()
;; make an array big enough to hold our key
(make-array (apply #'+ 1 (ensure-list k))
:adjustable t
:initial-element nil)))
(declare (inline create-array))
(case type
((nil :list :alist :plist)
nil)
((:hash-table)
(make-hash-table :test (%to-hash-test test)))
((:array)
(create-array))
(otherwise
(cond ((subtypep type 'hash-table)
(make-hash-table :test (%to-hash-test test)))
((subtypep type 'array)
(create-array))
((subtypep type 'standard-object)
(make-instance type)))))))
(defgeneric do-set-access (new o k &key type test key)
;; always return the new value as the first value
;; every primary method should return the modified object
(:method :around (new o k &key type test key)
(declare (ignore o k type test key))
(values new (call-next-method)))
(:method (new (o list) k &key type test key )
(if (or (eql type :alist)
(and (null type) (consp (first o))))
;;alist
(if-let ((assoc (assoc k o :test test :key key)))
(progn
(setf (cdr assoc) new)
o)
(list* (cons k new) o))
;;plist
(set-plist-val new k o :test test :key key)
))
(:method (new (o array) k &key type test key)
(declare (ignore type test key))
(setf (apply #'aref o (ensure-list k)) new)
o)
(:method (new (o hash-table) k &key type test key)
(declare (ignore type test key))
(let ((skey (string k)))
(multiple-value-bind (res found) (gethash k o)
(declare (ignore res))
(multiple-value-bind (sres sfound)
(when skey (gethash skey o))
(declare (ignore sres))
(cond
(found (setf (gethash k o) new))
(sfound (setf (gethash skey o) new))
(t (setf (gethash k o) new))))))
o)
(:method (new (o standard-object) k &key type test key)
(declare (ignore type test key))
(let ((actual-slot-name (has-slot? o k)))
(cond
;; same package so there must be no accessor
((eql actual-slot-name k)
(setf (slot-value o k) new))
;; different package, but we have a slot, so lets look for its accessor
(actual-slot-name
(set-access new o actual-slot-name))
))
o)
(:method (new (o structure-object) k &key type test key)
(declare (ignore type test key))
(let ((actual-slot-name (has-slot? o k)))
(when actual-slot-name
(setf (slot-value o actual-slot-name) new)))
o))
(defun set-access (new o k &key type (test #'equalper) (key #'identity))
"set places in plists, alists, hashtables and clos objects all
through the same interface"
;; make these easy to have the same defaults everywhere
(unless test (setf test #'equalper))
(unless key (setf key #'identity))
(unless o (setf o (%initialize-null-container k type test)))
(multiple-value-bind (res called) (setf-if-applicable new o k)
(if called
(values res o)
(do-set-access new o k :type type :test test :key key))))
(defun %to-hash-test (test)
(typecase test
(null 'eql)
(symbol test)
;; dequote double quoted tests
;; (its pretty easy to double quote with accesses)
(list (eql 'quote (first test))
(%to-hash-test (second test)))
(function
(cond
((eql test #'eql) 'eql)
((eql test #'equal) 'equal)
;; equalper does string based symbol comparing
;; we also handle this for hashtables separately
;; so I think this should be fine
((member test (list #'equalp #'equalper)) 'equalp)
(t (error "Hashtable tests should be a symbol! See make-hashtable"))))))
(define-setf-expander access (place k
&key type test key
&aux (new-val (gensym "NEW-VAL"))
(place-store (gensym "PLACE"))
&environment env)
"This should allow setting places through access"
(declare (ignore env))
(values () ;; not using temp vars
() ;; not using temp vals
`(,new-val)
`(progn
(multiple-value-bind (,new-val ,place-store)
(set-access ,new-val ,place ,k :test ,test :type ,type :key ,key)
(setf ,place ,place-store)
,new-val))
`(access ,place ,k :test ,test :type ,type :key ,key )))
(defun accesses (o &rest keys)
"keep accessing keys on resulting objects
eg: (accesses o k1 k2) => (access (access o k1) k2)"
(iter (for k in keys)
(destructuring-bind (k &key type test key)
(ensure-list k)
(setf o (access o k :test test :type type :key key ))))
o)
(defun set-accesses (new o &rest keys)
"keep accessing till you get to the end of keys , then store the result of
setting that field back up the call tree
returns the new value and the object that was stored there
(so for a plist / alist you have a ref to the val and the full list)
"
(labels ((rec-set (o key more)
(destructuring-bind (k &key type test key)
(ensure-list key)
;(unless test (setf test #'equalper))
;(unless key (setf key #'identity))
(cond
(more
(unless o
(setf o (%initialize-null-container k type test)))
(multiple-value-bind (new new-place-val)
(rec-set (access o k :test test :type type :key key)
(first more) (rest more))
(setf (access o k :test test :type type :key key) new-place-val)
(values new o)))
(t (set-access new o k :test test :type type :key key))))))
(rec-set o (first keys) (rest keys))))
(define-setf-expander accesses (place &rest keys
&aux (new-val (gensym "NEW-VAL"))
(place-store (gensym "PLACE"))
&environment env)
(declare (ignore env))
(values () ;; not using temp vars
() ;; not using temp vals
`(,new-val)
`(multiple-value-bind (,new-val ,place-store)
(set-accesses ,new-val ,place ,@keys)
(setf ,place ,place-store)
,new-val)
`(accesses ,place ,@keys)))
(defun mutate-access (o k fn)
"Mutate the value stored in key k on object o, by passing it through fn"
(when-let (value (access o k))
(setf (access o k) (funcall fn value))))
(defun access-copy (from to keys)
"Copy the values on 'from' to 'to' for all of the keys listed "
(iter (for k in keys)
(for (k1 k2) = (if (listp k) k (list k k)))
(setf (access to k2) (access from k1))))
(defmacro with-access ((&rest keys) val-form &body body)
"Similar to with-accessors except using the access functions"
(let* ((gval (gensym "val"))
(forms
(iter (for k in keys)
(for (k-to k-from) = (if (listp k) k (list k k)))
(collect `(,k-to (access ,gval ',k-from))))))
`(let ((,gval ,val-form))
(declare (ignorable ,gval))
(symbol-macrolet (,@forms)
,@body
))))
(defun %create-accessor-symbol-list (class)
"Gets the slots off a class an builds binding like (local::symbol orig::symbol)
where local is the current *package* and orig is the original package of the symbol
used in with-all-slot-accessors"
(let ((class (etypecase class
(symbol (find-class class))
(standard-class class))))
(closer-mop:ensure-finalized class)
(iter (for slot-name in (class-slot-names class))
;; collect bindings of local-symbol to class-slot-name
(collect (list (intern (symbol-name slot-name))
slot-name)))))
(defun %remove-quote-&-or (class-name)
"remove any quote / ors so that list type-specifications"
(typecase class-name
(list
(case (first class-name)
(quote (%remove-quote-&-or (second class-name)))
(or (%remove-quote-&-or (rest class-name)))
(t class-name)))
(symbol class-name)))
(defmacro with-access-values ((&rest bindings) obj &body body)
"A macro which binds local variables from accessed values on object
according to bindings
bindings: (local-symbol-and-access-key
or (local-symbol access-key)
...)
obj: the thing we are accessing data from
"
(flet ((key-for (it)
(etypecase it
(symbol `(quote ,it))
((or string keyword list) it))))
(let* ((o (gensym "OBJ"))
(expanded-bindings
(iter (for b in (ensure-list bindings))
(when (first-iteration-p)
(collect `(,o ,obj)))
(typecase b
(null)
(list (collect `(,(first b) (access ,o ,(key-for (second b))))))
(symbol (collect `(,b (access ,o ,(key-for b)))))))))
`(let* ,expanded-bindings
,@body))))
(defun %with-all-slot-helper (data class-name body
&key (with-name 'with-access)
(add-ignorables? nil)
&aux (sdata data))
"A macro which binds (like with-access) all slot names of a class to a local
symbolmacro let storing and retrieving using access
class-name: a symbol or a list of class-names (symbols)
to make this easier to call we ignore quote and or
eg: 't1=>t1, (or 't1 't2 ...)=> (t1 t2 ...)
"
(setf with-name (%remove-quote-&-or with-name))
(labels ((typed-form (class-name)
(let* ((symlist (%create-accessor-symbol-list class-name)))
`(,with-name ,symlist ,sdata
,@(when add-ignorables?
`((declare (ignorable ,@(mapcar #'first symlist)))))
,@body))))
(setf class-name (%remove-quote-&-or class-name))
(typecase class-name
(list
(setf sdata (gensym "DATA"))
`(let ((,sdata ,data))
(etypecase ,sdata
,@(iter (for cn in class-name)
(setf cn (%remove-quote-&-or cn))
(collect (list cn (typed-form cn)))))))
(symbol (typed-form class-name)))))
(defmacro with-all-slot-accessors ((data class-name) &body body)
"A macro which binds (like with-access) all slot names of a class to a local
symbolmacro let storing and retrieving using access
class-name: a symbol or a list of class-names (symbols)
to make this easier to call we ignore quote and or
eg: 't1=>t1, (or 't1 't2 ...)=> (t1 t2 ...)
"
(%with-all-slot-helper data class-name body))
(defmacro with-all-slot-access-values ((obj class) &body body)
"A macro which binds local variables for each slot value in class
as by access"
(%with-all-slot-helper obj class body
:with-name 'access:with-access-values
:add-ignorables? t))
;;;; DOT Syntax stuff
(defun split-dot-sym (sym)
(iter (for piece in (cl-ppcre:split "\\." (string sym)))
(collect (intern piece (or (symbol-package sym) *package*)))))
(defun translate-dot-sym (sym)
(let* ((pieces (split-dot-sym sym))
(fns (iter (for sym in (rest pieces))
(collect `(quote ,sym)))))
(if (eql 1 (length pieces))
sym
`(accesses ,(first pieces) ,@fns))))
(defun dot-translate-walker (form)
(typecase form
(cons (cons (dot-translate-walker (car form))
(dot-translate-walker (cdr form))))
(symbol (translate-dot-sym form))
(atom form)))
(defun name-has-dot? (n)
(cl-ppcre:all-matches "\\." (string n)))
(defun replace-dot-calls (forms)
(dot-translate-walker forms))
(defmacro with-dot (() &body body)
`(progn ,@(replace-dot-calls body)))
(defun dot-reader (-stream- char arg)
"Reads a form and replaces dot calls"
(declare (ignore arg char))
(first (replace-dot-calls (list (read -stream-)))))
(defvar *dot-previous-readtables* nil
"A stack which holds the previous readtables that have been pushed
here by ENABLE-DOT-SYNTAX.")
(defun %enable-dot-syntax ()
"Internal function used to enable reader syntax and store current
readtable on stack."
(push *readtable*
*dot-previous-readtables*)
(setq *readtable* (copy-readtable))
(set-dispatch-macro-character #\# #\D #'dot-reader)
(values))
(defun %disable-dot-syntax ()
"Internal function used to restore previous readtable."
(if *dot-previous-readtables*
(setq *readtable* (pop *dot-previous-readtables*))
(setq *readtable* (copy-readtable nil)))
(values))
(defmacro enable-dot-syntax ()
"Enable reader syntax."
`(eval-when (:compile-toplevel :load-toplevel :execute)
(%enable-dot-syntax)))
(defmacro disable-dot-syntax ()
"Restore readtable which was active before last call to If there was no such call, the standard
readtable is used."
`(eval-when (:compile-toplevel :load-toplevel :execute)
(%disable-dot-syntax)))