records.scm 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  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)
  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. (result (ref eq))
  73. (struct.new $vtable
  74. (i32.const 0)
  75. (global.get $root-vtable)
  76. (local.get $nfields)
  77. (local.get $printer)
  78. (local.get $name)
  79. (local.get $constructor)
  80. (local.get $properties)
  81. (local.get $parents)
  82. (local.get $mutable-fields)
  83. (local.get $compare)))
  84. nfields printer name constructor properties parents mutable-fields
  85. compare))))
  86. (define (record-type-parents rtd)
  87. (cond-expand
  88. (guile-vm
  89. (guile:record-type-parents rtd))
  90. (else
  91. (match (%inline-wasm
  92. '(func (param $vtable (ref $vtable)) (result (ref eq))
  93. (struct.get $vtable $parents (local.get $vtable)))
  94. rtd)
  95. ((? vector? parentv) parentv)
  96. (parent
  97. (let ((grandparents (record-type-parents parent)))
  98. (define parents
  99. (make-vector (1+ (vector-length grandparents)) parent))
  100. (vector-copy! parents 0 grandparents 0)
  101. (%inline-wasm
  102. '(func (param $vtable (ref $vtable)) (param $parentv (ref eq))
  103. (struct.set $vtable $parents (local.get $vtable)
  104. (local.get $parentv)))
  105. rtd parents)
  106. parents))))))
  107. (define-syntax define-record-type
  108. (lambda (stx)
  109. (define (acons x y z) (cons (cons x y) z))
  110. (define (parse-kwargs args k)
  111. (let lp ((args args) (kwargs '()))
  112. (syntax-case args ()
  113. ((kw val . args) (keyword? (syntax->datum #'kw))
  114. (lp #'args (append kwargs (list (syntax->datum #'kw) #'val))))
  115. (args (k #'args kwargs)))))
  116. (define* (parse-body id body #:key (printer #'#f) (parent #'#f) (uid #'#f)
  117. (extensible? #'#f) (allow-duplicate-field-names? #'#f)
  118. (opaque? #'#f))
  119. (define properties
  120. (datum->syntax
  121. #'nothing
  122. ((syntax-case extensible? ()
  123. (#t (lambda (props) (acons 'extensible? #t props)))
  124. (#f (lambda (props) props)))
  125. ((syntax-case opaque? ()
  126. (#t (lambda (props) (acons 'opaque? #t props)))
  127. (#f (lambda (props) props)))
  128. ((syntax-case uid ()
  129. (#f (lambda (props) props))
  130. (_ (? string? (syntax->datum uid))
  131. (lambda (props) (acons 'uid (syntax->datum uid) props))))
  132. '())))))
  133. (define id-str (symbol->string (syntax->datum id)))
  134. (define-values (parent-count
  135. parent-fields
  136. parent-mutable-fields
  137. parents)
  138. (syntax-case parent ()
  139. (#f (values 0 '() 0 #'#()))
  140. (_
  141. (let-values (((kind value) (syntax-local-binding parent)))
  142. (define (err reason)
  143. (syntax-violation 'define-record-type reason stx parent))
  144. (unless (and (eq? kind 'macro)
  145. (procedure-property value 'record-type?))
  146. (err "expected a record type as #:parent"))
  147. (unless (procedure-property value 'extensible?)
  148. (err "parent record type is final"))
  149. (when (procedure-property value 'opaque?)
  150. (unless (syntax-case opaque? () (#f #f) (_ #t))
  151. (err "can't make non-opaque subtype of opaque type")))
  152. (let ((parent-count (procedure-property value 'parent-count)))
  153. (values
  154. (1+ parent-count)
  155. (procedure-property value 'fields)
  156. (procedure-property value 'mutable-fields)
  157. (if (eq? parent-count 0)
  158. #`(vector #,parent)
  159. ;; Lazily initialize parentv on first access;
  160. ;; mentioning all of the vtables would make it
  161. ;; harder for peval / dce to elide unused vtables.
  162. parent)))))))
  163. (define (valid-constructor-args? cfields fields)
  164. (define (check-parent-fields cfields parent-fields)
  165. (cond
  166. ((null? parent-fields)
  167. (check-fields cfields fields))
  168. (else
  169. (syntax-case cfields ()
  170. (() #f)
  171. ((cfield . cfields)
  172. (and (identifier? #'cfield)
  173. (eq? (syntax->datum #'cfield) (car parent-fields))
  174. (check-parent-fields #'cfields (cdr parent-fields))))))))
  175. (define (check-fields cfields fields)
  176. (syntax-case cfields ()
  177. (() (syntax-case fields () (() #t) (_ #f)))
  178. ((cfield . cfields)
  179. (syntax-case fields ()
  180. ((field . fields)
  181. (and (free-identifier=? #'field #'cfield)
  182. (check-fields #'cfields #'fields)))
  183. (_ #f)))))
  184. (check-parent-fields cfields parent-fields))
  185. (define (compute-mutable-fields setters)
  186. (let lp ((setters setters) (out parent-mutable-fields)
  187. (i (length parent-fields)))
  188. (syntax-case setters ()
  189. (() out)
  190. ((() . setters) (lp #'setters out (1+ i)))
  191. (((_) . setters) (lp #'setters (logior out (ash 1 i)) (1+ i))))))
  192. (syntax-case body ()
  193. (((constructor cfield ...) predicate (field getter . setter) ...)
  194. (and (identifier? #'constructor)
  195. (identifier? #'predicate)
  196. (valid-constructor-args? #'(cfield ...) #'(field ...)))
  197. #`(begin
  198. (define (constructor cfield ...)
  199. (%make-struct #,id cfield ...))
  200. (define-syntax #,id
  201. (lambda (stx)
  202. #((record-type? . #t)
  203. (parent-count . #,parent-count)
  204. (fields cfield ...)
  205. (mutable-fields . #,(compute-mutable-fields #'(setter ...)))
  206. #,@properties)
  207. (syntax-case stx ()
  208. (x (identifier? #'x) #'vtable))))
  209. ;; Note that the procedures stored in record vtables are
  210. ;; treated as "trusted": they do no type checks. They
  211. ;; shouldn't be exposed to users because it may be that
  212. ;; they can apply to objects of different types but the
  213. ;; same shape.
  214. (define vtable
  215. (%make-vtable
  216. #,(length #'(cfield ...))
  217. '(cfield ...)
  218. #,(syntax-case printer ()
  219. (#f
  220. (syntax-case opaque? ()
  221. (#t
  222. #`(lambda (x port write-field)
  223. (write-string "#<" port)
  224. (write-string #,id-str port)
  225. (write-string ">" port)))
  226. (#f
  227. #`(lambda (x port write-field)
  228. (write-string "#<" port)
  229. (write-string #,id-str port)
  230. #,@(let lp ((fields (map syntax->datum
  231. #'(cfield ...)))
  232. (i 0))
  233. (cond
  234. ((null? fields) #'())
  235. (else
  236. (let ((name (symbol->string (car fields)))
  237. (fields (cdr fields)))
  238. #`((write-string " " port)
  239. (write-field #,name (%struct-ref x #,i) port)
  240. . #,(lp fields (1+ i)))))))
  241. (write-string ">" port)))))
  242. (_ #`(let ((p #,printer))
  243. (lambda (x port write-field) (p x port)))))
  244. '#,id
  245. (lambda (vtable cfield ...)
  246. (%make-struct vtable cfield ...))
  247. '#,properties
  248. #,parents
  249. #,(compute-mutable-fields #'(setter ...))
  250. #,(syntax-case opaque? ()
  251. (#t
  252. #`(lambda (x y equal?) #f))
  253. (#f
  254. #`(lambda (x y equal?)
  255. (and . #,(let lp ((fields #'(cfield ...))
  256. (i 0))
  257. (syntax-case fields ()
  258. (() #'())
  259. ((f . fields)
  260. #`((equal? (%struct-ref x #,i)
  261. (%struct-ref y #,i))
  262. . #,(lp #'fields (1+ i))))))))))))
  263. (define (predicate x)
  264. (and (%struct? x)
  265. #,(syntax-case extensible? ()
  266. (#f #`(%eq? (%struct-vtable x) #,id))
  267. (#t
  268. #`(let ((rtd (%struct-vtable x)))
  269. (or (%eq? rtd #,id)
  270. (let ((parents (record-type-parents rtd)))
  271. (and (< #,parent-count
  272. (vector-length parents))
  273. (%eq? (vector-ref parents #,parent-count)
  274. #,id)))))))))
  275. .
  276. #,(let lp ((accessors #'((getter . setter) ...))
  277. (i (length parent-fields)))
  278. (syntax-case accessors ()
  279. (() #'())
  280. (((get) . accessors)
  281. #`((define (get x)
  282. (check-type x predicate 'get)
  283. (%struct-ref x #,i))
  284. . #,(lp #'accessors (1+ i))))
  285. (((get set!) . accessors)
  286. #`((define (set! obj val)
  287. (check-type obj predicate 'set!)
  288. (%struct-set! obj #,i val))
  289. . #,(lp #'((get) . accessors) i)))))))))
  290. (syntax-case stx ()
  291. ((_ id arg ...)
  292. (parse-kwargs
  293. #'(arg ...)
  294. (lambda (tail kwargs)
  295. (apply parse-body #'id tail kwargs)))))))
  296. (define (record? x)
  297. (%struct? x))
  298. (define (write-record record port write)
  299. (define printer-field 1)
  300. (define (write-field name value port)
  301. (write-string name port)
  302. (write-string ": " port)
  303. (write value port))
  304. (match (%struct-ref (%struct-vtable record) printer-field)
  305. (#f (write-string "#<record with no printer!>" port))
  306. (print (print record port write-field)))))