~/bend-docscommunity

src/Decimal.bend source

src/Decimal.bend on the hub · documented module

import Base# Canonical bounded decimal support for compact storage formats.# The scanner consumes one character per recursive step and never splits input.type Error is Data:  Empty{}  LeadingZero{}  NonDigit{}  Overflow{}  MissingDelimiter{}type Stop is Data:  Delimited{delimiter: Char}  Complete{}type Parsed is Data:  Dec{value: Nat, rest: String}type Parse is Data:  Rejected{error: Error}  Accepted{parsed: Parsed}type Scan is Data:  Reading{value: Nat, started: Bool, leading_zero: Bool}  Finished{value: Nat}  Failed{error: Error}def render(num: Nat) -> String:  Nat.show(num)def is_digit_code(+code: U32) -> Bool:  Bool.and(U32.is_le(48, code), U32.is_le(code, 57))def digit_dec(ok: Bool, code: U32) -> Maybe<&2, Nat>:  match ok:    case False{}:      None{}    case True{}:      Some{U32.to_nat(U32.sub(code, 48))}def digit_of(ch: Char) -> Maybe<&2, Nat>:  +code = Char.to_u32(ch)  digit_dec(is_digit_code(code), code)def within_digit_limit(digit_ok: Bool, acc: Nat, digit: Nat, limit: Nat) -> Bool:  match digit_ok:    case False{}:      False{}    case True{}:      Nat.is_le(acc, Nat.div(Nat.sub(limit, digit), 10n))def can_accumulate(acc: Nat, +digit: Nat, +limit: Nat) -> Bool:  within_digit_limit(Nat.is_le(digit, limit), acc, digit, limit)def accumulated(ok: Bool, acc: Nat, digit: Nat, leading_zero: Bool) -> Scan:  match ok:    case False{}:      Failed{Overflow{}}    case True{}:      Reading{Nat.add(Nat.mul(acc, 10n), digit), True{}, leading_zero}def next_digit_zero(started: Bool, leading_zero: Bool, +acc: Nat, +digit: Nat, limit: Nat) -> Scan:  match started:    case False{}:      accumulated(can_accumulate(acc, digit, limit), acc, digit, True{})    case True{}:      match leading_zero:        case True{}:          Failed{LeadingZero{}}        case False{}:          accumulated(can_accumulate(acc, digit, limit), acc, digit, False{})def next_digit_nonzero(leading_zero: Bool, +acc: Nat, +digit: Nat, limit: Nat) -> Scan:  match leading_zero:    case True{}:      Failed{LeadingZero{}}    case False{}:      accumulated(can_accumulate(acc, digit, limit), acc, digit, False{})def next_digit_value(+digit: Nat, started: Bool, leading_zero: Bool, acc: Nat, limit: Nat) -> Scan:  match digit:    case 0n:      next_digit_zero(started, leading_zero, acc, 0n, limit)    case 1n+p:      next_digit_nonzero(leading_zero, acc, 1n+p, limit)def next_digit(+digit: Maybe<&2, Nat>, started: Bool, leading_zero: Bool, acc: Nat, limit: Nat) -> Scan:  match digit:    case None{}:      Failed{NonDigit{}}    case Some{d}:      next_digit_value(d, started, leading_zero, acc, limit)def finish_field(started: Bool, acc: Nat) -> Scan:  match started:    case False{}:      Failed{Empty{}}    case True{}:      Finished{acc}def delimited_step(is_delimiter: Bool, ch: Char, acc: Nat, started: Bool, leading_zero: Bool, limit: Nat) -> Scan:  match is_delimiter:    case True{}:      finish_field(started, acc)    case False{}:      next_digit(digit_of(ch), started, leading_zero, acc, limit)def scan_step(+ch: Char, st: Scan, limit: Nat, stop: Stop) -> Scan:  match st:    case Finished{value}:      Finished{value}    case Failed{error}:      Failed{error}    case Reading{value, started, leading_zero}:      match stop:        case Complete{}:          next_digit(digit_of(ch), started, leading_zero, value, limit)        case Delimited{delimiter}:          delimited_step(Char.is_eq(ch, delimiter), ch, value, started, leading_zero, limit)def complete_end(started: Bool, value: Nat, rest: String) -> Parse:  match started:    case False{}:      Rejected{Empty{}}    case True{}:      Accepted{Dec{value, rest}}def scan_end(st: Scan, rest: String, stop: Stop) -> Parse:  match st:    case Finished{value}:      Accepted{Dec{value, rest}}    case Failed{error}:      Rejected{error}    case Reading{value, started, leading_zero}:      match stop:        case Delimited{delimiter}:          Rejected{MissingDelimiter{}}        case Complete{}:          complete_end(started, value, rest)def scan(rest: String, st: Scan, +limit: Nat, +stop: Stop) -> Parse:  match rest st:    case _ Finished{value}:      Accepted{Dec{value, rest}}    case _ Failed{error}:      Rejected{error}    case SNil{} Reading{value, started, leading_zero}:      scan_end(Reading{value, started, leading_zero}, "", stop)    case SCon{c, t} Reading{value, started, leading_zero}:      scan(t, scan_step(c, Reading{value, started, leading_zero}, limit, stop), limit, stop)def parse_with_stop(str: String, limit: Nat, stop: Stop) -> Parse:  scan(str, Reading{0n, False{}, False{}}, limit, stop)def parse_delimited(str: String, limit: Nat, delimiter: Char) -> Parse:  parse_with_stop(str, limit, Delimited{delimiter})def parse_semicolon(str: String, limit: Nat) -> Parse:  parse_delimited(str, limit, ';')def parse_complete(str: String, limit: Nat) -> Parse:  parse_with_stop(str, limit, Complete{})def parsed_value(parsed: Parsed) -> Nat:  match parsed:    case Dec{value, rest}:      valuedef parsed_rest(parsed: Parsed) -> String:  match parsed:    case Dec{value, rest}:      restdef is_accepted(outcome: Parse) -> Bool:  match outcome:    case Rejected{error}:      False{}    case Accepted{parsed}:      True{}