import Base import ./books.bend as Books 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{} @unsafe def take_book_go(st: BookSt, rest: String, acc: String, +has_letter: Bool) -> String & String: match st: case BookCheck{}: match rest: case SNil{}: (acc, "") case SCon{+h, +t}: take_book_go(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(BookTake{}, rest, acc, has_letter) case BookTake{}: match rest: case SNil{}: (acc, "") case SCon{+h, +t}: take_book_go(BookCheck{}, t, String.append(acc, char1(h)), next_has_letter(has_letter, h)) def take_book(+s: String) -> String & String: take_book_go(BookCheck{}, s, "", False{}) type DigSt is Data: DigCheck{} DigDecide{is_d: Bool} DigTake{} @unsafe def take_digits_go(st: DigSt, rest: String, acc: String) -> String & String: match st: case DigCheck{}: match rest: case SNil{}: (acc, "") case SCon{+h, +t}: take_digits_go(DigDecide{Char.is_digit(h)}, rest, acc) case DigDecide{is_d}: match is_d: case False{}: (acc, rest) case True{}: take_digits_go(DigTake{}, rest, acc) case DigTake{}: match rest: case SNil{}: (acc, "") case SCon{+h, +t}: take_digits_go(DigCheck{}, t, String.append(acc, char1(h))) def take_digits(+s: String) -> String & String: take_digits_go(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{} 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: 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(+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: 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) -> 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_parsed(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_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) -> 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 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_after_verse_digits(+book: String, +ch: U32, +vn: U32, pair: String & String) -> Result: 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: 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: 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_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_chapter_range_pair(+book: String, +ch: U32, pair: String & String) -> Result: 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: 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: 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)) def parse_passage_trim(+s0: String) -> Result: parse_passage_go(s0, String.is_empty(s0)) def parse_passage(+input: String) -> Result: parse_passage_trim(String.trim(input)) def err_msg(e: ParseErr) -> String: match e: case Empty{}: "empty input" case BadBook{}: "unknown book" case BadFormat{}: "bad format" case BadNumber{}: "need a positive chapter/verse number" case CapChapter{+book, +max, +got}: String.append( String.append(String.append(Books.display_name(book), " has "), U32.show(max)), String.append(" chapters (got ", String.append(U32.show(got), ")")) ) case CapVerse{+book, +chapter, +max, +got}: String.append( String.append( String.append(String.append(Books.display_name(book), " "), U32.show(chapter)), String.append(" has ", U32.show(max)) ), String.append(" verses (got ", String.append(U32.show(got), ")")) ) case Reversed{}: "reversed range" def show_ok(+canonical: String, +display: String) -> IO(Unit): do IO: 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, chapter, verse, end_chapter, end_verse, kind}: show_ok(canonical, display) def join_args(xs: List<&1, String>) -> String: match xs: case Nil{}: "" case h <> Nil{}: h case h <> t: String.append(h, String.append(" ", join_args(t))) def usage() -> IO(Unit): IO.print("grab-bcv (Bend) — parse Bible references to OSIS\n\nUsage:\n bend main.bend -- \"John 3:16\"\n bend main.bend --publish\n") def run_query(+q: String) -> IO(Unit): show_result(parse_passage(q)) def run_args(xs: List<&1, String>) -> IO(Unit): match xs: case Nil{}: usage() case h <> t: run_query(join_args(h <> t)) def main() -> IO(Unit): do IO: args : List<&1, String> <- IO.args() run_args(args)