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 8 import Base 9 10 # the sign of an integer, a float, or a zone offset 11 type Sign is Data: 12 Plus{} 13 Minus{} 14 15 # a calendar date: year, month, day 16 type 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 20 type Clock is Data: 21 Clock{h: U32, mi: U32, s: U32, frac: String} 22 23 # a zone offset: Z, or a numeric offset 24 type Zone is Data: 25 Zulu{} 26 Shift{sign: Sign, h: U32, mi: U32} 27 28 # the four TOML datetime forms 29 type 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. 39 type 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 54 type Hit is Data: 55 Found{val: Val} 56 Miss{} 57 58 # a parsed document: the first error, and the root table in order 59 type 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 63 type Got is Data: 64 GotVal{val: Val} 65 GotBad{why: String} 66 67 # digits taken from the front of a character list 68 type Dig is Data: 69 DigOk{n: U32, cs: List<&2, Char>} 70 DigBad{} 71 72 # what a datetime scan produced 73 type WhenOut is Data: 74 WSome{when: When} 75 WFail{why: String} 76 WSkip{} 77 78 # a decimal numeral being built, least-significant digit first 79 type 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 91 type 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 96 type BK is Data: 97 BKCh{} 98 BKHex{} 99 BKEsc{mark: Char} 100 101 # where the scanner is in a document 102 type 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 140 type QKind is Data: 141 QBasic{} 142 QLit{} 143 QMulti{} 144 QMultiLit{} 145 146 # one character to read again 147 type Hold is Data: 148 HNone{} 149 HChar{c: Char} 150 151 # an open array or inline table 152 type 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 157 type 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 164 type 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 171 def 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 176 def 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 184 def 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 192 def 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 196 def 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 204 def 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 208 def 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 214 def 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 218 def basic.cls.hex(hit: Bool) -> BK: 219 match hit: 220 case True{}: 221 BKHex{} 222 case False{}: 223 BKCh{} 224 225 # backslash 226 def 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 234 def 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 242 def 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 250 def 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 258 def 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 266 def 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 274 def 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 282 def 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 286 def 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 296 def 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 301 def 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 309 def 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 314 def 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 322 def key(+ss: String) -> String: 323 key.go(bare(ss), ss) 324 325 # the overflow of a decimal step, least-significant digit first 326 def 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 334 def 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 338 def 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 346 def 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 350 def 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 358 def 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 362 def 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 371 def dec.ch(dd: U32) -> Char: 372 Chr{U32.add(48, dd)} 373 374 # digits as characters, in the order given 375 def 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 383 def 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 391 def 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 399 def 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 407 def 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 415 def 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 423 def 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 427 def 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 435 def 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 443 def 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 451 def 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 456 def dig.ok(dd: U32, base: U32) -> Bool: 457 U32.is_lt(dd, base) 458 459 # exactly k digits, or DigBad 460 def 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 468 def 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 476 def 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 484 def 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 492 def 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 504 def 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 508 def 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 516 def text.pad2(+nn: U32) -> String: 517 pad2.go(U32.is_lt(nn, 10), nn) 518 519 # the one-zero pad, or the digits themselves 520 def 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 528 def 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 536 def 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 544 def 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 548 def zone.mark(sign: Sign) -> String: 549 match sign: 550 case Plus{}: 551 "+" 552 case Minus{}: 553 "-" 554 555 # a numeric zone offset 556 def zone.shift(sign: Sign, hh: U32, mi: U32) -> String: 557 zone.mark(sign) ++ text.pad2(hh) ++ ":" ++ text.pad2(mi) 558 559 # a zone suffix 560 def 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 568 def 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 576 def 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 580 def 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 585 def 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 590 def 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 602 def 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 610 def 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 618 def 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 626 def 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 634 def 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 639 def when.done(_date: Date, _clock: Clock, ww: When) -> WhenOut: 640 WSome{ww} 641 642 # Z ends the token 643 def 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 651 def 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 659 def 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 667 def 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 684 def 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 692 def 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 700 def 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 708 def 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 716 def 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 720 def 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 728 def 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 736 def 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 744 def 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 753 def 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 762 def 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 766 def 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 782 def 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 791 def 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 799 def 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 807 def 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 815 def 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 823 def 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 840 def 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 849 def 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 857 def 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 865 def 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 873 def 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 881 def 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 889 def 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 897 def 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 905 def 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 913 def 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 922 def 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 930 def 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 939 def 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 947 def 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) 956 def 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 964 def 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 969 def 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 974 def 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 982 def 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 990 def 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 994 def 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 998 def 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 1002 def 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 1016 def 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 1024 def 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 1032 def 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 1040 def 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 1048 def 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 1056 def 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 1064 def 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 1072 def 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 1080 def 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 1088 def 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-) 1096 def 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 1104 def when.read(cs: List<&2, Char>) -> WhenOut: 1105 when.after2(take.n(2n, cs, 0, True{})) 1106 1107 # a dash after the first character 1108 def 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 1116 def 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 1124 def 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 1132 def 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 1136 def 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 1144 def 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 1152 def 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 1160 def 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 1168 def 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 1176 def 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 1184 def 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 1194 def str.eq(aa: String, bb: String) -> Bool: 1195 str.eq.step(aa, bb, True{}) 1196 1197 # a negative inf, or nan 1198 def 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 1206 def 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 1214 def 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 1222 def 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 1230 def 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 1238 def 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 1242 def 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 1252 def 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 1260 def 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 1268 def 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 1276 def 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 1284 def word.body(cs: List<&2, Char>) -> String: 1285 String.from_list(word.body.go(cs, True{}, [])) 1286 1287 # a fresh numeral scan 1288 def num.start() -> Num: 1289 Num{NLead{}, Plus{}, 10, [], False{}, False{}, ""} 1290 1291 # a scan that has failed 1292 def 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 1296 def num.sign(_st: Num, sign: Sign) -> Num: 1297 Num{NSign{}, sign, 10, [], False{}, False{}, ""} 1298 1299 # a digit that fits the base 1300 def 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 1308 def 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 1313 def num.digit(st: Num, +cc: Char, nk: NK) -> Num: 1314 num.digit.go(st, cc, nk) 1315 1316 # an underscore between digits 1317 def 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 1325 def 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 1330 def 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 1335 def 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 1343 def 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 1348 def 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 1356 def 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 1361 def num.sign.of(st: Num) -> Sign: 1362 Num{_nk, sign, _b, _ds, _us, _saw, _why} = st 1363 sign 1364 1365 # a first digit 1366 def 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 1374 def 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 1382 def 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 1390 def 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 1398 def 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 1404 def 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 1412 def 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 1417 def 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 1425 def 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 1433 def 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 1441 def 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 1449 def 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 1457 def 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 1465 def 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 1470 def 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 1478 def 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 1486 def 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 1494 def 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 1502 def 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 1510 def 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 1515 def 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 1523 def 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 1527 def 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 1535 def 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 1543 def 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 1551 def 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 1559 def 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 1564 def 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 1569 def 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 1577 def 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 1585 def 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 1593 def 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 1597 def 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 1605 def 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 1613 def 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 1617 def 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 1639 def 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 1644 def num.step(st: Num, +cc: Char) -> Num: 1645 num.step.go(st, cc) 1646 1647 # the walk over a numeral 1648 def 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 1656 def 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 1664 def 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 1675 def 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 1687 def 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 1691 def 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 1699 def 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 1707 def 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 1715 def 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 1723 def 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 1731 def 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 1753 def 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 1761 def 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 1766 def 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 1770 def 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 1778 def 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 1786 def word.special.minus(+cc: Char) -> Bool: 1787 Char.is_eq(cc, '-') 1788 1789 # '+' or '-' 1790 def 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 1798 def 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 1806 def 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 1814 def 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 1822 def 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 1830 def 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 1834 def 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 1842 def 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 1846 def 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 1855 def 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 1859 type Job is Data: 1860 JPut{val: Val} 1861 JDef{} 1862 JAot{} 1863 1864 # one table the walk has entered 1865 type 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 1871 type 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 1876 type 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 1880 type 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 1888 type 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 1893 def 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 1901 def err.first(+bad: String, why: String) -> String: 1902 err.first.go(String.is_empty(bad), bad, why) 1903 1904 # a key found earlier wins 1905 def 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 1913 def 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 1923 def get(rows: List<&2, Val>, +name: String) -> Hit: 1924 rows.find(rows, name) 1925 1926 # replace the value of one pair 1927 def 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 1935 def 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 1945 def 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 1954 def heads.by.head(path: String) -> String: 1955 SCon{'[', path} 1956 1957 # a table dotted keys defined, as heads records it 1958 def heads.by.dot(path: String) -> String: 1959 SCon{'.', path} 1960 1961 # whether a table path was defined, by a header or by dotted keys 1962 def 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 1972 def 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 1982 def 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 1996 def heads.drop(hs: List<&2, String>, +pre: String) -> List<&2, String>: 1997 match hs: 1998 case []: 1999 [] 2000 case SCon{mark, +path} <> t: Lines 1-2000 of 6368. More: https://jend.leeeet.dev/read.txt?path=emerging-eztoml@0.7.0.0/main.bend&start_line=2001