records.scm 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Ludovic Courtès <ludo@gnu.org>
  3. ;;; Copyright © 2018 Mark H Weaver <mhw@netris.org>
  4. ;;;
  5. ;;; This file is part of GNU Guix.
  6. ;;;
  7. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  8. ;;; under the terms of the GNU General Public License as published by
  9. ;;; the Free Software Foundation; either version 3 of the License, or (at
  10. ;;; your option) any later version.
  11. ;;;
  12. ;;; GNU Guix is distributed in the hope that it will be useful, but
  13. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  14. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. ;;; GNU General Public License for more details.
  16. ;;;
  17. ;;; You should have received a copy of the GNU General Public License
  18. ;;; 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. #:autoload (system base target) (target-most-positive-fixnum)
  27. #:export (define-record-type*
  28. this-record
  29. alist->record
  30. object->fields
  31. recutils->alist
  32. match-record))
  33. ;;; Commentary:
  34. ;;;
  35. ;;; Utilities for dealing with Scheme records.
  36. ;;;
  37. ;;; Code:
  38. (define-syntax record-error
  39. (syntax-rules ()
  40. "Report a syntactic error in use of CONSTRUCTOR."
  41. ((_ constructor form fmt args ...)
  42. (syntax-violation constructor
  43. (format #f fmt args ...)
  44. form))))
  45. (eval-when (expand load eval)
  46. ;; The procedures below are needed both at run time and at expansion time.
  47. (define (current-abi-identifier type)
  48. "Return an identifier unhygienically derived from TYPE for use as its
  49. \"current ABI\" variable."
  50. (let ((type-name (syntax->datum type)))
  51. (datum->syntax
  52. type
  53. (string->symbol
  54. (string-append "% " (symbol->string type-name)
  55. " abi-cookie")))))
  56. (define (abi-check type cookie)
  57. "Return syntax that checks that the current \"application binary
  58. interface\" (ABI) for TYPE is equal to COOKIE."
  59. (with-syntax ((current-abi (current-abi-identifier type)))
  60. #`(unless (eq? current-abi #,cookie)
  61. ;; The source file where this exception is thrown must be
  62. ;; recompiled.
  63. (throw 'record-abi-mismatch-error 'abi-check
  64. "~a: record ABI mismatch; recompilation needed"
  65. (list #,type) '()))))
  66. (define* (report-invalid-field-specifier name bindings
  67. #:optional parent-form)
  68. "Report the first invalid binding among BINDINGS. PARENT-FORM is used for
  69. error-reporting purposes."
  70. (let loop ((bindings bindings))
  71. (syntax-case bindings ()
  72. (((field value) rest ...) ;good
  73. (loop #'(rest ...)))
  74. ((weird _ ...) ;weird!
  75. ;; WEIRD may be an identifier, thus lacking source location info, and
  76. ;; BINDINGS is a list, also lacking source location info. Hopefully
  77. ;; PARENT-FORM provides source location info.
  78. (apply syntax-violation name "invalid field specifier"
  79. (if parent-form
  80. (list parent-form #'weird)
  81. (list #'weird)))))))
  82. (define (report-duplicate-field-specifier name ctor)
  83. "Report the first duplicate identifier among the bindings in CTOR."
  84. (syntax-case ctor ()
  85. ((_ bindings ...)
  86. (let loop ((bindings #'(bindings ...))
  87. (seen '()))
  88. (syntax-case bindings ()
  89. (((field value) rest ...)
  90. (not (memq (syntax->datum #'field) seen))
  91. (loop #'(rest ...) (cons (syntax->datum #'field) seen)))
  92. ((duplicate rest ...)
  93. (syntax-violation name "duplicate field initializer"
  94. #'duplicate))
  95. (()
  96. #t)))))))
  97. (define-syntax-parameter this-record
  98. (lambda (s)
  99. "Return the record being defined. This macro may only be used in the
  100. context of the definition of a thunked field."
  101. (syntax-case s ()
  102. (id
  103. (identifier? #'id)
  104. (syntax-violation 'this-record
  105. "cannot be used outside of a record instantiation"
  106. #'id)))))
  107. (define-syntax make-syntactic-constructor
  108. (syntax-rules ()
  109. "Make the syntactic constructor NAME for TYPE, that calls CTOR, and
  110. expects all of EXPECTED fields to be initialized. DEFAULTS is the list of
  111. FIELD/DEFAULT-VALUE tuples, THUNKED is the list of identifiers of thunked
  112. fields, and DELAYED is the list of identifiers of delayed fields.
  113. ABI-COOKIE is the cookie (an integer) against which to check the run-time ABI
  114. of TYPE matches the expansion-time ABI."
  115. ((_ type name ctor (expected ...)
  116. #:abi-cookie abi-cookie
  117. #:thunked thunked
  118. #:this-identifier this-identifier
  119. #:delayed delayed
  120. #:innate innate
  121. #:defaults defaults)
  122. (define-syntax name
  123. (lambda (s)
  124. (define (record-inheritance orig-record field+value)
  125. ;; Produce code that returns a record identical to ORIG-RECORD,
  126. ;; except that values for the FIELD+VALUE alist prevail.
  127. (define (field-inherited-value f)
  128. (and=> (find (lambda (x)
  129. (eq? f (car (syntax->datum x))))
  130. field+value)
  131. car))
  132. ;; Make sure there are no unknown field names.
  133. (let* ((fields (map (compose car syntax->datum) field+value))
  134. (unexpected (lset-difference eq? fields '(expected ...))))
  135. (when (pair? unexpected)
  136. (record-error 'name s "extraneous field initializers ~a"
  137. unexpected)))
  138. #`(make-struct/no-tail type
  139. #,@(map (lambda (field index)
  140. (or (field-inherited-value field)
  141. (if (innate-field? field)
  142. (wrap-field-value
  143. field (field-default-value field))
  144. #`(struct-ref #,orig-record
  145. #,index))))
  146. '(expected ...)
  147. (iota (length '(expected ...))))))
  148. (define (thunked-field? f)
  149. (memq (syntax->datum f) 'thunked))
  150. (define (delayed-field? f)
  151. (memq (syntax->datum f) 'delayed))
  152. (define (innate-field? f)
  153. (memq (syntax->datum f) 'innate))
  154. (define (wrap-field-value f value)
  155. (cond ((thunked-field? f)
  156. #`(lambda (x)
  157. (syntax-parameterize ((#,this-identifier
  158. (lambda (s)
  159. (syntax-case s ()
  160. (id
  161. (identifier? #'id)
  162. #'x)))))
  163. #,value)))
  164. ((delayed-field? f)
  165. #`(delay #,value))
  166. (else value)))
  167. (define default-values
  168. ;; List of symbol/value tuples.
  169. (map (match-lambda
  170. ((f v)
  171. (list (syntax->datum f) v)))
  172. #'defaults))
  173. (define (field-default-value f)
  174. (car (assoc-ref default-values (syntax->datum f))))
  175. (define (field-bindings field+value)
  176. ;; Return field to value bindings, for use in 'let*' below.
  177. (map (lambda (field+value)
  178. (syntax-case field+value ()
  179. ((field value)
  180. #`(field
  181. #,(wrap-field-value #'field #'value)))))
  182. field+value))
  183. (syntax-case s (inherit expected ...)
  184. ((_ (inherit orig-record) (field value) (... ...))
  185. #`(let* #,(field-bindings #'((field value) (... ...)))
  186. #,(abi-check #'type abi-cookie)
  187. #,(record-inheritance #'orig-record
  188. #'((field value) (... ...)))))
  189. ((_ (field value) (... ...))
  190. (let ((fields (map syntax->datum #'(field (... ...)))))
  191. (define (field-value f)
  192. (or (find (lambda (x)
  193. (eq? f (syntax->datum x)))
  194. #'(field (... ...)))
  195. (wrap-field-value f (field-default-value f))))
  196. ;; Pass S to make sure source location info is preserved.
  197. (report-duplicate-field-specifier 'name s)
  198. (let ((fields (append fields (map car default-values))))
  199. (cond ((lset= eq? fields '(expected ...))
  200. #`(let* #,(field-bindings
  201. #'((field value) (... ...)))
  202. #,(abi-check #'type abi-cookie)
  203. (ctor #,@(map field-value '(expected ...)))))
  204. ((pair? (lset-difference eq? fields
  205. '(expected ...)))
  206. (record-error 'name s
  207. "extraneous field initializers ~a"
  208. (lset-difference eq? fields
  209. '(expected ...))))
  210. (else
  211. (record-error 'name s
  212. "missing field initializers ~a"
  213. (lset-difference eq?
  214. '(expected ...)
  215. fields)))))))
  216. ((_ bindings (... ...))
  217. ;; One of BINDINGS doesn't match the (field value) pattern.
  218. ;; Report precisely which one is faulty, instead of letting the
  219. ;; "source expression failed to match any pattern" error.
  220. (report-invalid-field-specifier 'name
  221. #'(bindings (... ...))
  222. s))))))))
  223. (define-syntax-rule (define-field-property-predicate predicate property)
  224. "Define PREDICATE as a procedure that takes a syntax object and, when passed
  225. a field specification, returns the field name if it has the given PROPERTY."
  226. (define (predicate s)
  227. (syntax-case s (property)
  228. ((field (property values (... ...)) _ (... ...))
  229. #'field)
  230. ((field _ properties (... ...))
  231. (predicate #'(field properties (... ...))))
  232. (_ #f))))
  233. (define-syntax define-record-type*
  234. (lambda (s)
  235. "Define the given record type such that an additional \"syntactic
  236. constructor\" is defined, which allows instances to be constructed with named
  237. field initializers, à la SRFI-35, as well as default values. An example use
  238. may look like this:
  239. (define-record-type* <thing> thing make-thing
  240. thing?
  241. this-thing
  242. (name thing-name (default \"chbouib\"))
  243. (port thing-port
  244. (default (current-output-port)) (thunked))
  245. (loc thing-location (innate) (default (current-source-location))))
  246. This example defines a macro 'thing' that can be used to instantiate records
  247. of this type:
  248. (thing
  249. (name \"foo\")
  250. (port (current-error-port)))
  251. The value of 'name' or 'port' could as well be omitted, in which case the
  252. default value specified in the 'define-record-type*' form is used:
  253. (thing)
  254. The 'port' field is \"thunked\", meaning that calls like '(thing-port x)' will
  255. actually compute the field's value in the current dynamic extent, which is
  256. useful when referring to fluids in a field's value. Furthermore, that thunk
  257. can access the record it belongs to via the 'this-thing' identifier.
  258. A field can also be marked as \"delayed\" instead of \"thunked\", in which
  259. case its value is effectively wrapped in a (delay …) form.
  260. It is possible to copy an object 'x' created with 'thing' like this:
  261. (thing (inherit x) (name \"bar\"))
  262. This expression returns a new object equal to 'x' except for its 'name'
  263. field and its 'loc' field---the latter is marked as \"innate\", so it is not
  264. inherited."
  265. (define (field-default-value s)
  266. (syntax-case s (default)
  267. ((field (default val) _ ...)
  268. (list #'field #'val))
  269. ((field _ properties ...)
  270. (field-default-value #'(field properties ...)))
  271. (_ #f)))
  272. (define-field-property-predicate delayed-field? delayed)
  273. (define-field-property-predicate thunked-field? thunked)
  274. (define-field-property-predicate innate-field? innate)
  275. (define (wrapped-field? s)
  276. (or (thunked-field? s) (delayed-field? s)))
  277. (define (wrapped-field-accessor-name field)
  278. ;; Return the name (an unhygienic syntax object) of the "real"
  279. ;; getter for field, which is assumed to be a wrapped field.
  280. (syntax-case field ()
  281. ((field get properties ...)
  282. (let* ((getter (syntax->datum #'get))
  283. (real-getter (symbol-append '% getter '-real)))
  284. (datum->syntax #'get real-getter)))))
  285. (define (field-spec->srfi-9 field)
  286. ;; Convert a field spec of our style to a SRFI-9 field spec of the
  287. ;; form (field get).
  288. (syntax-case field ()
  289. ((name get properties ...)
  290. #`(name
  291. #,(if (wrapped-field? field)
  292. (wrapped-field-accessor-name field)
  293. #'get)))))
  294. (define (thunked-field-accessor-definition field)
  295. ;; Return the real accessor for FIELD, which is assumed to be a
  296. ;; thunked field.
  297. (syntax-case field ()
  298. ((name get _ ...)
  299. (with-syntax ((real-get (wrapped-field-accessor-name field)))
  300. #'(define-inlinable (get x)
  301. ;; The real value of that field is a thunk, so call it.
  302. ((real-get x) x))))))
  303. (define (delayed-field-accessor-definition field)
  304. ;; Return the real accessor for FIELD, which is assumed to be a
  305. ;; delayed field.
  306. (syntax-case field ()
  307. ((name get _ ...)
  308. (with-syntax ((real-get (wrapped-field-accessor-name field)))
  309. #'(define-inlinable (get x)
  310. ;; The real value of that field is a promise, so force it.
  311. (force (real-get x)))))))
  312. (define (compute-abi-cookie field-specs)
  313. ;; Compute an "ABI cookie" for the given FIELD-SPECS. We use
  314. ;; 'string-hash' because that's a better hash function that 'hash' on a
  315. ;; list of symbols.
  316. (syntax-case field-specs ()
  317. (((field get properties ...) ...)
  318. (string-hash (object->string
  319. (syntax->datum #'((field properties ...) ...)))
  320. (cond-expand
  321. (guile-3 (target-most-positive-fixnum))
  322. (else most-positive-fixnum))))))
  323. (syntax-case s ()
  324. ((_ type syntactic-ctor ctor pred
  325. this-identifier
  326. (field get properties ...) ...)
  327. (identifier? #'this-identifier)
  328. (let* ((field-spec #'((field get properties ...) ...))
  329. (thunked (filter-map thunked-field? field-spec))
  330. (delayed (filter-map delayed-field? field-spec))
  331. (innate (filter-map innate-field? field-spec))
  332. (defaults (filter-map field-default-value
  333. #'((field properties ...) ...)))
  334. (cookie (compute-abi-cookie field-spec)))
  335. (with-syntax (((field-spec* ...)
  336. (map field-spec->srfi-9 field-spec))
  337. ((thunked-field-accessor ...)
  338. (filter-map (lambda (field)
  339. (and (thunked-field? field)
  340. (thunked-field-accessor-definition
  341. field)))
  342. field-spec))
  343. ((delayed-field-accessor ...)
  344. (filter-map (lambda (field)
  345. (and (delayed-field? field)
  346. (delayed-field-accessor-definition
  347. field)))
  348. field-spec)))
  349. #`(begin
  350. (define-record-type type
  351. (ctor field ...)
  352. pred
  353. field-spec* ...)
  354. (define #,(current-abi-identifier #'type)
  355. #,cookie)
  356. #,@(if (free-identifier=? #'this-identifier #'this-record)
  357. #'()
  358. #'((define-syntax-parameter this-identifier
  359. (lambda (s)
  360. "Return the record being defined. This macro may
  361. only be used in the context of the definition of a thunked field."
  362. (syntax-case s ()
  363. (id
  364. (identifier? #'id)
  365. (syntax-violation 'this-identifier
  366. "cannot be used outside \
  367. of a record instantiation"
  368. #'id)))))))
  369. thunked-field-accessor ...
  370. delayed-field-accessor ...
  371. (make-syntactic-constructor type syntactic-ctor ctor
  372. (field ...)
  373. #:abi-cookie #,cookie
  374. #:thunked #,thunked
  375. #:this-identifier #'this-identifier
  376. #:delayed #,delayed
  377. #:innate #,innate
  378. #:defaults #,defaults)))))
  379. ((_ type syntactic-ctor ctor pred
  380. (field get properties ...) ...)
  381. ;; When no 'this' identifier was specified, use 'this-record'.
  382. #'(define-record-type* type syntactic-ctor ctor pred
  383. this-record
  384. (field get properties ...) ...)))))
  385. (define* (alist->record alist make keys
  386. #:optional (multiple-value-keys '()))
  387. "Apply MAKE to the values associated with KEYS in ALIST. Items in KEYS that
  388. are also in MULTIPLE-VALUE-KEYS are considered to occur possibly multiple
  389. times in ALIST, and thus their value is a list."
  390. (let ((args (map (lambda (key)
  391. (if (member key multiple-value-keys)
  392. (filter-map (match-lambda
  393. ((k . v)
  394. (and (equal? k key) v)))
  395. alist)
  396. (assoc-ref alist key)))
  397. keys)))
  398. (apply make args)))
  399. (define (object->fields object fields port)
  400. "Write OBJECT (typically a record) as a series of recutils-style fields to
  401. PORT, according to FIELDS. FIELDS must be a list of field name/getter pairs."
  402. (let loop ((fields fields))
  403. (match fields
  404. (()
  405. object)
  406. (((field . get) rest ...)
  407. (format port "~a: ~a~%" field (get object))
  408. (loop rest)))))
  409. (define %recutils-field-charset
  410. ;; Valid characters starting a recutils field.
  411. ;; info "(recutils) Fields"
  412. (char-set-union char-set:upper-case
  413. char-set:lower-case
  414. (char-set #\%)))
  415. (define (recutils->alist port)
  416. "Read a recutils-style record from PORT and return it as a list of key/value
  417. pairs. Stop upon an empty line (after consuming it) or EOF."
  418. (let loop ((line (read-line port))
  419. (result '()))
  420. (cond ((eof-object? line)
  421. (reverse result))
  422. ((string-null? line)
  423. (if (null? result)
  424. (loop (read-line port) result) ; leading space: ignore it
  425. (reverse result))) ; end-of-record marker
  426. (else
  427. ;; Now check the first character of LINE, since that's what the
  428. ;; recutils manual says is enough.
  429. (let ((first (string-ref line 0)))
  430. (cond
  431. ((char-set-contains? %recutils-field-charset first)
  432. (let* ((colon (string-index line #\:))
  433. (field (string-take line colon))
  434. (value (string-trim (string-drop line (+ 1 colon)))))
  435. (loop (read-line port)
  436. (alist-cons field value result))))
  437. ((eqv? first #\#) ;info "(recutils) Comments"
  438. (loop (read-line port) result))
  439. ((eqv? first #\+) ;info "(recutils) Fields"
  440. (let ((new-line (if (string-prefix? "+ " line)
  441. (string-drop line 2)
  442. (string-drop line 1))))
  443. (match result
  444. (((field . value) rest ...)
  445. (loop (read-line port)
  446. `((,field . ,(string-append value "\n" new-line))
  447. ,@rest))))))
  448. (else
  449. (error "unmatched line" line))))))))
  450. (define-syntax match-record
  451. (syntax-rules ()
  452. "Bind each FIELD of a RECORD of the given TYPE to it's FIELD name.
  453. The current implementation does not support thunked and delayed fields."
  454. ((_ record type (field fields ...) body ...)
  455. (if (eq? (struct-vtable record) type)
  456. ;; TODO compute indices and report wrong-field-name errors at
  457. ;; expansion time
  458. ;; TODO support thunked and delayed fields
  459. (let ((field ((record-accessor type 'field) record)))
  460. (match-record record type (fields ...) body ...))
  461. (throw 'wrong-type-arg record)))
  462. ((_ record type () body ...)
  463. (begin body ...))))
  464. ;;; records.scm ends here