aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMaxime Devos <maximedevos@telenet.be>2023-01-29 23:36:29 +0100
committerMaxime Devos <maximedevos@telenet.be>2023-01-30 00:17:27 +0100
commit656f43559f488fbee4a4ebd13421136bfe32bcfc (patch)
tree4ee5a150472ed8c04e9e624184b2edb91e3b5bcf
parent6cc094bc15814353a462b118cf8e203270bd91d0 (diff)
downloadgnunet-scheme-656f43559f488fbee4a4ebd13421136bfe32bcfc.tar.gz
gnunet-scheme-656f43559f488fbee4a4ebd13421136bfe32bcfc.zip
records: New API for record types, specialised to bytevector slices.
This simplifies many record definitions and is less prone to errors -- if there is an error in the generation code, it will likely impact multiple record types, so tests for one record type also partially test other record types. It also reduces the amount of code to be written -- reducing boilerplate, in other words. * gnu/gnunet/utils/records.scm: New module. * Makefile.am (modules): Add it. * gnu/gnunet/cadet/client.scm: Use it. * gnu/gnunet/hashcode.scm: Likewise. * gnu/gnunet/fs/uri.scm: Likewise.
-rw-r--r--Makefile.am1
-rw-r--r--gnu/gnunet/cadet/client.scm39
-rw-r--r--gnu/gnunet/fs/uri.scm81
-rw-r--r--gnu/gnunet/hashcode.scm67
-rw-r--r--gnu/gnunet/utils/records.scm222
5 files changed, 310 insertions, 100 deletions
diff --git a/Makefile.am b/Makefile.am
index 1c9fbe3..a0437f4 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -68,6 +68,7 @@ modules = \
68 gnu/gnunet/utils/cut-syntax.scm \ 68 gnu/gnunet/utils/cut-syntax.scm \
69 gnu/gnunet/utils/netstruct.scm \ 69 gnu/gnunet/utils/netstruct.scm \
70 gnu/gnunet/utils/platform-enum.scm \ 70 gnu/gnunet/utils/platform-enum.scm \
71 gnu/gnunet/utils/records.scm \
71 gnu/gnunet/utils/tokeniser.scm \ 72 gnu/gnunet/utils/tokeniser.scm \
72 \ 73 \
73 gnu/gnunet/block.scm \ 74 gnu/gnunet/block.scm \
diff --git a/gnu/gnunet/cadet/client.scm b/gnu/gnunet/cadet/client.scm
index 7306cb4..970c75a 100644
--- a/gnu/gnunet/cadet/client.scm
+++ b/gnu/gnunet/cadet/client.scm
@@ -1,6 +1,6 @@
1;#!r6rs 1;#!r6rs
2;; This file is part of Scheme-GNUnet. 2;; This file is part of Scheme-GNUnet.
3;; Copyright © 2022 GNUnet e.V. 3;; Copyright © 2022--2023 GNUnet e.V.
4;; 4;;
5;; Scheme-GNUnet is free software: you can redistribute it and/or modify it 5;; Scheme-GNUnet is free software: you can redistribute it and/or modify it
6;; under the terms of the GNU Affero General Public License as published 6;; under the terms of the GNU Affero General Public License as published
@@ -80,19 +80,19 @@
80 sizeof %sizeof read% r% s% define-analyser analyse 80 sizeof %sizeof read% r% s% define-analyser analyse
81 construct =>! =>slice!) 81 construct =>! =>slice!)
82 (only (gnu gnunet utils bv-slice) 82 (only (gnu gnunet utils bv-slice)
83 make-slice/read-write slice-copy/read-only slice-length 83 slice-copy/read-only slice-length
84 slice-copy! slice-slice) 84 slice-copy! slice-slice slice-contents-equal?)
85 (only (gnu gnunet utils hat-let) 85 (only (gnu gnunet utils hat-let)
86 let^) 86 let^)
87 (only (rnrs base) 87 (only (rnrs base)
88 begin define lambda assert quote cons apply values 88 begin define lambda assert apply values
89 case else = define-syntax + expt - let and > 89 = + expt - let and > not if < list quote)
90 not if < append list)
91 (only (rnrs control) 90 (only (rnrs control)
92 when) 91 when)
93 (only (pfds bbtrees) 92 (only (pfds bbtrees)
94 bbtree-set make-bbtree bbtree-ref) 93 bbtree-set make-bbtree bbtree-ref)
95 (only (rnrs records syntactic) define-record-type) 94 (only (rnrs records syntactic) define-record-type)
95 (only (gnu gnunet utils records) define-record-type*)
96 (only (ice-9 control) let/ec) 96 (only (ice-9 control) let/ec)
97 (only (ice-9 match) match) 97 (only (ice-9 match) match)
98 (only (guile) define* error) 98 (only (guile) define* error)
@@ -370,20 +370,25 @@
370 rest message-queue (loop:terminal-condition loop) 370 rest message-queue (loop:terminal-condition loop)
371 (cut k/reconnect! channel-number->channel-map))))) 371 (cut k/reconnect! channel-number->channel-map)))))
372 372
373 (define-record-type (<cadet-address> make-cadet-address cadet-address?) 373 (define-record-type* (<cadet-address> cadet-address?)
374 (fields (immutable peer cadet-address-peer) 374 #:constructor (make-cadet-address
375 (immutable port cadet-address-port)) 375 "Make a CADET address for contacting the peer @var{peer}
376 (protocol (lambda (%make)
377 "Make a CADET address for contacting the peer @var{peer}
378(a readable bytevector slice containing a @code{/peer-identity}) at port 376(a readable bytevector slice containing a @code{/peer-identity}) at port
379@var{port} (a readable bytevector slice containing a @code{/hashcode:512}). 377@var{port} (a readable bytevector slice containing a @code{/hashcode:512}).
380The slices @var{peer} and @var{port} are copied, so future changes to them 378The slices @var{peer} and @var{port} are copied, so future changes to them
381do not have any impact on the cadet address." 379do not have any impact on the cadet address.")
382 (lambda (peer port) 380 #:field (peer #:getter cadet-address-peer
383 (assert (= (sizeof /peer-identity '()) (slice-length peer))) 381 #:predicate (lambda (peer)
384 (assert (= (sizeof /hashcode:512 '()) (slice-length port))) 382 (= (sizeof /peer-identity '())
385 (%make (slice-copy/read-only peer) 383 (slice-length peer)))
386 (slice-copy/read-only port)))))) 384 #:preprocess slice-copy/read-only
385 #:equality slice-contents-equal?)
386 #:field (query #:getter cadet-address-port
387 #:predicate (lambda (port)
388 (= (sizeof /hashcode:512 '())
389 (slice-length port)))
390 #:preprocess slice-copy/read-only
391 #:equality slice-contents-equal?))
387 392
388 (define* (construct-local-channel-create cadet-address channel-number 393 (define* (construct-local-channel-create cadet-address channel-number
389 #:optional (options 0)) 394 #:optional (options 0))
diff --git a/gnu/gnunet/fs/uri.scm b/gnu/gnunet/fs/uri.scm
index 88cdb56..8e0200d 100644
--- a/gnu/gnunet/fs/uri.scm
+++ b/gnu/gnunet/fs/uri.scm
@@ -1,6 +1,6 @@
1;#!r6rs 1;#!r6rs
2;; This file is part of scheme-GNUnet, a partial Scheme port of GNUnet. 2;; This file is part of scheme-GNUnet, a partial Scheme port of GNUnet.
3;; Copyright (C) 2003--2014, 2020, 2022 GNUnet e.V. 3;; Copyright (C) 2003--2014, 2020, 2022--2023 GNUnet e.V.
4;; 4;;
5;; GNUnet is free software: you can redistribute it and/or modify it 5;; GNUnet is free software: you can redistribute it and/or modify it
6;; under the terms of the GNU Affero General Public License as published 6;; under the terms of the GNU Affero General Public License as published
@@ -84,7 +84,6 @@
84 chk-uri? make-chk-uri chk-uri-file-length chk-uri-chk 84 chk-uri? make-chk-uri chk-uri-file-length chk-uri-chk
85 chk-uri-parse) 85 chk-uri-parse)
86 (import (rnrs base) 86 (import (rnrs base)
87 (rnrs records syntactic)
88 (gnu gnunet hashcode) 87 (gnu gnunet hashcode)
89 (gnu gnunet hashcode-ascii) 88 (gnu gnunet hashcode-ascii)
90 (only (gnu gnunet fs struct) /content-hash-key) 89 (only (gnu gnunet fs struct) /content-hash-key)
@@ -92,59 +91,55 @@
92 (only (guile) make-regexp regexp-exec) 91 (only (guile) make-regexp regexp-exec)
93 (only (ice-9 regex) match:substring) 92 (only (ice-9 regex) match:substring)
94 (only (srfi srfi-2) and-let*) 93 (only (srfi srfi-2) and-let*)
95 (only (gnu gnunet netstruct syntactic) 94 (only (gnu gnunet netstruct syntactic) s%)
96 define-analyser s%)) 95 (only (gnu gnunet utils records)
96 define-record-type*))
97 97
98 ;; Size of the individual blocks used for file-sharing. 98 ;; Size of the individual blocks used for file-sharing.
99 ;; TODO: what is the proper place to define this constant 99 ;; TODO: what is the proper place to define this constant
100 #;(define DBLOCK_SIZE (* 32 1024)) 100 #;(define DBLOCK_SIZE (* 32 1024))
101 101
102 ;; Content hash key 102 ;; Content hash key
103 (define-record-type (<content-hash-key> %make-content-hash-key 103 (define-record-type* (<content-hash-key> content-hash-key?)
104 content-hash-key?) 104 #:constructor (make-content-hash-key "Construct a <content-hash-key>")
105 (fields ;; Hash of the original content, used for encryption. 105 #:network-structure /content-hash-key
106 ;; Of type <hashcode:512>. 106 #:analyse (make-content-hash-key/share
107 (immutable key content-hash-key-key) 107 "Construct a <content-hash-key> corresponding to the
108 ;; Hash of the encrypted content, used for querying.
109 ;; Of type <hashcode:512>
110 (immutable query content-hash-key-query)))
111
112 (define (make-content-hash-key key query)
113 "Construct a <content-hash-key>"
114 (assert (hashcode:512? key))
115 (assert (hashcode:512? query))
116 (%make-content-hash-key key query))
117
118 (define-analyser make-content-hash-key/share /content-hash-key
119 "Construct a <content-hash-key> corresponding to the
120@code{/content-hash-key} slice. The slice may not be modified 108@code{/content-hash-key} slice. The slice may not be modified
121while the content hash key is in use." 109while the content hash key is in use.")
122 (make-content-hash-key 110 ;; Hash of the original content, used for encryption.
123 (make-hashcode:512/share (s% key)) 111 #:field (key #:getter content-hash-key-key
124 (make-hashcode:512/share (s% query)))) 112 #:predicate hashcode:512?
113 #:analyse make-hashcode:512/share
114 #:network-structure-select (s% key))
115 ;; Hash of the encrypted content, used for querying.
116 #:field (query #:getter content-hash-key-query
117 #:predicate hashcode:512?
118 #:analyse make-hashcode:512/share
119 #:network-structure-select (s% query)))
125 120
126 ;; Information needed to retrieve a file (content-hash-key 121 ;; Information needed to retrieve a file (content-hash-key
127 ;; plus file size) 122 ;; plus file size)
128 (define-record-type (<chk-uri> %make-chk-uri chk-uri?) 123 (define-record-type* (<chk-uri> chk-uri?)
129 (fields ;; Total size of the file referred to in bytes. 124 #:constructor (make-chk-uri "Make a chk-URI")
130 (immutable file-length chk-uri-file-length) 125 ;; Total size of the file referred to in bytes.
131 ;; Query and key of the top GNUNET_EC_IBlock. 126 #:field (file-length #:getter chk-uri-file-length
132 ;; Of type <content-hash-key>. 127 #:predicate
133 (immutable chk chk-uri-chk))) 128 (lambda (n)
129 (and (exact? file-length)
130 (integer? file-length)
131 (<= 0 file-length)
132 (< file-length file-length-limit)))
133 #:equality =)
134 ;; Query and key of the top GNUNET_EC_IBlock.
135 ;; Of type <content-hash-key>.
136 #:field (chk #:getter chk-uri-chk
137 #:predicate content-hash-key?))
134 138
135 ;; TODO: is this limitation on file size 139 ;; TODO: is this limitation on file size
136 ;; merely a limit of the implementation? 140 ;; merely a limit of the implementation?
137 (define file-length-limit (expt 2 64)) 141 (define file-length-limit (expt 2 64))
138 142
139 (define (make-chk-uri file-length chk)
140 "Make a chk-URI"
141 (assert (and (exact? file-length)
142 (integer? file-length)))
143 (assert (and (<= 0 file-length)
144 (< file-length file-length-limit)))
145 (assert (content-hash-key? chk))
146 (%make-chk-uri file-length chk))
147
148 ;; TODO: location URIs, ksk URIs? 143 ;; TODO: location URIs, ksk URIs?
149 ;; Why does GNUnet have location URIs? 144 ;; Why does GNUnet have location URIs?
150 145
@@ -163,7 +158,7 @@ error."
163 (query-hashcode (ascii->hashcode query-match)) 158 (query-hashcode (ascii->hashcode query-match))
164 (size (string->number size-match 10)) 159 (size (string->number size-match 10))
165 (size-ok (< size file-length-limit))) 160 (size-ok (< size file-length-limit)))
166 (%make-chk-uri size 161 (make-chk-uri size
167 (%make-content-hash-key key-hashcode 162 (make-content-hash-key key-hashcode
168 query-hashcode))))))) 163 query-hashcode)))))))
169 164
diff --git a/gnu/gnunet/hashcode.scm b/gnu/gnunet/hashcode.scm
index 2137755..d1b9c6b 100644
--- a/gnu/gnunet/hashcode.scm
+++ b/gnu/gnunet/hashcode.scm
@@ -1,6 +1,6 @@
1;#!r6rs 1;#!r6rs
2;; This file is part of scheme-GNUnet, a partial Scheme port of GNUnet. 2;; This file is part of scheme-GNUnet, a partial Scheme port of GNUnet.
3;; Copyright (C) 2006--2020, 2022 GNUnet e.V. 3;; Copyright (C) 2006--2020, 2022--2023 GNUnet e.V.
4;; 4;;
5;; GNUnet is free software: you can redistribute it and/or modify it 5;; GNUnet is free software: you can redistribute it and/or modify it
6;; under the terms of the GNU Affero General Public License as published 6;; under the terms of the GNU Affero General Public License as published
@@ -29,7 +29,10 @@
29 copy-hashcode:512 copy-hashcode:256) 29 copy-hashcode:512 copy-hashcode:256)
30 (import (rnrs base) 30 (import (rnrs base)
31 (gnu gnunet utils bv-slice) 31 (gnu gnunet utils bv-slice)
32 (rnrs records syntactic)) 32 (only (gnu gnunet hashcode struct)
33 /hashcode:512 /hashcode:256)
34 (only (gnu gnunet utils records)
35 define-record-type*))
33 36
34 (define hashcode:512-bit-length 512) 37 (define hashcode:512-bit-length 512)
35 (define hashcode:256-bit-length 256) 38 (define hashcode:256-bit-length 256)
@@ -38,51 +41,35 @@
38 41
39 ;; A 512-bit hashcode. These are the default length for GNUnet, 42 ;; A 512-bit hashcode. These are the default length for GNUnet,
40 ;; using SHA-512. 43 ;; using SHA-512.
41 (define-record-type (<hashcode:512> make-hashcode:512/share hashcode:512?) 44 (define-record-type* (<hashcode:512> hashcode:512?)
42 (fields (immutable slice hashcode:512->slice)) 45 #:network-structure /hashcode:512
43 (opaque #t) 46 #:read-only-slice-wrapper #true
44 (sealed #t) 47 #:unwrap hashcode:512->slice
45 (protocol 48 #:constructor (make-hashcode:512/share
46 (lambda (%make) 49 "Make a hashcode, containing @var{slice} (a readable
47 (lambda (slice)
48 "Make a hashcode, containing @var{slice} (a readable
49@code{/hashcode:512} bytevector slice). @var{slice} may not be mutated 50@code{/hashcode:512} bytevector slice). @var{slice} may not be mutated
50while the constructed hashcode is in use." 51while the constructed hashcode is in use.")
51 (assert (= (slice-length slice) hashcode:512-u8-length)) 52 #:constructor/copy make-hashcode:512
52 (%make (slice/read-only slice)))))) 53 #:copy (copy-hashcode:512
53 54 "Make a copy of the hashcode:512 @var{hashcode:512}. This can be useful if
54 (define (make-hashcode:512 slice)
55 "Make a hashcode, containing @var{slice} (a readable @code{/hashcode:512}
56bytevector slice). @var{slice} may not be mutated while the constructed
57hashcode is in use."
58 (make-hashcode:512/share (slice-copy/read-only slice)))
59
60 (define (copy-hashcode:512 hashcode:512)
61 "Make a copy of the hashcode:512 @var{hashcode:512}. This can be useful if
62the slice used during the construction of @var{hashcode:512} is potentially 55the slice used during the construction of @var{hashcode:512} is potentially
63going to be mutated while a hashcode will still be in use." 56going to be mutated while a hashcode will still be in use."))
64 (make-hashcode:512 (hashcode:512->slice hashcode:512)))
65 57
66 ;; A 256-bit hashcode. Used under special conditions, like when space 58 ;; A 256-bit hashcode. Used under special conditions, like when space
67 ;; is critical and security is not impacted by it. 59 ;; is critical and security is not impacted by it.
68 (define-record-type (<hashcode:256> make-hashcode:256/share hashcode:256?) 60 (define-record-type* (<hashcode:256> hashcode:256?)
69 (fields (immutable slice hashcode:256->slice)) 61 #:network-structure /hashcode:256
70 (opaque #t) 62 #:read-only-slice-wrapper #true
71 (sealed #t) 63 #:unwrap hashcode:256->slice
72 (protocol 64 #:constructor (make-hashcode:256/share
73 (lambda (%make) 65 "Make a short hashcode, containing @var{slice} (a readable
74 (lambda (slice)
75 "Make a short hashcode, containing @var{slice} (a readable
76@code{/hashcode:256} bytevector slice). @var{slice} may not be mutated 66@code{/hashcode:256} bytevector slice). @var{slice} may not be mutated
77while the constructed short hashcode is in use." 67while the constructed short hashcode is in use.")
78 (assert (= (slice-length slice) hashcode:256-u8-length)) 68 #:constructor/copy make-hashcode:256
79 (%make (slice/read-only slice)))))) 69 #:copy (copy-hashcode:256
80 70 "Make a copy of the hashcode:256 @var{hashcode:256}. This can be useful if
81 (define (copy-hashcode:256 hashcode:256)
82 "Make a copy of the hashcode:256 @var{hashcode:256}. This can be useful if
83the slice used during the construction of @var{hashcode:256} is potentially 71the slice used during the construction of @var{hashcode:256} is potentially
84going to be mutated while a hashcode will still be in use." 72going to be mutated while a hashcode will still be in use."))
85 (make-hashcode:256 (hashcode:256->slice hashcode:256)))
86 73
87 (define (bv->hashcode:512 bv) 74 (define (bv->hashcode:512 bv)
88 "Read a hashcode from a bytevector (deprecated)." 75 "Read a hashcode from a bytevector (deprecated)."
diff --git a/gnu/gnunet/utils/records.scm b/gnu/gnunet/utils/records.scm
new file mode 100644
index 0000000..a4cefd8
--- /dev/null
+++ b/gnu/gnunet/utils/records.scm
@@ -0,0 +1,222 @@
1;; This file is part of scheme-GNUnet, a partial Scheme port of GNUnet.
2;; Copyright (C) 2023 GNUnet e.V.
3;;
4;; Scheme-GNUnet is free software: you can redistribute it and/or modify it
5;; under the terms of the GNU Affero General Public License as published
6;; by the Free Software Foundation, either version 3 of the License,
7;; or (at your option) any later version.
8;;
9;; Scheme-GNUnet is distributed in the hope that it will be useful, but
10;; WITHOUT ANY WARRANTY; without even the implied warranty of
11;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12;; Affero General Public License for more details.
13;;
14;; You should have received a copy of the GNU Affero General Public License
15;; along with this program. If not, see <http://www.gnu.org/licenses/>.
16;;
17;; SPDX-License-Identifier: AGPL-3.0-or-later
18(define-library (gnu gnunet utils records)
19 (export define-record-type*)
20 ;; keyword? cannot be used from (srfi srfi-88) because that sets
21 ;; a reader option.
22 (import (only (guile) define* keyword? error define-values pk)
23 (only (ice-9 match) match)
24 (only (rnrs base)
25 begin define lambda define-syntax cons quasiquote quote unquote
26 unquote-splicing apply reverse append null? eq? and not if
27 string? values map assert car cdr cadr cddr let or pair?)
28 (only (rnrs control) when unless)
29 (only (rnrs syntax-case)
30 syntax quasisyntax unsyntax unsyntax-splicing syntax-case
31 syntax->datum identifier? generate-temporaries)
32 (only (rnrs records syntactic) define-record-type)
33 (only (srfi srfi-1) assoc)
34 ;; in generated code
35 (only (rnrs base) =)
36 (only (gnu gnunet netstruct syntactic)
37 define-analyser sizeof)
38 (only (gnu gnunet utils bv-slice)
39 slice? slice-readable? slice-length
40 slice-contents-equal? slice/read-only
41 slice-copy/read-only))
42 (begin
43 (define unset (cons #false #false))
44
45 (define* (process fields^ <type> type?
46 #:key
47 (constructor unset)
48 (constructor/copy unset)
49 (read-only-slice-wrapper #false)
50 (equality unset)
51 (analyse unset)
52 (copy unset)
53 (unwrap unset)
54 (network-structure unset))
55 (define fields*
56 (match (syntax->datum read-only-slice-wrapper)
57 (#true
58 (unless (null? fields^)
59 (error "fields may not be manually defined when #:read-only-slice-wrapper is #true"))
60 (when (eq? network-structure unset)
61 (error "when #:read-only-slice-wrapper is set, #:network-structure must be defined"))
62 (when (eq? unwrap unset)
63 (error "when #:read-only-slice-wrapper is set, #:unwrap must be defined"))
64 `((,#'slice
65 (#:getter . ,unwrap)
66 (#:predicate . ,#`(lambda (s)
67 (and (slice? s)
68 (slice-readable? s)
69 (= (slice-length s)
70 (sizeof #,network-structure '())))))
71 (#:preprocess . ,#'slice/read-only)
72 (#:equality . ,#'slice-contents-equal?))))
73 (#false fields^)))
74 (when (and (not (eq? unwrap unset))
75 (eq? read-only-slice-wrapper unset))
76 (error "#:unwrap can only be used in combination with #:read-only-slice-wrapper #true"))
77 ;; s: unset, or syntax of the form '(identifier "docstring")',
78 ;; or syntax of the form 'identifier'.
79 ;;
80 ;; Return types: (unset | identifier), syntax
81 (define (maybe-identifier-maybe-with-docstring s)
82 (if (eq? s unset)
83 (values unset #false)
84 (syntax-case s ()
85 ((id docstring)
86 (and (identifier? #'id) (string? (syntax->datum #'docstring)))
87 (values #'id #'docstring))
88 (id
89 (identifier? #'id)
90 (values #'id #false)))))
91 (define-values (constructor** constructor-docstring)
92 (maybe-identifier-maybe-with-docstring constructor))
93 (define constructor*
94 (if (eq? constructor** unset)
95 ;; define-record-type always requires a constructor
96 (car (generate-temporaries '(stuff)))
97 constructor**))
98 (define-values (equality* equality-docstring)
99 (maybe-identifier-maybe-with-docstring equality))
100 (define-values (analyse* analyse-docstring)
101 (maybe-identifier-maybe-with-docstring analyse))
102 (define-values (copy* copy-docstring)
103 ;; The generated code for 'constructor/copy*' expects
104 ;; a 'copy' procedure to exist.
105 (if (and (eq? copy unset) (not (eq? constructor/copy unset)))
106 (car (generate-temporaries '(copy)))
107 (maybe-identifier-maybe-with-docstring copy)))
108 (define-values (constructor/copy* constructor/copy-docstring)
109 (maybe-identifier-maybe-with-docstring constructor/copy))
110 (define (field-name field) ; -> identifier
111 (car field))
112 (define (field-verify field)
113 (if (field-set field #:predicate)
114 #`(assert (#,(field-ref field #:predicate) #,(field-name field)))
115 #'#true)) ; exact value doesn't matter
116 (define (field-compare field this that)
117 (define g (field-ref field #:getter)) ; always defined
118 #`(#,(field-ref field #:equality) ; sometimes undefined
119 (#,g #,this)
120 (#,g #,that)))
121 (define (field->analyse-fragment field)
122 ;; TODO: #:network-structure-read, e.g. for when it's just a number?
123 #`(#,(field-ref field #:analyse) ; sometimes undefined
124 #,(field-ref field #:network-structure-select))) ; sometimes undefined
125 (define (field-clause field)
126 #`(immutable #,(field-name field)
127 #,(field-ref field #:getter)))
128 ;; TODO bail out if unrecognised field settings
129 (define (field-preprocess field)
130 (if (field-set field #:preprocess)
131 #`(#,(field-ref field #:preprocess) #,(field-name field))
132 (field-name field)))
133 #`(begin
134 (define-record-type (#,<type> #,constructor* #,type?)
135 (fields #,@(map field-clause fields*))
136 (protocol
137 (lambda (%make)
138 (lambda #,(map field-name fields*)
139 #,constructor-docstring
140 #,@(map field-verify fields*)
141 (%make #,@(map field-preprocess fields*)))))
142 (sealed #true)
143 (opaque #true))
144 #,@(if (eq? equality* unset)
145 #'()
146 #`((define (#,equality* this that)
147 #,equality-docstring
148 (and #,@(map (lambda (f) (field-compare f #'this #'that)) fields*)))))
149 #,@(if (eq? analyse* unset)
150 #'()
151 #`((define-analyser #,analyse* #,network-structure
152 #,analyse-docstring
153 (#,constructor*
154 #,@(map field->analyse-fragment fields*)))))
155 #,@(if (eq? copy* unset)
156 #'()
157 ;; Note: support for read-only-slice-wrapper = unset can be
158 ;; implemented if needed with some work.
159 (begin
160 (assert (eq? #true (syntax->datum read-only-slice-wrapper)))
161 #`((define (#,copy* slice)
162 (#,constructor*
163 (slice-copy/read-only
164 (#,(field-ref (car fields*) #:getter) slice)))))))
165 #,@(if (eq? constructor/copy* unset)
166 #'()
167 ;; Note: likewise.
168 (begin
169 (assert (eq? #true (syntax->datum read-only-slice-wrapper)))
170 #`((define (#,constructor/copy* object)
171 #,constructor/copy-docstring
172 (#,copy* (#,constructor* object))))))))
173
174 (define (field-ref field keyword)
175 (match (assoc keyword (cdr field))
176 ((key . value) value)
177 (_ (pk 'field-ref field keyword)
178 (error "missing keyword in field"))))
179
180 (define (field-set field keyword)
181 (pair? (assoc keyword (cdr field))))
182
183 (define (decompose-field-syntax stuff)
184 (define (decompose-field-syntax-stuff* rest accumulated)
185 (syntax-case rest ()
186 ;; Assuming no duplicates, the order of the keyword arguments
187 ;; doesn't matter, so no reversal needed here.
188 (() accumulated)
189 ((keyword value . rest*)
190 (keyword? (syntax->datum #'keyword))
191 (decompose-field-syntax-stuff*
192 #'rest*
193 `((,(syntax->datum #'keyword) . ,#'value) ,@accumulated)))))
194 (syntax-case stuff ()
195 ((name . rest)
196 (cons #'name (decompose-field-syntax-stuff* #'rest '())))))
197
198 (define (decompose-syntax s accumulated-fields accumulated-arguments k . k*)
199 (syntax-case s ()
200 ;; order of keyword arguments doesn't matter, so no reversal there.
201 ;; (it is assumed there are no duplicates)
202 (() (apply k (reverse accumulated-fields)
203 (append k* accumulated-arguments)))
204 ((#:field stuff . rest)
205 (apply decompose-syntax
206 #'rest
207 (cons (decompose-field-syntax #'stuff) accumulated-fields)
208 accumulated-arguments
209 k k*))
210 ((keyword value . rest) ; not #:field
211 (keyword? (syntax->datum #'keyword))
212 (apply decompose-syntax
213 #'rest
214 accumulated-fields
215 `(,(syntax->datum #'keyword) ,#'value ,@accumulated-arguments)
216 k k*))))
217
218 (define-syntax define-record-type*
219 (lambda (s)
220 (syntax-case s ()
221 ((_ (<type> type?) . stuff)
222 (decompose-syntax #'stuff '() '() process #'<type> #'type?)))))))