This repository has been archived by the owner on May 14, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 10
/
cl-slice-dev.lisp
204 lines (179 loc) · 9.05 KB
/
cl-slice-dev.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
;;; -*- Mode:Lisp; Syntax:ANSI-Common-Lisp; Coding:utf-8 -*-
(defpackage #:cl-slice-dev
(:use #:cl #:alexandria #:anaphora #:let-plus)
(:export
;; resolving slices into canonical representations
#:canonical-singleton
#:canonical-range
#:canonical-sequence
#:axis-dimension
#:slice-reserved-symbol?
#:canonical-representation
#:canonical-representations
;; traversing slices
#:singleton-representation?
#:all-singleton-representations?
#:representation-dimensions
#:row-major-setup
#:traverse-representations))
(in-package #:cl-slice-dev)
;;; resolving slices into canonical representations
(defun canonical-singleton (index)
"Canonical representation of a singleton index (a nonnegative integer, which is a valid array index)."
(assert (typep index 'array-index))
index)
(defstruct canonical-range
"Canonical representation of a contiguous set of array indices from START (inclusive) to END (exclusive)."
(start nil :type array-index)
(end nil :type array-index))
(defun canonical-range (start end)
"Canonical representation of a contiguous set of array indices from START (inclusive) to END (exclusive)."
(assert (and (typep start 'array-index)
(typep end 'array-index)
(< start end)))
(make-canonical-range :start start :end end))
(defstruct canonical-sequence
"Canonical representation of a sequence of array indexes."
(vector nil :type (simple-array array-index (*))))
(defun canonical-sequence (sequence)
"Canonical representation of array indexes from sequence. May share structure. Vectors of the upgraded type of (SIMPLE-ARRAY ARRAY-INDEX (*)) are preferred for efficiency, otherwise they are coerced."
(let ((vector (coerce sequence '(simple-array array-index (*)))))
(assert (and (plusp (length vector))
(every (lambda (index)
(typep index 'array-index))
vector)))
(make-canonical-sequence :vector vector)))
(defgeneric axis-dimension (axis)
(:documentation "Return the dimension of axis. Needs to be defined for non-integer axes."))
(defun slice-reserved-symbol? (symbol)
"Test if SYMBOL has special semantics for SLICE."
(or (eq symbol t) (eq symbol nil)))
(defgeneric canonical-representation (axis slice)
(:documentation "Canonical representation of SLICE, given information in AXIS. The default methods just use dimensions as AXIS.
Each slice needs to be resolved into a canonical representation, which is either a singleton, a range, or a sequence of subscripts. They should only be constructed with the corresponding CANONICAL-SINGLETION, CANONICAL-RANGE and CANONICAL-SEQUENCE functions.
CANONICAL-REPRESENTATION needs to ensure that the represented subscripts are valid for the axis.
Unless a specialized method is found, the dimension of the axis is queried with AXIS-DIMENSION and resolution is attempted using the latter. It recommended that methods that resolve symbols test them with SLICE-RESERVED-SYMBOL? and use CALL-NEXT-METHOD.")
(:method (axis slice)
;; fallback: try to get dimension and proceed based that
(canonical-representation (axis-dimension axis) slice))
;; canonical representations resolve to themselves QUESTION unchecked?
(:method (axis (canonical-range canonical-range))
canonical-range)
(:method (axis (canonical-sequence canonical-sequence))
canonical-sequence)
;; DSL for slices
(:method ((axis integer) (slice null))
(canonical-singleton axis))
(:method ((axis integer) (slice integer))
(canonical-singleton
(if (minusp slice)
(aprog1 (+ axis slice)
(assert (<= 0 it)))
(aprog1 slice
(assert (< slice axis))))))
(:method (axis (slice cons))
(let+ (((start . end) slice)
(start (canonical-representation axis start))
(end (canonical-representation axis end)))
(canonical-range start end)))
(:method (axis (slice vector))
(let+ (subscripts
((&flet collect (value)
(push value subscripts))))
(loop for s across slice
do (aetypecase (canonical-representation axis s)
(array-index
(collect it))
(canonical-range
(loop for index
from (canonical-range-start it)
below (canonical-range-end it)
do (collect index)))
(canonical-sequence
(map 'nil #'collect (canonical-sequence-vector it)))))
(canonical-sequence (nreverse subscripts))))
(:method ((axis integer) (slice (eql t)))
(canonical-range 0 axis))
(:method (axis (slice bit-vector))
(canonical-sequence (loop for bit across slice
for index from 0
when (plusp bit) collect index))))
(defun canonical-representations (axes slices)
"Return the canonical representations of SLICES given the corresponding AXES, checking for matching length."
(assert (length= axes slices))
(mapcar #'canonical-representation axes slices))
;;; walking subscripts
(defun singleton-representation? (representation)
"Test if a canonical REPRESENTATION is a singleton."
(integerp representation))
(defun all-singleton-representations? (representations)
"Test if all canonical representations are singletons."
(every #'singleton-representation? representations))
(defun representation-dimension (representation)
"Return the dimension of a canonical-representation, or NIL for singleton slices (they are dropped)."
(aetypecase representation
(array-index nil)
(canonical-range (- (canonical-range-end it) (canonical-range-start it)))
(canonical-sequence (length (canonical-sequence-vector it)))))
(defun representation-dimensions (representations)
"Return a list for the dimensions of canonical representations, dropping singletons."
(loop for r in representations
for d = (representation-dimension r)
when d collect d))
(defun representation-initial-value (representation)
"Initial value for iteration."
(aetypecase representation
(array-index it)
(canonical-range (canonical-range-start it))
(canonical-sequence (aref (canonical-sequence-vector it) 0))))
(defun representation-iterator (representation carry cons)
"Return a closure that sets the car of CONS to the next value each time it is called, resetting and calling CARRY when it reaches the end of its range."
(flet ((save (value)
(setf (car cons) value))
(carry ()
(funcall carry)))
(aetypecase representation
(array-index carry)
(canonical-range (let+ (((&structure-r/o canonical-range- start end) it)
(slice start))
(lambda ()
(let ((carry? (= (incf slice) end)))
(when carry?
(setf slice start))
(save slice)
(when carry?
(carry))))))
(canonical-sequence (let* ((vector (canonical-sequence-vector it))
(dimension (length vector))
(position 0))
(lambda ()
(when (= (incf position) dimension)
(setf position 0)
(carry))
(save (aref vector position))))))))
(defun row-major-setup (representations terminator)
"Return SUBSCRIPTS (a list) and ITERATOR (a closure, no arguments) that increments the contents of SUBSCRIPTS. TERMINATOR is called when all subscripts have been visited."
(let ((iterator terminator)
(subscripts (mapcar #'representation-initial-value representations)))
(loop for r in representations
for cons on subscripts
do (setf iterator
(representation-iterator r iterator cons)))
(values subscripts iterator)))
;;; NOTE commented out as untested
;; (defun column-major-setup (representations terminator)
;; (let+ (((&values subscripts iterator)
;; (row-major-setup (reverse representations) terminator)))
;; (values (nreverse subscripts) iterator)))
(defmacro traverse-representations ((subscripts representations
&key index
(setup 'row-major-setup))
&body body)
"A macro for traversing representations. Loops over all possible subscripts in REPRESENTAITONS, making them available in SUBSCRIPS during the execution of BODY. The iterator is constructed using the function SETUP (see for example ROW-MAJOR-SETUP). When INDEX is given, a variable with that name is provided, containing an index that counts iterations."
(with-unique-names (block-name next)
`(block ,block-name
(let+ (((&values ,subscripts ,next)
(,setup ,representations (lambda () (return-from ,block-name)))))
(loop ,@(when index `(for ,index from 0))
do ,@body
(funcall ,next))))))