records.scm 25 KB

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