summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLudovic Courtès <ludo@gnu.org>2022-11-19 17:23:04 +0100
committerLudovic Courtès <ludo@gnu.org>2022-12-08 13:21:23 +0100
commit754a7660a1716998b557aedeb805ee9040afdcdf (patch)
tree290d583946af03f1e712bb7b3f5aae0633c38db0
parenta420b4f34e7449319f6ec73301ffb932845b66d6 (diff)
records: 'match-record' checks fields at macro-expansion time.
This allows 'match-record' to be more efficient (field offsets are computed at compilation time) and to report unknown fields at macro-expansion time. * guix/records.scm (map-fields): New macro. (define-record-type*)[rtd-identifier]: New procedure. Define TYPE as a macro and use a separate identifier for the RTD. (lookup-field, match-record-inner): New macros. (match-record): Rewrite in terms of 'match-error-inner'. * tests/records.scm ("match-record, simple") ("match-record, unknown field"): New tests. * gnu/services/cuirass.scm (cuirass-shepherd-service): Rename 'log-file' local variable to 'main-log-file'. * gnu/services/getmail.scm (serialize-getmail-configuration-file): Move after <getmail-configuration-file> definition.
-rw-r--r--gnu/services/cuirass.scm4
-rw-r--r--gnu/services/getmail.scm22
-rw-r--r--guix/records.scm87
-rw-r--r--tests/records.scm33
4 files changed, 122 insertions, 24 deletions
diff --git a/gnu/services/cuirass.scm b/gnu/services/cuirass.scm
index 52de5ca7c0b..d7c6ab9877a 100644
--- a/gnu/services/cuirass.scm
+++ b/gnu/services/cuirass.scm
@@ -125,7 +125,7 @@
125 (let ((cuirass (cuirass-configuration-cuirass config)) 125 (let ((cuirass (cuirass-configuration-cuirass config))
126 (cache-directory (cuirass-configuration-cache-directory config)) 126 (cache-directory (cuirass-configuration-cache-directory config))
127 (web-log-file (cuirass-configuration-web-log-file config)) 127 (web-log-file (cuirass-configuration-web-log-file config))
128 (log-file (cuirass-configuration-log-file config)) 128 (main-log-file (cuirass-configuration-log-file config))
129 (user (cuirass-configuration-user config)) 129 (user (cuirass-configuration-user config))
130 (group (cuirass-configuration-group config)) 130 (group (cuirass-configuration-group config))
131 (interval (cuirass-configuration-interval config)) 131 (interval (cuirass-configuration-interval config))
@@ -169,7 +169,7 @@
169 169
170 #:user #$user 170 #:user #$user
171 #:group #$group 171 #:group #$group
172 #:log-file #$log-file)) 172 #:log-file #$main-log-file))
173 (stop #~(make-kill-destructor))) 173 (stop #~(make-kill-destructor)))
174 ,(shepherd-service 174 ,(shepherd-service
175 (documentation "Run Cuirass web interface.") 175 (documentation "Run Cuirass web interface.")
diff --git a/gnu/services/getmail.scm b/gnu/services/getmail.scm
index fb82d054ca7..19faea782f4 100644
--- a/gnu/services/getmail.scm
+++ b/gnu/services/getmail.scm
@@ -215,17 +215,6 @@ lines.")
215 (parameter-alist '()) 215 (parameter-alist '())
216 "Extra options to include.")) 216 "Extra options to include."))
217 217
218(define (serialize-getmail-configuration-file field-name val)
219 (match-record val <getmail-configuration-file>
220 (retriever destination options)
221 #~(string-append
222 "[retriever]\n"
223 #$(serialize-getmail-retriever-configuration #f retriever)
224 "\n[destination]\n"
225 #$(serialize-getmail-destination-configuration #f destination)
226 "\n[options]\n"
227 #$(serialize-getmail-options-configuration #f options))))
228
229(define-configuration getmail-configuration-file 218(define-configuration getmail-configuration-file
230 (retriever 219 (retriever
231 (getmail-retriever-configuration (getmail-retriever-configuration)) 220 (getmail-retriever-configuration (getmail-retriever-configuration))
@@ -237,6 +226,17 @@ lines.")
237 (getmail-options-configuration (getmail-options-configuration)) 226 (getmail-options-configuration (getmail-options-configuration))
238 "Configure getmail.")) 227 "Configure getmail."))
239 228
229(define (serialize-getmail-configuration-file field-name val)
230 (match-record val <getmail-configuration-file>
231 (retriever destination options)
232 #~(string-append
233 "[retriever]\n"
234 #$(serialize-getmail-retriever-configuration #f retriever)
235 "\n[destination]\n"
236 #$(serialize-getmail-destination-configuration #f destination)
237 "\n[options]\n"
238 #$(serialize-getmail-options-configuration #f options))))
239
240(define (serialize-symbol field-name val) "") 240(define (serialize-symbol field-name val) "")
241(define (serialize-getmail-configuration field-name val) "") 241(define (serialize-getmail-configuration field-name val) "")
242 242
diff --git a/guix/records.scm b/guix/records.scm
index ed94c83dac4..13463647c82 100644
--- a/guix/records.scm
+++ b/guix/records.scm
@@ -1,5 +1,5 @@
1;;; GNU Guix --- Functional package management for GNU 1;;; GNU Guix --- Functional package management for GNU
2;;; Copyright © 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Ludovic Courtès <ludo@gnu.org> 2;;; Copyright © 2012-2022 Ludovic Courtès <ludo@gnu.org>
3;;; Copyright © 2018 Mark H Weaver <mhw@netris.org> 3;;; Copyright © 2018 Mark H Weaver <mhw@netris.org>
4;;; 4;;;
5;;; This file is part of GNU Guix. 5;;; This file is part of GNU Guix.
@@ -104,6 +104,10 @@ error-reporting purposes."
104 (() 104 (()
105 #t))))))) 105 #t)))))))
106 106
107(define-syntax map-fields
108 (lambda (x)
109 (syntax-violation 'map-fields "bad use of syntactic keyword" x x)))
110
107(define-syntax-parameter this-record 111(define-syntax-parameter this-record
108 (lambda (s) 112 (lambda (s)
109 "Return the record being defined. This macro may only be used in the 113 "Return the record being defined. This macro may only be used in the
@@ -325,6 +329,15 @@ This expression returns a new object equal to 'x' except for its 'name'
325field and its 'loc' field---the latter is marked as \"innate\", so it is not 329field and its 'loc' field---the latter is marked as \"innate\", so it is not
326inherited." 330inherited."
327 331
332 (define (rtd-identifier type)
333 ;; Return an identifier derived from TYPE to name its record type
334 ;; descriptor (RTD).
335 (let ((type-name (syntax->datum type)))
336 (datum->syntax
337 type
338 (string->symbol
339 (string-append "% " (symbol->string type-name) " rtd")))))
340
328 (define (field-default-value s) 341 (define (field-default-value s)
329 (syntax-case s (default) 342 (syntax-case s (default)
330 ((field (default val) _ ...) 343 ((field (default val) _ ...)
@@ -428,10 +441,31 @@ inherited."
428 field))) 441 field)))
429 field-spec))) 442 field-spec)))
430 #`(begin 443 #`(begin
431 (define-record-type type 444 (define-record-type #,(rtd-identifier #'type)
432 (ctor field ...) 445 (ctor field ...)
433 pred 446 pred
434 field-spec* ...) 447 field-spec* ...)
448
449 ;; Rectify the vtable type name...
450 (set-struct-vtable-name! #,(rtd-identifier #'type) 'type)
451 (cond-expand
452 (guile-3
453 ;; ... and the record type name.
454 (struct-set! #,(rtd-identifier #'type) vtable-offset-user
455 'type))
456 (else #f))
457
458 (define-syntax type
459 (lambda (s)
460 "This macro lets us query record type info at
461macro-expansion time."
462 (syntax-case s (map-fields)
463 ((_ map-fields macro)
464 #'(macro (field ...)))
465 (id
466 (identifier? #'id)
467 #'#,(rtd-identifier #'type)))))
468
435 (define #,(current-abi-identifier #'type) 469 (define #,(current-abi-identifier #'type)
436 #,cookie) 470 #,cookie)
437 471
@@ -535,19 +569,50 @@ pairs. Stop upon an empty line (after consuming it) or EOF."
535 (else 569 (else
536 (error "unmatched line" line)))))))) 570 (error "unmatched line" line))))))))
537 571
572
573;;;
574;;; Pattern matching.
575;;;
576
577(define-syntax lookup-field
578 (lambda (s)
579 "Look up FIELD in the given list and return an expression that represents
580its offset in the record. Raise a syntax violation when the field is not
581found."
582 (syntax-case s ()
583 ((_ field offset ())
584 (syntax-violation 'lookup-field "unknown record type field"
585 s #'field))
586 ((_ field offset (head tail ...))
587 (free-identifier=? #'field #'head)
588 #'offset)
589 ((_ field offset (_ tail ...))
590 #'(lookup-field field (+ 1 offset) (tail ...))))))
591
592(define-syntax match-record-inner
593 (lambda (s)
594 (syntax-case s ()
595 ((_ record type (field rest ...) body ...)
596 #`(let-syntax ((field-offset (syntax-rules ()
597 ((_ f)
598 (lookup-field field 0 f)))))
599 (let* ((offset (type map-fields field-offset))
600 (field (struct-ref record offset)))
601 (match-record-inner record type (rest ...) body ...))))
602 ((_ record type () body ...)
603 #'(begin body ...)))))
604
538(define-syntax match-record 605(define-syntax match-record
539 (syntax-rules () 606 (syntax-rules ()
540 "Bind each FIELD of a RECORD of the given TYPE to it's FIELD name. 607 "Bind each FIELD of a RECORD of the given TYPE to it's FIELD name.
608The order in which fields appear does not matter. A syntax error is raised if
609an unknown field is queried.
610
541The current implementation does not support thunked and delayed fields." 611The current implementation does not support thunked and delayed fields."
542 ((_ record type (field fields ...) body ...) 612 ;; TODO support thunked and delayed fields
613 ((_ record type (fields ...) body ...)
543 (if (eq? (struct-vtable record) type) 614 (if (eq? (struct-vtable record) type)
544 ;; TODO compute indices and report wrong-field-name errors at 615 (match-record-inner record type (fields ...) body ...)
545 ;; expansion time 616 (throw 'wrong-type-arg record)))))
546 ;; TODO support thunked and delayed fields
547 (let ((field ((record-accessor type 'field) record)))
548 (match-record record type (fields ...) body ...))
549 (throw 'wrong-type-arg record)))
550 ((_ record type () body ...)
551 (begin body ...))))
552 617
553;;; records.scm ends here 618;;; records.scm ends here
diff --git a/tests/records.scm b/tests/records.scm
index 00c58b07364..8504c8d5a54 100644
--- a/tests/records.scm
+++ b/tests/records.scm
@@ -528,4 +528,37 @@ Description: 1st line,
528 '("a" "b" "c") 528 '("a" "b" "c")
529 '("a"))) 529 '("a")))
530 530
531(test-equal "match-record, simple"
532 '((1 2) (a b))
533 (let ()
534 (define-record-type* <foo> foo make-foo
535 foo?
536 (first foo-first (default 1))
537 (second foo-second))
538
539 (list (match-record (foo (second 2)) <foo>
540 (first second)
541 (list first second))
542 (match-record (foo (first 'a) (second 'b)) <foo>
543 (second first)
544 (list first second)))))
545
546(test-equal "match-record, unknown field"
547 'syntax-error
548 (catch 'syntax-error
549 (lambda ()
550 (eval '(begin
551 (use-modules (guix records))
552
553 (define-record-type* <foo> foo make-foo
554 foo?
555 (first foo-first (default 1))
556 (second foo-second))
557
558 (match-record (foo (second 2)) <foo>
559 (one two)
560 #f))
561 (make-fresh-user-module)))
562 (lambda (key . args) key)))
563
531(test-end) 564(test-end)