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