~/bend-docscommunity

parse.bend source

parse.bend on the hub · documented module

import Baseimport ./books.bend as Booksdef char1(+c: Char) -> String:  String.from_list(c <> Nil{})def is_book_stop_flags(+has_letter: Bool, is_d: Bool, is_a: Bool, is_s: Bool) -> Bool:  match is_d:    case True{}:      has_letter    case False{}:      match is_a:        case True{}:          False{}        case False{}:          match is_s:            case True{}:              False{}            case False{}:              True{}def is_book_stop(+has_letter: Bool, +c: Char) -> Bool:  is_book_stop_flags(has_letter, Char.is_digit(c), Char.is_alpha(c), Char.is_space(c))def next_has_letter_go(has_letter: Bool, is_a: Bool) -> Bool:  match has_letter:    case True{}:      True{}    case False{}:      is_adef next_has_letter(+has_letter: Bool, +c: Char) -> Bool:  next_has_letter_go(has_letter, Char.is_alpha(c))type BookSt is Data:  BookCheck{}  BookDecide{stop: Bool}  BookTake{}def take_book_go(fuel: Nat, st: BookSt, rest: String, acc: String, +has_letter: Bool) -> String & String:  match fuel:    case 0n:      (acc, rest)    case 1n+p:      match st:        case BookCheck{}:          match rest:            case SNil{}:              (acc, "")            case SCon{+h, +t}:              take_book_go(p, BookDecide{is_book_stop(has_letter, h)}, rest, acc, has_letter)        case BookDecide{stop}:          match stop:            case True{}:              (acc, rest)            case False{}:              take_book_go(p, BookTake{}, rest, acc, has_letter)        case BookTake{}:          match rest:            case SNil{}:              (acc, "")            case SCon{+h, +t}:              take_book_go(p, BookCheck{}, t, String.append(acc, char1(h)), next_has_letter(has_letter, h))def take_book(+s: String) -> String & String:  take_book_go(Nat.add(Nat.mul(3n, String.length(s)), 3n), BookCheck{}, s, "", False{})type DigSt is Data:  DigCheck{}  DigDecide{is_d: Bool}  DigTake{}def take_digits_go(fuel: Nat, st: DigSt, rest: String, acc: String) -> String & String:  match fuel:    case 0n:      (acc, rest)    case 1n+p:      match st:        case DigCheck{}:          match rest:            case SNil{}:              (acc, "")            case SCon{+h, +t}:              take_digits_go(p, DigDecide{Char.is_digit(h)}, rest, acc)        case DigDecide{is_d}:          match is_d:            case False{}:              (acc, rest)            case True{}:              take_digits_go(p, DigTake{}, rest, acc)        case DigTake{}:          match rest:            case SNil{}:              (acc, "")            case SCon{+h, +t}:              take_digits_go(p, DigCheck{}, t, String.append(acc, char1(h)))def take_digits(+s: String) -> String & String:  take_digits_go(Nat.add(Nat.mul(3n, String.length(s)), 3n), DigCheck{}, s, "")def is_colon_or_dot(+c: Char) -> Bool:  Bool.or(Char.is_eq(c, ':'), Char.is_eq(c, '.'))def drop_head(s: String) -> String:  match s:    case SNil{}:      ""    case SCon{h, t}:      tdef drop_cv_sep_go(+s: String, drop: Bool) -> String:  match drop:    case True{}:      drop_head(s)    case False{}:      sdef drop_cv_sep(+s: String) -> String:  match s:    case SNil{}:      ""    case SCon{+h, +t}:      drop_cv_sep_go(s, is_colon_or_dot(h))def drop_dash_go(+s: String, drop: Bool) -> String:  match drop:    case True{}:      drop_head(s)    case False{}:      sdef drop_dash(+s: String) -> String:  match s:    case SNil{}:      ""    case SCon{+h, +t}:      drop_dash_go(s, Char.is_eq(h, '-'))def is_dash_head(s: String) -> Bool:  match s:    case SNil{}:      False{}    case SCon{h, t}:      Char.is_eq(h, '-')type RangeKind is Data:  Single{}  Chapter{}  SameChapter{}  ChapterRange{}type ParseErr is Data:  Empty{}  BadBook{}  BadFormat{}  BadNumber{}  CapChapter{book: String, max: U32, got: U32}  CapVerse{book: String, chapter: U32, max: U32, got: U32}  Reversed{}type Parsed is Data:  Parsed{    canonical: String,    display: String,    book: String,    chapter: U32,    verse: Maybe<&2, U32>,    end_chapter: U32,    end_verse: Maybe<&2, U32>,    kind: RangeKind  }def require_u32_nz(n: U32, z: Bool) -> Result<ParseErr, U32>:  match z:    case True{}:      Fail{BadNumber{}}    case False{}:      Done{n}def require_u32_go(m: Maybe<&2, U32>) -> Result<ParseErr, U32>:  match m:    case None{}:      Fail{BadNumber{}}    case Some{+n}:      require_u32_nz(n, U32.is_zero(n))def require_u32(s: String) -> Result<ParseErr, U32>:  require_u32_go(U32.read(String.trim(s)))def check_ch_le(+book: String, +ch: U32, +max: U32, ok: Bool) -> Result<ParseErr, Unit>:  match ok:    case True{}:      Done{Unit{}}    case False{}:      Fail{CapChapter{book, max, ch}}def check_ch_go(+book: String, +ch: U32, m: Maybe<&2, U32>) -> Result<ParseErr, Unit>:  match m:    case None{}:      Fail{BadBook{}}    case Some{+max}:      check_ch_le(book, ch, max, U32.is_le(ch, max))def check_ch(+book: String, +ch: U32) -> Result<ParseErr, Unit>:  check_ch_go(book, ch, Books.chapter_count(book))def check_vs_le(+book: String, +ch: U32, +v: U32, +max: U32, ok: Bool) -> Result<ParseErr, Unit>:  match ok:    case True{}:      Done{Unit{}}    case False{}:      Fail{CapVerse{book, ch, max, v}}def check_vs_go(+book: String, +ch: U32, +v: U32, m: Maybe<&2, U32>) -> Result<ParseErr, Unit>:  match m:    case None{}:      Fail{BadNumber{}}    case Some{+max}:      check_vs_le(book, ch, v, max, U32.is_le(v, max))def check_vs(+book: String, +ch: U32, +v: U32) -> Result<ParseErr, Unit>:  check_vs_go(book, ch, v, Books.verse_count(book, ch))def fmt_osis_part(+book: String, +ch: U32, v: Maybe<&2, U32>) -> String:  match v:    case None{}:      String.append(String.append(book, "."), U32.show(ch))    case Some{n}:      String.append(        String.append(String.append(String.append(book, "."), U32.show(ch)), "."),        U32.show(n)      )def fmt_same_canonical(+book: String, +ch: U32, a: U32, ev: Maybe<&2, U32>) -> String:  match ev:    case None{}:      fmt_osis_part(book, ch, Some{a})    case Some{b}:      String.append(String.append(fmt_osis_part(book, ch, Some{a}), "-"), U32.show(b))def fmt_canonical_same(+book: String, +ch: U32, v: Maybe<&2, U32>, ev: Maybe<&2, U32>) -> String:  match v:    case None{}:      fmt_osis_part(book, ch, None{})    case Some{a}:      fmt_same_canonical(book, ch, a, ev)def fmt_canonical(+book: String, +ch: U32, +v: Maybe<&2, U32>, +ech: U32, +ev: Maybe<&2, U32>, +kind: RangeKind) -> String:  match kind:    case Single{}:      fmt_osis_part(book, ch, v)    case Chapter{}:      fmt_osis_part(book, ch, None{})    case SameChapter{}:      fmt_canonical_same(book, ch, v, ev)    case ChapterRange{}:      String.append(String.append(fmt_osis_part(book, ch, None{}), "-"), U32.show(ech))def fmt_display_verse(+name: String, +ch: U32, v: Maybe<&2, U32>) -> String:  match v:    case None{}:      String.append(String.append(name, " "), U32.show(ch))    case Some{n}:      String.append(        String.append(String.append(String.append(name, " "), U32.show(ch)), ":"),        U32.show(n)      )def fmt_display_same2(+name: String, +ch: U32, a: U32, ev: Maybe<&2, U32>) -> String:  match ev:    case None{}:      fmt_display_verse(name, ch, Some{a})    case Some{b}:      String.append(        String.append(          String.append(String.append(String.append(name, " "), U32.show(ch)), ":"),          U32.show(a)        ),        String.append("-", U32.show(b))      )def fmt_display_same(+name: String, +ch: U32, v: Maybe<&2, U32>, ev: Maybe<&2, U32>) -> String:  match v:    case None{}:      name    case Some{a}:      fmt_display_same2(name, ch, a, ev)def fmt_display_with(+name: String, +ch: U32, +v: Maybe<&2, U32>, +ech: U32, +ev: Maybe<&2, U32>, +kind: RangeKind) -> String:  match kind:    case Single{}:      fmt_display_verse(name, ch, v)    case Chapter{}:      String.append(String.append(name, " "), U32.show(ch))    case SameChapter{}:      fmt_display_same(name, ch, v, ev)    case ChapterRange{}:      String.append(        String.append(String.append(String.append(name, " "), U32.show(ch)), "-"),        U32.show(ech)      )def fmt_display(+book: String, +ch: U32, +v: Maybe<&2, U32>, +ech: U32, +ev: Maybe<&2, U32>, +kind: RangeKind) -> String:  fmt_display_with(Books.display_name(book), ch, v, ech, ev, kind)def mk_parsed(+book: String, +ch: U32, +v: Maybe<&2, U32>, +ech: U32, +ev: Maybe<&2, U32>, +kind: RangeKind) -> Parsed:  Parsed{    fmt_canonical(book, ch, v, ech, ev, kind),    fmt_display(book, ch, v, ech, ev, kind),    book, ch, v, ech, ev, kind  }def finish_ch_ok(+book: String, +ch: U32, +ech: U32, +kind: RangeKind, rev: Bool) -> Result<ParseErr, Parsed>:  match rev:    case True{}:      Fail{Reversed{}}    case False{}:      Done{mk_parsed(book, ch, None{}, ech, None{}, kind)}def finish_ch_after(+book: String, +ch: U32, +ech: U32,  +kind: RangeKind, r: Result<ParseErr, Unit>) -> Result<ParseErr, Parsed>:  match r:    case Fail{e}:      Fail{e}    case Done{_}:      finish_ch_ok(book, ch, ech, kind, U32.is_lt(ech, ch))def finish_ch_match(+book: String, +ch: U32, +ech: U32, +kind: RangeKind, r: Result<ParseErr, Unit>) -> Result<ParseErr, Parsed>:  match r:    case Fail{e}:      Fail{e}    case Done{_}:      finish_ch_after(book, ch, ech, kind, check_ch(book, ech))def finish_ch(+book: String, +ch: U32, +ech: U32, +kind: RangeKind) -> Result<ParseErr, Parsed>:  finish_ch_match(book, ch, ech, kind, check_ch(book, ch))def finish_vs_end_ok(+book: String, +ch: U32, +vn: U32, +en: U32, rev: Bool) -> Result<ParseErr, Parsed>:  match rev:    case True{}:      Fail{Reversed{}}    case False{}:      Done{mk_parsed(book, ch, Some{vn}, ch, Some{en}, SameChapter{})}def finish_vs_end(+book: String, +ch: U32, +vn: U32, +en: U32, r: Result<ParseErr, Unit>) -> Result<ParseErr, Parsed>:  match r:    case Fail{e}:      Fail{e}    case Done{_}:      finish_vs_end_ok(book, ch, vn, en, U32.is_lt(en, vn))def finish_vs_ev(+book: String, +ch: U32, +vn: U32, ev: Maybe<&2, U32>) -> Result<ParseErr, Parsed>:  match ev:    case None{}:      Done{mk_parsed(book, ch, Some{vn}, ch, Some{vn}, Single{})}    case Some{+en}:      finish_vs_end(book, ch, vn, en, check_vs(book, ch, en))def finish_vs_after_vn(+book: String, +ch: U32, +vn: U32, ev: Maybe<&2, U32>, r: Result<ParseErr, Unit>) -> Result<ParseErr, Parsed>:  match r:    case Fail{e}:      Fail{e}    case Done{_}:      finish_vs_ev(book, ch, vn, ev)def finish_vs_match(+book: String, +ch: U32, +vn: U32, ev: Maybe<&2, U32>, r: Result<ParseErr, Unit>) -> Result<ParseErr, Parsed>:  match r:    case Fail{e}:      Fail{e}    case Done{_}:      finish_vs_after_vn(book, ch, vn, ev, check_vs(book, ch, vn))def finish_vs(+book: String, +ch: U32, +vn: U32, ev: Maybe<&2, U32>) -> Result<ParseErr, Parsed>:  finish_vs_match(book, ch, vn, ev, check_ch(book, ch))def parse_after_verse_ev(+book: String, +ch: U32, +vn: U32, r: Result<ParseErr, U32>) -> Result<ParseErr, Parsed>:  match r:    case Fail{e}:      Fail{e}    case Done{ev}:      finish_vs(book, ch, vn, Some{ev})def parse_after_verse_digits(+book: String, +ch: U32, +vn: U32, pair: String & String) -> Result<ParseErr, Parsed>:  match pair:    case (ev_s, _junk):      parse_after_verse_ev(book, ch, vn, require_u32(ev_s))def parse_after_verse_num_go(+book: String, +ch: U32, +vn: U32, +rest: String, empty: Bool) -> Result<ParseErr, Parsed>:  match empty:    case True{}:      finish_vs(book, ch, vn, None{})    case False{}:      parse_after_verse_digits(book, ch, vn, take_digits(String.trim(drop_dash(rest))))def parse_after_verse_num_trim(+book: String, +ch: U32, +vn: U32, +r: String) -> Result<ParseErr, Parsed>:  parse_after_verse_num_go(book, ch, vn, r, String.is_empty(r))def parse_after_verse_num(+book: String, +ch: U32, +vn: U32, +rest: String) -> Result<ParseErr, Parsed>:  parse_after_verse_num_trim(book, ch, vn, String.trim(rest))def parse_verse_branch_num(+book: String, +ch: U32, +rest2: String, r: Result<ParseErr, U32>) -> Result<ParseErr, Parsed>:  match r:    case Fail{e}:      Fail{e}    case Done{vn}:      parse_after_verse_num(book, ch, vn, rest2)def parse_verse_branch_pair(+book: String, +ch: U32, pair: String & String) -> Result<ParseErr, Parsed>:  match pair:    case (vs, rest2):      parse_verse_branch_num(book, ch, rest2, require_u32(vs))def parse_verse_branch(+book: String, +ch: U32, +rest: String) -> Result<ParseErr, Parsed>:  parse_verse_branch_pair(book, ch, take_digits(rest))def parse_chapter_range_num(+book: String, +ch: U32, r: Result<ParseErr, U32>) -> Result<ParseErr, Parsed>:  match r:    case Fail{e}:      Fail{e}    case Done{ech}:      finish_ch(book, ch, ech, ChapterRange{})def parse_chapter_range_pair(+book: String, +ch: U32, pair: String & String) -> Result<ParseErr, Parsed>:  match pair:    case (ech_s, _junk):      parse_chapter_range_num(book, ch, require_u32(ech_s))def parse_chapter_range_branch(+book: String, +ch: U32, +rest: String) -> Result<ParseErr, Parsed>:  parse_chapter_range_pair(book, ch, take_digits(String.trim(drop_dash(rest))))def parse_after_chapter_sep_go(+book: String, +ch: U32, +rest: String, empty: Bool, dash: Bool) -> Result<ParseErr, Parsed>:  match empty:    case True{}:      finish_ch(book, ch, ch, Chapter{})    case False{}:      match dash:        case True{}:          parse_chapter_range_branch(book, ch, rest)        case False{}:          parse_verse_branch(book, ch, rest)def parse_after_chapter_sep_trim(+book: String, +ch: U32, +r: String) -> Result<ParseErr, Parsed>:  parse_after_chapter_sep_go(book, ch, r, String.is_empty(r), is_dash_head(r))def parse_after_chapter_sep(+book: String, +ch: U32, +rest: String) -> Result<ParseErr, Parsed>:  parse_after_chapter_sep_trim(book, ch, String.trim(rest))def parse_tail_num(+book: String, +rest0: String, r: Result<ParseErr, U32>) -> Result<ParseErr, Parsed>:  match r:    case Fail{e}:      Fail{e}    case Done{ch}:      parse_after_chapter_sep(book, ch, drop_cv_sep(String.trim(rest0)))def parse_tail_pair(+book: String, pair: String & String) -> Result<ParseErr, Parsed>:  match pair:    case (ch_s, rest0):      parse_tail_num(book, rest0, require_u32(ch_s))def parse_tail(+book: String, +tail: String) -> Result<ParseErr, Parsed>:  parse_tail_pair(book, take_digits(drop_cv_sep(String.trim(tail))))def parse_passage_book(+rest: String, m: Maybe<&2, String>) -> Result<ParseErr, Parsed>:  match m:    case None{}:      Fail{BadBook{}}    case Some{code}:      parse_tail(code, rest)def parse_passage_pair(pair: String & String) -> Result<ParseErr, Parsed>:  match pair:    case (book_raw, rest):      parse_passage_book(rest, Books.resolve_alias(String.trim(book_raw)))def parse_passage_go(+s0: String, empty: Bool) -> Result<ParseErr, Parsed>:  match empty:    case True{}:      Fail{Empty{}}    case False{}:      parse_passage_pair(take_book(s0))# --- URL / route.bible helpers (pure Bend) ---def hex_digit_lo(code: U32, ok: Bool) -> Maybe<&2, U32>:  match ok:    case False{}:      None{}    case True{}:      Some{U32.sub(code, 48)}def hex_digit_up(code: U32, ok: Bool) -> Maybe<&2, U32>:  match ok:    case False{}:      None{}    case True{}:      Some{U32.sub(code, 55)}def hex_digit_low(code: U32, ok: Bool) -> Maybe<&2, U32>:  match ok:    case False{}:      None{}    case True{}:      Some{U32.sub(code, 87)}def hex_nibble_af(+code: U32, dig: Maybe<&2, U32>) -> Maybe<&2, U32>:  match dig:    case Some{n}:      Some{n}    case None{}:      hex_digit_low(code, Bool.and(U32.is_le(97, code), U32.is_le(code, 102)))def hex_nibble_az(+code: U32, dig: Maybe<&2, U32>) -> Maybe<&2, U32>:  match dig:    case Some{n}:      Some{n}    case None{}:      hex_nibble_af(        code,        hex_digit_up(code, Bool.and(U32.is_le(65, code), U32.is_le(code, 70)))      )def hex_nibble_from_code(+code: U32) -> Maybe<&2, U32>:  hex_nibble_az(    code,    hex_digit_lo(code, Bool.and(U32.is_le(48, code), U32.is_le(code, 57)))  )def hex_nibble(+c: Char) -> Maybe<&2, U32>:  match c:    case Chr{+code}:      hex_nibble_from_code(code)def hex_byte(hi: U32, lo: U32) -> U32:  (U32.add(U32.mul(hi, 16), lo) : U32)# Single recursive decoder — Maybe results passed as DecSt fields (no computed match)type DecSt is Data:  DecCopy{}  DecClass{c: Char, is_pct: Bool, is_plus: Bool}  DecPct{}  DecNib1{m: Maybe<&2, U32>, spill: Char}  DecHi{hi: U32}  DecNib2{hi: U32, m: Maybe<&2, U32>, spill: Char}  DecApply{hi: U32, lo: U32}def url_decode_go(fuel: Nat, st: DecSt, rest: String, acc: String) -> String:  match fuel:    case 0n:      acc    case 1n+p:      match st:        case DecCopy{}:          match rest:            case SNil{}:              acc            case SCon{+h, +t}:              url_decode_go(p, DecClass{h, Char.is_eq(h, '%'), Char.is_eq(h, '+')}, t, acc)        case DecClass{+c, is_pct, is_plus}:          match is_pct:            case True{}:              url_decode_go(p, DecPct{}, rest, acc)            case False{}:              match is_plus:                case True{}:                  url_decode_go(p, DecCopy{}, rest, String.append(acc, " "))                case False{}:                  url_decode_go(p, DecCopy{}, rest, String.append(acc, char1(c)))        case DecPct{}:          match rest:            case SNil{}:              String.append(acc, "%")            case SCon{+h, +t}:              url_decode_go(p, DecNib1{hex_nibble(h), h}, t, acc)        case DecNib1{m, +spill}:          match m:            case None{}:              url_decode_go(p, DecCopy{}, SCon{spill, rest}, String.append(acc, "%"))            case Some{hi}:              url_decode_go(p, DecHi{hi}, rest, acc)        case DecHi{+hi}:          match rest:            case SNil{}:              String.append(acc, "%")            case SCon{+h, +t}:              url_decode_go(p, DecNib2{hi, hex_nibble(h), h}, t, acc)        case DecNib2{+hi, m, +spill}:          match m:            case None{}:              url_decode_go(p, DecCopy{}, SCon{spill, rest}, String.append(acc, "%"))            case Some{lo}:              url_decode_go(p, DecApply{hi, lo}, rest, acc)        case DecApply{+hi, +lo}:          url_decode_go(            p,            DecCopy{},            rest,            String.append(acc, char1(Char.from_u32(hex_byte(hi, lo))))          )def url_decode(+s: String) -> String:  url_decode_go(Nat.add(Nat.mul(4n, String.length(s)), 4n), DecCopy{}, s, "")type CutSt is Data:  CutCheck{}  CutDecide{stop: Bool}  CutTake{}def is_query_or_hash(+c: Char) -> Bool:  Bool.or(Char.is_eq(c, '?'), Char.is_eq(c, '#'))def take_until_qh_go(fuel: Nat, st: CutSt, rest: String, acc: String) -> String:  match fuel:    case 0n:      acc    case 1n+p:      match st:        case CutCheck{}:          match rest:            case SNil{}:              acc            case SCon{+h, +t}:              take_until_qh_go(p, CutDecide{is_query_or_hash(h)}, rest, acc)        case CutDecide{stop}:          match stop:            case True{}:              acc            case False{}:              take_until_qh_go(p, CutTake{}, rest, acc)        case CutTake{}:          match rest:            case SNil{}:              acc            case SCon{+h, +t}:              take_until_qh_go(p, CutCheck{}, t, String.append(acc, char1(h)))def strip_query(+s: String) -> String:  take_until_qh_go(Nat.add(Nat.mul(3n, String.length(s)), 3n), CutCheck{}, s, "")def take_until_slash_go(fuel: Nat, st: CutSt, rest: String, acc: String) -> String:  match fuel:    case 0n:      acc    case 1n+p:      match st:        case CutCheck{}:          match rest:            case SNil{}:              acc            case SCon{+h, +t}:              take_until_slash_go(p, CutDecide{Char.is_eq(h, '/')}, rest, acc)        case CutDecide{stop}:          match stop:            case True{}:              acc            case False{}:              take_until_slash_go(p, CutTake{}, rest, acc)        case CutTake{}:          match rest:            case SNil{}:              acc            case SCon{+h, +t}:              take_until_slash_go(p, CutCheck{}, t, String.append(acc, char1(h)))def last_path_segment(+s: String) -> String:  String.reverse(    take_until_slash_go(      Nat.add(Nat.mul(3n, String.length(s)), 3n),      CutCheck{},      String.reverse(s),      ""    )  )def parse_passage_trim(+s0: String) -> Result<ParseErr, Parsed>:  parse_passage_go(s0, String.is_empty(s0))def parse_passage(+input: String) -> Result<ParseErr, Parsed>:  parse_passage_trim(String.trim(input))def looks_like_url(+s: String) -> Bool:  Bool.or(String.contains(s, "://"), String.starts_with(s, "www."))def extract_url_ref(+s: String) -> String:  url_decode(last_path_segment(strip_query(s)))def parse_any_go(+s: String, is_url: Bool) -> Result<ParseErr, Parsed>:  match is_url:    case True{}:      parse_passage(extract_url_ref(s))    case False{}:      parse_passage(s)def parse_any_trim(+s: String) -> Result<ParseErr, Parsed>:  parse_any_go(s, looks_like_url(s))def parse_any(+input: String) -> Result<ParseErr, Parsed>:  parse_any_trim(String.trim(input))def err_msg(e: ParseErr) -> String:  match e:    case Empty{}:      "empty input"    case BadBook{}:      "unknown book"    case BadFormat{}:      "bad format"    case BadNumber{}:      "need a positive chapter/verse number"    case CapChapter{+book, +max, +got}:      String.append(        String.append(String.append(Books.display_name(book), " has "), U32.show(max)),        String.append(" chapters (got ", String.append(U32.show(got), ")"))      )    case CapVerse{+book, +chapter, +max, +got}:      String.append(        String.append(          String.append(String.append(Books.display_name(book), " "), U32.show(chapter)),          String.append(" has ", U32.show(max))        ),        String.append(" verses (got ", String.append(U32.show(got), ")"))      )    case Reversed{}:      "reversed range"def show_ok(+canonical: String, +display: String) -> IO(Unit):  do IO<Unit>:    IO.print(canonical)    IO.print("  (")    IO.print(display)    IO.print(")\n")def show_result(r: Result<ParseErr, Parsed>) -> IO(Unit):  match r:    case Fail{e}:      do IO<Unit>:        IO.print("ERR ")        IO.print(err_msg(e))        IO.print("\n")    case Done{p}:      match p:        case Parsed{+canonical, +display, book, chapter, verse, end_chapter, end_verse, kind}:          show_ok(canonical, display)def join_args(xs: List<&1, String>) -> String:  match xs:    case Nil{}:      ""    case h <> Nil{}:      h    case h <> t:      String.append(h, String.append(" ", join_args(t)))def usage() -> IO(Unit):  IO.print("grab-bcv (Bend) — parse Bible references to OSIS\n\nUsage:\n  bend main.bend -- \"John 3:16\"\n  bend main.bend --publish\n")def run_query(+q: String) -> IO(Unit):  show_result(parse_any(q))def run_args(xs: List<&1, String>) -> IO(Unit):  match xs:    case Nil{}:      usage()    case h <> t:      run_query(join_args(h <> t))def main() -> IO(Unit):  do IO<Unit>:    args : List<&1, String> <- IO.args()    run_args(args)