records.scm 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2012-2023 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-11)
  23. #:use-module (srfi srfi-26)
  24. #:use-module (ice-9 match)
  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. match-record-lambda))
  34. ;;; Commentary:
  35. ;;;
  36. ;;; Utilities for dealing with Scheme records.
  37. ;;;
  38. ;;; Code:
  39. (define-syntax record-error
  40. (syntax-rules ()
  41. "Report a syntactic error in use of CONSTRUCTOR."
  42. ((_ constructor form fmt args ...)
  43. (syntax-violation constructor
  44. (format #f fmt args ...)
  45. form))))
  46. (eval-when (expand load eval)
  47. ;; The procedures below are needed both at run time and at expansion time.
  48. (define (current-abi-identifier type)
  49. "Return an identifier unhygienically derived from TYPE for use as its
  50. \"current ABI\" variable."
  51. (let ((type-name (syntax->datum type)))
  52. (datum->syntax
  53. type
  54. (string->symbol
  55. (string-append "% " (symbol->string type-name)
  56. " abi-cookie")))))
  57. (define (abi-check type cookie)
  58. "Return syntax that checks that the current \"application binary
  59. interface\" (ABI) for TYPE is equal to COOKIE."
  60. (with-syntax ((current-abi (current-abi-identifier type)))
  61. #`(unless (eq? current-abi #,cookie)
  62. ;; The source file where this exception is thrown must be
  63. ;; recompiled.
  64. (throw 'record-abi-mismatch-error 'abi-check
  65. "~a: record ABI mismatch; recompilation needed"
  66. (list #,type) '()))))
  67. (define* (report-invalid-field-specifier name bindings
  68. #:optional parent-form)
  69. "Report the first invalid binding among BINDINGS. PARENT-FORM is used for
  70. error-reporting purposes."
  71. (let loop ((bindings bindings))
  72. (syntax-case bindings ()
  73. (((field value) rest ...) ;good
  74. (loop #'(rest ...)))
  75. ((weird _ ...) ;weird!
  76. ;; WEIRD may be an identifier, thus lacking source location info, and
  77. ;; BINDINGS is a list, also lacking source location info. Hopefully
  78. ;; PARENT-FORM provides source location info.
  79. (apply syntax-violation name "invalid field specifier"
  80. (if parent-form
  81. (list parent-form #'weird)
  82. (list #'weird)))))))
  83. (define (report-duplicate-field-specifier name ctor)
  84. "Report the first duplicate identifier among the bindings in CTOR."
  85. (syntax-case ctor ()
  86. ((_ bindings ...)
  87. (let loop ((bindings #'(bindings ...))
  88. (seen '()))
  89. (syntax-case bindings ()
  90. (((field value) rest ...)
  91. (not (memq (syntax->datum #'field) seen))
  92. (loop #'(rest ...) (cons (syntax->datum #'field) seen)))
  93. ((duplicate rest ...)
  94. (syntax-violation name "duplicate field initializer"
  95. #'duplicate))
  96. (()
  97. #t)))))))
  98. (define-syntax map-fields
  99. (lambda (x)
  100. (syntax-case x ()
  101. ((_ type within)
  102. (syntax-violation (syntax->datum #'within)
  103. "undefined record type"
  104. #'type))
  105. (_ (syntax-violation 'map-fields "bad use of syntactic keyword" x x)))))
  106. (define-syntax-parameter this-record
  107. (lambda (s)
  108. "Return the record being defined. This macro may only be used in the
  109. context of the definition of a thunked field."
  110. (syntax-case s ()
  111. (id
  112. (identifier? #'id)
  113. (syntax-violation 'this-record
  114. "cannot be used outside of a record instantiation"
  115. #'id)))))
  116. (define-syntax make-syntactic-constructor
  117. (syntax-rules ()
  118. "Make the syntactic constructor NAME for TYPE, that calls CTOR, and
  119. expects all of EXPECTED fields to be initialized. DEFAULTS is the list of
  120. FIELD/DEFAULT-VALUE tuples, THUNKED is the list of identifiers of thunked
  121. fields, DELAYED is the list of identifiers of delayed fields, and SANITIZERS
  122. is the list of FIELD/SANITIZER tuples.
  123. ABI-COOKIE is the cookie (an integer) against which to check the run-time ABI
  124. of TYPE matches the expansion-time ABI."
  125. ((_ type name ctor (expected ...)
  126. #:abi-cookie abi-cookie
  127. #:thunked thunked
  128. #:this-identifier this-identifier
  129. #:delayed delayed
  130. #:innate innate
  131. #:sanitizers sanitizers
  132. #:defaults defaults)
  133. (define-syntax name
  134. (lambda (s)
  135. (define (record-inheritance orig-record field+value)
  136. ;; Produce code that returns a record identical to ORIG-RECORD,
  137. ;; except that values for the FIELD+VALUE alist prevail.
  138. (define (field-inherited-value f)
  139. (and=> (find (lambda (x)
  140. (eq? f (car (syntax->datum x))))
  141. field+value)
  142. car))
  143. ;; Make sure there are no unknown field names.
  144. (let* ((fields (map (compose car syntax->datum) field+value))
  145. (unexpected (lset-difference eq? fields '(expected ...))))
  146. (when (pair? unexpected)
  147. (record-error 'name s "extraneous field initializers ~a"
  148. unexpected)))
  149. #`(make-struct/no-tail type
  150. #,@(map (lambda (field index)
  151. (or (field-inherited-value field)
  152. (if (innate-field? field)
  153. (wrap-field-value
  154. field (field-default-value field))
  155. #`(struct-ref #,orig-record
  156. #,index))))
  157. '(expected ...)
  158. (iota (length '(expected ...))))))
  159. (define (thunked-field? f)
  160. (memq (syntax->datum f) 'thunked))
  161. (define (delayed-field? f)
  162. (memq (syntax->datum f) 'delayed))
  163. (define (innate-field? f)
  164. (memq (syntax->datum f) 'innate))
  165. (define field-sanitizer
  166. (let ((lst (map (match-lambda
  167. ((f p)
  168. (list (syntax->datum f) p)))
  169. #'sanitizers)))
  170. (lambda (f)
  171. (or (and=> (assoc-ref lst (syntax->datum f)) car)
  172. #'(lambda (x) x)))))
  173. (define (wrap-field-value f value)
  174. (let* ((sanitizer (field-sanitizer f))
  175. (value #`(#,sanitizer #,value)))
  176. (cond ((thunked-field? f)
  177. #`(lambda (x)
  178. (syntax-parameterize ((#,this-identifier
  179. (lambda (s)
  180. (syntax-case s ()
  181. (id
  182. (identifier? #'id)
  183. #'x)))))
  184. #,value)))
  185. ((delayed-field? f)
  186. #`(delay #,value))
  187. (else value))))
  188. (define default-values
  189. ;; List of symbol/value tuples.
  190. (map (match-lambda
  191. ((f v)
  192. (list (syntax->datum f) v)))
  193. #'defaults))
  194. (define (field-default-value f)
  195. (car (assoc-ref default-values (syntax->datum f))))
  196. (define (field-bindings field+value)
  197. ;; Return field to value bindings, for use in 'let*' below.
  198. (map (lambda (field+value)
  199. (syntax-case field+value ()
  200. ((field value)
  201. #`(field
  202. #,(wrap-field-value #'field #'value)))))
  203. field+value))
  204. (syntax-case s (inherit expected ...)
  205. ((_ (inherit orig-record) (field value) (... ...))
  206. #`(let* #,(field-bindings #'((field value) (... ...)))
  207. #,(abi-check #'type abi-cookie)
  208. #,(record-inheritance #'orig-record
  209. #'((field value) (... ...)))))
  210. ((_ (field value) (... ...))
  211. (let ((fields (map syntax->datum #'(field (... ...)))))
  212. (define (field-value f)
  213. (or (find (lambda (x)
  214. (eq? f (syntax->datum x)))
  215. #'(field (... ...)))
  216. (wrap-field-value f (field-default-value f))))
  217. ;; Pass S to make sure source location info is preserved.
  218. (report-duplicate-field-specifier 'name s)
  219. (let ((fields (append fields (map car default-values))))
  220. (cond ((lset= eq? fields '(expected ...))
  221. #`(let* #,(field-bindings
  222. #'((field value) (... ...)))
  223. #,(abi-check #'type abi-cookie)
  224. (ctor #,@(map field-value '(expected ...)))))
  225. ((pair? (lset-difference eq? fields
  226. '(expected ...)))
  227. (record-error 'name s
  228. "extraneous field initializers ~a"
  229. (lset-difference eq? fields
  230. '(expected ...))))
  231. (else
  232. (record-error 'name s
  233. "missing field initializers ~a"
  234. (lset-difference eq?
  235. '(expected ...)
  236. fields)))))))
  237. ((_ bindings (... ...))
  238. ;; One of BINDINGS doesn't match the (field value) pattern.
  239. ;; Report precisely which one is faulty, instead of letting the
  240. ;; "source expression failed to match any pattern" error.
  241. (report-invalid-field-specifier 'name
  242. #'(bindings (... ...))
  243. s))))))))
  244. (define-syntax-rule (define-field-property-predicate predicate property)
  245. "Define PREDICATE as a procedure that takes a syntax object and, when passed
  246. a field specification, returns the field name if it has the given PROPERTY."
  247. (define (predicate s)
  248. (syntax-case s (property)
  249. ((field (property values (... ...)) _ (... ...))
  250. #'field)
  251. ((field _ properties (... ...))
  252. (predicate #'(field properties (... ...))))
  253. (_ #f))))
  254. (define-syntax define-record-type*
  255. (lambda (s)
  256. "Define the given record type such that an additional \"syntactic
  257. constructor\" is defined, which allows instances to be constructed with named
  258. field initializers, à la SRFI-35, as well as default values. An example use
  259. may look like this:
  260. (define-record-type* <thing> thing make-thing
  261. thing?
  262. this-thing
  263. (name thing-name (default \"chbouib\"))
  264. (port thing-port
  265. (default (current-output-port)) (thunked))
  266. (loc thing-location (innate) (default (current-source-location))))
  267. This example defines a macro 'thing' that can be used to instantiate records
  268. of this type:
  269. (thing
  270. (name \"foo\")
  271. (port (current-error-port)))
  272. The value of 'name' or 'port' could as well be omitted, in which case the
  273. default value specified in the 'define-record-type*' form is used:
  274. (thing)
  275. The 'port' field is \"thunked\", meaning that calls like '(thing-port x)' will
  276. actually compute the field's value in the current dynamic extent, which is
  277. useful when referring to fluids in a field's value. Furthermore, that thunk
  278. can access the record it belongs to via the 'this-thing' identifier.
  279. A field can also be marked as \"delayed\" instead of \"thunked\", in which
  280. case its value is effectively wrapped in a (delay …) form.
  281. A field can also have an associated \"sanitizer\", which is a procedure that
  282. takes a user-supplied field value and returns a \"sanitized\" value for the
  283. field:
  284. (define-record-type* <thing> thing make-thing
  285. thing?
  286. this-thing
  287. (name thing-name
  288. (sanitize (lambda (value)
  289. (cond ((string? value) value)
  290. ((symbol? value) (symbol->string value))
  291. (else (throw 'bad! value)))))))
  292. It is possible to copy an object 'x' created with 'thing' like this:
  293. (thing (inherit x) (name \"bar\"))
  294. This expression returns a new object equal to 'x' except for its 'name'
  295. field and its 'loc' field---the latter is marked as \"innate\", so it is not
  296. inherited."
  297. (define (rtd-identifier type)
  298. ;; Return an identifier derived from TYPE to name its record type
  299. ;; descriptor (RTD).
  300. (let ((type-name (syntax->datum type)))
  301. (datum->syntax
  302. type
  303. (string->symbol
  304. (string-append "% " (symbol->string type-name) " rtd")))))
  305. (define (field-default-value s)
  306. (syntax-case s (default)
  307. ((field (default val) _ ...)
  308. (list #'field #'val))
  309. ((field _ properties ...)
  310. (field-default-value #'(field properties ...)))
  311. (_ #f)))
  312. (define (field-sanitizer s)
  313. (syntax-case s (sanitize)
  314. ((field (sanitize proc) _ ...)
  315. (list #'field #'proc))
  316. ((field _ properties ...)
  317. (field-sanitizer #'(field properties ...)))
  318. (_ #f)))
  319. (define-field-property-predicate delayed-field? delayed)
  320. (define-field-property-predicate thunked-field? thunked)
  321. (define-field-property-predicate innate-field? innate)
  322. (define (wrapped-field? s)
  323. (or (thunked-field? s) (delayed-field? s)))
  324. (define (wrapped-field-accessor-name field)
  325. ;; Return the name (an unhygienic syntax object) of the "real"
  326. ;; getter for field, which is assumed to be a wrapped field.
  327. (syntax-case field ()
  328. ((field get properties ...)
  329. (let* ((getter (syntax->datum #'get))
  330. (real-getter (symbol-append '% getter '-real)))
  331. (datum->syntax #'get real-getter)))))
  332. (define (field-spec->srfi-9 field)
  333. ;; Convert a field spec of our style to a SRFI-9 field spec of the
  334. ;; form (field get).
  335. (syntax-case field ()
  336. ((name get properties ...)
  337. #`(name
  338. #,(if (wrapped-field? field)
  339. (wrapped-field-accessor-name field)
  340. #'get)))))
  341. (define (thunked-field-accessor-definition field)
  342. ;; Return the real accessor for FIELD, which is assumed to be a
  343. ;; thunked field.
  344. (syntax-case field ()
  345. ((name get _ ...)
  346. (with-syntax ((real-get (wrapped-field-accessor-name field)))
  347. #'(define-inlinable (get x)
  348. ;; The real value of that field is a thunk, so call it.
  349. ((real-get x) x))))))
  350. (define (delayed-field-accessor-definition field)
  351. ;; Return the real accessor for FIELD, which is assumed to be a
  352. ;; delayed field.
  353. (syntax-case field ()
  354. ((name get _ ...)
  355. (with-syntax ((real-get (wrapped-field-accessor-name field)))
  356. #'(define-inlinable (get x)
  357. ;; The real value of that field is a promise, so force it.
  358. (force (real-get x)))))))
  359. (define (compute-abi-cookie field-specs)
  360. ;; Compute an "ABI cookie" for the given FIELD-SPECS. We use
  361. ;; 'string-hash' because that's a better hash function that 'hash' on a
  362. ;; list of symbols.
  363. (syntax-case field-specs ()
  364. (((field get properties ...) ...)
  365. (string-hash (object->string
  366. (syntax->datum #'((field properties ...) ...)))
  367. (cond-expand
  368. (guile-3 (target-most-positive-fixnum))
  369. (else most-positive-fixnum))))))
  370. (syntax-case s ()
  371. ((_ type syntactic-ctor ctor pred
  372. this-identifier
  373. (field get properties ...) ...)
  374. (identifier? #'this-identifier)
  375. (let* ((field-spec #'((field get properties ...) ...))
  376. (thunked (filter-map thunked-field? field-spec))
  377. (delayed (filter-map delayed-field? field-spec))
  378. (innate (filter-map innate-field? field-spec))
  379. (defaults (filter-map field-default-value
  380. #'((field properties ...) ...)))
  381. (sanitizers (filter-map field-sanitizer
  382. #'((field properties ...) ...)))
  383. (cookie (compute-abi-cookie field-spec)))
  384. (with-syntax (((field-spec* ...)
  385. (map field-spec->srfi-9 field-spec))
  386. ((field-type ...)
  387. (map (match-lambda
  388. ((? thunked-field?)
  389. (datum->syntax s 'thunked))
  390. ((? delayed-field?)
  391. (datum->syntax s 'delayed))
  392. (else
  393. (datum->syntax s 'normal)))
  394. field-spec))
  395. ((thunked-field-accessor ...)
  396. (filter-map (lambda (field)
  397. (and (thunked-field? field)
  398. (thunked-field-accessor-definition
  399. field)))
  400. field-spec))
  401. ((delayed-field-accessor ...)
  402. (filter-map (lambda (field)
  403. (and (delayed-field? field)
  404. (delayed-field-accessor-definition
  405. field)))
  406. field-spec)))
  407. #`(begin
  408. (define-record-type #,(rtd-identifier #'type)
  409. (ctor field ...)
  410. pred
  411. field-spec* ...)
  412. ;; Rectify the vtable type name...
  413. (set-struct-vtable-name! #,(rtd-identifier #'type) 'type)
  414. (cond-expand
  415. (guile-3
  416. ;; ... and the record type name.
  417. (struct-set! #,(rtd-identifier #'type) vtable-offset-user
  418. 'type))
  419. (else #f))
  420. (define-syntax type
  421. (lambda (s)
  422. "This macro lets us query record type info at
  423. macro-expansion time."
  424. (syntax-case s (map-fields)
  425. ((_ (map-fields _ _) macro)
  426. #'(macro ((field field-type) ...)))
  427. (id
  428. (identifier? #'id)
  429. #'#,(rtd-identifier #'type)))))
  430. (define #,(current-abi-identifier #'type)
  431. #,cookie)
  432. #,@(if (free-identifier=? #'this-identifier #'this-record)
  433. #'()
  434. #'((define-syntax-parameter this-identifier
  435. (lambda (s)
  436. "Return the record being defined. This macro may
  437. only be used in the context of the definition of a thunked field."
  438. (syntax-case s ()
  439. (id
  440. (identifier? #'id)
  441. (syntax-violation 'this-identifier
  442. "cannot be used outside \
  443. of a record instantiation"
  444. #'id)))))))
  445. thunked-field-accessor ...
  446. delayed-field-accessor ...
  447. (make-syntactic-constructor type syntactic-ctor ctor
  448. (field ...)
  449. #:abi-cookie #,cookie
  450. #:thunked #,thunked
  451. #:this-identifier #'this-identifier
  452. #:delayed #,delayed
  453. #:innate #,innate
  454. #:sanitizers #,sanitizers
  455. #:defaults #,defaults)))))
  456. ((_ type syntactic-ctor ctor pred
  457. (field get properties ...) ...)
  458. ;; When no 'this' identifier was specified, use 'this-record'.
  459. #'(define-record-type* type syntactic-ctor ctor pred
  460. this-record
  461. (field get properties ...) ...)))))
  462. (define* (alist->record alist make keys
  463. #:optional (multiple-value-keys '()))
  464. "Apply MAKE to the values associated with KEYS in ALIST. Items in KEYS that
  465. are also in MULTIPLE-VALUE-KEYS are considered to occur possibly multiple
  466. times in ALIST, and thus their value is a list."
  467. (let ((args (map (lambda (key)
  468. (if (member key multiple-value-keys)
  469. (filter-map (match-lambda
  470. ((k . v)
  471. (and (equal? k key) v)))
  472. alist)
  473. (assoc-ref alist key)))
  474. keys)))
  475. (apply make args)))
  476. (define (object->fields object fields port)
  477. "Write OBJECT (typically a record) as a series of recutils-style fields to
  478. PORT, according to FIELDS. FIELDS must be a list of field name/getter pairs."
  479. (let loop ((fields fields))
  480. (match fields
  481. (()
  482. object)
  483. (((field . get) rest ...)
  484. (format port "~a: ~a~%" field (get object))
  485. (loop rest)))))
  486. (define %recutils-field-charset
  487. ;; Valid characters starting a recutils field.
  488. ;; info "(recutils) Fields"
  489. (char-set-union char-set:upper-case
  490. char-set:lower-case
  491. (char-set #\%)))
  492. (define (recutils->alist port)
  493. "Read a recutils-style record from PORT and return it as a list of key/value
  494. pairs. Stop upon an empty line (after consuming it) or EOF."
  495. (let loop ((line (read-line port))
  496. (result '()))
  497. (cond ((eof-object? line)
  498. (reverse result))
  499. ((string-null? line)
  500. (if (null? result)
  501. (loop (read-line port) result) ; leading space: ignore it
  502. (reverse result))) ; end-of-record marker
  503. (else
  504. ;; Now check the first character of LINE, since that's what the
  505. ;; recutils manual says is enough.
  506. (let ((first (string-ref line 0)))
  507. (cond
  508. ((char-set-contains? %recutils-field-charset first)
  509. (let* ((colon (string-index line #\:))
  510. (field (string-take line colon))
  511. (value (string-trim (string-drop line (+ 1 colon)))))
  512. (loop (read-line port)
  513. (alist-cons field value result))))
  514. ((eqv? first #\#) ;info "(recutils) Comments"
  515. (loop (read-line port) result))
  516. ((eqv? first #\+) ;info "(recutils) Fields"
  517. (let ((new-line (if (string-prefix? "+ " line)
  518. (string-drop line 2)
  519. (string-drop line 1))))
  520. (match result
  521. (((field . value) rest ...)
  522. (loop (read-line port)
  523. `((,field . ,(string-append value "\n" new-line))
  524. ,@rest))))))
  525. (else
  526. (error "unmatched line" line))))))))
  527. ;;;
  528. ;;; Pattern matching.
  529. ;;;
  530. (define-syntax lookup-field+wrapper
  531. (lambda (s)
  532. "Look up FIELD in the given list and return both an expression that represents
  533. its offset in the record and a procedure that wraps it to return its \"true\" value
  534. (for instance, FORCE is returned in the case of a delayed field). RECORD is passed
  535. to thunked values. Raise a syntax violation when the field is not found."
  536. (syntax-case s (normal delayed thunked)
  537. ((_ record field offset ())
  538. (syntax-violation 'match-record
  539. "unknown record type field"
  540. ;; Attach the local source data to the field.
  541. (datum->syntax #f (syntax->datum #'field) #:source s)))
  542. ((_ record field offset ((head normal) tail ...))
  543. (free-identifier=? #'field #'head)
  544. #'(values offset identity))
  545. ((_ record field offset ((head delayed) tail ...))
  546. (free-identifier=? #'field #'head)
  547. #'(values offset force))
  548. ((_ record field offset ((head thunked) tail ...))
  549. (free-identifier=? #'field #'head)
  550. #'(values offset (cut <> record)))
  551. ((_ record field offset (_ tail ...))
  552. #'(lookup-field+wrapper record field
  553. (+ 1 offset) (tail ...))))))
  554. (define-syntax match-record-inner
  555. (lambda (s)
  556. (syntax-case s ()
  557. ((_ record type ((field variable) rest ...) body ...)
  558. #'(let-syntax ((field-offset+wrapper
  559. (syntax-rules ()
  560. ((_ f)
  561. (lookup-field+wrapper record field 0 f)))))
  562. (let*-values (((offset wrap)
  563. (type (map-fields type match-record)
  564. field-offset+wrapper))
  565. ((variable)
  566. (wrap (struct-ref record offset))))
  567. (match-record-inner record type (rest ...) body ...))))
  568. ((_ record type (field rest ...) body ...)
  569. ;; Redirect to the canonical form above.
  570. #'(match-record-inner record type ((field field) rest ...) body ...))
  571. ((_ record type () body ...)
  572. #'(begin body ...)))))
  573. (define-syntax match-record
  574. (syntax-rules ()
  575. "Bind each FIELD of a RECORD of the given TYPE to it's FIELD name.
  576. The order in which fields appear does not matter. A syntax error is raised if
  577. an unknown field is queried."
  578. ((_ record type (fields ...) body ...)
  579. (if (eq? (struct-vtable record) type)
  580. (match-record-inner record type (fields ...) body ...)
  581. (throw 'wrong-type-arg record)))))
  582. (define-syntax match-record-lambda
  583. (syntax-rules ()
  584. "Return a procedure accepting a single record of the given TYPE for which each
  585. FIELD will be bound to its FIELD name within the returned procedure. A syntax error
  586. is raised if an unknown field is queried."
  587. ((_ type (field ...) body ...)
  588. (lambda (record)
  589. (if (eq? (struct-vtable record) type)
  590. (match-record-inner record type (field ...) body ...)
  591. (throw 'wrong-type-arg record))))))
  592. ;;; records.scm ends here