1 # eztoml: TOML for Bend 2, a proven parser and renderer for TOML documents. 2 # 3 # eztoml reads and writes TOML documents. Values are strings, integers, floats, 4 # booleans, datetimes, arrays, inline tables, tables, and arrays of tables. 5 # Comments and blank lines are passed over. 6 # 7 # parse, render, get, at, root, bad, string, digits, flag 8 # str, integer, float, boolean, array, inline, table, pair 9 # key, wf 10 import Base 11 12 # the sign of an integer, a float, or a zone offset 13 type Sign is Data: 14 Plus{} 15 Minus{} 16 17 # a calendar date: year, month, day 18 type Date is Data: 19 Date{y: U32, mo: U32, d: U32} 20 21 # a time of day. frac is the fractional second, empty when there is none 22 type Clock is Data: 23 Clock{h: U32, mi: U32, s: U32, frac: String} 24 25 # a zone offset: Z, or a numeric offset 26 type Zone is Data: 27 Zulu{} 28 Shift{sign: Sign, h: U32, mi: U32} 29 30 # the four TOML datetime forms 31 type When is Data: 32 WOff{date: Date, clock: Clock, zone: Zone} 33 WLocal{date: Date, clock: Clock} 34 WDate{date: Date} 35 WTime{clock: Clock} 36 37 # one TOML value. Tables remember the dotted path they were opened under. 38 # An inline table is VInl. An array of tables is VAots, and each element is 39 # VAot. VPair is a key in a table. VSpan is a plain string: the first n 40 # characters of a source suffix. 41 type Val is Data: 42 VStr{text: String} 43 VSpan{src: String, n: U32} 44 VInt{sign: Sign, digits: String} 45 VFlo{sign: Sign, text: String} 46 VBool{bit: Bool} 47 VWhen{when: When} 48 VArr{items: List<&2, Val>} 49 VInl{rows: List<&2, Val>} 50 VHead{path: String, rows: List<&2, Val>} 51 VAot{path: String, rows: List<&2, Val>} 52 VAots{path: String, elems: List<&2, Val>} 53 VPair{name: String, val: Val} 54 55 # a key found in a table, or not 56 type Hit is Data: 57 Found{val: Val} 58 Miss{} 59 60 # a parsed document: the first error, and the root table in order 61 type Doc is Data: 62 Doc{bad: String, rows: List<&2, Val>} 63 64 # a value read from a bare word, or the reason it is not one 65 type Got is Data: 66 GotVal{val: Val} 67 GotBad{why: String} 68 69 # digits taken from the front of a character list 70 type Dig is Data: 71 DigOk{n: U32, cs: List<&2, Char>} 72 DigBad{} 73 74 # what a datetime scan produced 75 type WhenOut is Data: 76 WSome{when: When} 77 WFail{why: String} 78 WSkip{} 79 80 # a decimal numeral being built, least-significant digit first 81 type NK is Data: 82 NLead{} 83 NSign{} 84 NZero{} 85 NDig{} 86 NBase{} 87 NFrac{} 88 NExp{} 89 NEDig{} 90 NBad{} 91 92 # the numeral scan 93 type Num is Data: 94 Num{nk: NK, sign: Sign, base: U32, ds: List<&2, U32>, us: Bool, saw: Bool, 95 why: String} 96 97 # how a basic-string character is written 98 type BK is Data: 99 BKCh{} 100 BKHex{} 101 BKEsc{mark: Char} 102 103 # where the scanner is in a document 104 type Lex is Data: 105 LTop{} 106 LLine{} 107 LKey{} 108 LKeyDot{} 109 LAfter{} 110 LKeyQ{} 111 LKeyE{} 112 LKeyU{} 113 LVal{} 114 LQ{} 115 LQ2{} 116 LStr{} 117 LEsc{} 118 LUni{} 119 LFold{} 120 LMq{} 121 LMqEnd{} 122 LTrim{} 123 LTrim2{} 124 LBare{} 125 LDate{} 126 LCom{} 127 LHead{} 128 LHeadGap{} 129 LHeadDot{} 130 LHeadQ{} 131 LHeadE{} 132 LHeadU{} 133 LHeadR{} 134 LArr{} 135 LArrC{} 136 LInl{} 137 LInlK{} 138 LInlC{} 139 LSink{} 140 141 # which quotes a string is using 142 type QKind is Data: 143 QBasic{} 144 QLit{} 145 QMulti{} 146 QMultiLit{} 147 148 # one character to read again 149 type Hold is Data: 150 HNone{} 151 HChar{c: Char} 152 153 # an open array or inline table 154 type Ctx is Data: 155 CArr{items: List<&2, Val>, parts: List<&2, String>, here: List<&2, String>} 156 CInl{rows: List<&2, Val>, parts: List<&2, String>, here: List<&2, String>} 157 158 # the scanner 159 type St is Data: 160 St{bad: String, rows: List<&2, Val>, heads: List<&2, String>, 161 here: List<&2, String>, parts: List<&2, String>, buf: List<&2, Char>, 162 lex: Lex, back: Lex, qkind: QKind, stack: List<&2, Ctx>, hold: Hold, 163 uni: U32, left: U32, arrtab: Bool, lead: Bool} 164 165 # an escape in a basic string 166 type Esc is Data: 167 EChar{c: Char} 168 EUni{n: U32} 169 EFold{saw: Bool} 170 EBad{} 171 172 # a character a bare key may hold 173 def bare.at(+cc: Char) -> Bool: 174 Bool.or(Char.is_alpha(cc), Bool.or(Char.is_digit(cc), 175 Bool.or(Char.is_eq(cc, '_'), Char.is_eq(cc, '-')))) 176 177 # one step of the bare-key walk, with the head's answer in hand 178 def bare.all.step(here: Bool, rest: Unit -> Bool) -> Bool: 179 match here: 180 case True{}: 181 rest(Unit{}) 182 case False{}: 183 False{} 184 185 # whether every character of a key is one a bare key may hold 186 def bare.all(cs: List<&2, Char>) -> Bool: 187 match cs: 188 case []: 189 True{} 190 case +h <> t: 191 bare.all.step(bare.at(h), _u => bare.all(t)) 192 193 # whether a key can be written without quotes 194 def bare(+ss: String) -> Bool: 195 Bool.and(Bool.not(String.is_empty(ss)), bare.all(String.to_list(ss))) 196 197 # one hex nibble as a character 198 def hex.char.go(dec: Bool, nn: U32) -> Char: 199 match dec: 200 case True{}: 201 Chr{U32.add(48, nn)} 202 case False{}: 203 Chr{U32.add(87, nn)} 204 205 # one hex nibble as a character 206 def hex.char(+nn: U32) -> Char: 207 hex.char.go(U32.is_lt(nn, 10), nn) 208 209 # four lowercase hex digits of a code point, then the \u introducer, reversed 210 def basic.hex(+uu: U32, out: List<&2, Char>) -> List<&2, Char>: 211 hex.char(U32.mod(uu, 16)) <> (hex.char(U32.mod(U32.shrn(uu, 4n), 16)) <> 212 (hex.char(U32.mod(U32.shrn(uu, 8n), 16)) <> 213 (hex.char(U32.mod(U32.shrn(uu, 12n), 16)) <> ('u' <> ('\\' <> out))))) 214 215 # an escape mark reversed onto the output: the mark, then the backslash 216 def basic.esc(mark: Char, out: List<&2, Char>) -> List<&2, Char>: 217 mark <> ('\\' <> out) 218 219 # other controls are \u escapes; the rest are themselves 220 def basic.cls.hex(hit: Bool) -> BK: 221 match hit: 222 case True{}: 223 BKHex{} 224 case False{}: 225 BKCh{} 226 227 # backslash 228 def basic.cls.bslash(hit: Bool, +uu: U32) -> BK: 229 match hit: 230 case True{}: 231 BKEsc{'\\'} 232 case False{}: 233 basic.cls.hex(Bool.or(U32.is_lt(uu, 32), U32.is_eq(uu, 127))) 234 235 # quotation mark 236 def basic.cls.quote(hit: Bool, +uu: U32) -> BK: 237 match hit: 238 case True{}: 239 BKEsc{'"'} 240 case False{}: 241 basic.cls.bslash(U32.is_eq(uu, 92), uu) 242 243 # carriage return 244 def basic.cls.cr(hit: Bool, +uu: U32) -> BK: 245 match hit: 246 case True{}: 247 BKEsc{'r'} 248 case False{}: 249 basic.cls.quote(U32.is_eq(uu, 34), uu) 250 251 # form feed 252 def basic.cls.ff(hit: Bool, +uu: U32) -> BK: 253 match hit: 254 case True{}: 255 BKEsc{'f'} 256 case False{}: 257 basic.cls.cr(U32.is_eq(uu, 13), uu) 258 259 # line feed 260 def basic.cls.lf(hit: Bool, +uu: U32) -> BK: 261 match hit: 262 case True{}: 263 BKEsc{'n'} 264 case False{}: 265 basic.cls.ff(U32.is_eq(uu, 12), uu) 266 267 # tab 268 def basic.cls.tab(hit: Bool, +uu: U32) -> BK: 269 match hit: 270 case True{}: 271 BKEsc{'t'} 272 case False{}: 273 basic.cls.lf(U32.is_eq(uu, 10), uu) 274 275 # backspace, then the rest 276 def basic.cls.bs(hit: Bool, +uu: U32) -> BK: 277 match hit: 278 case True{}: 279 BKEsc{'b'} 280 case False{}: 281 basic.cls.tab(U32.is_eq(uu, 9), uu) 282 283 # the class of a code point in a basic string 284 def basic.cls(+uu: U32) -> BK: 285 basic.cls.bs(U32.is_eq(uu, 8), uu) 286 287 # one character of a basic string, reversed onto the output 288 def basic.disp(kk: BK, cc: Char, uu: U32, out: List<&2, Char>) -> List<&2, Char>: 289 match kk: 290 case BKCh{}: 291 cc <> out 292 case BKEsc{mark}: 293 basic.esc(mark, out) 294 case BKHex{}: 295 basic.hex(uu, out) 296 297 # one character of a basic string 298 def basic.one(+cc: Char, out: List<&2, Char>) -> List<&2, Char>: 299 +u = Char.to_u32(cc) 300 basic.disp(basic.cls(u), cc, u, out) 301 302 # the escaped characters of a basic string, reversed 303 def basic.go(cs: List<&2, Char>, out: List<&2, Char>) -> List<&2, Char>: 304 match cs: 305 case []: 306 out 307 case +h <> t: 308 basic.go(t, basic.one(h, out)) 309 310 # the buffer is reversed, so each head goes on the front of the text 311 def buf.str.go(buf: List<&2, Char>, acc: String) -> String: 312 match buf: 313 case []: 314 acc 315 case +h <> t: 316 buf.str.go(t, SCon{h, acc}) 317 318 # the buffer, in order 319 def buf.str(buf: List<&2, Char>) -> String: 320 buf.str.go(buf, "") 321 322 # a basic string: quotes, with controls and quotes escaped 323 def key.basic(+ss: String) -> String: 324 "\"" ++ buf.str(basic.go(String.to_list(ss), [])) ++ "\"" 325 326 # bare when the name allows it, otherwise a basic string 327 def key.go(ok: Bool, +ss: String) -> String: 328 match ok: 329 case True{}: 330 ss 331 case False{}: 332 key.basic(ss) 333 334 # a key as TOML writes it 335 def key(+ss: String) -> String: 336 key.go(bare(ss), ss) 337 338 # the overflow of a decimal step, least-significant digit first 339 def dec.carry.c0(zz: Bool, cc: U32) -> List<&2, U32>: 340 match zz: 341 case True{}: 342 [] 343 case False{}: 344 [U32.mod(cc, 10)] 345 346 # the overflow of a decimal step 347 def dec.carry.c(+cc: U32) -> List<&2, U32>: 348 dec.carry.c0(U32.is_zero(cc), cc) 349 350 # the next digit of an overflow 351 def dec.carry.b0(zz: Bool, +cc: U32) -> List<&2, U32>: 352 match zz: 353 case True{}: 354 [] 355 case False{}: 356 U32.mod(cc, 10) <> dec.carry.c(U32.div(cc, 10)) 357 358 # the next digit of an overflow 359 def dec.carry.b(+cc: U32) -> List<&2, U32>: 360 dec.carry.b0(U32.is_zero(cc), cc) 361 362 # the first digit of an overflow 363 def dec.carry.a(zz: Bool, +cc: U32) -> List<&2, U32>: 364 match zz: 365 case True{}: 366 [] 367 case False{}: 368 U32.mod(cc, 10) <> dec.carry.b(U32.div(cc, 10)) 369 370 # the digits of a leftover carry 371 def dec.carry(+cc: U32) -> List<&2, U32>: 372 dec.carry.a(U32.is_zero(cc), cc) 373 374 # multiply a decimal numeral by a radix and add a digit 375 def dec.go(ds: List<&2, U32>, +radix: U32, +carry: U32) -> List<&2, U32>: 376 match ds: 377 case []: 378 dec.carry(carry) 379 case +d <> t: 380 U32.mod(U32.add(U32.mul(d, radix), carry), 10) <> dec.go(t, radix, 381 U32.div(U32.add(U32.mul(d, radix), carry), 10)) 382 383 # a digit character 384 def dec.ch(dd: U32) -> Char: 385 Chr{U32.add(48, dd)} 386 387 # digits as characters, in the order given 388 def dec.chars(ds: List<&2, U32>) -> List<&2, Char>: 389 match ds: 390 case []: 391 [] 392 case d <> t: 393 dec.ch(d) <> dec.chars(t) 394 395 # whether a leading zero is still being skipped 396 def dec.drop.lead(lead: Bool, +hh: Char) -> Bool: 397 match lead: 398 case False{}: 399 False{} 400 case True{}: 401 Char.is_eq(hh, '0') 402 403 # a leading zero is skipped; any other character is kept 404 def dec.drop.outz(zz: Bool, hh: Char, out: List<&2, Char>) -> List<&2, Char>: 405 match zz: 406 case True{}: 407 out 408 case False{}: 409 hh <> out 410 411 # the output after one character, reversed 412 def dec.drop.out(lead: Bool, +hh: Char, out: List<&2, Char>) -> List<&2, Char>: 413 match lead: 414 case False{}: 415 hh <> out 416 case True{}: 417 dec.drop.outz(Char.is_eq(hh, '0'), hh, out) 418 419 # the numeral, or 0 when every digit was zero 420 def dec.drop.end(lead: Bool, out: List<&2, Char>) -> String: 421 match lead: 422 case True{}: 423 "0" 424 case False{}: 425 buf.str(out) 426 427 # drop leading zeros from a most-significant-first digit list 428 def dec.drop(cs: List<&2, Char>, +lead: Bool, out: List<&2, Char>) -> String: 429 match cs: 430 case []: 431 dec.drop.end(lead, out) 432 case +h <> t: 433 dec.drop(t, dec.drop.lead(lead, h), dec.drop.out(lead, h, out)) 434 435 # a decimal numeral. The digit list is least-significant first 436 def dec.text(ds: List<&2, U32>) -> String: 437 dec.drop(dec.chars(List.reverse(&2, U32, ds)), True{}, []) 438 439 # a code point's digit value, or 99 when the character is not a digit 440 def dig.up(ok: Bool, uu: U32) -> U32: 441 match ok: 442 case True{}: 443 U32.sub(uu, 55) 444 case False{}: 445 99 446 447 # a lowercase hex digit, otherwise an uppercase one 448 def dig.low(ok: Bool, +uu: U32) -> U32: 449 match ok: 450 case True{}: 451 U32.sub(uu, 87) 452 case False{}: 453 dig.up(Bool.and(U32.is_ge(uu, 65), U32.is_le(uu, 70)), uu) 454 455 # a decimal digit, otherwise a hex letter 456 def dig.dec(ok: Bool, +uu: U32) -> U32: 457 match ok: 458 case True{}: 459 U32.sub(uu, 48) 460 case False{}: 461 dig.low(Bool.and(U32.is_ge(uu, 97), U32.is_le(uu, 102)), uu) 462 463 # the value of a digit character, or 99 464 def dig.val(+cc: Char) -> U32: 465 +u = Char.to_u32(cc) 466 dig.dec(Bool.and(U32.is_ge(u, 48), U32.is_le(u, 57)), u) 467 468 # whether a digit value fits a base 469 def dig.ok(dd: U32, base: U32) -> Bool: 470 U32.is_lt(dd, base) 471 472 # exactly k digits, or DigBad 473 def take.acc.d(digit: Bool, nn: U32, +cc: Char) -> U32: 474 match digit: 475 case False{}: 476 nn 477 case True{}: 478 U32.add(U32.mul(nn, 10), U32.sub(Char.to_u32(cc), 48)) 479 480 # the accumulator after one character 481 def take.acc(ok: Bool, nn: U32, +cc: Char) -> U32: 482 match ok: 483 case False{}: 484 nn 485 case True{}: 486 take.acc.d(Char.is_digit(cc), nn, cc) 487 488 # the scan stays well only while every character is a digit 489 def take.ok(ok: Bool, +cc: Char) -> Bool: 490 match ok: 491 case False{}: 492 False{} 493 case True{}: 494 Char.is_digit(cc) 495 496 # the finished take 497 def take.fin(ok: Bool, nn: U32, cs: List<&2, Char>) -> Dig: 498 match ok: 499 case True{}: 500 DigOk{nn, cs} 501 case False{}: 502 DigBad{} 503 504 # exactly k digits from the front of a list 505 def take.n(kk: Nat, cs: List<&2, Char>, nn: U32, +ok: Bool) -> Dig: 506 match kk: 507 case 0n: 508 take.fin(ok, nn, cs) 509 case 1n+p: 510 match cs: 511 case []: 512 DigBad{} 513 case +h <> t: 514 take.n(p, t, take.acc(ok, nn, h), take.ok(ok, h)) 515 516 # a number is inside the inclusive bounds 517 def bound.rng(+nn: U32, lo: U32, hi: U32) -> Bool: 518 Bool.and(U32.is_ge(nn, lo), U32.is_le(nn, hi)) 519 520 # two digits, zero padded 521 def pad2.go(small: Bool, nn: U32) -> String: 522 match small: 523 case True{}: 524 "0" ++ U32.show(nn) 525 case False{}: 526 U32.show(nn) 527 528 # two digits, zero padded 529 def text.pad2(+nn: U32) -> String: 530 pad2.go(U32.is_lt(nn, 10), nn) 531 532 # the one-zero pad, or the digits themselves 533 def pad4.big(nn: U32, big: Bool) -> String: 534 match big: 535 case True{}: 536 "0" ++ U32.show(nn) 537 case False{}: 538 U32.show(nn) 539 540 # the three-digit and two-digit pads 541 def pad4.mid(nn: U32, mid: Bool, big: Bool) -> String: 542 match mid: 543 case True{}: 544 "00" ++ U32.show(nn) 545 case False{}: 546 pad4.big(nn, big) 547 548 # four digits, zero padded 549 def pad4.go(nn: U32, small: Bool, mid: Bool, big: Bool) -> String: 550 match small: 551 case True{}: 552 "000" ++ U32.show(nn) 553 case False{}: 554 pad4.mid(nn, mid, big) 555 556 # four digits, zero padded 557 def text.pad4(+nn: U32) -> String: 558 pad4.go(nn, U32.is_lt(nn, 10), U32.is_lt(nn, 100), U32.is_lt(nn, 1000)) 559 560 # the sign of a zone offset 561 def zone.mark(sign: Sign) -> String: 562 match sign: 563 case Plus{}: 564 "+" 565 case Minus{}: 566 "-" 567 568 # a numeric zone offset 569 def zone.shift(sign: Sign, hh: U32, mi: U32) -> String: 570 zone.mark(sign) ++ text.pad2(hh) ++ ":" ++ text.pad2(mi) 571 572 # a zone suffix 573 def zone.text(zz: Zone) -> String: 574 match zz: 575 case Zulu{}: 576 "Z" 577 case Shift{sign, h, mi}: 578 zone.shift(sign, h, mi) 579 580 # the fractional second, or nothing 581 def clock.frac.go(empty: Bool, frac: String) -> String: 582 match empty: 583 case True{}: 584 "" 585 case False{}: 586 "." ++ frac 587 588 # the fractional second, or nothing 589 def clock.frac(+frac: String) -> String: 590 clock.frac.go(String.is_empty(frac), frac) 591 592 # a clock, with a fractional second when it has one 593 def clock.text(cc: Clock) -> String: 594 Clock{h, mi, s, frac} = cc 595 text.pad2(h) ++ ":" ++ text.pad2(mi) ++ ":" ++ text.pad2(s) ++ clock.frac(frac) 596 597 # a date 598 def date.text(dd: Date) -> String: 599 Date{y, mo, day} = dd 600 text.pad4(y) ++ "-" ++ text.pad2(mo) ++ "-" ++ text.pad2(day) 601 602 # a datetime in TOML spelling 603 def when.text(ww: When) -> String: 604 match ww: 605 case WOff{date, clock, zone}: 606 date.text(date) ++ "T" ++ clock.text(clock) ++ zone.text(zone) 607 case WLocal{date, clock}: 608 date.text(date) ++ "T" ++ clock.text(clock) 609 case WDate{date}: 610 date.text(date) 611 case WTime{clock}: 612 clock.text(clock) 613 614 # the sign of an integer or a float, empty when it is positive 615 def sign.text(sign: Sign) -> String: 616 match sign: 617 case Plus{}: 618 "" 619 case Minus{}: 620 "-" 621 622 # whether the list still has a character 623 def cs.some(cs: List<&2, Char>) -> Bool: 624 match cs: 625 case []: 626 False{} 627 case _ <> _: 628 True{} 629 630 # one character, when the list has one 631 def cs.head(cs: List<&2, Char>) -> Char: 632 match cs: 633 case []: 634 ' ' 635 case h <> _: 636 h 637 638 # the tail of a list 639 def cs.tail(cs: List<&2, Char>) -> List<&2, Char>: 640 match cs: 641 case []: 642 [] 643 case _ <> t: 644 t 645 646 # a clock with its fraction filled in 647 def clock.with(cc: Clock, frac: String) -> Clock: 648 Clock{h, mi, s, _f} = cc 649 Clock{h, mi, s, frac} 650 651 # a finished local datetime, or one with a zone still to read 652 def when.done(_date: Date, _clock: Clock, ww: When) -> WhenOut: 653 WSome{ww} 654 655 # Z ends the token 656 def when.zone.z(tt: List<&2, Char>, clock: Clock, date: Date) -> WhenOut: 657 match tt: 658 case []: 659 WSome{WOff{date, clock, Zulu{}}} 660 case _ <> _: 661 WFail{"invalid zone"} 662 663 # the offset minute is in range 664 def when.zone.mm.ok2(mi_ok: Bool, sign: Sign, hh: U32, mi: U32, clock: Clock, date: Date) -> WhenOut: 665 match mi_ok: 666 case False{}: 667 WFail{"zone minute is out of range"} 668 case True{}: 669 WSome{WOff{date, clock, Shift{sign, hh, mi}}} 670 671 # the offset fields are in range 672 def when.zone.mm.ok(h_ok: Bool, mi_ok: Bool, sign: Sign, hh: U32, mi: U32, clock: Clock, date: Date) -> WhenOut: 673 match h_ok: 674 case False{}: 675 WFail{"zone hour is out of range"} 676 case True{}: 677 when.zone.mm.ok2(mi_ok, sign, hh, mi, clock, date) 678 679 # nothing follows an offset, and its fields are in range 680 def when.zone.mm.end( 681 cs: List<&2, Char>, 682 h_ok: Bool, 683 mi_ok: Bool, 684 sign: Sign, 685 hh: U32, 686 mi: U32, 687 clock: Clock, 688 date: Date 689 ) -> WhenOut: 690 match cs: 691 case []: 692 when.zone.mm.ok(h_ok, mi_ok, sign, hh, mi, clock, date) 693 case _ <> _: 694 WFail{"invalid zone"} 695 696 # the offset minute is in range 697 def when.zone.mm.go(dd: Dig, sign: Sign, +hh: U32, clock: Clock, date: Date) -> WhenOut: 698 match dd: 699 case DigBad{}: 700 WFail{"invalid zone"} 701 case DigOk{+n, cs}: 702 when.zone.mm.end(cs, bound.rng(hh, 0, 23), bound.rng(n, 0, 59), sign, hh, n, clock, date) 703 704 # the offset minute 705 def when.zone.mm(hit: Bool, dd: Dig, sign: Sign, hh: U32, clock: Clock, date: Date) -> WhenOut: 706 match hit: 707 case False{}: 708 WFail{"invalid zone"} 709 case True{}: 710 when.zone.mm.go(dd, sign, hh, clock, date) 711 712 # the colon in an offset 713 def when.zone.hh.colon(cs: List<&2, Char>, sign: Sign, hh: U32, clock: Clock, date: Date) -> WhenOut: 714 match cs: 715 case []: 716 WFail{"invalid zone"} 717 case +c <> t: 718 when.zone.mm(Char.is_eq(c, ':'), take.n(2n, t, 0, True{}), sign, hh, clock, date) 719 720 # the offset hour 721 def when.zone.hh(dd: Dig, sign: Sign, clock: Clock, date: Date) -> WhenOut: 722 match dd: 723 case DigBad{}: 724 WFail{"invalid zone"} 725 case DigOk{n, cs}: 726 when.zone.hh.colon(cs, sign, n, clock, date) 727 728 # HH:MM of a numeric offset 729 def when.zone.off(sign: Sign, tt: List<&2, Char>, clock: Clock, date: Date) -> WhenOut: 730 when.zone.hh(take.n(2n, tt, 0, True{}), sign, clock, date) 731 732 # a minus starts a numeric offset 733 def when.zone.minus(minus: Bool, tt: List<&2, Char>, clock: Clock, date: Date) -> WhenOut: 734 match minus: 735 case True{}: 736 when.zone.off(Minus{}, tt, clock, date) 737 case False{}: 738 WFail{"invalid zone"} 739 740 # a plus or a minus starts a numeric offset 741 def when.zone.sign(plus: Bool, minus: Bool, tt: List<&2, Char>, clock: Clock, date: Date) -> WhenOut: 742 match plus: 743 case True{}: 744 when.zone.off(Plus{}, tt, clock, date) 745 case False{}: 746 when.zone.minus(minus, tt, clock, date) 747 748 # Z, or a signed numeric offset 749 def when.zone.go(zee: Bool, plus: Bool, minus: Bool, tt: List<&2, Char>, clock: Clock, date: Date) -> WhenOut: 750 match zee: 751 case True{}: 752 when.zone.z(tt, clock, date) 753 case False{}: 754 when.zone.sign(plus, minus, tt, clock, date) 755 756 # a zone, or the end of a local datetime 757 def when.zone(cs: List<&2, Char>, clock: Clock, date: Date) -> WhenOut: 758 match cs: 759 case []: 760 WSome{WLocal{date, clock}} 761 case +c <> t: 762 when.zone.go(Bool.or(Char.is_eq(c, 'Z'), Char.is_eq(c, 'z')), 763 Char.is_eq(c, '+'), Char.is_eq(c, '-'), t, clock, date) 764 765 # the fraction must hold a digit 766 def when.frac.end.go(some: Bool, rev: List<&2, Char>, clock: Clock, date: Date, rest: List<&2, Char>) -> WhenOut: 767 match some: 768 case False{}: 769 WFail{"invalid fraction"} 770 case True{}: 771 when.zone(rest, clock.with(clock, buf.str(rev)), 772 date) 773 774 # the fraction must hold a digit 775 def when.frac.end(+rev: List<&2, Char>, clock: Clock, date: Date, rest: List<&2, Char>) -> WhenOut: 776 when.frac.end.go(cs.some(rev), rev, clock, date, rest) 777 778 # one fractional digit, or the end of the fraction 779 def when.frac.digit( 780 digit: Bool, 781 +cc: Char, 782 tt: List<&2, Char>, 783 rev: List<&2, Char>, 784 clock: Clock, 785 date: Date, 786 more: Unit -> WhenOut 787 ) -> WhenOut: 788 match digit: 789 case True{}: 790 more(Unit{}) 791 case False{}: 792 when.frac.end(rev, clock, date, cc <> tt) 793 794 # fractional digits 795 def when.frac.digits(cs: List<&2, Char>, +rev: List<&2, Char>, +clock: Clock, +date: Date) -> WhenOut: 796 match cs: 797 case []: 798 when.frac.end(rev, clock, date, []) 799 case +c <> +t: 800 when.frac.digit(Char.is_digit(c), c, t, rev, clock, date, 801 _u => when.frac.digits(t, c <> rev, clock, date)) 802 803 # a dot starts a fraction; anything else is a zone 804 def when.frac.go(dot: Bool, tt: List<&2, Char>, clock: Clock, date: Date, +cc: Char) -> WhenOut: 805 match dot: 806 case True{}: 807 when.frac.digits(tt, [], clock, date) 808 case False{}: 809 when.zone(cc <> tt, clock, date) 810 811 # an optional fractional second 812 def when.frac(cs: List<&2, Char>, +clock: Clock, +date: Date) -> WhenOut: 813 match cs: 814 case []: 815 when.done(date, clock, WLocal{date, clock}) 816 case +c <> t: 817 when.frac.go(Char.is_eq(c, '.'), t, clock, date, c) 818 819 # the second is in range, then the fraction and the zone 820 def when.clock.range.s(s_ok: Bool, hh: U32, mi: U32, ss: U32, cs: List<&2, Char>, date: Date) -> WhenOut: 821 match s_ok: 822 case False{}: 823 WFail{"second is out of range"} 824 case True{}: 825 when.frac(cs, Clock{hh, mi, ss, ""}, date) 826 827 # minute and second are in range 828 def when.clock.range.m(mi_ok: Bool, s_ok: Bool, hh: U32, mi: U32, ss: U32, cs: List<&2, Char>, date: Date) -> WhenOut: 829 match mi_ok: 830 case False{}: 831 WFail{"minute is out of range"} 832 case True{}: 833 when.clock.range.s(s_ok, hh, mi, ss, cs, date) 834 835 # hour, minute, and second are in range 836 def when.clock.range( 837 h_ok: Bool, 838 mi_ok: Bool, 839 s_ok: Bool, 840 hh: U32, 841 mi: U32, 842 ss: U32, 843 cs: List<&2, Char>, 844 date: Date 845 ) -> WhenOut: 846 match h_ok: 847 case False{}: 848 WFail{"hour is out of range"} 849 case True{}: 850 when.clock.range.m(mi_ok, s_ok, hh, mi, ss, cs, date) 851 852 # seconds, then an optional fraction and zone 853 def when.clock.ss.go(dd: Dig, +hh: U32, +mi: U32, date: Date) -> WhenOut: 854 match dd: 855 case DigBad{}: 856 WFail{"invalid time"} 857 case DigOk{+n, cs}: 858 when.clock.range(bound.rng(hh, 0, 23), bound.rng(mi, 0, 59), bound.rng(n, 0, 60), hh, mi, n, 859 cs, date) 860 861 # seconds after the colon 862 def when.clock.ss(hit: Bool, dd: Dig, hh: U32, mi: U32, date: Date) -> WhenOut: 863 match hit: 864 case False{}: 865 WFail{"invalid time"} 866 case True{}: 867 when.clock.ss.go(dd, hh, mi, date) 868 869 # the colon after the minutes 870 def when.clock.mm.colon(cs: List<&2, Char>, hh: U32, mi: U32, date: Date) -> WhenOut: 871 match cs: 872 case []: 873 WFail{"invalid time"} 874 case +c <> t: 875 when.clock.ss(Char.is_eq(c, ':'), take.n(2n, t, 0, True{}), hh, mi, date) 876 877 # minutes, then seconds 878 def when.clock.mm(dd: Dig, hh: U32, date: Date) -> WhenOut: 879 match dd: 880 case DigBad{}: 881 WFail{"invalid time"} 882 case DigOk{n, cs}: 883 when.clock.mm.colon(cs, hh, n, date) 884 885 # the colon after the hour 886 def when.clock.colon.go(hit: Bool, hh: U32, tt: List<&2, Char>, date: Date) -> WhenOut: 887 match hit: 888 case False{}: 889 WFail{"invalid time"} 890 case True{}: 891 when.clock.mm(take.n(2n, tt, 0, True{}), hh, date) 892 893 # the colon after the hour 894 def when.clock.colon(cs: List<&2, Char>, hh: U32, date: Date) -> WhenOut: 895 match cs: 896 case []: 897 WFail{"invalid time"} 898 case +c <> t: 899 when.clock.colon.go(Char.is_eq(c, ':'), hh, t, date) 900 901 # a clock following a date 902 def when.clock.of(dd: Dig, date: Date) -> WhenOut: 903 match dd: 904 case DigBad{}: 905 WFail{"invalid time"} 906 case DigOk{n, cs}: 907 when.clock.colon(cs, n, date) 908 909 # a space separates a date from a time 910 def when.after.sp(sp: Bool, date: Date, tt: List<&2, Char>) -> WhenOut: 911 match sp: 912 case True{}: 913 when.clock.of(take.n(2n, tt, 0, True{}), date) 914 case False{}: 915 WFail{"invalid datetime"} 916 917 # T or a space separates a date from a time 918 def when.after.sep(tee: Bool, sp: Bool, date: Date, tt: List<&2, Char>) -> WhenOut: 919 match tee: 920 case True{}: 921 when.clock.of(take.n(2n, tt, 0, True{}), date) 922 case False{}: 923 when.after.sp(sp, date, tt) 924 925 # the character after a date: nothing, T, or a space, and then a time 926 def when.after.date(date: Date, cs: List<&2, Char>) -> WhenOut: 927 match cs: 928 case []: 929 WSome{WDate{date}} 930 case +c <> t: 931 when.after.sep(Bool.or(Char.is_eq(c, 'T'), Char.is_eq(c, 't')), 932 Char.is_eq(c, ' '), date, t) 933 934 # the day is in range, then the rest of the token is a time or nothing 935 def when.date.ok.d(d_ok: Bool, yy: U32, mo: U32, dd: U32, cs: List<&2, Char>) -> WhenOut: 936 match d_ok: 937 case False{}: 938 WFail{"day is out of range"} 939 case True{}: 940 when.after.date(Date{yy, mo, dd}, cs) 941 942 # a date whose fields are in range 943 def when.date.ok.go(mo_ok: Bool, d_ok: Bool, yy: U32, mo: U32, dd: U32, cs: List<&2, Char>) -> WhenOut: 944 match mo_ok: 945 case False{}: 946 WFail{"month is out of range"} 947 case True{}: 948 when.date.ok.d(d_ok, yy, mo, dd, cs) 949 950 # February in a year divisible by 4 and by 100: 29 days only when the year 951 # is also divisible by 400 952 def when.feb.quad(four_hundred: Bool) -> U32: 953 match four_hundred: 954 case True{}: 955 29 956 case False{}: 957 28 958 959 # February in a year divisible by 4: 29 days unless the year is a century 960 def when.feb.cent(hundred: Bool, four_hundred: Bool) -> U32: 961 match hundred: 962 case False{}: 963 29 964 case True{}: 965 when.feb.quad(four_hundred) 966 967 # February: 29 days in a leap year and 28 otherwise. A leap year is divisible 968 # by 4, except by 100 unless also by 400 (RFC 3339, Appendix C) 969 def when.feb.go(four: Bool, hundred: Bool, four_hundred: Bool) -> U32: 970 match four: 971 case False{}: 972 28 973 case True{}: 974 when.feb.cent(hundred, four_hundred) 975 976 # the days of February in a year 977 def when.feb(+yy: U32) -> U32: 978 when.feb.go(U32.is_eq(U32.mod(yy, 4), 0), U32.is_eq(U32.mod(yy, 100), 0), 979 U32.is_eq(U32.mod(yy, 400), 0)) 980 981 # April, June, September and November have 30 days 982 def when.short(+mo: U32) -> Bool: 983 Bool.or(Bool.or(U32.is_eq(mo, 4), U32.is_eq(mo, 6)), 984 Bool.or(U32.is_eq(mo, 9), U32.is_eq(mo, 11))) 985 986 # 30 days in a short month and 31 in the others 987 def when.days.short(short: Bool) -> U32: 988 match short: 989 case True{}: 990 30 991 case False{}: 992 31 993 994 # the days of a month, by whether it is February or a short month 995 def when.days.go(feb: Bool, short: Bool, +yy: U32) -> U32: 996 match feb: 997 case True{}: 998 when.feb(yy) 999 case False{}: 1000 when.days.short(short) 1001 1002 # the days of month mo in year yy 1003 def when.days(+yy: U32, +mo: U32) -> U32: 1004 when.days.go(U32.is_eq(mo, 2), when.short(mo), yy) 1005 1006 # a date whose fields are in range: the day within its month in that year 1007 def when.date.ok(+yy: U32, +mo: U32, +dd: U32, cs: List<&2, Char>) -> WhenOut: 1008 when.date.ok.go(bound.rng(mo, 1, 12), bound.rng(dd, 1, when.days(yy, mo)), yy, mo, dd, cs) 1009 1010 # a local time: HH:MM:SS, with the hour already taken 1011 def when.time.rest(tt: List<&2, Char>, hh: U32) -> WhenOut: 1012 when.clock.mm(take.n(2n, tt, 0, True{}), hh, Date{0, 1, 1}) 1013 1014 # a local time uses a dummy date and then keeps only the clock 1015 def when.time.only(ww: WhenOut) -> WhenOut: 1016 match ww: 1017 case WSome{WLocal{_date, clock}}: 1018 WSome{WTime{clock}} 1019 case WSome{WOff{_date, clock, zone}}: 1020 WFail{"invalid time"} 1021 case WSome{other}: 1022 WFail{"invalid time"} 1023 case WFail{why}: 1024 WFail{why} 1025 case WSkip{}: 1026 WFail{"invalid time"} 1027 1028 # a complete date, then whatever follows it 1029 def when.day.end(dd: Dig, yy: U32, mo: U32) -> WhenOut: 1030 match dd: 1031 case DigBad{}: 1032 WFail{"invalid date"} 1033 case DigOk{n, cs}: 1034 when.date.ok(yy, mo, n, cs) 1035 1036 # the day is two digits 1037 def when.day.go(hit: Bool, dd: Dig, yy: U32, mo: U32) -> WhenOut: 1038 match hit: 1039 case False{}: 1040 WFail{"invalid date"} 1041 case True{}: 1042 when.day.end(dd, yy, mo) 1043 1044 # the day 1045 def when.day(cs: List<&2, Char>, yy: U32, mo: U32) -> WhenOut: 1046 match cs: 1047 case []: 1048 WFail{"invalid date"} 1049 case +c <> t: 1050 when.day.go(Char.is_eq(c, '-'), take.n(2n, t, 0, True{}), yy, mo) 1051 1052 # the dash after the month 1053 def when.month.go(dd: Dig, yy: U32) -> WhenOut: 1054 match dd: 1055 case DigBad{}: 1056 WFail{"invalid date"} 1057 case DigOk{n, cs}: 1058 when.day(cs, yy, n) 1059 1060 # the month 1061 def when.month(hit: Bool, dd: Dig, yy: U32) -> WhenOut: 1062 match hit: 1063 case False{}: 1064 WFail{"invalid date"} 1065 case True{}: 1066 when.month.go(dd, yy) 1067 1068 # the dash after the year 1069 def when.year.dash(cs: List<&2, Char>, yy: U32) -> WhenOut: 1070 match cs: 1071 case []: 1072 WFail{"invalid date"} 1073 case +c <> t: 1074 when.month(Char.is_eq(c, '-'), take.n(2n, t, 0, True{}), yy) 1075 1076 # the fourth year digit, then the month 1077 def when.year4(dd: Dig) -> WhenOut: 1078 match dd: 1079 case DigBad{}: 1080 WFail{"invalid date"} 1081 case DigOk{n, cs}: 1082 when.year.dash(cs, n) 1083 1084 # two more year digits, then a dash 1085 def when.year.more(digit: Bool, nn: U32, +cc: Char, tt: List<&2, Char>) -> WhenOut: 1086 match digit: 1087 case False{}: 1088 WFail{"invalid datetime"} 1089 case True{}: 1090 when.year4(take.n(1n, tt, U32.add(U32.mul(nn, 10), dig.val(cc)), True{})) 1091 1092 # a colon means a local time; another digit continues a year 1093 def when.branch(colon: Bool, digit: Bool, nn: U32, +cc: Char, tt: List<&2, Char>) -> WhenOut: 1094 match colon: 1095 case True{}: 1096 when.time.only(when.time.rest(tt, nn)) 1097 case False{}: 1098 when.year.more(digit, nn, cc, tt) 1099 1100 # two digits decide a time or a date 1101 def when.after2.go(cs: List<&2, Char>, nn: U32) -> WhenOut: 1102 match cs: 1103 case []: 1104 WFail{"invalid datetime"} 1105 case +c <> t: 1106 when.branch(Char.is_eq(c, ':'), Char.is_digit(c), nn, c, t) 1107 1108 # two digits decide a time (HH:) or a date (YYYY-) 1109 def when.after2(dd: Dig) -> WhenOut: 1110 match dd: 1111 case DigBad{}: 1112 WFail{"invalid datetime"} 1113 case DigOk{n, cs}: 1114 when.after2.go(cs, n) 1115 1116 # a datetime token 1117 def when.read(cs: List<&2, Char>) -> WhenOut: 1118 when.after2(take.n(2n, cs, 0, True{})) 1119 1120 # a dash after the first character 1121 def word.dash.hit(first: Bool, +cc: Char) -> Bool: 1122 match first: 1123 case True{}: 1124 False{} 1125 case False{}: 1126 Char.is_eq(cc, '-') 1127 1128 # either flag 1129 def word.or(aa: Bool, bb: Bool) -> Bool: 1130 match aa: 1131 case True{}: 1132 True{} 1133 case False{}: 1134 bb 1135 1136 # a colon anywhere in the token 1137 def word.colon(cs: List<&2, Char>) -> Bool: 1138 match cs: 1139 case []: 1140 False{} 1141 case +c <> t: 1142 word.or(Char.is_eq(c, ':'), word.colon(t)) 1143 1144 # e or E: an exponent, so the token is a numeral rather than a datetime 1145 def word.ee.hit(+cc: Char) -> Bool: 1146 Bool.or(Char.is_eq(cc, 'e'), Char.is_eq(cc, 'E')) 1147 1148 # whether the token holds an exponent marker 1149 def word.ee(cs: List<&2, Char>) -> Bool: 1150 match cs: 1151 case []: 1152 False{} 1153 case +c <> t: 1154 word.or(word.ee.hit(c), word.ee(t)) 1155 1156 # a datetime scan only when the token is not an exponent numeral 1157 def word.as_date(date: Bool, exp: Bool) -> Bool: 1158 match exp: 1159 case True{}: 1160 False{} 1161 case False{}: 1162 date 1163 1164 # a dash after the first character 1165 def word.dash(cs: List<&2, Char>, first: Bool) -> Bool: 1166 match cs: 1167 case []: 1168 False{} 1169 case +c <> t: 1170 word.or(word.dash.hit(first, c), word.dash(t, False{})) 1171 1172 # a negative nan, or not a special 1173 def word.spec.nan3(cc: Bool) -> Got: 1174 match cc: 1175 case True{}: 1176 GotVal{VFlo{Minus{}, "nan"}} 1177 case False{}: 1178 GotBad{""} 1179 1180 # a signed nan 1181 def word.spec.nan2(bb: Bool, cc: Bool) -> Got: 1182 match bb: 1183 case True{}: 1184 GotVal{VFlo{Plus{}, "nan"}} 1185 case False{}: 1186 word.spec.nan3(cc) 1187 1188 # nan 1189 def word.spec.nan(aa: Bool, bb: Bool, cc: Bool) -> Got: 1190 match aa: 1191 case True{}: 1192 GotVal{VFlo{Plus{}, "nan"}} 1193 case False{}: 1194 word.spec.nan2(bb, cc) 1195 1196 # character-for-character equality. a mismatch drops the rest and stops 1197 def str.eq.step(aa: String, bb: String, same: Bool) -> Bool: 1198 match aa bb same: 1199 case SNil{} SNil{} True{}: 1200 True{} 1201 case SCon{x, xt} SCon{y, yt} True{}: 1202 str.eq.step(xt, yt, Char.is_eq(x, y)) 1203 case _a _b _s: 1204 False{} 1205 1206 # the search only needs the bool, not a rebuilt copy of either string 1207 def str.eq(aa: String, bb: String) -> Bool: 1208 str.eq.step(aa, bb, True{}) 1209 1210 # a negative inf, or nan 1211 def word.spec.inf3(cc: Bool, +ss: String) -> Got: 1212 match cc: 1213 case True{}: 1214 GotVal{VFlo{Minus{}, "inf"}} 1215 case False{}: 1216 word.spec.nan(str.eq(ss, "nan"), str.eq(ss, "+nan"), str.eq(ss, "-nan")) 1217 1218 # a signed inf, or nan 1219 def word.spec.inf2(bb: Bool, cc: Bool, +ss: String) -> Got: 1220 match bb: 1221 case True{}: 1222 GotVal{VFlo{Plus{}, "inf"}} 1223 case False{}: 1224 word.spec.inf3(cc, ss) 1225 1226 # inf, or nan 1227 def word.spec.inf(aa: Bool, bb: Bool, cc: Bool, +ss: String) -> Got: 1228 match aa: 1229 case True{}: 1230 GotVal{VFlo{Plus{}, "inf"}} 1231 case False{}: 1232 word.spec.inf2(bb, cc, ss) 1233 1234 # false, or the rest of the specials 1235 def word.spec.f(hit: Bool, +ss: String) -> Got: 1236 match hit: 1237 case True{}: 1238 GotVal{VBool{False{}}} 1239 case False{}: 1240 word.spec.inf(str.eq(ss, "inf"), str.eq(ss, "+inf"), str.eq(ss, "-inf"), ss) 1241 1242 # true, or the rest of the specials 1243 def word.spec.t(hit: Bool, other: Bool, +ss: String) -> Got: 1244 match hit: 1245 case True{}: 1246 GotVal{VBool{True{}}} 1247 case False{}: 1248 word.spec.f(other, ss) 1249 1250 # true, false, inf, and nan, including a sign on inf and nan 1251 def word.spec(+ss: String) -> Got: 1252 word.spec.t(str.eq(ss, "true"), str.eq(ss, "false"), ss) 1253 1254 # a datetime result as a value 1255 def word.from.when(ww: WhenOut) -> Got: 1256 match ww: 1257 case WSome{when}: 1258 GotVal{VWhen{when}} 1259 case WFail{why}: 1260 GotBad{why} 1261 case WSkip{}: 1262 GotBad{"invalid datetime"} 1263 1264 # the spelling of a numeral with the leading sign and underscores removed 1265 def word.body.put.sign(is_sign: Bool, cc: Char, out: List<&2, Char>) -> List<&2, Char>: 1266 match is_sign: 1267 case True{}: 1268 out 1269 case False{}: 1270 cc <> out 1271 1272 # an underscore is dropped; every other character is kept 1273 def word.body.put.us(is_us: Bool, cc: Char, out: List<&2, Char>) -> List<&2, Char>: 1274 match is_us: 1275 case True{}: 1276 out 1277 case False{}: 1278 cc <> out 1279 1280 # one character of a numeral spelling 1281 def word.body.put(lead: Bool, +cc: Char, out: List<&2, Char>) -> List<&2, Char>: 1282 match lead: 1283 case True{}: 1284 word.body.put.sign(Bool.or(Char.is_eq(cc, '+'), Char.is_eq(cc, '-')), cc, out) 1285 case False{}: 1286 word.body.put.us(Char.is_eq(cc, '_'), cc, out) 1287 1288 # the spelling, reversed, then put back in order at the end 1289 def word.body.go(cs: List<&2, Char>, lead: Bool, out: List<&2, Char>) -> List<&2, Char>: 1290 match cs: 1291 case []: 1292 List.reverse(&2, Char, out) 1293 case +c <> t: 1294 word.body.go(t, False{}, word.body.put(lead, c, out)) 1295 1296 # the spelling of a numeral with the leading sign and underscores removed 1297 def word.body(cs: List<&2, Char>) -> String: 1298 String.from_list(word.body.go(cs, True{}, [])) 1299 1300 # a fresh numeral scan 1301 def num.start() -> Num: 1302 Num{NLead{}, Plus{}, 10, [], False{}, False{}, ""} 1303 1304 # a scan that has failed 1305 def num.bad(_st: Num, why: String) -> Num: 1306 Num{NBad{}, Plus{}, 10, [], False{}, False{}, why} 1307 1308 # set the sign and wait for a digit 1309 def num.sign(_st: Num, sign: Sign) -> Num: 1310 Num{NSign{}, sign, 10, [], False{}, False{}, ""} 1311 1312 # a digit that fits the base 1313 def num.digit.ok(ok: Bool, +cc: Char, nk: NK, sign: Sign, +base: U32, ds: List<&2, U32>) -> Num: 1314 match ok: 1315 case False{}: 1316 num.bad(Num{NLead{}, Plus{}, 10, [], False{}, False{}, ""}, "invalid digit") 1317 case True{}: 1318 Num{nk, sign, base, dec.go(ds, base, dig.val(cc)), False{}, True{}, ""} 1319 1320 # a decimal digit joined to the numeral 1321 def num.digit.go(st: Num, +cc: Char, nk: NK) -> Num: 1322 Num{_nk, sign, +base, ds, _us, _saw, _why} = st 1323 num.digit.ok(dig.ok(dig.val(cc), base), cc, nk, sign, base, ds) 1324 1325 # a decimal digit joined to the numeral 1326 def num.digit(st: Num, +cc: Char, nk: NK) -> Num: 1327 num.digit.go(st, cc, nk) 1328 1329 # an underscore between digits 1330 def num.us.go(ok: Bool, nk: NK, sign: Sign, base: U32, ds: List<&2, U32>, why: String) -> Num: 1331 match ok: 1332 case False{}: 1333 num.bad(Num{NLead{}, Plus{}, 10, [], False{}, False{}, ""}, "invalid underscore") 1334 case True{}: 1335 Num{nk, sign, base, ds, True{}, True{}, why} 1336 1337 # an underscore between digits 1338 def num.us(st: Num) -> Num: 1339 Num{nk, sign, base, ds, us, saw, why} = st 1340 num.us.go(Bool.and(saw, Bool.not(us)), nk, sign, base, ds, why) 1341 1342 # the base prefix after a leading zero 1343 def num.base(st: Num, base: U32) -> Num: 1344 Num{_nk, sign, _b, _ds, _us, _saw, _why} = st 1345 Num{NBase{}, sign, base, [], False{}, False{}, ""} 1346 1347 # a dot begins the fraction 1348 def num.dot.go(ok: Bool, sign: Sign) -> Num: 1349 match ok: 1350 case False{}: 1351 num.bad(Num{NLead{}, Plus{}, 10, [], False{}, False{}, ""}, "invalid fraction") 1352 case True{}: 1353 Num{NFrac{}, sign, 10, [], False{}, False{}, ""} 1354 1355 # a dot begins the fraction 1356 def num.dot(st: Num) -> Num: 1357 Num{_nk, sign, _b, _ds, us, saw, _why} = st 1358 num.dot.go(Bool.and(saw, Bool.not(us)), sign) 1359 1360 # an exponent marker 1361 def num.exp.go(ok: Bool, sign: Sign) -> Num: 1362 match ok: 1363 case False{}: 1364 num.bad(Num{NLead{}, Plus{}, 10, [], False{}, False{}, ""}, "invalid exponent") 1365 case True{}: 1366 Num{NExp{}, sign, 10, [], False{}, False{}, ""} 1367 1368 # an exponent marker 1369 def num.exp(st: Num) -> Num: 1370 Num{_nk, sign, _b, _ds, us, saw, _why} = st 1371 num.exp.go(Bool.and(saw, Bool.not(us)), sign) 1372 1373 # the sign already chosen 1374 def num.sign.of(st: Num) -> Sign: 1375 Num{_nk, sign, _b, _ds, _us, _saw, _why} = st 1376 sign 1377 1378 # a first digit 1379 def num.lead.digit(digit: Bool, st: Num, +cc: Char) -> Num: 1380 match digit: 1381 case True{}: 1382 num.digit(st, cc, NDig{}) 1383 case False{}: 1384 num.bad(st, "invalid number") 1385 1386 # a leading zero, or another digit 1387 def num.lead.zero(zero: Bool, digit: Bool, st: Num, +cc: Char) -> Num: 1388 match zero: 1389 case True{}: 1390 Num{NZero{}, num.sign.of(st), 10, [], False{}, True{}, ""} 1391 case False{}: 1392 num.lead.digit(digit, st, cc) 1393 1394 # a minus, a zero, or a digit 1395 def num.lead.minus(minus: Bool, zero: Bool, digit: Bool, st: Num, +cc: Char) -> Num: 1396 match minus: 1397 case True{}: 1398 num.sign(st, Minus{}) 1399 case False{}: 1400 num.lead.zero(zero, digit, st, cc) 1401 1402 # a sign, a zero, or a digit 1403 def num.lead.go(plus: Bool, minus: Bool, zero: Bool, digit: Bool, st: Num, +cc: Char) -> Num: 1404 match plus: 1405 case True{}: 1406 num.sign(st, Plus{}) 1407 case False{}: 1408 num.lead.minus(minus, zero, digit, st, cc) 1409 1410 # the first character after an optional sign 1411 def num.lead(st: Num, +cc: Char) -> Num: 1412 num.lead.go(Char.is_eq(cc, '+'), Char.is_eq(cc, '-'), Char.is_eq(cc, '0'), 1413 Char.is_digit(cc), st, cc) 1414 1415 # a second sign is refused; any other character is read as the first one of 1416 # an unsigned numeral, with the sign kept 1417 def num.after.sign.go(twice: Bool, sign: Sign, +cc: Char) -> Num: 1418 match twice: 1419 case True{}: 1420 num.bad(num.start(), "invalid number") 1421 case False{}: 1422 num.lead(Num{NLead{}, sign, 10, [], False{}, False{}, ""}, cc) 1423 1424 # the character after a sign 1425 def num.after.sign(st: Num, +cc: Char) -> Num: 1426 num.after.sign.go(Bool.or(Char.is_eq(cc, '+'), Char.is_eq(cc, '-')), num.sign.of(st), cc) 1427 1428 # a capital exponent; any other character is refused, since a digit or an 1429 # underscore after a leading zero (`01`, `0_1`) would hide it 1430 def num.zero.exp2(ee: Bool, st: Num, _c: Char) -> Num: 1431 match ee: 1432 case True{}: 1433 num.exp(st) 1434 case False{}: 1435 num.bad(st, "invalid number") 1436 1437 # an exponent after zero, or a rejected extra digit 1438 def num.zero.exp(eee: Bool, ee: Bool, st: Num, +cc: Char) -> Num: 1439 match eee: 1440 case True{}: 1441 num.exp(st) 1442 case False{}: 1443 num.zero.exp2(ee, st, cc) 1444 1445 # a fraction or an exponent after zero 1446 def num.zero.dot(dot: Bool, eee: Bool, ee: Bool, st: Num, +cc: Char) -> Num: 1447 match dot: 1448 case True{}: 1449 num.dot(st) 1450 case False{}: 1451 num.zero.exp(eee, ee, st, cc) 1452 1453 # a binary prefix, or a float 1454 def num.zero.b(bb: Bool, dot: Bool, eee: Bool, ee: Bool, st: Num, +cc: Char) -> Num: 1455 match bb: 1456 case True{}: 1457 num.base(st, 2) 1458 case False{}: 1459 num.zero.dot(dot, eee, ee, st, cc) 1460 1461 # an octal prefix, or the rest 1462 def num.zero.o(oo: Bool, bb: Bool, dot: Bool, eee: Bool, ee: Bool, st: Num, +cc: Char) -> Num: 1463 match oo: 1464 case True{}: 1465 num.base(st, 8) 1466 case False{}: 1467 num.zero.b(bb, dot, eee, ee, st, cc) 1468 1469 # a base prefix, a fraction, or an exponent 1470 def num.zero.go(xx: Bool, oo: Bool, bb: Bool, dot: Bool, eee: Bool, ee: Bool, st: Num, +cc: Char) -> Num: 1471 match xx: 1472 case True{}: 1473 num.base(st, 16) 1474 case False{}: 1475 num.zero.o(oo, bb, dot, eee, ee, st, cc) 1476 1477 # the character after a lone zero 1478 def num.zero(st: Num, +cc: Char) -> Num: 1479 num.zero.go(Char.is_eq(cc, 'x'), Char.is_eq(cc, 'o'), Char.is_eq(cc, 'b'), 1480 Char.is_eq(cc, '.'), Char.is_eq(cc, 'e'), Char.is_eq(cc, 'E'), st, cc) 1481 1482 # a capital exponent, or a rejected character 1483 def num.digs.exp2(ee: Bool, st: Num, _c: Char) -> Num: 1484 match ee: 1485 case True{}: 1486 num.exp(st) 1487 case False{}: 1488 num.bad(st, "invalid number") 1489 1490 # an exponent 1491 def num.digs.exp(eee: Bool, ee: Bool, st: Num, +cc: Char) -> Num: 1492 match eee: 1493 case True{}: 1494 num.exp(st) 1495 case False{}: 1496 num.digs.exp2(ee, st, cc) 1497 1498 # a dot or an exponent 1499 def num.digs.dot(dot: Bool, eee: Bool, ee: Bool, st: Num, +cc: Char) -> Num: 1500 match dot: 1501 case True{}: 1502 num.dot(st) 1503 case False{}: 1504 num.digs.exp(eee, ee, st, cc) 1505 1506 # an underscore, a dot, or an exponent 1507 def num.digs.us(us: Bool, dot: Bool, eee: Bool, ee: Bool, st: Num, +cc: Char) -> Num: 1508 match us: 1509 case True{}: 1510 num.us(st) 1511 case False{}: 1512 num.digs.dot(dot, eee, ee, st, cc) 1513 1514 # a decimal digit, an underscore, a dot, or an exponent 1515 def num.digs.go(digit: Bool, us: Bool, dot: Bool, eee: Bool, ee: Bool, st: Num, +cc: Char) -> Num: 1516 match digit: 1517 case True{}: 1518 num.digit(st, cc, NDig{}) 1519 case False{}: 1520 num.digs.us(us, dot, eee, ee, st, cc) 1521 1522 # a decimal digit, an underscore, a dot, or an exponent 1523 def num.digs(st: Num, +cc: Char) -> Num: 1524 num.digs.go(Char.is_digit(cc), Char.is_eq(cc, '_'), Char.is_eq(cc, '.'), 1525 Char.is_eq(cc, 'e'), Char.is_eq(cc, 'E'), st, cc) 1526 1527 # an underscore, or a digit of the base 1528 def num.based.go(us: Bool, st: Num, +cc: Char) -> Num: 1529 match us: 1530 case True{}: 1531 num.us(st) 1532 case False{}: 1533 num.digit(st, cc, NBase{}) 1534 1535 # a digit of a prefixed integer, or an underscore 1536 def num.based(st: Num, +cc: Char) -> Num: 1537 num.based.go(Char.is_eq(cc, '_'), st, cc) 1538 1539 # a capital exponent, or a rejected character 1540 def num.frac.exp2(ee: Bool, st: Num, _c: Char) -> Num: 1541 match ee: 1542 case True{}: 1543 num.exp(st) 1544 case False{}: 1545 num.bad(st, "invalid fraction") 1546 1547 # an exponent in a fraction 1548 def num.frac.exp(eee: Bool, ee: Bool, st: Num, +cc: Char) -> Num: 1549 match eee: 1550 case True{}: 1551 num.exp(st) 1552 case False{}: 1553 num.frac.exp2(ee, st, cc) 1554 1555 # an underscore or an exponent in a fraction 1556 def num.frac.us(us: Bool, eee: Bool, ee: Bool, st: Num, +cc: Char) -> Num: 1557 match us: 1558 case True{}: 1559 num.us(st) 1560 case False{}: 1561 num.frac.exp(eee, ee, st, cc) 1562 1563 # a fraction digit, an underscore, or an exponent 1564 def num.frac.go(digit: Bool, us: Bool, eee: Bool, ee: Bool, st: Num, +cc: Char) -> Num: 1565 match digit: 1566 case True{}: 1567 num.digit(st, cc, NFrac{}) 1568 case False{}: 1569 num.frac.us(us, eee, ee, st, cc) 1570 1571 # a fraction digit, an underscore, or an exponent 1572 def num.frac(st: Num, +cc: Char) -> Num: 1573 num.frac.go(Char.is_digit(cc), Char.is_eq(cc, '_'), Char.is_eq(cc, 'e'), 1574 Char.is_eq(cc, 'E'), st, cc) 1575 1576 # the exponent sign is part of the spelling; digits follow 1577 def num.exp.sign(st: Num) -> Num: 1578 Num{_nk, sign, _b, _ds, _us, _saw, _why} = st 1579 Num{NEDig{}, sign, 10, [], False{}, False{}, ""} 1580 1581 # a digit of the exponent 1582 def num.expon.digit(digit: Bool, st: Num, +cc: Char) -> Num: 1583 match digit: 1584 case True{}: 1585 num.digit(st, cc, NEDig{}) 1586 case False{}: 1587 num.bad(st, "invalid exponent") 1588 1589 # a minus or a digit in an exponent 1590 def num.expon.minus(minus: Bool, digit: Bool, st: Num, +cc: Char) -> Num: 1591 match minus: 1592 case True{}: 1593 num.exp.sign(st) 1594 case False{}: 1595 num.expon.digit(digit, st, cc) 1596 1597 # the sign or the first digit of an exponent 1598 def num.expon.go(plus: Bool, minus: Bool, digit: Bool, st: Num, +cc: Char) -> Num: 1599 match plus: 1600 case True{}: 1601 num.exp.sign(st) 1602 case False{}: 1603 num.expon.minus(minus, digit, st, cc) 1604 1605 # the sign or the first digit of an exponent 1606 def num.expon(st: Num, +cc: Char) -> Num: 1607 num.expon.go(Char.is_eq(cc, '+'), Char.is_eq(cc, '-'), Char.is_digit(cc), st, cc) 1608 1609 # an underscore in an exponent 1610 def num.edig.us(us: Bool, st: Num, _c: Char) -> Num: 1611 match us: 1612 case True{}: 1613 num.us(st) 1614 case False{}: 1615 num.bad(st, "invalid exponent") 1616 1617 # an exponent digit or an underscore 1618 def num.edig.go(digit: Bool, us: Bool, st: Num, +cc: Char) -> Num: 1619 match digit: 1620 case True{}: 1621 num.digit(st, cc, NEDig{}) 1622 case False{}: 1623 num.edig.us(us, st, cc) 1624 1625 # an exponent digit or an underscore 1626 def num.edig(st: Num, +cc: Char) -> Num: 1627 num.edig.go(Char.is_digit(cc), Char.is_eq(cc, '_'), st, cc) 1628 1629 # one character of a numeral 1630 def num.on(nk: NK, st: Num, +cc: Char) -> Num: 1631 match nk: 1632 case NLead{}: 1633 num.lead(st, cc) 1634 case NSign{}: 1635 num.after.sign(st, cc) 1636 case NZero{}: 1637 num.zero(st, cc) 1638 case NDig{}: 1639 num.digs(st, cc) 1640 case NBase{}: 1641 num.based(st, cc) 1642 case NFrac{}: 1643 num.frac(st, cc) 1644 case NExp{}: 1645 num.expon(st, cc) 1646 case NEDig{}: 1647 num.edig(st, cc) 1648 case NBad{}: 1649 st 1650 1651 # one character of a numeral 1652 def num.step.go(st: Num, +cc: Char) -> Num: 1653 Num{+nk, sign, base, ds, us, saw, why} = st 1654 num.on(nk, Num{nk, sign, base, ds, us, saw, why}, cc) 1655 1656 # one character of a numeral 1657 def num.step(st: Num, +cc: Char) -> Num: 1658 num.step.go(st, cc) 1659 1660 # the walk over a numeral 1661 def num.go(cs: List<&2, Char>, st: Num) -> Num: 1662 match cs: 1663 case []: 1664 st 1665 case +h <> t: 1666 num.go(t, num.step(st, h)) 1667 1668 # the positive limit is 2^63-1; the negative limit is 2^63 1669 def i64.limit(sign: Sign) -> String: 1670 match sign: 1671 case Plus{}: 1672 "9223372036854775807" 1673 case Minus{}: 1674 "9223372036854775808" 1675 1676 # the first differing digit stands; a tie takes the new order 1677 def i64.ord(seen: Cmp, now: Cmp) -> Cmp: 1678 match seen now: 1679 case EQ{} n: 1680 n 1681 case LT{} _n: 1682 LT{} 1683 case GT{} _n: 1684 GT{} 1685 1686 # a shorter digit string fits and a longer one does not. equal length uses 1687 # digit order. the walk does not rebuild either string 1688 def i64.step(+digits: String, +limit: String, ord: Cmp) -> Bool: 1689 match digits limit: 1690 case SNil{} SNil{}: 1691 Cmp.is_le(ord) 1692 case SNil{} SCon{_h, _t}: 1693 True{} 1694 case SCon{_h, _t} SNil{}: 1695 False{} 1696 case SCon{x, xt} SCon{y, yt}: 1697 i64.step(xt, yt, i64.ord(ord, U32.cmp(Char.to_u32(x), Char.to_u32(y)))) 1698 1699 # whether the integer fits in a signed 64-bit integer 1700 def i64.fits(+sign: Sign, +digits: String) -> Bool: 1701 i64.step(digits, i64.limit(sign), EQ{}) 1702 1703 # an integer inside the signed 64-bit range 1704 def num.fin.range(ok: Bool, sign: Sign, digits: String) -> Got: 1705 match ok: 1706 case True{}: 1707 GotVal{VInt{sign, digits}} 1708 case False{}: 1709 GotBad{"out of range"} 1710 1711 # a finished integer, rejected when it falls outside signed 64-bit 1712 def num.fin.int(saw: Bool, +sign: Sign, +digits: String) -> Got: 1713 match saw: 1714 case False{}: 1715 GotBad{"invalid number"} 1716 case True{}: 1717 num.fin.range(i64.fits(sign, digits), sign, digits) 1718 1719 # a numeral needs a digit in its last run 1720 def num.fin.saw(saw: Bool, val: Val) -> Got: 1721 match saw: 1722 case False{}: 1723 GotBad{"invalid number"} 1724 case True{}: 1725 GotVal{val} 1726 1727 # a prefixed integer may not carry a sign 1728 def num.signed(cs: List<&2, Char>) -> Bool: 1729 match cs: 1730 case []: 1731 False{} 1732 case +c <> _: 1733 Bool.or(Char.is_eq(c, '+'), Char.is_eq(c, '-')) 1734 1735 # a prefixed integer, rejected when a sign was written 1736 def num.fin.base(signed: Bool, saw: Bool, sign: Sign, ds: List<&2, U32>) -> Got: 1737 match signed: 1738 case True{}: 1739 GotBad{"invalid number"} 1740 case False{}: 1741 num.fin.int(saw, sign, dec.text(ds)) 1742 1743 # the kind of numeral the scan finished in 1744 def num.fin.nk(nk: NK, sign: Sign, ds: List<&2, U32>, saw: Bool, why: String, cs: List<&2, Char>) -> Got: 1745 match nk: 1746 case NBad{}: 1747 GotBad{why} 1748 case NLead{}: 1749 GotBad{"invalid number"} 1750 case NSign{}: 1751 GotBad{"invalid number"} 1752 case NZero{}: 1753 GotVal{VInt{sign, "0"}} 1754 case NDig{}: 1755 num.fin.int(saw, sign, dec.text(ds)) 1756 case NBase{}: 1757 num.fin.base(num.signed(cs), saw, sign, ds) 1758 case NFrac{}: 1759 num.fin.saw(saw, VFlo{sign, word.body(cs)}) 1760 case NExp{}: 1761 GotBad{"invalid exponent"} 1762 case NEDig{}: 1763 num.fin.saw(saw, VFlo{sign, word.body(cs)}) 1764 1765 # a trailing underscore is rejected 1766 def num.fin.us(us: Bool, nk: NK, sign: Sign, ds: List<&2, U32>, saw: Bool, why: String, cs: List<&2, Char>) -> Got: 1767 match us: 1768 case True{}: 1769 GotBad{"invalid underscore"} 1770 case False{}: 1771 num.fin.nk(nk, sign, ds, saw, why, cs) 1772 1773 # the value of a finished numeral 1774 def num.fin(st: Num, cs: List<&2, Char>) -> Got: 1775 Num{nk, sign, _base, ds, us, saw, why} = st 1776 num.fin.us(us, nk, sign, ds, saw, why, cs) 1777 1778 # a numeral token 1779 def word.num(+cs: List<&2, Char>) -> Got: 1780 num.fin(num.go(cs, num.start()), cs) 1781 1782 # a datetime when the token has a colon or a dash, otherwise a numeral 1783 def word.route.date(date: Bool, cs: List<&2, Char>) -> Got: 1784 match date: 1785 case True{}: 1786 word.from.when(when.read(cs)) 1787 case False{}: 1788 word.num(cs) 1789 1790 # a special word, or a numeral 1791 def word.route(gg: Got, date: Bool, +cs: List<&2, Char>) -> Got: 1792 match gg: 1793 case GotVal{val}: 1794 GotVal{val} 1795 case GotBad{_}: 1796 word.route.date(word.as_date(date, word.ee(cs)), cs) 1797 1798 # '-' can start a signed inf or nan 1799 def word.special.minus(+cc: Char) -> Bool: 1800 Char.is_eq(cc, '-') 1801 1802 # '+' or '-' 1803 def word.special.plus(hit: Bool, +cc: Char) -> Bool: 1804 match hit: 1805 case True{}: 1806 True{} 1807 case False{}: 1808 word.special.minus(cc) 1809 1810 # 'n', or a sign 1811 def word.special.n(hit: Bool, +cc: Char) -> Bool: 1812 match hit: 1813 case True{}: 1814 True{} 1815 case False{}: 1816 word.special.plus(Char.is_eq(cc, '+'), cc) 1817 1818 # 'i', or nan, or a sign 1819 def word.special.i(hit: Bool, +cc: Char) -> Bool: 1820 match hit: 1821 case True{}: 1822 True{} 1823 case False{}: 1824 word.special.n(Char.is_eq(cc, 'n'), cc) 1825 1826 # 'f', or inf, or nan, or a sign 1827 def word.special.f(hit: Bool, +cc: Char) -> Bool: 1828 match hit: 1829 case True{}: 1830 True{} 1831 case False{}: 1832 word.special.i(Char.is_eq(cc, 'i'), cc) 1833 1834 # 't', or false, or inf, or nan, or a sign 1835 def word.special.t(hit: Bool, +cc: Char) -> Bool: 1836 match hit: 1837 case True{}: 1838 True{} 1839 case False{}: 1840 word.special.f(Char.is_eq(cc, 'f'), cc) 1841 1842 # whether this character can start a special bare word 1843 def word.special(+cc: Char) -> Bool: 1844 word.special.t(Char.is_eq(cc, 't'), cc) 1845 1846 # the first character, when the word has one 1847 def word.lead(cs: List<&2, Char>) -> Bool: 1848 match cs: 1849 case []: 1850 False{} 1851 case +h <> _t: 1852 word.special(h) 1853 1854 # a numeral or a datetime. the token cannot be a keyword 1855 def word.val.num(+cs: List<&2, Char>) -> Got: 1856 word.route(GotBad{""}, word.or(word.colon(cs), word.dash(cs, True{})), cs) 1857 1858 # a special word, or a numeral. a digit does not build a string to compare 1859 def word.val.pick(special: Bool, +cs: List<&2, Char>) -> Got: 1860 match special: 1861 case True{}: 1862 word.route(word.spec(String.from_list(cs)), 1863 word.or(word.colon(cs), word.dash(cs, True{})), cs) 1864 case False{}: 1865 word.val.num(cs) 1866 1867 # a bare word: a special, a datetime, or a numeral 1868 def word.val(+cs: List<&2, Char>) -> Got: 1869 word.val.pick(word.lead(cs), cs) 1870 1871 # a change applied at the end of a key path 1872 type Job is Data: 1873 JPut{val: Val} 1874 JDef{} 1875 JAot{} 1876 1877 # one table the walk has entered 1878 type Fr is Data: 1879 FrNew{name: String, parent: List<&2, Val>, path: String} 1880 FrRep{name: String, parent: List<&2, Val>, path: String} 1881 FrAot{name: String, parent: List<&2, Val>, pre: List<&2, Val>, path: String} 1882 1883 # the walk: the table being edited, and the frames above it 1884 type Tw is Data: 1885 Tw{vs: List<&2, Val>, job: Job, stack: List<&2, Fr>, bad: String, pre: String, 1886 heads: List<&2, String>, inl: Bool} 1887 1888 # the root table after a walk 1889 type End is Data: 1890 End{vs: List<&2, Val>, bad: String, heads: List<&2, String>} 1891 1892 # what a key in a table refers to 1893 type Look is Data: 1894 KMiss{} 1895 KHead{path: String, rows: List<&2, Val>} 1896 KInl{} 1897 KAots{path: String, elems: List<&2, Val>} 1898 KNo{why: String} 1899 1900 # the last element of an array of tables 1901 type Last is Data: 1902 LastSome{path: String, pre: List<&2, Val>, rows: List<&2, Val>} 1903 LastNo{why: String} 1904 1905 # the first error is kept 1906 def err.first.go(empty: Bool, bad: String, why: String) -> String: 1907 match empty: 1908 case True{}: 1909 why 1910 case False{}: 1911 bad 1912 1913 # the first error is kept 1914 def err.first(+bad: String, why: String) -> String: 1915 err.first.go(String.is_empty(bad), bad, why) 1916 1917 # a key found earlier wins 1918 def find.step(eq: Bool, vv: Val, more: Unit -> Hit) -> Hit: 1919 match eq: 1920 case True{}: 1921 Found{vv} 1922 case False{}: 1923 more(Unit{}) 1924 1925 # the first pair of this name 1926 def rows.find(rows: List<&2, Val>, +name: String) -> Hit: 1927 match rows: 1928 case []: 1929 Miss{} 1930 case VPair{n, v} <> t: 1931 find.step(str.eq(n, name), v, _u => rows.find(t, name)) 1932 case _ <> t: 1933 rows.find(t, name) 1934 1935 # the value written for a key, or Miss 1936 def get(rows: List<&2, Val>, +name: String) -> Hit: 1937 rows.find(rows, name) 1938 1939 # replace the value of one pair 1940 def rows.repl.step(eq: Bool, nn: String, old: Val, val: Val, more: Unit -> List<&2, Val>) -> List<&2, Val>: 1941 match eq: 1942 case True{}: 1943 VPair{nn, val} <> more(Unit{}) 1944 case False{}: 1945 VPair{nn, old} <> more(Unit{}) 1946 1947 # replace every pair of this name 1948 def rows.repl(rows: List<&2, Val>, +name: String, +val: Val) -> List<&2, Val>: 1949 match rows: 1950 case []: 1951 [] 1952 case VPair{+n, old} <> t: 1953 rows.repl.step(str.eq(n, name), n, old, val, _u => rows.repl(t, name, val)) 1954 case h <> t: 1955 h <> rows.repl(t, name, val) 1956 1957 # either flag 1958 def heads.or(aa: Bool, bb: Bool) -> Bool: 1959 match aa: 1960 case True{}: 1961 True{} 1962 case False{}: 1963 bb 1964 1965 # heads records each table the document has defined as its path behind one 1966 # mark: `[` when a header defined it, `.` when dotted keys did 1967 def heads.by.head(path: String) -> String: 1968 SCon{'[', path} 1969 1970 # a table dotted keys defined, as heads records it 1971 def heads.by.dot(path: String) -> String: 1972 SCon{'.', path} 1973 1974 # whether a table path was defined, by a header or by dotted keys 1975 def heads.has(hs: List<&2, String>, +pp: String) -> Bool: 1976 match hs: 1977 case []: 1978 False{} 1979 case SCon{_mark, h} <> t: 1980 heads.or(str.eq(h, pp), heads.has(t, pp)) 1981 case SNil{} <> t: 1982 heads.has(t, pp) 1983 1984 # whether a table path was defined by a header 1985 def heads.head(hs: List<&2, String>, +pp: String) -> Bool: 1986 match hs: 1987 case []: 1988 False{} 1989 case SCon{mark, h} <> t: 1990 heads.or(Bool.and(Char.is_eq(mark, '['), str.eq(h, pp)), heads.head(t, pp)) 1991 case SNil{} <> t: 1992 heads.head(t, pp) 1993 1994 # a recorded table under the prefix is dropped 1995 def heads.drop.step( 1996 under: Bool, 1997 mark: Char, 1998 path: String, 1999 rest: List<&2, String> 2000 ) -> List<&2, String>: Lines 1-2000 of 6369. More: https://jend.leeeet.dev/read.txt?path=emerging-eztoml@0.9.0.0/main.bend&start_line=2001