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{""})