git.fiddlerwoaroof.com
Browse code

Add the proof of concept implementation

fiddlerwoaroof authored on 26/10/2016 03:36:45
Showing 7 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,3 @@
1
+# Docs:
2
+
3
+See example.lisp for a simple usage
0 4
new file mode 100644
... ...
@@ -0,0 +1,18 @@
1
+;;;; collection-classes.asd
2
+
3
+(asdf:defsystem #:collection-class
4
+  :description "Defines a macro for declaring a class that is a collection of another class"
5
+  :author "Edward Langley <el@elangley.org>"
6
+  :license "MIT"
7
+  :depends-on (#:alexandria
8
+               #:for
9
+               #:serapeum
10
+               )
11
+  :serial t
12
+  :components ((:file "package")
13
+               (:file "collections")
14
+               (:file "collections-for")
15
+               #+sbcl
16
+               (:file "collections-sbcl-iterators")
17
+               ))
18
+
0 19
new file mode 100644
... ...
@@ -0,0 +1,22 @@
1
+(in-package :collection-class)
2
+
3
+(defclass collection-iterator (for:iterator)
4
+  ())
5
+
6
+(defmethod initialize-instance :after ((iterator collection-iterator) &key object)
7
+  (setf (for:object iterator)
8
+        (items object)))
9
+
10
+(defmethod for:has-more ((iterator collection-iterator))
11
+  (not (null (for:object iterator))))
12
+
13
+(defmethod for:next ((iterator collection-iterator))
14
+  (let ((collection-items (for:object iterator)))
15
+    (prog1 (car collection-items)
16
+      (setf (for:object iterator)
17
+            (cdr collection-items)))))
18
+
19
+(defmethod for:make-iterator ((collection collection) &key)
20
+  (make-instance 'collection-iterator :object collection))
21
+
22
+
0 23
new file mode 100644
... ...
@@ -0,0 +1,28 @@
1
+(in-package :collection-class)
2
+
3
+(defmethod sb-sequence:length ((sequence collection))
4
+  (length (items sequence)))
5
+
6
+(defmethod sb-sequence:elt ((sequence collection) index)
7
+  (elt (items sequence) index))
8
+
9
+(defmethod (setf sb-sequence:elt) (new-value (sequence collection) index)
10
+  (setf (elt (items sequence) index) new-value))
11
+
12
+(defmethod sb-sequence:adjust-sequence ((sequence collection) length &key initial-element initial-contents)
13
+  (let ((result (duplicate-collection sequence)))
14
+    (when (or initial-element initial-contents)
15
+      (setf (items result)
16
+            (sb-sequence:adjust-sequence (items result) length
17
+                                         :initial-element initial-element
18
+                                         :initial-contents initial-contents)))
19
+    result))
20
+
21
+(defmethod sb-sequence:make-sequence-like ((sequence collection) length &key initial-element initial-contents)
22
+  (let ((result (duplicate-collection sequence)))
23
+    (setf (items result)
24
+          (sb-sequence:make-sequence-like (items result) length
25
+                                          :initial-element initial-element
26
+                                          :initial-contents initial-contents))
27
+    result))
28
+
0 29
new file mode 100644
... ...
@@ -0,0 +1,61 @@
1
+(in-package :collection-class)
2
+
3
+(defclass collection (standard-object #+sbcl sequence)
4
+  ())
5
+
6
+(define-condition value-error ()
7
+  ((value :initarg :value :accessor value)))
8
+
9
+(defgeneric push-item (item collection)
10
+  (:documentation "Push item onto the beginning of the collection"))
11
+
12
+(defgeneric items (collection)
13
+  (:documentation "Get the items from a collection"))
14
+
15
+(defgeneric duplicate-collection (collection))
16
+
17
+(defgeneric copy-instance (object &rest initargs &key &allow-other-keys)
18
+  (:documentation "Makes and returns a shallow copy of OBJECT.
19
+
20
+                   An uninitialized object of the same class as OBJECT is allocated by
21
+                   calling ALLOCATE-INSTANCE.  For all slots returned by
22
+                   CLASS-SLOTS, the returned object has the
23
+                   same slot values and slot-unbound status as OBJECT.
24
+
25
+                   REINITIALIZE-INSTANCE is called to update the copy with INITARGS.")
26
+  (:method ((object standard-object) &rest initargs &key &allow-other-keys)
27
+   (let* ((class (class-of object))
28
+          (copy (allocate-instance class)))
29
+     (dolist (slot-name (mapcar #'closer-mop:slot-definition-name (closer-mop:class-slots class)))
30
+       (when (slot-boundp object slot-name)
31
+         (setf (slot-value copy slot-name)
32
+               (slot-value object slot-name))))
33
+     (apply #'reinitialize-instance copy initargs))))
34
+
35
+
36
+; TODO: actually use item-class...
37
+; TODO: finish initform handling.  Have to figure out how to make initform work with push-item
38
+(defmacro define-collection ((name item-class &key (initform '(list))) (&rest supers) &body ((&rest slots) &rest other-stuff))
39
+  (with-gensyms (item-slot-sym)
40
+    `(progn (defclass ,name (,@supers collection)
41
+              ((,item-slot-sym :initform ,initform :accessor items)
42
+               ,@slots)
43
+              ,@other-stuff)
44
+            (defmethod duplicate-collection ((collection ,name))
45
+              (let ((result (copy-instance collection)))
46
+                (setf (items result)
47
+                      (copy-seq (items result)))
48
+                result))
49
+            (defmethod push-item ((item ,item-class) (collection ,name))
50
+              (push item (items collection))))))
51
+
52
+(defmethod random-item ((collection collection) &optional (random-state *random-state*))
53
+  (let* ((length (length (items collection)))
54
+         (selected-index (random length random-state)))
55
+    (elt (items collection)
56
+         selected-index)))
57
+
58
+(defmethod nth-item ((collection collection) (index integer))
59
+  (if (>= index 0)
60
+    (elt (items collection) index)
61
+    (error 'value-error :value index)))
0 62
new file mode 100644
... ...
@@ -0,0 +1,31 @@
1
+(defpackage #:collection-class.example
2
+  (:use #:cl #:collection-class #:alexandria #:serapeum))
3
+
4
+(in-package :collection-class.example)
5
+
6
+; Define the class to be contained in the collection
7
+(defclass foo ()
8
+  ((%slot1 :initarg :slot1 :accessor slot1)
9
+   (%slot2 :initarg :slot2 :reader slot2)))
10
+
11
+; Define the collection: it's basically a class form, with the name being a list
12
+; of (collection-name collected-class)
13
+(define-collection (collected-foos foo) ()
14
+  ((%collection-slot :initarg :collection-slot :reader collection-slot)))
15
+
16
+
17
+; Define a collection
18
+(defparameter *collection* (make-instance 'collected-foos :collection-slot "howdy?"))
19
+
20
+(push-item (make-instance 'foo :slot1 1 :slot2 2) *collection*)
21
+(push-item (make-instance 'foo :slot1 2 :slot2 2) *collection*)
22
+(push-item (make-instance 'foo :slot1 2 :slot2 3) *collection*)
23
+
24
+; On sbcl, we implement the iterator protocol, so we can
25
+; use the builtin sequence functions on the collection.
26
+#+sbcl
27
+(progn
28
+  (collection-slot *collection*)   #| --> "howdy?" |#
29
+  (map 'list #'slot1 *collection*) #| --> (1 2 2)  |#
30
+  (map 'list #'slot2 *collection*) #| --> (2 2 3)  |# 
31
+  )
0 32
new file mode 100644
... ...
@@ -0,0 +1,6 @@
1
+;;;; package.lisp
2
+
3
+(defpackage #:collection-class
4
+  (:use #:cl #:alexandria #:serapeum)
5
+  (:export collection value-error push-item define-collection random-item nth-item items))
6
+