summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--guix/utils.scm63
-rw-r--r--tests/utils.scm16
2 files changed, 78 insertions, 1 deletions
diff --git a/guix/utils.scm b/guix/utils.scm
index 5415ab9e63..05c04b87f1 100644
--- a/guix/utils.scm
+++ b/guix/utils.scm
@@ -18,6 +18,7 @@
 
 (define-module (guix utils)
   #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-9)
   #:use-module (srfi srfi-26)
   #:use-module (srfi srfi-39)
   #:use-module (srfi srfi-60)
@@ -27,6 +28,7 @@
   #:autoload   (ice-9 popen)  (open-pipe*)
   #:autoload   (ice-9 rdelim) (read-line)
   #:use-module (ice-9 regex)
+  #:use-module (ice-9 match)
   #:use-module ((chop hash)
                 #:select (bytevector-hash
                           hash-method/sha256))
@@ -42,6 +44,7 @@
             %nixpkgs-directory
             nixpkgs-derivation
 
+            define-record-type*
             memoize
             gnu-triplet->nix-system
             %current-system))
@@ -391,6 +394,66 @@ starting from the right of S."
 ;;; Miscellaneous.
 ;;;
 
+(define-syntax define-record-type*
+  (lambda (s)
+    "Define the given record type such that an additional \"syntactic
+constructor\" is defined, which allows instances to be constructed with named
+field initializers, à la SRFI-35, as well as default values."
+    (define (make-syntactic-constructor name ctor fields defaults)
+      "Make the syntactic constructor NAME that calls CTOR, and expects all
+of FIELDS to be initialized.  DEFAULTS is the list of FIELD/DEFAULT-VALUE
+tuples."
+      (with-syntax ((name     name)
+                    (ctor     ctor)
+                    (expected fields)
+                    (defaults defaults))
+        #'(define-syntax name
+            (lambda (s)
+              (syntax-case s expected
+                ((_ (field value) (... ...))
+                 (let ((fields   (map syntax->datum #'(field (... ...))))
+                       (inits    (map (match-lambda
+                                       ((f v)
+                                        (list (syntax->datum f) v)))
+                                      #'((field value) (... ...))))
+                       (dflt      (map (match-lambda
+                                        ((f v)
+                                         (list (syntax->datum f) v)))
+                                       #'defaults)))
+
+                   (define (field-value f)
+                     (match (assoc f inits)
+                       ((_ v) v)
+                       (#f (car (assoc-ref dflt f)))))
+
+                   (if (lset= eq? (append fields (map car dflt))
+                              'expected)
+                       #`(ctor #,@(map field-value 'expected))
+                       (error "missing or extraneous field initializers"
+                              (lset-difference eq? fields 'expected))))))))))
+
+    (define (field-default-value s)
+      (syntax-case s (default)
+        ((field (default val) _ ...)
+         (list #'field #'val))
+        ((field _ options ...)
+         (field-default-value #'(field options ...)))
+        (_ #f)))
+
+    (syntax-case s ()
+      ((_ type syntactic-ctor ctor pred
+          (field get options ...) ...)
+       #`(begin
+           (define-record-type type
+             (ctor field ...)
+             pred
+             (field get) ...)
+           #,(make-syntactic-constructor #'syntactic-ctor #'ctor
+                                         #'(field ...)
+                                         (filter-map field-default-value
+                                                     #'((field options ...)
+                                                        ...))))))))
+
 (define (memoize proc)
   "Return a memoizing version of PROC."
   (let ((cache (make-hash-table)))
diff --git a/tests/utils.scm b/tests/utils.scm
index b3c7fefa39..83a78b7a78 100644
--- a/tests/utils.scm
+++ b/tests/utils.scm
@@ -26,7 +26,8 @@
   #:use-module (rnrs bytevectors)
   #:use-module (rnrs io ports)
   #:use-module (ice-9 rdelim)
-  #:use-module (ice-9 popen))
+  #:use-module (ice-9 popen)
+  #:use-module (ice-9 match))
 
 (test-begin "utils")
 
@@ -98,6 +99,19 @@
                (equal? nix (gnu-triplet->nix-system gnu)))
              gnu nix))))
 
+(test-assert "define-record-type*"
+  (begin
+    (define-record-type* <foo> foo make-foo
+      foo?
+      (bar foo-bar)
+      (baz foo-baz (default (+ 40 2))))
+    (and (match (foo (bar 1) (baz 2))
+           (($ <foo> 1 2) #t))
+         (match (foo (baz 2) (bar 1))
+           (($ <foo> 1 2) #t))
+         (match (foo (bar 1))
+           (($ <foo> 1 42) #t)))))
+
 (test-end)