main.bend

emerging-eztoml@0.7.0.0 · 6,368 lines · 0 definitions

1# eztoml reads and writes TOML documents. Values are strings, integers, floats,
2# booleans, datetimes, arrays, inline tables, tables, and arrays of tables.
3# Comments and blank lines are passed over.
4#
5# parse, render, get, at, root, bad, string, digits, flag
6# str, integer, float, boolean, array, inline, table, pair
7# key, wf
8import Base
9
10# the sign of an integer, a float, or a zone offset
11type Sign is Data:
12  Plus{}
13  Minus{}
14
15# a calendar date: year, month, day
16type Date is Data:
17  Date{y: U32, mo: U32, d: U32}
18
19# a time of day. frac is the fractional second, empty when there is none
20type Clock is Data:
21  Clock{h: U32, mi: U32, s: U32, frac: String}
22
23# a zone offset: Z, or a numeric offset
24type Zone is Data:
25  Zulu{}
26  Shift{sign: Sign, h: U32, mi: U32}
27
28# the four TOML datetime forms
29type When is Data:
30  WOff{date: Date, clock: Clock, zone: Zone}
31  WLocal{date: Date, clock: Clock}
32  WDate{date: Date}
33  WTime{clock: Clock}
34
35# one TOML value. Tables remember the dotted path they were opened under.
36# An inline table is VInl. An array of tables is VAots, and each element is
37# VAot. VPair is a key in a table. VSpan is a plain string: the first n
38# characters of a source suffix.
39type Val is Data:
40  VStr{text: String}
41  VSpan{src: String, n: U32}
42  VInt{sign: Sign, digits: String}
43  VFlo{sign: Sign, text: String}
44  VBool{bit: Bool}
45  VWhen{when: When}
46  VArr{items: List<&2, Val>}
47  VInl{rows: List<&2, Val>}
48  VHead{path: String, rows: List<&2, Val>}
49  VAot{path: String, rows: List<&2, Val>}
50  VAots{path: String, elems: List<&2, Val>}
51  VPair{name: String, val: Val}
52
53# a key found in a table, or not
54type Hit is Data:
55  Found{val: Val}
56  Miss{}
57
58# a parsed document: the first error, and the root table in order
59type Doc is Data:
60  Doc{bad: String, rows: List<&2, Val>}
61
62# a value read from a bare word, or the reason it is not one
63type Got is Data:
64  GotVal{val: Val}
65  GotBad{why: String}
66
67# digits taken from the front of a character list
68type Dig is Data:
69  DigOk{n: U32, cs: List<&2, Char>}
70  DigBad{}
71
72# what a datetime scan produced
73type WhenOut is Data:
74  WSome{when: When}
75  WFail{why: String}
76  WSkip{}
77
78# a decimal numeral being built, least-significant digit first
79type NK is Data:
80  NLead{}
81  NSign{}
82  NZero{}
83  NDig{}
84  NBase{}
85  NFrac{}
86  NExp{}
87  NEDig{}
88  NBad{}
89
90# the numeral scan
91type Num is Data:
92  Num{nk: NK, sign: Sign, base: U32, ds: List<&2, U32>, us: Bool, saw: Bool,
93    why: String}
94
95# how a basic-string character is written
96type BK is Data:
97  BKCh{}
98  BKHex{}
99  BKEsc{mark: Char}
100
101# where the scanner is in a document
102type Lex is Data:
103  LTop{}
104  LLine{}
105  LKey{}
106  LKeyDot{}
107  LAfter{}
108  LKeyQ{}
109  LKeyE{}
110  LKeyU{}
111  LVal{}
112  LQ{}
113  LQ2{}
114  LStr{}
115  LEsc{}
116  LUni{}
117  LFold{}
118  LMq{}
119  LMqEnd{}
120  LTrim{}
121  LTrim2{}
122  LBare{}
123  LDate{}
124  LCom{}
125  LHead{}
126  LHeadGap{}
127  LHeadDot{}
128  LHeadQ{}
129  LHeadE{}
130  LHeadU{}
131  LHeadR{}
132  LArr{}
133  LArrC{}
134  LInl{}
135  LInlK{}
136  LInlC{}
137  LSink{}
138
139# which quotes a string is using
140type QKind is Data:
141  QBasic{}
142  QLit{}
143  QMulti{}
144  QMultiLit{}
145
146# one character to read again
147type Hold is Data:
148  HNone{}
149  HChar{c: Char}
150
151# an open array or inline table
152type Ctx is Data:
153  CArr{items: List<&2, Val>, parts: List<&2, String>, here: List<&2, String>}
154  CInl{rows: List<&2, Val>, parts: List<&2, String>, here: List<&2, String>}
155
156# the scanner
157type St is Data:
158  St{bad: String, rows: List<&2, Val>, heads: List<&2, String>,
159    here: List<&2, String>, parts: List<&2, String>, buf: List<&2, Char>,
160    lex: Lex, back: Lex, qkind: QKind, stack: List<&2, Ctx>, hold: Hold,
161    uni: U32, left: U32, arrtab: Bool, lead: Bool}
162
163# an escape in a basic string
164type Esc is Data:
165  EChar{c: Char}
166  EUni{n: U32}
167  EFold{saw: Bool}
168  EBad{}
169
170# a character a bare key may hold
171def bare.at(+cc: Char) -> Bool:
172  Bool.or(Char.is_alpha(cc), Bool.or(Char.is_digit(cc),
173    Bool.or(Char.is_eq(cc, '_'), Char.is_eq(cc, '-'))))
174
175# one step of the bare-key walk, with the head's answer in hand
176def bare.all.step(here: Bool, rest: Unit -> Bool) -> Bool:
177  match here:
178    case True{}:
179      rest(Unit{})
180    case False{}:
181      False{}
182
183# whether every character of a key is one a bare key may hold
184def bare.all(cs: List<&2, Char>) -> Bool:
185  match cs:
186    case []:
187      True{}
188    case +h <> t:
189      bare.all.step(bare.at(h), _u => bare.all(t))
190
191# whether a key can be written without quotes
192def bare(+ss: String) -> Bool:
193  Bool.and(Bool.not(String.is_empty(ss)), bare.all(String.to_list(ss)))
194
195# one hex nibble as a character
196def hex.char.go(dec: Bool, nn: U32) -> Char:
197  match dec:
198    case True{}:
199      Chr{U32.add(48, nn)}
200    case False{}:
201      Chr{U32.add(87, nn)}
202
203# one hex nibble as a character
204def hex.char(+nn: U32) -> Char:
205  hex.char.go(U32.is_lt(nn, 10), nn)
206
207# four lowercase hex digits of a code point, then the \u introducer, reversed
208def basic.hex(+uu: U32, out: List<&2, Char>) -> List<&2, Char>:
209  hex.char(U32.mod(uu, 16)) <> (hex.char(U32.mod(U32.shrn(uu, 4n), 16)) <>
210    (hex.char(U32.mod(U32.shrn(uu, 8n), 16)) <>
211      (hex.char(U32.mod(U32.shrn(uu, 12n), 16)) <> ('u' <> ('\\' <> out)))))
212
213# an escape mark reversed onto the output: the mark, then the backslash
214def basic.esc(mark: Char, out: List<&2, Char>) -> List<&2, Char>:
215  mark <> ('\\' <> out)
216
217# other controls are \u escapes; the rest are themselves
218def basic.cls.hex(hit: Bool) -> BK:
219  match hit:
220    case True{}:
221      BKHex{}
222    case False{}:
223      BKCh{}
224
225# backslash
226def basic.cls.bslash(hit: Bool, +uu: U32) -> BK:
227  match hit:
228    case True{}:
229      BKEsc{'\\'}
230    case False{}:
231      basic.cls.hex(Bool.or(U32.is_lt(uu, 32), U32.is_eq(uu, 127)))
232
233# quotation mark
234def basic.cls.quote(hit: Bool, +uu: U32) -> BK:
235  match hit:
236    case True{}:
237      BKEsc{'"'}
238    case False{}:
239      basic.cls.bslash(U32.is_eq(uu, 92), uu)
240
241# carriage return
242def basic.cls.cr(hit: Bool, +uu: U32) -> BK:
243  match hit:
244    case True{}:
245      BKEsc{'r'}
246    case False{}:
247      basic.cls.quote(U32.is_eq(uu, 34), uu)
248
249# form feed
250def basic.cls.ff(hit: Bool, +uu: U32) -> BK:
251  match hit:
252    case True{}:
253      BKEsc{'f'}
254    case False{}:
255      basic.cls.cr(U32.is_eq(uu, 13), uu)
256
257# line feed
258def basic.cls.lf(hit: Bool, +uu: U32) -> BK:
259  match hit:
260    case True{}:
261      BKEsc{'n'}
262    case False{}:
263      basic.cls.ff(U32.is_eq(uu, 12), uu)
264
265# tab
266def basic.cls.tab(hit: Bool, +uu: U32) -> BK:
267  match hit:
268    case True{}:
269      BKEsc{'t'}
270    case False{}:
271      basic.cls.lf(U32.is_eq(uu, 10), uu)
272
273# backspace, then the rest
274def basic.cls.bs(hit: Bool, +uu: U32) -> BK:
275  match hit:
276    case True{}:
277      BKEsc{'b'}
278    case False{}:
279      basic.cls.tab(U32.is_eq(uu, 9), uu)
280
281# the class of a code point in a basic string
282def basic.cls(+uu: U32) -> BK:
283  basic.cls.bs(U32.is_eq(uu, 8), uu)
284
285# one character of a basic string, reversed onto the output
286def basic.disp(kk: BK, cc: Char, uu: U32, out: List<&2, Char>) -> List<&2, Char>:
287  match kk:
288    case BKCh{}:
289      cc <> out
290    case BKEsc{mark}:
291      basic.esc(mark, out)
292    case BKHex{}:
293      basic.hex(uu, out)
294
295# one character of a basic string
296def basic.one(+cc: Char, out: List<&2, Char>) -> List<&2, Char>:
297  +u = Char.to_u32(cc)
298  basic.disp(basic.cls(u), cc, u, out)
299
300# the escaped characters of a basic string, reversed
301def basic.go(cs: List<&2, Char>, out: List<&2, Char>) -> List<&2, Char>:
302  match cs:
303    case []:
304      out
305    case +h <> t:
306      basic.go(t, basic.one(h, out))
307
308# a basic string: quotes, with controls and quotes escaped
309def key.basic(+ss: String) -> String:
310  "\"" ++ String.from_list(List.reverse(&2, Char,
311    basic.go(String.to_list(ss), []))) ++ "\""
312
313# bare when the name allows it, otherwise a basic string
314def key.go(ok: Bool, +ss: String) -> String:
315  match ok:
316    case True{}:
317      ss
318    case False{}:
319      key.basic(ss)
320
321# a key as TOML writes it
322def key(+ss: String) -> String:
323  key.go(bare(ss), ss)
324
325# the overflow of a decimal step, least-significant digit first
326def dec.carry.c0(zz: Bool, cc: U32) -> List<&2, U32>:
327  match zz:
328    case True{}:
329      []
330    case False{}:
331      [U32.mod(cc, 10)]
332
333# the overflow of a decimal step
334def dec.carry.c(+cc: U32) -> List<&2, U32>:
335  dec.carry.c0(U32.is_zero(cc), cc)
336
337# the next digit of an overflow
338def dec.carry.b0(zz: Bool, +cc: U32) -> List<&2, U32>:
339  match zz:
340    case True{}:
341      []
342    case False{}:
343      U32.mod(cc, 10) <> dec.carry.c(U32.div(cc, 10))
344
345# the next digit of an overflow
346def dec.carry.b(+cc: U32) -> List<&2, U32>:
347  dec.carry.b0(U32.is_zero(cc), cc)
348
349# the first digit of an overflow
350def dec.carry.a(zz: Bool, +cc: U32) -> List<&2, U32>:
351  match zz:
352    case True{}:
353      []
354    case False{}:
355      U32.mod(cc, 10) <> dec.carry.b(U32.div(cc, 10))
356
357# the digits of a leftover carry
358def dec.carry(+cc: U32) -> List<&2, U32>:
359  dec.carry.a(U32.is_zero(cc), cc)
360
361# multiply a decimal numeral by a radix and add a digit
362def dec.go(ds: List<&2, U32>, +radix: U32, +carry: U32) -> List<&2, U32>:
363  match ds:
364    case []:
365      dec.carry(carry)
366    case +d <> t:
367      U32.mod(U32.add(U32.mul(d, radix), carry), 10) <> dec.go(t, radix,
368        U32.div(U32.add(U32.mul(d, radix), carry), 10))
369
370# a digit character
371def dec.ch(dd: U32) -> Char:
372  Chr{U32.add(48, dd)}
373
374# digits as characters, in the order given
375def dec.chars(ds: List<&2, U32>) -> List<&2, Char>:
376  match ds:
377    case []:
378      []
379    case d <> t:
380      dec.ch(d) <> dec.chars(t)
381
382# whether a leading zero is still being skipped
383def dec.drop.lead(lead: Bool, +hh: Char) -> Bool:
384  match lead:
385    case False{}:
386      False{}
387    case True{}:
388      Char.is_eq(hh, '0')
389
390# a leading zero is skipped; any other character is kept
391def dec.drop.outz(zz: Bool, hh: Char, out: List<&2, Char>) -> List<&2, Char>:
392  match zz:
393    case True{}:
394      out
395    case False{}:
396      hh <> out
397
398# the output after one character, reversed
399def dec.drop.out(lead: Bool, +hh: Char, out: List<&2, Char>) -> List<&2, Char>:
400  match lead:
401    case False{}:
402      hh <> out
403    case True{}:
404      dec.drop.outz(Char.is_eq(hh, '0'), hh, out)
405
406# the numeral, or 0 when every digit was zero
407def dec.drop.end(lead: Bool, out: List<&2, Char>) -> String:
408  match lead:
409    case True{}:
410      "0"
411    case False{}:
412      String.from_list(List.reverse(&2, Char, out))
413
414# drop leading zeros from a most-significant-first digit list
415def dec.drop(cs: List<&2, Char>, +lead: Bool, out: List<&2, Char>) -> String:
416  match cs:
417    case []:
418      dec.drop.end(lead, out)
419    case +h <> t:
420      dec.drop(t, dec.drop.lead(lead, h), dec.drop.out(lead, h, out))
421
422# a decimal numeral. The digit list is least-significant first
423def dec.text(ds: List<&2, U32>) -> String:
424  dec.drop(dec.chars(List.reverse(&2, U32, ds)), True{}, [])
425
426# a code point's digit value, or 99 when the character is not a digit
427def dig.up(ok: Bool, uu: U32) -> U32:
428  match ok:
429    case True{}:
430      U32.sub(uu, 55)
431    case False{}:
432      99
433
434# a lowercase hex digit, otherwise an uppercase one
435def dig.low(ok: Bool, +uu: U32) -> U32:
436  match ok:
437    case True{}:
438      U32.sub(uu, 87)
439    case False{}:
440      dig.up(Bool.and(U32.is_ge(uu, 65), U32.is_le(uu, 70)), uu)
441
442# a decimal digit, otherwise a hex letter
443def dig.dec(ok: Bool, +uu: U32) -> U32:
444  match ok:
445    case True{}:
446      U32.sub(uu, 48)
447    case False{}:
448      dig.low(Bool.and(U32.is_ge(uu, 97), U32.is_le(uu, 102)), uu)
449
450# the value of a digit character, or 99
451def dig.val(+cc: Char) -> U32:
452  +u = Char.to_u32(cc)
453  dig.dec(Bool.and(U32.is_ge(u, 48), U32.is_le(u, 57)), u)
454
455# whether a digit value fits a base
456def dig.ok(dd: U32, base: U32) -> Bool:
457  U32.is_lt(dd, base)
458
459# exactly k digits, or DigBad
460def take.acc.d(digit: Bool, nn: U32, +cc: Char) -> U32:
461  match digit:
462    case False{}:
463      nn
464    case True{}:
465      U32.add(U32.mul(nn, 10), U32.sub(Char.to_u32(cc), 48))
466
467# the accumulator after one character
468def take.acc(ok: Bool, nn: U32, +cc: Char) -> U32:
469  match ok:
470    case False{}:
471      nn
472    case True{}:
473      take.acc.d(Char.is_digit(cc), nn, cc)
474
475# the scan stays well only while every character is a digit
476def take.ok(ok: Bool, +cc: Char) -> Bool:
477  match ok:
478    case False{}:
479      False{}
480    case True{}:
481      Char.is_digit(cc)
482
483# the finished take
484def take.fin(ok: Bool, nn: U32, cs: List<&2, Char>) -> Dig:
485  match ok:
486    case True{}:
487      DigOk{nn, cs}
488    case False{}:
489      DigBad{}
490
491# exactly k digits from the front of a list
492def take.n(kk: Nat, cs: List<&2, Char>, nn: U32, +ok: Bool) -> Dig:
493  match kk:
494    case 0n:
495      take.fin(ok, nn, cs)
496    case 1n+p:
497      match cs:
498        case []:
499          DigBad{}
500        case +h <> t:
501          take.n(p, t, take.acc(ok, nn, h), take.ok(ok, h))
502
503# a number is inside the inclusive bounds
504def bound.rng(+nn: U32, lo: U32, hi: U32) -> Bool:
505  Bool.and(U32.is_ge(nn, lo), U32.is_le(nn, hi))
506
507# two digits, zero padded
508def pad2.go(small: Bool, nn: U32) -> String:
509  match small:
510    case True{}:
511      "0" ++ U32.show(nn)
512    case False{}:
513      U32.show(nn)
514
515# two digits, zero padded
516def text.pad2(+nn: U32) -> String:
517  pad2.go(U32.is_lt(nn, 10), nn)
518
519# the one-zero pad, or the digits themselves
520def pad4.big(nn: U32, big: Bool) -> String:
521  match big:
522    case True{}:
523      "0" ++ U32.show(nn)
524    case False{}:
525      U32.show(nn)
526
527# the three-digit and two-digit pads
528def pad4.mid(nn: U32, mid: Bool, big: Bool) -> String:
529  match mid:
530    case True{}:
531      "00" ++ U32.show(nn)
532    case False{}:
533      pad4.big(nn, big)
534
535# four digits, zero padded
536def pad4.go(nn: U32, small: Bool, mid: Bool, big: Bool) -> String:
537  match small:
538    case True{}:
539      "000" ++ U32.show(nn)
540    case False{}:
541      pad4.mid(nn, mid, big)
542
543# four digits, zero padded
544def text.pad4(+nn: U32) -> String:
545  pad4.go(nn, U32.is_lt(nn, 10), U32.is_lt(nn, 100), U32.is_lt(nn, 1000))
546
547# the sign of a zone offset
548def zone.mark(sign: Sign) -> String:
549  match sign:
550    case Plus{}:
551      "+"
552    case Minus{}:
553      "-"
554
555# a numeric zone offset
556def zone.shift(sign: Sign, hh: U32, mi: U32) -> String:
557  zone.mark(sign) ++ text.pad2(hh) ++ ":" ++ text.pad2(mi)
558
559# a zone suffix
560def zone.text(zz: Zone) -> String:
561  match zz:
562    case Zulu{}:
563      "Z"
564    case Shift{sign, h, mi}:
565      zone.shift(sign, h, mi)
566
567# the fractional second, or nothing
568def clock.frac.go(empty: Bool, frac: String) -> String:
569  match empty:
570    case True{}:
571      ""
572    case False{}:
573      "." ++ frac
574
575# the fractional second, or nothing
576def clock.frac(+frac: String) -> String:
577  clock.frac.go(String.is_empty(frac), frac)
578
579# a clock, with a fractional second when it has one
580def clock.text(cc: Clock) -> String:
581  Clock{h, mi, s, frac} = cc
582  text.pad2(h) ++ ":" ++ text.pad2(mi) ++ ":" ++ text.pad2(s) ++ clock.frac(frac)
583
584# a date
585def date.text(dd: Date) -> String:
586  Date{y, mo, day} = dd
587  text.pad4(y) ++ "-" ++ text.pad2(mo) ++ "-" ++ text.pad2(day)
588
589# a datetime in TOML spelling
590def when.text(ww: When) -> String:
591  match ww:
592    case WOff{date, clock, zone}:
593      date.text(date) ++ "T" ++ clock.text(clock) ++ zone.text(zone)
594    case WLocal{date, clock}:
595      date.text(date) ++ "T" ++ clock.text(clock)
596    case WDate{date}:
597      date.text(date)
598    case WTime{clock}:
599      clock.text(clock)
600
601# the sign of an integer or a float, empty when it is positive
602def sign.text(sign: Sign) -> String:
603  match sign:
604    case Plus{}:
605      ""
606    case Minus{}:
607      "-"
608
609# whether the list still has a character
610def cs.some(cs: List<&2, Char>) -> Bool:
611  match cs:
612    case []:
613      False{}
614    case _ <> _:
615      True{}
616
617# one character, when the list has one
618def cs.head(cs: List<&2, Char>) -> Char:
619  match cs:
620    case []:
621      ' '
622    case h <> _:
623      h
624
625# the tail of a list
626def cs.tail(cs: List<&2, Char>) -> List<&2, Char>:
627  match cs:
628    case []:
629      []
630    case _ <> t:
631      t
632
633# a clock with its fraction filled in
634def clock.with(cc: Clock, frac: String) -> Clock:
635  Clock{h, mi, s, _f} = cc
636  Clock{h, mi, s, frac}
637
638# a finished local datetime, or one with a zone still to read
639def when.done(_date: Date, _clock: Clock, ww: When) -> WhenOut:
640  WSome{ww}
641
642# Z ends the token
643def when.zone.z(tt: List<&2, Char>, clock: Clock, date: Date) -> WhenOut:
644  match tt:
645    case []:
646      WSome{WOff{date, clock, Zulu{}}}
647    case _ <> _:
648      WFail{"invalid zone"}
649
650# the offset minute is in range
651def when.zone.mm.ok2(mi_ok: Bool, sign: Sign, hh: U32, mi: U32, clock: Clock, date: Date) -> WhenOut:
652  match mi_ok:
653    case False{}:
654      WFail{"zone minute is out of range"}
655    case True{}:
656      WSome{WOff{date, clock, Shift{sign, hh, mi}}}
657
658# the offset fields are in range
659def when.zone.mm.ok(h_ok: Bool, mi_ok: Bool, sign: Sign, hh: U32, mi: U32, clock: Clock, date: Date) -> WhenOut:
660  match h_ok:
661    case False{}:
662      WFail{"zone hour is out of range"}
663    case True{}:
664      when.zone.mm.ok2(mi_ok, sign, hh, mi, clock, date)
665
666# nothing follows an offset, and its fields are in range
667def when.zone.mm.end(
668  cs: List<&2, Char>,
669  h_ok: Bool,
670  mi_ok: Bool,
671  sign: Sign,
672  hh: U32,
673  mi: U32,
674  clock: Clock,
675  date: Date
676) -> WhenOut:
677  match cs:
678    case []:
679      when.zone.mm.ok(h_ok, mi_ok, sign, hh, mi, clock, date)
680    case _ <> _:
681      WFail{"invalid zone"}
682
683# the offset minute is in range
684def when.zone.mm.go(dd: Dig, sign: Sign, +hh: U32, clock: Clock, date: Date) -> WhenOut:
685  match dd:
686    case DigBad{}:
687      WFail{"invalid zone"}
688    case DigOk{+n, cs}:
689      when.zone.mm.end(cs, bound.rng(hh, 0, 23), bound.rng(n, 0, 59), sign, hh, n, clock, date)
690
691# the offset minute
692def when.zone.mm(hit: Bool, dd: Dig, sign: Sign, hh: U32, clock: Clock, date: Date) -> WhenOut:
693  match hit:
694    case False{}:
695      WFail{"invalid zone"}
696    case True{}:
697      when.zone.mm.go(dd, sign, hh, clock, date)
698
699# the colon in an offset
700def when.zone.hh.colon(cs: List<&2, Char>, sign: Sign, hh: U32, clock: Clock, date: Date) -> WhenOut:
701  match cs:
702    case []:
703      WFail{"invalid zone"}
704    case +c <> t:
705      when.zone.mm(Char.is_eq(c, ':'), take.n(2n, t, 0, True{}), sign, hh, clock, date)
706
707# the offset hour
708def when.zone.hh(dd: Dig, sign: Sign, clock: Clock, date: Date) -> WhenOut:
709  match dd:
710    case DigBad{}:
711      WFail{"invalid zone"}
712    case DigOk{n, cs}:
713      when.zone.hh.colon(cs, sign, n, clock, date)
714
715# HH:MM of a numeric offset
716def when.zone.off(sign: Sign, tt: List<&2, Char>, clock: Clock, date: Date) -> WhenOut:
717  when.zone.hh(take.n(2n, tt, 0, True{}), sign, clock, date)
718
719# a minus starts a numeric offset
720def when.zone.minus(minus: Bool, tt: List<&2, Char>, clock: Clock, date: Date) -> WhenOut:
721  match minus:
722    case True{}:
723      when.zone.off(Minus{}, tt, clock, date)
724    case False{}:
725      WFail{"invalid zone"}
726
727# a plus or a minus starts a numeric offset
728def when.zone.sign(plus: Bool, minus: Bool, tt: List<&2, Char>, clock: Clock, date: Date) -> WhenOut:
729  match plus:
730    case True{}:
731      when.zone.off(Plus{}, tt, clock, date)
732    case False{}:
733      when.zone.minus(minus, tt, clock, date)
734
735# Z, or a signed numeric offset
736def when.zone.go(zee: Bool, plus: Bool, minus: Bool, tt: List<&2, Char>, clock: Clock, date: Date) -> WhenOut:
737  match zee:
738    case True{}:
739      when.zone.z(tt, clock, date)
740    case False{}:
741      when.zone.sign(plus, minus, tt, clock, date)
742
743# a zone, or the end of a local datetime
744def when.zone(cs: List<&2, Char>, clock: Clock, date: Date) -> WhenOut:
745  match cs:
746    case []:
747      WSome{WLocal{date, clock}}
748    case +c <> t:
749      when.zone.go(Bool.or(Char.is_eq(c, 'Z'), Char.is_eq(c, 'z')),
750        Char.is_eq(c, '+'), Char.is_eq(c, '-'), t, clock, date)
751
752# the fraction must hold a digit
753def when.frac.end.go(some: Bool, rev: List<&2, Char>, clock: Clock, date: Date, rest: List<&2, Char>) -> WhenOut:
754  match some:
755    case False{}:
756      WFail{"invalid fraction"}
757    case True{}:
758      when.zone(rest, clock.with(clock, String.from_list(List.reverse(&2, Char, rev))),
759        date)
760
761# the fraction must hold a digit
762def when.frac.end(+rev: List<&2, Char>, clock: Clock, date: Date, rest: List<&2, Char>) -> WhenOut:
763  when.frac.end.go(cs.some(rev), rev, clock, date, rest)
764
765# one fractional digit, or the end of the fraction
766def when.frac.digit(
767  digit: Bool,
768  +cc: Char,
769  tt: List<&2, Char>,
770  rev: List<&2, Char>,
771  clock: Clock,
772  date: Date,
773  more: Unit -> WhenOut
774) -> WhenOut:
775  match digit:
776    case True{}:
777      more(Unit{})
778    case False{}:
779      when.frac.end(rev, clock, date, cc <> tt)
780
781# fractional digits
782def when.frac.digits(cs: List<&2, Char>, +rev: List<&2, Char>, +clock: Clock, +date: Date) -> WhenOut:
783  match cs:
784    case []:
785      when.frac.end(rev, clock, date, [])
786    case +c <> +t:
787      when.frac.digit(Char.is_digit(c), c, t, rev, clock, date,
788        _u => when.frac.digits(t, c <> rev, clock, date))
789
790# a dot starts a fraction; anything else is a zone
791def when.frac.go(dot: Bool, tt: List<&2, Char>, clock: Clock, date: Date, +cc: Char) -> WhenOut:
792  match dot:
793    case True{}:
794      when.frac.digits(tt, [], clock, date)
795    case False{}:
796      when.zone(cc <> tt, clock, date)
797
798# an optional fractional second
799def when.frac(cs: List<&2, Char>, +clock: Clock, +date: Date) -> WhenOut:
800  match cs:
801    case []:
802      when.done(date, clock, WLocal{date, clock})
803    case +c <> t:
804      when.frac.go(Char.is_eq(c, '.'), t, clock, date, c)
805
806# the second is in range, then the fraction and the zone
807def when.clock.range.s(s_ok: Bool, hh: U32, mi: U32, ss: U32, cs: List<&2, Char>, date: Date) -> WhenOut:
808  match s_ok:
809    case False{}:
810      WFail{"second is out of range"}
811    case True{}:
812      when.frac(cs, Clock{hh, mi, ss, ""}, date)
813
814# minute and second are in range
815def when.clock.range.m(mi_ok: Bool, s_ok: Bool, hh: U32, mi: U32, ss: U32, cs: List<&2, Char>, date: Date) -> WhenOut:
816  match mi_ok:
817    case False{}:
818      WFail{"minute is out of range"}
819    case True{}:
820      when.clock.range.s(s_ok, hh, mi, ss, cs, date)
821
822# hour, minute, and second are in range
823def when.clock.range(
824  h_ok: Bool,
825  mi_ok: Bool,
826  s_ok: Bool,
827  hh: U32,
828  mi: U32,
829  ss: U32,
830  cs: List<&2, Char>,
831  date: Date
832) -> WhenOut:
833  match h_ok:
834    case False{}:
835      WFail{"hour is out of range"}
836    case True{}:
837      when.clock.range.m(mi_ok, s_ok, hh, mi, ss, cs, date)
838
839# seconds, then an optional fraction and zone
840def when.clock.ss.go(dd: Dig, +hh: U32, +mi: U32, date: Date) -> WhenOut:
841  match dd:
842    case DigBad{}:
843      WFail{"invalid time"}
844    case DigOk{+n, cs}:
845      when.clock.range(bound.rng(hh, 0, 23), bound.rng(mi, 0, 59), bound.rng(n, 0, 60), hh, mi, n,
846        cs, date)
847
848# seconds after the colon
849def when.clock.ss(hit: Bool, dd: Dig, hh: U32, mi: U32, date: Date) -> WhenOut:
850  match hit:
851    case False{}:
852      WFail{"invalid time"}
853    case True{}:
854      when.clock.ss.go(dd, hh, mi, date)
855
856# the colon after the minutes
857def when.clock.mm.colon(cs: List<&2, Char>, hh: U32, mi: U32, date: Date) -> WhenOut:
858  match cs:
859    case []:
860      WFail{"invalid time"}
861    case +c <> t:
862      when.clock.ss(Char.is_eq(c, ':'), take.n(2n, t, 0, True{}), hh, mi, date)
863
864# minutes, then seconds
865def when.clock.mm(dd: Dig, hh: U32, date: Date) -> WhenOut:
866  match dd:
867    case DigBad{}:
868      WFail{"invalid time"}
869    case DigOk{n, cs}:
870      when.clock.mm.colon(cs, hh, n, date)
871
872# the colon after the hour
873def when.clock.colon.go(hit: Bool, hh: U32, tt: List<&2, Char>, date: Date) -> WhenOut:
874  match hit:
875    case False{}:
876      WFail{"invalid time"}
877    case True{}:
878      when.clock.mm(take.n(2n, tt, 0, True{}), hh, date)
879
880# the colon after the hour
881def when.clock.colon(cs: List<&2, Char>, hh: U32, date: Date) -> WhenOut:
882  match cs:
883    case []:
884      WFail{"invalid time"}
885    case +c <> t:
886      when.clock.colon.go(Char.is_eq(c, ':'), hh, t, date)
887
888# a clock following a date
889def when.clock.of(dd: Dig, date: Date) -> WhenOut:
890  match dd:
891    case DigBad{}:
892      WFail{"invalid time"}
893    case DigOk{n, cs}:
894      when.clock.colon(cs, n, date)
895
896# a space separates a date from a time
897def when.after.sp(sp: Bool, date: Date, tt: List<&2, Char>) -> WhenOut:
898  match sp:
899    case True{}:
900      when.clock.of(take.n(2n, tt, 0, True{}), date)
901    case False{}:
902      WFail{"invalid datetime"}
903
904# T or a space separates a date from a time
905def when.after.sep(tee: Bool, sp: Bool, date: Date, tt: List<&2, Char>) -> WhenOut:
906  match tee:
907    case True{}:
908      when.clock.of(take.n(2n, tt, 0, True{}), date)
909    case False{}:
910      when.after.sp(sp, date, tt)
911
912# the character after a date: nothing, T, or a space, and then a time
913def when.after.date(date: Date, cs: List<&2, Char>) -> WhenOut:
914  match cs:
915    case []:
916      WSome{WDate{date}}
917    case +c <> t:
918      when.after.sep(Bool.or(Char.is_eq(c, 'T'), Char.is_eq(c, 't')),
919        Char.is_eq(c, ' '), date, t)
920
921# the day is in range, then the rest of the token is a time or nothing
922def when.date.ok.d(d_ok: Bool, yy: U32, mo: U32, dd: U32, cs: List<&2, Char>) -> WhenOut:
923  match d_ok:
924    case False{}:
925      WFail{"day is out of range"}
926    case True{}:
927      when.after.date(Date{yy, mo, dd}, cs)
928
929# a date whose fields are in range
930def when.date.ok.go(mo_ok: Bool, d_ok: Bool, yy: U32, mo: U32, dd: U32, cs: List<&2, Char>) -> WhenOut:
931  match mo_ok:
932    case False{}:
933      WFail{"month is out of range"}
934    case True{}:
935      when.date.ok.d(d_ok, yy, mo, dd, cs)
936
937# February in a year divisible by 4 and by 100: 29 days only when the year
938# is also divisible by 400
939def when.feb.quad(four_hundred: Bool) -> U32:
940  match four_hundred:
941    case True{}:
942      29
943    case False{}:
944      28
945
946# February in a year divisible by 4: 29 days unless the year is a century
947def when.feb.cent(hundred: Bool, four_hundred: Bool) -> U32:
948  match hundred:
949    case False{}:
950      29
951    case True{}:
952      when.feb.quad(four_hundred)
953
954# February: 29 days in a leap year and 28 otherwise. A leap year is divisible
955# by 4, except by 100 unless also by 400 (RFC 3339, Appendix C)
956def when.feb.go(four: Bool, hundred: Bool, four_hundred: Bool) -> U32:
957  match four:
958    case False{}:
959      28
960    case True{}:
961      when.feb.cent(hundred, four_hundred)
962
963# the days of February in a year
964def when.feb(+yy: U32) -> U32:
965  when.feb.go(U32.is_eq(U32.mod(yy, 4), 0), U32.is_eq(U32.mod(yy, 100), 0),
966    U32.is_eq(U32.mod(yy, 400), 0))
967
968# April, June, September and November have 30 days
969def when.short(+mo: U32) -> Bool:
970  Bool.or(Bool.or(U32.is_eq(mo, 4), U32.is_eq(mo, 6)),
971    Bool.or(U32.is_eq(mo, 9), U32.is_eq(mo, 11)))
972
973# 30 days in a short month and 31 in the others
974def when.days.short(short: Bool) -> U32:
975  match short:
976    case True{}:
977      30
978    case False{}:
979      31
980
981# the days of a month, by whether it is February or a short month
982def when.days.go(feb: Bool, short: Bool, +yy: U32) -> U32:
983  match feb:
984    case True{}:
985      when.feb(yy)
986    case False{}:
987      when.days.short(short)
988
989# the days of month mo in year yy
990def when.days(+yy: U32, +mo: U32) -> U32:
991  when.days.go(U32.is_eq(mo, 2), when.short(mo), yy)
992
993# a date whose fields are in range: the day within its month in that year
994def when.date.ok(+yy: U32, +mo: U32, +dd: U32, cs: List<&2, Char>) -> WhenOut:
995  when.date.ok.go(bound.rng(mo, 1, 12), bound.rng(dd, 1, when.days(yy, mo)), yy, mo, dd, cs)
996
997# a local time: HH:MM:SS, with the hour already taken
998def when.time.rest(tt: List<&2, Char>, hh: U32) -> WhenOut:
999  when.clock.mm(take.n(2n, tt, 0, True{}), hh, Date{0, 1, 1})
1000
1001# a local time uses a dummy date and then keeps only the clock
1002def when.time.only(ww: WhenOut) -> WhenOut:
1003  match ww:
1004    case WSome{WLocal{_date, clock}}:
1005      WSome{WTime{clock}}
1006    case WSome{WOff{_date, clock, zone}}:
1007      WFail{"invalid time"}
1008    case WSome{other}:
1009      WFail{"invalid time"}
1010    case WFail{why}:
1011      WFail{why}
1012    case WSkip{}:
1013      WFail{"invalid time"}
1014
1015# a complete date, then whatever follows it
1016def when.day.end(dd: Dig, yy: U32, mo: U32) -> WhenOut:
1017  match dd:
1018    case DigBad{}:
1019      WFail{"invalid date"}
1020    case DigOk{n, cs}:
1021      when.date.ok(yy, mo, n, cs)
1022
1023# the day is two digits
1024def when.day.go(hit: Bool, dd: Dig, yy: U32, mo: U32) -> WhenOut:
1025  match hit:
1026    case False{}:
1027      WFail{"invalid date"}
1028    case True{}:
1029      when.day.end(dd, yy, mo)
1030
1031# the day
1032def when.day(cs: List<&2, Char>, yy: U32, mo: U32) -> WhenOut:
1033  match cs:
1034    case []:
1035      WFail{"invalid date"}
1036    case +c <> t:
1037      when.day.go(Char.is_eq(c, '-'), take.n(2n, t, 0, True{}), yy, mo)
1038
1039# the dash after the month
1040def when.month.go(dd: Dig, yy: U32) -> WhenOut:
1041  match dd:
1042    case DigBad{}:
1043      WFail{"invalid date"}
1044    case DigOk{n, cs}:
1045      when.day(cs, yy, n)
1046
1047# the month
1048def when.month(hit: Bool, dd: Dig, yy: U32) -> WhenOut:
1049  match hit:
1050    case False{}:
1051      WFail{"invalid date"}
1052    case True{}:
1053      when.month.go(dd, yy)
1054
1055# the dash after the year
1056def when.year.dash(cs: List<&2, Char>, yy: U32) -> WhenOut:
1057  match cs:
1058    case []:
1059      WFail{"invalid date"}
1060    case +c <> t:
1061      when.month(Char.is_eq(c, '-'), take.n(2n, t, 0, True{}), yy)
1062
1063# the fourth year digit, then the month
1064def when.year4(dd: Dig) -> WhenOut:
1065  match dd:
1066    case DigBad{}:
1067      WFail{"invalid date"}
1068    case DigOk{n, cs}:
1069      when.year.dash(cs, n)
1070
1071# two more year digits, then a dash
1072def when.year.more(digit: Bool, nn: U32, +cc: Char, tt: List<&2, Char>) -> WhenOut:
1073  match digit:
1074    case False{}:
1075      WFail{"invalid datetime"}
1076    case True{}:
1077      when.year4(take.n(1n, tt, U32.add(U32.mul(nn, 10), dig.val(cc)), True{}))
1078
1079# a colon means a local time; another digit continues a year
1080def when.branch(colon: Bool, digit: Bool, nn: U32, +cc: Char, tt: List<&2, Char>) -> WhenOut:
1081  match colon:
1082    case True{}:
1083      when.time.only(when.time.rest(tt, nn))
1084    case False{}:
1085      when.year.more(digit, nn, cc, tt)
1086
1087# two digits decide a time or a date
1088def when.after2.go(cs: List<&2, Char>, nn: U32) -> WhenOut:
1089  match cs:
1090    case []:
1091      WFail{"invalid datetime"}
1092    case +c <> t:
1093      when.branch(Char.is_eq(c, ':'), Char.is_digit(c), nn, c, t)
1094
1095# two digits decide a time (HH:) or a date (YYYY-)
1096def when.after2(dd: Dig) -> WhenOut:
1097  match dd:
1098    case DigBad{}:
1099      WFail{"invalid datetime"}
1100    case DigOk{n, cs}:
1101      when.after2.go(cs, n)
1102
1103# a datetime token
1104def when.read(cs: List<&2, Char>) -> WhenOut:
1105  when.after2(take.n(2n, cs, 0, True{}))
1106
1107# a dash after the first character
1108def word.dash.hit(first: Bool, +cc: Char) -> Bool:
1109  match first:
1110    case True{}:
1111      False{}
1112    case False{}:
1113      Char.is_eq(cc, '-')
1114
1115# either flag
1116def word.or(aa: Bool, bb: Bool) -> Bool:
1117  match aa:
1118    case True{}:
1119      True{}
1120    case False{}:
1121      bb
1122
1123# a colon anywhere in the token
1124def word.colon(cs: List<&2, Char>) -> Bool:
1125  match cs:
1126    case []:
1127      False{}
1128    case +c <> t:
1129      word.or(Char.is_eq(c, ':'), word.colon(t))
1130
1131# e or E: an exponent, so the token is a numeral rather than a datetime
1132def word.ee.hit(+cc: Char) -> Bool:
1133  Bool.or(Char.is_eq(cc, 'e'), Char.is_eq(cc, 'E'))
1134
1135# whether the token holds an exponent marker
1136def word.ee(cs: List<&2, Char>) -> Bool:
1137  match cs:
1138    case []:
1139      False{}
1140    case +c <> t:
1141      word.or(word.ee.hit(c), word.ee(t))
1142
1143# a datetime scan only when the token is not an exponent numeral
1144def word.as_date(date: Bool, exp: Bool) -> Bool:
1145  match exp:
1146    case True{}:
1147      False{}
1148    case False{}:
1149      date
1150
1151# a dash after the first character
1152def word.dash(cs: List<&2, Char>, first: Bool) -> Bool:
1153  match cs:
1154    case []:
1155      False{}
1156    case +c <> t:
1157      word.or(word.dash.hit(first, c), word.dash(t, False{}))
1158
1159# a negative nan, or not a special
1160def word.spec.nan3(cc: Bool) -> Got:
1161  match cc:
1162    case True{}:
1163      GotVal{VFlo{Minus{}, "nan"}}
1164    case False{}:
1165      GotBad{""}
1166
1167# a signed nan
1168def word.spec.nan2(bb: Bool, cc: Bool) -> Got:
1169  match bb:
1170    case True{}:
1171      GotVal{VFlo{Plus{}, "nan"}}
1172    case False{}:
1173      word.spec.nan3(cc)
1174
1175# nan
1176def word.spec.nan(aa: Bool, bb: Bool, cc: Bool) -> Got:
1177  match aa:
1178    case True{}:
1179      GotVal{VFlo{Plus{}, "nan"}}
1180    case False{}:
1181      word.spec.nan2(bb, cc)
1182
1183# character-for-character equality. a mismatch drops the rest and stops
1184def str.eq.step(aa: String, bb: String, same: Bool) -> Bool:
1185  match aa bb same:
1186    case SNil{} SNil{} True{}:
1187      True{}
1188    case SCon{x, xt} SCon{y, yt} True{}:
1189      str.eq.step(xt, yt, Char.is_eq(x, y))
1190    case _a _b _s:
1191      False{}
1192
1193# the search only needs the bool, not a rebuilt copy of either string
1194def str.eq(aa: String, bb: String) -> Bool:
1195  str.eq.step(aa, bb, True{})
1196
1197# a negative inf, or nan
1198def word.spec.inf3(cc: Bool, +ss: String) -> Got:
1199  match cc:
1200    case True{}:
1201      GotVal{VFlo{Minus{}, "inf"}}
1202    case False{}:
1203      word.spec.nan(str.eq(ss, "nan"), str.eq(ss, "+nan"), str.eq(ss, "-nan"))
1204
1205# a signed inf, or nan
1206def word.spec.inf2(bb: Bool, cc: Bool, +ss: String) -> Got:
1207  match bb:
1208    case True{}:
1209      GotVal{VFlo{Plus{}, "inf"}}
1210    case False{}:
1211      word.spec.inf3(cc, ss)
1212
1213# inf, or nan
1214def word.spec.inf(aa: Bool, bb: Bool, cc: Bool, +ss: String) -> Got:
1215  match aa:
1216    case True{}:
1217      GotVal{VFlo{Plus{}, "inf"}}
1218    case False{}:
1219      word.spec.inf2(bb, cc, ss)
1220
1221# false, or the rest of the specials
1222def word.spec.f(hit: Bool, +ss: String) -> Got:
1223  match hit:
1224    case True{}:
1225      GotVal{VBool{False{}}}
1226    case False{}:
1227      word.spec.inf(str.eq(ss, "inf"), str.eq(ss, "+inf"), str.eq(ss, "-inf"), ss)
1228
1229# true, or the rest of the specials
1230def word.spec.t(hit: Bool, other: Bool, +ss: String) -> Got:
1231  match hit:
1232    case True{}:
1233      GotVal{VBool{True{}}}
1234    case False{}:
1235      word.spec.f(other, ss)
1236
1237# true, false, inf, and nan, including a sign on inf and nan
1238def word.spec(+ss: String) -> Got:
1239  word.spec.t(str.eq(ss, "true"), str.eq(ss, "false"), ss)
1240
1241# a datetime result as a value
1242def word.from.when(ww: WhenOut) -> Got:
1243  match ww:
1244    case WSome{when}:
1245      GotVal{VWhen{when}}
1246    case WFail{why}:
1247      GotBad{why}
1248    case WSkip{}:
1249      GotBad{"invalid datetime"}
1250
1251# the spelling of a numeral with the leading sign and underscores removed
1252def word.body.put.sign(is_sign: Bool, cc: Char, out: List<&2, Char>) -> List<&2, Char>:
1253  match is_sign:
1254    case True{}:
1255      out
1256    case False{}:
1257      cc <> out
1258
1259# an underscore is dropped; every other character is kept
1260def word.body.put.us(is_us: Bool, cc: Char, out: List<&2, Char>) -> List<&2, Char>:
1261  match is_us:
1262    case True{}:
1263      out
1264    case False{}:
1265      cc <> out
1266
1267# one character of a numeral spelling
1268def word.body.put(lead: Bool, +cc: Char, out: List<&2, Char>) -> List<&2, Char>:
1269  match lead:
1270    case True{}:
1271      word.body.put.sign(Bool.or(Char.is_eq(cc, '+'), Char.is_eq(cc, '-')), cc, out)
1272    case False{}:
1273      word.body.put.us(Char.is_eq(cc, '_'), cc, out)
1274
1275# the spelling, reversed, then put back in order at the end
1276def word.body.go(cs: List<&2, Char>, lead: Bool, out: List<&2, Char>) -> List<&2, Char>:
1277  match cs:
1278    case []:
1279      List.reverse(&2, Char, out)
1280    case +c <> t:
1281      word.body.go(t, False{}, word.body.put(lead, c, out))
1282
1283# the spelling of a numeral with the leading sign and underscores removed
1284def word.body(cs: List<&2, Char>) -> String:
1285  String.from_list(word.body.go(cs, True{}, []))
1286
1287# a fresh numeral scan
1288def num.start() -> Num:
1289  Num{NLead{}, Plus{}, 10, [], False{}, False{}, ""}
1290
1291# a scan that has failed
1292def num.bad(_st: Num, why: String) -> Num:
1293  Num{NBad{}, Plus{}, 10, [], False{}, False{}, why}
1294
1295# set the sign and wait for a digit
1296def num.sign(_st: Num, sign: Sign) -> Num:
1297  Num{NSign{}, sign, 10, [], False{}, False{}, ""}
1298
1299# a digit that fits the base
1300def num.digit.ok(ok: Bool, +cc: Char, nk: NK, sign: Sign, +base: U32, ds: List<&2, U32>) -> Num:
1301  match ok:
1302    case False{}:
1303      num.bad(Num{NLead{}, Plus{}, 10, [], False{}, False{}, ""}, "invalid digit")
1304    case True{}:
1305      Num{nk, sign, base, dec.go(ds, base, dig.val(cc)), False{}, True{}, ""}
1306
1307# a decimal digit joined to the numeral
1308def num.digit.go(st: Num, +cc: Char, nk: NK) -> Num:
1309  Num{_nk, sign, +base, ds, _us, _saw, _why} = st
1310  num.digit.ok(dig.ok(dig.val(cc), base), cc, nk, sign, base, ds)
1311
1312# a decimal digit joined to the numeral
1313def num.digit(st: Num, +cc: Char, nk: NK) -> Num:
1314  num.digit.go(st, cc, nk)
1315
1316# an underscore between digits
1317def num.us.go(ok: Bool, nk: NK, sign: Sign, base: U32, ds: List<&2, U32>, why: String) -> Num:
1318  match ok:
1319    case False{}:
1320      num.bad(Num{NLead{}, Plus{}, 10, [], False{}, False{}, ""}, "invalid underscore")
1321    case True{}:
1322      Num{nk, sign, base, ds, True{}, True{}, why}
1323
1324# an underscore between digits
1325def num.us(st: Num) -> Num:
1326  Num{nk, sign, base, ds, us, saw, why} = st
1327  num.us.go(Bool.and(saw, Bool.not(us)), nk, sign, base, ds, why)
1328
1329# the base prefix after a leading zero
1330def num.base(st: Num, base: U32) -> Num:
1331  Num{_nk, sign, _b, _ds, _us, _saw, _why} = st
1332  Num{NBase{}, sign, base, [], False{}, False{}, ""}
1333
1334# a dot begins the fraction
1335def num.dot.go(ok: Bool, sign: Sign) -> Num:
1336  match ok:
1337    case False{}:
1338      num.bad(Num{NLead{}, Plus{}, 10, [], False{}, False{}, ""}, "invalid fraction")
1339    case True{}:
1340      Num{NFrac{}, sign, 10, [], False{}, False{}, ""}
1341
1342# a dot begins the fraction
1343def num.dot(st: Num) -> Num:
1344  Num{_nk, sign, _b, _ds, us, saw, _why} = st
1345  num.dot.go(Bool.and(saw, Bool.not(us)), sign)
1346
1347# an exponent marker
1348def num.exp.go(ok: Bool, sign: Sign) -> Num:
1349  match ok:
1350    case False{}:
1351      num.bad(Num{NLead{}, Plus{}, 10, [], False{}, False{}, ""}, "invalid exponent")
1352    case True{}:
1353      Num{NExp{}, sign, 10, [], False{}, False{}, ""}
1354
1355# an exponent marker
1356def num.exp(st: Num) -> Num:
1357  Num{_nk, sign, _b, _ds, us, saw, _why} = st
1358  num.exp.go(Bool.and(saw, Bool.not(us)), sign)
1359
1360# the sign already chosen
1361def num.sign.of(st: Num) -> Sign:
1362  Num{_nk, sign, _b, _ds, _us, _saw, _why} = st
1363  sign
1364
1365# a first digit
1366def num.lead.digit(digit: Bool, st: Num, +cc: Char) -> Num:
1367  match digit:
1368    case True{}:
1369      num.digit(st, cc, NDig{})
1370    case False{}:
1371      num.bad(st, "invalid number")
1372
1373# a leading zero, or another digit
1374def num.lead.zero(zero: Bool, digit: Bool, st: Num, +cc: Char) -> Num:
1375  match zero:
1376    case True{}:
1377      Num{NZero{}, num.sign.of(st), 10, [], False{}, True{}, ""}
1378    case False{}:
1379      num.lead.digit(digit, st, cc)
1380
1381# a minus, a zero, or a digit
1382def num.lead.minus(minus: Bool, zero: Bool, digit: Bool, st: Num, +cc: Char) -> Num:
1383  match minus:
1384    case True{}:
1385      num.sign(st, Minus{})
1386    case False{}:
1387      num.lead.zero(zero, digit, st, cc)
1388
1389# a sign, a zero, or a digit
1390def num.lead.go(plus: Bool, minus: Bool, zero: Bool, digit: Bool, st: Num, +cc: Char) -> Num:
1391  match plus:
1392    case True{}:
1393      num.sign(st, Plus{})
1394    case False{}:
1395      num.lead.minus(minus, zero, digit, st, cc)
1396
1397# the first character after an optional sign
1398def num.lead(st: Num, +cc: Char) -> Num:
1399  num.lead.go(Char.is_eq(cc, '+'), Char.is_eq(cc, '-'), Char.is_eq(cc, '0'),
1400    Char.is_digit(cc), st, cc)
1401
1402# a second sign is refused; any other character is read as the first one of
1403# an unsigned numeral, with the sign kept
1404def num.after.sign.go(twice: Bool, sign: Sign, +cc: Char) -> Num:
1405  match twice:
1406    case True{}:
1407      num.bad(num.start(), "invalid number")
1408    case False{}:
1409      num.lead(Num{NLead{}, sign, 10, [], False{}, False{}, ""}, cc)
1410
1411# the character after a sign
1412def num.after.sign(st: Num, +cc: Char) -> Num:
1413  num.after.sign.go(Bool.or(Char.is_eq(cc, '+'), Char.is_eq(cc, '-')), num.sign.of(st), cc)
1414
1415# a capital exponent; any other character is refused, since a digit or an
1416# underscore after a leading zero (`01`, `0_1`) would hide it
1417def num.zero.exp2(ee: Bool, st: Num, _c: Char) -> Num:
1418  match ee:
1419    case True{}:
1420      num.exp(st)
1421    case False{}:
1422      num.bad(st, "invalid number")
1423
1424# an exponent after zero, or a rejected extra digit
1425def num.zero.exp(eee: Bool, ee: Bool, st: Num, +cc: Char) -> Num:
1426  match eee:
1427    case True{}:
1428      num.exp(st)
1429    case False{}:
1430      num.zero.exp2(ee, st, cc)
1431
1432# a fraction or an exponent after zero
1433def num.zero.dot(dot: Bool, eee: Bool, ee: Bool, st: Num, +cc: Char) -> Num:
1434  match dot:
1435    case True{}:
1436      num.dot(st)
1437    case False{}:
1438      num.zero.exp(eee, ee, st, cc)
1439
1440# a binary prefix, or a float
1441def num.zero.b(bb: Bool, dot: Bool, eee: Bool, ee: Bool, st: Num, +cc: Char) -> Num:
1442  match bb:
1443    case True{}:
1444      num.base(st, 2)
1445    case False{}:
1446      num.zero.dot(dot, eee, ee, st, cc)
1447
1448# an octal prefix, or the rest
1449def num.zero.o(oo: Bool, bb: Bool, dot: Bool, eee: Bool, ee: Bool, st: Num, +cc: Char) -> Num:
1450  match oo:
1451    case True{}:
1452      num.base(st, 8)
1453    case False{}:
1454      num.zero.b(bb, dot, eee, ee, st, cc)
1455
1456# a base prefix, a fraction, or an exponent
1457def num.zero.go(xx: Bool, oo: Bool, bb: Bool, dot: Bool, eee: Bool, ee: Bool, st: Num, +cc: Char) -> Num:
1458  match xx:
1459    case True{}:
1460      num.base(st, 16)
1461    case False{}:
1462      num.zero.o(oo, bb, dot, eee, ee, st, cc)
1463
1464# the character after a lone zero
1465def num.zero(st: Num, +cc: Char) -> Num:
1466  num.zero.go(Char.is_eq(cc, 'x'), Char.is_eq(cc, 'o'), Char.is_eq(cc, 'b'),
1467    Char.is_eq(cc, '.'), Char.is_eq(cc, 'e'), Char.is_eq(cc, 'E'), st, cc)
1468
1469# a capital exponent, or a rejected character
1470def num.digs.exp2(ee: Bool, st: Num, _c: Char) -> Num:
1471  match ee:
1472    case True{}:
1473      num.exp(st)
1474    case False{}:
1475      num.bad(st, "invalid number")
1476
1477# an exponent
1478def num.digs.exp(eee: Bool, ee: Bool, st: Num, +cc: Char) -> Num:
1479  match eee:
1480    case True{}:
1481      num.exp(st)
1482    case False{}:
1483      num.digs.exp2(ee, st, cc)
1484
1485# a dot or an exponent
1486def num.digs.dot(dot: Bool, eee: Bool, ee: Bool, st: Num, +cc: Char) -> Num:
1487  match dot:
1488    case True{}:
1489      num.dot(st)
1490    case False{}:
1491      num.digs.exp(eee, ee, st, cc)
1492
1493# an underscore, a dot, or an exponent
1494def num.digs.us(us: Bool, dot: Bool, eee: Bool, ee: Bool, st: Num, +cc: Char) -> Num:
1495  match us:
1496    case True{}:
1497      num.us(st)
1498    case False{}:
1499      num.digs.dot(dot, eee, ee, st, cc)
1500
1501# a decimal digit, an underscore, a dot, or an exponent
1502def num.digs.go(digit: Bool, us: Bool, dot: Bool, eee: Bool, ee: Bool, st: Num, +cc: Char) -> Num:
1503  match digit:
1504    case True{}:
1505      num.digit(st, cc, NDig{})
1506    case False{}:
1507      num.digs.us(us, dot, eee, ee, st, cc)
1508
1509# a decimal digit, an underscore, a dot, or an exponent
1510def num.digs(st: Num, +cc: Char) -> Num:
1511  num.digs.go(Char.is_digit(cc), Char.is_eq(cc, '_'), Char.is_eq(cc, '.'),
1512    Char.is_eq(cc, 'e'), Char.is_eq(cc, 'E'), st, cc)
1513
1514# an underscore, or a digit of the base
1515def num.based.go(us: Bool, st: Num, +cc: Char) -> Num:
1516  match us:
1517    case True{}:
1518      num.us(st)
1519    case False{}:
1520      num.digit(st, cc, NBase{})
1521
1522# a digit of a prefixed integer, or an underscore
1523def num.based(st: Num, +cc: Char) -> Num:
1524  num.based.go(Char.is_eq(cc, '_'), st, cc)
1525
1526# a capital exponent, or a rejected character
1527def num.frac.exp2(ee: Bool, st: Num, _c: Char) -> Num:
1528  match ee:
1529    case True{}:
1530      num.exp(st)
1531    case False{}:
1532      num.bad(st, "invalid fraction")
1533
1534# an exponent in a fraction
1535def num.frac.exp(eee: Bool, ee: Bool, st: Num, +cc: Char) -> Num:
1536  match eee:
1537    case True{}:
1538      num.exp(st)
1539    case False{}:
1540      num.frac.exp2(ee, st, cc)
1541
1542# an underscore or an exponent in a fraction
1543def num.frac.us(us: Bool, eee: Bool, ee: Bool, st: Num, +cc: Char) -> Num:
1544  match us:
1545    case True{}:
1546      num.us(st)
1547    case False{}:
1548      num.frac.exp(eee, ee, st, cc)
1549
1550# a fraction digit, an underscore, or an exponent
1551def num.frac.go(digit: Bool, us: Bool, eee: Bool, ee: Bool, st: Num, +cc: Char) -> Num:
1552  match digit:
1553    case True{}:
1554      num.digit(st, cc, NFrac{})
1555    case False{}:
1556      num.frac.us(us, eee, ee, st, cc)
1557
1558# a fraction digit, an underscore, or an exponent
1559def num.frac(st: Num, +cc: Char) -> Num:
1560  num.frac.go(Char.is_digit(cc), Char.is_eq(cc, '_'), Char.is_eq(cc, 'e'),
1561    Char.is_eq(cc, 'E'), st, cc)
1562
1563# the exponent sign is part of the spelling; digits follow
1564def num.exp.sign(st: Num) -> Num:
1565  Num{_nk, sign, _b, _ds, _us, _saw, _why} = st
1566  Num{NEDig{}, sign, 10, [], False{}, False{}, ""}
1567
1568# a digit of the exponent
1569def num.expon.digit(digit: Bool, st: Num, +cc: Char) -> Num:
1570  match digit:
1571    case True{}:
1572      num.digit(st, cc, NEDig{})
1573    case False{}:
1574      num.bad(st, "invalid exponent")
1575
1576# a minus or a digit in an exponent
1577def num.expon.minus(minus: Bool, digit: Bool, st: Num, +cc: Char) -> Num:
1578  match minus:
1579    case True{}:
1580      num.exp.sign(st)
1581    case False{}:
1582      num.expon.digit(digit, st, cc)
1583
1584# the sign or the first digit of an exponent
1585def num.expon.go(plus: Bool, minus: Bool, digit: Bool, st: Num, +cc: Char) -> Num:
1586  match plus:
1587    case True{}:
1588      num.exp.sign(st)
1589    case False{}:
1590      num.expon.minus(minus, digit, st, cc)
1591
1592# the sign or the first digit of an exponent
1593def num.expon(st: Num, +cc: Char) -> Num:
1594  num.expon.go(Char.is_eq(cc, '+'), Char.is_eq(cc, '-'), Char.is_digit(cc), st, cc)
1595
1596# an underscore in an exponent
1597def num.edig.us(us: Bool, st: Num, _c: Char) -> Num:
1598  match us:
1599    case True{}:
1600      num.us(st)
1601    case False{}:
1602      num.bad(st, "invalid exponent")
1603
1604# an exponent digit or an underscore
1605def num.edig.go(digit: Bool, us: Bool, st: Num, +cc: Char) -> Num:
1606  match digit:
1607    case True{}:
1608      num.digit(st, cc, NEDig{})
1609    case False{}:
1610      num.edig.us(us, st, cc)
1611
1612# an exponent digit or an underscore
1613def num.edig(st: Num, +cc: Char) -> Num:
1614  num.edig.go(Char.is_digit(cc), Char.is_eq(cc, '_'), st, cc)
1615
1616# one character of a numeral
1617def num.on(nk: NK, st: Num, +cc: Char) -> Num:
1618  match nk:
1619    case NLead{}:
1620      num.lead(st, cc)
1621    case NSign{}:
1622      num.after.sign(st, cc)
1623    case NZero{}:
1624      num.zero(st, cc)
1625    case NDig{}:
1626      num.digs(st, cc)
1627    case NBase{}:
1628      num.based(st, cc)
1629    case NFrac{}:
1630      num.frac(st, cc)
1631    case NExp{}:
1632      num.expon(st, cc)
1633    case NEDig{}:
1634      num.edig(st, cc)
1635    case NBad{}:
1636      st
1637
1638# one character of a numeral
1639def num.step.go(st: Num, +cc: Char) -> Num:
1640  Num{+nk, sign, base, ds, us, saw, why} = st
1641  num.on(nk, Num{nk, sign, base, ds, us, saw, why}, cc)
1642
1643# one character of a numeral
1644def num.step(st: Num, +cc: Char) -> Num:
1645  num.step.go(st, cc)
1646
1647# the walk over a numeral
1648def num.go(cs: List<&2, Char>, st: Num) -> Num:
1649  match cs:
1650    case []:
1651      st
1652    case +h <> t:
1653      num.go(t, num.step(st, h))
1654
1655# the positive limit is 2^63-1; the negative limit is 2^63
1656def i64.limit(sign: Sign) -> String:
1657  match sign:
1658    case Plus{}:
1659      "9223372036854775807"
1660    case Minus{}:
1661      "9223372036854775808"
1662
1663# the first differing digit stands; a tie takes the new order
1664def i64.ord(seen: Cmp, now: Cmp) -> Cmp:
1665  match seen now:
1666    case EQ{} n:
1667      n
1668    case LT{} _n:
1669      LT{}
1670    case GT{} _n:
1671      GT{}
1672
1673# a shorter digit string fits and a longer one does not. equal length uses
1674# digit order. the walk does not rebuild either string
1675def i64.step(+digits: String, +limit: String, ord: Cmp) -> Bool:
1676  match digits limit:
1677    case SNil{} SNil{}:
1678      Cmp.is_le(ord)
1679    case SNil{} SCon{_h, _t}:
1680      True{}
1681    case SCon{_h, _t} SNil{}:
1682      False{}
1683    case SCon{x, xt} SCon{y, yt}:
1684      i64.step(xt, yt, i64.ord(ord, U32.cmp(Char.to_u32(x), Char.to_u32(y))))
1685
1686# whether the integer fits in a signed 64-bit integer
1687def i64.fits(+sign: Sign, +digits: String) -> Bool:
1688  i64.step(digits, i64.limit(sign), EQ{})
1689
1690# an integer inside the signed 64-bit range
1691def num.fin.range(ok: Bool, sign: Sign, digits: String) -> Got:
1692  match ok:
1693    case True{}:
1694      GotVal{VInt{sign, digits}}
1695    case False{}:
1696      GotBad{"out of range"}
1697
1698# a finished integer, rejected when it falls outside signed 64-bit
1699def num.fin.int(saw: Bool, +sign: Sign, +digits: String) -> Got:
1700  match saw:
1701    case False{}:
1702      GotBad{"invalid number"}
1703    case True{}:
1704      num.fin.range(i64.fits(sign, digits), sign, digits)
1705
1706# a numeral needs a digit in its last run
1707def num.fin.saw(saw: Bool, val: Val) -> Got:
1708  match saw:
1709    case False{}:
1710      GotBad{"invalid number"}
1711    case True{}:
1712      GotVal{val}
1713
1714# a prefixed integer may not carry a sign
1715def num.signed(cs: List<&2, Char>) -> Bool:
1716  match cs:
1717    case []:
1718      False{}
1719    case +c <> _:
1720      Bool.or(Char.is_eq(c, '+'), Char.is_eq(c, '-'))
1721
1722# a prefixed integer, rejected when a sign was written
1723def num.fin.base(signed: Bool, saw: Bool, sign: Sign, ds: List<&2, U32>) -> Got:
1724  match signed:
1725    case True{}:
1726      GotBad{"invalid number"}
1727    case False{}:
1728      num.fin.int(saw, sign, dec.text(ds))
1729
1730# the kind of numeral the scan finished in
1731def num.fin.nk(nk: NK, sign: Sign, ds: List<&2, U32>, saw: Bool, why: String, cs: List<&2, Char>) -> Got:
1732  match nk:
1733    case NBad{}:
1734      GotBad{why}
1735    case NLead{}:
1736      GotBad{"invalid number"}
1737    case NSign{}:
1738      GotBad{"invalid number"}
1739    case NZero{}:
1740      GotVal{VInt{sign, "0"}}
1741    case NDig{}:
1742      num.fin.int(saw, sign, dec.text(ds))
1743    case NBase{}:
1744      num.fin.base(num.signed(cs), saw, sign, ds)
1745    case NFrac{}:
1746      num.fin.saw(saw, VFlo{sign, word.body(cs)})
1747    case NExp{}:
1748      GotBad{"invalid exponent"}
1749    case NEDig{}:
1750      num.fin.saw(saw, VFlo{sign, word.body(cs)})
1751
1752# a trailing underscore is rejected
1753def num.fin.us(us: Bool, nk: NK, sign: Sign, ds: List<&2, U32>, saw: Bool, why: String, cs: List<&2, Char>) -> Got:
1754  match us:
1755    case True{}:
1756      GotBad{"invalid underscore"}
1757    case False{}:
1758      num.fin.nk(nk, sign, ds, saw, why, cs)
1759
1760# the value of a finished numeral
1761def num.fin(st: Num, cs: List<&2, Char>) -> Got:
1762  Num{nk, sign, _base, ds, us, saw, why} = st
1763  num.fin.us(us, nk, sign, ds, saw, why, cs)
1764
1765# a numeral token
1766def word.num(+cs: List<&2, Char>) -> Got:
1767  num.fin(num.go(cs, num.start()), cs)
1768
1769# a datetime when the token has a colon or a dash, otherwise a numeral
1770def word.route.date(date: Bool, cs: List<&2, Char>) -> Got:
1771  match date:
1772    case True{}:
1773      word.from.when(when.read(cs))
1774    case False{}:
1775      word.num(cs)
1776
1777# a special word, or a numeral
1778def word.route(gg: Got, date: Bool, +cs: List<&2, Char>) -> Got:
1779  match gg:
1780    case GotVal{val}:
1781      GotVal{val}
1782    case GotBad{_}:
1783      word.route.date(word.as_date(date, word.ee(cs)), cs)
1784
1785# '-' can start a signed inf or nan
1786def word.special.minus(+cc: Char) -> Bool:
1787  Char.is_eq(cc, '-')
1788
1789# '+' or '-'
1790def word.special.plus(hit: Bool, +cc: Char) -> Bool:
1791  match hit:
1792    case True{}:
1793      True{}
1794    case False{}:
1795      word.special.minus(cc)
1796
1797# 'n', or a sign
1798def word.special.n(hit: Bool, +cc: Char) -> Bool:
1799  match hit:
1800    case True{}:
1801      True{}
1802    case False{}:
1803      word.special.plus(Char.is_eq(cc, '+'), cc)
1804
1805# 'i', or nan, or a sign
1806def word.special.i(hit: Bool, +cc: Char) -> Bool:
1807  match hit:
1808    case True{}:
1809      True{}
1810    case False{}:
1811      word.special.n(Char.is_eq(cc, 'n'), cc)
1812
1813# 'f', or inf, or nan, or a sign
1814def word.special.f(hit: Bool, +cc: Char) -> Bool:
1815  match hit:
1816    case True{}:
1817      True{}
1818    case False{}:
1819      word.special.i(Char.is_eq(cc, 'i'), cc)
1820
1821# 't', or false, or inf, or nan, or a sign
1822def word.special.t(hit: Bool, +cc: Char) -> Bool:
1823  match hit:
1824    case True{}:
1825      True{}
1826    case False{}:
1827      word.special.f(Char.is_eq(cc, 'f'), cc)
1828
1829# whether this character can start a special bare word
1830def word.special(+cc: Char) -> Bool:
1831  word.special.t(Char.is_eq(cc, 't'), cc)
1832
1833# the first character, when the word has one
1834def word.lead(cs: List<&2, Char>) -> Bool:
1835  match cs:
1836    case []:
1837      False{}
1838    case +h <> _t:
1839      word.special(h)
1840
1841# a numeral or a datetime. the token cannot be a keyword
1842def word.val.num(+cs: List<&2, Char>) -> Got:
1843  word.route(GotBad{""}, word.or(word.colon(cs), word.dash(cs, True{})), cs)
1844
1845# a special word, or a numeral. a digit does not build a string to compare
1846def word.val.pick(special: Bool, +cs: List<&2, Char>) -> Got:
1847  match special:
1848    case True{}:
1849      word.route(word.spec(String.from_list(cs)),
1850        word.or(word.colon(cs), word.dash(cs, True{})), cs)
1851    case False{}:
1852      word.val.num(cs)
1853
1854# a bare word: a special, a datetime, or a numeral
1855def word.val(+cs: List<&2, Char>) -> Got:
1856  word.val.pick(word.lead(cs), cs)
1857
1858# a change applied at the end of a key path
1859type Job is Data:
1860  JPut{val: Val}
1861  JDef{}
1862  JAot{}
1863
1864# one table the walk has entered
1865type Fr is Data:
1866  FrNew{name: String, parent: List<&2, Val>, path: String}
1867  FrRep{name: String, parent: List<&2, Val>, path: String}
1868  FrAot{name: String, parent: List<&2, Val>, pre: List<&2, Val>, path: String}
1869
1870# the walk: the table being edited, and the frames above it
1871type Tw is Data:
1872  Tw{vs: List<&2, Val>, job: Job, stack: List<&2, Fr>, bad: String, pre: String,
1873    heads: List<&2, String>, inl: Bool}
1874
1875# the root table after a walk
1876type End is Data:
1877  End{vs: List<&2, Val>, bad: String, heads: List<&2, String>}
1878
1879# what a key in a table refers to
1880type Look is Data:
1881  KMiss{}
1882  KHead{path: String, rows: List<&2, Val>}
1883  KInl{}
1884  KAots{path: String, elems: List<&2, Val>}
1885  KNo{why: String}
1886
1887# the last element of an array of tables
1888type Last is Data:
1889  LastSome{path: String, pre: List<&2, Val>, rows: List<&2, Val>}
1890  LastNo{why: String}
1891
1892# the first error is kept
1893def err.first.go(empty: Bool, bad: String, why: String) -> String:
1894  match empty:
1895    case True{}:
1896      why
1897    case False{}:
1898      bad
1899
1900# the first error is kept
1901def err.first(+bad: String, why: String) -> String:
1902  err.first.go(String.is_empty(bad), bad, why)
1903
1904# a key found earlier wins
1905def find.step(eq: Bool, vv: Val, more: Unit -> Hit) -> Hit:
1906  match eq:
1907    case True{}:
1908      Found{vv}
1909    case False{}:
1910      more(Unit{})
1911
1912# the first pair of this name
1913def rows.find(rows: List<&2, Val>, +name: String) -> Hit:
1914  match rows:
1915    case []:
1916      Miss{}
1917    case VPair{n, v} <> t:
1918      find.step(str.eq(n, name), v, _u => rows.find(t, name))
1919    case _ <> t:
1920      rows.find(t, name)
1921
1922# the value written for a key, or Miss
1923def get(rows: List<&2, Val>, +name: String) -> Hit:
1924  rows.find(rows, name)
1925
1926# replace the value of one pair
1927def rows.repl.step(eq: Bool, nn: String, old: Val, val: Val, more: Unit -> List<&2, Val>) -> List<&2, Val>:
1928  match eq:
1929    case True{}:
1930      VPair{nn, val} <> more(Unit{})
1931    case False{}:
1932      VPair{nn, old} <> more(Unit{})
1933
1934# replace every pair of this name
1935def rows.repl(rows: List<&2, Val>, +name: String, +val: Val) -> List<&2, Val>:
1936  match rows:
1937    case []:
1938      []
1939    case VPair{+n, old} <> t:
1940      rows.repl.step(str.eq(n, name), n, old, val, _u => rows.repl(t, name, val))
1941    case h <> t:
1942      h <> rows.repl(t, name, val)
1943
1944# either flag
1945def heads.or(aa: Bool, bb: Bool) -> Bool:
1946  match aa:
1947    case True{}:
1948      True{}
1949    case False{}:
1950      bb
1951
1952# heads records each table the document has defined as its path behind one
1953# mark: `[` when a header defined it, `.` when dotted keys did
1954def heads.by.head(path: String) -> String:
1955  SCon{'[', path}
1956
1957# a table dotted keys defined, as heads records it
1958def heads.by.dot(path: String) -> String:
1959  SCon{'.', path}
1960
1961# whether a table path was defined, by a header or by dotted keys
1962def heads.has(hs: List<&2, String>, +pp: String) -> Bool:
1963  match hs:
1964    case []:
1965      False{}
1966    case SCon{_mark, h} <> t:
1967      heads.or(str.eq(h, pp), heads.has(t, pp))
1968    case SNil{} <> t:
1969      heads.has(t, pp)
1970
1971# whether a table path was defined by a header
1972def heads.head(hs: List<&2, String>, +pp: String) -> Bool:
1973  match hs:
1974    case []:
1975      False{}
1976    case SCon{mark, h} <> t:
1977      heads.or(Bool.and(Char.is_eq(mark, '['), str.eq(h, pp)), heads.head(t, pp))
1978    case SNil{} <> t:
1979      heads.head(t, pp)
1980
1981# a recorded table under the prefix is dropped
1982def heads.drop.step(
1983  under: Bool,
1984  mark: Char,
1985  path: String,
1986  rest: List<&2, String>
1987) -> List<&2, String>:
1988  match under:
1989    case True{}:
1990      rest
1991    case False{}:
1992      SCon{mark, path} <> rest
1993
1994# forget every table whose path starts with the prefix. A new element of an
1995# array of tables starts with none of the tables the element before defined
1996def heads.drop(hs: List<&2, String>, +pre: String) -> List<&2, String>:
1997  match hs:
1998    case []:
1999      []
2000    case SCon{mark, +path} <> t:
2001      heads.drop.step(String.starts_with(path, pre), mark, path, heads.drop(t, pre))
2002    case SNil{} <> t:
2003      heads.drop(t, pre)
2004
2005# a table defined by a dotted key is recorded once
2006def heads.define.new(seen: Bool, path: String, heads: List<&2, String>) -> List<&2, String>:
2007  match seen:
2008    case True{}:
2009      heads
2010    case False{}:
2011      heads.by.dot(path) <> heads
2012
2013# a dotted key defines each table before its last segment
2014def heads.define(job: Job, +path: String, +heads: List<&2, String>) -> List<&2, String>:
2015  match job:
2016    case JPut{_val}:
2017      heads.define.new(heads.has(heads, path), path, heads)
2018    case _:
2019      heads
2020
2021# a dotted path from a prefix and one segment
2022def tree.pre.go(empty: Bool, pre: String, name: String) -> String:
2023  match empty:
2024    case True{}:
2025      key(name)
2026    case False{}:
2027      pre ++ "." ++ key(name)
2028
2029# a dotted path from a prefix and one segment
2030def tree.pre(+pre: String, name: String) -> String:
2031  tree.pre.go(String.is_empty(pre), pre, name)
2032
2033# the value a key already holds
2034def look.of(vv: Val) -> Look:
2035  match vv:
2036    case VHead{path, rows}:
2037      KHead{path, rows}
2038    case VInl{_rows}:
2039      KInl{}
2040    case VAots{path, elems}:
2041      KAots{path, elems}
2042    case _:
2043      KNo{"not a table"}
2044
2045# the matching pair, or the rest of the table
2046def look.step(eq: Bool, vv: Val, more: Unit -> Look) -> Look:
2047  match eq:
2048    case True{}:
2049      look.of(vv)
2050    case False{}:
2051      more(Unit{})
2052
2053# what a key in this table refers to
2054def rows.look(rows: List<&2, Val>, +name: String) -> Look:
2055  match rows:
2056    case []:
2057      KMiss{}
2058    case VPair{n, v} <> t:
2059      look.step(str.eq(n, name), v, _u => rows.look(t, name))
2060    case _ <> t:
2061      rows.look(t, name)
2062
2063# the newest element. rows stay newest-first until the document is sealed
2064def look.last.one(vv: Val, path: String, older: List<&2, Val>) -> Last:
2065  match vv:
2066    case VAot{_p, rows}:
2067      LastSome{path, older, rows}
2068    case _:
2069      LastNo{"not a table"}
2070
2071# the newest element of an array of tables, and the older elements behind it
2072def look.last(elems: List<&2, Val>, path: String) -> Last:
2073  match elems:
2074    case []:
2075      LastNo{"empty array of tables"}
2076    case h <> t:
2077      look.last.one(h, path, t)
2078
2079# put the child rows back into the parent table. A new implicit table is
2080# consed, not copied on. Inside an inline table it is a table too until the
2081# inline table closes, so the next dotted key can go on in it (inl.seal)
2082def tree.wrap(fr: Fr, child: List<&2, Val>) -> List<&2, Val>:
2083  match fr:
2084    case FrNew{name, parent, path}:
2085      VPair{name, VHead{path, child}} <> parent
2086    case FrRep{name, parent, path}:
2087      rows.repl(parent, name, VHead{path, child})
2088    case FrAot{name, parent, pre, +path}:
2089      rows.repl(parent, name, VAots{path, VAot{path, child} <> pre})
2090
2091# walk the frames back to the root
2092def tree.rebuild(stack: List<&2, Fr>, vs: List<&2, Val>, bad: String, heads: List<&2, String>) -> End:
2093  match stack:
2094    case []:
2095      End{vs, bad, heads}
2096    case fr <> t:
2097      tree.rebuild(t, tree.wrap(fr, vs), bad, heads)
2098
2099# a key that is already present is a duplicate
2100def tree.leaf.put(
2101  hit: Hit,
2102  name: String,
2103  vs: List<&2, Val>,
2104  val: Val,
2105  stack: List<&2, Fr>,
2106  bad: String,
2107  heads: List<&2, String>
2108) -> End:
2109  match hit:
2110    case Found{_v}:
2111      tree.rebuild(stack, vs, err.first(bad, "duplicate key"), heads)
2112    case Miss{}:
2113      tree.rebuild(stack, VPair{name, val} <> vs, bad, heads)
2114
2115# an explicit header for a table that already exists
2116def tree.leaf.def.head(
2117  seen: Bool,
2118  name: String,
2119  vs: List<&2, Val>,
2120  rows: List<&2, Val>,
2121  stack: List<&2, Fr>,
2122  bad: String,
2123  +path: String,
2124  heads: List<&2, String>
2125) -> End:
2126  match seen:
2127    case True{}:
2128      tree.rebuild(stack, vs, err.first(bad, "duplicate table"), heads)
2129    case False{}:
2130      tree.rebuild(stack, rows.repl(vs, name, VHead{path, rows}), bad,
2131        heads.by.head(path) <> heads)
2132
2133# open a standard table
2134def tree.leaf.def.hit(
2135  hit: Look,
2136  name: String,
2137  vs: List<&2, Val>,
2138  stack: List<&2, Fr>,
2139  bad: String,
2140  +path: String,
2141  +heads: List<&2, String>
2142) -> End:
2143  match hit:
2144    case KMiss{}:
2145      tree.rebuild(stack, VPair{name, VHead{path, []}} <> vs, bad,
2146        heads.by.head(path) <> heads)
2147    case KHead{_p, rows}:
2148      tree.leaf.def.head(heads.has(heads, path), name, vs, rows, stack, bad, path,
2149        heads)
2150    case KInl{}:
2151      tree.rebuild(stack, vs, err.first(bad, "inline table is closed"), heads)
2152    case KAots{_p, _elems}:
2153      tree.rebuild(stack, vs, err.first(bad, "not a table"), heads)
2154    case KNo{why}:
2155      tree.rebuild(stack, vs, err.first(bad, why), heads)
2156
2157# open a standard table
2158def tree.leaf.def(
2159  hit: Look,
2160  +name: String,
2161  vs: List<&2, Val>,
2162  stack: List<&2, Fr>,
2163  bad: String,
2164  pre: String,
2165  heads: List<&2, String>
2166) -> End:
2167  tree.leaf.def.hit(hit, name, vs, stack, bad, tree.pre(pre, name), heads)
2168
2169# append one element of an array of tables
2170def tree.leaf.aot.hit(
2171  hit: Look,
2172  name: String,
2173  vs: List<&2, Val>,
2174  stack: List<&2, Fr>,
2175  bad: String,
2176  +path: String,
2177  heads: List<&2, String>
2178) -> End:
2179  match hit:
2180    case KMiss{}:
2181      tree.rebuild(stack, VPair{name, VAots{path, [VAot{path, []}]}} <> vs, bad, heads)
2182    case KAots{_p, elems}:
2183      tree.rebuild(stack, rows.repl(vs, name, VAots{path, VAot{path, []} <> elems}),
2184        bad, heads.drop(heads, path ++ "."))
2185    case KHead{_p, _rows}:
2186      tree.rebuild(stack, vs, err.first(bad, "not a table"), heads)
2187    case KInl{}:
2188      tree.rebuild(stack, vs, err.first(bad, "inline table is closed"), heads)
2189    case KNo{why}:
2190      tree.rebuild(stack, vs, err.first(bad, why), heads)
2191
2192# append one element of an array of tables
2193def tree.leaf.aot(
2194  hit: Look,
2195  +name: String,
2196  vs: List<&2, Val>,
2197  stack: List<&2, Fr>,
2198  bad: String,
2199  pre: String,
2200  heads: List<&2, String>
2201) -> End:
2202  tree.leaf.aot.hit(hit, name, vs, stack, bad, tree.pre(pre, name), heads)
2203
2204# the key at the end of the path
2205def tree.leaf.job(
2206  job: Job,
2207  +name: String,
2208  +vs: List<&2, Val>,
2209  stack: List<&2, Fr>,
2210  bad: String,
2211  pre: String,
2212  heads: List<&2, String>
2213) -> End:
2214  match job:
2215    case JPut{val}:
2216      tree.leaf.put(get(vs, name), name, vs, val, stack, bad, heads)
2217    case JDef{}:
2218      tree.leaf.def(rows.look(vs, name), name, vs, stack, bad, pre, heads)
2219    case JAot{}:
2220      tree.leaf.aot(rows.look(vs, name), name, vs, stack, bad, pre, heads)
2221
2222# a failed walk rebuilds what it has
2223def tree.leaf.bad(
2224  ok: Bool,
2225  name: String,
2226  vs: List<&2, Val>,
2227  job: Job,
2228  stack: List<&2, Fr>,
2229  bad: String,
2230  pre: String,
2231  heads: List<&2, String>
2232) -> End:
2233  match ok:
2234    case False{}:
2235      tree.rebuild(stack, vs, bad, heads)
2236    case True{}:
2237      tree.leaf.job(job, name, vs, stack, bad, pre, heads)
2238
2239# the key at the end of the path
2240def tree.leaf(name: String, tw: Tw) -> End:
2241  Tw{vs, job, stack, +bad, pre, heads, _inl} = tw
2242  tree.leaf.bad(String.is_empty(bad), name, vs, job, stack, bad, pre, heads)
2243
2244# enter the last element of an array of tables
2245def tree.sub.aots(
2246  last: Last,
2247  +name: String,
2248  +vs: List<&2, Val>,
2249  job: Job,
2250  stack: List<&2, Fr>,
2251  +pre: String,
2252  heads: List<&2, String>,
2253  inl: Bool,
2254  path: String
2255) -> Tw:
2256  match last:
2257    case LastSome{_p, prefix, rows}:
2258      Tw{rows, job, FrAot{name, vs, prefix, path} <> stack, "", tree.pre(pre, name),
2259        heads, inl}
2260    case LastNo{why}:
2261      Tw{vs, job, stack, why, pre, heads, inl}
2262
2263# a prefix of the current table is navigation; a dotted segment defines a table
2264def tree.sub.job(mark: Bool, job: Job) -> Job:
2265  match mark:
2266    case True{}:
2267      job
2268    case False{}:
2269      JDef{}
2270
2271# a segment of a dotted key, which defines tables as it goes
2272def tree.sub.dot(job: Job) -> Bool:
2273  match job:
2274    case JPut{_val}:
2275      True{}
2276    case _:
2277      False{}
2278
2279# enter a table, unless a dotted key would add to a table a header defined
2280def tree.sub.head.go(
2281  shut: Bool,
2282  path: String,
2283  rows: List<&2, Val>,
2284  +name: String,
2285  vs: List<&2, Val>,
2286  +job: Job,
2287  stack: List<&2, Fr>,
2288  +pre: String,
2289  heads: List<&2, String>,
2290  inl: Bool,
2291  mark: Bool
2292) -> Tw:
2293  match shut:
2294    case True{}:
2295      Tw{vs, job, stack, "duplicate table", pre, heads, inl}
2296    case False{}:
2297      Tw{rows, job, FrRep{name, vs, path} <> stack, "", tree.pre(pre, name),
2298        heads.define(tree.sub.job(mark, job), tree.pre(pre, name), heads), inl}
2299
2300# a dotted key asks whether a header defined the table; a header walk does not
2301def tree.sub.head(
2302  dot: Bool,
2303  path: String,
2304  rows: List<&2, Val>,
2305  +name: String,
2306  vs: List<&2, Val>,
2307  job: Job,
2308  stack: List<&2, Fr>,
2309  +pre: String,
2310  +heads: List<&2, String>,
2311  inl: Bool,
2312  mark: Bool
2313) -> Tw:
2314  match dot:
2315    case True{}:
2316      tree.sub.head.go(heads.head(heads, tree.pre(pre, name)), path, rows, name, vs, job,
2317        stack, pre, heads, inl, mark)
2318    case False{}:
2319      tree.sub.head.go(False{}, path, rows, name, vs, job, stack, pre, heads, inl, mark)
2320
2321# a header enters the last element of an array of tables; a dotted key cannot
2322# add to an array of tables
2323def tree.sub.arr(
2324  dot: Bool,
2325  +path: String,
2326  elems: List<&2, Val>,
2327  +name: String,
2328  +vs: List<&2, Val>,
2329  job: Job,
2330  stack: List<&2, Fr>,
2331  +pre: String,
2332  heads: List<&2, String>,
2333  inl: Bool
2334) -> Tw:
2335  match dot:
2336    case True{}:
2337      Tw{vs, job, stack, "duplicate table", pre, heads, inl}
2338    case False{}:
2339      tree.sub.aots(look.last(elems, path), name, vs, job, stack, pre, heads, inl, path)
2340
2341# descend into one key, or record why it cannot be entered
2342def tree.sub.look(
2343  hit: Look,
2344  +name: String,
2345  +vs: List<&2, Val>,
2346  +job: Job,
2347  stack: List<&2, Fr>,
2348  +pre: String,
2349  heads: List<&2, String>,
2350  +inl: Bool,
2351  +mark: Bool
2352) -> Tw:
2353  match hit:
2354    case KMiss{}:
2355      Tw{[], job, FrNew{name, vs, tree.pre(pre, name)} <> stack, "",
2356        tree.pre(pre, name), heads.define(tree.sub.job(mark, job), tree.pre(pre, name),
2357          heads), inl}
2358    case KHead{path, rows}:
2359      tree.sub.head(tree.sub.dot(tree.sub.job(mark, job)), path, rows, name, vs, job,
2360        stack, pre, heads, inl, mark)
2361    case KInl{}:
2362      Tw{vs, job, stack, "inline table is closed", pre, heads, inl}
2363    case KAots{path, elems}:
2364      tree.sub.arr(tree.sub.dot(tree.sub.job(mark, job)), path, elems, name, vs, job,
2365        stack, pre, heads, inl)
2366    case KNo{why}:
2367      Tw{vs, job, stack, why, pre, heads, inl}
2368
2369# descend into one key when the walk is still well
2370def tree.sub.bad(
2371  ok: Bool,
2372  +name: String,
2373  +vs: List<&2, Val>,
2374  job: Job,
2375  stack: List<&2, Fr>,
2376  bad: String,
2377  pre: String,
2378  heads: List<&2, String>,
2379  inl: Bool,
2380  mark: Bool
2381) -> Tw:
2382  match ok:
2383    case False{}:
2384      Tw{vs, job, stack, bad, pre, heads, inl}
2385    case True{}:
2386      tree.sub.look(rows.look(vs, name), name, vs, job, stack, pre, heads, inl, mark)
2387
2388# descend into one key
2389def tree.sub(name: String, tw: Tw, mark: Bool) -> Tw:
2390  Tw{+vs, job, stack, +bad, pre, heads, inl} = tw
2391  tree.sub.bad(String.is_empty(bad), name, vs, job, stack, bad, pre, heads, inl, mark)
2392
2393# how many segments are already the current table
2394def path.len(xs: List<&2, String>, nn: U32) -> U32:
2395  match xs:
2396    case []:
2397      nn
2398    case _ <> t:
2399      path.len(t, U32.add(nn, 1))
2400
2401# one fewer prefix segment, staying at zero
2402def path.dec.z(zz: Bool, skip: U32) -> U32:
2403  match zz:
2404    case True{}:
2405      0
2406    case False{}:
2407      U32.sub(skip, 1)
2408
2409# one fewer prefix segment, staying at zero
2410def path.dec(+skip: U32) -> U32:
2411  path.dec.z(U32.is_zero(skip), skip)
2412
2413# an empty path records an error
2414def tree.walk.none(tw: Tw) -> End:
2415  Tw{vs, _job, stack, bad, _pre, heads, _inl} = tw
2416  tree.rebuild(stack, vs, err.first(bad, "empty key"), heads)
2417
2418# follow a key path and apply the job at its end. skip is the current table
2419def tree.walk(path: List<&2, String>, tw: Tw, +skip: U32) -> End:
2420  match path:
2421    case []:
2422      tree.walk.none(tw)
2423    case name <> rest:
2424      match rest:
2425        case []:
2426          tree.leaf(name, tw)
2427        case _ <> _:
2428          tree.walk(rest, tree.sub(name, tw, U32.is_zero(skip)), path.dec(skip))
2429
2430# space or tab
2431def ch.sp(+cc: Char) -> Bool:
2432  Bool.or(U32.is_eq(Char.to_u32(cc), 32), U32.is_eq(Char.to_u32(cc), 9))
2433
2434# the space character itself, U+0020, the only one a datetime may hold
2435def ch.space(+cc: Char) -> Bool:
2436  U32.is_eq(Char.to_u32(cc), 32)
2437
2438# line feed or carriage return
2439def ch.nl(+cc: Char) -> Bool:
2440  Bool.or(U32.is_eq(Char.to_u32(cc), 10), U32.is_eq(Char.to_u32(cc), 13))
2441
2442# a control: U+0000 to U+001F, or U+007F
2443def ch.ctl(+cc: Char) -> Bool:
2444  Bool.or(U32.is_lt(Char.to_u32(cc), 32), U32.is_eq(Char.to_u32(cc), 127))
2445
2446# a character that ends a bare value
2447def ch.delim(+cc: Char) -> Bool:
2448  Bool.or(ch.sp(cc), Bool.or(ch.nl(cc), Bool.or(Char.is_eq(cc, '#'),
2449    Bool.or(Char.is_eq(cc, ','), Bool.or(Char.is_eq(cc, ']'), Char.is_eq(cc, '}'))))))
2450
2451# whitespace that a bare value swallows
2452def ch.skip(+cc: Char) -> Bool:
2453  Bool.or(ch.sp(cc), ch.nl(cc))
2454
2455# the quote character of a string kind
2456def q.char(kk: QKind) -> Char:
2457  match kk:
2458    case QLit{}:
2459      '\''
2460    case QMultiLit{}:
2461      '\''
2462    case _:
2463      '"'
2464
2465# a multiline string
2466def q.multi(kk: QKind) -> Bool:
2467  match kk:
2468    case QMulti{}:
2469      True{}
2470    case QMultiLit{}:
2471      True{}
2472    case _:
2473      False{}
2474
2475# a basic string, which has escapes
2476def q.basic(kk: QKind) -> Bool:
2477  match kk:
2478    case QBasic{}:
2479      True{}
2480    case QMulti{}:
2481      True{}
2482    case _:
2483      False{}
2484
2485# the multiline kind of a one-line kind
2486def q.as.multi(kk: QKind) -> QKind:
2487  match kk:
2488    case QLit{}:
2489      QMultiLit{}
2490    case _:
2491      QMulti{}
2492
2493# a pattern character that stands for a digit
2494def date.mode(pp: Char) -> Bool:
2495  U32.is_eq(Char.to_u32(pp), 68)
2496
2497# whether the rest of the shape matches
2498def date.eq.go(ok: Bool, more: Unit -> Bool) -> Bool:
2499  match ok:
2500    case False{}:
2501      False{}
2502    case True{}:
2503      more(Unit{})
2504
2505# one character of a date shape
2506def date.step(mode: Bool, digit: Bool, same: Bool, more: Unit -> Bool) -> Bool:
2507  match mode:
2508    case True{}:
2509      date.eq.go(digit, more)
2510    case False{}:
2511      date.eq.go(same, more)
2512
2513# both lists ended together
2514def date.eq.nil(pat: List<&2, Char>) -> Bool:
2515  match pat:
2516    case []:
2517      True{}
2518    case _ <> _:
2519      False{}
2520
2521# YYYY-MM-DD, with D standing for a digit
2522def date.eq(cs: List<&2, Char>, pat: List<&2, Char>) -> Bool:
2523  match cs:
2524    case []:
2525      date.eq.nil(pat)
2526    case +h <> t:
2527      match pat:
2528        case []:
2529          False{}
2530        case +p <> r:
2531          date.step(date.mode(p), Char.is_digit(h), Char.is_eq(h, p),
2532            _u => date.eq(t, r))
2533
2534# the buffer is a calendar date
2535def date.shaped(buf: List<&2, Char>) -> Bool:
2536  date.eq(List.reverse(&2, Char, buf), String.to_list("DDDD-DD-DD"))
2537
2538# the buffer is reversed, so each head goes on the front of the text
2539def buf.str.go(buf: List<&2, Char>, acc: String) -> String:
2540  match buf:
2541    case []:
2542      acc
2543    case +h <> t:
2544      buf.str.go(t, SCon{h, acc})
2545
2546# the buffer, in order
2547def buf.str(buf: List<&2, Char>) -> String:
2548  buf.str.go(buf, "")
2549
2550# the first n characters of a source span
2551def span.str(src: String, +nn: U32, zz: Bool) -> String:
2552  match src zz:
2553    case _s True{}:
2554      ""
2555    case SNil{} False{}:
2556      ""
2557    case SCon{+h, t} False{}:
2558      SCon{h, span.str(t, U32.sub(nn, 1), U32.is_zero(U32.sub(nn, 1)))}
2559
2560# the first n characters, reversed, so an escape can keep copying
2561def span.rev(src: String, +nn: U32, zz: Bool, acc: List<&2, Char>) -> List<&2, Char>:
2562  match src zz:
2563    case _s True{}:
2564      acc
2565    case SNil{} False{}:
2566      acc
2567    case SCon{h, t} False{}:
2568      span.rev(t, U32.sub(nn, 1), U32.is_zero(U32.sub(nn, 1)), h <> acc)
2569
2570# the unread suffix of a span that will not be stored
2571def span.drop(src: String) -> Unit:
2572  match src:
2573    case SNil{}:
2574      Unit{}
2575    case SCon{_h, t}:
2576      span.drop(t)
2577
2578# the key path, document table then this key
2579def path.cat(here: List<&2, String>, parts: List<&2, String>) -> List<&2, String>:
2580  List.append(&2, String, here, List.reverse(&2, String, parts))
2581
2582# a multiline string keeps a newline
2583def str.bad.nl(nl: Bool, multi: Bool) -> Bool:
2584  match nl:
2585    case False{}:
2586      True{}
2587    case True{}:
2588      Bool.not(multi)
2589
2590# tab is kept
2591def str.bad.tab(tab: Bool, nl: Bool, multi: Bool) -> Bool:
2592  match tab:
2593    case True{}:
2594      False{}
2595    case False{}:
2596      str.bad.nl(nl, multi)
2597
2598# a raw control is rejected
2599def str.bad.go(ctrl: Bool, tab: Bool, nl: Bool, multi: Bool) -> Bool:
2600  match ctrl:
2601    case False{}:
2602      False{}
2603    case True{}:
2604      str.bad.tab(tab, nl, multi)
2605
2606# a raw control is rejected. Tab is kept. A multiline string also keeps newlines
2607def str.bad(+uu: U32, multi: Bool) -> Bool:
2608  str.bad.go(Bool.or(U32.is_lt(uu, 32), U32.is_eq(uu, 127)), U32.is_eq(uu, 9),
2609    Bool.or(U32.is_eq(uu, 10), U32.is_eq(uu, 13)), multi)
2610
2611# the scanner has failed
2612def st.err(st: St, why: String) -> St:
2613  St{+bad, rows, heads, here, parts, buf, _lex, back, qkind, stack, _hold, uni, left,
2614    arrtab, lead} = st
2615  St{err.first(bad, why), rows, heads, here, parts, buf, LSink{}, back, qkind, stack,
2616    HNone{}, uni, left, arrtab, lead}
2617
2618# replace the lex state
2619def st.lex(st: St, lex: Lex) -> St:
2620  St{bad, rows, heads, here, parts, buf, _lex, back, qkind, stack, hold, uni, left,
2621    arrtab, lead} = st
2622  St{bad, rows, heads, here, parts, buf, lex, back, qkind, stack, hold, uni, left,
2623    arrtab, lead}
2624
2625# hold one character and set the lex state
2626def st.hold(st: St, +lex: Lex, cc: Char) -> St:
2627  St{bad, rows, heads, here, parts, buf, _lex, _back, qkind, stack, _hold, uni, left,
2628    arrtab, lead} = st
2629  St{bad, rows, heads, here, parts, buf, lex, lex, qkind, stack, HChar{cc}, uni, left,
2630    arrtab, lead}
2631
2632# append one character
2633def st.push(st: St, cc: Char, lex: Lex) -> St:
2634  St{bad, rows, heads, here, parts, buf, _lex, back, qkind, stack, hold, uni, left,
2635    arrtab, lead} = st
2636  St{bad, rows, heads, here, parts, cc <> buf, lex, back, qkind, stack, hold, uni, left,
2637    arrtab, lead}
2638
2639# the segment in the buffer joins the key, and the buffer is cleared
2640def seg.push(st: St, lex: Lex) -> St:
2641  St{bad, rows, heads, here, parts, buf, _lex, back, qkind, stack, hold, uni, left,
2642    arrtab, lead} = st
2643  St{bad, rows, heads, here, buf.str(buf) <> parts, [], lex, back, qkind, stack, hold,
2644    uni, left, arrtab, lead}
2645
2646# a finished value at the document root
2647def take.root(
2648  end: End,
2649  here: List<&2, String>,
2650  back: Lex,
2651  qkind: QKind,
2652  uni: U32,
2653  left: U32,
2654  arrtab: Bool,
2655  lead: Bool
2656) -> St:
2657  End{rows, bad, heads} = end
2658  St{bad, rows, heads, here, [], [], LLine{}, back, qkind, [], HNone{}, uni, left, arrtab,
2659    lead}
2660
2661# a finished value inside an inline table
2662def take.inl(
2663  end: End,
2664  rows: List<&2, Val>,
2665  heads: List<&2, String>,
2666  +here: List<&2, String>,
2667  +parts: List<&2, String>,
2668  rest: List<&2, Ctx>,
2669  back: Lex,
2670  qkind: QKind,
2671  uni: U32,
2672  left: U32,
2673  arrtab: Bool,
2674  lead: Bool
2675) -> St:
2676  End{inl, bad, _h} = end
2677  St{bad, rows, heads, here, [], [], LInlC{}, back, qkind,
2678    CInl{inl, parts, here} <> rest, HNone{}, uni, left, arrtab, lead}
2679
2680# place a finished value into the current table, array, or inline table
2681def take.top(
2682  stack: List<&2, Ctx>,
2683  val: Val,
2684  bad: String,
2685  rows: List<&2, Val>,
2686  heads: List<&2, String>,
2687  +here: List<&2, String>,
2688  parts: List<&2, String>,
2689  back: Lex,
2690  qkind: QKind,
2691  uni: U32,
2692  left: U32,
2693  arrtab: Bool,
2694  lead: Bool
2695) -> St:
2696  match stack:
2697    case []:
2698      take.root(tree.walk(path.cat(here, parts),
2699        Tw{rows, JPut{val}, [], bad, "", heads, False{}}, path.len(here, 0)), here,
2700        back, qkind, uni, left, arrtab, lead)
2701    case CArr{items, +saved, +at} <> rest:
2702      St{bad, rows, heads, at, saved, [], LArrC{}, back, qkind,
2703        CArr{val <> items, saved, at} <> rest, HNone{}, uni, left, arrtab, lead}
2704    case CInl{inl, +saved, +at} <> rest:
2705      take.inl(tree.walk(List.reverse(&2, String, parts),
2706        Tw{inl, JPut{val}, [], bad, "", [], True{}}, 0), rows, heads, at, saved, rest,
2707        back,
2708        qkind, uni, left, arrtab, lead)
2709
2710# place a finished value
2711def val.take(st: St, val: Val) -> St:
2712  St{bad, rows, heads, here, parts, _buf, _lex, back, qkind, stack, _hold, uni, left,
2713    arrtab, lead} = st
2714  take.top(stack, val, bad, rows, heads, here, parts, back, qkind, uni, left, arrtab,
2715    lead)
2716
2717# hold the delimiter when the following state must see it
2718def bare.hold.on(st: St, cc: Char) -> St:
2719  St{bad, rows, heads, here, parts, buf, lex, back, qkind, stack, _h, uni, left,
2720    arrtab, lead} = st
2721  St{bad, rows, heads, here, parts, buf, lex, back, qkind, stack, HChar{cc}, uni, left,
2722    arrtab, lead}
2723
2724# hold the delimiter when the following state must see it
2725# a newline that ended a root value already separates the next expression
2726def bare.line.lex(lex: Lex, st: St) -> St:
2727  match lex:
2728    case LLine{}:
2729      st.lex(st, LTop{})
2730    case _:
2731      st
2732
2733# a consumed newline returns to the top level after a root value
2734def bare.line.go(st: St) -> St:
2735  St{bad, rows, heads, here, parts, buf, +lex, back, qkind, stack, hold, uni, left,
2736    arrtab, lead} = st
2737  bare.line.lex(lex, St{bad, rows, heads, here, parts, buf, lex, back, qkind, stack,
2738    hold, uni, left, arrtab, lead})
2739
2740# a consumed newline returns to the top level after a root value
2741def bare.line(st: St, nl: Bool) -> St:
2742  match nl:
2743    case True{}:
2744      bare.line.go(st)
2745    case False{}:
2746      st
2747
2748# hold the delimiter when the following state must see it
2749def bare.hold(st: St, cc: Char, hold: Bool) -> St:
2750  match hold:
2751    case False{}:
2752      bare.line(st, ch.nl(cc))
2753    case True{}:
2754      bare.hold.on(st, cc)
2755
2756# a bare word has been classified
2757def bare.got(gg: Got, st: St, cc: Char, hold: Bool) -> St:
2758  match gg:
2759    case GotBad{why}:
2760      st.err(st, why)
2761    case GotVal{val}:
2762      bare.hold(val.take(st, val), cc, hold)
2763
2764# finish a bare word. hold is true when the delimiter is not whitespace
2765def bare.finish(st: St, cc: Char, hold: Bool) -> St:
2766  St{bad, rows, heads, here, parts, buf, lex, back, qkind, stack, h, uni, left, arrtab,
2767    lead} = st
2768  bare.got(word.val(List.reverse(&2, Char, buf)),
2769    St{bad, rows, heads, here, parts, [], lex, back, qkind, stack, h, uni, left, arrtab,
2770      lead}, cc, hold)
2771
2772# close the header that was just read
2773def head.apply(end: End, segs: List<&2, String>, back: Lex, qkind: QKind, uni: U32, left: U32, lead: Bool) -> St:
2774  End{rows, bad, heads} = end
2775  St{bad, rows, heads, segs, [], [], LLine{}, back, qkind, [], HNone{}, uni, left,
2776    False{}, lead}
2777
2778# an array-of-tables header, or a table header
2779def head.job(
2780  arr: Bool,
2781  bad: String,
2782  rows: List<&2, Val>,
2783  heads: List<&2, String>,
2784  +segs: List<&2, String>,
2785  back: Lex,
2786  qkind: QKind,
2787  uni: U32,
2788  left: U32,
2789  lead: Bool
2790) -> St:
2791  match arr:
2792    case True{}:
2793      head.apply(tree.walk(segs, Tw{rows, JAot{}, [], bad, "", heads, False{}}, 0),
2794        segs,
2795        back, qkind, uni, left, lead)
2796    case False{}:
2797      head.apply(tree.walk(segs, Tw{rows, JDef{}, [], bad, "", heads, False{}}, 0),
2798        segs,
2799        back, qkind, uni, left, lead)
2800
2801# open the table or the array of tables named by the header
2802def head.on(st: St, segs: List<&2, String>) -> St:
2803  St{bad, rows, heads, _here, _parts, _buf, _lex, back, qkind, _stack, _hold, uni, left,
2804    arrtab, lead} = st
2805  head.job(arrtab, bad, rows, heads, segs, back, qkind, uni, left, lead)
2806
2807# the header key is ready
2808def head.go(empty: Bool, st: St, segs: List<&2, String>) -> St:
2809  match empty:
2810    case True{}:
2811      st.err(st, "empty key")
2812    case False{}:
2813      head.on(st, segs)
2814
2815# the segment was pushed
2816def head.use(st: St) -> St:
2817  St{bad, rows, heads, here, +parts, buf, lex, back, qkind, stack, hold, uni, left,
2818    arrtab, lead} = st
2819  head.go(List.is_empty(&2, String, parts),
2820    St{bad, rows, heads, here, parts, buf, lex, back, qkind, stack, hold, uni, left,
2821      arrtab, lead}, List.reverse(&2, String, parts))
2822
2823# the buffer is the last segment when it holds one
2824def head.fin.buf(empty: Bool, st: St) -> St:
2825  match empty:
2826    case True{}:
2827      head.use(st)
2828    case False{}:
2829      head.use(seg.push(st, LTop{}))
2830
2831# push a trailing segment, then open the header
2832def head.fin(st: St) -> St:
2833  St{bad, rows, heads, here, parts, +buf, lex, back, qkind, stack, hold, uni, left,
2834    arrtab, lead} = st
2835  head.fin.buf(List.is_empty(&2, Char, buf),
2836    St{bad, rows, heads, here, parts, buf, lex, back, qkind, stack, hold, uni, left,
2837      arrtab, lead})
2838
2839# close an array and place it
2840def arr.top(
2841  stack: List<&2, Ctx>,
2842  bad: String,
2843  rows: List<&2, Val>,
2844  heads: List<&2, String>,
2845  back: Lex,
2846  qkind: QKind,
2847  uni: U32,
2848  left: U32,
2849  arrtab: Bool,
2850  lead: Bool
2851) -> St:
2852  match stack:
2853    case CArr{items, parts, here} <> rest:
2854      val.take(St{bad, rows, heads, here, parts, [], LTop{}, back, qkind, rest, HNone{},
2855        uni, left, arrtab, lead}, VArr{List.reverse(&2, Val, items)})
2856    case _:
2857      St{err.first(bad, "unclosed"), rows, heads, [], [], [], LSink{}, back, qkind, [],
2858        HNone{}, uni, left, arrtab, lead}
2859
2860# close an array and place it
2861def arr.close(st: St) -> St:
2862  St{bad, rows, heads, _here, _parts, _buf, _lex, back, qkind, stack, _hold, uni, left,
2863    arrtab, lead} = st
2864  arr.top(stack, bad, rows, heads, back, qkind, uni, left, arrtab, lead)
2865
2866# an inline table is closed once written: the tables its dotted keys made,
2867# open while it was read, close with it
2868def inl.seal(rows: List<&2, Val>) -> List<&2, Val>:
2869  match rows:
2870    case []:
2871      []
2872    case VPair{name, VHead{_path, sub}} <> t:
2873      VPair{name, VInl{inl.seal(sub)}} <> inl.seal(t)
2874    case h <> t:
2875      h <> inl.seal(t)
2876
2877# close an inline table and place it
2878def inl.top(
2879  stack: List<&2, Ctx>,
2880  bad: String,
2881  rows: List<&2, Val>,
2882  heads: List<&2, String>,
2883  back: Lex,
2884  qkind: QKind,
2885  uni: U32,
2886  left: U32,
2887  arrtab: Bool,
2888  lead: Bool
2889) -> St:
2890  match stack:
2891    case CInl{inl, parts, here} <> rest:
2892      val.take(St{bad, rows, heads, here, parts, [], LTop{}, back, qkind, rest, HNone{},
2893        uni, left, arrtab, lead}, VInl{inl.seal(inl)})
2894    case _:
2895      St{err.first(bad, "unclosed"), rows, heads, [], [], [], LSink{}, back, qkind, [],
2896        HNone{}, uni, left, arrtab, lead}
2897
2898# close an inline table and place it
2899def inl.close(st: St) -> St:
2900  St{bad, rows, heads, _here, _parts, _buf, _lex, back, qkind, stack, _hold, uni, left,
2901    arrtab, lead} = st
2902  inl.top(stack, bad, rows, heads, back, qkind, uni, left, arrtab, lead)
2903
2904# the innermost frame is an inline table
2905def inl.open(stack: List<&2, Ctx>) -> Bool:
2906  match stack:
2907    case CInl{_rows, _parts, _here} <> _rest:
2908      True{}
2909    case _:
2910      False{}
2911
2912# a string is finished
2913def str.fin(st: St) -> St:
2914  St{bad, rows, heads, here, parts, buf, lex, back, qkind, stack, hold, uni, left, arrtab,
2915    lead} = st
2916  val.take(St{bad, rows, heads, here, parts, [], lex, back, qkind, stack, hold, uni, left,
2917    arrtab, lead}, VStr{buf.str(buf)})
2918
2919# one or two quotes that did not close a multiline string
2920def mq.put(one: Bool, +qq: Char, buf: List<&2, Char>) -> List<&2, Char>:
2921  match one:
2922    case True{}:
2923      qq <> buf
2924    case False{}:
2925      qq <> (qq <> buf)
2926
2927# a code point may be written as a character
2928def uni.ok(+uu: U32) -> Bool:
2929  Bool.and(U32.is_le(uu, 1114111),
2930    Bool.not(Bool.and(U32.is_ge(uu, 55296), U32.is_le(uu, 57343))))
2931
2932# a finished unicode escape written into the buffer
2933def uni.finish.on(st: St, uu: U32, back: Lex) -> St:
2934  St{bad, rows, heads, here, parts, buf, _lex, b, qkind, stack, _hold, _uni, _left,
2935    arrtab, lead} = st
2936  St{bad, rows, heads, here, parts, Char.from_u32(uu) <> buf, back, b, qkind, stack,
2937    HNone{}, 0, 0, arrtab, lead}
2938
2939# a finished unicode escape
2940def uni.finish(ok: Bool, st: St, uu: U32, back: Lex) -> St:
2941  match ok:
2942    case False{}:
2943      st.err(st, "invalid escape")
2944    case True{}:
2945      uni.finish.on(st, uu, back)
2946
2947# another hex digit is still required
2948def uni.next.more(st: St, uu: U32, left: U32) -> St:
2949  St{bad, rows, heads, here, parts, buf, lex, b, qkind, stack, hold, _uni, _left,
2950    arrtab, lead} = st
2951  St{bad, rows, heads, here, parts, buf, lex, b, qkind, stack, hold, uu,
2952    U32.sub(left, 1), arrtab, lead}
2953
2954# the escape is finished, or another hex digit is required
2955def uni.next(last: Bool, st: St, +uu: U32, back: Lex, left: U32) -> St:
2956  match last:
2957    case True{}:
2958      uni.finish(uni.ok(uu), st, uu, back)
2959    case False{}:
2960      uni.next.more(st, uu, left)
2961
2962# one hex digit of a unicode escape
2963def uni.go(ok: Bool, st: St, +cc: Char, back: Lex, uni: U32, +left: U32) -> St:
2964  match ok:
2965    case False{}:
2966      st.err(st, "invalid escape")
2967    case True{}:
2968      uni.next(U32.is_eq(left, 1), st, U32.add(U32.mul(uni, 16), dig.val(cc)), back, left)
2969
2970# one hex digit of a unicode escape
2971def uni.step(st: St, +cc: Char, back: Lex) -> St:
2972  St{bad, rows, heads, here, parts, buf, lex, b, qkind, stack, hold, +uni, +left, arrtab,
2973    lead} = st
2974  uni.go(dig.ok(dig.val(cc), 16),
2975    St{bad, rows, heads, here, parts, buf, lex, b, qkind, stack, hold, uni, left, arrtab,
2976      lead}, cc, back, uni, left)
2977
2978# a long unicode escape, or an unknown escape
2979def esc.uu(uu: Bool) -> Esc:
2980  match uu:
2981    case True{}:
2982      EUni{8}
2983    case False{}:
2984      EBad{}
2985
2986# a unicode escape
2987def esc.u(uuu: Bool, uu: Bool) -> Esc:
2988  match uuu:
2989    case True{}:
2990      EUni{4}
2991    case False{}:
2992      esc.uu(uu)
2993
2994# carriage return or a unicode escape
2995def esc.r(rr: Bool, uuu: Bool, uu: Bool) -> Esc:
2996  match rr:
2997    case True{}:
2998      EChar{'\r'}
2999    case False{}:
3000      esc.u(uuu, uu)
3001
3002# form feed or the rest
3003def esc.f(ff: Bool, rr: Bool, uuu: Bool, uu: Bool) -> Esc:
3004  match ff:
3005    case True{}:
3006      EChar{Chr{12}}
3007    case False{}:
3008      esc.r(rr, uuu, uu)
3009
3010# newline or the rest
3011def esc.n(nn: Bool, ff: Bool, rr: Bool, uuu: Bool, uu: Bool) -> Esc:
3012  match nn:
3013    case True{}:
3014      EChar{'\n'}
3015    case False{}:
3016      esc.f(ff, rr, uuu, uu)
3017
3018# tab, newline, form feed, carriage return, or a unicode escape
3019def esc.t(tt: Bool, nn: Bool, ff: Bool, rr: Bool, uuu: Bool, uu: Bool) -> Esc:
3020  match tt:
3021    case True{}:
3022      EChar{'\t'}
3023    case False{}:
3024      esc.n(nn, ff, rr, uuu, uu)
3025
3026# backslash, quotation mark, or a letter escape
3027def esc.letter(bb: Bool, tt: Bool, nn: Bool, ff: Bool, rr: Bool, uuu: Bool, uu: Bool) -> Esc:
3028  match bb:
3029    case True{}:
3030      EChar{Chr{8}}
3031    case False{}:
3032      esc.t(tt, nn, ff, rr, uuu, uu)
3033
3034# spaces before the newline of a line-ending backslash
3035def esc.sp(sp: Bool, bb: Bool, tt: Bool, nn: Bool, ff: Bool, rr: Bool, uuu: Bool, uu: Bool) -> Esc:
3036  match sp:
3037    case True{}:
3038      EFold{False{}}
3039    case False{}:
3040      esc.letter(bb, tt, nn, ff, rr, uuu, uu)
3041
3042# a line-ending backslash, or a letter
3043def esc.fold(nl: Bool, sp: Bool, bb: Bool, tt: Bool, nn: Bool, ff: Bool, rr: Bool, uuu: Bool, uu: Bool) -> Esc:
3044  match nl:
3045    case True{}:
3046      EFold{True{}}
3047    case False{}:
3048      esc.sp(sp, bb, tt, nn, ff, rr, uuu, uu)
3049
3050# a backslash or a letter
3051def esc.bs(bs: Bool, bb: Bool, tt: Bool, nn: Bool, ff: Bool, rr: Bool, uuu: Bool, uu: Bool, nl: Bool, sp: Bool) -> Esc:
3052  match bs:
3053    case True{}:
3054      EChar{'\\'}
3055    case False{}:
3056      esc.fold(nl, sp, bb, tt, nn, ff, rr, uuu, uu)
3057
3058# a quote, a backslash, or a letter
3059def esc.of.go(
3060  qq: Bool,
3061  bs: Bool,
3062  bb: Bool,
3063  tt: Bool,
3064  nn: Bool,
3065  ff: Bool,
3066  rr: Bool,
3067  uuu: Bool,
3068  uu: Bool,
3069  nl: Bool,
3070  sp: Bool
3071) -> Esc:
3072  match qq:
3073    case True{}:
3074      EChar{'"'}
3075    case False{}:
3076      esc.bs(bs, bb, tt, nn, ff, rr, uuu, uu, nl, sp)
3077
3078# classify one escaped character
3079def esc.of(+cc: Char) -> Esc:
3080  esc.of.go(Char.is_eq(cc, '"'), Char.is_eq(cc, '\\'), Char.is_eq(cc, 'b'),
3081    Char.is_eq(cc, 't'), Char.is_eq(cc, 'n'), Char.is_eq(cc, 'f'), Char.is_eq(cc, 'r'),
3082    Char.is_eq(cc, 'u'), Char.is_eq(cc, 'U'), ch.nl(cc), ch.sp(cc))
3083
3084# the lex state that reads the hex digits of an escape
3085def esc.uni.lex(back: Lex) -> Lex:
3086  match back:
3087    case LKeyQ{}:
3088      LKeyU{}
3089    case LHeadQ{}:
3090      LHeadU{}
3091    case _:
3092      LUni{}
3093
3094# a unicode escape starts with no digits seen
3095def esc.uni(st: St, back: Lex, nn: U32) -> St:
3096  St{bad, rows, heads, here, parts, buf, _lex, b, qkind, stack, hold, _uni, _left,
3097    arrtab, lead} = st
3098  St{bad, rows, heads, here, parts, buf, esc.uni.lex(back), b, qkind, stack, hold, 0, nn,
3099    arrtab, lead}
3100
3101# 1 once a newline has been seen
3102def esc.fold.bit(saw: Bool) -> U32:
3103  match saw:
3104    case True{}:
3105      1
3106    case False{}:
3107      0
3108
3109# a line-ending backslash skips following whitespace
3110def esc.fold.on(st: St, saw: Bool) -> St:
3111  St{bad, rows, heads, here, parts, buf, _lex, b, qkind, stack, hold, _uni, _left,
3112    arrtab, lead} = st
3113  St{bad, rows, heads, here, parts, buf, LFold{}, b, qkind, stack, hold, 0,
3114    esc.fold.bit(saw), arrtab, lead}
3115
3116# a line-ending backslash is only part of a multiline string
3117def esc.fold.kind(multi: Bool, st: St, saw: Bool) -> St:
3118  match multi:
3119    case True{}:
3120      esc.fold.on(st, saw)
3121    case False{}:
3122      st.err(st, "invalid escape")
3123
3124# a line-ending backslash is only part of a multiline string
3125def esc.fold.str(st: St, saw: Bool) -> St:
3126  St{bad, rows, heads, here, parts, buf, lex, b, +qkind, stack, hold, uni, left,
3127    arrtab, lead} = st
3128  esc.fold.kind(q.multi(qkind), St{bad, rows, heads, here, parts, buf, lex, b, qkind,
3129    stack, hold, uni, left, arrtab, lead}, saw)
3130
3131# a line-ending backslash is only part of a string
3132def esc.fold.use(back: Lex, st: St, saw: Bool) -> St:
3133  match back:
3134    case LStr{}:
3135      esc.fold.str(st, saw)
3136    case _:
3137      st.err(st, "invalid escape")
3138
3139# apply one escape and return to the string
3140def esc.use(ee: Esc, st: St, back: Lex) -> St:
3141  match ee:
3142    case EChar{c}:
3143      st.push(st, c, back)
3144    case EUni{n}:
3145      esc.uni(st, back, n)
3146    case EFold{saw}:
3147      esc.fold.use(back, st, saw)
3148    case EBad{}:
3149      st.err(st, "invalid escape")
3150
3151# the lex state a comment returns to
3152def com.enter(st: St, back: Lex) -> St:
3153  St{bad, rows, heads, here, parts, buf, _lex, _back, qkind, stack, hold, uni, left,
3154    arrtab, lead} = st
3155  St{bad, rows, heads, here, parts, buf, LCom{}, back, qkind, stack, hold, uni, left,
3156    arrtab, lead}
3157
3158# a bare key, or a rejected character
3159def lex.top.bare(bare: Bool, st: St, cc: Char) -> St:
3160  match bare:
3161    case True{}:
3162      st.push(st, cc, LKey{})
3163    case False{}:
3164      st.err(st, "invalid key")
3165
3166# a header starts with a clear key
3167def lex.bracket(st: St) -> St:
3168  St{bad, rows, heads, here, _parts, _buf, _lex, back, qkind, stack, hold, uni, left,
3169    _arr, lead} = st
3170  St{bad, rows, heads, here, [], [], LHead{}, back, qkind, stack, hold, uni, left,
3171    False{}, lead}
3172
3173# a quoted key starts with an empty buffer
3174def lex.qkey(st: St, kind: QKind, lex: Lex) -> St:
3175  St{bad, rows, heads, here, parts, _buf, _lex, back, _q, stack, hold, uni, left, arrtab,
3176    lead} = st
3177  St{bad, rows, heads, here, parts, [], lex, back, kind, stack, hold, uni, left, arrtab,
3178    lead}
3179
3180# a literal key or a bare key
3181def lex.top.lit(lit: Bool, bare: Bool, st: St, cc: Char) -> St:
3182  match lit:
3183    case True{}:
3184      lex.qkey(st, QLit{}, LKeyQ{})
3185    case False{}:
3186      lex.top.bare(bare, st, cc)
3187
3188# a quoted key or a bare key
3189def lex.top.q(qq: Bool, lit: Bool, bare: Bool, st: St, cc: Char) -> St:
3190  match qq:
3191    case True{}:
3192      lex.qkey(st, QBasic{}, LKeyQ{})
3193    case False{}:
3194      lex.top.lit(lit, bare, st, cc)
3195
3196# a header or a key
3197def lex.top.br(br: Bool, qq: Bool, lit: Bool, bare: Bool, st: St, cc: Char) -> St:
3198  match br:
3199    case True{}:
3200      lex.bracket(st)
3201    case False{}:
3202      lex.top.q(qq, lit, bare, st, cc)
3203
3204# a comment, a header, or a key
3205def lex.top.hash(hash: Bool, br: Bool, qq: Bool, lit: Bool, bare: Bool, st: St, cc: Char) -> St:
3206  match hash:
3207    case True{}:
3208      com.enter(st, LTop{})
3209    case False{}:
3210      lex.top.br(br, qq, lit, bare, st, cc)
3211
3212# a newline, a comment, a header, or a key
3213def lex.top.nl(nl: Bool, hash: Bool, br: Bool, qq: Bool, lit: Bool, bare: Bool, st: St, cc: Char) -> St:
3214  match nl:
3215    case True{}:
3216      st
3217    case False{}:
3218      lex.top.hash(hash, br, qq, lit, bare, st, cc)
3219
3220# whitespace, a comment, a header, or a key
3221def lex.top.go(sp: Bool, nl: Bool, hash: Bool, br: Bool, qq: Bool, lit: Bool, bare: Bool, st: St, cc: Char) -> St:
3222  match sp:
3223    case True{}:
3224      st
3225    case False{}:
3226      lex.top.nl(nl, hash, br, qq, lit, bare, st, cc)
3227
3228# top level: a key, a header, a comment, or whitespace
3229def lex.top(st: St, +cc: Char) -> St:
3230  lex.top.go(ch.sp(cc), ch.nl(cc), Char.is_eq(cc, '#'), Char.is_eq(cc, '['),
3231    Char.is_eq(cc, '"'), Char.is_eq(cc, '\''), bare.at(cc), st, cc)
3232
3233# a newline before equals is rejected
3234def lex.key.nl(nl: Bool, st: St, _c: Char) -> St:
3235  match nl:
3236    case True{}:
3237      st.err(st, "newline in key")
3238    case False{}:
3239      st.err(st, "invalid key")
3240
3241# an empty segment is rejected
3242def lex.key.end.go(empty: Bool, st: St, lex: Lex) -> St:
3243  match empty:
3244    case True{}:
3245      st.err(st, "empty key")
3246    case False{}:
3247      seg.push(st, lex)
3248
3249# end a nonempty segment
3250def lex.key.end(st: St, next: Lex) -> St:
3251  St{bad, rows, heads, here, parts, +buf, lx, back, qkind, stack, hold, uni, left,
3252    arrtab, lead} = st
3253  lex.key.end.go(List.is_empty(&2, Char, buf),
3254    St{bad, rows, heads, here, parts, buf, lx, back, qkind, stack, hold, uni, left,
3255      arrtab, lead}, next)
3256
3257# a comment ends the segment and waits for equals
3258def lex.key.hash(hash: Bool, nl: Bool, st: St, cc: Char) -> St:
3259  match hash:
3260    case True{}:
3261      com.enter(lex.key.end(st, LAfter{}), LAfter{})
3262    case False{}:
3263      lex.key.nl(nl, st, cc)
3264
3265# an equals ends the key
3266def lex.key.eq(eq: Bool, hash: Bool, nl: Bool, st: St, +cc: Char) -> St:
3267  match eq:
3268    case True{}:
3269      lex.key.end(st, LVal{})
3270    case False{}:
3271      lex.key.hash(hash, nl, st, cc)
3272
3273# a dot ends the segment and skips whitespace before the next one
3274def lex.key.dot(dot: Bool, eq: Bool, hash: Bool, nl: Bool, st: St, cc: Char) -> St:
3275  match dot:
3276    case True{}:
3277      lex.key.end(st, LKeyDot{})
3278    case False{}:
3279      lex.key.eq(eq, hash, nl, st, cc)
3280
3281# whitespace ends the segment
3282def lex.key.sp(sp: Bool, dot: Bool, eq: Bool, hash: Bool, nl: Bool, st: St, cc: Char) -> St:
3283  match sp:
3284    case True{}:
3285      lex.key.end(st, LAfter{})
3286    case False{}:
3287      lex.key.dot(dot, eq, hash, nl, st, cc)
3288
3289# a bare character, whitespace, a dot, or an equals
3290def lex.key.go(bare: Bool, sp: Bool, dot: Bool, eq: Bool, hash: Bool, nl: Bool, st: St, cc: Char) -> St:
3291  match bare:
3292    case True{}:
3293      st.push(st, cc, LKey{})
3294    case False{}:
3295      lex.key.sp(sp, dot, eq, hash, nl, st, cc)
3296
3297# inside a bare key segment
3298def lex.key(st: St, +cc: Char) -> St:
3299  lex.key.go(bare.at(cc), ch.sp(cc), Char.is_eq(cc, '.'), Char.is_eq(cc, '='),
3300    Char.is_eq(cc, '#'), ch.nl(cc), st, cc)
3301
3302# a newline before equals is rejected
3303def lex.after.nl(nl: Bool, st: St, _c: Char) -> St:
3304  match nl:
3305    case True{}:
3306      st.err(st, "newline in key")
3307    case False{}:
3308      st.err(st, "invalid key")
3309
3310# a comment waits for equals
3311def lex.after.hash(hash: Bool, nl: Bool, st: St, cc: Char) -> St:
3312  match hash:
3313    case True{}:
3314      com.enter(st, LAfter{})
3315    case False{}:
3316      lex.after.nl(nl, st, cc)
3317
3318# equals starts the value
3319def lex.after.eq(eq: Bool, hash: Bool, nl: Bool, st: St, cc: Char) -> St:
3320  match eq:
3321    case True{}:
3322      st.lex(st, LVal{})
3323    case False{}:
3324      lex.after.hash(hash, nl, st, cc)
3325
3326# a dot starts the next segment, after any whitespace
3327def lex.after.dot(dot: Bool, eq: Bool, hash: Bool, nl: Bool, st: St, cc: Char) -> St:
3328  match dot:
3329    case True{}:
3330      st.lex(st, LKeyDot{})
3331    case False{}:
3332      lex.after.eq(eq, hash, nl, st, cc)
3333
3334# whitespace, a dot, or an equals
3335def lex.after.go(sp: Bool, dot: Bool, eq: Bool, hash: Bool, nl: Bool, st: St, cc: Char) -> St:
3336  match sp:
3337    case True{}:
3338      st
3339    case False{}:
3340      lex.after.dot(dot, eq, hash, nl, st, cc)
3341
3342# after a key segment, before equals or a dot
3343def lex.after(st: St, +cc: Char) -> St:
3344  lex.after.go(ch.sp(cc), Char.is_eq(cc, '.'), Char.is_eq(cc, '='), Char.is_eq(cc, '#'),
3345    ch.nl(cc), st, cc)
3346
3347# a value starts on the line of its key
3348def lex.val.nl3(inside: Bool, st: St) -> St:
3349  match inside:
3350    case True{}:
3351      st.err(st, "newline in inline table")
3352    case False{}:
3353      st.err(st, "newline before value")
3354
3355# a newline before a value is rejected
3356def lex.val.nl2(st: St, _c: Char) -> St:
3357  St{bad, rows, heads, here, parts, buf, lx, back, qkind, +stack, hold, uni, left,
3358    arrtab, lead} = st
3359  lex.val.nl3(inl.open(stack),
3360    St{bad, rows, heads, here, parts, buf, lx, back, qkind, stack, hold, uni, left,
3361      arrtab, lead})
3362
3363# an array starts a frame and waits for a value
3364def lex.array(st: St) -> St:
3365  St{bad, rows, heads, +here, +parts, _buf, _lex, back, qkind, stack, hold, uni, left,
3366    arrtab, lead} = st
3367  St{bad, rows, heads, here, [], [], LArr{}, back, qkind,
3368    CArr{[], parts, here} <> stack, hold, uni, left, arrtab, lead}
3369
3370# an inline table starts a frame and waits for a key
3371def lex.inline(st: St) -> St:
3372  St{bad, rows, heads, +here, +parts, _buf, _lex, back, qkind, stack, hold, uni, left,
3373    arrtab, lead} = st
3374  St{bad, rows, heads, here, [], [], LInl{}, back, qkind,
3375    CInl{[], parts, here} <> stack, hold, uni, left, arrtab, lead}
3376
3377# an inline table, or a bare value
3378def lex.val.inl(inl: Bool, st: St, cc: Char) -> St:
3379  match inl:
3380    case True{}:
3381      lex.inline(st)
3382    case False{}:
3383      st.push(st, cc, LBare{})
3384
3385# an array, or another value
3386def lex.val.br(br: Bool, inl: Bool, st: St, cc: Char) -> St:
3387  match br:
3388    case True{}:
3389      lex.array(st)
3390    case False{}:
3391      lex.val.inl(inl, st, cc)
3392
3393# a literal string, or another value
3394def lex.val.lit(lit: Bool, br: Bool, inl: Bool, st: St, cc: Char) -> St:
3395  match lit:
3396    case True{}:
3397      lex.qkey(st, QLit{}, LQ{})
3398    case False{}:
3399      lex.val.br(br, inl, st, cc)
3400
3401# a basic string, or another value
3402def lex.val.q(qq: Bool, lit: Bool, br: Bool, inl: Bool, st: St, cc: Char) -> St:
3403  match qq:
3404    case True{}:
3405      lex.qkey(st, QBasic{}, LQ{})
3406    case False{}:
3407      lex.val.lit(lit, br, inl, st, cc)
3408
3409# a comment, a string, an array, an inline table, or a bare value
3410def lex.val.hash(hash: Bool, qq: Bool, lit: Bool, br: Bool, inl: Bool, st: St, cc: Char) -> St:
3411  match hash:
3412    case True{}:
3413      com.enter(st, LVal{})
3414    case False{}:
3415      lex.val.q(qq, lit, br, inl, st, cc)
3416
3417# a newline before a value is rejected
3418def lex.val.nl(nl: Bool, hash: Bool, qq: Bool, lit: Bool, br: Bool, inl: Bool, st: St, cc: Char) -> St:
3419  match nl:
3420    case True{}:
3421      lex.val.nl2(st, cc)
3422    case False{}:
3423      lex.val.hash(hash, qq, lit, br, inl, st, cc)
3424
3425# whitespace or the value itself
3426def lex.val.go(sp: Bool, nl: Bool, hash: Bool, qq: Bool, lit: Bool, br: Bool, inl: Bool, st: St, cc: Char) -> St:
3427  match sp:
3428    case True{}:
3429      st
3430    case False{}:
3431      lex.val.nl(nl, hash, qq, lit, br, inl, st, cc)
3432
3433# the start of a value
3434def lex.val(st: St, +cc: Char) -> St:
3435  lex.val.go(ch.sp(cc), ch.nl(cc), Char.is_eq(cc, '#'), Char.is_eq(cc, '"'),
3436    Char.is_eq(cc, '\''), Char.is_eq(cc, '['), Char.is_eq(cc, '{'), st, cc)
3437
3438# a second quote may start a multiline string; otherwise the first character is held
3439def lex.q.go(same: Bool, st: St, cc: Char) -> St:
3440  match same:
3441    case True{}:
3442      st.lex(st, LQ2{})
3443    case False{}:
3444      st.hold(st.lex(st, LStr{}), LStr{}, cc)
3445
3446# the first quote has been seen
3447def lex.q(st: St, +cc: Char) -> St:
3448  St{bad, rows, heads, here, parts, buf, lx, back, +qkind, stack, hold, uni, left,
3449    arrtab, lead} = st
3450  lex.q.go(Char.is_eq(cc, q.char(qkind)),
3451    St{bad, rows, heads, here, parts, buf, lx, back, qkind, stack, hold, uni, left,
3452      arrtab, lead}, cc)
3453
3454# a multiline string, about to trim one opening newline
3455def lex.multi(st: St, kind: QKind) -> St:
3456  St{bad, rows, heads, here, parts, buf, _lex, back, _q, stack, hold, uni, left, arrtab,
3457    lead} = st
3458  St{bad, rows, heads, here, parts, buf, LTrim{}, back, q.as.multi(kind), stack, hold,
3459    uni, left, arrtab, lead}
3460
3461# a third quote starts a multiline string; two quotes are an empty string
3462def lex.q2.go(same: Bool, st: St, cc: Char, kind: QKind) -> St:
3463  match same:
3464    case True{}:
3465      lex.multi(st, kind)
3466    case False{}:
3467      bare.hold(val.take(st, VStr{""}), cc, True{})
3468
3469# two quotes have been seen
3470def lex.q2(st: St, +cc: Char) -> St:
3471  St{bad, rows, heads, here, parts, buf, lx, back, +qkind, stack, hold, uni, left,
3472    arrtab, lead} = st
3473  lex.q2.go(Char.is_eq(cc, q.char(qkind)),
3474    St{bad, rows, heads, here, parts, buf, lx, back, qkind, stack, hold, uni, left,
3475      arrtab, lead}, cc, qkind)
3476
3477# a line feed is trimmed; any other character is the first of the string
3478def lex.trim.nl(nl: Bool, st: St, cc: Char) -> St:
3479  match nl:
3480    case True{}:
3481      st.lex(st, LStr{})
3482    case False{}:
3483      st.hold(st.lex(st, LStr{}), LStr{}, cc)
3484
3485# a carriage return may still have a line feed after it
3486def lex.trim.go(cr: Bool, nl: Bool, st: St, cc: Char) -> St:
3487  match cr:
3488    case True{}:
3489      st.lex(st, LTrim2{})
3490    case False{}:
3491      lex.trim.nl(nl, st, cc)
3492
3493# trim one opening newline of a multiline string
3494def lex.trim(st: St, +cc: Char) -> St:
3495  lex.trim.go(U32.is_eq(Char.to_u32(cc), 13), ch.nl(cc), st, cc)
3496
3497# the line feed of a CRLF opening newline
3498def lex.trim2.go(lf: Bool, st: St, cc: Char) -> St:
3499  match lf:
3500    case True{}:
3501      st.lex(st, LStr{})
3502    case False{}:
3503      st.hold(st.lex(st, LStr{}), LStr{}, cc)
3504
3505# the line feed of a CRLF opening newline
3506def lex.trim2(st: St, +cc: Char) -> St:
3507  lex.trim2.go(U32.is_eq(Char.to_u32(cc), 10), st, cc)
3508
3509# a forbidden control, or one character of the string
3510def lex.str.char.go(bad: Bool, st: St, cc: Char) -> St:
3511  match bad:
3512    case True{}:
3513      st.err(st, "invalid control")
3514    case False{}:
3515      st.push(st, cc, LStr{})
3516
3517# a raw character, rejected when it is a forbidden control
3518def lex.str.char(multi: Bool, st: St, +cc: Char) -> St:
3519  lex.str.char.go(str.bad(Char.to_u32(cc), multi), st, cc)
3520
3521# an escape, or a plain character
3522def lex.str.bs(bs: Bool, multi: Bool, st: St, cc: Char, _kind: QKind) -> St:
3523  match bs:
3524    case True{}:
3525      st.lex(st, LEsc{})
3526    case False{}:
3527      lex.str.char(multi, st, cc)
3528
3529# a backslash starts an escape in a basic string
3530def lex.str.esc(basic: Bool, multi: Bool, st: St, +cc: Char, kind: QKind) -> St:
3531  match basic:
3532    case True{}:
3533      lex.str.bs(Char.is_eq(cc, '\\'), multi, st, cc, kind)
3534    case False{}:
3535      lex.str.char(multi, st, cc)
3536
3537# count closing quotes of a multiline string
3538def lex.mq(st: St, nn: U32) -> St:
3539  St{bad, rows, heads, here, parts, buf, _lex, back, qkind, stack, hold, _uni, _left,
3540    arrtab, lead} = st
3541  St{bad, rows, heads, here, parts, buf, LMq{}, back, qkind, stack, hold, 0, nn, arrtab,
3542    lead}
3543
3544# one quote closes a one-line string and may close a multiline string
3545def lex.str.quote(multi: Bool, st: St) -> St:
3546  match multi:
3547    case False{}:
3548      str.fin(st)
3549    case True{}:
3550      lex.mq(st, 1)
3551
3552# a quote, an escape, or a character
3553def lex.str.go(quote: Bool, multi: Bool, basic: Bool, st: St, +cc: Char, kind: QKind) -> St:
3554  match quote:
3555    case True{}:
3556      lex.str.quote(multi, st)
3557    case False{}:
3558      lex.str.esc(basic, multi, st, cc, kind)
3559
3560# inside a string
3561def lex.str(st: St, +cc: Char) -> St:
3562  St{bad, rows, heads, here, parts, buf, lx, back, +qkind, stack, hold, uni, left,
3563    arrtab, lead} = st
3564  lex.str.go(Char.is_eq(cc, q.char(qkind)), q.multi(qkind), q.basic(qkind),
3565    St{bad, rows, heads, here, parts, buf, lx, back, qkind, stack, hold, uni, left,
3566      arrtab, lead}, cc, qkind)
3567
3568# a closing delimiter has been read. up to two more quotes are still the string's
3569def lex.mq.shut(st: St) -> St:
3570  St{bad, rows, heads, here, parts, buf, _lex, back, qkind, stack, hold, uni, _left,
3571    arrtab, lead} = st
3572  St{bad, rows, heads, here, parts, buf, LMqEnd{}, back, qkind, stack, hold, uni, 0,
3573    arrtab, lead}
3574
3575# the second quote waits; the third is a closing delimiter
3576def lex.mq.one(one: Bool, st: St) -> St:
3577  match one:
3578    case True{}:
3579      lex.mq(st, 2)
3580    case False{}:
3581      lex.mq.shut(st)
3582
3583# quotes that were not a closer return to the string, and the character is held
3584def lex.mq.back(st: St, cc: Char, buf: List<&2, Char>) -> St:
3585  St{bad, rows, heads, here, parts, _buf, _lex, back, qkind, stack, _hold, uni, _left,
3586    arrtab, lead} = st
3587  St{bad, rows, heads, here, parts, buf, LStr{}, back, qkind, stack, HChar{cc}, uni, 0,
3588    arrtab, lead}
3589
3590# a quote continues the closer; anything else is content
3591def lex.mq.go(quote: Bool, one: Bool, st: St, cc: Char, buf: List<&2, Char>, kind: QKind, _left: U32) -> St:
3592  match quote:
3593    case True{}:
3594      lex.mq.one(one, st)
3595    case False{}:
3596      lex.mq.back(st, cc, mq.put(one, q.char(kind), buf))
3597
3598# one character while closing a multiline string
3599def lex.mq.step(st: St, +cc: Char) -> St:
3600  St{bad, rows, heads, here, parts, +buf, lx, back, +qkind, stack, hold, uni, +left,
3601    arrtab, lead} = st
3602  lex.mq.go(Char.is_eq(cc, q.char(qkind)), U32.is_eq(left, 1),
3603    St{bad, rows, heads, here, parts, buf, lx, back, qkind, stack, hold, uni, left,
3604      arrtab, lead}, cc, buf, qkind, left)
3605
3606# a quote after the closing delimiter: the delimiter moves one quote on
3607def lex.mq.more(st: St, cc: Char) -> St:
3608  St{bad, rows, heads, here, parts, buf, lx, back, qkind, stack, hold, uni, left, arrtab,
3609    lead} = st
3610  St{bad, rows, heads, here, parts, cc <> buf, lx, back, qkind, stack, hold, uni,
3611    U32.add(left, 1), arrtab, lead}
3612
3613# the string ends before this character, which the next state reads
3614def lex.mq.fin(st: St, cc: Char) -> St:
3615  bare.hold(str.fin(st), cc, True{})
3616
3617# a third quote after the delimiter would make three in the string
3618def lex.mq.room(room: Bool, st: St, cc: Char) -> St:
3619  match room:
3620    case True{}:
3621      lex.mq.more(st, cc)
3622    case False{}:
3623      lex.mq.fin(st, cc)
3624
3625# a quote may still belong to the string; anything else follows it
3626def lex.mq.end.go(quote: Bool, room: Bool, st: St, cc: Char) -> St:
3627  match quote:
3628    case True{}:
3629      lex.mq.room(room, st, cc)
3630    case False{}:
3631      lex.mq.fin(st, cc)
3632
3633# one character after a multiline string's closing delimiter
3634def lex.mq.end(st: St, +cc: Char) -> St:
3635  St{bad, rows, heads, here, parts, buf, lx, back, +qkind, stack, hold, uni, +left,
3636    arrtab, lead} = st
3637  lex.mq.end.go(Char.is_eq(cc, q.char(qkind)), U32.is_lt(left, 2),
3638    St{bad, rows, heads, here, parts, buf, lx, back, qkind, stack, hold, uni, left,
3639      arrtab, lead}, cc)
3640
3641# one escaped character
3642def lex.esc(st: St, +cc: Char) -> St:
3643  esc.use(esc.of(cc), st, LStr{})
3644
3645# remember that a newline was seen
3646def lex.fold.saw(st: St) -> St:
3647  St{bad, rows, heads, here, parts, buf, lex, back, qkind, stack, hold, uni, _left,
3648    arrtab, lead} = st
3649  St{bad, rows, heads, here, parts, buf, lex, back, qkind, stack, hold, uni, 1, arrtab,
3650    lead}
3651
3652# a character before the newline is rejected
3653def lex.fold.end.go(saw: Bool, st: St, cc: Char) -> St:
3654  match saw:
3655    case True{}:
3656      st.hold(st.lex(st, LStr{}), LStr{}, cc)
3657    case False{}:
3658      st.err(st, "invalid escape")
3659
3660# the next character ends the fold only after a newline
3661def lex.fold.end(st: St, cc: Char) -> St:
3662  St{bad, rows, heads, here, parts, buf, lx, back, qkind, stack, hold, uni, +left,
3663    arrtab, lead} = st
3664  lex.fold.end.go(U32.is_eq(left, 1),
3665    St{bad, rows, heads, here, parts, buf, lx, back, qkind, stack, hold, uni, left,
3666      arrtab, lead}, cc)
3667
3668# a newline has been seen, so later whitespace may still be skipped
3669def lex.fold.nl(nl: Bool, st: St, cc: Char) -> St:
3670  match nl:
3671    case True{}:
3672      lex.fold.saw(st)
3673    case False{}:
3674      lex.fold.end(st, cc)
3675
3676# more whitespace, or the next character of the string
3677def lex.fold.go(sp: Bool, nl: Bool, st: St, cc: Char) -> St:
3678  match sp:
3679    case True{}:
3680      st
3681    case False{}:
3682      lex.fold.nl(nl, st, cc)
3683
3684# whitespace after a line-ending backslash
3685def lex.fold(st: St, +cc: Char) -> St:
3686  lex.fold.go(ch.sp(cc), ch.nl(cc), st, cc)
3687
3688# keep the space only when the buffer is a date; otherwise it ends the token
3689def lex.bare.date.go(date: Bool, st: St, cc: Char) -> St:
3690  match date:
3691    case True{}:
3692      st.lex(st, LDate{})
3693    case False{}:
3694      bare.finish(st, cc, False{})
3695
3696# a date may continue after one space
3697def lex.bare.date(st: St, cc: Char) -> St:
3698  St{bad, rows, heads, here, parts, +buf, lx, back, qkind, stack, hold, uni, left,
3699    arrtab, lead} = st
3700  lex.bare.date.go(date.shaped(buf),
3701    St{bad, rows, heads, here, parts, buf, lx, back, qkind, stack, hold, uni, left,
3702      arrtab, lead}, cc)
3703
3704# a newline inside an inline table is held, and so is any other delimiter
3705def lex.bare.hold(skip: Bool, nl: Bool, inside: Bool) -> Bool:
3706  Bool.or(Bool.not(skip), Bool.and(nl, inside))
3707
3708# a delimiter other than a date-space is held unless it is whitespace
3709def lex.bare.end(st: St, +cc: Char) -> St:
3710  St{bad, rows, heads, here, parts, buf, lx, back, qkind, +stack, hold, uni, left,
3711    arrtab, lead} = st
3712  bare.finish(St{bad, rows, heads, here, parts, buf, lx, back, qkind, stack, hold,
3713    uni, left, arrtab, lead}, cc, lex.bare.hold(ch.skip(cc), ch.nl(cc), inl.open(stack)))
3714
3715# a space may be the space inside a datetime
3716def lex.bare.sp(sp: Bool, st: St, cc: Char) -> St:
3717  match sp:
3718    case True{}:
3719      lex.bare.date(st, cc)
3720    case False{}:
3721      lex.bare.end(st, cc)
3722
3723# a delimiter ends the token
3724def lex.bare.go(delim: Bool, sp: Bool, st: St, cc: Char) -> St:
3725  match delim:
3726    case False{}:
3727      st.push(st, cc, LBare{})
3728    case True{}:
3729      lex.bare.sp(sp, st, cc)
3730
3731# a bare value
3732def lex.bare(st: St, +cc: Char) -> St:
3733  lex.bare.go(ch.delim(cc), ch.space(cc), st, cc)
3734
3735# the space and the digit belong to the datetime
3736def lex.date.digit(st: St, cc: Char) -> St:
3737  St{bad, rows, heads, here, parts, buf, _lex, back, qkind, stack, hold, uni, left,
3738    arrtab, lead} = st
3739  St{bad, rows, heads, here, parts, cc <> (' ' <> buf), LBare{}, back, qkind, stack, hold,
3740    uni, left, arrtab, lead}
3741
3742# the character after the space of a possible datetime
3743def lex.date.go(digit: Bool, st: St, cc: Char) -> St:
3744  match digit:
3745    case True{}:
3746      lex.date.digit(st, cc)
3747    case False{}:
3748      bare.finish(st, cc, True{})
3749
3750# the character after the space of a possible datetime
3751def lex.date(st: St, +cc: Char) -> St:
3752  lex.date.go(Char.is_digit(cc), st, cc)
3753
3754# the newline is read again in the state that was waiting
3755def lex.com.nl(st: St, cc: Char) -> St:
3756  St{bad, rows, heads, here, parts, buf, _lex, +back, qkind, stack, _hold, uni, left,
3757    arrtab, lead} = st
3758  St{bad, rows, heads, here, parts, buf, back, back, qkind, stack, HChar{cc}, uni, left,
3759    arrtab, lead}
3760
3761# tab is the one control a comment may hold
3762def lex.com.tab(tab: Bool, st: St) -> St:
3763  match tab:
3764    case True{}:
3765      st
3766    case False{}:
3767      st.err(st, "invalid control in comment")
3768
3769# any other character is part of the comment
3770def lex.com.ctl(ctl: Bool, tab: Bool, st: St) -> St:
3771  match ctl:
3772    case False{}:
3773      st
3774    case True{}:
3775      lex.com.tab(tab, st)
3776
3777# a comment runs to the end of the line and holds no control but tab
3778def lex.com.go(nl: Bool, ctl: Bool, tab: Bool, st: St, cc: Char) -> St:
3779  match nl:
3780    case False{}:
3781      lex.com.ctl(ctl, tab, st)
3782    case True{}:
3783      lex.com.nl(st, cc)
3784
3785# a comment runs to the end of the line
3786def lex.com(st: St, +cc: Char) -> St:
3787  lex.com.go(ch.nl(cc), ch.ctl(cc), U32.is_eq(Char.to_u32(cc), 9), st, cc)
3788
3789# the header is an array of tables
3790def lex.head.arr(st: St) -> St:
3791  St{bad, rows, heads, here, parts, buf, lex, back, qkind, stack, hold, uni, left, _arr,
3792    lead} = st
3793  St{bad, rows, heads, here, parts, buf, lex, back, qkind, stack, hold, uni, left,
3794    True{}, lead}
3795
3796# one extra bracket
3797def lex.head.br2(once: Bool, st: St) -> St:
3798  match once:
3799    case False{}:
3800      st.err(st, "invalid header")
3801    case True{}:
3802      lex.head.arr(st)
3803
3804# the second bracket is only legal at the start
3805def lex.head.br.go(fresh: Bool, once: Bool, st: St) -> St:
3806  match fresh:
3807    case False{}:
3808      st.err(st, "invalid header")
3809    case True{}:
3810      lex.head.br2(once, st)
3811
3812# [[ opens an array of tables when the key has not started
3813def lex.head.br(st: St) -> St:
3814  St{bad, rows, heads, here, +parts, +buf, lx, back, qkind, stack, hold, uni, left,
3815    +arrtab, lead} = st
3816  lex.head.br.go(Bool.and(List.is_empty(&2, String, parts),
3817    List.is_empty(&2, Char, buf)), Bool.not(arrtab),
3818    St{bad, rows, heads, here, parts, buf, lx, back, qkind, stack, hold, uni, left,
3819      arrtab, lead})
3820
3821# between segments a dot is required
3822def lex.head.ch.go(gap: Bool, st: St, cc: Char) -> St:
3823  match gap:
3824    case True{}:
3825      st.err(st, "invalid header")
3826    case False{}:
3827      st.push(st, cc, LHead{})
3828
3829# a bare character continues the segment, or starts one at the beginning
3830def lex.head.ch(st: St, cc: Char) -> St:
3831  St{bad, rows, heads, here, +parts, +buf, lx, back, qkind, stack, hold, uni, left,
3832    arrtab, lead} = st
3833  lex.head.ch.go(Bool.and(Bool.not(List.is_empty(&2, String, parts)),
3834    List.is_empty(&2, Char, buf)),
3835    St{bad, rows, heads, here, parts, buf, lx, back, qkind, stack, hold, uni, left,
3836      arrtab, lead}, cc)
3837
3838# whitespace with no segment open still needs a segment after it, so a second
3839# bracket there is refused: `[[` is two brackets with nothing between
3840def lex.head.gap.go(empty: Bool, st: St) -> St:
3841  match empty:
3842    case True{}:
3843      st.lex(st, LHeadDot{})
3844    case False{}:
3845      seg.push(st, LHeadGap{})
3846
3847# whitespace ends a segment when one is open
3848def lex.head.gap(st: St) -> St:
3849  St{bad, rows, heads, here, parts, +buf, lx, back, qkind, stack, hold, uni, left,
3850    arrtab, lead} = st
3851  lex.head.gap.go(List.is_empty(&2, Char, buf),
3852    St{bad, rows, heads, here, parts, buf, lx, back, qkind, stack, hold, uni, left,
3853      arrtab, lead})
3854
3855# a dot with no segment is rejected
3856def lex.head.dot3(empty: Bool, st: St) -> St:
3857  match empty:
3858    case True{}:
3859      st.err(st, "empty key")
3860    case False{}:
3861      seg.push(st, LHeadDot{})
3862
3863# a dot ends the open segment
3864def lex.head.dot2(st: St) -> St:
3865  St{bad, rows, heads, here, parts, +buf, lx, back, qkind, stack, hold, uni, left,
3866    arrtab, lead} = st
3867  lex.head.dot3(List.is_empty(&2, Char, buf),
3868    St{bad, rows, heads, here, parts, buf, lx, back, qkind, stack, hold, uni, left,
3869      arrtab, lead})
3870
3871# an array of tables still needs one ]
3872def lex.head.close2(arr: Bool, st: St) -> St:
3873  match arr:
3874    case True{}:
3875      st.lex(st, LHeadR{})
3876    case False{}:
3877      head.fin(st)
3878
3879# push the last segment when the buffer holds one
3880def lex.head.close.go(empty: Bool, arr: Bool, st: St) -> St:
3881  match empty:
3882    case True{}:
3883      lex.head.close2(arr, st)
3884    case False{}:
3885      lex.head.close2(arr, seg.push(st, LHead{}))
3886
3887# ] closes, or waits for the second ] of an array of tables
3888def lex.head.close(st: St) -> St:
3889  St{bad, rows, heads, here, parts, +buf, lx, back, qkind, stack, hold, uni, left,
3890    +arrtab, lead} = st
3891  lex.head.close.go(List.is_empty(&2, Char, buf), arrtab,
3892    St{bad, rows, heads, here, parts, buf, lx, back, qkind, stack, hold, uni, left,
3893      arrtab, lead})
3894
3895# a comment in a header
3896def lex.head.hash(hash: Bool, st: St) -> St:
3897  match hash:
3898    case True{}:
3899      com.enter(st, LHead{})
3900    case False{}:
3901      st.err(st, "invalid header")
3902
3903# a quote opens a segment only where none has started. After a bare character
3904# it would glue two keys into one segment, which toml.abnf does not allow
3905def lex.head.quote.go(fresh: Bool, st: St, kind: QKind) -> St:
3906  match fresh:
3907    case True{}:
3908      lex.qkey(st, kind, LHeadQ{})
3909    case False{}:
3910      st.err(st, "invalid header")
3911
3912# a quoted header segment, right after the bracket
3913def lex.head.quote(st: St, kind: QKind) -> St:
3914  St{bad, rows, heads, here, +parts, +buf, lx, back, qkind, stack, hold, uni, left,
3915    arrtab, lead} = st
3916  lex.head.quote.go(Bool.and(List.is_empty(&2, Char, buf),
3917    List.is_empty(&2, String, parts)),
3918    St{bad, rows, heads, here, parts, buf, lx, back, qkind, stack, hold, uni, left,
3919      arrtab, lead}, kind)
3920
3921# a literal header segment or a comment
3922def lex.head.lit(lit: Bool, hash: Bool, st: St) -> St:
3923  match lit:
3924    case True{}:
3925      lex.head.quote(st, QLit{})
3926    case False{}:
3927      lex.head.hash(hash, st)
3928
3929# a quoted header segment or a comment
3930def lex.head.q(qq: Bool, lit: Bool, hash: Bool, st: St, _c: Char) -> St:
3931  match qq:
3932    case True{}:
3933      lex.head.quote(st, QBasic{})
3934    case False{}:
3935      lex.head.lit(lit, hash, st)
3936
3937# the closing bracket, a quoted key, or a comment
3938def lex.head.end(end: Bool, qq: Bool, lit: Bool, hash: Bool, st: St, cc: Char) -> St:
3939  match end:
3940    case True{}:
3941      lex.head.close(st)
3942    case False{}:
3943      lex.head.q(qq, lit, hash, st, cc)
3944
3945# a dot, the end, or a quoted segment
3946def lex.head.dot(dot: Bool, end: Bool, qq: Bool, lit: Bool, hash: Bool, st: St, cc: Char) -> St:
3947  match dot:
3948    case True{}:
3949      lex.head.dot2(st)
3950    case False{}:
3951      lex.head.end(end, qq, lit, hash, st, cc)
3952
3953# whitespace, a dot, or the closing bracket
3954def lex.head.sp(sp: Bool, dot: Bool, end: Bool, qq: Bool, lit: Bool, hash: Bool, st: St, cc: Char) -> St:
3955  match sp:
3956    case True{}:
3957      lex.head.gap(st)
3958    case False{}:
3959      lex.head.dot(dot, end, qq, lit, hash, st, cc)
3960
3961# a bare character, whitespace, a dot, or the end
3962def lex.head.bare(bare: Bool, sp: Bool, dot: Bool, end: Bool, qq: Bool, lit: Bool, hash: Bool, st: St, cc: Char) -> St:
3963  match bare:
3964    case True{}:
3965      lex.head.ch(st, cc)
3966    case False{}:
3967      lex.head.sp(sp, dot, end, qq, lit, hash, st, cc)
3968
3969# a second bracket, a key character, or whitespace
3970def lex.head.go(
3971  br: Bool,
3972  bare: Bool,
3973  sp: Bool,
3974  dot: Bool,
3975  end: Bool,
3976  qq: Bool,
3977  lit: Bool,
3978  hash: Bool,
3979  st: St,
3980  cc: Char
3981) -> St:
3982  match br:
3983    case True{}:
3984      lex.head.br(st)
3985    case False{}:
3986      lex.head.bare(bare, sp, dot, end, qq, lit, hash, st, cc)
3987
3988# inside a header
3989def lex.head(st: St, +cc: Char) -> St:
3990  lex.head.go(Char.is_eq(cc, '['), bare.at(cc), ch.sp(cc), Char.is_eq(cc, '.'),
3991    Char.is_eq(cc, ']'), Char.is_eq(cc, '"'), Char.is_eq(cc, '\''), Char.is_eq(cc, '#'),
3992    st, cc)
3993
3994# the end, or a comment
3995def lex.hgap.end(end: Bool, hash: Bool, st: St) -> St:
3996  match end:
3997    case True{}:
3998      lex.head.close(st)
3999    case False{}:
4000      lex.head.hash(hash, st)
4001
4002# a dot starts the next segment
4003def lex.hgap.dot(dot: Bool, end: Bool, hash: Bool, st: St) -> St:
4004  match dot:
4005    case True{}:
4006      st.lex(st, LHeadDot{})
4007    case False{}:
4008      lex.hgap.end(end, hash, st)
4009
4010# whitespace, a dot, or the end
4011def lex.hgap(sp: Bool, dot: Bool, end: Bool, hash: Bool, st: St, _c: Char) -> St:
4012  match sp:
4013    case True{}:
4014      st
4015    case False{}:
4016      lex.hgap.dot(dot, end, hash, st)
4017
4018# between header segments
4019def lex.head.gap.step(st: St, +cc: Char) -> St:
4020  lex.hgap(ch.sp(cc), Char.is_eq(cc, '.'), Char.is_eq(cc, ']'), Char.is_eq(cc, '#'), st, cc)
4021
4022# a literal segment, or a rejected character
4023def lex.hdot.lit(lit: Bool, st: St, _c: Char) -> St:
4024  match lit:
4025    case True{}:
4026      lex.qkey(st, QLit{}, LHeadQ{})
4027    case False{}:
4028      st.err(st, "invalid header")
4029
4030# a quoted segment
4031def lex.hdot.q(qq: Bool, lit: Bool, st: St, cc: Char) -> St:
4032  match qq:
4033    case True{}:
4034      lex.qkey(st, QBasic{}, LHeadQ{})
4035    case False{}:
4036      lex.hdot.lit(lit, st, cc)
4037
4038# a bare segment or a quoted one
4039def lex.hdot.bare(bare: Bool, qq: Bool, lit: Bool, st: St, cc: Char) -> St:
4040  match bare:
4041    case True{}:
4042      st.push(st, cc, LHead{})
4043    case False{}:
4044      lex.hdot.q(qq, lit, st, cc)
4045
4046# whitespace or the next segment
4047def lex.hdot(sp: Bool, bare: Bool, qq: Bool, lit: Bool, st: St, cc: Char) -> St:
4048  match sp:
4049    case True{}:
4050      st
4051    case False{}:
4052      lex.hdot.bare(bare, qq, lit, st, cc)
4053
4054# after a dot, a segment must start
4055def lex.head.dot.step(st: St, +cc: Char) -> St:
4056  lex.hdot(ch.sp(cc), bare.at(cc), Char.is_eq(cc, '"'), Char.is_eq(cc, '\''), st, cc)
4057
4058# a forbidden control, or one character of the key
4059def lex.kq.char.go(bad: Bool, st: St, cc: Char, stay: Lex) -> St:
4060  match bad:
4061    case True{}:
4062      st.err(st, "invalid control")
4063    case False{}:
4064      st.push(st, cc, stay)
4065
4066# one character of a quoted key
4067def lex.kq.char(st: St, +cc: Char, stay: Lex) -> St:
4068  lex.kq.char.go(str.bad(Char.to_u32(cc), False{}), st, cc, stay)
4069
4070# a newline in a key is rejected
4071def lex.kq.nl(nl: Bool, st: St, +cc: Char, stay: Lex) -> St:
4072  match nl:
4073    case True{}:
4074      st.err(st, "newline in key")
4075    case False{}:
4076      lex.kq.char(st, cc, stay)
4077
4078# the escape state for a quoted key
4079def lex.esc.lex(stay: Lex) -> Lex:
4080  match stay:
4081    case LHeadQ{}:
4082      LHeadE{}
4083    case _:
4084      LKeyE{}
4085
4086# the escape returns to the quoted key
4087def lex.kesc(st: St, +stay: Lex) -> St:
4088  St{bad, rows, heads, here, parts, buf, _lex, _b, qkind, stack, hold, uni, left, arrtab,
4089    lead} = st
4090  St{bad, rows, heads, here, parts, buf, lex.esc.lex(stay), stay, qkind, stack, hold,
4091    uni, left, arrtab, lead}
4092
4093# only a basic key treats the backslash as an escape
4094def lex.kq.esc(basic: Bool, st: St, stay: Lex) -> St:
4095  match basic:
4096    case True{}:
4097      lex.kesc(st, stay)
4098    case False{}:
4099      st.push(st, '\\', stay)
4100
4101# an escape in a basic key
4102def lex.kq.bs(bs: Bool, basic: Bool, nl: Bool, st: St, cc: Char, stay: Lex) -> St:
4103  match bs:
4104    case True{}:
4105      lex.kq.esc(basic, st, stay)
4106    case False{}:
4107      lex.kq.nl(nl, st, cc, stay)
4108
4109# a quoted segment may be empty
4110def lex.qkey.end(st: St, next: Lex) -> St:
4111  seg.push(st, next)
4112
4113# the closer, an escape, or a character
4114def lex.kq.go(quote: Bool, bs: Bool, basic: Bool, nl: Bool, st: St, cc: Char, back: Lex, stay: Lex) -> St:
4115  match quote:
4116    case True{}:
4117      lex.qkey.end(st, back)
4118    case False{}:
4119      lex.kq.bs(bs, basic, nl, st, cc, stay)
4120
4121# a quoted key segment. back is the state after the closer; stay is this quote
4122def lex.kq(st: St, +cc: Char, back: Lex, stay: Lex) -> St:
4123  St{bad, rows, heads, here, parts, buf, lex, b, +qkind, stack, hold, uni, left, arrtab,
4124    lead} = st
4125  lex.kq.go(Char.is_eq(cc, q.char(qkind)), Char.is_eq(cc, '\\'), q.basic(qkind), ch.nl(cc),
4126    St{bad, rows, heads, here, parts, buf, lex, b, qkind, stack, hold, uni, left, arrtab,
4127      lead}, cc, back, stay)
4128
4129# the second ] of an array of tables
4130def lex.head.r.go(end: Bool, st: St) -> St:
4131  match end:
4132    case True{}:
4133      head.fin(st)
4134    case False{}:
4135      st.err(st, "invalid header")
4136
4137# the second ] of an array of tables
4138def lex.head.r(st: St, +cc: Char) -> St:
4139  lex.head.r.go(Char.is_eq(cc, ']'), st)
4140
4141# ] closes; anything else is a value
4142def lex.arr.end(end: Bool, st: St, cc: Char) -> St:
4143  match end:
4144    case True{}:
4145      arr.close(st)
4146    case False{}:
4147      st.hold(st.lex(st, LVal{}), LVal{}, cc)
4148
4149# a comment, the end, or a value
4150def lex.arr.hash(hash: Bool, end: Bool, st: St, cc: Char) -> St:
4151  match hash:
4152    case True{}:
4153      com.enter(st, LArr{})
4154    case False{}:
4155      lex.arr.end(end, st, cc)
4156
4157# a newline is whitespace in an array
4158def lex.arr.nl(nl: Bool, hash: Bool, end: Bool, st: St, cc: Char) -> St:
4159  match nl:
4160    case True{}:
4161      st
4162    case False{}:
4163      lex.arr.hash(hash, end, st, cc)
4164
4165# whitespace, a comment, the end, or a value
4166def lex.arr.go(sp: Bool, nl: Bool, hash: Bool, end: Bool, st: St, cc: Char) -> St:
4167  match sp:
4168    case True{}:
4169      st
4170    case False{}:
4171      lex.arr.nl(nl, hash, end, st, cc)
4172
4173# an array waits for a value or ]
4174def lex.arr(st: St, +cc: Char) -> St:
4175  lex.arr.go(ch.sp(cc), ch.nl(cc), Char.is_eq(cc, '#'), Char.is_eq(cc, ']'), st, cc)
4176
4177# ] closes the array
4178def lex.arrc.end(end: Bool, st: St) -> St:
4179  match end:
4180    case True{}:
4181      arr.close(st)
4182    case False{}:
4183      st.err(st, "invalid array")
4184
4185# a comma waits for another value
4186def lex.arrc.comma(comma: Bool, end: Bool, st: St) -> St:
4187  match comma:
4188    case True{}:
4189      st.lex(st, LArr{})
4190    case False{}:
4191      lex.arrc.end(end, st)
4192
4193# a comment, a comma, or the end
4194def lex.arrc.hash(hash: Bool, comma: Bool, end: Bool, st: St, _c: Char) -> St:
4195  match hash:
4196    case True{}:
4197      com.enter(st, LArrC{})
4198    case False{}:
4199      lex.arrc.comma(comma, end, st)
4200
4201# a newline is whitespace
4202def lex.arrc.nl(nl: Bool, hash: Bool, comma: Bool, end: Bool, st: St, cc: Char) -> St:
4203  match nl:
4204    case True{}:
4205      st
4206    case False{}:
4207      lex.arrc.hash(hash, comma, end, st, cc)
4208
4209# whitespace, a comment, a comma, or the end
4210def lex.arrc.go(sp: Bool, nl: Bool, hash: Bool, comma: Bool, end: Bool, st: St, cc: Char) -> St:
4211  match sp:
4212    case True{}:
4213      st
4214    case False{}:
4215      lex.arrc.nl(nl, hash, comma, end, st, cc)
4216
4217# after an array value: a comma, ], or whitespace
4218def lex.arrc(st: St, +cc: Char) -> St:
4219  lex.arrc.go(ch.sp(cc), ch.nl(cc), Char.is_eq(cc, '#'), Char.is_eq(cc, ','),
4220    Char.is_eq(cc, ']'), st, cc)
4221
4222# a new key starts with an empty segment list
4223def lex.key.clear(st: St) -> St:
4224  St{bad, rows, heads, here, _parts, _buf, _lex, back, qkind, stack, hold, uni, left,
4225    arrtab, lead} = st
4226  St{bad, rows, heads, here, [], [], LKey{}, back, qkind, stack, hold, uni, left,
4227    arrtab, lead}
4228
4229# a bare character opens the key
4230def lex.key.bare(ok: Bool, st: St, cc: Char) -> St:
4231  match ok:
4232    case True{}:
4233      st.push(st, cc, LKey{})
4234    case False{}:
4235      st.err(st, "invalid key")
4236
4237# a literal quote or a bare character opens the key
4238def lex.key.lit(lit: Bool, st: St, +cc: Char) -> St:
4239  match lit:
4240    case True{}:
4241      lex.qkey(st, QLit{}, LKeyQ{})
4242    case False{}:
4243      lex.key.bare(bare.at(cc), st, cc)
4244
4245# a quote or a bare character opens the key
4246def lex.key.q(qq: Bool, st: St, +cc: Char) -> St:
4247  match qq:
4248    case True{}:
4249      lex.qkey(st, QBasic{}, LKeyQ{})
4250    case False{}:
4251      lex.key.lit(Char.is_eq(cc, '\''), st, cc)
4252
4253# the first character of a key inside an inline table
4254def lex.key.start(st: St, +cc: Char) -> St:
4255  lex.key.q(Char.is_eq(cc, '"'), lex.key.clear(st), cc)
4256
4257# a newline is rejected; anything else is a key
4258def lex.inl.nl(nl: Bool, st: St, cc: Char) -> St:
4259  match nl:
4260    case True{}:
4261      st.err(st, "newline in inline table")
4262    case False{}:
4263      lex.key.start(st, cc)
4264
4265# } closes an empty inline table
4266def lex.inl.end(end: Bool, nl: Bool, st: St, cc: Char) -> St:
4267  match end:
4268    case True{}:
4269      inl.close(st)
4270    case False{}:
4271      lex.inl.nl(nl, st, cc)
4272
4273# a comment, the end, or a key
4274def lex.inl.hash(hash: Bool, end: Bool, nl: Bool, st: St, cc: Char) -> St:
4275  match hash:
4276    case True{}:
4277      com.enter(st, LInl{})
4278    case False{}:
4279      lex.inl.end(end, nl, st, cc)
4280
4281# whitespace, a comment, the end, or a key
4282def lex.inl.go(sp: Bool, hash: Bool, end: Bool, nl: Bool, st: St, cc: Char) -> St:
4283  match sp:
4284    case True{}:
4285      st
4286    case False{}:
4287      lex.inl.hash(hash, end, nl, st, cc)
4288
4289# an inline table waits for a key or }
4290def lex.inl(st: St, +cc: Char) -> St:
4291  lex.inl.go(ch.sp(cc), Char.is_eq(cc, '#'), Char.is_eq(cc, '}'), ch.nl(cc), st, cc)
4292
4293# a comment or a key
4294def lex.inlk.hash(hash: Bool, nl: Bool, st: St, cc: Char) -> St:
4295  match hash:
4296    case True{}:
4297      com.enter(st, LInlK{})
4298    case False{}:
4299      lex.inl.nl(nl, st, cc)
4300
4301# whitespace or a key
4302def lex.inlk.go(sp: Bool, hash: Bool, nl: Bool, st: St, cc: Char) -> St:
4303  match sp:
4304    case True{}:
4305      st
4306    case False{}:
4307      lex.inlk.hash(hash, nl, st, cc)
4308
4309# after a comma, a key is required
4310def lex.inlk(st: St, +cc: Char) -> St:
4311  lex.inlk.go(ch.sp(cc), Char.is_eq(cc, '#'), ch.nl(cc), st, cc)
4312
4313# a newline is rejected
4314def lex.inlc.nl(nl: Bool, st: St) -> St:
4315  match nl:
4316    case True{}:
4317      st.err(st, "newline in inline table")
4318    case False{}:
4319      st.err(st, "invalid inline table")
4320
4321# } closes the inline table
4322def lex.inlc.end(end: Bool, nl: Bool, st: St, _c: Char) -> St:
4323  match end:
4324    case True{}:
4325      inl.close(st)
4326    case False{}:
4327      lex.inlc.nl(nl, st)
4328
4329# a comma requires another key
4330def lex.inlc.comma(comma: Bool, end: Bool, nl: Bool, st: St, cc: Char) -> St:
4331  match comma:
4332    case True{}:
4333      st.lex(st, LInlK{})
4334    case False{}:
4335      lex.inlc.end(end, nl, st, cc)
4336
4337# a comment, a comma, or the end
4338def lex.inlc.hash(hash: Bool, comma: Bool, end: Bool, nl: Bool, st: St, cc: Char) -> St:
4339  match hash:
4340    case True{}:
4341      com.enter(st, LInlC{})
4342    case False{}:
4343      lex.inlc.comma(comma, end, nl, st, cc)
4344
4345# whitespace, a comment, a comma, or the end
4346def lex.inlc.go(sp: Bool, hash: Bool, comma: Bool, end: Bool, nl: Bool, st: St, cc: Char) -> St:
4347  match sp:
4348    case True{}:
4349      st
4350    case False{}:
4351      lex.inlc.hash(hash, comma, end, nl, st, cc)
4352
4353# after an inline value: a comma or }
4354def lex.inlc(st: St, +cc: Char) -> St:
4355  lex.inlc.go(ch.sp(cc), Char.is_eq(cc, '#'), Char.is_eq(cc, ','), Char.is_eq(cc, '}'),
4356    ch.nl(cc), st, cc)
4357
4358# the rest of a failed document is ignored
4359def lex.sink(st: St, _c: Char) -> St:
4360  st
4361
4362# anything else on the same line is another expression
4363def lex.line.nl(nl: Bool, st: St) -> St:
4364  match nl:
4365    case True{}:
4366      st.lex(st, LTop{})
4367    case False{}:
4368      st.err(st, "newline required")
4369
4370# a comment may follow an expression
4371def lex.line.hash(hash: Bool, nl: Bool, st: St, _c: Char) -> St:
4372  match hash:
4373    case True{}:
4374      com.enter(st, LLine{})
4375    case False{}:
4376      lex.line.nl(nl, st)
4377
4378# whitespace, a comment, or a newline after an expression
4379def lex.line.go(sp: Bool, hash: Bool, nl: Bool, st: St, cc: Char) -> St:
4380  match sp:
4381    case True{}:
4382      st
4383    case False{}:
4384      lex.line.hash(hash, nl, st, cc)
4385
4386# after an expression, the rest of the line is whitespace or a comment
4387def lex.line(st: St, +cc: Char) -> St:
4388  lex.line.go(ch.sp(cc), Char.is_eq(cc, '#'), ch.nl(cc), st, cc)
4389
4390# a newline in a dotted key is rejected
4391def lex.kdot.nl(nl: Bool, st: St) -> St:
4392  match nl:
4393    case True{}:
4394      st.err(st, "newline in key")
4395    case False{}:
4396      st.err(st, "invalid key")
4397
4398# a second dot is an empty segment
4399def lex.kdot.dot(dot: Bool, nl: Bool, st: St) -> St:
4400  match dot:
4401    case True{}:
4402      st.err(st, "empty key")
4403    case False{}:
4404      lex.kdot.nl(nl, st)
4405
4406# a bare character starts the segment
4407def lex.kdot.bare(bare: Bool, dot: Bool, nl: Bool, st: St, cc: Char) -> St:
4408  match bare:
4409    case True{}:
4410      st.push(st, cc, LKey{})
4411    case False{}:
4412      lex.kdot.dot(dot, nl, st)
4413
4414# a literal quote starts the segment
4415def lex.kdot.lit(lit: Bool, dot: Bool, nl: Bool, st: St, +cc: Char) -> St:
4416  match lit:
4417    case True{}:
4418      lex.qkey(st, QLit{}, LKeyQ{})
4419    case False{}:
4420      lex.kdot.bare(bare.at(cc), dot, nl, st, cc)
4421
4422# a quote starts the segment
4423def lex.kdot.q(qq: Bool, lit: Bool, dot: Bool, nl: Bool, st: St, cc: Char) -> St:
4424  match qq:
4425    case True{}:
4426      lex.qkey(st, QBasic{}, LKeyQ{})
4427    case False{}:
4428      lex.kdot.lit(lit, dot, nl, st, cc)
4429
4430# whitespace or the next segment
4431def lex.kdot.go(sp: Bool, qq: Bool, lit: Bool, dot: Bool, nl: Bool, st: St, cc: Char) -> St:
4432  match sp:
4433    case True{}:
4434      st
4435    case False{}:
4436      lex.kdot.q(qq, lit, dot, nl, st, cc)
4437
4438# after a dot, whitespace and then a segment
4439def lex.kdot(st: St, +cc: Char) -> St:
4440  lex.kdot.go(ch.sp(cc), Char.is_eq(cc, '"'), Char.is_eq(cc, '\''), Char.is_eq(cc, '.'),
4441    ch.nl(cc), st, cc)
4442
4443# a quoted header segment
4444def lex.hq(st: St, +cc: Char) -> St:
4445  lex.kq(st, cc, LHeadGap{}, LHeadQ{})
4446
4447# one character, dispatched on the lex state
4448def read.on(lex: Lex, st: St, cc: Char) -> St:
4449  match lex:
4450    case LTop{}:
4451      lex.top(st, cc)
4452    case LLine{}:
4453      lex.line(st, cc)
4454    case LKey{}:
4455      lex.key(st, cc)
4456    case LKeyDot{}:
4457      lex.kdot(st, cc)
4458    case LAfter{}:
4459      lex.after(st, cc)
4460    case LKeyQ{}:
4461      lex.kq(st, cc, LAfter{}, LKeyQ{})
4462    case LKeyE{}:
4463      esc.use(esc.of(cc), st, LKeyQ{})
4464    case LKeyU{}:
4465      uni.step(st, cc, LKeyQ{})
4466    case LVal{}:
4467      lex.val(st, cc)
4468    case LQ{}:
4469      lex.q(st, cc)
4470    case LQ2{}:
4471      lex.q2(st, cc)
4472    case LStr{}:
4473      lex.str(st, cc)
4474    case LEsc{}:
4475      lex.esc(st, cc)
4476    case LUni{}:
4477      uni.step(st, cc, LStr{})
4478    case LFold{}:
4479      lex.fold(st, cc)
4480    case LMq{}:
4481      lex.mq.step(st, cc)
4482    case LMqEnd{}:
4483      lex.mq.end(st, cc)
4484    case LTrim{}:
4485      lex.trim(st, cc)
4486    case LTrim2{}:
4487      lex.trim2(st, cc)
4488    case LBare{}:
4489      lex.bare(st, cc)
4490    case LDate{}:
4491      lex.date(st, cc)
4492    case LCom{}:
4493      lex.com(st, cc)
4494    case LHead{}:
4495      lex.head(st, cc)
4496    case LHeadGap{}:
4497      lex.head.gap.step(st, cc)
4498    case LHeadDot{}:
4499      lex.head.dot.step(st, cc)
4500    case LHeadQ{}:
4501      lex.hq(st, cc)
4502    case LHeadE{}:
4503      esc.use(esc.of(cc), st, LHeadQ{})
4504    case LHeadU{}:
4505      uni.step(st, cc, LHeadQ{})
4506    case LHeadR{}:
4507      lex.head.r(st, cc)
4508    case LArr{}:
4509      lex.arr(st, cc)
4510    case LArrC{}:
4511      lex.arrc(st, cc)
4512    case LInl{}:
4513      lex.inl(st, cc)
4514    case LInlK{}:
4515      lex.inlk(st, cc)
4516    case LInlC{}:
4517      lex.inlc(st, cc)
4518    case LSink{}:
4519      lex.sink(st, cc)
4520
4521# read the lex state and step
4522def read.on.st(st: St, cc: Char) -> St:
4523  St{bad, rows, heads, here, parts, buf, +lex, back, qkind, stack, hold, uni, left,
4524    arrtab, lead} = st
4525  read.on(lex, St{bad, rows, heads, here, parts, buf, lex, back, qkind, stack, hold, uni,
4526    left, arrtab, lead}, cc)
4527
4528# one character, after a leading mark has been decided
4529def read.step.bom(bom: Bool, st: St, cc: Char) -> St:
4530  match bom:
4531    case True{}:
4532      st
4533    case False{}:
4534      read.on.st(st, cc)
4535
4536# the first character may be a byte-order mark
4537def read.step.lead(lead: Bool, st: St, +cc: Char) -> St:
4538  match lead:
4539    case False{}:
4540      read.on.st(st, cc)
4541    case True{}:
4542      read.step.bom(U32.is_eq(Char.to_u32(cc), 65279), st, cc)
4543
4544# one character of the document
4545def read.step(st: St, +cc: Char) -> St:
4546  St{bad, rows, heads, here, parts, buf, lex, back, qkind, stack, hold, uni, left,
4547    arrtab, +lead} = st
4548  read.step.lead(lead, St{bad, rows, heads, here, parts, buf, lex, back, qkind, stack,
4549    hold, uni, left, arrtab, False{}}, cc)
4550
4551# a held character is read once
4552def read.once.hold(hold: Hold, st: St) -> St:
4553  match hold:
4554    case HNone{}:
4555      st
4556    case HChar{c}:
4557      read.step(st, c)
4558
4559# a held character is read once
4560def read.once2(st: St) -> St:
4561  St{bad, rows, heads, here, parts, buf, lex, back, qkind, stack, hold, uni, left,
4562    arrtab, lead} = st
4563  read.once.hold(hold, St{bad, rows, heads, here, parts, buf, lex, back, qkind, stack,
4564    HNone{}, uni, left, arrtab, lead})
4565
4566# one new character, then a held character if the step asked for one
4567def read.once(st: St, cc: Char) -> St:
4568  read.once2(read.step(st, cc))
4569
4570# a bare token, an empty string or a closed multiline string still open at the end
4571def read.fin.lex(lex: Lex, st: St) -> St:
4572  match lex:
4573    case LBare{}:
4574      bare.finish(st, ' ', False{})
4575    case LDate{}:
4576      bare.finish(st, ' ', False{})
4577    case LQ2{}:
4578      val.take(st, VStr{""})
4579    case LMqEnd{}:
4580      str.fin(st)
4581    case _:
4582      st
4583
4584# a bare token still open at the end is closed
4585def read.fin.close(st: St) -> St:
4586  St{bad, rows, heads, here, parts, buf, +lex, back, qkind, stack, hold, uni, left,
4587    arrtab, lead} = st
4588  read.fin.lex(lex, St{bad, rows, heads, here, parts, buf, lex, back, qkind, stack, hold,
4589    uni, left, arrtab, lead})
4590
4591# builder order is newest-first. a kept list is reversed back; a builder list is not
4592def rows.seal.fin(rev: Bool, acc: List<&2, Val>) -> List<&2, Val>:
4593  match rev:
4594    case True{}:
4595      acc
4596    case False{}:
4597      List.reverse(&2, Val, acc)
4598
4599# put every table back in insertion order. one pass, no append along the way
4600def rows.seal.go(vs: List<&2, Val>, rev: Bool, acc: List<&2, Val>) -> List<&2, Val>:
4601  match vs:
4602    case []:
4603      rows.seal.fin(rev, acc)
4604    case VPair{name, VHead{path, rows}} <> t:
4605      rows.seal.go(t, rev, VPair{name, VHead{path, rows.seal.go(rows, True{}, [])}} <> acc)
4606    case VPair{name, VAots{path, elems}} <> t:
4607      rows.seal.go(t, rev,
4608        VPair{name, VAots{path, rows.seal.go(elems, True{}, [])}} <> acc)
4609    case VPair{name, VInl{rows}} <> t:
4610      rows.seal.go(t, rev, VPair{name, VInl{rows.seal.go(rows, True{}, [])}} <> acc)
4611    case VPair{name, VArr{items}} <> t:
4612      rows.seal.go(t, rev, VPair{name, VArr{rows.seal.go(items, False{}, [])}} <> acc)
4613    case VAot{path, rows} <> t:
4614      rows.seal.go(t, rev, VAot{path, rows.seal.go(rows, True{}, [])} <> acc)
4615    case VAots{path, elems} <> t:
4616      rows.seal.go(t, rev, VAots{path, rows.seal.go(elems, True{}, [])} <> acc)
4617    case VInl{rows} <> t:
4618      rows.seal.go(t, rev, VInl{rows.seal.go(rows, True{}, [])} <> acc)
4619    case VArr{items} <> t:
4620      rows.seal.go(t, rev, VArr{rows.seal.go(items, False{}, [])} <> acc)
4621    case h <> t:
4622      rows.seal.go(t, rev, h <> acc)
4623
4624# insertion order of a table built newest-first
4625def rows.seal(rows: List<&2, Val>) -> List<&2, Val>:
4626  rows.seal.go(rows, True{}, [])
4627
4628# an open frame at the end is an error
4629def read.fin.stack(stack: List<&2, Ctx>, bad: String, rows: List<&2, Val>) -> Doc:
4630  match stack:
4631    case []:
4632      Doc{bad, rows.seal(rows)}
4633    case _ <> _:
4634      Doc{err.first(bad, "unclosed"), rows.seal(rows)}
4635
4636# the document from a finished state
4637def read.fin.doc(st: St) -> Doc:
4638  St{bad, rows, _heads, _here, _parts, _buf, _lex, _back, _q, stack, _hold, _uni, _left,
4639    _arr, _lead} = st
4640  read.fin.stack(stack, bad, rows)
4641
4642# an unfinished document keeps the first error
4643def read.fin.unclosed(st: St) -> Doc:
4644  St{bad, rows, _heads, _here, _parts, _buf, _lex, _back, _q, _stack, _hold, _uni,
4645    _left, _arr, _lead} = st
4646  Doc{err.first(bad, "unclosed"), rows.seal(rows)}
4647
4648# a comment may end a document only where the line already could
4649def read.fin.end(lex: Lex) -> Bool:
4650  match lex:
4651    case LTop{}:
4652      True{}
4653    case LLine{}:
4654      True{}
4655    case LSink{}:
4656      True{}
4657    case _:
4658      False{}
4659
4660# a comment at the end is unfinished when it interrupted a key or a value
4661def read.fin.com(ok: Bool, st: St) -> Doc:
4662  match ok:
4663    case True{}:
4664      read.fin.doc(st)
4665    case False{}:
4666      read.fin.unclosed(st)
4667
4668# a comment at the end is unfinished when it interrupted a key or a value
4669def read.fin.com.st(st: St) -> Doc:
4670  St{bad, rows, heads, here, parts, buf, lex, +back, qkind, stack, hold, uni, left,
4671    arrtab, lead} = st
4672  read.fin.com(read.fin.end(back), St{bad, rows, heads, here, parts, buf, lex, back,
4673    qkind, stack, hold, uni, left, arrtab, lead})
4674
4675# any other state is unfinished
4676def read.fin.bad(st: St) -> Doc:
4677  read.fin.unclosed(st)
4678
4679# the states that may end a document
4680def read.fin.ok(lex: Lex, st: St) -> Doc:
4681  match lex:
4682    case LTop{}:
4683      read.fin.doc(st)
4684    case LLine{}:
4685      read.fin.doc(st)
4686    case LCom{}:
4687      read.fin.com.st(st)
4688    case LSink{}:
4689      read.fin.doc(st)
4690    case _:
4691      read.fin.bad(st)
4692
4693# the document once the last token has been closed
4694def read.fin2(st: St) -> Doc:
4695  St{bad, rows, heads, here, parts, buf, +lex, back, qkind, stack, hold, uni, left,
4696    arrtab, lead} = st
4697  read.fin.ok(lex, St{bad, rows, heads, here, parts, buf, lex, back, qkind, stack, hold,
4698    uni, left, arrtab, lead})
4699
4700# the document at the end of the text
4701def read.fin(st: St) -> Doc:
4702  read.fin2(read.fin.close(st))
4703
4704# one scanner state, with the hold cleared and the first character already taken
4705def read.fast.st(
4706  bad: String,
4707  rows: List<&2, Val>,
4708  heads: List<&2, String>,
4709  here: List<&2, String>,
4710  parts: List<&2, String>,
4711  buf: List<&2, Char>,
4712  lex: Lex,
4713  back: Lex,
4714  qkind: QKind,
4715  stack: List<&2, Ctx>,
4716  uni: U32,
4717  left: U32,
4718  arrtab: Bool
4719) -> St:
4720  St{bad, rows, heads, here, parts, buf, lex, back, qkind, stack, HNone{}, uni, left,
4721    arrtab, False{}}
4722
4723# the character joins the buffer and the state stays
4724def read.fast.cons(
4725  bad: String,
4726  rows: List<&2, Val>,
4727  heads: List<&2, String>,
4728  here: List<&2, String>,
4729  parts: List<&2, String>,
4730  buf: List<&2, Char>,
4731  lex: Lex,
4732  back: Lex,
4733  qkind: QKind,
4734  stack: List<&2, Ctx>,
4735  uni: U32,
4736  left: U32,
4737  arrtab: Bool,
4738  +cc: Char
4739) -> St:
4740  read.fast.st(bad, rows, heads, here, parts, cc <> buf, lex, back, qkind, stack, uni,
4741    left, arrtab)
4742
4743# a quote, an escape, or a state this step does not special-case
4744def read.fast.old(
4745  bad: String,
4746  rows: List<&2, Val>,
4747  heads: List<&2, String>,
4748  here: List<&2, String>,
4749  parts: List<&2, String>,
4750  buf: List<&2, Char>,
4751  lex: Lex,
4752  back: Lex,
4753  qkind: QKind,
4754  stack: List<&2, Ctx>,
4755  uni: U32,
4756  left: U32,
4757  arrtab: Bool,
4758  +cc: Char
4759) -> St:
4760  read.once2(read.on.st(read.fast.st(bad, rows, heads, here, parts, buf, lex, back,
4761    qkind, stack, uni, left, arrtab), cc))
4762
4763# a forbidden control takes the full step; a plain character is stored
4764def read.fast.push(
4765  ctrl: Bool,
4766  bad: String,
4767  rows: List<&2, Val>,
4768  heads: List<&2, String>,
4769  here: List<&2, String>,
4770  parts: List<&2, String>,
4771  buf: List<&2, Char>,
4772  lex: Lex,
4773  back: Lex,
4774  qkind: QKind,
4775  stack: List<&2, Ctx>,
4776  uni: U32,
4777  left: U32,
4778  arrtab: Bool,
4779  +cc: Char
4780) -> St:
4781  match ctrl:
4782    case False{}:
4783      read.fast.cons(bad, rows, heads, here, parts, buf, lex, back, qkind, stack, uni,
4784        left, arrtab, cc)
4785    case True{}:
4786      read.fast.old(bad, rows, heads, here, parts, buf, lex, back, qkind, stack, uni,
4787        left, arrtab, cc)
4788
4789# a basic-string character that is not a quote
4790def read.fast.basic.bs(
4791  bs: Bool,
4792  bad: String,
4793  rows: List<&2, Val>,
4794  heads: List<&2, String>,
4795  here: List<&2, String>,
4796  parts: List<&2, String>,
4797  buf: List<&2, Char>,
4798  back: Lex,
4799  stack: List<&2, Ctx>,
4800  uni: U32,
4801  left: U32,
4802  arrtab: Bool,
4803  +cc: Char
4804) -> St:
4805  match bs:
4806    case True{}:
4807      read.fast.old(bad, rows, heads, here, parts, buf, LStr{}, back, QBasic{}, stack,
4808        uni, left, arrtab, cc)
4809    case False{}:
4810      read.fast.push(str.bad(Char.to_u32(cc), False{}), bad, rows, heads, here, parts,
4811        buf, LStr{}, back, QBasic{}, stack, uni, left, arrtab, cc)
4812
4813# a basic-string character: a quote or a backslash still takes the full step
4814def read.fast.basic(
4815  quote: Bool,
4816  bs: Bool,
4817  bad: String,
4818  rows: List<&2, Val>,
4819  heads: List<&2, String>,
4820  here: List<&2, String>,
4821  parts: List<&2, String>,
4822  buf: List<&2, Char>,
4823  back: Lex,
4824  stack: List<&2, Ctx>,
4825  uni: U32,
4826  left: U32,
4827  arrtab: Bool,
4828  +cc: Char
4829) -> St:
4830  match quote:
4831    case True{}:
4832      read.fast.old(bad, rows, heads, here, parts, buf, LStr{}, back, QBasic{}, stack,
4833        uni, left, arrtab, cc)
4834    case False{}:
4835      read.fast.basic.bs(bs, bad, rows, heads, here, parts, buf, back, stack, uni, left,
4836        arrtab, cc)
4837
4838# a literal-string character: a quote still takes the full step
4839def read.fast.lit(
4840  quote: Bool,
4841  bad: String,
4842  rows: List<&2, Val>,
4843  heads: List<&2, String>,
4844  here: List<&2, String>,
4845  parts: List<&2, String>,
4846  buf: List<&2, Char>,
4847  back: Lex,
4848  stack: List<&2, Ctx>,
4849  uni: U32,
4850  left: U32,
4851  arrtab: Bool,
4852  +cc: Char
4853) -> St:
4854  match quote:
4855    case True{}:
4856      read.fast.old(bad, rows, heads, here, parts, buf, LStr{}, back, QLit{}, stack,
4857        uni, left, arrtab, cc)
4858    case False{}:
4859      read.fast.push(str.bad(Char.to_u32(cc), False{}), bad, rows, heads, here, parts,
4860        buf, LStr{}, back, QLit{}, stack, uni, left, arrtab, cc)
4861
4862# one character of a string
4863def read.fast.str(
4864  qkind: QKind,
4865  bad: String,
4866  rows: List<&2, Val>,
4867  heads: List<&2, String>,
4868  here: List<&2, String>,
4869  parts: List<&2, String>,
4870  buf: List<&2, Char>,
4871  back: Lex,
4872  stack: List<&2, Ctx>,
4873  uni: U32,
4874  left: U32,
4875  arrtab: Bool,
4876  +cc: Char
4877) -> St:
4878  match qkind:
4879    case QBasic{}:
4880      read.fast.basic(Char.is_eq(cc, '"'), Char.is_eq(cc, '\\'), bad, rows, heads, here,
4881        parts, buf, back, stack, uni, left, arrtab, cc)
4882    case QLit{}:
4883      read.fast.lit(Char.is_eq(cc, '\''), bad, rows, heads, here, parts, buf, back,
4884        stack, uni, left, arrtab, cc)
4885    case other:
4886      read.fast.old(bad, rows, heads, here, parts, buf, LStr{}, back, other, stack,
4887        uni, left, arrtab, cc)
4888
4889# one character of a bare key
4890def read.fast.key(
4891  ok: Bool,
4892  bad: String,
4893  rows: List<&2, Val>,
4894  heads: List<&2, String>,
4895  here: List<&2, String>,
4896  parts: List<&2, String>,
4897  buf: List<&2, Char>,
4898  back: Lex,
4899  qkind: QKind,
4900  stack: List<&2, Ctx>,
4901  uni: U32,
4902  left: U32,
4903  arrtab: Bool,
4904  +cc: Char
4905) -> St:
4906  match ok:
4907    case True{}:
4908      read.fast.cons(bad, rows, heads, here, parts, buf, LKey{}, back, qkind, stack,
4909        uni, left, arrtab, cc)
4910    case False{}:
4911      read.fast.old(bad, rows, heads, here, parts, buf, LKey{}, back, qkind, stack,
4912        uni, left, arrtab, cc)
4913
4914# one character of a bare value
4915def read.fast.bare(
4916  delim: Bool,
4917  bad: String,
4918  rows: List<&2, Val>,
4919  heads: List<&2, String>,
4920  here: List<&2, String>,
4921  parts: List<&2, String>,
4922  buf: List<&2, Char>,
4923  back: Lex,
4924  qkind: QKind,
4925  stack: List<&2, Ctx>,
4926  uni: U32,
4927  left: U32,
4928  arrtab: Bool,
4929  +cc: Char
4930) -> St:
4931  match delim:
4932    case False{}:
4933      read.fast.cons(bad, rows, heads, here, parts, buf, LBare{}, back, qkind, stack,
4934        uni, left, arrtab, cc)
4935    case True{}:
4936      read.fast.old(bad, rows, heads, here, parts, buf, LBare{}, back, qkind, stack,
4937        uni, left, arrtab, cc)
4938
4939# one character of a comment. a control, a newline among them, takes the full step
4940def read.fast.com(
4941  ctl: Bool,
4942  bad: String,
4943  rows: List<&2, Val>,
4944  heads: List<&2, String>,
4945  here: List<&2, String>,
4946  parts: List<&2, String>,
4947  buf: List<&2, Char>,
4948  back: Lex,
4949  qkind: QKind,
4950  stack: List<&2, Ctx>,
4951  uni: U32,
4952  left: U32,
4953  arrtab: Bool,
4954  +cc: Char
4955) -> St:
4956  match ctl:
4957    case False{}:
4958      read.fast.st(bad, rows, heads, here, parts, buf, LCom{}, back, qkind, stack,
4959        uni, left, arrtab)
4960    case True{}:
4961      read.fast.old(bad, rows, heads, here, parts, buf, LCom{}, back, qkind, stack,
4962        uni, left, arrtab, cc)
4963
4964# whitespace that this state ignores. keep is true when a newline is also ignored
4965def read.fast.ws.keep(
4966  keep: Bool,
4967  bad: String,
4968  rows: List<&2, Val>,
4969  heads: List<&2, String>,
4970  here: List<&2, String>,
4971  parts: List<&2, String>,
4972  buf: List<&2, Char>,
4973  lex: Lex,
4974  back: Lex,
4975  qkind: QKind,
4976  stack: List<&2, Ctx>,
4977  uni: U32,
4978  left: U32,
4979  arrtab: Bool,
4980  +cc: Char
4981) -> St:
4982  match keep:
4983    case True{}:
4984      read.fast.st(bad, rows, heads, here, parts, buf, lex, back, qkind, stack, uni,
4985        left, arrtab)
4986    case False{}:
4987      read.fast.old(bad, rows, heads, here, parts, buf, lex, back, qkind, stack, uni,
4988        left, arrtab, cc)
4989
4990# a newline this state ignores, or the full step
4991def read.fast.ws.nl(
4992  nl: Bool,
4993  keep: Bool,
4994  bad: String,
4995  rows: List<&2, Val>,
4996  heads: List<&2, String>,
4997  here: List<&2, String>,
4998  parts: List<&2, String>,
4999  buf: List<&2, Char>,
5000  lex: Lex,
5001  back: Lex,
5002  qkind: QKind,
5003  stack: List<&2, Ctx>,
5004  uni: U32,
5005  left: U32,
5006  arrtab: Bool,
5007  +cc: Char
5008) -> St:
5009  match nl:
5010    case True{}:
5011      read.fast.ws.keep(keep, bad, rows, heads, here, parts, buf, lex, back, qkind,
5012        stack, uni, left, arrtab, cc)
5013    case False{}:
5014      read.fast.old(bad, rows, heads, here, parts, buf, lex, back, qkind, stack, uni,
5015        left, arrtab, cc)
5016
5017# space stays in this state; a newline stays only when keep is set
5018def read.fast.ws(
5019  sp: Bool,
5020  nl: Bool,
5021  keep: Bool,
5022  bad: String,
5023  rows: List<&2, Val>,
5024  heads: List<&2, String>,
5025  here: List<&2, String>,
5026  parts: List<&2, String>,
5027  buf: List<&2, Char>,
5028  lex: Lex,
5029  back: Lex,
5030  qkind: QKind,
5031  stack: List<&2, Ctx>,
5032  uni: U32,
5033  left: U32,
5034  arrtab: Bool,
5035  +cc: Char
5036) -> St:
5037  match sp:
5038    case True{}:
5039      read.fast.st(bad, rows, heads, here, parts, buf, lex, back, qkind, stack, uni,
5040        left, arrtab)
5041    case False{}:
5042      read.fast.ws.nl(nl, keep, bad, rows, heads, here, parts, buf, lex, back, qkind,
5043        stack, uni, left, arrtab, cc)
5044
5045# the states whose plain characters do not change the scanner
5046def read.fast.lex(
5047  lex: Lex,
5048  qkind: QKind,
5049  bad: String,
5050  rows: List<&2, Val>,
5051  heads: List<&2, String>,
5052  here: List<&2, String>,
5053  parts: List<&2, String>,
5054  buf: List<&2, Char>,
5055  back: Lex,
5056  stack: List<&2, Ctx>,
5057  uni: U32,
5058  left: U32,
5059  arrtab: Bool,
5060  +cc: Char
5061) -> St:
5062  match lex:
5063    case LStr{}:
5064      read.fast.str(qkind, bad, rows, heads, here, parts, buf, back, stack, uni, left,
5065        arrtab, cc)
5066    case LKey{}:
5067      read.fast.key(bare.at(cc), bad, rows, heads, here, parts, buf, back, qkind,
5068        stack, uni, left, arrtab, cc)
5069    case LBare{}:
5070      read.fast.bare(ch.delim(cc), bad, rows, heads, here, parts, buf, back, qkind,
5071        stack, uni, left, arrtab, cc)
5072    case LCom{}:
5073      read.fast.com(ch.ctl(cc), bad, rows, heads, here, parts, buf, back, qkind, stack,
5074        uni, left, arrtab, cc)
5075    case LTop{}:
5076      read.fast.ws(ch.sp(cc), ch.nl(cc), True{}, bad, rows, heads, here, parts, buf,
5077        LTop{}, back, qkind, stack, uni, left, arrtab, cc)
5078    case LArr{}:
5079      read.fast.ws(ch.sp(cc), ch.nl(cc), True{}, bad, rows, heads, here, parts, buf,
5080        LArr{}, back, qkind, stack, uni, left, arrtab, cc)
5081    case LArrC{}:
5082      read.fast.ws(ch.sp(cc), ch.nl(cc), True{}, bad, rows, heads, here, parts, buf,
5083        LArrC{}, back, qkind, stack, uni, left, arrtab, cc)
5084    case LLine{}:
5085      read.fast.ws(ch.sp(cc), ch.nl(cc), False{}, bad, rows, heads, here, parts, buf,
5086        LLine{}, back, qkind, stack, uni, left, arrtab, cc)
5087    case LVal{}:
5088      read.fast.ws(ch.sp(cc), ch.nl(cc), False{}, bad, rows, heads, here, parts, buf,
5089        LVal{}, back, qkind, stack, uni, left, arrtab, cc)
5090    case LAfter{}:
5091      read.fast.ws(ch.sp(cc), ch.nl(cc), False{}, bad, rows, heads, here, parts, buf,
5092        LAfter{}, back, qkind, stack, uni, left, arrtab, cc)
5093    case LInl{}:
5094      read.fast.ws(ch.sp(cc), ch.nl(cc), False{}, bad, rows, heads, here, parts, buf,
5095        LInl{}, back, qkind, stack, uni, left, arrtab, cc)
5096    case LInlK{}:
5097      read.fast.ws(ch.sp(cc), ch.nl(cc), False{}, bad, rows, heads, here, parts, buf,
5098        LInlK{}, back, qkind, stack, uni, left, arrtab, cc)
5099    case LInlC{}:
5100      read.fast.ws(ch.sp(cc), ch.nl(cc), False{}, bad, rows, heads, here, parts, buf,
5101        LInlC{}, back, qkind, stack, uni, left, arrtab, cc)
5102    case other:
5103      read.fast.old(bad, rows, heads, here, parts, buf, other, back, qkind, stack,
5104        uni, left, arrtab, cc)
5105
5106# the first character may be a byte-order mark; later characters take the fast step
5107def read.fast.lead(
5108  lead: Bool,
5109  lex: Lex,
5110  qkind: QKind,
5111  bad: String,
5112  rows: List<&2, Val>,
5113  heads: List<&2, String>,
5114  here: List<&2, String>,
5115  parts: List<&2, String>,
5116  buf: List<&2, Char>,
5117  back: Lex,
5118  stack: List<&2, Ctx>,
5119  uni: U32,
5120  left: U32,
5121  arrtab: Bool,
5122  +cc: Char
5123) -> St:
5124  match lead:
5125    case True{}:
5126      read.once(St{bad, rows, heads, here, parts, buf, lex, back, qkind, stack,
5127        HNone{}, uni, left, arrtab, True{}}, cc)
5128    case False{}:
5129      read.fast.lex(lex, qkind, bad, rows, heads, here, parts, buf, back, stack, uni,
5130        left, arrtab, cc)
5131
5132# a held character is still read by the full step, then this character
5133def read.fast.hold(
5134  hold: Hold,
5135  lead: Bool,
5136  lex: Lex,
5137  qkind: QKind,
5138  bad: String,
5139  rows: List<&2, Val>,
5140  heads: List<&2, String>,
5141  here: List<&2, String>,
5142  parts: List<&2, String>,
5143  buf: List<&2, Char>,
5144  back: Lex,
5145  stack: List<&2, Ctx>,
5146  uni: U32,
5147  left: U32,
5148  arrtab: Bool,
5149  +cc: Char
5150) -> St:
5151  match hold:
5152    case HChar{held}:
5153      read.once(read.once2(St{bad, rows, heads, here, parts, buf, lex, back, qkind,
5154        stack, HChar{held}, uni, left, arrtab, lead}), cc)
5155    case HNone{}:
5156      read.fast.lead(lead, lex, qkind, bad, rows, heads, here, parts, buf, back,
5157        stack, uni, left, arrtab, cc)
5158
5159# one source character
5160def read.fast(st: St, +cc: Char) -> St:
5161  St{bad, rows, heads, here, parts, buf, lex, back, qkind, stack, hold, uni, left,
5162    arrtab, lead} = st
5163  read.fast.hold(hold, lead, lex, qkind, bad, rows, heads, here, parts, buf, back,
5164    stack, uni, left, arrtab, cc)
5165
5166# where the next source character is read. MEat is the body of a plain string, and
5167# MCr follows a carriage return, which only a line feed may follow
5168type Mode is Data:
5169  MScan{st: St}
5170  MCr{st: St}
5171  MOpen{basic: Bool, q: Char, qkind: QKind, bad: String, rows: List<&2, Val>,
5172    heads: List<&2, String>, here: List<&2, String>, parts: List<&2, String>,
5173    buf: List<&2, Char>, back: Lex, stack: List<&2, Ctx>, uni: U32, left: U32,
5174    arrtab: Bool}
5175  MEat{basic: Bool, q: Char, cut: String, n: U32, bad: String, rows: List<&2, Val>,
5176    heads: List<&2, String>, here: List<&2, String>, parts: List<&2, String>,
5177    qkind: QKind, stack: List<&2, Ctx>, uni: U32, left: U32, arrtab: Bool}
5178
5179# an unclosed string
5180def eat.fin(
5181  _n: U32,
5182  _u: Unit,
5183  bad: String,
5184  rows: List<&2, Val>,
5185  heads: List<&2, String>,
5186  here: List<&2, String>,
5187  parts: List<&2, String>,
5188  qkind: QKind,
5189  stack: List<&2, Ctx>,
5190  uni: U32,
5191  left: U32,
5192  arrtab: Bool
5193) -> Doc:
5194  read.fin.unclosed(St{bad, rows, heads, here, parts, [], LStr{}, LStr{}, qkind, stack,
5195    HNone{}, uni, left, arrtab, False{}})
5196
5197# a plain string is the first n characters of the source suffix
5198def eat.close(
5199  cut: String,
5200  nn: U32,
5201  bad: String,
5202  rows: List<&2, Val>,
5203  heads: List<&2, String>,
5204  here: List<&2, String>,
5205  parts: List<&2, String>,
5206  qkind: QKind,
5207  stack: List<&2, Ctx>,
5208  uni: U32,
5209  left: U32,
5210  arrtab: Bool
5211) -> St:
5212  val.take(St{bad, rows, heads, here, parts, [], LStr{}, LStr{}, qkind, stack, HNone{},
5213    uni, left, arrtab, False{}}, VSpan{cut, nn})
5214
5215# an escape or a forbidden control copies the span so far and continues
5216def eat.copy(
5217  +cc: Char,
5218  cut: String,
5219  +nn: U32,
5220  bad: String,
5221  rows: List<&2, Val>,
5222  heads: List<&2, String>,
5223  here: List<&2, String>,
5224  parts: List<&2, String>,
5225  qkind: QKind,
5226  stack: List<&2, Ctx>,
5227  uni: U32,
5228  left: U32,
5229  arrtab: Bool
5230) -> Mode:
5231  MScan{lex.str(St{bad, rows, heads, here, parts, span.rev(cut, nn, U32.is_zero(nn), []),
5232    LStr{}, LStr{}, qkind, stack, HNone{}, uni, left, arrtab, False{}}, cc)}
5233
5234# a forbidden control leaves the span; a plain character extends it
5235def eat.ctrl(
5236  badc: Bool,
5237  basic: Bool,
5238  qq: Char,
5239  cc: Char,
5240  cut: String,
5241  nn: U32,
5242  bad: String,
5243  rows: List<&2, Val>,
5244  heads: List<&2, String>,
5245  here: List<&2, String>,
5246  parts: List<&2, String>,
5247  qkind: QKind,
5248  stack: List<&2, Ctx>,
5249  uni: U32,
5250  left: U32,
5251  arrtab: Bool
5252) -> Mode:
5253  match badc:
5254    case True{}:
5255      eat.copy(cc, cut, nn, bad, rows, heads, here, parts, qkind, stack, uni, left,
5256        arrtab)
5257    case False{}:
5258      MEat{basic, qq, cut, U32.add(nn, 1), bad, rows, heads, here, parts, qkind, stack,
5259        uni, left, arrtab}
5260
5261# a backslash in a basic string leaves the span
5262def eat.bs(
5263  bs: Bool,
5264  basic: Bool,
5265  qq: Char,
5266  +cc: Char,
5267  cut: String,
5268  nn: U32,
5269  bad: String,
5270  rows: List<&2, Val>,
5271  heads: List<&2, String>,
5272  here: List<&2, String>,
5273  parts: List<&2, String>,
5274  qkind: QKind,
5275  stack: List<&2, Ctx>,
5276  uni: U32,
5277  left: U32,
5278  arrtab: Bool
5279) -> Mode:
5280  match bs:
5281    case True{}:
5282      eat.copy(cc, cut, nn, bad, rows, heads, here, parts, qkind, stack, uni, left,
5283        arrtab)
5284    case False{}:
5285      eat.ctrl(str.bad(Char.to_u32(cc), False{}), basic, qq, cc, cut, nn, bad, rows, heads,
5286        here, parts, qkind, stack, uni, left, arrtab)
5287
5288# a literal string keeps a backslash; a basic string does not
5289def eat.basic(
5290  basic: Bool,
5291  qq: Char,
5292  +cc: Char,
5293  cut: String,
5294  nn: U32,
5295  bad: String,
5296  rows: List<&2, Val>,
5297  heads: List<&2, String>,
5298  here: List<&2, String>,
5299  parts: List<&2, String>,
5300  qkind: QKind,
5301  stack: List<&2, Ctx>,
5302  uni: U32,
5303  left: U32,
5304  arrtab: Bool
5305) -> Mode:
5306  match basic:
5307    case False{}:
5308      eat.ctrl(str.bad(Char.to_u32(cc), False{}), False{}, qq, cc, cut, nn, bad, rows, heads,
5309        here, parts, qkind, stack, uni, left, arrtab)
5310    case True{}:
5311      eat.bs(Char.is_eq(cc, '\\'), True{}, qq, cc, cut, nn, bad, rows, heads, here, parts,
5312        qkind, stack, uni, left, arrtab)
5313
5314# a quote closes the span
5315def eat.feed.q(
5316  quote: Bool,
5317  basic: Bool,
5318  qq: Char,
5319  cc: Char,
5320  cut: String,
5321  nn: U32,
5322  bad: String,
5323  rows: List<&2, Val>,
5324  heads: List<&2, String>,
5325  here: List<&2, String>,
5326  parts: List<&2, String>,
5327  qkind: QKind,
5328  stack: List<&2, Ctx>,
5329  uni: U32,
5330  left: U32,
5331  arrtab: Bool
5332) -> Mode:
5333  match quote:
5334    case True{}:
5335      MScan{eat.close(cut, nn, bad, rows, heads, here, parts, qkind, stack, uni, left,
5336        arrtab)}
5337    case False{}:
5338      eat.basic(basic, qq, cc, cut, nn, bad, rows, heads, here, parts, qkind, stack, uni,
5339        left, arrtab)
5340
5341# one character of a plain string. cut is the suffix where the text began
5342def eat.feed(
5343  basic: Bool,
5344  +qq: Char,
5345  cut: String,
5346  nn: U32,
5347  bad: String,
5348  rows: List<&2, Val>,
5349  heads: List<&2, String>,
5350  here: List<&2, String>,
5351  parts: List<&2, String>,
5352  qkind: QKind,
5353  stack: List<&2, Ctx>,
5354  uni: U32,
5355  left: U32,
5356  arrtab: Bool,
5357  +cc: Char
5358) -> Mode:
5359  eat.feed.q(Char.is_eq(cc, qq), basic, qq, cc, cut, nn, bad, rows, heads, here, parts,
5360    qkind, stack, uni, left, arrtab)
5361
5362# the opening quote's back state is not kept on the span
5363def doc.open.eat(_back: Lex, mode: Mode) -> Mode:
5364  mode
5365
5366# a suffix the span does not keep
5367def doc.drop(_tail: String, mode: Mode) -> Mode:
5368  mode
5369
5370# an empty buffer starts a span; a buffer already in use stays on the old path
5371def doc.open.buf(
5372  basic: Bool,
5373  qq: Char,
5374  qkind: QKind,
5375  bad: String,
5376  rows: List<&2, Val>,
5377  heads: List<&2, String>,
5378  here: List<&2, String>,
5379  parts: List<&2, String>,
5380  buf: List<&2, Char>,
5381  back: Lex,
5382  stack: List<&2, Ctx>,
5383  uni: U32,
5384  left: U32,
5385  arrtab: Bool,
5386  cc: Char,
5387  tail: String
5388) -> Mode:
5389  match buf:
5390    case []:
5391      doc.open.eat(back, MEat{basic, qq, SCon{cc, tail}, 1, bad, rows, heads, here, parts,
5392        qkind, stack, uni, left, arrtab})
5393    case h <> t:
5394      doc.drop(tail, MScan{read.fast.lex(LQ{}, qkind, bad, rows, heads, here, parts,
5395        h <> t, back, stack, uni, left, arrtab, cc)})
5396
5397# a forbidden control leaves the span
5398def doc.open.ok(
5399  badc: Bool,
5400  basic: Bool,
5401  qq: Char,
5402  qkind: QKind,
5403  bad: String,
5404  rows: List<&2, Val>,
5405  heads: List<&2, String>,
5406  here: List<&2, String>,
5407  parts: List<&2, String>,
5408  buf: List<&2, Char>,
5409  back: Lex,
5410  stack: List<&2, Ctx>,
5411  uni: U32,
5412  left: U32,
5413  arrtab: Bool,
5414  cc: Char,
5415  tail: String
5416) -> Mode:
5417  match badc:
5418    case True{}:
5419      doc.drop(tail, MScan{read.fast.lex(LQ{}, qkind, bad, rows, heads, here, parts,
5420        buf, back, stack, uni, left, arrtab, cc)})
5421    case False{}:
5422      doc.open.buf(basic, qq, qkind, bad, rows, heads, here, parts, buf, back, stack,
5423        uni, left, arrtab, cc, tail)
5424
5425# a backslash in a basic string leaves the span
5426def doc.open.bs(
5427  bs: Bool,
5428  qq: Char,
5429  qkind: QKind,
5430  bad: String,
5431  rows: List<&2, Val>,
5432  heads: List<&2, String>,
5433  here: List<&2, String>,
5434  parts: List<&2, String>,
5435  buf: List<&2, Char>,
5436  back: Lex,
5437  stack: List<&2, Ctx>,
5438  uni: U32,
5439  left: U32,
5440  arrtab: Bool,
5441  +cc: Char,
5442  tail: String
5443) -> Mode:
5444  match bs:
5445    case True{}:
5446      doc.drop(tail, MScan{read.fast.lex(LQ{}, qkind, bad, rows, heads, here, parts,
5447        buf, back, stack, uni, left, arrtab, cc)})
5448    case False{}:
5449      doc.open.ok(str.bad(Char.to_u32(cc), False{}), True{}, qq, qkind, bad, rows, heads,
5450        here, parts, buf, back, stack, uni, left, arrtab, cc, tail)
5451
5452# a literal string keeps a backslash; a basic string does not
5453def doc.open.b(
5454  basic: Bool,
5455  qq: Char,
5456  qkind: QKind,
5457  bad: String,
5458  rows: List<&2, Val>,
5459  heads: List<&2, String>,
5460  here: List<&2, String>,
5461  parts: List<&2, String>,
5462  buf: List<&2, Char>,
5463  back: Lex,
5464  stack: List<&2, Ctx>,
5465  uni: U32,
5466  left: U32,
5467  arrtab: Bool,
5468  +cc: Char,
5469  tail: String
5470) -> Mode:
5471  match basic:
5472    case False{}:
5473      doc.open.ok(str.bad(Char.to_u32(cc), False{}), False{}, qq, qkind, bad, rows, heads,
5474        here, parts, buf, back, stack, uni, left, arrtab, cc, tail)
5475    case True{}:
5476      doc.open.bs(Char.is_eq(cc, '\\'), qq, qkind, bad, rows, heads, here, parts, buf,
5477        back, stack, uni, left, arrtab, cc, tail)
5478
5479# a second quote is an empty string or the start of a multiline string
5480def doc.open.q(
5481  same: Bool,
5482  basic: Bool,
5483  qq: Char,
5484  qkind: QKind,
5485  bad: String,
5486  rows: List<&2, Val>,
5487  heads: List<&2, String>,
5488  here: List<&2, String>,
5489  parts: List<&2, String>,
5490  buf: List<&2, Char>,
5491  back: Lex,
5492  stack: List<&2, Ctx>,
5493  uni: U32,
5494  left: U32,
5495  arrtab: Bool,
5496  cc: Char,
5497  tail: String
5498) -> Mode:
5499  match same:
5500    case True{}:
5501      doc.drop(tail, MScan{read.fast.lex(LQ{}, qkind, bad, rows, heads, here, parts,
5502        buf, back, stack, uni, left, arrtab, cc)})
5503    case False{}:
5504      doc.open.b(basic, qq, qkind, bad, rows, heads, here, parts, buf, back, stack, uni,
5505        left, arrtab, cc, tail)
5506
5507# the character after an opening quote
5508def doc.open(
5509  basic: Bool,
5510  +qq: Char,
5511  qkind: QKind,
5512  bad: String,
5513  rows: List<&2, Val>,
5514  heads: List<&2, String>,
5515  here: List<&2, String>,
5516  parts: List<&2, String>,
5517  buf: List<&2, Char>,
5518  back: Lex,
5519  stack: List<&2, Ctx>,
5520  uni: U32,
5521  left: U32,
5522  arrtab: Bool,
5523  +cc: Char,
5524  tail: String
5525) -> Mode:
5526  doc.open.q(Char.is_eq(cc, qq), basic, qq, qkind, bad, rows, heads, here, parts, buf,
5527    back, stack, uni, left, arrtab, cc, tail)
5528
5529# a one-line string may be a span; a multiline string stays on the old path
5530def doc.scan.kind(
5531  qkind: QKind,
5532  bad: String,
5533  rows: List<&2, Val>,
5534  heads: List<&2, String>,
5535  here: List<&2, String>,
5536  parts: List<&2, String>,
5537  buf: List<&2, Char>,
5538  back: Lex,
5539  stack: List<&2, Ctx>,
5540  uni: U32,
5541  left: U32,
5542  arrtab: Bool
5543) -> Mode:
5544  match qkind:
5545    case QBasic{}:
5546      MOpen{True{}, '"', QBasic{}, bad, rows, heads, here, parts, buf, back, stack,
5547        uni, left, arrtab}
5548    case QLit{}:
5549      MOpen{False{}, '\'', QLit{}, bad, rows, heads, here, parts, buf, back, stack,
5550        uni, left, arrtab}
5551    case other:
5552      MScan{St{bad, rows, heads, here, parts, buf, LQ{}, back, other, stack, HNone{},
5553        uni, left, arrtab, False{}}}
5554
5555# the first character of the file is still the old step
5556def doc.scan.lead(
5557  lead: Bool,
5558  qkind: QKind,
5559  bad: String,
5560  rows: List<&2, Val>,
5561  heads: List<&2, String>,
5562  here: List<&2, String>,
5563  parts: List<&2, String>,
5564  buf: List<&2, Char>,
5565  back: Lex,
5566  stack: List<&2, Ctx>,
5567  uni: U32,
5568  left: U32,
5569  arrtab: Bool
5570) -> Mode:
5571  match lead:
5572    case True{}:
5573      MScan{St{bad, rows, heads, here, parts, buf, LQ{}, back, qkind, stack, HNone{},
5574        uni, left, arrtab, True{}}}
5575    case False{}:
5576      doc.scan.kind(qkind, bad, rows, heads, here, parts, buf, back, stack, uni, left,
5577        arrtab)
5578
5579# a held character is not the start of a span
5580def doc.scan.hold(
5581  hold: Hold,
5582  lead: Bool,
5583  qkind: QKind,
5584  bad: String,
5585  rows: List<&2, Val>,
5586  heads: List<&2, String>,
5587  here: List<&2, String>,
5588  parts: List<&2, String>,
5589  buf: List<&2, Char>,
5590  back: Lex,
5591  stack: List<&2, Ctx>,
5592  uni: U32,
5593  left: U32,
5594  arrtab: Bool
5595) -> Mode:
5596  match hold:
5597    case HChar{held}:
5598      MScan{St{bad, rows, heads, here, parts, buf, LQ{}, back, qkind, stack,
5599        HChar{held}, uni, left, arrtab, lead}}
5600    case HNone{}:
5601      doc.scan.lead(lead, qkind, bad, rows, heads, here, parts, buf, back, stack, uni,
5602        left, arrtab)
5603
5604# a quote opened a one-line string, or the state is rebuilt around that quote
5605def doc.wrap.lex(
5606  lex: Lex,
5607  bad: String,
5608  rows: List<&2, Val>,
5609  heads: List<&2, String>,
5610  here: List<&2, String>,
5611  parts: List<&2, String>,
5612  buf: List<&2, Char>,
5613  back: Lex,
5614  qkind: QKind,
5615  stack: List<&2, Ctx>,
5616  hold: Hold,
5617  uni: U32,
5618  left: U32,
5619  arrtab: Bool,
5620  lead: Bool
5621) -> Mode:
5622  match lex:
5623    case LQ{}:
5624      doc.scan.hold(hold, lead, qkind, bad, rows, heads, here, parts, buf, back, stack,
5625        uni, left, arrtab)
5626    case other:
5627      MScan{St{bad, rows, heads, here, parts, buf, other, back, qkind, stack, hold,
5628        uni, left, arrtab, lead}}
5629
5630# a quote may have opened a string
5631def doc.wrap(st: St) -> Mode:
5632  St{bad, rows, heads, here, parts, buf, lex, back, qkind, stack, hold, uni, left,
5633    arrtab, lead} = st
5634  doc.wrap.lex(lex, bad, rows, heads, here, parts, buf, back, qkind, stack, hold,
5635    uni, left, arrtab, lead)
5636
5637# a carriage return must be followed by a line feed
5638def doc.scan.cr(cr: Bool, st: St, cc: Char) -> Mode:
5639  match cr:
5640    case True{}:
5641      MCr{read.fast(st, cc)}
5642    case False{}:
5643      MScan{read.fast(st, cc)}
5644
5645# an apostrophe may open a literal string
5646def doc.scan.sq(sq: Bool, cr: Bool, st: St, cc: Char) -> Mode:
5647  match sq:
5648    case True{}:
5649      doc.wrap(read.fast(st, cc))
5650    case False{}:
5651      doc.scan.cr(cr, st, cc)
5652
5653# a quote may open a basic string. any other character is the old step
5654def doc.scan.q(dq: Bool, sq: Bool, cr: Bool, st: St, cc: Char) -> Mode:
5655  match dq:
5656    case True{}:
5657      doc.wrap(read.fast(st, cc))
5658    case False{}:
5659      doc.scan.sq(sq, cr, st, cc)
5660
5661# one source character outside a plain string
5662def doc.scan(st: St, +cc: Char) -> Mode:
5663  doc.scan.q(Char.is_eq(cc, '"'), Char.is_eq(cc, '\''), U32.is_eq(Char.to_u32(cc), 13),
5664    st, cc)
5665
5666# the character after a carriage return: a line feed ends the line, anything else fails
5667def doc.cr(lf: Bool, st: St, cc: Char) -> Mode:
5668  match lf:
5669    case True{}:
5670      MScan{read.fast(st, cc)}
5671    case False{}:
5672      MScan{st.err(st, "carriage return without line feed")}
5673
5674# one character at a time. a plain string keeps a span of this suffix
5675def doc.read(text: String, mode: Mode) -> Doc:
5676  match text mode:
5677    case SNil{} MScan{st}:
5678      read.fin(read.once2(st))
5679    case SNil{} MCr{st}:
5680      read.fin(read.once2(st.err(st, "carriage return without line feed")))
5681    case SNil{} MOpen{_basic, _q, qkind, bad, rows, heads, here, parts, buf, back,
5682      stack, uni, left, arrtab}:
5683      read.fin(read.once2(St{bad, rows, heads, here, parts, buf, LQ{}, back, qkind,
5684        stack, HNone{}, uni, left, arrtab, False{}}))
5685    case SNil{} MEat{_basic, _q, cut, n, bad, rows, heads, here, parts, qkind, stack,
5686      uni, left, arrtab}:
5687      eat.fin(n, span.drop(cut), bad, rows, heads, here, parts, qkind, stack, uni,
5688        left, arrtab)
5689    case SCon{+h, +t} MOpen{basic, q, qkind, bad, rows, heads, here, parts, buf, back,
5690      stack, uni, left, arrtab}:
5691      doc.read(t, doc.open(basic, q, qkind, bad, rows, heads, here, parts, buf, back,
5692        stack, uni, left, arrtab, h, t))
5693    case SCon{+h, t} MEat{basic, q, cut, n, bad, rows, heads, here, parts, qkind,
5694      stack, uni, left, arrtab}:
5695      doc.read(t, eat.feed(basic, q, cut, n, bad, rows, heads, here, parts, qkind,
5696        stack, uni, left, arrtab, h))
5697    case SCon{+h, t} MScan{st}:
5698      doc.read(t, doc.scan(st, h))
5699    case SCon{+h, t} MCr{st}:
5700      doc.read(t, doc.cr(Char.is_eq(h, '\n'), st, h))
5701
5702# a scanner at the start of a document
5703def st.start() -> St:
5704  St{"", [], [], [], [], [], LTop{}, LTop{}, QBasic{}, [], HNone{}, 0, 0, False{},
5705    True{}}
5706
5707# a span walked for quoting. SqOk is the body reversed, with the closing quote already on
5708type Sq is Data:
5709  SqOk{rev: List<&2, Char>}
5710  SqNo{}
5711
5712# the reversed body while a span is still plain. SqStop means an escape was seen
5713type SqAcc is Data:
5714  SqRun{rev: List<&2, Char>}
5715  SqStop{}
5716
5717# quote, backslash, or a control. those still take the escaping writer
5718def span.esc(+uu: U32) -> Bool:
5719  Bool.or(U32.is_lt(uu, 32), Bool.or(U32.is_eq(uu, 34), Bool.or(U32.is_eq(uu, 92),
5720    U32.is_eq(uu, 127))))
5721
5722# cons one plain character. an escape, or a stop already seen, stays stopped
5723def span.quote.push(esc: Bool, acc: SqAcc, +hh: Char) -> SqAcc:
5724  match esc acc:
5725    case True{} _a:
5726      SqStop{}
5727    case False{} SqStop{}:
5728      SqStop{}
5729    case False{} SqRun{rev}:
5730      SqRun{hh <> rev}
5731
5732# the reversed body, or a stop, as the quoting result
5733def span.quote.end(acc: SqAcc) -> Sq:
5734  match acc:
5735    case SqStop{}:
5736      SqNo{}
5737    case SqRun{rev}:
5738      SqOk{rev}
5739
5740# the first n characters, reversed onto acc. a character that needs an escape stops
5741def span.quote.go(src: String, +nn: U32, zz: Bool, acc: SqAcc) -> Sq:
5742  match src zz:
5743    case _s True{}:
5744      span.quote.end(acc)
5745    case SNil{} False{}:
5746      span.quote.end(acc)
5747    case SCon{+h, t} False{}:
5748      span.quote.go(t, U32.sub(nn, 1), U32.is_zero(U32.sub(nn, 1)),
5749        span.quote.push(span.esc(Char.to_u32(h)), acc, h))
5750
5751# quotes around a plain span, or the escaping writer when a character needs it
5752def span.quote.fin(hit: Sq, src: String, +nn: U32) -> String:
5753  match hit:
5754    case SqNo{}:
5755      key.basic(span.str(src, nn, U32.is_zero(nn)))
5756    case SqOk{rev}:
5757      String.from_list(List.reverse(&2, Char, '"' <> rev))
5758
5759# a plain string span, quoted without rebuilding an escaped copy
5760def span.quote(+src: String, +nn: U32) -> String:
5761  span.quote.fin(span.quote.go(src, nn, U32.is_zero(nn), SqRun{'"' <> []}), src, nn)
5762
5763# true or false
5764def render.bool(bit: Bool) -> String:
5765  match bit:
5766    case True{}:
5767      "true"
5768    case False{}:
5769      "false"
5770
5771# a scalar value
5772def render.atom(vv: Val) -> String:
5773  match vv:
5774    case VStr{text}:
5775      key.basic(text)
5776    case VSpan{src, +n}:
5777      span.quote(src, n)
5778    case VInt{sign, digits}:
5779      sign.text(sign) ++ digits
5780    case VFlo{sign, text}:
5781      sign.text(sign) ++ text
5782    case VBool{bit}:
5783      render.bool(bit)
5784    case VWhen{w}:
5785      when.text(w)
5786    case _:
5787      ""
5788
5789# a separator that is omitted after the last item
5790def render.suf.last(last: Bool, gap: String) -> String:
5791  match last:
5792    case True{}:
5793      ""
5794    case False{}:
5795      gap
5796
5797# a separator. trail keeps the separator after the last item
5798def render.suf(+gap: String, trail: Bool, last: Bool) -> String:
5799  match trail:
5800    case True{}:
5801      gap
5802    case False{}:
5803      render.suf.last(last, gap)
5804
5805# the values of a table, an array, or an inline table
5806def render.go(vs: List<&2, Val>, +gap: String, +trail: Bool) -> String:
5807  match vs:
5808    case []:
5809      ""
5810    case VPair{_name, VHead{path, rows}} <> +t:
5811      "[" ++ path ++ "]\n" ++ render.go(rows, "\n", True{}) ++
5812        render.suf(gap, trail, List.is_empty(&2, Val, t)) ++ render.go(t, gap, trail)
5813    case VPair{_name, VAots{_path, elems}} <> +t:
5814      render.go(elems, "\n", True{}) ++
5815        render.suf(gap, trail, List.is_empty(&2, Val, t)) ++ render.go(t, gap, trail)
5816    case VAot{path, rows} <> +t:
5817      "[[" ++ path ++ "]]\n" ++ render.go(rows, "\n", True{}) ++
5818        render.suf(gap, trail, List.is_empty(&2, Val, t)) ++ render.go(t, gap, trail)
5819    case VPair{name, VArr{items}} <> +t:
5820      key(name) ++ " = [" ++ render.go(items, ", ", False{}) ++ "]" ++
5821        render.suf(gap, trail, List.is_empty(&2, Val, t)) ++ render.go(t, gap, trail)
5822    case VPair{name, VInl{rows}} <> +t:
5823      key(name) ++ " = {" ++ render.go(rows, ", ", False{}) ++ "}" ++
5824        render.suf(gap, trail, List.is_empty(&2, Val, t)) ++ render.go(t, gap, trail)
5825    case VPair{name, val} <> +t:
5826      key(name) ++ " = " ++ render.atom(val) ++
5827        render.suf(gap, trail, List.is_empty(&2, Val, t)) ++ render.go(t, gap, trail)
5828    case VArr{items} <> +t:
5829      "[" ++ render.go(items, ", ", False{}) ++ "]" ++
5830        render.suf(gap, trail, List.is_empty(&2, Val, t)) ++ render.go(t, gap, trail)
5831    case VInl{rows} <> +t:
5832      "{" ++ render.go(rows, ", ", False{}) ++ "}" ++
5833        render.suf(gap, trail, List.is_empty(&2, Val, t)) ++ render.go(t, gap, trail)
5834    case v <> +t:
5835      render.atom(v) ++ render.suf(gap, trail, List.is_empty(&2, Val, t)) ++
5836        render.go(t, gap, trail)
5837
5838# a nested table rendering that this pass does not print
5839def render.skip(
5840  _path: String,
5841  _rows: List<&2, Val>,
5842  _tail: List<&2, Val>,
5843  _gap: String,
5844  _trail: Bool,
5845  nest: Unit -> String,
5846  rest: Unit -> String
5847) -> String:
5848  _n = nest
5849  rest(Unit{})
5850
5851# the rows are already closed over by the nested rendering
5852def render.use(_rows: List<&2, Val>, text: String) -> String:
5853  text
5854
5855# a header already ends in a newline
5856def render.rest(_tail: List<&2, Val>, _gap: String, _trail: Bool, rest: Unit -> String) -> String:
5857  rest(Unit{})
5858
5859# a table header. Direct keys are printed by the other pass
5860def render.head(
5861  tabs: Bool,
5862  path: String,
5863  rows: List<&2, Val>,
5864  tail: List<&2, Val>,
5865  gap: String,
5866  trail: Bool,
5867  nest: Unit -> String,
5868  rest: Unit -> String
5869) -> String:
5870  match tabs:
5871    case False{}:
5872      render.skip(path, rows, tail, gap, trail, nest, rest)
5873    case True{}:
5874      "[" ++ path ++ "]\n" ++ render.use(rows, nest(Unit{})) ++
5875        render.rest(tail, gap, trail, rest)
5876
5877# an array of tables. Its elements are headers
5878def render.aots(
5879  tabs: Bool,
5880  elems: List<&2, Val>,
5881  tail: List<&2, Val>,
5882  gap: String,
5883  trail: Bool,
5884  nest: Unit -> String,
5885  rest: Unit -> String
5886) -> String:
5887  match tabs:
5888    case False{}:
5889      render.skip("", elems, tail, gap, trail, nest, rest)
5890    case True{}:
5891      render.use(elems, nest(Unit{})) ++ render.rest(tail, gap, trail, rest)
5892
5893# one element of an array of tables
5894def render.aot(
5895  tabs: Bool,
5896  path: String,
5897  rows: List<&2, Val>,
5898  tail: List<&2, Val>,
5899  gap: String,
5900  trail: Bool,
5901  nest: Unit -> String,
5902  rest: Unit -> String
5903) -> String:
5904  match tabs:
5905    case False{}:
5906      render.skip(path, rows, tail, gap, trail, nest, rest)
5907    case True{}:
5908      "[[" ++ path ++ "]]\n" ++ render.use(rows, nest(Unit{})) ++
5909        render.rest(tail, gap, trail, rest)
5910
5911# a key, an array, or an inline table. The text is built only by the pass that writes keys
5912def render.late(
5913  tabs: Bool,
5914  text: Unit -> String,
5915  tail: List<&2, Val>,
5916  gap: String,
5917  trail: Bool,
5918  rest: Unit -> String
5919) -> String:
5920  match tabs:
5921    case True{}:
5922      rest(Unit{})
5923    case False{}:
5924      text(Unit{}) ++ render.suf(gap, trail, List.is_empty(&2, Val, tail)) ++ rest(Unit{})
5925
5926# one pass: keys, or the tables that follow them
5927def render.pass(vs: List<&2, Val>, +gap: String, +trail: Bool, +tabs: Bool) -> String:
5928  match vs:
5929    case []:
5930      ""
5931    case VPair{_name, VHead{path, +rows}} <> +t:
5932      render.head(tabs, path, rows, t, gap, trail,
5933        _u => render.pass(rows, "\n", True{}, False{}) ++
5934          render.pass(rows, "\n", True{}, True{}),
5935        _u => render.pass(t, gap, trail, tabs))
5936    case VPair{_name, VAots{_path, +elems}} <> +t:
5937      render.aots(tabs, elems, t, gap, trail,
5938        _u => render.pass(elems, "\n", True{}, False{}) ++
5939          render.pass(elems, "\n", True{}, True{}),
5940        _u => render.pass(t, gap, trail, tabs))
5941    case VAot{path, +rows} <> +t:
5942      render.aot(tabs, path, rows, t, gap, trail,
5943        _u => render.pass(rows, "\n", True{}, False{}) ++
5944          render.pass(rows, "\n", True{}, True{}),
5945        _u => render.pass(t, gap, trail, tabs))
5946    case VPair{name, VArr{items}} <> +t:
5947      render.late(tabs, _u => key(name) ++ " = [" ++ render.go(items, ", ", False{}) ++ "]",
5948        t, gap, trail, _v => render.pass(t, gap, trail, tabs))
5949    case VPair{name, VInl{rows}} <> +t:
5950      render.late(tabs, _u => key(name) ++ " = {" ++ render.go(rows, ", ", False{}) ++ "}",
5951        t, gap, trail, _v => render.pass(t, gap, trail, tabs))
5952    case VPair{name, val} <> +t:
5953      render.late(tabs, _u => key(name) ++ " = " ++ render.atom(val), t, gap, trail,
5954        _v => render.pass(t, gap, trail, tabs))
5955    case VArr{items} <> +t:
5956      render.late(tabs, _u => "[" ++ render.go(items, ", ", False{}) ++ "]",
5957        t, gap, trail, _v => render.pass(t, gap, trail, tabs))
5958    case VInl{rows} <> +t:
5959      render.late(tabs, _u => "{" ++ render.go(rows, ", ", False{}) ++ "}",
5960        t, gap, trail, _v => render.pass(t, gap, trail, tabs))
5961    case v <> +t:
5962      render.late(tabs, _u => render.atom(v), t, gap, trail,
5963        _v => render.pass(t, gap, trail, tabs))
5964
5965# keys first, then tables and arrays of tables
5966def render.both(+vs: List<&2, Val>, +gap: String, +trail: Bool) -> String:
5967  render.pass(vs, gap, trail, False{}) ++ render.pass(vs, gap, trail, True{})
5968
5969# the text of a string value
5970def string(vv: Val) -> String:
5971  match vv:
5972    case VStr{text}:
5973      text
5974    case VSpan{src, +n}:
5975      span.str(src, n, U32.is_zero(n))
5976    case _:
5977      ""
5978
5979# the digits of an integer value
5980def digits(vv: Val) -> String:
5981  match vv:
5982    case VInt{_sign, text}:
5983      text
5984    case _:
5985      ""
5986
5987# the bit of a boolean value
5988def flag(vv: Val) -> Bool:
5989  match vv:
5990    case VBool{bit}:
5991      bit
5992    case _:
5993      False{}
5994
5995# the rows of a table value
5996def at.rows(vv: Val) -> List<&2, Val>:
5997  match vv:
5998    case VHead{_path, rows}:
5999      rows
6000    case VInl{rows}:
6001      rows
6002    case VAot{_path, rows}:
6003      rows
6004    case _:
6005      []
6006
6007# the rows of a hit, when it is a table
6008def at.rows_of(hh: Hit) -> List<&2, Val>:
6009  match hh:
6010    case Found{val}:
6011      at.rows(val)
6012    case Miss{}:
6013      []
6014
6015# the rest of a path after the first key
6016def at.step(rest: List<&2, String>, hit: Hit) -> Hit:
6017  match rest:
6018    case []:
6019      hit
6020    case name <> t:
6021      at.step(t, get(at.rows_of(hit), name))
6022
6023# the value at a dotted path
6024def at(rows: List<&2, Val>, path: List<&2, String>) -> Hit:
6025  match path:
6026    case []:
6027      Miss{}
6028    case name <> rest:
6029      at.step(rest, get(rows, name))
6030
6031# the root table
6032def root(doc: Doc) -> List<&2, Val>:
6033  Doc{_bad, rows} = doc
6034  rows
6035
6036# the first error, or "" when the document is well formed
6037def bad(doc: Doc) -> String:
6038  Doc{text, _rows} = doc
6039  text
6040
6041# a string value
6042def str(text: String) -> Val:
6043  VStr{text}
6044
6045# an integer value
6046def integer(sign: Sign, text: String) -> Val:
6047  VInt{sign, text}
6048
6049# a float value
6050def float(sign: Sign, text: String) -> Val:
6051  VFlo{sign, text}
6052
6053# a boolean value
6054def boolean(bit: Bool) -> Val:
6055  VBool{bit}
6056
6057# an array value
6058def array(items: List<&2, Val>) -> Val:
6059  VArr{items}
6060
6061# an inline table
6062def inline(rows: List<&2, Val>) -> Val:
6063  VInl{rows}
6064
6065# a table
6066def table(path: String, rows: List<&2, Val>) -> Val:
6067  VHead{path, rows}
6068
6069# a key and a value
6070def pair(name: String, val: Val) -> Val:
6071  VPair{name, val}
6072
6073# a document written back as TOML
6074def render(doc: Doc) -> String:
6075  Doc{_bad, rows} = doc
6076  render.both(rows, "\n", True{})
6077
6078# a document read from TOML text
6079def parse(text: String) -> Doc:
6080  doc.read(text, MScan{st.start()})
6081
6082# Well-formed documents: the shapes parse returns. The builders make any shape,
6083# and render writes TOML only for these.
6084
6085# where a list of values sits: a table's rows under its path (the root's is
6086# ""), an inline table's rows, an array's items, or the elements of an array of
6087# tables under its path
6088type WfAt is Data:
6089  WfRows{path: String}
6090  WfInl{}
6091  WfItems{}
6092  WfElems{path: String}
6093
6094# how far a float's spelling has been read: toml.abnf's float with no sign
6095# and no underscore, `dec-int ( exp / frac [ exp ] )`
6096type Fl is Data:
6097  FlStart{}
6098  FlZero{}
6099  FlInt{}
6100  FlDot{}
6101  FlFrac{}
6102  FlE{}
6103  FlESign{}
6104  FlExp{}
6105  FlNo{}
6106
6107# the rest when this part holds, and False otherwise
6108def wf.then(ok: Bool, more: Unit -> Bool) -> Bool:
6109  match ok:
6110    case True{}:
6111      more(Unit{})
6112    case False{}:
6113      False{}
6114
6115# a Unicode scalar value: no surrogate, and nothing past U+10FFFF
6116def wf.char(+cc: Char) -> Bool:
6117  uni.ok(Char.to_u32(cc))
6118
6119# every character of a string or a key is a Unicode scalar value
6120def wf.text(ss: String) -> Bool:
6121  match ss:
6122    case SNil{}:
6123      True{}
6124    case SCon{+h, t}:
6125      wf.then(wf.char(h), _u => wf.text(t))
6126
6127# a span's source holds its n characters, each a Unicode scalar value
6128def wf.span(src: String, +nn: U32, zz: Bool) -> Bool:
6129  match src zz:
6130    case _s True{}:
6131      True{}
6132    case SNil{} False{}:
6133      False{}
6134    case SCon{+h, t} False{}:
6135      wf.then(wf.char(h), _u => wf.span(t, U32.sub(nn, 1), U32.is_zero(U32.sub(nn, 1))))
6136
6137# every character is an ASCII digit
6138def wf.digits(ss: String) -> Bool:
6139  match ss:
6140    case SNil{}:
6141      True{}
6142    case SCon{+h, t}:
6143      wf.then(Char.is_digit(h), _u => wf.digits(t))
6144
6145# a lone 0, or a first digit that is not 0 and more digits
6146def wf.dec.lead(zero: Bool, +cc: Char, rest: String) -> Bool:
6147  match zero:
6148    case True{}:
6149      String.is_empty(rest)
6150    case False{}:
6151      Bool.and(Char.is_digit(cc), wf.digits(rest))
6152
6153# decimal digits with no leading zero, except 0 itself
6154def wf.dec(ss: String) -> Bool:
6155  match ss:
6156    case SNil{}:
6157      False{}
6158    case SCon{+h, t}:
6159      wf.dec.lead(Char.is_eq(h, '0'), h, t)
6160
6161# an integer: decimal digits within signed 64 bits
6162def wf.int(+sign: Sign, +digits: String) -> Bool:
6163  Bool.and(wf.dec(digits), i64.fits(sign, digits))
6164
6165# e or E
6166def wf.flo.is_e(+cc: Char) -> Bool:
6167  Bool.or(Char.is_eq(cc, 'e'), Char.is_eq(cc, 'E'))
6168
6169# after the integer part: a fraction or an exponent
6170def wf.flo.tail(+cc: Char) -> Fl:
6171  Bool.pick(Fl, Char.is_eq(cc, '.'), FlDot{}, Bool.pick(Fl, wf.flo.is_e(cc), FlE{}, FlNo{}))
6172
6173# one character of a float's spelling
6174def wf.flo.step(st: Fl, +cc: Char) -> Fl:
6175  match st:
6176    case FlStart{}:
6177      Bool.pick(Fl, Char.is_eq(cc, '0'), FlZero{}, Bool.pick(Fl, Char.is_digit(cc), FlInt{}, FlNo{}))
6178    case FlZero{}:
6179      wf.flo.tail(cc)
6180    case FlInt{}:
6181      Bool.pick(Fl, Char.is_digit(cc), FlInt{}, wf.flo.tail(cc))
6182    case FlDot{}:
6183      Bool.pick(Fl, Char.is_digit(cc), FlFrac{}, FlNo{})
6184    case FlFrac{}:
6185      Bool.pick(Fl, Char.is_digit(cc), FlFrac{}, Bool.pick(Fl, wf.flo.is_e(cc), FlE{}, FlNo{}))
6186    case FlE{}:
6187      Bool.pick(Fl, Bool.or(Char.is_eq(cc, '+'), Char.is_eq(cc, '-')), FlESign{},
6188        Bool.pick(Fl, Char.is_digit(cc), FlExp{}, FlNo{}))
6189    case FlESign{}:
6190      Bool.pick(Fl, Char.is_digit(cc), FlExp{}, FlNo{})
6191    case FlExp{}:
6192      Bool.pick(Fl, Char.is_digit(cc), FlExp{}, FlNo{})
6193    case FlNo{}:
6194      FlNo{}
6195
6196# a float's spelling read from the given state
6197def wf.flo.go(cs: List<&2, Char>, st: Fl) -> Fl:
6198  match cs:
6199    case []:
6200      st
6201    case +h <> t:
6202      wf.flo.go(t, wf.flo.step(st, h))
6203
6204# a spelling ends after a fraction's digit or an exponent's digit
6205def wf.flo.end(st: Fl) -> Bool:
6206  match st:
6207    case FlFrac{}:
6208      True{}
6209    case FlExp{}:
6210      True{}
6211    case _:
6212      False{}
6213
6214# a float's spelling: inf, nan, or toml.abnf's float with no sign and no `_`
6215def wf.flo(+text: String) -> Bool:
6216  Bool.or(str.eq(text, "inf"), Bool.or(str.eq(text, "nan"),
6217    wf.flo.end(wf.flo.go(String.to_list(text), FlStart{}))))
6218
6219# a date: a four-digit year, a month, and a day within that month in that year
6220def wf.date(dd: Date) -> Bool:
6221  Date{+y, +mo, +day} = dd
6222  Bool.and(U32.is_le(y, 9999), Bool.and(bound.rng(mo, 1, 12), bound.rng(day, 1, when.days(y, mo))))
6223
6224# a time of day: hour, minute and second in range, and a fraction of digits
6225def wf.clock(cc: Clock) -> Bool:
6226  Clock{+h, +mi, +s, frac} = cc
6227  Bool.and(U32.is_le(h, 23), Bool.and(U32.is_le(mi, 59), Bool.and(U32.is_le(s, 60), wf.digits(frac))))
6228
6229# a zone offset in range
6230def wf.zone(zz: Zone) -> Bool:
6231  match zz:
6232    case Zulu{}:
6233      True{}
6234    case Shift{_sign, +h, +mi}:
6235      Bool.and(U32.is_le(h, 23), U32.is_le(mi, 59))
6236
6237# a datetime whose fields are in range
6238def wf.when(ww: When) -> Bool:
6239  match ww:
6240    case WOff{date, clock, zone}:
6241      Bool.and(wf.date(date), Bool.and(wf.clock(clock), wf.zone(zone)))
6242    case WLocal{date, clock}:
6243      Bool.and(wf.date(date), wf.clock(clock))
6244    case WDate{date}:
6245      wf.date(date)
6246    case WTime{clock}:
6247      wf.clock(clock)
6248
6249# a string, a number, a boolean or a datetime, well-formed. Nothing else is a
6250# scalar
6251def wf.atom(vv: Val) -> Bool:
6252  match vv:
6253    case VStr{text}:
6254      wf.text(text)
6255    case VSpan{src, +n}:
6256      wf.span(src, n, U32.is_zero(n))
6257    case VInt{+sign, +digits}:
6258      wf.int(sign, digits)
6259    case VFlo{_sign, +text}:
6260      wf.flo(text)
6261    case VBool{_bit}:
6262      True{}
6263    case VWhen{w}:
6264      wf.when(w)
6265    case _:
6266      False{}
6267
6268# a name no later pair of the table has
6269def wf.fresh(hit: Hit) -> Bool:
6270  match hit:
6271    case Found{_v}:
6272      False{}
6273    case Miss{}:
6274      True{}
6275
6276# a pair's key: Unicode scalar values, and no later pair of the same name
6277def wf.key(+name: String, +rest: List<&2, Val>) -> Bool:
6278  Bool.and(wf.text(name), wf.fresh(get(rest, name)))
6279
6280# a table as a pair's value, in a table's rows, at the path its name gives
6281def wf.head(at: WfAt, +name: String, +path: String, +rest: List<&2, Val>, more: Unit -> Bool) -> Bool:
6282  match at:
6283    case WfRows{+here}:
6284      wf.then(Bool.and(wf.key(name, rest), str.eq(path, tree.pre(here, name))), more)
6285    case _:
6286      False{}
6287
6288# an array of tables as a pair's value, in a table's rows, at the path its
6289# name gives, with at least one element
6290def wf.aots(
6291  at: WfAt,
6292  +name: String,
6293  +path: String,
6294  some: Bool,
6295  +rest: List<&2, Val>,
6296  more: Unit -> Bool
6297) -> Bool:
6298  match at:
6299    case WfRows{+here}:
6300      wf.then(Bool.and(some, Bool.and(wf.key(name, rest), str.eq(path, tree.pre(here, name)))), more)
6301    case _:
6302      False{}
6303
6304# an element of an array of tables, at the array's path
6305def wf.aot(at: WfAt, +path: String, more: Unit -> Bool) -> Bool:
6306  match at:
6307    case WfElems{here}:
6308      wf.then(str.eq(path, here), more)
6309    case _:
6310      False{}
6311
6312# a pair of any other value, in a table's or an inline table's rows
6313def wf.pair(at: WfAt, +name: String, +rest: List<&2, Val>, ok: Bool, more: Unit -> Bool) -> Bool:
6314  match at:
6315    case WfRows{_here}:
6316      wf.then(Bool.and(ok, wf.key(name, rest)), more)
6317    case WfInl{}:
6318      wf.then(Bool.and(ok, wf.key(name, rest)), more)
6319    case _:
6320      False{}
6321
6322# a value that is not a pair, as an array's item
6323def wf.item(at: WfAt, ok: Bool, more: Unit -> Bool) -> Bool:
6324  match at:
6325    case WfItems{}:
6326      wf.then(ok, more)
6327    case _:
6328      False{}
6329
6330# a list of values, well-formed where it sits
6331def wf.go(vs: List<&2, Val>, +at: WfAt) -> Bool:
6332  match vs:
6333    case []:
6334      True{}
6335    case VPair{+name, VHead{+path, rows}} <> +t:
6336      wf.head(at, name, path, t,
6337        _u => wf.then(wf.go(rows, WfRows{path}), _v => wf.go(t, at)))
6338    case VPair{+name, VAots{+path, +elems}} <> +t:
6339      wf.aots(at, name, path, Bool.not(List.is_empty(&2, Val, elems)), t,
6340        _u => wf.then(wf.go(elems, WfElems{path}), _v => wf.go(t, at)))
6341    case VPair{+name, VArr{items}} <> +t:
6342      wf.pair(at, name, t, True{},
6343        _u => wf.then(wf.go(items, WfItems{}), _v => wf.go(t, at)))
6344    case VPair{+name, VInl{rows}} <> +t:
6345      wf.pair(at, name, t, True{},
6346        _u => wf.then(wf.go(rows, WfInl{}), _v => wf.go(t, at)))
6347    case VPair{+name, val} <> +t:
6348      wf.pair(at, name, t, wf.atom(val), _u => wf.go(t, at))
6349    case VAot{+path, rows} <> t:
6350      wf.aot(at, path,
6351        _u => wf.then(wf.go(rows, WfRows{path}), _v => wf.go(t, at)))
6352    case VArr{items} <> t:
6353      wf.item(at, True{},
6354        _u => wf.then(wf.go(items, WfItems{}), _v => wf.go(t, at)))
6355    case VInl{rows} <> t:
6356      wf.item(at, True{},
6357        _u => wf.then(wf.go(rows, WfInl{}), _v => wf.go(t, at)))
6358    case val <> t:
6359      wf.item(at, wf.atom(val), _u => wf.go(t, at))
6360
6361# whether a document is well-formed: the shapes parse returns. Tables and
6362# arrays of tables sit only as a pair's value in a table's rows, each at the
6363# path its parent's path and its name give; a table's keys are distinct;
6364# numbers, datetimes and strings are as parse reads them. The error is not
6365# looked at
6366def wf(doc: Doc) -> Bool:
6367  Doc{_bad, rows} = doc
6368  wf.go(rows, WfRows{""})