main.bend

emerging-eztoml@0.8.0.0 · 6,370 lines · 0 definitions

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