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.
This commit is contained in:
Ludovic Courtès 2022-11-19 17:23:04 +01:00
parent 594f5ef351
commit 7c1161dba4
No known key found for this signature in database
GPG Key ID: 090B11993D9AEBB5
4 changed files with 122 additions and 24 deletions

View File

@ -125,7 +125,7 @@
(let ((cuirass (cuirass-configuration-cuirass config)) (let ((cuirass (cuirass-configuration-cuirass config))
(cache-directory (cuirass-configuration-cache-directory config)) (cache-directory (cuirass-configuration-cache-directory config))
(web-log-file (cuirass-configuration-web-log-file config)) (web-log-file (cuirass-configuration-web-log-file config))
(log-file (cuirass-configuration-log-file config)) (main-log-file (cuirass-configuration-log-file config))
(user (cuirass-configuration-user config)) (user (cuirass-configuration-user config))
(group (cuirass-configuration-group config)) (group (cuirass-configuration-group config))
(interval (cuirass-configuration-interval config)) (interval (cuirass-configuration-interval config))
@ -169,7 +169,7 @@
#:user #$user #:user #$user
#:group #$group #:group #$group
#:log-file #$log-file)) #:log-file #$main-log-file))
(stop #~(make-kill-destructor))) (stop #~(make-kill-destructor)))
,(shepherd-service ,(shepherd-service
(documentation "Run Cuirass web interface.") (documentation "Run Cuirass web interface.")

View File

@ -215,17 +215,6 @@ lines.")
(parameter-alist '()) (parameter-alist '())
"Extra options to include.")) "Extra options to include."))
(define (serialize-getmail-configuration-file field-name val)
(match-record val <getmail-configuration-file>
(retriever destination options)
#~(string-append
"[retriever]\n"
#$(serialize-getmail-retriever-configuration #f retriever)
"\n[destination]\n"
#$(serialize-getmail-destination-configuration #f destination)
"\n[options]\n"
#$(serialize-getmail-options-configuration #f options))))
(define-configuration getmail-configuration-file (define-configuration getmail-configuration-file
(retriever (retriever
(getmail-retriever-configuration (getmail-retriever-configuration)) (getmail-retriever-configuration (getmail-retriever-configuration))
@ -237,6 +226,17 @@ lines.")
(getmail-options-configuration (getmail-options-configuration)) (getmail-options-configuration (getmail-options-configuration))
"Configure getmail.")) "Configure getmail."))
(define (serialize-getmail-configuration-file field-name val)
(match-record val <getmail-configuration-file>
(retriever destination options)
#~(string-append
"[retriever]\n"
#$(serialize-getmail-retriever-configuration #f retriever)
"\n[destination]\n"
#$(serialize-getmail-destination-configuration #f destination)
"\n[options]\n"
#$(serialize-getmail-options-configuration #f options))))
(define (serialize-symbol field-name val) "") (define (serialize-symbol field-name val) "")
(define (serialize-getmail-configuration field-name val) "") (define (serialize-getmail-configuration field-name val) "")

View File

@ -1,5 +1,5 @@
;;; GNU Guix --- Functional package management for GNU ;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Ludovic Courtès <ludo@gnu.org> ;;; Copyright © 2012-2022 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2018 Mark H Weaver <mhw@netris.org> ;;; Copyright © 2018 Mark H Weaver <mhw@netris.org>
;;; ;;;
;;; This file is part of GNU Guix. ;;; This file is part of GNU Guix.
@ -104,6 +104,10 @@ error-reporting purposes."
(() (()
#t))))))) #t)))))))
(define-syntax map-fields
(lambda (x)
(syntax-violation 'map-fields "bad use of syntactic keyword" x x)))
(define-syntax-parameter this-record (define-syntax-parameter this-record
(lambda (s) (lambda (s)
"Return the record being defined. This macro may only be used in the "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'
field and its 'loc' field---the latter is marked as \"innate\", so it is not field and its 'loc' field---the latter is marked as \"innate\", so it is not
inherited." inherited."
(define (rtd-identifier type)
;; Return an identifier derived from TYPE to name its record type
;; descriptor (RTD).
(let ((type-name (syntax->datum type)))
(datum->syntax
type
(string->symbol
(string-append "% " (symbol->string type-name) " rtd")))))
(define (field-default-value s) (define (field-default-value s)
(syntax-case s (default) (syntax-case s (default)
((field (default val) _ ...) ((field (default val) _ ...)
@ -428,10 +441,31 @@ inherited."
field))) field)))
field-spec))) field-spec)))
#`(begin #`(begin
(define-record-type type (define-record-type #,(rtd-identifier #'type)
(ctor field ...) (ctor field ...)
pred pred
field-spec* ...) field-spec* ...)
;; Rectify the vtable type name...
(set-struct-vtable-name! #,(rtd-identifier #'type) 'type)
(cond-expand
(guile-3
;; ... and the record type name.
(struct-set! #,(rtd-identifier #'type) vtable-offset-user
'type))
(else #f))
(define-syntax type
(lambda (s)
"This macro lets us query record type info at
macro-expansion time."
(syntax-case s (map-fields)
((_ map-fields macro)
#'(macro (field ...)))
(id
(identifier? #'id)
#'#,(rtd-identifier #'type)))))
(define #,(current-abi-identifier #'type) (define #,(current-abi-identifier #'type)
#,cookie) #,cookie)
@ -535,19 +569,50 @@ pairs. Stop upon an empty line (after consuming it) or EOF."
(else (else
(error "unmatched line" line)))))))) (error "unmatched line" line))))))))
;;;
;;; Pattern matching.
;;;
(define-syntax lookup-field
(lambda (s)
"Look up FIELD in the given list and return an expression that represents
its offset in the record. Raise a syntax violation when the field is not
found."
(syntax-case s ()
((_ field offset ())
(syntax-violation 'lookup-field "unknown record type field"
s #'field))
((_ field offset (head tail ...))
(free-identifier=? #'field #'head)
#'offset)
((_ field offset (_ tail ...))
#'(lookup-field field (+ 1 offset) (tail ...))))))
(define-syntax match-record-inner
(lambda (s)
(syntax-case s ()
((_ record type (field rest ...) body ...)
#`(let-syntax ((field-offset (syntax-rules ()
((_ f)
(lookup-field field 0 f)))))
(let* ((offset (type map-fields field-offset))
(field (struct-ref record offset)))
(match-record-inner record type (rest ...) body ...))))
((_ record type () body ...)
#'(begin body ...)))))
(define-syntax match-record (define-syntax match-record
(syntax-rules () (syntax-rules ()
"Bind each FIELD of a RECORD of the given TYPE to it's FIELD name. "Bind each FIELD of a RECORD of the given TYPE to it's FIELD name.
The order in which fields appear does not matter. A syntax error is raised if
an unknown field is queried.
The current implementation does not support thunked and delayed fields." The current implementation does not support thunked and delayed fields."
((_ record type (field fields ...) body ...) ;; TODO support thunked and delayed fields
((_ record type (fields ...) body ...)
(if (eq? (struct-vtable record) type) (if (eq? (struct-vtable record) type)
;; TODO compute indices and report wrong-field-name errors at (match-record-inner record type (fields ...) body ...)
;; expansion time (throw 'wrong-type-arg record)))))
;; TODO support thunked and delayed fields
(let ((field ((record-accessor type 'field) record)))
(match-record record type (fields ...) body ...))
(throw 'wrong-type-arg record)))
((_ record type () body ...)
(begin body ...))))
;;; records.scm ends here ;;; records.scm ends here

View File

@ -528,4 +528,37 @@ Description: 1st line,
'("a" "b" "c") '("a" "b" "c")
'("a"))) '("a")))
(test-equal "match-record, simple"
'((1 2) (a b))
(let ()
(define-record-type* <foo> foo make-foo
foo?
(first foo-first (default 1))
(second foo-second))
(list (match-record (foo (second 2)) <foo>
(first second)
(list first second))
(match-record (foo (first 'a) (second 'b)) <foo>
(second first)
(list first second)))))
(test-equal "match-record, unknown field"
'syntax-error
(catch 'syntax-error
(lambda ()
(eval '(begin
(use-modules (guix records))
(define-record-type* <foo> foo make-foo
foo?
(first foo-first (default 1))
(second foo-second))
(match-record (foo (second 2)) <foo>
(one two)
#f))
(make-fresh-user-module)))
(lambda (key . args) key)))
(test-end) (test-end)