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)