diff options
author | Maxime Devos <maximedevos@telenet.be> | 2023-01-29 23:36:29 +0100 |
---|---|---|
committer | Maxime Devos <maximedevos@telenet.be> | 2023-01-30 00:17:27 +0100 |
commit | 656f43559f488fbee4a4ebd13421136bfe32bcfc (patch) | |
tree | 4ee5a150472ed8c04e9e624184b2edb91e3b5bcf | |
parent | 6cc094bc15814353a462b118cf8e203270bd91d0 (diff) | |
download | gnunet-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.am | 1 | ||||
-rw-r--r-- | gnu/gnunet/cadet/client.scm | 39 | ||||
-rw-r--r-- | gnu/gnunet/fs/uri.scm | 81 | ||||
-rw-r--r-- | gnu/gnunet/hashcode.scm | 67 | ||||
-rw-r--r-- | gnu/gnunet/utils/records.scm | 222 |
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}). |
380 | The slices @var{peer} and @var{port} are copied, so future changes to them | 378 | The slices @var{peer} and @var{port} are copied, so future changes to them |
381 | do not have any impact on the cadet address." | 379 | do 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 |
121 | while the content hash key is in use." | 109 | while 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 |
50 | while the constructed hashcode is in use." | 51 | while 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} | ||
56 | bytevector slice). @var{slice} may not be mutated while the constructed | ||
57 | hashcode 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 | ||
62 | the slice used during the construction of @var{hashcode:512} is potentially | 55 | the slice used during the construction of @var{hashcode:512} is potentially |
63 | going to be mutated while a hashcode will still be in use." | 56 | going 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 |
77 | while the constructed short hashcode is in use." | 67 | while 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 | ||
83 | the slice used during the construction of @var{hashcode:256} is potentially | 71 | the slice used during the construction of @var{hashcode:256} is potentially |
84 | going to be mutated while a hashcode will still be in use." | 72 | going 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?))))))) | ||