records.scm 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. ;;; Records
  2. ;;; Copyright (C) 2024 Igalia, S.L.
  3. ;;;
  4. ;;; Licensed under the Apache License, Version 2.0 (the "License");
  5. ;;; you may not use this file except in compliance with the License.
  6. ;;; You may obtain a copy of the License at
  7. ;;;
  8. ;;; http://www.apache.org/licenses/LICENSE-2.0
  9. ;;;
  10. ;;; Unless required by applicable law or agreed to in writing, software
  11. ;;; distributed under the License is distributed on an "AS IS" BASIS,
  12. ;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. ;;; See the License for the specific language governing permissions and
  14. ;;; limitations under the License.
  15. ;;; Commentary:
  16. ;;;
  17. ;;; Records.
  18. ;;;
  19. ;;; Code:
  20. (library (hoot records)
  21. (export define-record-type
  22. record-type-parents
  23. record?
  24. write-record)
  25. (import (hoot primitives)
  26. (hoot cond-expand)
  27. (hoot pairs)
  28. (hoot numbers)
  29. (hoot eq)
  30. (hoot ports)
  31. (hoot lists)
  32. (hoot keywords)
  33. (hoot symbols)
  34. (hoot values)
  35. (hoot vectors)
  36. (hoot errors)
  37. (hoot match)
  38. (hoot bitwise))
  39. (define-syntax-rule (%make-vtable nfields field-names printer name
  40. constructor properties parents
  41. mutable-fields compare field-ref)
  42. (cond-expand
  43. (guile-vm
  44. (let ()
  45. (define (assq-ref alist k)
  46. (and (pair? alist)
  47. (if (eq? (caar alist) k)
  48. (cdar alist)
  49. (assq-ref (cdr alist) k))))
  50. (guile:make-record-type
  51. name
  52. field-names
  53. (and printer
  54. (lambda (s p)
  55. (error "guile-side I/O not implemented")))
  56. ;; Rely on define-record-type to do lazy initialization.
  57. #:parent (if (vector? parents) #f parents)
  58. #:uid (assq-ref properties 'uid)
  59. #:extensible? (assq-ref properties 'extensible?)
  60. #:allow-duplicate-field-names? #t
  61. #:opaque? (assq-ref properties 'opaque?))))
  62. (else
  63. (%inline-wasm
  64. '(func (param $nfields (ref eq))
  65. (param $printer (ref eq))
  66. (param $name (ref eq))
  67. (param $constructor (ref eq))
  68. (param $properties (ref eq))
  69. (param $parents (ref eq))
  70. (param $mutable-fields (ref eq))
  71. (param $compare (ref eq))
  72. (param $field-ref (ref eq))
  73. (result (ref eq))
  74. (struct.new $vtable
  75. (i32.const 0)
  76. (global.get $root-vtable)
  77. (local.get $nfields)
  78. (local.get $printer)
  79. (local.get $name)
  80. (local.get $constructor)
  81. (local.get $properties)
  82. (local.get $parents)
  83. (local.get $mutable-fields)
  84. (local.get $compare)
  85. (local.get $field-ref)))
  86. nfields printer name constructor properties parents mutable-fields
  87. compare field-ref))))
  88. (define (record-type-parents rtd)
  89. (cond-expand
  90. (guile-vm
  91. (guile:record-type-parents rtd))
  92. (else
  93. (match (%inline-wasm
  94. '(func (param $vtable (ref $vtable)) (result (ref eq))
  95. (struct.get $vtable $parents (local.get $vtable)))
  96. rtd)
  97. ((? vector? parentv) parentv)
  98. (parent
  99. (let ((grandparents (record-type-parents parent)))
  100. (define parents
  101. (make-vector (1+ (vector-length grandparents)) parent))
  102. (vector-copy! parents 0 grandparents 0)
  103. (%inline-wasm
  104. '(func (param $vtable (ref $vtable)) (param $parentv (ref eq))
  105. (struct.set $vtable $parents (local.get $vtable)
  106. (local.get $parentv)))
  107. rtd parents)
  108. parents))))))
  109. (define-syntax define-record-type
  110. (lambda (stx)
  111. (define (acons x y z) (cons (cons x y) z))
  112. (define (parse-kwargs args k)
  113. (let lp ((args args) (kwargs '()))
  114. (syntax-case args ()
  115. ((kw val . args) (keyword? (syntax->datum #'kw))
  116. (lp #'args (append kwargs (list (syntax->datum #'kw) #'val))))
  117. (args (k #'args kwargs)))))
  118. (define* (parse-body id body #:key (printer #'#f) (parent #'#f) (uid #'#f)
  119. (extensible? #'#f) (allow-duplicate-field-names? #'#f)
  120. (opaque? #'#f))
  121. (define properties
  122. (datum->syntax
  123. #'nothing
  124. ((syntax-case extensible? ()
  125. (#t (lambda (props) (acons 'extensible? #t props)))
  126. (#f (lambda (props) props)))
  127. ((syntax-case opaque? ()
  128. (#t (lambda (props) (acons 'opaque? #t props)))
  129. (#f (lambda (props) props)))
  130. ((syntax-case uid ()
  131. (#f (lambda (props) props))
  132. (_ (? string? (syntax->datum uid))
  133. (lambda (props) (acons 'uid (syntax->datum uid) props))))
  134. '())))))
  135. (define id-str (symbol->string (syntax->datum id)))
  136. (define-values (parent-count
  137. parent-fields
  138. parent-mutable-fields
  139. parents)
  140. (syntax-case parent ()
  141. (#f (values 0 '() 0 #'#()))
  142. (_
  143. (let-values (((kind value) (syntax-local-binding parent)))
  144. (define (err reason)
  145. (syntax-violation 'define-record-type reason stx parent))
  146. (unless (and (eq? kind 'macro)
  147. (procedure-property value 'record-type?))
  148. (err "expected a record type as #:parent"))
  149. (unless (procedure-property value 'extensible?)
  150. (err "parent record type is final"))
  151. (when (procedure-property value 'opaque?)
  152. (unless (syntax-case opaque? () (#f #f) (_ #t))
  153. (err "can't make non-opaque subtype of opaque type")))
  154. (let ((parent-count (procedure-property value 'parent-count)))
  155. (values
  156. (1+ parent-count)
  157. (procedure-property value 'fields)
  158. (procedure-property value 'mutable-fields)
  159. (if (eq? parent-count 0)
  160. #`(vector #,parent)
  161. ;; Lazily initialize parentv on first access;
  162. ;; mentioning all of the vtables would make it
  163. ;; harder for peval / dce to elide unused vtables.
  164. parent)))))))
  165. (define (valid-constructor-args? cfields fields)
  166. (define (check-parent-fields cfields parent-fields)
  167. (cond
  168. ((null? parent-fields)
  169. (check-fields cfields fields))
  170. (else
  171. (syntax-case cfields ()
  172. (() #f)
  173. ((cfield . cfields)
  174. (and (identifier? #'cfield)
  175. (eq? (syntax->datum #'cfield) (car parent-fields))
  176. (check-parent-fields #'cfields (cdr parent-fields))))))))
  177. (define (check-fields cfields fields)
  178. (syntax-case cfields ()
  179. (() (syntax-case fields () (() #t) (_ #f)))
  180. ((cfield . cfields)
  181. (syntax-case fields ()
  182. ((field . fields)
  183. (and (free-identifier=? #'field #'cfield)
  184. (check-fields #'cfields #'fields)))
  185. (_ #f)))))
  186. (check-parent-fields cfields parent-fields))
  187. (define (compute-mutable-fields setters)
  188. (let lp ((setters setters) (out parent-mutable-fields)
  189. (i (length parent-fields)))
  190. (syntax-case setters ()
  191. (() out)
  192. ((() . setters) (lp #'setters out (1+ i)))
  193. (((_) . setters) (lp #'setters (logior out (ash 1 i)) (1+ i))))))
  194. (syntax-case body ()
  195. (((constructor cfield ...) predicate (field getter . setter) ...)
  196. (and (identifier? #'constructor)
  197. (identifier? #'predicate)
  198. (valid-constructor-args? #'(cfield ...) #'(field ...)))
  199. #`(begin
  200. (define (constructor cfield ...)
  201. (%make-struct #,id cfield ...))
  202. (define-syntax #,id
  203. (lambda (stx)
  204. #((record-type? . #t)
  205. (parent-count . #,parent-count)
  206. (fields cfield ...)
  207. (mutable-fields . #,(compute-mutable-fields #'(setter ...)))
  208. #,@properties)
  209. (syntax-case stx ()
  210. (x (identifier? #'x) #'vtable))))
  211. ;; Note that the procedures stored in record vtables are
  212. ;; treated as "trusted": they do no type checks. They
  213. ;; shouldn't be exposed to users because it may be that
  214. ;; they can apply to objects of different types but the
  215. ;; same shape.
  216. (define vtable
  217. (%make-vtable
  218. #,(length #'(cfield ...))
  219. '(cfield ...)
  220. #,(syntax-case printer ()
  221. (#f
  222. (syntax-case opaque? ()
  223. (#t
  224. #`(lambda (x port write-field)
  225. (write-string "#<" port)
  226. (write-string #,id-str port)
  227. (write-string ">" port)))
  228. (#f
  229. #`(lambda (x port write-field)
  230. (write-string "#<" port)
  231. (write-string #,id-str port)
  232. #,@(let lp ((fields (map syntax->datum
  233. #'(cfield ...)))
  234. (i 0))
  235. (cond
  236. ((null? fields) #'())
  237. (else
  238. (let ((name (symbol->string (car fields)))
  239. (fields (cdr fields)))
  240. #`((write-string " " port)
  241. (write-field #,name (%struct-ref x #,i) port)
  242. . #,(lp fields (1+ i)))))))
  243. (write-string ">" port)))))
  244. (_ #`(let ((p #,printer))
  245. (lambda (x port write-field) (p x port)))))
  246. '#,id
  247. (lambda (vtable cfield ...)
  248. (%make-struct vtable cfield ...))
  249. '#,properties
  250. #,parents
  251. #,(compute-mutable-fields #'(setter ...))
  252. #,(syntax-case opaque? ()
  253. (#t
  254. #`(lambda (x y equal?) #f))
  255. (#f
  256. #`(lambda (x y equal?)
  257. (and . #,(let lp ((fields #'(cfield ...))
  258. (i 0))
  259. (syntax-case fields ()
  260. (() #'())
  261. ((f . fields)
  262. #`((equal? (%struct-ref x #,i)
  263. (%struct-ref y #,i))
  264. . #,(lp #'fields (1+ i))))))))))
  265. (lambda (x idx)
  266. (case idx
  267. #,@(let lp ((fields #'(cfield ...))
  268. (i 0))
  269. (syntax-case fields ()
  270. (() #'())
  271. ((f . fields)
  272. #`(((#,i) (%struct-ref x #,i))
  273. . #,(lp #'fields (1+ i))))))
  274. (else
  275. (error "invalid record field index"
  276. idx))))))
  277. (define (predicate x)
  278. (and (%struct? x)
  279. #,(syntax-case extensible? ()
  280. (#f #`(%eq? (%struct-vtable x) #,id))
  281. (#t
  282. #`(let ((rtd (%struct-vtable x)))
  283. (or (%eq? rtd #,id)
  284. (let ((parents (record-type-parents rtd)))
  285. (and (< #,parent-count
  286. (vector-length parents))
  287. (%eq? (vector-ref parents #,parent-count)
  288. #,id)))))))))
  289. .
  290. #,(let lp ((accessors #'((getter . setter) ...))
  291. (i (length parent-fields)))
  292. (syntax-case accessors ()
  293. (() #'())
  294. (((get) . accessors)
  295. #`((define (get x)
  296. (check-type x predicate 'get)
  297. (%struct-ref x #,i))
  298. . #,(lp #'accessors (1+ i))))
  299. (((get set!) . accessors)
  300. #`((define (set! obj val)
  301. (check-type obj predicate 'set!)
  302. (%struct-set! obj #,i val))
  303. . #,(lp #'((get) . accessors) i)))))))))
  304. (syntax-case stx ()
  305. ((_ id arg ...)
  306. (parse-kwargs
  307. #'(arg ...)
  308. (lambda (tail kwargs)
  309. (apply parse-body #'id tail kwargs)))))))
  310. (define (record? x)
  311. (%struct? x))
  312. (define (write-record record port write)
  313. (define printer-field 1)
  314. (define (write-field name value port)
  315. (write-string name port)
  316. (write-string ": " port)
  317. (write value port))
  318. (match (%struct-ref (%struct-vtable record) printer-field)
  319. (#f (write-string "#<record with no printer!>" port))
  320. (print (print record port write-field)))))