gnu: python-ptyprocess: Use INVOKE.
[jackhill/guix/guix.git] / guix / records.scm
CommitLineData
c0cd1b3e 1;;; GNU Guix --- Functional package management for GNU
7874bbbb 2;;; Copyright © 2012, 2013, 2014, 2015, 2016, 2017, 2018 Ludovic Courtès <ludo@gnu.org>
c2dcff41 3;;; Copyright © 2018 Mark H Weaver <mhw@netris.org>
c0cd1b3e
LC
4;;;
5;;; This file is part of GNU Guix.
6;;;
7;;; GNU Guix is free software; you can redistribute it and/or modify it
8;;; under the terms of the GNU General Public License as published by
9;;; the Free Software Foundation; either version 3 of the License, or (at
10;;; your option) any later version.
11;;;
12;;; GNU Guix is distributed in the hope that it will be useful, but
13;;; WITHOUT ANY WARRANTY; without even the implied warranty of
14;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15;;; GNU General Public License for more details.
16;;;
17;;; You should have received a copy of the GNU General Public License
18;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
19
20(define-module (guix records)
21 #:use-module (srfi srfi-1)
22 #:use-module (srfi srfi-9)
23 #:use-module (srfi srfi-26)
24 #:use-module (ice-9 match)
fdc1bf65
LC
25 #:use-module (ice-9 regex)
26 #:use-module (ice-9 rdelim)
c0cd1b3e
LC
27 #:export (define-record-type*
28 alist->record
fdc1bf65 29 object->fields
6692d845 30 recutils->alist
31 match-record))
c0cd1b3e
LC
32
33;;; Commentary:
34;;;
35;;; Utilities for dealing with Scheme records.
36;;;
37;;; Code:
38
b1353e7a
LC
39(define-syntax record-error
40 (syntax-rules ()
41 "Report a syntactic error in use of CONSTRUCTOR."
42 ((_ constructor form fmt args ...)
43 (syntax-violation constructor
44 (format #f fmt args ...)
45 form))))
46
babc2c80
LC
47(define (report-invalid-field-specifier name bindings)
48 "Report the first invalid binding among BINDINGS."
49 (let loop ((bindings bindings))
50 (syntax-case bindings ()
51 (((field value) rest ...) ;good
52 (loop #'(rest ...)))
53 ((weird _ ...) ;weird!
54 (syntax-violation name "invalid field specifier" #'weird)))))
55
c2dcff41
LC
56(define (report-duplicate-field-specifier name ctor)
57 "Report the first duplicate identifier among the bindings in CTOR."
58 (syntax-case ctor ()
59 ((_ bindings ...)
60 (let loop ((bindings #'(bindings ...))
61 (seen '()))
62 (syntax-case bindings ()
63 (((field value) rest ...)
64 (not (memq (syntax->datum #'field) seen))
65 (loop #'(rest ...) (cons (syntax->datum #'field) seen)))
66 ((duplicate rest ...)
67 (syntax-violation name "duplicate field initializer"
68 #'duplicate))
69 (()
70 #t))))))
71
9768848a
LC
72(eval-when (expand load eval)
73 ;; The procedures below are needed both at run time and at expansion time.
74
75 (define (current-abi-identifier type)
76 "Return an identifier unhygienically derived from TYPE for use as its
7874bbbb 77\"current ABI\" variable."
9768848a
LC
78 (let ((type-name (syntax->datum type)))
79 (datum->syntax
80 type
81 (string->symbol
82 (string-append "% " (symbol->string type-name)
83 " abi-cookie")))))
7874bbbb 84
9768848a
LC
85 (define (abi-check type cookie)
86 "Return syntax that checks that the current \"application binary
7874bbbb 87interface\" (ABI) for TYPE is equal to COOKIE."
9768848a
LC
88 (with-syntax ((current-abi (current-abi-identifier type)))
89 #`(unless (eq? current-abi #,cookie)
de5cbd4a
LC
90 ;; The source file where this exception is thrown must be
91 ;; recompiled.
92 (throw 'record-abi-mismatch-error 'abi-check
93 "~a: record ABI mismatch; recompilation needed"
8e1395be 94 (list #,type) '())))))
7874bbbb 95
39fc041a
LC
96(define-syntax make-syntactic-constructor
97 (syntax-rules ()
98 "Make the syntactic constructor NAME for TYPE, that calls CTOR, and
99expects all of EXPECTED fields to be initialized. DEFAULTS is the list of
100FIELD/DEFAULT-VALUE tuples, THUNKED is the list of identifiers of thunked
7874bbbb
LC
101fields, and DELAYED is the list of identifiers of delayed fields.
102
103ABI-COOKIE is the cookie (an integer) against which to check the run-time ABI
104of TYPE matches the expansion-time ABI."
39fc041a 105 ((_ type name ctor (expected ...)
7874bbbb 106 #:abi-cookie abi-cookie
39fc041a
LC
107 #:thunked thunked
108 #:delayed delayed
8a16d064 109 #:innate innate
39fc041a
LC
110 #:defaults defaults)
111 (define-syntax name
112 (lambda (s)
113 (define (record-inheritance orig-record field+value)
114 ;; Produce code that returns a record identical to ORIG-RECORD,
115 ;; except that values for the FIELD+VALUE alist prevail.
116 (define (field-inherited-value f)
117 (and=> (find (lambda (x)
118 (eq? f (car (syntax->datum x))))
119 field+value)
120 car))
121
122 ;; Make sure there are no unknown field names.
123 (let* ((fields (map (compose car syntax->datum) field+value))
124 (unexpected (lset-difference eq? fields '(expected ...))))
125 (when (pair? unexpected)
126 (record-error 'name s "extraneous field initializers ~a"
127 unexpected)))
128
cea25b08 129 #`(make-struct/no-tail type
39fc041a
LC
130 #,@(map (lambda (field index)
131 (or (field-inherited-value field)
8a16d064
LC
132 (if (innate-field? field)
133 (wrap-field-value
134 field (field-default-value field))
135 #`(struct-ref #,orig-record
136 #,index))))
39fc041a
LC
137 '(expected ...)
138 (iota (length '(expected ...))))))
139
140 (define (thunked-field? f)
141 (memq (syntax->datum f) 'thunked))
142
143 (define (delayed-field? f)
144 (memq (syntax->datum f) 'delayed))
145
8a16d064
LC
146 (define (innate-field? f)
147 (memq (syntax->datum f) 'innate))
148
39fc041a
LC
149 (define (wrap-field-value f value)
150 (cond ((thunked-field? f)
151 #`(lambda () #,value))
152 ((delayed-field? f)
153 #`(delay #,value))
154 (else value)))
155
b9c86473
LC
156 (define default-values
157 ;; List of symbol/value tuples.
158 (map (match-lambda
159 ((f v)
160 (list (syntax->datum f) v)))
161 #'defaults))
162
163 (define (field-default-value f)
164 (car (assoc-ref default-values (syntax->datum f))))
165
39fc041a
LC
166 (define (field-bindings field+value)
167 ;; Return field to value bindings, for use in 'let*' below.
168 (map (lambda (field+value)
169 (syntax-case field+value ()
170 ((field value)
171 #`(field
172 #,(wrap-field-value #'field #'value)))))
173 field+value))
174
175 (syntax-case s (inherit expected ...)
176 ((_ (inherit orig-record) (field value) (... ...))
177 #`(let* #,(field-bindings #'((field value) (... ...)))
7874bbbb 178 #,(abi-check #'type abi-cookie)
39fc041a
LC
179 #,(record-inheritance #'orig-record
180 #'((field value) (... ...)))))
181 ((_ (field value) (... ...))
b9c86473 182 (let ((fields (map syntax->datum #'(field (... ...)))))
39fc041a 183 (define (field-value f)
94df39cc
LC
184 (or (find (lambda (x)
185 (eq? f (syntax->datum x)))
186 #'(field (... ...)))
b9c86473 187 (wrap-field-value f (field-default-value f))))
39fc041a 188
c2dcff41
LC
189 ;; Pass S to make sure source location info is preserved.
190 (report-duplicate-field-specifier 'name s)
191
b9c86473 192 (let ((fields (append fields (map car default-values))))
39fc041a
LC
193 (cond ((lset= eq? fields '(expected ...))
194 #`(let* #,(field-bindings
195 #'((field value) (... ...)))
7874bbbb 196 #,(abi-check #'type abi-cookie)
39fc041a
LC
197 (ctor #,@(map field-value '(expected ...)))))
198 ((pair? (lset-difference eq? fields
199 '(expected ...)))
200 (record-error 'name s
201 "extraneous field initializers ~a"
202 (lset-difference eq? fields
203 '(expected ...))))
204 (else
205 (record-error 'name s
206 "missing field initializers ~a"
207 (lset-difference eq?
208 '(expected ...)
babc2c80
LC
209 fields)))))))
210 ((_ bindings (... ...))
211 ;; One of BINDINGS doesn't match the (field value) pattern.
212 ;; Report precisely which one is faulty, instead of letting the
213 ;; "source expression failed to match any pattern" error.
214 (report-invalid-field-specifier 'name
215 #'(bindings (... ...))))))))))
cf4efb39 216
faef3b6a
LC
217(define-syntax-rule (define-field-property-predicate predicate property)
218 "Define PREDICATE as a procedure that takes a syntax object and, when passed
219a field specification, returns the field name if it has the given PROPERTY."
220 (define (predicate s)
221 (syntax-case s (property)
222 ((field (property values (... ...)) _ (... ...))
223 #'field)
224 ((field _ properties (... ...))
225 (predicate #'(field properties (... ...))))
226 (_ #f))))
227
c0cd1b3e
LC
228(define-syntax define-record-type*
229 (lambda (s)
230 "Define the given record type such that an additional \"syntactic
231constructor\" is defined, which allows instances to be constructed with named
e2540884
LC
232field initializers, à la SRFI-35, as well as default values. An example use
233may look like this:
234
235 (define-record-type* <thing> thing make-thing
236 thing?
237 (name thing-name (default \"chbouib\"))
238 (port thing-port
8a16d064
LC
239 (default (current-output-port)) (thunked))
240 (loc thing-location (innate) (default (current-source-location))))
e2540884
LC
241
242This example defines a macro 'thing' that can be used to instantiate records
243of this type:
244
245 (thing
246 (name \"foo\")
247 (port (current-error-port)))
248
249The value of 'name' or 'port' could as well be omitted, in which case the
250default value specified in the 'define-record-type*' form is used:
251
252 (thing)
253
254The 'port' field is \"thunked\", meaning that calls like '(thing-port x)' will
255actually compute the field's value in the current dynamic extent, which is
256useful when referring to fluids in a field's value.
257
310b32a2
LC
258A field can also be marked as \"delayed\" instead of \"thunked\", in which
259case its value is effectively wrapped in a (delay …) form.
260
e2540884
LC
261It is possible to copy an object 'x' created with 'thing' like this:
262
263 (thing (inherit x) (name \"bar\"))
264
265This expression returns a new object equal to 'x' except for its 'name'
8a16d064
LC
266field and its 'loc' field---the latter is marked as \"innate\", so it is not
267inherited."
e2540884 268
c0cd1b3e
LC
269 (define (field-default-value s)
270 (syntax-case s (default)
271 ((field (default val) _ ...)
272 (list #'field #'val))
792798f4
LC
273 ((field _ properties ...)
274 (field-default-value #'(field properties ...)))
c0cd1b3e
LC
275 (_ #f)))
276
faef3b6a
LC
277 (define-field-property-predicate delayed-field? delayed)
278 (define-field-property-predicate thunked-field? thunked)
8a16d064 279 (define-field-property-predicate innate-field? innate)
c0cd1b3e 280
310b32a2
LC
281 (define (wrapped-field? s)
282 (or (thunked-field? s) (delayed-field? s)))
283
284 (define (wrapped-field-accessor-name field)
c0cd1b3e 285 ;; Return the name (an unhygienic syntax object) of the "real"
310b32a2 286 ;; getter for field, which is assumed to be a wrapped field.
c0cd1b3e 287 (syntax-case field ()
792798f4 288 ((field get properties ...)
c0cd1b3e
LC
289 (let* ((getter (syntax->datum #'get))
290 (real-getter (symbol-append '% getter '-real)))
291 (datum->syntax #'get real-getter)))))
292
293 (define (field-spec->srfi-9 field)
294 ;; Convert a field spec of our style to a SRFI-9 field spec of the
295 ;; form (field get).
296 (syntax-case field ()
792798f4 297 ((name get properties ...)
c0cd1b3e 298 #`(name
310b32a2
LC
299 #,(if (wrapped-field? field)
300 (wrapped-field-accessor-name field)
c0cd1b3e
LC
301 #'get)))))
302
303 (define (thunked-field-accessor-definition field)
304 ;; Return the real accessor for FIELD, which is assumed to be a
305 ;; thunked field.
306 (syntax-case field ()
307 ((name get _ ...)
310b32a2 308 (with-syntax ((real-get (wrapped-field-accessor-name field)))
c0cd1b3e
LC
309 #'(define-inlinable (get x)
310 ;; The real value of that field is a thunk, so call it.
311 ((real-get x)))))))
312
310b32a2
LC
313 (define (delayed-field-accessor-definition field)
314 ;; Return the real accessor for FIELD, which is assumed to be a
315 ;; delayed field.
316 (syntax-case field ()
317 ((name get _ ...)
318 (with-syntax ((real-get (wrapped-field-accessor-name field)))
319 #'(define-inlinable (get x)
320 ;; The real value of that field is a promise, so force it.
321 (force (real-get x)))))))
322
7874bbbb
LC
323 (define (compute-abi-cookie field-specs)
324 ;; Compute an "ABI cookie" for the given FIELD-SPECS. We use
325 ;; 'string-hash' because that's a better hash function that 'hash' on a
326 ;; list of symbols.
327 (syntax-case field-specs ()
328 (((field get properties ...) ...)
329 (string-hash (object->string
330 (syntax->datum #'((field properties ...) ...)))
331 most-positive-fixnum))))
332
c0cd1b3e
LC
333 (syntax-case s ()
334 ((_ type syntactic-ctor ctor pred
792798f4
LC
335 (field get properties ...) ...)
336 (let* ((field-spec #'((field get properties ...) ...))
9b543456 337 (thunked (filter-map thunked-field? field-spec))
310b32a2 338 (delayed (filter-map delayed-field? field-spec))
8a16d064 339 (innate (filter-map innate-field? field-spec))
9b543456 340 (defaults (filter-map field-default-value
7874bbbb
LC
341 #'((field properties ...) ...)))
342 (cookie (compute-abi-cookie field-spec)))
c0cd1b3e
LC
343 (with-syntax (((field-spec* ...)
344 (map field-spec->srfi-9 field-spec))
345 ((thunked-field-accessor ...)
346 (filter-map (lambda (field)
347 (and (thunked-field? field)
348 (thunked-field-accessor-definition
349 field)))
310b32a2
LC
350 field-spec))
351 ((delayed-field-accessor ...)
352 (filter-map (lambda (field)
353 (and (delayed-field? field)
354 (delayed-field-accessor-definition
355 field)))
c0cd1b3e
LC
356 field-spec)))
357 #`(begin
358 (define-record-type type
359 (ctor field ...)
360 pred
361 field-spec* ...)
7874bbbb
LC
362 (define #,(current-abi-identifier #'type)
363 #,cookie)
ad7c1a2c
LC
364 thunked-field-accessor ...
365 delayed-field-accessor ...
39fc041a
LC
366 (make-syntactic-constructor type syntactic-ctor ctor
367 (field ...)
7874bbbb 368 #:abi-cookie #,cookie
39fc041a
LC
369 #:thunked #,thunked
370 #:delayed #,delayed
8a16d064 371 #:innate #,innate
39fc041a 372 #:defaults #,defaults))))))))
c0cd1b3e 373
c8772a7a
LC
374(define* (alist->record alist make keys
375 #:optional (multiple-value-keys '()))
376 "Apply MAKE to the values associated with KEYS in ALIST. Items in KEYS that
377are also in MULTIPLE-VALUE-KEYS are considered to occur possibly multiple
378times in ALIST, and thus their value is a list."
379 (let ((args (map (lambda (key)
380 (if (member key multiple-value-keys)
381 (filter-map (match-lambda
382 ((k . v)
383 (and (equal? k key) v)))
384 alist)
385 (assoc-ref alist key)))
386 keys)))
c0cd1b3e
LC
387 (apply make args)))
388
389(define (object->fields object fields port)
390 "Write OBJECT (typically a record) as a series of recutils-style fields to
391PORT, according to FIELDS. FIELDS must be a list of field name/getter pairs."
392 (let loop ((fields fields))
393 (match fields
394 (()
395 object)
396 (((field . get) rest ...)
397 (format port "~a: ~a~%" field (get object))
398 (loop rest)))))
399
fb519bd8
LC
400(define %recutils-field-charset
401 ;; Valid characters starting a recutils field.
402 ;; info "(recutils) Fields"
403 (char-set-union char-set:upper-case
404 char-set:lower-case
405 (char-set #\%)))
836d10f1 406
fdc1bf65
LC
407(define (recutils->alist port)
408 "Read a recutils-style record from PORT and return it as a list of key/value
409pairs. Stop upon an empty line (after consuming it) or EOF."
410 (let loop ((line (read-line port))
411 (result '()))
b7b88288 412 (cond ((eof-object? line)
fdc1bf65 413 (reverse result))
b7b88288
LC
414 ((string-null? line)
415 (if (null? result)
416 (loop (read-line port) result) ; leading space: ignore it
417 (reverse result))) ; end-of-record marker
fdc1bf65 418 (else
fb519bd8
LC
419 ;; Now check the first character of LINE, since that's what the
420 ;; recutils manual says is enough.
421 (let ((first (string-ref line 0)))
422 (cond
423 ((char-set-contains? %recutils-field-charset first)
424 (let* ((colon (string-index line #\:))
425 (field (string-take line colon))
426 (value (string-trim (string-drop line (+ 1 colon)))))
427 (loop (read-line port)
428 (alist-cons field value result))))
429 ((eqv? first #\#) ;info "(recutils) Comments"
430 (loop (read-line port) result))
431 ((eqv? first #\+) ;info "(recutils) Fields"
432 (let ((new-line (if (string-prefix? "+ " line)
433 (string-drop line 2)
434 (string-drop line 1))))
435 (match result
436 (((field . value) rest ...)
437 (loop (read-line port)
438 `((,field . ,(string-append value "\n" new-line))
439 ,@rest))))))
440 (else
441 (error "unmatched line" line))))))))
fdc1bf65 442
6692d845 443(define-syntax match-record
444 (syntax-rules ()
445 "Bind each FIELD of a RECORD of the given TYPE to it's FIELD name.
446The current implementation does not support thunked and delayed fields."
447 ((_ record type (field fields ...) body ...)
448 (if (eq? (struct-vtable record) type)
449 ;; TODO compute indices and report wrong-field-name errors at
450 ;; expansion time
451 ;; TODO support thunked and delayed fields
452 (let ((field ((record-accessor type 'field) record)))
453 (match-record record type (fields ...) body ...))
454 (throw 'wrong-type-arg record)))
455 ((_ record type () body ...)
456 (begin body ...))))
457
c0cd1b3e 458;;; records.scm ends here