import Base import ./books.bend as Books import ./normalize.bend as Norm import ./share.bend as Share import ./complete.bend as Comp import ./context.bend as Ctx import ./route.bend as Route import ./ref_token.bend as Tok def 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_a def 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}: t def drop_cv_sep_go(+s: String, drop: Bool) -> String: match drop: case True{}: drop_head(s) case False{}: s def 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{}: s def 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{} CrossReference{} 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, end_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: match z: case True{}: Fail{BadNumber{}} case False{}: Done{n} def require_u32_go(m: Maybe<&2, U32>) -> Result: match m: case None{}: Fail{BadNumber{}} case Some{+n}: require_u32_nz(n, U32.is_zero(n)) def require_u32(s: String) -> Result: require_u32_go(U32.read(String.trim(s))) def check_ch_le(+book: String, +ch: U32, +max: U32, ok: Bool) -> Result: 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: 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: check_ch_go(book, ch, Books.chapter_count(book)) def check_vs_le(+book: String, +ch: U32, +v: U32, +max: U32, ok: Bool) -> Result: 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: 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: 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_cross(+book: String, +end_book: String, +ch: U32, +v: Maybe<&2, U32>, +ech: U32, +ev: Maybe<&2, U32>) -> String: String.append(String.append(fmt_osis_part(book, ch, v), "-"), fmt_osis_part(end_book, ech, ev)) def fmt_canonical(+book: String, +end_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)) case CrossReference{}: fmt_canonical_cross(book, end_book, ch, v, ech, ev) 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_cv_part(+ch: U32, v: Maybe<&2, U32>) -> String: match v: case None{}: U32.show(ch) case Some{n}: String.append(String.append(U32.show(ch), ":"), U32.show(n)) def fmt_display_cross_same(+name: String, +ch: U32, +v: Maybe<&2, U32>, +ech: U32, +ev: Maybe<&2, U32>) -> String: String.append( String.append(String.append(name, " "), fmt_cv_part(ch, v)), String.append("-", fmt_cv_part(ech, ev)) ) def fmt_display_cross_diff(+nb: String, +neb: String, +ch: U32, +v: Maybe<&2, U32>, +ech: U32, +ev: Maybe<&2, U32>) -> String: String.append( String.append(String.append(nb, " "), fmt_cv_part(ch, v)), String.append(String.append("-", String.append(neb, " ")), fmt_cv_part(ech, ev)) ) def fmt_display_cross_go(+same: Bool, +book: String, +end_book: String, +ch: U32, +v: Maybe<&2, U32>, +ech: U32, +ev: Maybe<&2, U32>) -> String: match same: case True{}: fmt_display_cross_same(Books.display_name(book), ch, v, ech, ev) case False{}: fmt_display_cross_diff(Books.display_name(book), Books.display_name(end_book), ch, v, ech, ev) def fmt_display_cross(+book: String, +end_book: String, +ch: U32, +v: Maybe<&2, U32>, +ech: U32, +ev: Maybe<&2, U32>) -> String: fmt_display_cross_go(String.eq(book, end_book), book, end_book, ch, v, ech, ev) def fmt_display_with(+book: String, +end_book: String, +ch: U32, +v: Maybe<&2, U32>, +ech: U32, +ev: Maybe<&2, U32>, +kind: RangeKind) -> String: match kind: case Single{}: fmt_display_verse(Books.display_name(book), ch, v) case Chapter{}: String.append(String.append(Books.display_name(book), " "), U32.show(ch)) case SameChapter{}: fmt_display_same(Books.display_name(book), ch, v, ev) case ChapterRange{}: String.append( String.append(String.append(String.append(Books.display_name(book), " "), U32.show(ch)), "-"), U32.show(ech) ) case CrossReference{}: fmt_display_cross(book, end_book, ch, v, ech, ev) def fmt_display(+book: String, +end_book: String, +ch: U32, +v: Maybe<&2, U32>, +ech: U32, +ev: Maybe<&2, U32>, +kind: RangeKind) -> String: fmt_display_with(book, end_book, ch, v, ech, ev, kind) def mk_parsed(+book: String, +end_book: String, +ch: U32, +v: Maybe<&2, U32>, +ech: U32, +ev: Maybe<&2, U32>, +kind: RangeKind) -> Parsed: Parsed{ fmt_canonical(book, end_book, ch, v, ech, ev, kind), fmt_display(book, end_book, ch, v, ech, ev, kind), book, end_book, ch, v, ech, ev, kind } def mk_same(+book: String, +ch: U32, +v: Maybe<&2, U32>, +ech: U32, +ev: Maybe<&2, U32>, +kind: RangeKind) -> Parsed: mk_parsed(book, book, ch, v, ech, ev, kind) def finish_ch_ok(+book: String, +ch: U32, +ech: U32, +kind: RangeKind, rev: Bool) -> Result: match rev: case True{}: Fail{Reversed{}} case False{}: Done{mk_same(book, ch, None{}, ech, None{}, kind)} def finish_ch_after(+book: String, +ch: U32, +ech: U32, +kind: RangeKind, r: Result) -> Result: 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) -> Result: 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: 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: match rev: case True{}: Fail{Reversed{}} case False{}: Done{mk_same(book, ch, Some{vn}, ch, Some{en}, SameChapter{})} def finish_vs_end(+book: String, +ch: U32, +vn: U32, +en: U32, r: Result) -> Result: 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: match ev: case None{}: Done{mk_same(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) -> Result: 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) -> Result: 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: finish_vs_match(book, ch, vn, ev, check_ch(book, ch)) def is_cv_sep_head(s: String) -> Bool: match s: case SNil{}: False{} case SCon{+h, t}: is_colon_or_dot(h) def is_book_num_char(+c: Char) -> Bool: Bool.or( Char.is_eq(c, '1'), Bool.or(Char.is_eq(c, '2'), Char.is_eq(c, '3')) ) def alpha_head_s(s: String) -> Bool: match s: case SNil{}: False{} case SCon{+h, t}: Char.is_alpha(h) def bookish_head(s: String) -> Bool: match s: case SNil{}: False{} case SCon{+h, +t}: Bool.or(Char.is_alpha(h), Bool.and(is_book_num_char(h), alpha_head_s(t))) def verse_ord_start(v: Maybe<&2, U32>) -> U32: match v: case None{}: 0 case Some{n}: n def verse_ord_end(v: Maybe<&2, U32>) -> U32: match v: case None{}: 4294967295 case Some{n}: n def rev_same_book(+sc: U32, +sv: Maybe<&2, U32>, +ec: U32, +ev: Maybe<&2, U32>, ch_lt: Bool, ch_eq: Bool) -> Bool: match ch_lt: case True{}: True{} case False{}: match ch_eq: case False{}: False{} case True{}: U32.is_lt(verse_ord_end(ev), verse_ord_start(sv)) def rev_orders(+so: U32, +eo: U32, +sc: U32, +sv: Maybe<&2, U32>, +ec: U32, +ev: Maybe<&2, U32>, book_lt: Bool, book_eq: Bool) -> Bool: match book_lt: case True{}: True{} case False{}: match book_eq: case False{}: False{} case True{}: rev_same_book(sc, sv, ec, ev, U32.is_lt(ec, sc), U32.is_eq(ec, sc)) def rev_orders_m(+so: U32, eo_m: Maybe<&2, U32>, +sc: U32, +sv: Maybe<&2, U32>, +ec: U32, +ev: Maybe<&2, U32>) -> Result: match eo_m: case None{}: Fail{BadBook{}} case Some{+eo}: Done{rev_orders(so, eo, sc, sv, ec, ev, U32.is_lt(eo, so), U32.is_eq(eo, so))} def rev_books_m(so_m: Maybe<&2, U32>, +eb: String, +sc: U32, +sv: Maybe<&2, U32>, +ec: U32, +ev: Maybe<&2, U32>) -> Result: match so_m: case None{}: Fail{BadBook{}} case Some{so}: rev_orders_m(so, Books.book_order(eb), sc, sv, ec, ev) def is_reversed_cross(+sb: String, +sc: U32, +sv: Maybe<&2, U32>, +eb: String, +ec: U32, +ev: Maybe<&2, U32>) -> Result: rev_books_m(Books.book_order(sb), eb, sc, sv, ec, ev) def finish_cross_ok(+sb: String, +eb: String, +sc: U32, +sv: Maybe<&2, U32>, +ec: U32, +ev: Maybe<&2, U32>, rev: Bool) -> Result: match rev: case True{}: Fail{Reversed{}} case False{}: Done{mk_parsed(sb, eb, sc, sv, ec, ev, CrossReference{})} def finish_cross_after_rev(+sb: String, +eb: String, +sc: U32, +sv: Maybe<&2, U32>, +ec: U32, +ev: Maybe<&2, U32>, r: Result) -> Result: match r: case Fail{e}: Fail{e} case Done{rev}: finish_cross_ok(sb, eb, sc, sv, ec, ev, rev) def finish_cross_ev(+sb: String, +eb: String, +sc: U32, +sv: Maybe<&2, U32>, +ec: U32, +ev: Maybe<&2, U32>, r: Result) -> Result: match r: case Fail{e}: Fail{e} case Done{_}: finish_cross_after_rev(sb, eb, sc, sv, ec, ev, is_reversed_cross(sb, sc, sv, eb, ec, ev)) def finish_cross_ec_ev(ev: Maybe<&2, U32>, +sb: String, +eb: String, +sc: U32, +sv: Maybe<&2, U32>, +ec: U32) -> Result: match ev: case None{}: finish_cross_ev(sb, eb, sc, sv, ec, None{}, Done{Unit{}}) case Some{+en}: finish_cross_ev(sb, eb, sc, sv, ec, Some{en}, check_vs(eb, ec, en)) def finish_cross_ec(+sb: String, +eb: String, +sc: U32, +sv: Maybe<&2, U32>, +ec: U32, +ev: Maybe<&2, U32>, r: Result) -> Result: match r: case Fail{e}: Fail{e} case Done{_}: finish_cross_ec_ev(ev, sb, eb, sc, sv, ec) def finish_cross_sc(+sb: String, +eb: String, +sc: U32, +sv: Maybe<&2, U32>, +ec: U32, +ev: Maybe<&2, U32>, r: Result) -> Result: match r: case Fail{e}: Fail{e} case Done{_}: finish_cross_ec(sb, eb, sc, sv, ec, ev, check_ch(eb, ec)) def finish_cross_sv_m(sv: Maybe<&2, U32>, +sb: String, +eb: String, +sc: U32, +ec: U32, +ev: Maybe<&2, U32>) -> Result: match sv: case None{}: finish_cross_sc(sb, eb, sc, None{}, ec, ev, check_ch(sb, sc)) case Some{+vn}: finish_cross_sc(sb, eb, sc, Some{vn}, ec, ev, check_vs(sb, sc, vn)) def finish_cross_sv(+sb: String, +eb: String, +sc: U32, +sv: Maybe<&2, U32>, +ec: U32, +ev: Maybe<&2, U32>, r: Result) -> Result: match r: case Fail{e}: Fail{e} case Done{_}: finish_cross_sv_m(sv, sb, eb, sc, ec, ev) def finish_cross(+sb: String, +eb: String, +sc: U32, +sv: Maybe<&2, U32>, +ec: U32, +ev: Maybe<&2, U32>) -> Result: finish_cross_sv(sb, eb, sc, sv, ec, ev, check_ch(sb, sc)) def parse_end_verse_num(+sb: String, +eb: String, +sc: U32, +sv: Maybe<&2, U32>, +ec: U32, r: Result) -> Result: match r: case Fail{e}: Fail{e} case Done{en}: finish_cross(sb, eb, sc, sv, ec, Some{en}) def parse_end_verse_vs(empty_vs: Bool, +sb: String, +eb: String, +sc: U32, +sv: Maybe<&2, U32>, +ec: U32, +vs: String) -> Result: match empty_vs: case True{}: Fail{BadFormat{}} case False{}: parse_end_verse_num(sb, eb, sc, sv, ec, require_u32(vs)) def parse_end_verse_rest(rest_empty: Bool, +sb: String, +eb: String, +sc: U32, +sv: Maybe<&2, U32>, +ec: U32, +vs: String) -> Result: match rest_empty: case False{}: Fail{BadFormat{}} case True{}: parse_end_verse_vs(String.is_empty(vs), sb, eb, sc, sv, ec, vs) def parse_end_verse_pair(+sb: String, +eb: String, +sc: U32, +sv: Maybe<&2, U32>, +ec: U32, pair: String & String) -> Result: match pair: case (+vs, rest2): parse_end_verse_rest(String.is_empty(String.trim(rest2)), sb, eb, sc, sv, ec, vs) def parse_end_after_ch(+sb: String, +eb: String, +sc: U32, +sv: Maybe<&2, U32>, +ec: U32, +rest: String, empty: Bool, sep: Bool) -> Result: match empty: case True{}: finish_cross(sb, eb, sc, sv, ec, None{}) case False{}: match sep: case True{}: parse_end_verse_pair(sb, eb, sc, sv, ec, take_digits(drop_cv_sep(rest))) case False{}: Fail{BadFormat{}} def parse_end_ch_num(+sb: String, +eb: String, +sc: U32, +sv: Maybe<&2, U32>, +rest0: String, r: Result) -> Result: match r: case Fail{e}: Fail{e} case Done{ec}: parse_end_after_ch(sb, eb, sc, sv, ec, String.trim(rest0), String.is_empty(String.trim(rest0)), is_cv_sep_head(String.trim(rest0))) def parse_end_tail_ch(empty: Bool, +sb: String, +eb: String, +sc: U32, +sv: Maybe<&2, U32>, +ch_s: String, +rest0: String) -> Result: match empty: case True{}: Fail{BadFormat{}} case False{}: parse_end_ch_num(sb, eb, sc, sv, rest0, require_u32(ch_s)) def parse_end_tail_pair(+sb: String, +eb: String, +sc: U32, +sv: Maybe<&2, U32>, pair: String & String) -> Result: match pair: case (+ch_s, rest0): parse_end_tail_ch(String.is_empty(ch_s), sb, eb, sc, sv, ch_s, rest0) def parse_end_tail(+sb: String, +eb: String, +sc: U32, +sv: Maybe<&2, U32>, +tail: String) -> Result: parse_end_tail_pair(sb, eb, sc, sv, take_digits(drop_cv_sep(String.trim(tail)))) def parse_cross_second_book_code(+sb: String, +sc: U32, +sv: Maybe<&2, U32>, +rest: String, m: Maybe<&2, String>) -> Result: match m: case None{}: Fail{BadFormat{}} case Some{eb}: parse_end_tail(sb, eb, sc, sv, rest) def parse_cross_second_book_pair(+sb: String, +sc: U32, +sv: Maybe<&2, U32>, pair: String & String) -> Result: match pair: case (book_raw, rest2): parse_cross_second_book_code(sb, sc, sv, rest2, Books.resolve_alias(String.trim(book_raw))) def parse_cross_second_book(+sb: String, +sc: U32, +sv: Maybe<&2, U32>, +rest: String) -> Result: parse_cross_second_book_pair(sb, sc, sv, take_book(rest)) def parse_cross_cv_verse(+sb: String, +sc: U32, +sv: Maybe<&2, U32>, +ec: U32, r: Result) -> Result: match r: case Fail{e}: Fail{e} case Done{en}: finish_cross(sb, sb, sc, sv, ec, Some{en}) def parse_cross_cv_vs(empty_vs: Bool, +sb: String, +sc: U32, +sv: Maybe<&2, U32>, +ec: U32, +vs: String) -> Result: match empty_vs: case True{}: Fail{BadFormat{}} case False{}: parse_cross_cv_verse(sb, sc, sv, ec, require_u32(vs)) def parse_cross_cv_rest(rest_empty: Bool, +sb: String, +sc: U32, +sv: Maybe<&2, U32>, +ec: U32, +vs: String) -> Result: match rest_empty: case False{}: Fail{BadFormat{}} case True{}: parse_cross_cv_vs(String.is_empty(vs), sb, sc, sv, ec, vs) def parse_cross_cv_pair(+sb: String, +sc: U32, +sv: Maybe<&2, U32>, +ec: U32, pair: String & String) -> Result: match pair: case (+vs, rest2): parse_cross_cv_rest(String.is_empty(String.trim(rest2)), sb, sc, sv, ec, vs) def parse_cross_cv_ech(+sb: String, +sc: U32, +sv: Maybe<&2, U32>, +rest_after_sep: String, r: Result) -> Result: match r: case Fail{e}: Fail{e} case Done{ec}: parse_cross_cv_pair(sb, sc, sv, ec, take_digits(rest_after_sep)) def parse_after_verse_ev(+book: String, +ch: U32, +vn: U32, r: Result) -> Result: match r: case Fail{e}: Fail{e} case Done{ev}: finish_vs(book, ch, vn, Some{ev}) def parse_chapter_range_num(+book: String, +ch: U32, r: Result) -> Result: match r: case Fail{e}: Fail{e} case Done{ech}: finish_ch(book, ch, ech, ChapterRange{}) def parse_range_digits_same(+sb: String, +sc: U32, +sv: Maybe<&2, U32>, +digs: String) -> Result: match sv: case None{}: parse_chapter_range_num(sb, sc, require_u32(digs)) case Some{+vn}: parse_after_verse_ev(sb, sc, vn, require_u32(digs)) def parse_range_digits_allow(allow: Bool, +sb: String, +sc: U32, +sv: Maybe<&2, U32>, +digs: String) -> Result: match allow: case True{}: parse_range_digits_same(sb, sc, sv, digs) case False{}: Fail{BadFormat{}} def parse_range_digits_sep(sep: Bool, +sb: String, +sc: U32, +sv: Maybe<&2, U32>, +digs: String, +rest2: String) -> Result: match sep: case True{}: parse_cross_cv_ech(sb, sc, sv, drop_cv_sep(rest2), require_u32(digs)) case False{}: Fail{BadFormat{}} def parse_range_digits_rest(rest_empty: Bool, +allow_same_ch_digits: Bool, +sb: String, +sc: U32, +sv: Maybe<&2, U32>, +digs: String, +rest2: String) -> Result: match rest_empty: case True{}: parse_range_digits_allow(allow_same_ch_digits, sb, sc, sv, digs) case False{}: parse_range_digits_sep(is_cv_sep_head(rest2), sb, sc, sv, digs, rest2) def parse_range_after_digs(digs_empty: Bool, +sb: String, +sc: U32, +sv: Maybe<&2, U32>, +allow_same_ch_digits: Bool, +digs: String, +rest2: String) -> Result: match digs_empty: case True{}: parse_cross_second_book(sb, sc, sv, rest2) case False{}: parse_range_digits_rest(String.is_empty(String.trim(rest2)), allow_same_ch_digits, sb, sc, sv, digs, rest2) def parse_range_after_dash_pair(+sb: String, +sc: U32, +sv: Maybe<&2, U32>, +allow_same_ch_digits: Bool, pair: String & String) -> Result: match pair: case (+digs, rest2): parse_range_after_digs(String.is_empty(digs), sb, sc, sv, allow_same_ch_digits, digs, rest2) def parse_range_after_dash_go(bookish: Bool, +sb: String, +sc: U32, +sv: Maybe<&2, U32>, +allow_same_ch_digits: Bool, +rest: String) -> Result: match bookish: case True{}: parse_cross_second_book(sb, sc, sv, rest) case False{}: parse_range_after_dash_pair(sb, sc, sv, allow_same_ch_digits, take_digits(rest)) def parse_range_after_dash(+sb: String, +sc: U32, +sv: Maybe<&2, U32>, +allow_same_ch_digits: Bool, +rest: String) -> Result: parse_range_after_dash_go(bookish_head(String.trim(rest)), sb, sc, sv, allow_same_ch_digits, String.trim(rest)) def parse_after_verse_num_go(+book: String, +ch: U32, +vn: U32, +rest: String, empty: Bool) -> Result: match empty: case True{}: finish_vs(book, ch, vn, None{}) case False{}: parse_range_after_dash(book, ch, Some{vn}, True{}, String.trim(drop_dash(rest))) def parse_after_verse_num_trim(+book: String, +ch: U32, +vn: U32, +r: String) -> Result: 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: parse_after_verse_num_trim(book, ch, vn, String.trim(rest)) def parse_verse_branch_num(+book: String, +ch: U32, +rest2: String, r: Result) -> Result: 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: 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: parse_verse_branch_pair(book, ch, take_digits(rest)) def parse_chapter_range_branch(+book: String, +ch: U32, +rest: String) -> Result: parse_range_after_dash(book, ch, None{}, True{}, String.trim(drop_dash(rest))) def parse_after_chapter_sep_go(+book: String, +ch: U32, +rest: String, empty: Bool, dash: Bool) -> Result: 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: 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: parse_after_chapter_sep_trim(book, ch, String.trim(rest)) def parse_tail_num(+book: String, +rest0: String, r: Result) -> Result: 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: match pair: case (ch_s, rest0): parse_tail_num(book, rest0, require_u32(ch_s)) def parse_tail(+book: String, +tail: String) -> Result: parse_tail_pair(book, take_digits(drop_cv_sep(String.trim(tail)))) def parse_passage_book(+rest: String, m: Maybe<&2, String>) -> Result: match m: case None{}: Fail{BadBook{}} case Some{code}: parse_tail(code, rest) def parse_passage_pair(pair: String & String) -> Result: 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: 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{}: orig def 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: parse_passage_go(s0, String.is_empty(s0)) def parse_passage(+input: String) -> Result: 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{}: fallback def 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: 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: parse_any_go(s, looks_like_url(s)) def parse_any(+input: String) -> Result: parse_any_trim(Norm.normalize_intake(input)) # String-only probe for linkify (avoids Result across modules) def canonical_of_r(r: Result) -> String: match r: case Fail{e}: "" case Done{p}: match p: case Parsed{+canonical, display, book, end_book, chapter, verse, end_chapter, end_verse, kind}: canonical def try_canonical(+input: String) -> String: canonical_of_r(parse_any(input)) # Multi-ref free-text: REFERENCE_TOKEN-shaped scan (grab-bcv spirit), not only ';' splits. # Walks input at word boundaries; tries URL tokens then ref tokens; dedupes by canonical. 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 canon_of(+p: Parsed) -> String: match p: case Parsed{+canonical, display, book, end_book, chapter, verse, end_chapter, end_verse, kind}: canonical type ShPh is Data: ShScan{} ShEq{eq: Bool} def seen_has_go(fuel: Nat, ph: ShPh, +c: String, xs: List<&2, String>) -> Bool: match fuel: case 0n: False{} case 1n+p: match ph: case ShScan{}: match xs: case Nil{}: False{} case h <> t: seen_has_go(p, ShEq{String.eq(h, c)}, c, t) case ShEq{eq}: match eq: case True{}: True{} case False{}: seen_has_go(p, ShScan{}, c, xs) def seen_has(+c: String, +xs: List<&2, String>) -> Bool: seen_has_go( Nat.add(List.length(&2, String, xs), 2n), ShScan{}, c, xs ) def find_push_dedup( dup: Bool, +p: Parsed, acc: List<&2, Parsed>, seen: List<&2, String> ) -> List<&2, Parsed> & List<&2, String>: match dup: case True{}: (acc, seen) case False{}: (p <> acc, canon_of(p) <> seen) def find_push_done(+p: Parsed, acc: List<&2, Parsed>, +seen: List<&2, String>) -> List<&2, Parsed> & List<&2, String>: find_push_dedup(seen_has(canon_of(p), seen), p, acc, seen) def find_push_r( r: Result, acc: List<&2, Parsed>, seen: List<&2, String> ) -> List<&2, Parsed> & List<&2, String>: match r: case Fail{e}: (acc, seen) case Done{+p}: find_push_done(p, acc, seen) # On parse failure return "" consume-tok so FaGot advances one char (not the false token). def find_push_r_tok_done( +tok: String, pair: List<&2, Parsed> & List<&2, String> ) -> String & (List<&2, Parsed> & List<&2, String>): match pair: case (acc, seen): (tok, (acc, seen)) def find_push_r_tok( +tok: String, r: Result, acc: List<&2, Parsed>, seen: List<&2, String> ) -> String & (List<&2, Parsed> & List<&2, String>): match r: case Fail{e}: ("", (acc, seen)) case Done{+p}: find_push_r_tok_done(tok, find_push_done(p, acc, seen)) def find_try_tok_empty( empty: Bool, +tok: String, acc: List<&2, Parsed>, seen: List<&2, String> ) -> String & (List<&2, Parsed> & List<&2, String>): match empty: case True{}: ("", (acc, seen)) case False{}: find_push_r_tok(tok, parse_any(tok), acc, seen) def find_try_tok( +tok: String, acc: List<&2, Parsed>, seen: List<&2, String> ) -> String & (List<&2, Parsed> & List<&2, String>): find_try_tok_empty(String.is_empty(tok), tok, acc, seen) def str_drop_n_go(fuel: Nat, n: Nat, s: String) -> String: match fuel: case 0n: s case 1n+p: match n: case 0n: s case 1n+m: match s: case SNil{}: "" case SCon{h, t}: str_drop_n_go(p, m, t) def str_drop_n(+n: Nat, +s: String) -> String: str_drop_n_go(Nat.add(n, 2n), n, s) def is_alnum_c(+c: Char) -> Bool: Bool.or(Char.is_alpha(c), Char.is_digit(c)) type FaPh is Data: FaScan{prev_alnum: Bool} FaBound{prev_alnum: Bool, ok: Bool} FaTryUrl{} FaAfterUrl{+utok: String, empty: Bool} FaTryRef{} FaAfterRef{+rtok: String, empty: Bool} FaGot{ +tok: String, acc: List<&2, Parsed>, seen: List<&2, String>, empty: Bool } FaAdvance{} def fa_url_ph_tok(+utok: String) -> FaPh: FaAfterUrl{utok, String.is_empty(utok)} def fa_url_ph(+rest: String) -> FaPh: fa_url_ph_tok(Tok.match_url_token(rest)) def fa_ref_ph_tok(+rtok: String) -> FaPh: FaAfterRef{rtok, String.is_empty(rtok)} def fa_ref_ph(+rest: String) -> FaPh: fa_ref_ph_tok(Tok.match_ref_token(rest)) def fa_got_from_pair( pair: String & (List<&2, Parsed> & List<&2, String>) ) -> FaPh: match pair: case (+tok, (acc, seen)): FaGot{tok, acc, seen, String.is_empty(tok)} def find_any_go( fuel: Nat, ph: FaPh, +rest: String, +acc: List<&2, Parsed>, +seen: List<&2, String> ) -> List<&2, Parsed>: match fuel: case 0n: acc case 1n+p: match ph: case FaScan{+prev_alnum}: match rest: case SNil{}: acc case SCon{+h, +t}: find_any_go( p, FaBound{prev_alnum, Tok.boundary_ok(prev_alnum)}, rest, acc, seen ) case FaBound{+prev_alnum, ok}: match ok: case False{}: find_any_go(p, FaAdvance{}, rest, acc, seen) case True{}: find_any_go(p, FaTryUrl{}, rest, acc, seen) case FaTryUrl{}: find_any_go(p, fa_url_ph(rest), rest, acc, seen) case FaAfterUrl{+utok, empty}: match empty: case False{}: find_any_go( p, fa_got_from_pair(find_try_tok(utok, acc, seen)), rest, acc, seen ) case True{}: find_any_go(p, FaTryRef{}, rest, acc, seen) case FaTryRef{}: find_any_go(p, fa_ref_ph(rest), rest, acc, seen) case FaAfterRef{+rtok, empty}: match empty: case False{}: find_any_go( p, fa_got_from_pair(find_try_tok(rtok, acc, seen)), rest, acc, seen ) case True{}: find_any_go(p, FaAdvance{}, rest, acc, seen) case FaGot{+tok, +acc2, +seen2, empty}: match empty: case True{}: find_any_go(p, FaAdvance{}, rest, acc, seen) case False{}: find_any_go( p, FaScan{True{}}, str_drop_n(String.length(tok), rest), acc2, seen2 ) case FaAdvance{}: match rest: case SNil{}: acc case SCon{+h, +t}: find_any_go(p, FaScan{is_alnum_c(h)}, t, acc, seen) def find_any(+input: String) -> List<&2, Parsed>: rev_parsed( find_any_go( Nat.add(Nat.mul(8n, String.length(input)), 16n), FaScan{False{}}, Norm.normalize_intake(input), Nil{}, 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: IO.print(canonical) IO.print(" (") IO.print(display) IO.print(")\n") def show_result(r: Result) -> IO(Unit): match r: case Fail{e}: do IO: IO.print("ERR ") IO.print(err_msg(e)) IO.print("\n") case Done{p}: match p: case Parsed{+canonical, +display, book, end_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 and Romans 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 -- --linkify --src ci \"John 3:16\"\n bend main.bend -- --linkify --rewrite-existing \"[John 3:16](https://example.com/x)\"\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: 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: 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) -> IO(Unit): match r: case Fail{e}: show_result(Fail{e}) case Done{p}: match p: case Parsed{canonical, display, +book, end_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, +src: String) -> IO(Unit): match r: case Fail{e}: show_result(Fail{e}) case Done{p}: match p: case Parsed{+canonical, +display, book, end_book, chapter, verse, end_chapter, end_verse, kind}: IO.print(String.append(Route.markdown_route_link_qvs(display, canonical, "", src), "\n")) def run_linkify(+q: String, +src: String) -> IO(Unit): run_linkify_r(parse_any(q), src) # After --linkify: optional --src / --rewrite-existing (rewrite ignored on single-ref path). type PlfPh is Data: PlfGo{+src: String} PlfNeedSrc{} PlfHead{+src: String, is_rw: Bool, is_src: Bool, +h: String} def parse_linkify_cli_go(fuel: Nat, ph: PlfPh, xs: List<&1, String>) -> IO(Unit): match fuel: case 0n: match ph: case PlfGo{+src}: run_linkify(join_args(xs), src) case PlfNeedSrc{}: run_linkify(join_args(xs), "") case PlfHead{+src, is_rw, is_src, +h}: run_linkify(join_args(h <> xs), src) case 1n+p: match ph: case PlfGo{+src}: match xs: case Nil{}: run_linkify("", src) case +h <> t: parse_linkify_cli_go( p, PlfHead{src, String.eq(h, "--rewrite-existing"), String.eq(h, "--src"), h}, t ) case PlfNeedSrc{}: match xs: case Nil{}: run_linkify("", "") case +tag <> t: parse_linkify_cli_go(p, PlfGo{tag}, t) case PlfHead{+src, is_rw, is_src, +h}: match is_rw: case True{}: parse_linkify_cli_go(p, PlfGo{src}, xs) case False{}: match is_src: case True{}: parse_linkify_cli_go(p, PlfNeedSrc{}, xs) case False{}: run_linkify(join_args(h <> xs), src) def parse_linkify_cli(xs: List<&1, String>) -> IO(Unit): parse_linkify_cli_go(64n, PlfGo{""}, xs) 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{}: parse_linkify_cli(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: args : List<&1, String> <- IO.args() run_args(args)