~/bend-docscommunity

parse.bend source

parse.bend on the hub · documented module

import Baseimport ./books.bend as Booksimport ./normalize.bend as Normimport ./share.bend as Shareimport ./complete.bend as Compimport ./context.bend as Ctximport ./route.bend as Routedef 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),      ""    )  )type TrPh is Data:  TrStart{h: Char, is_a: Bool}  TrAlpha{}  TrClass{h: Char, is_a: Bool, is_dot: Bool}  TrCommit{ok: Bool}def strip_tr_go(fuel: Nat, ph: TrPh, rest: String, +n: Nat, +orig: String) -> String:  match fuel:    case 0n:      orig    case 1n+p:      match ph:        case TrStart{+h, is_a}:          match is_a:            case False{}:              orig            case True{}:              strip_tr_go(p, TrAlpha{}, rest, 1n, orig)        case TrAlpha{}:          match rest:            case SNil{}:              orig            case SCon{+h, +t}:              strip_tr_go(p, TrClass{h, Char.is_alpha(h), Char.is_eq(h, '.')}, t, n, orig)        case TrClass{+h, is_a, is_dot}:          match is_a:            case True{}:              strip_tr_go(p, TrAlpha{}, rest, Nat.add(n, 1n), orig)            case False{}:              match is_dot:                case True{}:                  strip_tr_go(p, TrCommit{Bool.and(Nat.is_ge(n, 2n), Nat.is_le(n, 8n))}, rest, n, orig)                case False{}:                  orig        case TrCommit{ok}:          match ok:            case True{}:              String.reverse(rest)            case False{}:              origdef strip_tr_suffix_rev(+s: String, rev: String) -> String:  match rev:    case SNil{}:      s    case SCon{+h, +t}:      strip_tr_go(        Nat.add(Nat.mul(3n, String.length(s)), 4n),        TrStart{h, Char.is_alpha(h)},        t,        0n,        s      )def strip_tr_suffix(+s: String) -> String:  strip_tr_suffix_rev(s, 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(strip_tr_suffix(Norm.normalize_intake(input)))def looks_like_url(+s: String) -> Bool:  Bool.or(    String.contains(s, "://"),    Bool.or(      String.starts_with(s, "www."),      Bool.or(        String.contains(s, "bibleref="),        Bool.or(          String.contains(s, "bible/"),          Bool.or(            String.contains(s, "bible-commentary/"),            Bool.or(              String.contains(s, "v1/p/"),              String.starts_with(String.to_lower(s), "logosres:")            )          )        )      )    )  )def pick_url_ref_go(+share: String, +fallback: String, empty: Bool) -> String:  match empty:    case False{}:      url_decode(share)    case True{}:      fallbackdef pick_url_ref(+share: String, +fallback: String) -> String:  pick_url_ref_go(share, fallback, String.is_empty(share))def extract_url_ref(+s: String) -> String:  pick_url_ref(Share.extract_share_ref(s), 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(strip_tr_suffix(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(Norm.normalize_intake(input))# Multi-ref: split on ';' and parse each non-empty trimtype FrPh is Data:  FrScan{}  FrDecide{is_sep: Bool}  FrTake{}  FrEmit{tok: String}def find_any_push(+tok: String, acc: List<&2, Parsed>, r: Result<ParseErr, Parsed>) -> List<&2, Parsed>:  match r:    case Fail{e}:      acc    case Done{p}:      p <> accdef find_any_emit(fuel: Nat, +tok: String, rest: String, acc: List<&2, Parsed>, empty: Bool) -> List<&2, Parsed>:  match empty:    case True{}:      acc    case False{}:      find_any_push(tok, acc, parse_any(tok))def find_any_go(fuel: Nat, ph: FrPh, rest: String, +tok: String, acc: List<&2, Parsed>) -> List<&2, Parsed>:  match fuel:    case 0n:      acc    case 1n+p:      match ph:        case FrScan{}:          match rest:            case SNil{}:              find_any_emit(p, tok, "", acc, String.is_empty(String.trim(tok)))            case SCon{+h, +t}:              find_any_go(p, FrDecide{Char.is_eq(h, ';')}, rest, tok, acc)        case FrDecide{is_sep}:          match is_sep:            case True{}:              find_any_go(p, FrEmit{String.trim(tok)}, rest, "", acc)            case False{}:              find_any_go(p, FrTake{}, rest, tok, acc)        case FrEmit{+tok0}:          match rest:            case SNil{}:              find_any_push(tok0, acc, parse_any(tok0))            case SCon{h, t}:              find_any_go(p, FrScan{}, t, "", find_any_push(tok0, acc, parse_any(tok0)))        case FrTake{}:          match rest:            case SNil{}:              acc            case SCon{+h, +t}:              find_any_go(p, FrScan{}, t, String.append(tok, char1(h)), acc)def rev_parsed(xs: List<&2, Parsed>, acc: List<&2, Parsed>) -> List<&2, Parsed>:  match xs:    case Nil{}:      acc    case h <> t:      rev_parsed(t, h <> acc)def find_any(+input: String) -> List<&2, Parsed>:  rev_parsed(    find_any_go(      Nat.add(Nat.mul(5n, String.length(input)), 8n),      FrScan{},      Norm.normalize_intake(input),      "",      Nil{}    ),    Nil{}  )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 -- --all \"John 3:16; Rom 8:28\"\n  bend main.bend -- --complete \"john 3:\"\n  bend main.bend -- --context \"John 3:16\"\n  bend main.bend -- --linkify \"John 3:16\"\n  bend main.bend --publish\n")def show_parsed_list_rest(xs: List<&2, Parsed>) -> IO(Unit):  match xs:    case Nil{}:      IO.print("")    case h <> t:      do IO<Unit>:        show_result(Done{h})        show_parsed_list_rest(t)def show_parsed_list(xs: List<&2, Parsed>) -> IO(Unit):  match xs:    case Nil{}:      IO.print("(no refs)\n")    case h <> t:      do IO<Unit>:        show_result(Done{h})        show_parsed_list_rest(t)def run_context_v(+book: String, +chapter: U32, verse: Maybe<&2, U32>) -> IO(Unit):  match verse:    case None{}:      IO.print("context needs a verse\n")    case Some{+v}:      IO.print(String.append(Ctx.format_ctx(Ctx.expand_default(book, chapter, v)), "\n"))def run_context_r(r: Result<ParseErr, Parsed>) -> IO(Unit):  match r:    case Fail{e}:      show_result(Fail{e})    case Done{p}:      match p:        case Parsed{canonical, display, +book, +chapter, verse, end_chapter, end_verse, kind}:          run_context_v(book, chapter, verse)def run_context(+q: String) -> IO(Unit):  run_context_r(parse_any(q))def run_query(+q: String) -> IO(Unit):  show_result(parse_any(q))def run_all(+q: String) -> IO(Unit):  show_parsed_list(find_any(q))def run_complete(+q: String) -> IO(Unit):  Comp.show_suggestions(Comp.autocomplete(q, 8n))def run_linkify_r(r: Result<ParseErr, Parsed>) -> IO(Unit):  match r:    case Fail{e}:      show_result(Fail{e})    case Done{p}:      match p:        case Parsed{+canonical, +display, book, chapter, verse, end_chapter, end_verse, kind}:          IO.print(String.append(Route.markdown_route_link(display, canonical), "\n"))def run_linkify(+q: String) -> IO(Unit):  run_linkify_r(parse_any(q))def run_flag5(+h: String, t: List<&1, String>, is_all: Bool, is_comp: Bool, is_ctx: Bool, is_link: Bool) -> IO(Unit):  match is_all:    case True{}:      run_all(join_args(t))    case False{}:      match is_comp:        case True{}:          run_complete(join_args(t))        case False{}:          match is_ctx:            case True{}:              run_context(join_args(t))            case False{}:              match is_link:                case True{}:                  run_linkify(join_args(t))                case False{}:                  run_query(join_args(h <> t))def run_args(xs: List<&1, String>) -> IO(Unit):  match xs:    case Nil{}:      usage()    case +h <> t:      run_flag5(        h,        t,        String.eq(h, "--all"),        String.eq(h, "--complete"),        String.eq(h, "--context"),        String.eq(h, "--linkify")      )def main() -> IO(Unit):  do IO<Unit>:    args : List<&1, String> <- IO.args()    run_args(args)