1 # JSON values, parsed and encoded as RFC 8259. Source: https://github.com/paymog/bend-kit/tree/main/json 2 import Base 3 4 # JSON (RFC 8259) over text; decode bytes with Utf8 first. Nesting has no 5 # depth limit other than input size: the parser keeps its own stack, since a 6 # Bend def cannot call itself through another def. 7 # import ./json/json.bend as Json 8 9 # Num keeps the number's exact text, so no precision is lost. 10 type Val is Data: 11 Null{} 12 Flag{on: Bool} 13 Num{s: String} 14 Str{s: String} 15 Arr{xs: List<&2, Val>} 16 Obj{m: Map<&2, Val>} 17 18 type Next is Data: 19 Next{v: Val, rest: String} 20 21 # An open container: array items so far (reversed), or object fields and the 22 # key whose value comes next. 23 type Open is Data: 24 OArr{xs: List<&2, Val>} 25 OObj{m: Map<&2, Val>, key: String} 26 27 type St is Data: 28 SValue{s: String, stack: List<&2, Open>} 29 SKey{s: String, stack: List<&2, Open>} 30 SColon{key: String, s: String, stack: List<&2, Open>} 31 SStr{s: String, acc: String, key: Bool, stack: List<&2, Open>} 32 SAfter{v: Val, s: String, stack: List<&2, Open>} 33 SDone{v: Val, s: String} 34 SFail{} 35 36 # Whitespace 37 # ---------- 38 39 def is_ws(+c: U32) -> Bool: 40 Bool.or(U32.is_eq(c, 32), Bool.or(U32.is_eq(c, 9), Bool.or(U32.is_eq(c, 10), U32.is_eq(c, 13)))) 41 42 # Bool.pick runs both arms, so loops peek: the next char's class is a Bool 43 # param and only the matching arm recurses. 44 def ws.go(t: String, +c: U32, ws: Bool) -> String: 45 match t: 46 case SNil{}: 47 match ws: 48 case True{}: 49 SNil{} 50 case False{}: 51 SCon{Chr{c}, SNil{}} 52 case SCon{Chr{+d}, u}: 53 match ws: 54 case True{}: 55 ws.go(u, d, is_ws(d)) 56 case False{}: 57 SCon{Chr{c}, SCon{Chr{d}, u}} 58 59 def skip_ws(s: String) -> String: 60 match s: 61 case SNil{}: 62 SNil{} 63 case SCon{Chr{+c}, t}: 64 ws.go(t, c, is_ws(c)) 65 66 # Numbers (§6) 67 # ------------ 68 # States: 0 start, 1 after "-", 2 after a leading "0", 3 int digits, 4 after 69 # ".", 5 fraction digits, 6 after "e", 7 after the exponent sign, 8 exponent 70 # digits. 99 means the char does not continue the number. 71 72 def digit(+c: U32) -> Bool: 73 Bool.and(U32.is_le(48, c), U32.is_le(c, 57)) 74 75 def num.trans(+st: U32, +c: U32) -> U32: 76 +d = digit(c) 77 +z = U32.is_eq(c, 48) 78 +dot = U32.is_eq(c, 46) 79 +e = Bool.or(U32.is_eq(c, 101), U32.is_eq(c, 69)) 80 +sign = Bool.or(U32.is_eq(c, 43), U32.is_eq(c, 45)) 81 Bool.pick(U32, U32.is_eq(st, 0), Bool.pick(U32, U32.is_eq(c, 45), 1, Bool.pick(U32, z, 2, Bool.pick(U32, d, 3, 99))), 82 Bool.pick(U32, U32.is_eq(st, 1), Bool.pick(U32, z, 2, Bool.pick(U32, d, 3, 99)), 83 Bool.pick(U32, Bool.or(U32.is_eq(st, 2), U32.is_eq(st, 3)), Bool.pick(U32, Bool.and(d, U32.is_eq(st, 3)), 3, Bool.pick(U32, dot, 4, Bool.pick(U32, e, 6, 99))), 84 Bool.pick(U32, Bool.or(U32.is_eq(st, 4), U32.is_eq(st, 5)), Bool.pick(U32, d, 5, Bool.pick(U32, Bool.and(e, U32.is_eq(st, 5)), 6, 99)), 85 Bool.pick(U32, U32.is_eq(st, 6), Bool.pick(U32, sign, 7, Bool.pick(U32, d, 8, 99)), 86 Bool.pick(U32, Bool.or(U32.is_eq(st, 7), U32.is_eq(st, 8)), Bool.pick(U32, d, 8, 99), 99)))))) 87 88 def num.accepts(+st: U32) -> Bool: 89 Bool.or(Bool.or(U32.is_eq(st, 2), U32.is_eq(st, 3)), Bool.or(U32.is_eq(st, 5), U32.is_eq(st, 8))) 90 91 def num.end(+st: U32, acc: String, rest: String) -> Maybe<&2, Next>: 92 Bool.pick(Maybe<&2, Next>, num.accepts(st), Some{Next{Num{String.reverse(acc)}, rest}}, None{}) 93 94 def num.go(t: String, +c: U32, +st: U32, acc: String, stop: Bool) -> Maybe<&2, Next>: 95 match t: 96 case SNil{}: 97 match stop: 98 case True{}: 99 num.end(st, acc, SCon{Chr{c}, SNil{}}) 100 case False{}: 101 num.end(num.trans(st, c), SCon{Chr{c}, acc}, SNil{}) 102 case SCon{Chr{+d}, u}: 103 match stop: 104 case True{}: 105 num.end(st, acc, SCon{Chr{c}, SCon{Chr{d}, u}}) 106 case False{}: 107 +n = num.trans(st, c) 108 num.go(u, d, n, SCon{Chr{c}, acc}, U32.is_eq(num.trans(n, d), 99)) 109 110 def num(s: String) -> Maybe<&2, Next>: 111 match s: 112 case SNil{}: 113 None{} 114 case SCon{Chr{+c}, t}: 115 num.go(t, c, 0, SNil{}, U32.is_eq(num.trans(0, c), 99)) 116 117 # Literals 118 # -------- 119 120 def lit.if(+s: String, v: Val, n: Nat, ok: Bool) -> Maybe<&2, Next>: 121 match ok: 122 case False{}: 123 None{} 124 case True{}: 125 Some{Next{v, String.drop(s, n)}} 126 127 def lit(+s: String, word: String, v: Val, +n: Nat) -> Maybe<&2, Next>: 128 lit.if(s, v, n, String.starts_with(s, word)) 129 130 # Strings (§7) 131 # ------------ 132 133 type Esc is Data: 134 Esc{cp: U32, rest: String} 135 136 def hexd(+c: U32) -> U32: 137 Bool.pick(U32, digit(c), (c - 48 : U32), Bool.pick(U32, Bool.and(U32.is_le(97, c), U32.is_le(c, 102)), (c - 87 : U32), Bool.pick(U32, Bool.and(U32.is_le(65, c), U32.is_le(c, 70)), (c - 55 : U32), 99))) 138 139 def hex4.go(s: String, +acc: U32, n: Nat) -> Maybe<&2, Esc>: 140 match s: 141 case SNil{}: 142 match n: 143 case 0n: 144 Some{Esc{acc, SNil{}}} 145 case 1n+k: 146 None{} 147 case SCon{Chr{+c}, t}: 148 match n: 149 case 0n: 150 Some{Esc{acc, SCon{Chr{c}, t}}} 151 case 1n+k: 152 +h = hexd(c) 153 Bool.pick(Maybe<&2, Esc>, U32.is_eq(h, 99), None{}, hex4.go(t, (acc * 16 + h : U32), k)) 154 155 def hex4(s: String) -> Maybe<&2, Esc>: 156 hex4.go(s, 0, 4n) 157 158 def is_hi(+v: U32) -> Bool: 159 Bool.and(U32.is_le(55296, v), U32.is_le(v, 56319)) 160 161 def is_lo(+v: U32) -> Bool: 162 Bool.and(U32.is_le(56320, v), U32.is_le(v, 57343)) 163 164 def pair.lo(+hi: U32, +rest: String, lo: Maybe<&2, Esc>) -> Esc: 165 match lo: 166 case None{}: 167 Esc{65533, rest} 168 case Some{Esc{+v, r}}: 169 Bool.pick(Esc, is_lo(v), Esc{(65536 + (hi - 55296 : U32) * 1024 + (v - 56320 : U32) : U32), r}, Esc{65533, rest}) 170 171 # §8.2: a high surrogate pairs with a following \uDC00-\uDFFF; a lone one is U+FFFD. 172 def pair(+hi: U32, +rest: String) -> Esc: 173 Bool.pick(Esc, String.starts_with(rest, "\\u"), pair.lo(hi, rest, hex4(String.drop(rest, 2n))), Esc{65533, rest}) 174 175 def esc.u(e: Maybe<&2, Esc>) -> Maybe<&2, Esc>: 176 match e: 177 case None{}: 178 None{} 179 case Some{Esc{+v, +r}}: 180 Some{Bool.pick(Esc, is_hi(v), pair(v, r), Bool.pick(Esc, is_lo(v), Esc{65533, r}, Esc{v, r}))} 181 182 def esc.simple(+e: U32) -> U32: 183 Bool.pick(U32, U32.is_eq(e, 34), 34, Bool.pick(U32, U32.is_eq(e, 92), 92, Bool.pick(U32, U32.is_eq(e, 47), 47, 184 Bool.pick(U32, U32.is_eq(e, 98), 8, Bool.pick(U32, U32.is_eq(e, 102), 12, Bool.pick(U32, U32.is_eq(e, 110), 10, 185 Bool.pick(U32, U32.is_eq(e, 114), 13, Bool.pick(U32, U32.is_eq(e, 116), 9, 99)))))))) 186 187 # One escape after the backslash. 188 def esc.one(t: String) -> Maybe<&2, Esc>: 189 match t: 190 case SNil{}: 191 None{} 192 case SCon{Chr{+e}, +u}: 193 +v = esc.simple(e) 194 Bool.pick(Maybe<&2, Esc>, U32.is_eq(e, 117), esc.u(hex4(u)), Bool.pick(Maybe<&2, Esc>, U32.is_eq(v, 99), None{}, Some{Esc{v, u}})) 195 196 def str.esc(e: Maybe<&2, Esc>, acc: String, key: Bool, stack: List<&2, Open>) -> St: 197 match e: 198 case None{}: 199 SFail{} 200 case Some{Esc{cp, r}}: 201 SStr{r, SCon{Chr{cp}, acc}, key, stack} 202 203 def str.close(+acc: String, t: String, key: Bool, stack: List<&2, Open>) -> St: 204 match key: 205 case True{}: 206 SColon{String.reverse(acc), t, stack} 207 case False{}: 208 SAfter{Str{String.reverse(acc)}, t, stack} 209 210 def str.c(quote: Bool, ctl: Bool, bs: Bool, c: U32, t: String, acc: String, key: Bool, stack: List<&2, Open>) -> St: 211 match quote: 212 case True{}: 213 str.close(acc, t, key, stack) 214 case False{}: 215 match ctl: 216 case True{}: 217 SFail{} 218 case False{}: 219 match bs: 220 case True{}: 221 str.esc(esc.one(t), acc, key, stack) 222 case False{}: 223 SStr{t, SCon{Chr{c}, acc}, key, stack} 224 225 # §7: unescaped chars below U+0020 are not allowed in a string. 226 def str.step(s: String, acc: String, key: Bool, stack: List<&2, Open>) -> St: 227 match s: 228 case SNil{}: 229 SFail{} 230 case SCon{Chr{+c}, t}: 231 str.c(U32.is_eq(c, 34), U32.is_lt(c, 32), U32.is_eq(c, 92), c, t, acc, key, stack) 232 233 # Machine 234 # ------- 235 236 def got(m: Maybe<&2, Next>, stack: List<&2, Open>) -> St: 237 match m: 238 case None{}: 239 SFail{} 240 case Some{Next{v, r}}: 241 SAfter{v, r, stack} 242 243 def open.close(r: String, close: Val, stack: List<&2, Open>, empty: Bool, open: Open) -> St: 244 match empty: 245 case True{}: 246 SAfter{close, String.drop(r, 1n), stack} 247 case False{}: 248 match open: 249 case OArr{xs}: 250 SValue{r, Con{OArr{xs}, stack}} 251 case OObj{m, k}: 252 SKey{r, Con{OObj{m, k}, stack}} 253 254 def value.step(+s: String, +c: U32, +t: String, +stack: List<&2, Open>) -> St: 255 +r = skip_ws(t) 256 Bool.pick(St, U32.is_eq(c, 91), open.close(r, Arr{Nil{}}, stack, String.starts_with(r, "]"), OArr{Nil{}}), 257 Bool.pick(St, U32.is_eq(c, 123), open.close(r, Obj{Map.new(&2, Val)}, stack, String.starts_with(r, "}"), OObj{Map.new(&2, Val), ""}), 258 Bool.pick(St, U32.is_eq(c, 34), SStr{t, SNil{}, False{}, stack}, 259 Bool.pick(St, U32.is_eq(c, 116), got(lit(s, "true", Flag{True{}}, 4n), stack), 260 Bool.pick(St, U32.is_eq(c, 102), got(lit(s, "false", Flag{False{}}, 5n), stack), 261 Bool.pick(St, U32.is_eq(c, 110), got(lit(s, "null", Null{}, 4n), stack), 262 got(num(s), stack))))))) 263 264 def value(s: String, stack: List<&2, Open>) -> St: 265 match s: 266 case SNil{}: 267 SFail{} 268 case SCon{Chr{+c}, +t}: 269 value.step(SCon{Chr{c}, t}, c, t, stack) 270 271 def key(s: String, stack: List<&2, Open>) -> St: 272 match s: 273 case SNil{}: 274 SFail{} 275 case SCon{Chr{+c}, t}: 276 Bool.pick(St, U32.is_eq(c, 34), SStr{t, SNil{}, True{}, stack}, SFail{}) 277 278 def colon.top(k: String, r: String, stack: List<&2, Open>) -> St: 279 match stack: 280 case Nil{}: 281 SFail{} 282 case Con{OArr{xs}, up}: 283 SFail{} 284 case Con{OObj{m, old}, up}: 285 SValue{r, Con{OObj{m, k}, up}} 286 287 def colon(k: String, s: String, stack: List<&2, Open>) -> St: 288 match s: 289 case SNil{}: 290 SFail{} 291 case SCon{Chr{+c}, t}: 292 Bool.pick(St, U32.is_eq(c, 58), colon.top(k, t, stack), SFail{}) 293 294 # A finished value joins its container; then "," or the closing bracket. 295 def after.arr.c(comma: Bool, close: Bool, t: String, xs: List<&2, Val>, up: List<&2, Open>) -> St: 296 match comma: 297 case True{}: 298 SValue{t, Con{OArr{xs}, up}} 299 case False{}: 300 match close: 301 case True{}: 302 SAfter{Arr{List.reverse(&2, Val, xs)}, t, up} 303 case False{}: 304 SFail{} 305 306 def after.arr(xs: List<&2, Val>, r: String, up: List<&2, Open>) -> St: 307 match r: 308 case SNil{}: 309 SFail{} 310 case SCon{Chr{+c}, t}: 311 after.arr.c(U32.is_eq(c, 44), U32.is_eq(c, 93), t, xs, up) 312 313 def after.obj.c(comma: Bool, close: Bool, t: String, m: Map<&2, Val>, up: List<&2, Open>) -> St: 314 match comma: 315 case True{}: 316 SKey{t, Con{OObj{m, ""}, up}} 317 case False{}: 318 match close: 319 case True{}: 320 SAfter{Obj{m}, t, up} 321 case False{}: 322 SFail{} 323 324 def after.obj(m: Map<&2, Val>, r: String, up: List<&2, Open>) -> St: 325 match r: 326 case SNil{}: 327 SFail{} 328 case SCon{Chr{+c}, t}: 329 after.obj.c(U32.is_eq(c, 44), U32.is_eq(c, 125), t, m, up) 330 331 def after(v: Val, s: String, stack: List<&2, Open>) -> St: 332 match stack: 333 case Nil{}: 334 SDone{v, s} 335 case Con{OArr{xs}, up}: 336 after.arr(Con{v, xs}, skip_ws(s), up) 337 case Con{OObj{m, k}, up}: 338 after.obj(Map.set(&2, Val, m, k, v), skip_ws(s), up) 339 340 def step(st: St) -> St: 341 match st: 342 case SValue{s, stack}: 343 value(skip_ws(s), stack) 344 case SKey{s, stack}: 345 key(skip_ws(s), stack) 346 case SColon{k, s, stack}: 347 colon(k, skip_ws(s), stack) 348 case SStr{s, acc, k, stack}: 349 str.step(s, acc, k, stack) 350 case SAfter{v, s, stack}: 351 after(v, s, stack) 352 case SDone{v, s}: 353 SDone{v, s} 354 case SFail{}: 355 SFail{} 356 357 def done(v: Val, rest: String) -> Maybe<&2, Val>: 358 match rest: 359 case SNil{}: 360 Some{v} 361 case SCon{h, t}: 362 None{} 363 364 # fuel: every step but the last eats at least one char. 365 def run(fuel: Nat, st: St) -> Maybe<&2, Val>: 366 match fuel: 367 case 0n: 368 None{} 369 case 1n+f: 370 match st: 371 case SDone{v, s}: 372 done(v, skip_ws(s)) 373 case SFail{}: 374 None{} 375 case SValue{s, stack}: 376 run(f, step(SValue{s, stack})) 377 case SKey{s, stack}: 378 run(f, step(SKey{s, stack})) 379 case SColon{k, s, stack}: 380 run(f, step(SColon{k, s, stack})) 381 case SStr{s, acc, k, stack}: 382 run(f, step(SStr{s, acc, k, stack})) 383 case SAfter{v, s, stack}: 384 run(f, step(SAfter{v, s, stack})) 385 386 # One JSON text: a value with optional whitespace around it. 387 def parse(+s: String) -> Maybe<&2, Val>: 388 run(Nat.add(String.length(s), 2n), SValue{s, Nil{}}) 389 390 # Access 391 # ------ 392 393 def get.hit(r: Map<&2, Val> & Bool) -> Bool: 394 (m, b) = r 395 b 396 397 def get.some(r: Map<&2, Val> & Val) -> Maybe<&2, Val>: 398 (m, x) = r 399 Some{x} 400 401 def get.has(+m: Map<&2, Val>, +k: String, hit: Bool) -> Maybe<&2, Val>: 402 match hit: 403 case False{}: 404 None{} 405 case True{}: 406 get.some(Map.get(Val, Null{}, m, k)) 407 408 def get.of(+m: Map<&2, Val>, +k: String) -> Maybe<&2, Val>: 409 get.has(m, k, get.hit(Map.has(&2, Val, m, k))) 410 411 # The value under key k of an object. 412 def get(v: Val, k: String) -> Maybe<&2, Val>: 413 match v: 414 case Obj{m}: 415 get.of(m, k) 416 case Null{}: 417 None{} 418 case Flag{on}: 419 None{} 420 case Num{n}: 421 None{} 422 case Str{s}: 423 None{} 424 case Arr{xs}: 425 None{} 426 427 # Encode 428 # ------ 429 430 def hexc(+n: U32) -> Char: 431 Chr{Bool.pick(U32, U32.is_lt(n, 10), (48 + n : U32), (87 + n : U32))} 432 433 # The escaped form of one char, reversed (it is pushed onto a reversed acc). 434 def esc.put(+c: U32, +acc: String) -> String: 435 Bool.pick(String, U32.is_eq(c, 34), SCon{Chr{34}, SCon{Chr{92}, acc}}, 436 Bool.pick(String, U32.is_eq(c, 92), SCon{Chr{92}, SCon{Chr{92}, acc}}, 437 Bool.pick(String, U32.is_eq(c, 10), SCon{Chr{110}, SCon{Chr{92}, acc}}, 438 Bool.pick(String, U32.is_eq(c, 13), SCon{Chr{114}, SCon{Chr{92}, acc}}, 439 Bool.pick(String, U32.is_eq(c, 9), SCon{Chr{116}, SCon{Chr{92}, acc}}, 440 Bool.pick(String, U32.is_lt(c, 32), SCon{hexc(U32.and(c, 15)), SCon{hexc(U32.shrn(c, 4n)), SCon{Chr{48}, SCon{Chr{48}, SCon{Chr{117}, SCon{Chr{92}, acc}}}}}}, 441 SCon{Chr{c}, acc})))))) 442 443 def esc.go(s: String, acc: String) -> String: 444 match s: 445 case SNil{}: 446 String.reverse(acc) 447 case SCon{Chr{+c}, t}: 448 esc.go(t, esc.put(c, acc)) 449 450 def esc(s: String) -> String: 451 esc.go(s, SNil{}) 452 453 type Item is Data: 454 ILit{s: String} 455 IVal{v: Val} 456 457 def items.sep(first: Bool, tail: List<&2, Item>) -> List<&2, Item>: 458 match first: 459 case True{}: 460 tail 461 case False{}: 462 Con{ILit{","}, tail} 463 464 def items.arr(xs: List<&2, Val>, first: Bool, rest: List<&2, Item>) -> List<&2, Item>: 465 match xs: 466 case Nil{}: 467 Con{ILit{"]"}, rest} 468 case Con{x, t}: 469 items.sep(first, Con{IVal{x}, items.arr(t, False{}, rest)}) 470 471 def items.obj(kvs: List<&2, Sigma<&2, &2, String, _ => Val>>, first: Bool, rest: List<&2, Item>) -> List<&2, Item>: 472 match kvs: 473 case Nil{}: 474 Con{ILit{"}"}, rest} 475 case (k, v) <> t: 476 items.sep(first, Con{ILit{"\"" ++ esc(k) ++ "\":"}, Con{IVal{v}, items.obj(t, False{}, rest)}}) 477 478 def put(s: String, racc: String) -> String: 479 String.reverse(s) ++ racc 480 481 # ponytail: @unsafe because it walks a work list, not the Val; each step 482 # emits text or swaps a container for its strictly smaller parts. 483 @unsafe 484 def enc.go(items: List<&2, Item>, racc: String) -> String: 485 match items: 486 case Nil{}: 487 String.reverse(racc) 488 case Con{ILit{s}, t}: 489 enc.go(t, put(s, racc)) 490 case Con{IVal{Null{}}, t}: 491 enc.go(t, put("null", racc)) 492 case Con{IVal{Flag{True{}}}, t}: 493 enc.go(t, put("true", racc)) 494 case Con{IVal{Flag{False{}}}, t}: 495 enc.go(t, put("false", racc)) 496 case Con{IVal{Num{n}}, t}: 497 enc.go(t, put(n, racc)) 498 case Con{IVal{Str{s}}, t}: 499 enc.go(t, put("\"" ++ esc(s) ++ "\"", racc)) 500 case Con{IVal{Arr{xs}}, t}: 501 enc.go(Con{ILit{"["}, items.arr(xs, True{}, t)}, racc) 502 case Con{IVal{Obj{m}}, t}: 503 enc.go(Con{ILit{"{"}, items.obj(Map.to_list(&2, Val, m), True{}, t)}, racc) 504 505 # Compact JSON; object keys come out sorted. 506 def encode(v: Val) -> String: 507 enc.go(Con{IVal{v}, Nil{}}, "") 508 509 def at.go(xs: List<&2, Val>, n: Nat) -> Maybe<&2, Val>: 510 match xs: 511 case Nil{}: 512 None{} 513 case Con{v, t}: 514 match n: 515 case 0n: 516 Some{v} 517 case 1n+p: 518 at.go(t, p) 519 520 521 def at(v: Val, n: Nat) -> Maybe<&2, Val>: 522 match v: 523 case Arr{xs}: 524 at.go(xs, n) 525 case Null{}: 526 None{} 527 case Flag{on}: 528 None{} 529 case Num{s}: 530 None{} 531 case Str{s}: 532 None{} 533 case Obj{m}: 534 None{} 535 536 def u32.over(+acc: U32, +c: U32) -> Bool: 537 Bool.or(U32.is_lt(429496729, acc), Bool.and(U32.is_eq(acc, 429496729), U32.is_lt(53, c))) 538 539 def u32.dig(s: String, +acc: U32, bad: Bool) -> Maybe<&2, U32>: 540 match s: 541 case SNil{}: 542 match bad: 543 case True{}: 544 None{} 545 case False{}: 546 Some{acc} 547 case SCon{Chr{+c}, t}: 548 u32.dig(t, (acc * 10 + (c - 48 : U32) : U32), Bool.or(bad, Bool.or(Bool.not(digit(c)), u32.over(acc, c)))) 549 550 def u32.zero(t: String) -> Maybe<&2, U32>: 551 match t: 552 case SNil{}: 553 Some{0} 554 case SCon{d, u}: 555 None{} 556 557 def u32.lead(+c: U32, t: String, zero: Bool) -> Maybe<&2, U32>: 558 match zero: 559 case True{}: 560 u32.zero(t) 561 case False{}: 562 u32.dig(t, (c - 48 : U32), Bool.or(Bool.not(digit(c)), u32.over(0, c))) 563 564 def u32.num(s: String) -> Maybe<&2, U32>: 565 match s: 566 case SNil{}: 567 None{} 568 case SCon{Chr{+c}, t}: 569 u32.lead(c, t, U32.is_eq(c, 48)) 570 571 def u32(v: Val) -> Maybe<&2, U32>: 572 match v: 573 case Num{s}: 574 u32.num(s) 575 case Null{}: 576 None{} 577 case Flag{on}: 578 None{} 579 case Str{s}: 580 None{} 581 case Arr{xs}: 582 None{} 583 case Obj{m}: 584 None{}