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