records.scm 12 KB

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