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/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{}