bend-schema 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +230 -0
- package/core/LAWS.bend +379 -0
- package/core/LICENSE +23 -0
- package/core/PROOF.bend +4527 -0
- package/core/core.bend +1585 -0
- package/dist-core/core.d.mts +188 -0
- package/dist-core/core.mjs +2974 -0
- package/package.json +41 -0
- package/src/codec.ts +177 -0
- package/src/gen-cli.ts +61 -0
- package/src/gen.ts +96 -0
- package/src/index.ts +350 -0
package/core/core.bend
ADDED
|
@@ -0,0 +1,1585 @@
|
|
|
1
|
+
import Base
|
|
2
|
+
|
|
3
|
+
# bend-schema: a schema, and one check for every schema, proved once.
|
|
4
|
+
#
|
|
5
|
+
# The host turns any JSON value into a Raw with one universal codec that knows
|
|
6
|
+
# no schema: numbers that are whole and within the runtime's Nat become RNum,
|
|
7
|
+
# every other number, boolean or unsupported value becomes RBad. A list or an
|
|
8
|
+
# object past the codec's budget becomes one RTooBig, in its place: the codec
|
|
9
|
+
# counts elements and keys so that these walks never receive a value the
|
|
10
|
+
# runtime cannot carry, and a size becomes an error (TooLarge, at the node's
|
|
11
|
+
# path) rather than a crash. Lists and objects are chains built into Raw
|
|
12
|
+
# (RNil/RCons, REnd/RKey), so that every walk here is structural. RMissing is
|
|
13
|
+
# never produced by the codec: it is what looking up an absent key gives.
|
|
14
|
+
#
|
|
15
|
+
# A Schema says what a valid value is (`conforms`), and `check` finds the
|
|
16
|
+
# first thing wrong, with the path to it: depth first, lists in element order,
|
|
17
|
+
# objects in the schema's field order; an element of a bounded list is read
|
|
18
|
+
# whole before its bound, and an SListLen's element count is read before any
|
|
19
|
+
# element. Keys the schema does not name are ignored; a key it names must be
|
|
20
|
+
# present, and two constructors say what may be there instead: SOpt is a value
|
|
21
|
+
# that may be null, SOptional a field that may be absent (the host writes no
|
|
22
|
+
# key at all, so it is well-formed only as a field's own schema).
|
|
23
|
+
#
|
|
24
|
+
# Both walk the schema and the value together, in one self-recursive def: a
|
|
25
|
+
# step into a field or an optional shrinks the schema, a step along a list
|
|
26
|
+
# keeps it and shrinks the value (the checker reads the arguments left to
|
|
27
|
+
# right until one shrinks). A project's rules arrive as one template
|
|
28
|
+
# parameter, `~rule: Nat -> Raw -> Maybe<Err>`: at an SRule the value must
|
|
29
|
+
# conform to the rule's schema first, then the rule decides (the tag picks
|
|
30
|
+
# the rule; the rule's error path is relative to the value it was given).
|
|
31
|
+
# Template defs cannot cross the bundler, so the host calls the closed forms
|
|
32
|
+
# below (check0, conforms0). `prev` is carried but no case reads it today; a
|
|
33
|
+
# stateful combinator would.
|
|
34
|
+
|
|
35
|
+
type Raw is Data:
|
|
36
|
+
RNum{n: Nat}
|
|
37
|
+
RBool{b: Bool}
|
|
38
|
+
RNull{}
|
|
39
|
+
RStr{s: String}
|
|
40
|
+
RBad{}
|
|
41
|
+
RTooBig{}
|
|
42
|
+
RMissing{}
|
|
43
|
+
RNil{}
|
|
44
|
+
RCons{head: Raw, tail: Raw}
|
|
45
|
+
REnd{}
|
|
46
|
+
RKey{key: String, val: Raw, rest: Raw}
|
|
47
|
+
|
|
48
|
+
type Schema is Data:
|
|
49
|
+
SNat{}
|
|
50
|
+
SNatIn{lo: Nat, hi: Nat}
|
|
51
|
+
SStr{}
|
|
52
|
+
SStrLen{lo: Nat, hi: Nat, s: Schema}
|
|
53
|
+
SOpt{inner: Schema}
|
|
54
|
+
SList{elem: Schema}
|
|
55
|
+
SField{name: String, s: Schema, rest: Schema}
|
|
56
|
+
SEnd{}
|
|
57
|
+
SRule{s: Schema, tag: Nat}
|
|
58
|
+
SStrict{s: Schema}
|
|
59
|
+
STagged{key: String, name: String, s: Schema, rest: Schema}
|
|
60
|
+
STagEnd{key: String}
|
|
61
|
+
SBool{}
|
|
62
|
+
STrue{}
|
|
63
|
+
SEnum{names: List<&2, String>}
|
|
64
|
+
SVariant{name: String, s: Schema, rest: Schema}
|
|
65
|
+
SVEnd{}
|
|
66
|
+
STuple{s: Schema, rest: Schema}
|
|
67
|
+
STEnd{}
|
|
68
|
+
SOptional{inner: Schema}
|
|
69
|
+
SListLen{lo: Nat, hi: Nat, s: Schema}
|
|
70
|
+
|
|
71
|
+
# A step of a path. Positions count what was passed on the way, so that
|
|
72
|
+
# following a path never compares two names: AtIndex{i} is element i of a
|
|
73
|
+
# list, AtField{skip, name} the field `skip` places after this one in the
|
|
74
|
+
# schema, BoundAt{i, key} the bound of element i of a bounded list. The names
|
|
75
|
+
# are for the host to print.
|
|
76
|
+
type Step is Data:
|
|
77
|
+
AtIndex{i: Nat}
|
|
78
|
+
AtField{skip: Nat, name: String}
|
|
79
|
+
BoundAt{i: Nat, key: String}
|
|
80
|
+
AtKey{key: String}
|
|
81
|
+
|
|
82
|
+
type Why is Data:
|
|
83
|
+
Missing{}
|
|
84
|
+
NotNat{}
|
|
85
|
+
NotString{}
|
|
86
|
+
NotBool{}
|
|
87
|
+
NotList{}
|
|
88
|
+
NotObject{}
|
|
89
|
+
NoElements{}
|
|
90
|
+
OpenNotLast{}
|
|
91
|
+
LastNotOpen{}
|
|
92
|
+
NotIncreasing{prev: Nat, got: Nat}
|
|
93
|
+
NotTrue{}
|
|
94
|
+
NotOneOf{}
|
|
95
|
+
NoVariant{}
|
|
96
|
+
TwoVariants{}
|
|
97
|
+
TooShort{}
|
|
98
|
+
TooLong{}
|
|
99
|
+
LengthNotIn{lo: Nat, hi: Nat}
|
|
100
|
+
NotIn{lo: Nat, hi: Nat}
|
|
101
|
+
UnknownKey{}
|
|
102
|
+
RepeatedKey{key: String}
|
|
103
|
+
TooLarge{}
|
|
104
|
+
CountNotIn{lo: Nat, hi: Nat}
|
|
105
|
+
|
|
106
|
+
type Err is Data:
|
|
107
|
+
Err{path: List<&2, Step>, why: Why}
|
|
108
|
+
|
|
109
|
+
# ---- reading a value ----
|
|
110
|
+
|
|
111
|
+
def pick_raw(b: Bool, +x: Raw, +y: Raw) -> Raw:
|
|
112
|
+
match b:
|
|
113
|
+
case True{}:
|
|
114
|
+
x
|
|
115
|
+
case False{}:
|
|
116
|
+
y
|
|
117
|
+
|
|
118
|
+
# The value under `name` in an object, or RMissing.
|
|
119
|
+
def lookup(+name: String, r: Raw) -> Raw:
|
|
120
|
+
match r:
|
|
121
|
+
case RKey{k, +v, rest}:
|
|
122
|
+
pick_raw(String.eq(k, name), v, lookup(name, rest))
|
|
123
|
+
case _:
|
|
124
|
+
RMissing{}
|
|
125
|
+
|
|
126
|
+
def pick_bool(b: Bool, +x: Bool, +y: Bool) -> Bool:
|
|
127
|
+
match b:
|
|
128
|
+
case True{}:
|
|
129
|
+
x
|
|
130
|
+
case False{}:
|
|
131
|
+
y
|
|
132
|
+
|
|
133
|
+
def is_rnil(r: Raw) -> Bool:
|
|
134
|
+
match r:
|
|
135
|
+
case RNil{}:
|
|
136
|
+
True{}
|
|
137
|
+
case _:
|
|
138
|
+
False{}
|
|
139
|
+
|
|
140
|
+
# ---- the helpers of the choices: true, one of some names, one of some keys ----
|
|
141
|
+
|
|
142
|
+
def is_missing(v: Raw) -> Bool:
|
|
143
|
+
match v:
|
|
144
|
+
case RMissing{}:
|
|
145
|
+
True{}
|
|
146
|
+
case _:
|
|
147
|
+
False{}
|
|
148
|
+
|
|
149
|
+
def in_names(+x: String, names: List<&2, String>) -> Bool:
|
|
150
|
+
match names:
|
|
151
|
+
case Nil{}:
|
|
152
|
+
False{}
|
|
153
|
+
case n <> t:
|
|
154
|
+
Bool.or(String.eq(x, n), in_names(x, t))
|
|
155
|
+
|
|
156
|
+
# None of the keys of a variant chain is in the object r.
|
|
157
|
+
def none_present(s: Schema, +r: Raw) -> Bool:
|
|
158
|
+
match s:
|
|
159
|
+
case SVariant{n, vs, rest}:
|
|
160
|
+
Bool.and(is_missing(lookup(n, r)), none_present(rest, r))
|
|
161
|
+
case _:
|
|
162
|
+
True{}
|
|
163
|
+
|
|
164
|
+
# ---- a strict object: its keys ----
|
|
165
|
+
#
|
|
166
|
+
# The names a record or variant chain declares. SStrict refuses a key that is
|
|
167
|
+
# not one of them; wf asks that SStrict wrap such a chain.
|
|
168
|
+
def key_names(s: Schema) -> List<&2, String>:
|
|
169
|
+
match s:
|
|
170
|
+
case SField{n, fs, rest}:
|
|
171
|
+
n <> key_names(rest)
|
|
172
|
+
case SVariant{n, vs, rest}:
|
|
173
|
+
n <> key_names(rest)
|
|
174
|
+
case _:
|
|
175
|
+
Nil{}
|
|
176
|
+
|
|
177
|
+
# Every key of the object r is one of ns (anything but an object has none).
|
|
178
|
+
def no_extra(+ns: List<&2, String>, r: Raw) -> Bool:
|
|
179
|
+
match r:
|
|
180
|
+
case RKey{+k, v, o}:
|
|
181
|
+
Bool.and(in_names(k, ns), no_extra(ns, o))
|
|
182
|
+
case _:
|
|
183
|
+
True{}
|
|
184
|
+
|
|
185
|
+
# ---- a tagged union: its tag, and the object a case sees ----
|
|
186
|
+
#
|
|
187
|
+
# STagged{key, name, s, rest} is one case of a chain ending in STagEnd{key}:
|
|
188
|
+
# when the value under `key` is the string `name`, the object without that
|
|
189
|
+
# key must conform to s; otherwise the chain goes on. The case does not see
|
|
190
|
+
# the tag, so an SStrict case need not name it.
|
|
191
|
+
|
|
192
|
+
# The object r without its first key k (what lookup(k, r) reads).
|
|
193
|
+
def drop_key(+k: String, r: Raw) -> Raw:
|
|
194
|
+
match r:
|
|
195
|
+
case RKey{+j, +v, +o}:
|
|
196
|
+
pick_raw(String.eq(j, k), o, RKey{j, v, drop_key(k, o)})
|
|
197
|
+
case x:
|
|
198
|
+
x
|
|
199
|
+
|
|
200
|
+
def is_str(+n: String, v: Raw) -> Bool:
|
|
201
|
+
match v:
|
|
202
|
+
case RStr{x}:
|
|
203
|
+
String.eq(x, n)
|
|
204
|
+
case _:
|
|
205
|
+
False{}
|
|
206
|
+
|
|
207
|
+
# The tag under k is the string n.
|
|
208
|
+
def is_tag(+k: String, +n: String, r: Raw) -> Bool:
|
|
209
|
+
is_str(n, lookup(k, r))
|
|
210
|
+
|
|
211
|
+
# ---- a bound, as a test and as a reason ----
|
|
212
|
+
#
|
|
213
|
+
# SStrLen{lo, hi, s} is a string in the bounds that also satisfies s, SListLen
|
|
214
|
+
# the same over a list's elements, SNatIn is a number in the bounds, and SBool
|
|
215
|
+
# is a boolean. The bound travels with the schema instead of with a rule's tag,
|
|
216
|
+
# which is what lets a host build one (a rule is chosen by a number, and a
|
|
217
|
+
# number carries no bounds).
|
|
218
|
+
#
|
|
219
|
+
# A rule and a constructor state the same bounds, and these defs are where they
|
|
220
|
+
# are written once: str_len_ok and num_ok are the tests, in_err is how a failing
|
|
221
|
+
# test becomes an error at the value's own path, and len_ok is the same test
|
|
222
|
+
# read as a Bool (what conforms asks at an SStrLen). Both ends are included;
|
|
223
|
+
# lo > hi is not an error but an empty range, in which nothing is in bounds.
|
|
224
|
+
|
|
225
|
+
def in_err(b: Bool, w: Why) -> Maybe<&2, Err>:
|
|
226
|
+
match b:
|
|
227
|
+
case True{}:
|
|
228
|
+
None{}
|
|
229
|
+
case False{}:
|
|
230
|
+
Some{Err{Nil{}, w}}
|
|
231
|
+
|
|
232
|
+
def str_len_ok(+lo: Nat, +hi: Nat, +x: String) -> Bool:
|
|
233
|
+
Bool.and(Nat.is_le(lo, String.length(x)), Nat.is_le(String.length(x), hi))
|
|
234
|
+
|
|
235
|
+
def num_ok(+lo: Nat, +hi: Nat, +n: Nat) -> Bool:
|
|
236
|
+
Bool.and(Nat.is_le(lo, n), Nat.is_le(n, hi))
|
|
237
|
+
|
|
238
|
+
# r is a string whose length is in the bounds: what conforms reads at an
|
|
239
|
+
# SStrLen, and what len_err refuses exactly when it fails.
|
|
240
|
+
def len_ok(+lo: Nat, +hi: Nat, r: Raw) -> Bool:
|
|
241
|
+
match r:
|
|
242
|
+
case RStr{+x}:
|
|
243
|
+
str_len_ok(lo, hi, x)
|
|
244
|
+
case _:
|
|
245
|
+
False{}
|
|
246
|
+
|
|
247
|
+
# The same bounds as ready-made rules: a project's rule calls them by tag
|
|
248
|
+
# (`case 0n: str_len_in(1n, 64n, r)`), since a tag carries no numbers. Each
|
|
249
|
+
# refuses only the value it is about -- a string, a number -- and leaves any
|
|
250
|
+
# other kind to the schema's own check; the constructors below read the tests
|
|
251
|
+
# directly, because their bound is in the schema.
|
|
252
|
+
def str_len_in(+lo: Nat, +hi: Nat, r: Raw) -> Maybe<&2, Err>:
|
|
253
|
+
match r:
|
|
254
|
+
case RStr{+x}:
|
|
255
|
+
in_err(str_len_ok(lo, hi, x), LengthNotIn{lo, hi})
|
|
256
|
+
case _:
|
|
257
|
+
None{}
|
|
258
|
+
|
|
259
|
+
def nat_in(+lo: Nat, +hi: Nat, r: Raw) -> Maybe<&2, Err>:
|
|
260
|
+
match r:
|
|
261
|
+
case RNum{+n}:
|
|
262
|
+
in_err(num_ok(lo, hi, n), NotIn{lo, hi})
|
|
263
|
+
case _:
|
|
264
|
+
None{}
|
|
265
|
+
|
|
266
|
+
def is_bool(r: Raw) -> Bool:
|
|
267
|
+
match r:
|
|
268
|
+
case RBool{b}:
|
|
269
|
+
True{}
|
|
270
|
+
case _:
|
|
271
|
+
False{}
|
|
272
|
+
|
|
273
|
+
# A list's element count, as a test. SListLen{lo, hi, s} is a list whose
|
|
274
|
+
# element count is in the bounds that also satisfies s: the same shape an
|
|
275
|
+
# SStrLen has, over elements instead of characters. raw_list is "r is a list at
|
|
276
|
+
# all", raw_len counts a proper list's elements (0 for anything else, which is
|
|
277
|
+
# why every use asks raw_list too), count_ok is the bound read as a Bool, and
|
|
278
|
+
# list_len_ok is both together -- what conforms asks at an SListLen, and what
|
|
279
|
+
# count_err refuses exactly when it fails. Both ends are included; lo > hi is
|
|
280
|
+
# not an error but an empty range, in which no list is in bounds.
|
|
281
|
+
|
|
282
|
+
def raw_list(r: Raw) -> Bool:
|
|
283
|
+
match r:
|
|
284
|
+
case RNil{}:
|
|
285
|
+
True{}
|
|
286
|
+
case RCons{h, t}:
|
|
287
|
+
raw_list(t)
|
|
288
|
+
case _:
|
|
289
|
+
False{}
|
|
290
|
+
|
|
291
|
+
def raw_len(r: Raw) -> Nat:
|
|
292
|
+
match r:
|
|
293
|
+
case RCons{h, t}:
|
|
294
|
+
1n+raw_len(t)
|
|
295
|
+
case _:
|
|
296
|
+
0n
|
|
297
|
+
|
|
298
|
+
def count_ok(+lo: Nat, +hi: Nat, +r: Raw) -> Bool:
|
|
299
|
+
Bool.and(Nat.is_le(lo, raw_len(r)), Nat.is_le(raw_len(r), hi))
|
|
300
|
+
|
|
301
|
+
def list_len_ok(+lo: Nat, +hi: Nat, +r: Raw) -> Bool:
|
|
302
|
+
Bool.and(raw_list(r), count_ok(lo, hi, r))
|
|
303
|
+
|
|
304
|
+
# ---- a key the schema reads ----
|
|
305
|
+
#
|
|
306
|
+
# A key the schema reads as a field key (SField) or as a variant key (SVariant)
|
|
307
|
+
# must appear at most once in the value: with a key repeated, lookup reads the
|
|
308
|
+
# first one and the second is unreachable, so the value says something the
|
|
309
|
+
# schema cannot mean. A key the schema does not name is never read, so
|
|
310
|
+
# repeating it is unobservable and is left alone.
|
|
311
|
+
#
|
|
312
|
+
# The tag key of a tagged case is read the same way -- it is how the chain
|
|
313
|
+
# picks its case -- so it is checked the same way, and at the case the tag
|
|
314
|
+
# names: conforms, check and defect all ask key_once at the value's own tag
|
|
315
|
+
# key, and a case whose tag matched is refused when the key is there twice.
|
|
316
|
+
# The check sits at the case and not at the chain because a value whose tag
|
|
317
|
+
# names no case is refused at the chain's end whatever its keys are, and
|
|
318
|
+
# because the case is the only place the question has an answer: the case is
|
|
319
|
+
# checked against the object with the tag key taken out, which is the object
|
|
320
|
+
# its own schema is about.
|
|
321
|
+
#
|
|
322
|
+
# So the other half is wf's: a case's own schema may not read the tag key at
|
|
323
|
+
# all (no_key, above wf). If it did, enc would write that key twice -- the tag
|
|
324
|
+
# first, then the case's own field -- and the host builds the object as a JS
|
|
325
|
+
# object, where the second write overwrites the first, so the value written
|
|
326
|
+
# could not be read back. Refusing the schema is what keeps encode_conforms
|
|
327
|
+
# true.
|
|
328
|
+
|
|
329
|
+
# k is a key of the object r.
|
|
330
|
+
def has_key(+k: String, r: Raw) -> Bool:
|
|
331
|
+
match r:
|
|
332
|
+
case RKey{j, v, o}:
|
|
333
|
+
Bool.or(String.eq(k, j), has_key(k, o))
|
|
334
|
+
case _:
|
|
335
|
+
False{}
|
|
336
|
+
|
|
337
|
+
# name appears at most once among the keys of the object r. b is whether a key
|
|
338
|
+
# read before r was already name; the flag carries forward, and a key that is
|
|
339
|
+
# name while the flag is set is the second one -- refused here and nowhere
|
|
340
|
+
# else. The value is the first argument because a self-call must read its
|
|
341
|
+
# arguments left to right, unchanged until one shrinks, and only the value
|
|
342
|
+
# shrinks; the value is matched first because a match cannot be nested inside
|
|
343
|
+
# the branch of a match on another parameter. One pass, so a walk costs one
|
|
344
|
+
# key per key, the same as the lookup beside it.
|
|
345
|
+
def key_once_at(r: Raw, +b: Bool, +name: String) -> Bool:
|
|
346
|
+
match r:
|
|
347
|
+
case RKey{+k, +v, +o}:
|
|
348
|
+
Bool.and(Bool.not(Bool.and(b, String.eq(k, name))), key_once_at(o, Bool.or(b, String.eq(k, name)), name))
|
|
349
|
+
case _:
|
|
350
|
+
True{}
|
|
351
|
+
|
|
352
|
+
# name appears at most once among the keys of the object r: nothing has been
|
|
353
|
+
# read yet.
|
|
354
|
+
def key_once(+name: String, r: Raw) -> Bool:
|
|
355
|
+
key_once_at(r, False{}, name)
|
|
356
|
+
|
|
357
|
+
# The repeated key as check reports it: at the key's own name, with no step
|
|
358
|
+
# after it (the second occurrence is not a place in the schema). The test is
|
|
359
|
+
# passed in rather than read here (a match cannot scrutinize a computed value):
|
|
360
|
+
# b is key_once(name, r), so nothing is reported exactly when key_once holds,
|
|
361
|
+
# which is what check_exact needs.
|
|
362
|
+
def key_err(b: Bool, +name: String) -> Maybe<&2, Err>:
|
|
363
|
+
match b:
|
|
364
|
+
case True{}:
|
|
365
|
+
None{}
|
|
366
|
+
case False{}:
|
|
367
|
+
Some{Err{AtField{0n, name} <> Nil{}, RepeatedKey{name}}}
|
|
368
|
+
|
|
369
|
+
# ---- what a valid value is ----
|
|
370
|
+
|
|
371
|
+
def conforms(~rule: Nat -> Raw -> Maybe<&2, Err>, s: Schema, r: Raw, +prev: Maybe<&2, Nat>) -> Bool:
|
|
372
|
+
match s r:
|
|
373
|
+
case SNat{} RNum{n}:
|
|
374
|
+
True{}
|
|
375
|
+
case SNatIn{+lo, +hi} RNum{+n}:
|
|
376
|
+
num_ok(lo, hi, n)
|
|
377
|
+
case SStr{} RStr{x}:
|
|
378
|
+
True{}
|
|
379
|
+
case SStrLen{+lo, +hi, +s2} +x:
|
|
380
|
+
Bool.and(conforms(~rule, s2, x, prev), len_ok(lo, hi, x))
|
|
381
|
+
case SListLen{+lo, +hi, +s2} +r:
|
|
382
|
+
Bool.and(list_len_ok(lo, hi, r), conforms(~rule, s2, r, None{}))
|
|
383
|
+
case SOpt{inner} RNull{}:
|
|
384
|
+
True{}
|
|
385
|
+
case SOpt{inner} x:
|
|
386
|
+
conforms(~rule, inner, x, None{})
|
|
387
|
+
case SOptional{+inner} +x:
|
|
388
|
+
Bool.or(is_missing(x), conforms(~rule, inner, x, None{}))
|
|
389
|
+
case SList{e} RNil{}:
|
|
390
|
+
True{}
|
|
391
|
+
case SList{+e} RCons{h, t}:
|
|
392
|
+
Bool.and(conforms(~rule, e, h, None{}), conforms(~rule, SList{e}, t, None{}))
|
|
393
|
+
case SField{+name, fs, rest} REnd{}:
|
|
394
|
+
Bool.and(conforms(~rule, fs, RMissing{}, None{}), conforms(~rule, rest, REnd{}, None{}))
|
|
395
|
+
case SField{+name, fs, rest} RKey{+k, +v, +o}:
|
|
396
|
+
Bool.and(key_once(name, RKey{k, v, o}), Bool.and(conforms(~rule, fs, lookup(name, RKey{k, v, o}), None{}), conforms(~rule, rest, RKey{k, v, o}, None{})))
|
|
397
|
+
case SEnd{} REnd{}:
|
|
398
|
+
True{}
|
|
399
|
+
case SEnd{} RKey{k, v, o}:
|
|
400
|
+
True{}
|
|
401
|
+
case SRule{+s2, +tag} +x:
|
|
402
|
+
Bool.and(conforms(~rule, s2, x, prev), Maybe.is_none(&2, Err, rule(tag, x)))
|
|
403
|
+
case SStrict{+s2} +x:
|
|
404
|
+
Bool.and(conforms(~rule, s2, x, prev), no_extra(key_names(s2), x))
|
|
405
|
+
case STagged{+k, +n, cs, rest} +x:
|
|
406
|
+
pick_bool(is_tag(k, n, x), Bool.and(key_once(k, x), conforms(~rule, cs, drop_key(k, x), None{})), conforms(~rule, rest, x, None{}))
|
|
407
|
+
case STagEnd{k} x:
|
|
408
|
+
False{} # the end of a tagged chain: no case matched
|
|
409
|
+
case SBool{} RBool{b}:
|
|
410
|
+
True{} # any boolean is a boolean, and nothing else is
|
|
411
|
+
case STrue{} RBool{b}:
|
|
412
|
+
b
|
|
413
|
+
case SEnum{names} RStr{+x}:
|
|
414
|
+
in_names(x, names)
|
|
415
|
+
case SVariant{+name, +vs, +rest} REnd{}:
|
|
416
|
+
conforms(~rule, rest, REnd{}, None{})
|
|
417
|
+
case SVariant{+name, +vs, +rest} RKey{+k, +v, +o}:
|
|
418
|
+
pick_bool(is_missing(lookup(name, RKey{k, v, o})), conforms(~rule, rest, RKey{k, v, o}, None{}), Bool.and(key_once(name, RKey{k, v, o}), Bool.and(conforms(~rule, vs, lookup(name, RKey{k, v, o}), None{}), none_present(rest, RKey{k, v, o}))))
|
|
419
|
+
case STuple{ts, rest} RCons{h, t}:
|
|
420
|
+
Bool.and(conforms(~rule, ts, h, None{}), conforms(~rule, rest, t, None{}))
|
|
421
|
+
case STEnd{} RNil{}:
|
|
422
|
+
True{}
|
|
423
|
+
case _ _:
|
|
424
|
+
False{}
|
|
425
|
+
|
|
426
|
+
# ---- the first thing wrong ----
|
|
427
|
+
|
|
428
|
+
def here(w: Why) -> Maybe<&2, Err>:
|
|
429
|
+
Some{Err{Nil{}, w}}
|
|
430
|
+
|
|
431
|
+
# An error found under a step gets the step in front of its path.
|
|
432
|
+
def under(+st: Step, m: Maybe<&2, Err>) -> Maybe<&2, Err>:
|
|
433
|
+
match m:
|
|
434
|
+
case None{}:
|
|
435
|
+
None{}
|
|
436
|
+
case Some{Err{p, w}}:
|
|
437
|
+
Some{Err{st <> p, w}}
|
|
438
|
+
|
|
439
|
+
# At an SStrLen the shape comes first, as everywhere else: a value that is not
|
|
440
|
+
# a string is NotString, a string out of its bounds is LengthNotIn, and a node
|
|
441
|
+
# the codec refused to build or that is absent reads as it does at every kind.
|
|
442
|
+
def len_err(+lo: Nat, +hi: Nat, r: Raw) -> Maybe<&2, Err>:
|
|
443
|
+
match r:
|
|
444
|
+
case RStr{+x}:
|
|
445
|
+
in_err(str_len_ok(lo, hi, x), LengthNotIn{lo, hi})
|
|
446
|
+
case RMissing{}:
|
|
447
|
+
here(Missing{})
|
|
448
|
+
case RTooBig{}:
|
|
449
|
+
here(TooLarge{})
|
|
450
|
+
case _:
|
|
451
|
+
here(NotString{})
|
|
452
|
+
|
|
453
|
+
# An error found further along a list is one element further: its first
|
|
454
|
+
# step, if it counts elements, counts one more. A plain list's steps are
|
|
455
|
+
# AtIndex; a bounded list's are AtIndex and BoundAt.
|
|
456
|
+
def later_l_path(p: List<&2, Step>) -> List<&2, Step>:
|
|
457
|
+
match p:
|
|
458
|
+
case AtIndex{j} <> q:
|
|
459
|
+
AtIndex{1n+j} <> q
|
|
460
|
+
case q:
|
|
461
|
+
q
|
|
462
|
+
|
|
463
|
+
def later_l(m: Maybe<&2, Err>) -> Maybe<&2, Err>:
|
|
464
|
+
match m:
|
|
465
|
+
case None{}:
|
|
466
|
+
None{}
|
|
467
|
+
case Some{Err{p, w}}:
|
|
468
|
+
Some{Err{later_l_path(p), w}}
|
|
469
|
+
|
|
470
|
+
def later_i_path(p: List<&2, Step>) -> List<&2, Step>:
|
|
471
|
+
match p:
|
|
472
|
+
case AtIndex{j} <> q:
|
|
473
|
+
AtIndex{1n+j} <> q
|
|
474
|
+
case BoundAt{j, key} <> q:
|
|
475
|
+
BoundAt{1n+j, key} <> q
|
|
476
|
+
case q:
|
|
477
|
+
q
|
|
478
|
+
|
|
479
|
+
def later_i(m: Maybe<&2, Err>) -> Maybe<&2, Err>:
|
|
480
|
+
match m:
|
|
481
|
+
case None{}:
|
|
482
|
+
None{}
|
|
483
|
+
case Some{Err{p, w}}:
|
|
484
|
+
Some{Err{later_i_path(p), w}}
|
|
485
|
+
|
|
486
|
+
# An error found in a later field is one field further.
|
|
487
|
+
def later_f_path(p: List<&2, Step>) -> List<&2, Step>:
|
|
488
|
+
match p:
|
|
489
|
+
case AtField{k, n} <> q:
|
|
490
|
+
AtField{1n+k, n} <> q
|
|
491
|
+
case q:
|
|
492
|
+
q
|
|
493
|
+
|
|
494
|
+
def later_f(m: Maybe<&2, Err>) -> Maybe<&2, Err>:
|
|
495
|
+
match m:
|
|
496
|
+
case None{}:
|
|
497
|
+
None{}
|
|
498
|
+
case Some{Err{p, w}}:
|
|
499
|
+
Some{Err{later_f_path(p), w}}
|
|
500
|
+
|
|
501
|
+
# The first of two: the second counts only when the first found nothing.
|
|
502
|
+
def first(m: Maybe<&2, Err>, n: Maybe<&2, Err>) -> Maybe<&2, Err>:
|
|
503
|
+
match m:
|
|
504
|
+
case None{}:
|
|
505
|
+
n
|
|
506
|
+
case Some{e}:
|
|
507
|
+
Some{e}
|
|
508
|
+
|
|
509
|
+
# The reason a value that is not the shape is refused, at the place it was
|
|
510
|
+
# found: an absent slot is Missing, a node the host refused to build (RTooBig)
|
|
511
|
+
# is TooLarge, anything else is the schema kind's own reason. check and the
|
|
512
|
+
# defect helpers both answer through here, so the reason a path reports is the
|
|
513
|
+
# same one for both.
|
|
514
|
+
def missing_or(r: Raw, w: Why) -> Why:
|
|
515
|
+
match r:
|
|
516
|
+
case RMissing{}:
|
|
517
|
+
Missing{}
|
|
518
|
+
case RTooBig{}:
|
|
519
|
+
TooLarge{}
|
|
520
|
+
case _:
|
|
521
|
+
w
|
|
522
|
+
|
|
523
|
+
def pick_err(b: Bool, +x: Maybe<&2, Err>, +y: Maybe<&2, Err>) -> Maybe<&2, Err>:
|
|
524
|
+
match b:
|
|
525
|
+
case True{}:
|
|
526
|
+
x
|
|
527
|
+
case False{}:
|
|
528
|
+
y
|
|
529
|
+
|
|
530
|
+
def true_err(b: Bool) -> Maybe<&2, Err>:
|
|
531
|
+
match b:
|
|
532
|
+
case True{}:
|
|
533
|
+
None{}
|
|
534
|
+
case False{}:
|
|
535
|
+
here(NotTrue{})
|
|
536
|
+
|
|
537
|
+
def enum_err(b: Bool) -> Maybe<&2, Err>:
|
|
538
|
+
match b:
|
|
539
|
+
case True{}:
|
|
540
|
+
None{}
|
|
541
|
+
case False{}:
|
|
542
|
+
here(NotOneOf{})
|
|
543
|
+
|
|
544
|
+
# A second key of a variant chain in the object r: the first one found.
|
|
545
|
+
def dup_err(s: Schema, +r: Raw) -> Maybe<&2, Err>:
|
|
546
|
+
match s:
|
|
547
|
+
case SVariant{+n, vs, rest}:
|
|
548
|
+
pick_err(is_missing(lookup(n, r)), later_f(dup_err(rest, r)), Some{Err{AtField{0n, n} <> Nil{}, TwoVariants{}}})
|
|
549
|
+
case _:
|
|
550
|
+
None{}
|
|
551
|
+
|
|
552
|
+
# The first key of r that is not one of ns, at its own name.
|
|
553
|
+
def extra_err(+ns: List<&2, String>, r: Raw) -> Maybe<&2, Err>:
|
|
554
|
+
match r:
|
|
555
|
+
case RKey{+k, v, o}:
|
|
556
|
+
pick_err(in_names(k, ns), extra_err(ns, o), Some{Err{AtKey{k} <> Nil{}, UnknownKey{}}})
|
|
557
|
+
case _:
|
|
558
|
+
None{}
|
|
559
|
+
|
|
560
|
+
# At the end of a tagged chain no case matched: the value is not an object,
|
|
561
|
+
# or its tag is missing, or it names no case.
|
|
562
|
+
def tag_err(+k: String, r: Raw) -> Maybe<&2, Err>:
|
|
563
|
+
match r:
|
|
564
|
+
case REnd{}:
|
|
565
|
+
Some{Err{AtKey{k} <> Nil{}, Missing{}}}
|
|
566
|
+
case RKey{j, v, o}:
|
|
567
|
+
Some{Err{AtKey{k} <> Nil{}, missing_or(lookup(k, RKey{j, v, o}), NotOneOf{})}}
|
|
568
|
+
case x:
|
|
569
|
+
here(missing_or(x, NotObject{}))
|
|
570
|
+
|
|
571
|
+
# The count, as an error: at an SListLen the count is read before any element,
|
|
572
|
+
# so a list out of bounds is CountNotIn at the list's own path. A value that is
|
|
573
|
+
# not a list has no count to object to, and is refused as a list schema refuses
|
|
574
|
+
# it: NotList, or Missing/TooLarge for the two nodes that are never a shape
|
|
575
|
+
# (the same reasons check's own arms give them).
|
|
576
|
+
def count_err(+lo: Nat, +hi: Nat, r: Raw) -> Maybe<&2, Err>:
|
|
577
|
+
match r:
|
|
578
|
+
case RNil{}:
|
|
579
|
+
in_err(list_len_ok(lo, hi, r), CountNotIn{lo, hi})
|
|
580
|
+
case RCons{h, t}:
|
|
581
|
+
in_err(list_len_ok(lo, hi, r), CountNotIn{lo, hi})
|
|
582
|
+
case _:
|
|
583
|
+
here(missing_or(r, NotList{}))
|
|
584
|
+
|
|
585
|
+
def check(~rule: Nat -> Raw -> Maybe<&2, Err>, s: Schema, r: Raw, +prev: Maybe<&2, Nat>) -> Maybe<&2, Err>:
|
|
586
|
+
match s r:
|
|
587
|
+
case SNat{} RNum{n}:
|
|
588
|
+
None{}
|
|
589
|
+
case SNat{} x:
|
|
590
|
+
here(missing_or(x, NotNat{}))
|
|
591
|
+
case SStr{} RStr{x}:
|
|
592
|
+
None{}
|
|
593
|
+
case SStr{} x:
|
|
594
|
+
here(missing_or(x, NotString{}))
|
|
595
|
+
case SNatIn{+lo, +hi} RNum{+n}:
|
|
596
|
+
nat_in(lo, hi, RNum{n})
|
|
597
|
+
case SNatIn{lo_, hi_} +x:
|
|
598
|
+
here(missing_or(x, NotNat{}))
|
|
599
|
+
case SStrLen{+lo, +hi, +s2} +x:
|
|
600
|
+
first(check(~rule, s2, x, prev), len_err(lo, hi, x))
|
|
601
|
+
case SListLen{+lo, +hi, +s2} +r:
|
|
602
|
+
first(count_err(lo, hi, r), check(~rule, s2, r, None{}))
|
|
603
|
+
case SOpt{inner} RNull{}:
|
|
604
|
+
None{}
|
|
605
|
+
case SOpt{inner} x:
|
|
606
|
+
check(~rule, inner, x, None{})
|
|
607
|
+
case SOptional{inner} RMissing{}:
|
|
608
|
+
None{}
|
|
609
|
+
case SOptional{inner} x:
|
|
610
|
+
check(~rule, inner, x, None{})
|
|
611
|
+
case SList{e} RNil{}:
|
|
612
|
+
None{}
|
|
613
|
+
case SList{+e} RCons{h, t}:
|
|
614
|
+
first(under(AtIndex{0n}, check(~rule, e, h, None{})), later_l(check(~rule, SList{e}, t, None{})))
|
|
615
|
+
case SList{e} x:
|
|
616
|
+
here(missing_or(x, NotList{}))
|
|
617
|
+
case SField{+name, fs, rest} REnd{}:
|
|
618
|
+
first(under(AtField{0n, name}, check(~rule, fs, RMissing{}, None{})), later_f(check(~rule, rest, REnd{}, None{})))
|
|
619
|
+
case SField{+name, fs, rest} RKey{+k, +v, +o}:
|
|
620
|
+
first(key_err(key_once(name, RKey{k, v, o}), name), first(under(AtField{0n, name}, check(~rule, fs, lookup(name, RKey{k, v, o}), None{})), later_f(check(~rule, rest, RKey{k, v, o}, None{}))))
|
|
621
|
+
case SField{name, fs, rest} x:
|
|
622
|
+
here(missing_or(x, NotObject{}))
|
|
623
|
+
case SEnd{} REnd{}:
|
|
624
|
+
None{}
|
|
625
|
+
case SEnd{} RKey{k, v, o}:
|
|
626
|
+
None{}
|
|
627
|
+
case SEnd{} x:
|
|
628
|
+
here(missing_or(x, NotObject{}))
|
|
629
|
+
case SRule{+s2, +tag} +x:
|
|
630
|
+
first(check(~rule, s2, x, prev), rule(tag, x))
|
|
631
|
+
case SStrict{+s2} +x:
|
|
632
|
+
first(check(~rule, s2, x, prev), extra_err(key_names(s2), x))
|
|
633
|
+
case STagged{+k, +n, cs, rest} +x:
|
|
634
|
+
pick_err(is_tag(k, n, x), first(key_err(key_once(k, x), k), check(~rule, cs, drop_key(k, x), None{})), check(~rule, rest, x, None{}))
|
|
635
|
+
case STagEnd{k} x:
|
|
636
|
+
tag_err(k, x)
|
|
637
|
+
case SBool{} RBool{b}:
|
|
638
|
+
None{}
|
|
639
|
+
case SBool{} +x:
|
|
640
|
+
here(missing_or(x, NotBool{}))
|
|
641
|
+
case STrue{} RBool{b}:
|
|
642
|
+
true_err(b)
|
|
643
|
+
case STrue{} x:
|
|
644
|
+
here(missing_or(x, NotTrue{}))
|
|
645
|
+
case SEnum{names} RStr{+x}:
|
|
646
|
+
enum_err(in_names(x, names))
|
|
647
|
+
case SEnum{names} x:
|
|
648
|
+
here(missing_or(x, NotOneOf{}))
|
|
649
|
+
case SVariant{+name, +vs, +rest} REnd{}:
|
|
650
|
+
later_f(check(~rule, rest, REnd{}, None{}))
|
|
651
|
+
case SVariant{+name, +vs, +rest} RKey{+k, +v, +o}:
|
|
652
|
+
pick_err(is_missing(lookup(name, RKey{k, v, o})), later_f(check(~rule, rest, RKey{k, v, o}, None{})), first(key_err(key_once(name, RKey{k, v, o}), name), first(under(AtField{0n, name}, check(~rule, vs, lookup(name, RKey{k, v, o}), None{})), later_f(dup_err(rest, RKey{k, v, o})))))
|
|
653
|
+
case SVariant{+name, +vs, +rest} x:
|
|
654
|
+
here(missing_or(x, NotObject{}))
|
|
655
|
+
case SVEnd{} REnd{}:
|
|
656
|
+
here(NoVariant{})
|
|
657
|
+
case SVEnd{} RKey{k, v, o}:
|
|
658
|
+
here(NoVariant{})
|
|
659
|
+
case SVEnd{} x:
|
|
660
|
+
here(missing_or(x, NotObject{}))
|
|
661
|
+
case STuple{ts, rest} RCons{h, t}:
|
|
662
|
+
first(under(AtIndex{0n}, check(~rule, ts, h, None{})), later_l(check(~rule, rest, t, None{})))
|
|
663
|
+
case STuple{ts, rest} RNil{}:
|
|
664
|
+
here(TooShort{})
|
|
665
|
+
case STuple{ts, rest} x:
|
|
666
|
+
here(missing_or(x, NotList{}))
|
|
667
|
+
case STEnd{} RNil{}:
|
|
668
|
+
None{}
|
|
669
|
+
case STEnd{} RCons{h, t}:
|
|
670
|
+
here(TooLong{})
|
|
671
|
+
case STEnd{} x:
|
|
672
|
+
here(missing_or(x, NotList{}))
|
|
673
|
+
|
|
674
|
+
# ---- the claim the accuracy law makes ----
|
|
675
|
+
#
|
|
676
|
+
# defect follows a path instead of searching for one. Everything it passes on
|
|
677
|
+
# the way must conform, a step's name must be the schema's, and at the end of
|
|
678
|
+
# the path it answers what is wrong there. A path that does not fit the value
|
|
679
|
+
# leads nowhere (None).
|
|
680
|
+
|
|
681
|
+
def guard(b: Bool, m: Maybe<&2, Why>) -> Maybe<&2, Why>:
|
|
682
|
+
match b:
|
|
683
|
+
case True{}:
|
|
684
|
+
m
|
|
685
|
+
case False{}:
|
|
686
|
+
None{}
|
|
687
|
+
|
|
688
|
+
# The same two reasons at an SNatIn and an SBool, replayed along a path: the
|
|
689
|
+
# value's own kind is the defect when the path ends here, and it is not a
|
|
690
|
+
# defect at all further along (the walk passed it).
|
|
691
|
+
def nat_in_defect(+lo: Nat, +hi: Nat, r: Raw, p: List<&2, Step>) -> Maybe<&2, Why>:
|
|
692
|
+
match r p:
|
|
693
|
+
case RNum{+n} Nil{}:
|
|
694
|
+
guard(Bool.not(num_ok(lo, hi, n)), Some{NotIn{lo, hi}})
|
|
695
|
+
case RNum{n} st <> q:
|
|
696
|
+
None{}
|
|
697
|
+
case x Nil{}:
|
|
698
|
+
Some{missing_or(x, NotNat{})}
|
|
699
|
+
case x st <> q:
|
|
700
|
+
None{}
|
|
701
|
+
|
|
702
|
+
def bool_defect(r: Raw, p: List<&2, Step>) -> Maybe<&2, Why>:
|
|
703
|
+
match r p:
|
|
704
|
+
case RBool{b} _:
|
|
705
|
+
None{}
|
|
706
|
+
case x Nil{}:
|
|
707
|
+
Some{missing_or(x, NotBool{})}
|
|
708
|
+
case x st <> q:
|
|
709
|
+
None{}
|
|
710
|
+
|
|
711
|
+
# ---- a rule, its replay, and the closed forms ----
|
|
712
|
+
#
|
|
713
|
+
# A rule reports its first error with a path relative to the value it was
|
|
714
|
+
# given. defect replays it by running the rule again: its path must be the
|
|
715
|
+
# reported one (path_eq, which never compares two names), and the claim of
|
|
716
|
+
# the accuracy law at an SRule is that the value conforms to the rule's
|
|
717
|
+
# schema and the rule itself reported this error.
|
|
718
|
+
|
|
719
|
+
def no_rule(tag: Nat, r: Raw) -> Maybe<&2, Err>:
|
|
720
|
+
None{}
|
|
721
|
+
|
|
722
|
+
def first_why(m: Maybe<&2, Why>, +n: Maybe<&2, Why>) -> Maybe<&2, Why>:
|
|
723
|
+
match m:
|
|
724
|
+
case None{}:
|
|
725
|
+
n
|
|
726
|
+
case Some{w}:
|
|
727
|
+
Some{w}
|
|
728
|
+
|
|
729
|
+
def step_eq(x: Step, y: Step) -> Bool:
|
|
730
|
+
match x y:
|
|
731
|
+
case AtIndex{i} AtIndex{j}:
|
|
732
|
+
Nat.is_eq(i, j)
|
|
733
|
+
case AtField{a, n} AtField{b, m}:
|
|
734
|
+
Bool.and(Nat.is_eq(a, b), String.eq(n, m))
|
|
735
|
+
case BoundAt{i, k} BoundAt{j, m}:
|
|
736
|
+
Bool.and(Nat.is_eq(i, j), String.eq(k, m))
|
|
737
|
+
case AtKey{k} AtKey{m}:
|
|
738
|
+
String.eq(k, m)
|
|
739
|
+
case _ _:
|
|
740
|
+
False{}
|
|
741
|
+
|
|
742
|
+
def path_eq(x: List<&2, Step>, y: List<&2, Step>) -> Bool:
|
|
743
|
+
match x y:
|
|
744
|
+
case Nil{} Nil{}:
|
|
745
|
+
True{}
|
|
746
|
+
case a <> at b <> bt:
|
|
747
|
+
Bool.and(step_eq(a, b), path_eq(at, bt))
|
|
748
|
+
case _ _:
|
|
749
|
+
False{}
|
|
750
|
+
|
|
751
|
+
def rule_defect(m: Maybe<&2, Err>, +p: List<&2, Step>) -> Maybe<&2, Why>:
|
|
752
|
+
match m:
|
|
753
|
+
case Some{Err{q, w}}:
|
|
754
|
+
guard(path_eq(q, p), Some{w})
|
|
755
|
+
case None{}:
|
|
756
|
+
None{}
|
|
757
|
+
|
|
758
|
+
def not_list(x: Raw, p: List<&2, Step>) -> Maybe<&2, Why>:
|
|
759
|
+
match p:
|
|
760
|
+
case Nil{}:
|
|
761
|
+
Some{missing_or(x, NotList{})}
|
|
762
|
+
case st <> q:
|
|
763
|
+
None{}
|
|
764
|
+
|
|
765
|
+
def not_object(x: Raw, p: List<&2, Step>) -> Maybe<&2, Why>:
|
|
766
|
+
match p:
|
|
767
|
+
case Nil{}:
|
|
768
|
+
Some{missing_or(x, NotObject{})}
|
|
769
|
+
case st <> q:
|
|
770
|
+
None{}
|
|
771
|
+
|
|
772
|
+
def nat_defect(r: Raw, p: List<&2, Step>) -> Maybe<&2, Why>:
|
|
773
|
+
match r p:
|
|
774
|
+
case RNum{n} _:
|
|
775
|
+
None{}
|
|
776
|
+
case x Nil{}:
|
|
777
|
+
Some{missing_or(x, NotNat{})}
|
|
778
|
+
case x st <> q:
|
|
779
|
+
None{}
|
|
780
|
+
|
|
781
|
+
def str_defect(r: Raw, p: List<&2, Step>) -> Maybe<&2, Why>:
|
|
782
|
+
match r p:
|
|
783
|
+
case RStr{x} _:
|
|
784
|
+
None{}
|
|
785
|
+
case x Nil{}:
|
|
786
|
+
Some{missing_or(x, NotString{})}
|
|
787
|
+
case x st <> q:
|
|
788
|
+
None{}
|
|
789
|
+
|
|
790
|
+
def pick_why(b: Bool, +x: Maybe<&2, Why>, +y: Maybe<&2, Why>) -> Maybe<&2, Why>:
|
|
791
|
+
match b:
|
|
792
|
+
case True{}:
|
|
793
|
+
x
|
|
794
|
+
case False{}:
|
|
795
|
+
y
|
|
796
|
+
|
|
797
|
+
def true_defect(r: Raw, p: List<&2, Step>) -> Maybe<&2, Why>:
|
|
798
|
+
match r p:
|
|
799
|
+
case RBool{True{}} _:
|
|
800
|
+
None{}
|
|
801
|
+
case RBool{False{}} Nil{}:
|
|
802
|
+
Some{NotTrue{}}
|
|
803
|
+
case x Nil{}:
|
|
804
|
+
Some{missing_or(x, NotTrue{})}
|
|
805
|
+
case x st <> q:
|
|
806
|
+
None{}
|
|
807
|
+
|
|
808
|
+
def enum_defect(names: List<&2, String>, r: Raw, p: List<&2, Step>) -> Maybe<&2, Why>:
|
|
809
|
+
match r p:
|
|
810
|
+
case RStr{x} Nil{}:
|
|
811
|
+
guard(Bool.not(in_names(x, names)), Some{NotOneOf{}})
|
|
812
|
+
case RStr{x} st <> q:
|
|
813
|
+
None{}
|
|
814
|
+
case x Nil{}:
|
|
815
|
+
Some{missing_or(x, NotOneOf{})}
|
|
816
|
+
case x st <> q:
|
|
817
|
+
None{}
|
|
818
|
+
|
|
819
|
+
def no_variant(p: List<&2, Step>) -> Maybe<&2, Why>:
|
|
820
|
+
match p:
|
|
821
|
+
case Nil{}:
|
|
822
|
+
Some{NoVariant{}}
|
|
823
|
+
case st <> q:
|
|
824
|
+
None{}
|
|
825
|
+
|
|
826
|
+
# The claim about a second key: every key passed is absent, and the key the
|
|
827
|
+
# path names is there.
|
|
828
|
+
def dup_defect(s: Schema, +r: Raw, p: List<&2, Step>) -> Maybe<&2, Why>:
|
|
829
|
+
match s p:
|
|
830
|
+
case SVariant{+n2, vs, rr} AtField{0n, m} <> Nil{}:
|
|
831
|
+
guard(Bool.and(String.eq(m, n2), Bool.not(is_missing(lookup(n2, r)))), Some{TwoVariants{}})
|
|
832
|
+
case SVariant{+n2, vs, rr} AtField{1n+j, m} <> q:
|
|
833
|
+
guard(is_missing(lookup(n2, r)), dup_defect(rr, r, AtField{j, m} <> q))
|
|
834
|
+
case _ _:
|
|
835
|
+
None{}
|
|
836
|
+
|
|
837
|
+
def at_end(w: Why, p: List<&2, Step>) -> Maybe<&2, Why>:
|
|
838
|
+
match p:
|
|
839
|
+
case Nil{}:
|
|
840
|
+
Some{w}
|
|
841
|
+
case st <> q:
|
|
842
|
+
None{}
|
|
843
|
+
|
|
844
|
+
# At a strict object the path is one key, and the replay checks it on its
|
|
845
|
+
# own: the key is there, and it is not one of the names.
|
|
846
|
+
def extra_defect(ns: List<&2, String>, +r: Raw, p: List<&2, Step>) -> Maybe<&2, Why>:
|
|
847
|
+
match p:
|
|
848
|
+
case AtKey{+k} <> Nil{}:
|
|
849
|
+
guard(Bool.and(has_key(k, r), Bool.not(in_names(k, ns))), Some{UnknownKey{}})
|
|
850
|
+
case _:
|
|
851
|
+
None{}
|
|
852
|
+
|
|
853
|
+
def tag_defect(+k: String, r: Raw, p: List<&2, Step>) -> Maybe<&2, Why>:
|
|
854
|
+
match r p:
|
|
855
|
+
case REnd{} AtKey{j} <> Nil{}:
|
|
856
|
+
guard(String.eq(j, k), Some{Missing{}})
|
|
857
|
+
case RKey{+j, +v, +o} AtKey{i} <> Nil{}:
|
|
858
|
+
guard(String.eq(i, k), Some{missing_or(lookup(k, RKey{j, v, o}), NotOneOf{})})
|
|
859
|
+
case REnd{} q:
|
|
860
|
+
None{}
|
|
861
|
+
case RKey{j, v, o} q:
|
|
862
|
+
None{}
|
|
863
|
+
case x Nil{}:
|
|
864
|
+
Some{missing_or(x, NotObject{})}
|
|
865
|
+
case x q:
|
|
866
|
+
None{}
|
|
867
|
+
|
|
868
|
+
# The tag key of a tagged case, taken twice, as defect replays it. check
|
|
869
|
+
# reports it before the case's own schema is walked -- there is no place in the
|
|
870
|
+
# schema for the second occurrence -- so the path is one field step at the
|
|
871
|
+
# tag's own name and nothing after it. b is whether the key is not read once,
|
|
872
|
+
# so nothing is reported exactly when key_once holds; a path of any other shape
|
|
873
|
+
# is the case's own report, which the walk below answers.
|
|
874
|
+
def tag_key_defect(b: Bool, +k: String, p: List<&2, Step>) -> Maybe<&2, Why>:
|
|
875
|
+
match b p:
|
|
876
|
+
case False{} q:
|
|
877
|
+
None{}
|
|
878
|
+
case True{} AtField{0n, +j} <> Nil{}:
|
|
879
|
+
pick_why(String.eq(j, k), Some{RepeatedKey{k}}, None{})
|
|
880
|
+
case True{} q:
|
|
881
|
+
None{}
|
|
882
|
+
|
|
883
|
+
def defect(~rule: Nat -> Raw -> Maybe<&2, Err>, s: Schema, r: Raw, +prev: Maybe<&2, Nat>, p: List<&2, Step>) -> Maybe<&2, Why>:
|
|
884
|
+
match s r p:
|
|
885
|
+
case SNat{} x q:
|
|
886
|
+
nat_defect(x, q)
|
|
887
|
+
case SStr{} x q:
|
|
888
|
+
str_defect(x, q)
|
|
889
|
+
case SNatIn{+lo, +hi} x q:
|
|
890
|
+
nat_in_defect(lo, hi, x, q)
|
|
891
|
+
case SStrLen{+lo, +hi, +s2} +x +q:
|
|
892
|
+
pick_why(conforms(~rule, s2, x, prev), rule_defect(len_err(lo, hi, x), q), defect(~rule, s2, x, prev, q))
|
|
893
|
+
case SListLen{+lo, +hi, +s2} +r +q:
|
|
894
|
+
# The count came first in check, so the count's own error is the one
|
|
895
|
+
# reported here exactly when there was one to report -- for a list out of
|
|
896
|
+
# bounds and for a value that is not a list at all (list_len_ok is False
|
|
897
|
+
# for both, and count_err reported something for both).
|
|
898
|
+
pick_why(Bool.not(list_len_ok(lo, hi, r)), rule_defect(count_err(lo, hi, r), q), defect(~rule, s2, r, None{}, q))
|
|
899
|
+
case SOpt{inner} RNull{} q:
|
|
900
|
+
None{}
|
|
901
|
+
case SOpt{inner} x q:
|
|
902
|
+
defect(~rule, inner, x, None{}, q)
|
|
903
|
+
case SOptional{inner} RMissing{} q:
|
|
904
|
+
None{} # absent is not wrong, at this path or below it
|
|
905
|
+
case SOptional{inner} x q:
|
|
906
|
+
defect(~rule, inner, x, None{}, q)
|
|
907
|
+
case SList{e} RNil{} q:
|
|
908
|
+
None{}
|
|
909
|
+
case SList{e} RCons{h, t} AtIndex{0n} <> q:
|
|
910
|
+
defect(~rule, e, h, None{}, q)
|
|
911
|
+
case SList{+e} RCons{h, t} AtIndex{1n+j} <> q:
|
|
912
|
+
guard(conforms(~rule, e, h, None{}), defect(~rule, SList{e}, t, None{}, AtIndex{j} <> q))
|
|
913
|
+
case SList{+e} RCons{h, t} q:
|
|
914
|
+
guard(conforms(~rule, e, h, None{}), defect(~rule, SList{e}, t, None{}, q))
|
|
915
|
+
case SList{e} x q:
|
|
916
|
+
not_list(x, q)
|
|
917
|
+
case SField{+name, fs, rest} REnd{} AtField{0n, n} <> q:
|
|
918
|
+
guard(String.eq(n, name), defect(~rule, fs, RMissing{}, None{}, q))
|
|
919
|
+
case SField{+name, fs, rest} REnd{} AtField{1n+k, n} <> q:
|
|
920
|
+
guard(conforms(~rule, fs, RMissing{}, None{}), defect(~rule, rest, REnd{}, None{}, AtField{k, n} <> q))
|
|
921
|
+
case SField{+name, fs, rest} REnd{} q:
|
|
922
|
+
guard(conforms(~rule, fs, RMissing{}, None{}), defect(~rule, rest, REnd{}, None{}, q))
|
|
923
|
+
case SField{+name, fs, rest} RKey{+k, +v, +o} AtField{0n, n} <> +q:
|
|
924
|
+
pick_why(Bool.not(key_once(name, RKey{k, v, o})), at_end(RepeatedKey{name}, q), guard(String.eq(n, name), defect(~rule, fs, lookup(name, RKey{k, v, o}), None{}, q)))
|
|
925
|
+
case SField{+name, fs, rest} RKey{+k, +v, +o} AtField{1n+j, n} <> q:
|
|
926
|
+
guard(conforms(~rule, fs, lookup(name, RKey{k, v, o}), None{}), defect(~rule, rest, RKey{k, v, o}, None{}, AtField{j, n} <> q))
|
|
927
|
+
case SField{+name, fs, rest} RKey{+k, +v, +o} q:
|
|
928
|
+
guard(conforms(~rule, fs, lookup(name, RKey{k, v, o}), None{}), defect(~rule, rest, RKey{k, v, o}, None{}, q))
|
|
929
|
+
case SField{name, fs, rest} x q:
|
|
930
|
+
not_object(x, q)
|
|
931
|
+
case SEnd{} REnd{} q:
|
|
932
|
+
None{}
|
|
933
|
+
case SEnd{} RKey{k, v, o} q:
|
|
934
|
+
None{}
|
|
935
|
+
case SEnd{} x q:
|
|
936
|
+
not_object(x, q)
|
|
937
|
+
case SRule{+s2, +tag} +x +q:
|
|
938
|
+
pick_why(conforms(~rule, s2, x, prev), rule_defect(rule(tag, x), q), defect(~rule, s2, x, prev, q))
|
|
939
|
+
case SStrict{+s2} +x +q:
|
|
940
|
+
pick_why(conforms(~rule, s2, x, prev), extra_defect(key_names(s2), x, q), defect(~rule, s2, x, prev, q))
|
|
941
|
+
case STagged{+k, +n, cs, rest} +x +q:
|
|
942
|
+
pick_why(is_tag(k, n, x), first_why(tag_key_defect(Bool.not(key_once(k, x)), k, q), defect(~rule, cs, drop_key(k, x), None{}, q)), defect(~rule, rest, x, None{}, q))
|
|
943
|
+
case STagEnd{k} x q:
|
|
944
|
+
tag_defect(k, x, q)
|
|
945
|
+
case SBool{} x q:
|
|
946
|
+
bool_defect(x, q)
|
|
947
|
+
case STrue{} x q:
|
|
948
|
+
true_defect(x, q)
|
|
949
|
+
case SEnum{names} x q:
|
|
950
|
+
enum_defect(names, x, q)
|
|
951
|
+
case SVariant{+name, +vs, +rest} REnd{} AtField{1n+j, n} <> q:
|
|
952
|
+
defect(~rule, rest, REnd{}, None{}, AtField{j, n} <> q)
|
|
953
|
+
case SVariant{+name, +vs, +rest} REnd{} q:
|
|
954
|
+
defect(~rule, rest, REnd{}, None{}, q)
|
|
955
|
+
case SVariant{+name, +vs, +rest} RKey{+k, +v, +o} AtField{0n, +n} <> +q:
|
|
956
|
+
pick_why(is_missing(lookup(name, RKey{k, v, o})), defect(~rule, rest, RKey{k, v, o}, None{}, AtField{0n, n} <> q), pick_why(Bool.not(key_once(name, RKey{k, v, o})), at_end(RepeatedKey{name}, q), guard(String.eq(n, name), defect(~rule, vs, lookup(name, RKey{k, v, o}), None{}, q))))
|
|
957
|
+
case SVariant{+name, +vs, +rest} RKey{+k, +v, +o} AtField{1n+j, +n} <> +q:
|
|
958
|
+
pick_why(is_missing(lookup(name, RKey{k, v, o})), defect(~rule, rest, RKey{k, v, o}, None{}, AtField{j, n} <> q), guard(conforms(~rule, vs, lookup(name, RKey{k, v, o}), None{}), dup_defect(rest, RKey{k, v, o}, AtField{j, n} <> q)))
|
|
959
|
+
case SVariant{+name, +vs, +rest} RKey{+k, +v, +o} +q:
|
|
960
|
+
pick_why(is_missing(lookup(name, RKey{k, v, o})), defect(~rule, rest, RKey{k, v, o}, None{}, q), None{})
|
|
961
|
+
case SVariant{+name, +vs, +rest} x q:
|
|
962
|
+
not_object(x, q)
|
|
963
|
+
case SVEnd{} REnd{} q:
|
|
964
|
+
no_variant(q)
|
|
965
|
+
case SVEnd{} RKey{k, v, o} q:
|
|
966
|
+
no_variant(q)
|
|
967
|
+
case SVEnd{} x q:
|
|
968
|
+
not_object(x, q)
|
|
969
|
+
case STuple{ts, rest} RCons{h, t} AtIndex{0n} <> q:
|
|
970
|
+
defect(~rule, ts, h, None{}, q)
|
|
971
|
+
case STuple{+ts, rest} RCons{h, t} AtIndex{1n+j} <> q:
|
|
972
|
+
guard(conforms(~rule, ts, h, None{}), defect(~rule, rest, t, None{}, AtIndex{j} <> q))
|
|
973
|
+
case STuple{+ts, rest} RCons{h, t} q:
|
|
974
|
+
guard(conforms(~rule, ts, h, None{}), defect(~rule, rest, t, None{}, q))
|
|
975
|
+
case STuple{ts, rest} RNil{} q:
|
|
976
|
+
at_end(TooShort{}, q)
|
|
977
|
+
case STuple{ts, rest} x q:
|
|
978
|
+
not_list(x, q)
|
|
979
|
+
case STEnd{} RNil{} q:
|
|
980
|
+
None{}
|
|
981
|
+
case STEnd{} RCons{h, t} q:
|
|
982
|
+
at_end(TooLong{}, q)
|
|
983
|
+
case STEnd{} x q:
|
|
984
|
+
not_list(x, q)
|
|
985
|
+
|
|
986
|
+
# The closed forms: template defs cannot cross the bundler, so the host
|
|
987
|
+
# calls these (a project with no rules).
|
|
988
|
+
|
|
989
|
+
def check0(s: Schema, r: Raw) -> Maybe<&2, Err>:
|
|
990
|
+
check(~no_rule, s, r, None{})
|
|
991
|
+
|
|
992
|
+
def conforms0(s: Schema, r: Raw) -> Bool:
|
|
993
|
+
conforms(~no_rule, s, r, None{})
|
|
994
|
+
|
|
995
|
+
# ---- the claims the choice laws make ----
|
|
996
|
+
#
|
|
997
|
+
# A second reading of a variant chain, by counting: the value is an object,
|
|
998
|
+
# exactly one of the chain's keys is in it, and every key that is there holds
|
|
999
|
+
# a value that conforms. A key that is there is read the way conforms reads
|
|
1000
|
+
# it, key_once included: a key the chain names, taken twice, is not read once,
|
|
1001
|
+
# so the counting reading refuses it exactly where conforms does.
|
|
1002
|
+
|
|
1003
|
+
def is_chain(s: Schema) -> Bool:
|
|
1004
|
+
match s:
|
|
1005
|
+
case SVariant{n, vs, rest}:
|
|
1006
|
+
is_chain(rest)
|
|
1007
|
+
case SVEnd{}:
|
|
1008
|
+
True{}
|
|
1009
|
+
case _:
|
|
1010
|
+
False{}
|
|
1011
|
+
|
|
1012
|
+
def is_object(r: Raw) -> Bool:
|
|
1013
|
+
match r:
|
|
1014
|
+
case REnd{}:
|
|
1015
|
+
True{}
|
|
1016
|
+
case RKey{k, v, o}:
|
|
1017
|
+
True{}
|
|
1018
|
+
case _:
|
|
1019
|
+
False{}
|
|
1020
|
+
|
|
1021
|
+
def one_if_there(b: Bool) -> Nat:
|
|
1022
|
+
match b:
|
|
1023
|
+
case True{}:
|
|
1024
|
+
0n
|
|
1025
|
+
case False{}:
|
|
1026
|
+
1n
|
|
1027
|
+
|
|
1028
|
+
def count_present(s: Schema, +r: Raw) -> Nat:
|
|
1029
|
+
match s:
|
|
1030
|
+
case SVariant{+n, vs, rest}:
|
|
1031
|
+
Nat.add(one_if_there(is_missing(lookup(n, r))), count_present(rest, r))
|
|
1032
|
+
case _:
|
|
1033
|
+
0n
|
|
1034
|
+
|
|
1035
|
+
def present_conform(~rule: Nat -> Raw -> Maybe<&2, Err>, +s: Schema, +r: Raw) -> Bool:
|
|
1036
|
+
match s:
|
|
1037
|
+
case SVariant{+n, vs, rest}:
|
|
1038
|
+
pick_bool(is_missing(lookup(n, r)), present_conform(~rule, rest, r), Bool.and(key_once(n, r), Bool.and(conforms(~rule, vs, lookup(n, r), None{}), present_conform(~rule, rest, r))))
|
|
1039
|
+
case _:
|
|
1040
|
+
True{}
|
|
1041
|
+
|
|
1042
|
+
# ---- the claim the tuple law makes ----
|
|
1043
|
+
#
|
|
1044
|
+
# A second reading of a tuple, over plain lists: the schema is built from a
|
|
1045
|
+
# list of schemas, and the value conforms when it is a list of the same
|
|
1046
|
+
# length and each position conforms to the schema at the same position.
|
|
1047
|
+
|
|
1048
|
+
def tuple_of(ss: List<&2, Schema>) -> Schema:
|
|
1049
|
+
match ss:
|
|
1050
|
+
case Nil{}:
|
|
1051
|
+
STEnd{}
|
|
1052
|
+
case h <> t:
|
|
1053
|
+
STuple{h, tuple_of(t)}
|
|
1054
|
+
|
|
1055
|
+
# raw_list and raw_len (a list, and how many elements it has) sit with the
|
|
1056
|
+
# other bound helpers, above: an SListLen's test reads them.
|
|
1057
|
+
|
|
1058
|
+
def at_raw(r: Raw, +i: Nat) -> Raw:
|
|
1059
|
+
match r i:
|
|
1060
|
+
case RCons{h, t} 0n:
|
|
1061
|
+
h
|
|
1062
|
+
case RCons{h, t} 1n+j:
|
|
1063
|
+
at_raw(t, j)
|
|
1064
|
+
case _ _:
|
|
1065
|
+
RMissing{}
|
|
1066
|
+
|
|
1067
|
+
def each_pos(~rule: Nat -> Raw -> Maybe<&2, Err>, ss: List<&2, Schema>, +r: Raw, +i: Nat) -> Bool:
|
|
1068
|
+
match ss:
|
|
1069
|
+
case Nil{}:
|
|
1070
|
+
True{}
|
|
1071
|
+
case h <> t:
|
|
1072
|
+
Bool.and(conforms(~rule, h, at_raw(r, i), None{}), each_pos(~rule, t, r, 1n+i))
|
|
1073
|
+
|
|
1074
|
+
# ---- the claim the strict law makes ----
|
|
1075
|
+
#
|
|
1076
|
+
# How many keys of r are not in ns, counted rather than walked with a
|
|
1077
|
+
# Bool.and: a second algorithm, so that a no_extra that lets a key through
|
|
1078
|
+
# (or refuses a named one) disagrees with it.
|
|
1079
|
+
def count_unknown(+ns: List<&2, String>, r: Raw) -> Nat:
|
|
1080
|
+
match r:
|
|
1081
|
+
case RKey{+k, v, o}:
|
|
1082
|
+
Nat.add(one_if_there(in_names(k, ns)), count_unknown(ns, o))
|
|
1083
|
+
case _:
|
|
1084
|
+
0n
|
|
1085
|
+
|
|
1086
|
+
# ---- a schema's meaning, and reading a value into it ----
|
|
1087
|
+
#
|
|
1088
|
+
# Meaning(s) is the Bend type a schema describes: a record is its fields,
|
|
1089
|
+
# nested pairs ending in Unit; a variant chain is nested Eithers ending in
|
|
1090
|
+
# Empty; a value that may be null (SOpt) or absent (SOptional) is a Maybe. A
|
|
1091
|
+
# rule refines a shape and does not change its meaning. `enc` writes a
|
|
1092
|
+
# meaning as the host's JSON would be; `dec` reads one back. Both are written
|
|
1093
|
+
# once, for every schema, and LAWS.bend proves the round trip once.
|
|
1094
|
+
#
|
|
1095
|
+
# dec is a reader, not a check: run it on a value check accepted
|
|
1096
|
+
# (checked_decodes says it then succeeds). On a value check refuses it may
|
|
1097
|
+
# still read something, since a record reads its keys and ignores the rest.
|
|
1098
|
+
#
|
|
1099
|
+
# The round trip needs a well-formed schema (wf): a key named once in its
|
|
1100
|
+
# object or chain (lookup reads the first), and an optional value that is not
|
|
1101
|
+
# itself nullable (Some{None} and None would both be written null).
|
|
1102
|
+
|
|
1103
|
+
type Both<A: Data, B: Data> is Data:
|
|
1104
|
+
Both{a: A, b: B}
|
|
1105
|
+
|
|
1106
|
+
def Meaning(s: Schema) -> Data:
|
|
1107
|
+
match s:
|
|
1108
|
+
case SNat{}:
|
|
1109
|
+
Nat
|
|
1110
|
+
case SNatIn{lo, hi}:
|
|
1111
|
+
Nat
|
|
1112
|
+
case SStr{}:
|
|
1113
|
+
String
|
|
1114
|
+
case SStrLen{lo, hi, s2}:
|
|
1115
|
+
Meaning(s2)
|
|
1116
|
+
case SListLen{lo, hi, s2}:
|
|
1117
|
+
Meaning(s2)
|
|
1118
|
+
case SOpt{i}:
|
|
1119
|
+
Maybe<&2, Meaning(i)>
|
|
1120
|
+
case SOptional{i}:
|
|
1121
|
+
Maybe<&2, Meaning(i)>
|
|
1122
|
+
case SList{e}:
|
|
1123
|
+
List<&2, Meaning(e)>
|
|
1124
|
+
case SField{n, fs, rest}:
|
|
1125
|
+
Both<Meaning(fs), Meaning(rest)>
|
|
1126
|
+
case SEnd{}:
|
|
1127
|
+
Unit
|
|
1128
|
+
case SRule{s2, tag}:
|
|
1129
|
+
Meaning(s2)
|
|
1130
|
+
case SStrict{s2}:
|
|
1131
|
+
Meaning(s2)
|
|
1132
|
+
case STagged{k, n, cs, rest}:
|
|
1133
|
+
Either<&2, &2, Meaning(cs), Meaning(rest)>
|
|
1134
|
+
case STagEnd{k}:
|
|
1135
|
+
Empty
|
|
1136
|
+
case SBool{}:
|
|
1137
|
+
Bool
|
|
1138
|
+
case STrue{}:
|
|
1139
|
+
Unit
|
|
1140
|
+
case SEnum{names}:
|
|
1141
|
+
String
|
|
1142
|
+
case SVariant{n, vs, rest}:
|
|
1143
|
+
Either<&2, &2, Meaning(vs), Meaning(rest)>
|
|
1144
|
+
case SVEnd{}:
|
|
1145
|
+
Empty
|
|
1146
|
+
case STuple{ts, rest}:
|
|
1147
|
+
Both<Meaning(ts), Meaning(rest)>
|
|
1148
|
+
case STEnd{}:
|
|
1149
|
+
Unit
|
|
1150
|
+
|
|
1151
|
+
def enc(s: Schema, x: Meaning(s)) -> Raw:
|
|
1152
|
+
match s x:
|
|
1153
|
+
case SNat{} n:
|
|
1154
|
+
RNum{n}
|
|
1155
|
+
case SNatIn{lo_, hi_} n:
|
|
1156
|
+
RNum{n}
|
|
1157
|
+
case SStr{} x:
|
|
1158
|
+
RStr{x}
|
|
1159
|
+
case SStrLen{lo_, hi_, s2} v:
|
|
1160
|
+
enc(s2, v)
|
|
1161
|
+
case SListLen{lo_, hi_, s2} v:
|
|
1162
|
+
enc(s2, v)
|
|
1163
|
+
case SOpt{i} None{}:
|
|
1164
|
+
RNull{}
|
|
1165
|
+
case SOpt{i} Some{v}:
|
|
1166
|
+
enc(i, v)
|
|
1167
|
+
case SOptional{i} None{}:
|
|
1168
|
+
RMissing{} # absent: the host writes no key where this one lands
|
|
1169
|
+
case SOptional{i} Some{v}:
|
|
1170
|
+
enc(i, v)
|
|
1171
|
+
case SList{e} Nil{}:
|
|
1172
|
+
RNil{}
|
|
1173
|
+
case SList{+e} h <> t:
|
|
1174
|
+
RCons{enc(e, h), enc(SList{e}, t)}
|
|
1175
|
+
case SField{n, fs, rest} Both{a, b}:
|
|
1176
|
+
RKey{n, enc(fs, a), enc(rest, b)}
|
|
1177
|
+
case SEnd{} Unit{}:
|
|
1178
|
+
REnd{}
|
|
1179
|
+
case SRule{s2, tag} v:
|
|
1180
|
+
enc(s2, v)
|
|
1181
|
+
case SStrict{s2} v:
|
|
1182
|
+
enc(s2, v)
|
|
1183
|
+
case STagged{k, n, cs, rest} Inl{a}:
|
|
1184
|
+
RKey{k, RStr{n}, enc(cs, a)}
|
|
1185
|
+
case STagged{k, n, cs, rest} Inr{b}:
|
|
1186
|
+
enc(rest, b)
|
|
1187
|
+
case STagEnd{k} e:
|
|
1188
|
+
Empty.absurd(Raw, e)
|
|
1189
|
+
case STrue{} Unit{}:
|
|
1190
|
+
RBool{True{}}
|
|
1191
|
+
case SBool{} b:
|
|
1192
|
+
RBool{b}
|
|
1193
|
+
case SEnum{names} x:
|
|
1194
|
+
RStr{x}
|
|
1195
|
+
case SVariant{n, vs, rest} Inl{a}:
|
|
1196
|
+
RKey{n, enc(vs, a), REnd{}}
|
|
1197
|
+
case SVariant{n, vs, rest} Inr{b}:
|
|
1198
|
+
enc(rest, b)
|
|
1199
|
+
case SVEnd{} e:
|
|
1200
|
+
Empty.absurd(Raw, e)
|
|
1201
|
+
case STuple{ts, rest} Both{a, b}:
|
|
1202
|
+
RCons{enc(ts, a), enc(rest, b)}
|
|
1203
|
+
case STEnd{} Unit{}:
|
|
1204
|
+
RNil{}
|
|
1205
|
+
|
|
1206
|
+
def lcons(-A: Data, h: Maybe<&2, A>, t: Maybe<&2, List<&2, A>>) -> Maybe<&2, List<&2, A>>:
|
|
1207
|
+
match h t:
|
|
1208
|
+
case Some{x} Some{xs}:
|
|
1209
|
+
Some{x <> xs}
|
|
1210
|
+
case _ _:
|
|
1211
|
+
None{}
|
|
1212
|
+
|
|
1213
|
+
def both(-A: Data, -B: Data, p: Maybe<&2, A>, q: Maybe<&2, B>) -> Maybe<&2, Both<A, B>>:
|
|
1214
|
+
match p q:
|
|
1215
|
+
case Some{x} Some{y}:
|
|
1216
|
+
Some{Both{x, y}}
|
|
1217
|
+
case _ _:
|
|
1218
|
+
None{}
|
|
1219
|
+
|
|
1220
|
+
def opt_some(-A: Data, m: Maybe<&2, A>) -> Maybe<&2, Maybe<&2, A>>:
|
|
1221
|
+
match m:
|
|
1222
|
+
case Some{v}:
|
|
1223
|
+
Some{Some{v}}
|
|
1224
|
+
case None{}:
|
|
1225
|
+
None{}
|
|
1226
|
+
|
|
1227
|
+
def map_inl(-A: Data, -B: Data, m: Maybe<&2, A>) -> Maybe<&2, Either<&2, &2, A, B>>:
|
|
1228
|
+
match m:
|
|
1229
|
+
case Some{v}:
|
|
1230
|
+
Some{Inl{v}}
|
|
1231
|
+
case None{}:
|
|
1232
|
+
None{}
|
|
1233
|
+
|
|
1234
|
+
def map_inr(-A: Data, -B: Data, m: Maybe<&2, B>) -> Maybe<&2, Either<&2, &2, A, B>>:
|
|
1235
|
+
match m:
|
|
1236
|
+
case Some{v}:
|
|
1237
|
+
Some{Inr{v}}
|
|
1238
|
+
case None{}:
|
|
1239
|
+
None{}
|
|
1240
|
+
|
|
1241
|
+
def pick_m(-A: Data, b: Bool, x: A, y: A) -> A:
|
|
1242
|
+
match b:
|
|
1243
|
+
case True{}:
|
|
1244
|
+
x
|
|
1245
|
+
case False{}:
|
|
1246
|
+
y
|
|
1247
|
+
|
|
1248
|
+
def true_unit(b: Bool) -> Maybe<&2, Unit>:
|
|
1249
|
+
match b:
|
|
1250
|
+
case True{}:
|
|
1251
|
+
Some{Unit{}}
|
|
1252
|
+
case False{}:
|
|
1253
|
+
None{}
|
|
1254
|
+
|
|
1255
|
+
def nullish(r: Raw) -> Bool:
|
|
1256
|
+
match r:
|
|
1257
|
+
case RNull{}:
|
|
1258
|
+
True{}
|
|
1259
|
+
case RMissing{}:
|
|
1260
|
+
True{}
|
|
1261
|
+
case _:
|
|
1262
|
+
False{}
|
|
1263
|
+
|
|
1264
|
+
def dec(s: Schema, r: Raw) -> Maybe<&2, Meaning(s)>:
|
|
1265
|
+
match s r:
|
|
1266
|
+
case SNat{} RNum{n}:
|
|
1267
|
+
Some{n}
|
|
1268
|
+
case SNatIn{lo_, hi_} RNum{n}:
|
|
1269
|
+
Some{n}
|
|
1270
|
+
case SStr{} RStr{x}:
|
|
1271
|
+
Some{x}
|
|
1272
|
+
case SStrLen{lo_, hi_, s2} x:
|
|
1273
|
+
dec(s2, x)
|
|
1274
|
+
case SListLen{lo_, hi_, s2} x:
|
|
1275
|
+
dec(s2, x)
|
|
1276
|
+
case SOpt{+i} +x:
|
|
1277
|
+
pick_m(Maybe<&2, Maybe<&2, Meaning(i)>>, nullish(x), Some{None{}}, opt_some(Meaning(i), dec(i, x)))
|
|
1278
|
+
case SOptional{+i} +x:
|
|
1279
|
+
pick_m(Maybe<&2, Maybe<&2, Meaning(i)>>, is_missing(x), Some{None{}}, opt_some(Meaning(i), dec(i, x)))
|
|
1280
|
+
case SList{e} RNil{}:
|
|
1281
|
+
Some{Nil{}}
|
|
1282
|
+
case SList{+e} RCons{h, t}:
|
|
1283
|
+
lcons(Meaning(e), dec(e, h), dec(SList{e}, t))
|
|
1284
|
+
case SField{+n, +fs, +rest} +x:
|
|
1285
|
+
both(Meaning(fs), Meaning(rest), dec(fs, lookup(n, x)), dec(rest, x))
|
|
1286
|
+
case SEnd{} x:
|
|
1287
|
+
Some{Unit{}}
|
|
1288
|
+
case SRule{s2, tag} x:
|
|
1289
|
+
dec(s2, x)
|
|
1290
|
+
case SStrict{s2} x:
|
|
1291
|
+
dec(s2, x)
|
|
1292
|
+
case STagged{+k, +n, +cs, +rest} +x:
|
|
1293
|
+
pick_m(Maybe<&2, Either<&2, &2, Meaning(cs), Meaning(rest)>>, is_tag(k, n, x), map_inl(Meaning(cs), Meaning(rest), dec(cs, drop_key(k, x))), map_inr(Meaning(cs), Meaning(rest), dec(rest, x)))
|
|
1294
|
+
case STrue{} RBool{b}:
|
|
1295
|
+
true_unit(b)
|
|
1296
|
+
case SBool{} RBool{b}:
|
|
1297
|
+
Some{b}
|
|
1298
|
+
case SEnum{names} RStr{x}:
|
|
1299
|
+
Some{x}
|
|
1300
|
+
case SVariant{+n, +vs, +rest} +x:
|
|
1301
|
+
pick_m(Maybe<&2, Either<&2, &2, Meaning(vs), Meaning(rest)>>, is_missing(lookup(n, x)), map_inr(Meaning(vs), Meaning(rest), dec(rest, x)), map_inl(Meaning(vs), Meaning(rest), dec(vs, lookup(n, x))))
|
|
1302
|
+
case STuple{+ts, +rest} RCons{h, t}:
|
|
1303
|
+
both(Meaning(ts), Meaning(rest), dec(ts, h), dec(rest, t))
|
|
1304
|
+
case STEnd{} RNil{}:
|
|
1305
|
+
Some{Unit{}}
|
|
1306
|
+
case _ _:
|
|
1307
|
+
None{}
|
|
1308
|
+
|
|
1309
|
+
# ---- a well-formed schema ----
|
|
1310
|
+
#
|
|
1311
|
+
# wf is what the round trip needs: a key named once in its object or chain
|
|
1312
|
+
# (lookup reads the first), an optional value that is not itself nullable, and
|
|
1313
|
+
# a tagged case whose own schema leaves the tag key alone.
|
|
1314
|
+
# Three constructors ask more than their own shape:
|
|
1315
|
+
#
|
|
1316
|
+
# STagged writes its key and then the case's own object beside it, so the
|
|
1317
|
+
# case's schema may not put that key anywhere in the same object (no_key).
|
|
1318
|
+
# A case that names its own tag key encodes to a document holding the key
|
|
1319
|
+
# twice: enc writes the tag and the case's own field writes it again, and
|
|
1320
|
+
# the host builds that document as a JS object, where the second write --
|
|
1321
|
+
# the spread of the case's own fields -- overwrites the first. What the
|
|
1322
|
+
# encoder wrote is then not what the decoder reads, so refusing the schema
|
|
1323
|
+
# is what keeps encode_conforms true.
|
|
1324
|
+
#
|
|
1325
|
+
# SOptional is where a field may be absent, so it belongs immediately under
|
|
1326
|
+
# a field and nowhere else: every other place a schema sits is guarded by
|
|
1327
|
+
# opt_at, which asks whether an absent value can reach the top of what enc
|
|
1328
|
+
# writes for it. That excludes an SOptional anywhere but a field's schema,
|
|
1329
|
+
# and also a schema that would pass one straight through (SOpt{SOptional{i}}
|
|
1330
|
+
# writes RMissing for Some{None}, where nothing can tell it from an absent
|
|
1331
|
+
# key). An SOptional's own inner is guarded the same way: None and
|
|
1332
|
+
# Some{None} would both be written as an absent key.
|
|
1333
|
+
#
|
|
1334
|
+
# SOpt's inner may not be nullable at all (its own None and the inner's null
|
|
1335
|
+
# would both be written null), and nullable asks an SOptional too.
|
|
1336
|
+
|
|
1337
|
+
# An absent value reaches the top of what enc writes for s: s is an SOptional,
|
|
1338
|
+
# or it hands the value it was given straight to a schema that is (a rule, a
|
|
1339
|
+
# strict object, a bound, or the tail of a variant or tagged chain -- and an
|
|
1340
|
+
# SOpt, whose Some case passes the value through). Everything else wraps what
|
|
1341
|
+
# it writes in a constructor of its own, so an absent value cannot surface.
|
|
1342
|
+
def opt_at(s: Schema) -> Bool:
|
|
1343
|
+
match s:
|
|
1344
|
+
case SOptional{i}:
|
|
1345
|
+
True{}
|
|
1346
|
+
case SOpt{i}:
|
|
1347
|
+
opt_at(i)
|
|
1348
|
+
case SRule{s2, tag}:
|
|
1349
|
+
opt_at(s2)
|
|
1350
|
+
case SStrict{s2}:
|
|
1351
|
+
opt_at(s2)
|
|
1352
|
+
case SStrLen{lo, hi, s2}:
|
|
1353
|
+
opt_at(s2)
|
|
1354
|
+
case SListLen{lo, hi, s2}:
|
|
1355
|
+
opt_at(s2)
|
|
1356
|
+
case STagged{k, n, cs, rest}:
|
|
1357
|
+
opt_at(rest)
|
|
1358
|
+
case SVariant{n, vs, rest}:
|
|
1359
|
+
opt_at(rest)
|
|
1360
|
+
case _:
|
|
1361
|
+
False{}
|
|
1362
|
+
|
|
1363
|
+
def nullable(s: Schema) -> Bool:
|
|
1364
|
+
match s:
|
|
1365
|
+
case SOpt{i}:
|
|
1366
|
+
True{}
|
|
1367
|
+
case SOptional{i}:
|
|
1368
|
+
True{} # absent and the inner's own null read as the same None
|
|
1369
|
+
case SRule{s2, tag}:
|
|
1370
|
+
nullable(s2)
|
|
1371
|
+
case SStrict{s2}:
|
|
1372
|
+
nullable(s2)
|
|
1373
|
+
case SStrLen{lo, hi, s2}:
|
|
1374
|
+
nullable(s2)
|
|
1375
|
+
case SListLen{lo, hi, s2}:
|
|
1376
|
+
nullable(s2)
|
|
1377
|
+
case STagged{k, n, cs, rest}:
|
|
1378
|
+
nullable(rest)
|
|
1379
|
+
case SVariant{n, vs, rest}:
|
|
1380
|
+
nullable(rest)
|
|
1381
|
+
case _:
|
|
1382
|
+
False{}
|
|
1383
|
+
|
|
1384
|
+
# ---- the object a schema's encoding lands in ----
|
|
1385
|
+
#
|
|
1386
|
+
# enc writes an object as one flat chain of keys. An SField writes its name and
|
|
1387
|
+
# its rest continues that object; an SVariant writes its name and the value
|
|
1388
|
+
# under it is a chain of its own (REnd ends it, so it is a value and not a
|
|
1389
|
+
# continuation). A wrapper -- a rule, a strict object, a bound, an optional --
|
|
1390
|
+
# writes no key of its own and hands its value straight to its inner schema,
|
|
1391
|
+
# so a key reaches the top of the object from inside any of them. A tagged
|
|
1392
|
+
# case's schema is the same object the tag key was written into, and so is the
|
|
1393
|
+
# rest of its chain.
|
|
1394
|
+
#
|
|
1395
|
+
# no_key is that, written down: k does not reach the top of the object enc
|
|
1396
|
+
# builds for s, for any value of s. wf asks it of a tagged case's own schema,
|
|
1397
|
+
# which is what keeps the tag key from being written twice.
|
|
1398
|
+
def no_key(+k: String, s: Schema) -> Bool:
|
|
1399
|
+
match s:
|
|
1400
|
+
case SField{+n, fs, rest}:
|
|
1401
|
+
Bool.and(Bool.not(String.eq(n, k)), Bool.and(Bool.not(String.eq(k, n)), no_key(k, rest)))
|
|
1402
|
+
case SVariant{+n, vs, rest}:
|
|
1403
|
+
Bool.and(Bool.not(String.eq(n, k)), Bool.and(Bool.not(String.eq(k, n)), no_key(k, rest)))
|
|
1404
|
+
case SStrict{s2}:
|
|
1405
|
+
no_key(k, s2)
|
|
1406
|
+
case SRule{s2, tag}:
|
|
1407
|
+
no_key(k, s2)
|
|
1408
|
+
case SStrLen{lo, hi, s2}:
|
|
1409
|
+
no_key(k, s2)
|
|
1410
|
+
case SListLen{lo, hi, s2}:
|
|
1411
|
+
no_key(k, s2)
|
|
1412
|
+
case SOpt{i}:
|
|
1413
|
+
no_key(k, i)
|
|
1414
|
+
case SOptional{i}:
|
|
1415
|
+
no_key(k, i)
|
|
1416
|
+
case STagged{+k2, n2, cs2, rest2}:
|
|
1417
|
+
Bool.and(Bool.not(String.eq(k2, k)), Bool.and(Bool.not(String.eq(k, k2)), Bool.and(no_key(k, cs2), no_key(k, rest2))))
|
|
1418
|
+
case _:
|
|
1419
|
+
True{}
|
|
1420
|
+
|
|
1421
|
+
# n is not a key of the record chain s (a chain ends in SEnd). Both orders are
|
|
1422
|
+
# asked, as fresh_v asks them: which of the two names a step puts first depends
|
|
1423
|
+
# on which of them is the value's key there, and asking both spares a proof
|
|
1424
|
+
# that String.eq is symmetric.
|
|
1425
|
+
def fresh_f(+n: String, s: Schema) -> Bool:
|
|
1426
|
+
match s:
|
|
1427
|
+
case SField{+m, fs, rest}:
|
|
1428
|
+
Bool.and(Bool.not(String.eq(m, n)), Bool.and(Bool.not(String.eq(n, m)), fresh_f(n, rest)))
|
|
1429
|
+
case SEnd{}:
|
|
1430
|
+
True{}
|
|
1431
|
+
case _:
|
|
1432
|
+
False{}
|
|
1433
|
+
|
|
1434
|
+
# n is not a key of the variant chain s (a chain ends in SVEnd). Both orders
|
|
1435
|
+
# are asked: reading a key compares the names one way, reading past it the
|
|
1436
|
+
# other, and asking both spares a proof that String.eq is symmetric.
|
|
1437
|
+
def fresh_v(+n: String, s: Schema) -> Bool:
|
|
1438
|
+
match s:
|
|
1439
|
+
case SVariant{+m, vs, rest}:
|
|
1440
|
+
Bool.and(Bool.not(String.eq(m, n)), Bool.and(Bool.not(String.eq(n, m)), fresh_v(n, rest)))
|
|
1441
|
+
case SVEnd{}:
|
|
1442
|
+
True{}
|
|
1443
|
+
case _:
|
|
1444
|
+
False{}
|
|
1445
|
+
|
|
1446
|
+
# A record chain (ending in SEnd) or a variant chain (ending in SVEnd): what
|
|
1447
|
+
# SStrict may wrap, so every key its encoding writes is a declared name.
|
|
1448
|
+
# A chain of keys (SField or SVariant links, ending in SEnd or SVEnd): what
|
|
1449
|
+
# SStrict may wrap, so every key its encoding writes is a name it declares.
|
|
1450
|
+
def is_keyed(s: Schema) -> Bool:
|
|
1451
|
+
match s:
|
|
1452
|
+
case SField{n, fs, rest}:
|
|
1453
|
+
is_keyed(rest)
|
|
1454
|
+
case SEnd{}:
|
|
1455
|
+
True{}
|
|
1456
|
+
case SVariant{n, vs, rest}:
|
|
1457
|
+
is_keyed(rest)
|
|
1458
|
+
case SVEnd{}:
|
|
1459
|
+
True{}
|
|
1460
|
+
case _:
|
|
1461
|
+
False{}
|
|
1462
|
+
|
|
1463
|
+
# n names no case of the tagged chain s, whose key is k, and the chain ends
|
|
1464
|
+
# in STagEnd{k}: one key for the whole chain.
|
|
1465
|
+
def fresh_t(+k: String, +n: String, s: Schema) -> Bool:
|
|
1466
|
+
match s:
|
|
1467
|
+
case STagged{+k2, +m, cs, rest}:
|
|
1468
|
+
Bool.and(String.eq(k2, k), Bool.and(Bool.not(String.eq(m, n)), fresh_t(k, n, rest)))
|
|
1469
|
+
case STagEnd{k2}:
|
|
1470
|
+
String.eq(k2, k)
|
|
1471
|
+
case _:
|
|
1472
|
+
False{}
|
|
1473
|
+
|
|
1474
|
+
def wf(s: Schema) -> Bool:
|
|
1475
|
+
match s:
|
|
1476
|
+
case SOpt{+i}:
|
|
1477
|
+
Bool.and(Bool.not(nullable(i)), wf(i))
|
|
1478
|
+
case SOptional{+i}:
|
|
1479
|
+
Bool.and(Bool.not(opt_at(i)), wf(i))
|
|
1480
|
+
case SList{+e}:
|
|
1481
|
+
Bool.and(Bool.not(opt_at(e)), wf(e))
|
|
1482
|
+
case SListLen{lo_, hi_, +s2}:
|
|
1483
|
+
Bool.and(Bool.not(opt_at(s2)), wf(s2))
|
|
1484
|
+
case SStrLen{lo_, hi_, +s2}:
|
|
1485
|
+
Bool.and(Bool.not(opt_at(s2)), wf(s2))
|
|
1486
|
+
case SField{+n, fs, +rest}:
|
|
1487
|
+
Bool.and(wf(fs), Bool.and(fresh_f(n, rest), wf(rest)))
|
|
1488
|
+
case SRule{+s2, tag}:
|
|
1489
|
+
Bool.and(Bool.not(opt_at(s2)), wf(s2))
|
|
1490
|
+
case SStrict{+s2}:
|
|
1491
|
+
Bool.and(is_keyed(s2), Bool.and(Bool.not(opt_at(s2)), wf(s2)))
|
|
1492
|
+
case STagged{+k, +n, +cs, +rest}:
|
|
1493
|
+
Bool.and(Bool.not(opt_at(cs)), Bool.and(wf(cs), Bool.and(no_key(k, cs), Bool.and(fresh_t(k, n, rest), wf(rest)))))
|
|
1494
|
+
case SVariant{+n, +vs, +rest}:
|
|
1495
|
+
Bool.and(Bool.not(opt_at(vs)), Bool.and(wf(vs), Bool.and(fresh_v(n, rest), wf(rest))))
|
|
1496
|
+
case STuple{+ts, +rest}:
|
|
1497
|
+
Bool.and(Bool.not(opt_at(ts)), Bool.and(wf(ts), Bool.and(Bool.not(opt_at(rest)), wf(rest))))
|
|
1498
|
+
case _:
|
|
1499
|
+
True{}
|
|
1500
|
+
|
|
1501
|
+
# Every enum value in x is one of its names (encode_conforms' premise).
|
|
1502
|
+
def names_ok(s: Schema, x: Meaning(s)) -> Bool:
|
|
1503
|
+
match s x:
|
|
1504
|
+
case SOpt{i} None{}:
|
|
1505
|
+
True{}
|
|
1506
|
+
case SOpt{i} Some{v}:
|
|
1507
|
+
names_ok(i, v)
|
|
1508
|
+
case SList{e} Nil{}:
|
|
1509
|
+
True{}
|
|
1510
|
+
case SList{+e} h <> t:
|
|
1511
|
+
Bool.and(names_ok(e, h), names_ok(SList{e}, t))
|
|
1512
|
+
case SField{n, fs, rest} Both{a, b}:
|
|
1513
|
+
Bool.and(names_ok(fs, a), names_ok(rest, b))
|
|
1514
|
+
case SRule{s2, tag} v:
|
|
1515
|
+
names_ok(s2, v)
|
|
1516
|
+
case SStrict{s2} v:
|
|
1517
|
+
names_ok(s2, v)
|
|
1518
|
+
case SStrLen{lo_, hi_, s2} v:
|
|
1519
|
+
names_ok(s2, v)
|
|
1520
|
+
case SListLen{lo_, hi_, s2} v:
|
|
1521
|
+
names_ok(s2, v)
|
|
1522
|
+
case SOptional{i} None{}:
|
|
1523
|
+
True{}
|
|
1524
|
+
case SOptional{i} Some{v}:
|
|
1525
|
+
names_ok(i, v)
|
|
1526
|
+
case STagged{k, n, cs, rest} Inl{a}:
|
|
1527
|
+
names_ok(cs, a)
|
|
1528
|
+
case STagged{k, n, cs, rest} Inr{b}:
|
|
1529
|
+
names_ok(rest, b)
|
|
1530
|
+
case SEnum{names} x:
|
|
1531
|
+
in_names(x, names)
|
|
1532
|
+
case SVariant{n, vs, rest} Inl{a}:
|
|
1533
|
+
names_ok(vs, a)
|
|
1534
|
+
case SVariant{n, vs, rest} Inr{b}:
|
|
1535
|
+
names_ok(rest, b)
|
|
1536
|
+
case STuple{ts, rest} Both{a, b}:
|
|
1537
|
+
Bool.and(names_ok(ts, a), names_ok(rest, b))
|
|
1538
|
+
case _ _:
|
|
1539
|
+
True{}
|
|
1540
|
+
|
|
1541
|
+
# Every bound a constructor states holds of the value that will be written, at
|
|
1542
|
+
# every place one sits in the schema (encode_conforms' second premise, beside
|
|
1543
|
+
# names_ok). The encoder writes the meaning unchanged, so a value outside a
|
|
1544
|
+
# bound is written out as it is and refused by check on the way back: the host
|
|
1545
|
+
# that built it is at fault, and this is where it says so. An SStrLen's bound
|
|
1546
|
+
# is read off the string its own schema writes, and an SListLen's count off the
|
|
1547
|
+
# list it writes, which is why enc appears here.
|
|
1548
|
+
def bounds_ok(s: Schema, x: Meaning(s)) -> Bool:
|
|
1549
|
+
match s x:
|
|
1550
|
+
case SOpt{i} None{}:
|
|
1551
|
+
True{}
|
|
1552
|
+
case SOpt{i} Some{v}:
|
|
1553
|
+
bounds_ok(i, v)
|
|
1554
|
+
case SList{e} Nil{}:
|
|
1555
|
+
True{}
|
|
1556
|
+
case SList{+e} h <> t:
|
|
1557
|
+
Bool.and(bounds_ok(e, h), bounds_ok(SList{e}, t))
|
|
1558
|
+
case SField{n, fs, rest} Both{a, b}:
|
|
1559
|
+
Bool.and(bounds_ok(fs, a), bounds_ok(rest, b))
|
|
1560
|
+
case SRule{s2, tag} v:
|
|
1561
|
+
bounds_ok(s2, v)
|
|
1562
|
+
case SStrict{s2} v:
|
|
1563
|
+
bounds_ok(s2, v)
|
|
1564
|
+
case STagged{k, n, cs, rest} Inl{a}:
|
|
1565
|
+
bounds_ok(cs, a)
|
|
1566
|
+
case STagged{k, n, cs, rest} Inr{b}:
|
|
1567
|
+
bounds_ok(rest, b)
|
|
1568
|
+
case SNatIn{+lo, +hi} n:
|
|
1569
|
+
num_ok(lo, hi, n)
|
|
1570
|
+
case SStrLen{+lo, +hi, +s2} +v:
|
|
1571
|
+
Bool.and(bounds_ok(s2, v), len_ok(lo, hi, enc(s2, v)))
|
|
1572
|
+
case SListLen{+lo, +hi, +s2} +v:
|
|
1573
|
+
Bool.and(bounds_ok(s2, v), list_len_ok(lo, hi, enc(s2, v)))
|
|
1574
|
+
case SOptional{i} None{}:
|
|
1575
|
+
True{}
|
|
1576
|
+
case SOptional{i} Some{v}:
|
|
1577
|
+
bounds_ok(i, v)
|
|
1578
|
+
case SVariant{n, vs, rest} Inl{a}:
|
|
1579
|
+
bounds_ok(vs, a)
|
|
1580
|
+
case SVariant{n, vs, rest} Inr{b}:
|
|
1581
|
+
bounds_ok(rest, b)
|
|
1582
|
+
case STuple{ts, rest} Both{a, b}:
|
|
1583
|
+
Bool.and(bounds_ok(ts, a), bounds_ok(rest, b))
|
|
1584
|
+
case _ _:
|
|
1585
|
+
True{}
|