1 ;;; GNU Guix --- Functional package management for GNU
2 ;;; Copyright © 2012, 2013, 2014, 2015 Ludovic Courtès <ludo@gnu.org>
4 ;;; This file is part of GNU Guix.
6 ;;; GNU Guix is free software; you can redistribute it and/or modify it
7 ;;; under the terms of the GNU General Public License as published by
8 ;;; the Free Software Foundation; either version 3 of the License, or (at
9 ;;; your option) any later version.
11 ;;; GNU Guix is distributed in the hope that it will be useful, but
12 ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
13 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 ;;; GNU General Public License for more details.
16 ;;; You should have received a copy of the GNU General Public License
17 ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
19 (define-module (guix records)
20 #:use-module (srfi srfi-1)
21 #:use-module (srfi srfi-9)
22 #:use-module (srfi srfi-26)
23 #:use-module (ice-9 match)
24 #:use-module (ice-9 regex)
25 #:use-module (ice-9 rdelim)
26 #:export (define-record-type*
33 ;;; Utilities for dealing with Scheme records.
37 (define-syntax record-error
39 "Report a syntactic error in use of CONSTRUCTOR."
40 ((_ constructor form fmt args ...)
41 (syntax-violation constructor
42 (format #f fmt args ...)
45 (define-syntax make-syntactic-constructor
47 "Make the syntactic constructor NAME for TYPE, that calls CTOR, and
48 expects all of EXPECTED fields to be initialized. DEFAULTS is the list of
49 FIELD/DEFAULT-VALUE tuples, THUNKED is the list of identifiers of thunked
50 fields, and DELAYED is the list of identifiers of delayed fields."
51 ((_ type name ctor (expected ...)
57 (define (record-inheritance orig-record field+value)
58 ;; Produce code that returns a record identical to ORIG-RECORD,
59 ;; except that values for the FIELD+VALUE alist prevail.
60 (define (field-inherited-value f)
61 (and=> (find (lambda (x)
62 (eq? f (car (syntax->datum x))))
66 ;; Make sure there are no unknown field names.
67 (let* ((fields (map (compose car syntax->datum) field+value))
68 (unexpected (lset-difference eq? fields '(expected ...))))
69 (when (pair? unexpected)
70 (record-error 'name s "extraneous field initializers ~a"
74 #,@(map (lambda (field index)
75 (or (field-inherited-value field)
76 #`(struct-ref #,orig-record
79 (iota (length '(expected ...))))))
81 (define (thunked-field? f)
82 (memq (syntax->datum f) 'thunked))
84 (define (delayed-field? f)
85 (memq (syntax->datum f) 'delayed))
87 (define (wrap-field-value f value)
88 (cond ((thunked-field? f)
89 #`(lambda () #,value))
94 (define default-values
95 ;; List of symbol/value tuples.
98 (list (syntax->datum f) v)))
101 (define (field-default-value f)
102 (car (assoc-ref default-values (syntax->datum f))))
104 (define (field-bindings field+value)
105 ;; Return field to value bindings, for use in 'let*' below.
106 (map (lambda (field+value)
107 (syntax-case field+value ()
110 #,(wrap-field-value #'field #'value)))))
113 (syntax-case s (inherit expected ...)
114 ((_ (inherit orig-record) (field value) (... ...))
115 #`(let* #,(field-bindings #'((field value) (... ...)))
116 #,(record-inheritance #'orig-record
117 #'((field value) (... ...)))))
118 ((_ (field value) (... ...))
119 (let ((fields (map syntax->datum #'(field (... ...)))))
120 (define (field-value f)
121 (or (and=> (find (lambda (x)
122 (eq? f (car (syntax->datum x))))
123 #'((field value) (... ...)))
125 (wrap-field-value f (field-default-value f))))
127 (let ((fields (append fields (map car default-values))))
128 (cond ((lset= eq? fields '(expected ...))
129 #`(let* #,(field-bindings
130 #'((field value) (... ...)))
131 (ctor #,@(map field-value '(expected ...)))))
132 ((pair? (lset-difference eq? fields
134 (record-error 'name s
135 "extraneous field initializers ~a"
136 (lset-difference eq? fields
139 (record-error 'name s
140 "missing field initializers ~a"
145 (define-syntax-rule (define-field-property-predicate predicate property)
146 "Define PREDICATE as a procedure that takes a syntax object and, when passed
147 a field specification, returns the field name if it has the given PROPERTY."
148 (define (predicate s)
149 (syntax-case s (property)
150 ((field (property values (... ...)) _ (... ...))
152 ((field _ properties (... ...))
153 (predicate #'(field properties (... ...))))
156 (define-syntax define-record-type*
158 "Define the given record type such that an additional \"syntactic
159 constructor\" is defined, which allows instances to be constructed with named
160 field initializers, à la SRFI-35, as well as default values. An example use
163 (define-record-type* <thing> thing make-thing
165 (name thing-name (default \"chbouib\"))
167 (default (current-output-port)) (thunked)))
169 This example defines a macro 'thing' that can be used to instantiate records
174 (port (current-error-port)))
176 The value of 'name' or 'port' could as well be omitted, in which case the
177 default value specified in the 'define-record-type*' form is used:
181 The 'port' field is \"thunked\", meaning that calls like '(thing-port x)' will
182 actually compute the field's value in the current dynamic extent, which is
183 useful when referring to fluids in a field's value.
185 A field can also be marked as \"delayed\" instead of \"thunked\", in which
186 case its value is effectively wrapped in a (delay …) form.
188 It is possible to copy an object 'x' created with 'thing' like this:
190 (thing (inherit x) (name \"bar\"))
192 This expression returns a new object equal to 'x' except for its 'name'
195 (define (field-default-value s)
196 (syntax-case s (default)
197 ((field (default val) _ ...)
198 (list #'field #'val))
199 ((field _ options ...)
200 (field-default-value #'(field options ...)))
203 (define-field-property-predicate delayed-field? delayed)
204 (define-field-property-predicate thunked-field? thunked)
206 (define (wrapped-field? s)
207 (or (thunked-field? s) (delayed-field? s)))
209 (define (wrapped-field-accessor-name field)
210 ;; Return the name (an unhygienic syntax object) of the "real"
211 ;; getter for field, which is assumed to be a wrapped field.
212 (syntax-case field ()
213 ((field get options ...)
214 (let* ((getter (syntax->datum #'get))
215 (real-getter (symbol-append '% getter '-real)))
216 (datum->syntax #'get real-getter)))))
218 (define (field-spec->srfi-9 field)
219 ;; Convert a field spec of our style to a SRFI-9 field spec of the
221 (syntax-case field ()
222 ((name get options ...)
224 #,(if (wrapped-field? field)
225 (wrapped-field-accessor-name field)
228 (define (thunked-field-accessor-definition field)
229 ;; Return the real accessor for FIELD, which is assumed to be a
231 (syntax-case field ()
233 (with-syntax ((real-get (wrapped-field-accessor-name field)))
234 #'(define-inlinable (get x)
235 ;; The real value of that field is a thunk, so call it.
238 (define (delayed-field-accessor-definition field)
239 ;; Return the real accessor for FIELD, which is assumed to be a
241 (syntax-case field ()
243 (with-syntax ((real-get (wrapped-field-accessor-name field)))
244 #'(define-inlinable (get x)
245 ;; The real value of that field is a promise, so force it.
246 (force (real-get x)))))))
249 ((_ type syntactic-ctor ctor pred
250 (field get options ...) ...)
251 (let* ((field-spec #'((field get options ...) ...))
252 (thunked (filter-map thunked-field? field-spec))
253 (delayed (filter-map delayed-field? field-spec))
254 (defaults (filter-map field-default-value
255 #'((field options ...) ...))))
256 (with-syntax (((field-spec* ...)
257 (map field-spec->srfi-9 field-spec))
258 ((thunked-field-accessor ...)
259 (filter-map (lambda (field)
260 (and (thunked-field? field)
261 (thunked-field-accessor-definition
264 ((delayed-field-accessor ...)
265 (filter-map (lambda (field)
266 (and (delayed-field? field)
267 (delayed-field-accessor-definition
271 (define-record-type type
275 (begin thunked-field-accessor ...
276 delayed-field-accessor ...)
277 (make-syntactic-constructor type syntactic-ctor ctor
281 #:defaults #,defaults))))))))
283 (define* (alist->record alist make keys
284 #:optional (multiple-value-keys '()))
285 "Apply MAKE to the values associated with KEYS in ALIST. Items in KEYS that
286 are also in MULTIPLE-VALUE-KEYS are considered to occur possibly multiple
287 times in ALIST, and thus their value is a list."
288 (let ((args (map (lambda (key)
289 (if (member key multiple-value-keys)
290 (filter-map (match-lambda
292 (and (equal? k key) v)))
294 (assoc-ref alist key)))
298 (define (object->fields object fields port)
299 "Write OBJECT (typically a record) as a series of recutils-style fields to
300 PORT, according to FIELDS. FIELDS must be a list of field name/getter pairs."
301 (let loop ((fields fields))
305 (((field . get) rest ...)
306 (format port "~a: ~a~%" field (get object))
309 (define %recutils-field-charset
310 ;; Valid characters starting a recutils field.
311 ;; info "(recutils) Fields"
312 (char-set-union char-set:upper-case
316 (define (recutils->alist port)
317 "Read a recutils-style record from PORT and return it as a list of key/value
318 pairs. Stop upon an empty line (after consuming it) or EOF."
319 (let loop ((line (read-line port))
321 (cond ((eof-object? line)
325 (loop (read-line port) result) ; leading space: ignore it
326 (reverse result))) ; end-of-record marker
328 ;; Now check the first character of LINE, since that's what the
329 ;; recutils manual says is enough.
330 (let ((first (string-ref line 0)))
332 ((char-set-contains? %recutils-field-charset first)
333 (let* ((colon (string-index line #\:))
334 (field (string-take line colon))
335 (value (string-trim (string-drop line (+ 1 colon)))))
336 (loop (read-line port)
337 (alist-cons field value result))))
338 ((eqv? first #\#) ;info "(recutils) Comments"
339 (loop (read-line port) result))
340 ((eqv? first #\+) ;info "(recutils) Fields"
341 (let ((new-line (if (string-prefix? "+ " line)
343 (string-drop line 1))))
345 (((field . value) rest ...)
346 (loop (read-line port)
347 `((,field . ,(string-append value "\n" new-line))
350 (error "unmatched line" line))))))))
352 ;;; records.scm ends here