summary refs log tree commit diff
path: root/guix/records.scm
diff options
context:
space:
mode:
authorLudovic Courtès <ludo@gnu.org>2013-05-12 15:46:16 +0200
committerLudovic Courtès <ludo@gnu.org>2013-05-12 15:46:16 +0200
commitc0cd1b3ea7753fe2826f7a336019000df9dea96f (patch)
treea7e376f68216dcd26eab06aea5ff529b483fea00 /guix/records.scm
parent9b1ef2f3232e7af111ba05353008ebd2f8955f02 (diff)
downloadguix-c0cd1b3ea7753fe2826f7a336019000df9dea96f.tar.gz
Move record utilities to (guix records).
* guix/utils.scm (define-record-type*): Move to...
* guix/records.scm: ... here.  New file.
* guix/build-system.scm, guix/packages.scm: Use it.
* guix/gnu-maintenance.scm: Likewise.
  (official-gnu-packages)[alist->record]: Remove.
* guix/scripts/substitute-binary.scm: Likewise.
  (alist->record, object->fields): Remove.
* tests/utils.scm ("define-record-type*", "define-record-type* with
  letrec* behavior", "define-record-type* & inherit",
  "define-record-type* & inherit & letrec* behavior",
  "define-record-type* & thunked", "define-record-type* & thunked &
  default", "define-record-type* & thunked & inherited"): Move to...
* tests/records.scm: ... here.  New file.
Diffstat (limited to 'guix/records.scm')
-rw-r--r--guix/records.scm214
1 files changed, 214 insertions, 0 deletions
diff --git a/guix/records.scm b/guix/records.scm
new file mode 100644
index 0000000000..54e1c17752
--- /dev/null
+++ b/guix/records.scm
@@ -0,0 +1,214 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2012, 2013 Ludovic Courtès <ludo@gnu.org>
+;;;
+;;; This file is part of GNU Guix.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (guix records)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-9)
+  #:use-module (srfi srfi-26)
+  #:use-module (ice-9 match)
+  #:export (define-record-type*
+            alist->record
+            object->fields))
+
+;;; Commentary:
+;;;
+;;; Utilities for dealing with Scheme records.
+;;;
+;;; Code:
+
+(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 type name ctor fields thunked defaults)
+      "Make the syntactic constructor NAME for TYPE, that calls CTOR, and
+expects all of FIELDS to be initialized.  DEFAULTS is the list of
+FIELD/DEFAULT-VALUE tuples, and THUNKED is the list of identifiers of
+thunked fields."
+      (with-syntax ((type     type)
+                    (name     name)
+                    (ctor     ctor)
+                    (expected fields)
+                    (defaults defaults))
+        #`(define-syntax name
+            (lambda (s)
+              (define (record-inheritance orig-record field+value)
+                ;; Produce code that returns a record identical to
+                ;; ORIG-RECORD, except that values for the FIELD+VALUE alist
+                ;; prevail.
+                (define (field-inherited-value f)
+                  (and=> (find (lambda (x)
+                                 (eq? f (car (syntax->datum x))))
+                               field+value)
+                         car))
+
+                #`(make-struct type 0
+                               #,@(map (lambda (field index)
+                                         (or (field-inherited-value field)
+                                             #`(struct-ref #,orig-record
+                                                           #,index)))
+                                       'expected
+                                       (iota (length 'expected)))))
+
+              (define (thunked-field? f)
+                (memq (syntax->datum f) '#,thunked))
+
+              (define (field-bindings field+value)
+                ;; Return field to value bindings, for use in `letrec*' below.
+                (map (lambda (field+value)
+                       (syntax-case field+value ()
+                         ((field value)
+                          #`(field
+                             #,(if (thunked-field? #'field)
+                                   #'(lambda () value)
+                                   #'value)))))
+                     field+value))
+
+              (syntax-case s (inherit #,@fields)
+                ((_ (inherit orig-record) (field value) (... ...))
+                 #`(letrec* #,(field-bindings #'((field value) (... ...)))
+                     #,(record-inheritance #'orig-record
+                                           #'((field value) (... ...)))))
+                ((_ (field value) (... ...))
+                 (let ((fields (map syntax->datum #'(field (... ...))))
+                       (dflt   (map (match-lambda
+                                     ((f v)
+                                      (list (syntax->datum f) v)))
+                                    #'defaults)))
+
+                   (define (field-value f)
+                     (or (and=> (find (lambda (x)
+                                        (eq? f (car (syntax->datum x))))
+                                      #'((field value) (... ...)))
+                                car)
+                         (let ((value
+                                (car (assoc-ref dflt
+                                                (syntax->datum f)))))
+                           (if (thunked-field? f)
+                               #`(lambda () #,value)
+                               value))))
+
+                   (let-syntax ((error*
+                                 (syntax-rules ()
+                                   ((_ fmt args (... ...))
+                                    (syntax-violation 'name
+                                                      (format #f fmt args
+                                                              (... ...))
+                                                      s)))))
+                     (let ((fields (append fields (map car dflt))))
+                       (cond ((lset= eq? fields 'expected)
+                              #`(letrec* #,(field-bindings
+                                            #'((field value) (... ...)))
+                                  (ctor #,@(map field-value 'expected))))
+                             ((pair? (lset-difference eq? fields 'expected))
+                              (error* "extraneous field initializers ~a"
+                                      (lset-difference eq? fields 'expected)))
+                             (else
+                              (error* "missing field initializers ~a"
+                                      (lset-difference eq? 'expected
+                                                       fields)))))))))))))
+
+    (define (field-default-value s)
+      (syntax-case s (default)
+        ((field (default val) _ ...)
+         (list #'field #'val))
+        ((field _ options ...)
+         (field-default-value #'(field options ...)))
+        (_ #f)))
+
+    (define (thunked-field? s)
+      ;; Return the field name if the field defined by S is thunked.
+      (syntax-case s (thunked)
+        ((field (thunked) _ ...)
+         #'field)
+        ((field _ options ...)
+         (thunked-field? #'(field options ...)))
+        (_ #f)))
+
+    (define (thunked-field-accessor-name field)
+      ;; Return the name (an unhygienic syntax object) of the "real"
+      ;; getter for field, which is assumed to be a thunked field.
+      (syntax-case field ()
+        ((field get options ...)
+         (let* ((getter      (syntax->datum #'get))
+                (real-getter (symbol-append '% getter '-real)))
+           (datum->syntax #'get real-getter)))))
+
+    (define (field-spec->srfi-9 field)
+      ;; Convert a field spec of our style to a SRFI-9 field spec of the
+      ;; form (field get).
+      (syntax-case field ()
+        ((name get options ...)
+         #`(name
+            #,(if (thunked-field? field)
+                  (thunked-field-accessor-name field)
+                  #'get)))))
+
+    (define (thunked-field-accessor-definition field)
+      ;; Return the real accessor for FIELD, which is assumed to be a
+      ;; thunked field.
+      (syntax-case field ()
+        ((name get _ ...)
+         (with-syntax ((real-get (thunked-field-accessor-name field)))
+           #'(define-inlinable (get x)
+               ;; The real value of that field is a thunk, so call it.
+               ((real-get x)))))))
+
+    (syntax-case s ()
+      ((_ type syntactic-ctor ctor pred
+          (field get options ...) ...)
+       (let* ((field-spec #'((field get options ...) ...)))
+         (with-syntax (((field-spec* ...)
+                        (map field-spec->srfi-9 field-spec))
+                       ((thunked-field-accessor ...)
+                        (filter-map (lambda (field)
+                                      (and (thunked-field? field)
+                                           (thunked-field-accessor-definition
+                                            field)))
+                                    field-spec)))
+           #`(begin
+               (define-record-type type
+                 (ctor field ...)
+                 pred
+                 field-spec* ...)
+               (begin thunked-field-accessor ...)
+               #,(make-syntactic-constructor #'type #'syntactic-ctor #'ctor
+                                             #'(field ...)
+                                             (filter-map thunked-field? field-spec)
+                                             (filter-map field-default-value
+                                                         #'((field options ...)
+                                                            ...))))))))))
+
+(define (alist->record alist make keys)
+  "Apply MAKE to the values associated with KEYS in ALIST."
+  (let ((args (map (cut assoc-ref alist <>) keys)))
+    (apply make args)))
+
+(define (object->fields object fields port)
+  "Write OBJECT (typically a record) as a series of recutils-style fields to
+PORT, according to FIELDS.  FIELDS must be a list of field name/getter pairs."
+  (let loop ((fields fields))
+    (match fields
+      (()
+       object)
+      (((field . get) rest ...)
+       (format port "~a: ~a~%" field (get object))
+       (loop rest)))))
+
+;;; records.scm ends here