ref_token.bend source
ref_token.bend on the hub · documented module
# REFERENCE_TOKEN-shaped scanners (grab-bcv share.ts / bible-linkify spirit).# Pure Bend — helper-pair style, Nat-fuel where looping, no @unsafe.import Basedef char1(+c: Char) -> String: String.from_list(c <> Nil{})def is_alnum(+c: Char) -> Bool: Bool.or(Char.is_alpha(c), Char.is_digit(c))def is_book_num(+c: Char) -> Bool: Bool.or( Char.is_eq(c, '1'), Bool.or(Char.is_eq(c, '2'), Char.is_eq(c, '3')) )def drop_head(+s: String) -> String: match s: case SNil{}: "" case SCon{h, t}: tdef longer_tok_go(ge: Bool, +a: String, +b: String) -> String: match ge: case True{}: a case False{}: bdef longer_tok(+a: String, +b: String) -> String: longer_tok_go(Nat.is_ge(String.length(a), String.length(b)), a, b)def pick_nonempty_b(b_empty: Bool, +a: String, +b: String) -> String: match b_empty: case True{}: a case False{}: longer_tok(a, b)def pick_nonempty_a(a_empty: Bool, +a: String, +b: String) -> String: match a_empty: case True{}: b case False{}: pick_nonempty_b(String.is_empty(b), a, b)def pick_nonempty(+a: String, +b: String) -> String: pick_nonempty_a(String.is_empty(a), a, b)# --- take alphas / digits / alnum ---type TkPh is Data: TkCheck{} TkDec{ok: Bool} TkTake{}def take_alphas_go(fuel: Nat, ph: TkPh, rest: String, acc: String) -> String & String: match fuel: case 0n: (acc, rest) case 1n+p: match ph: case TkCheck{}: match rest: case SNil{}: (acc, "") case SCon{+h, +t}: take_alphas_go(p, TkDec{Char.is_alpha(h)}, rest, acc) case TkDec{ok}: match ok: case False{}: (acc, rest) case True{}: take_alphas_go(p, TkTake{}, rest, acc) case TkTake{}: match rest: case SNil{}: (acc, "") case SCon{+h, +t}: take_alphas_go(p, TkCheck{}, t, String.append(acc, char1(h)))def take_alphas(+s: String) -> String & String: take_alphas_go(Nat.add(Nat.mul(3n, String.length(s)), 3n), TkCheck{}, s, "")def take_digits_go(fuel: Nat, ph: TkPh, rest: String, acc: String) -> String & String: match fuel: case 0n: (acc, rest) case 1n+p: match ph: case TkCheck{}: match rest: case SNil{}: (acc, "") case SCon{+h, +t}: take_digits_go(p, TkDec{Char.is_digit(h)}, rest, acc) case TkDec{ok}: match ok: case False{}: (acc, rest) case True{}: take_digits_go(p, TkTake{}, rest, acc) case TkTake{}: match rest: case SNil{}: (acc, "") case SCon{+h, +t}: take_digits_go(p, TkCheck{}, 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), TkCheck{}, s, "")def take_alnum_go(fuel: Nat, ph: TkPh, rest: String, acc: String) -> String & String: match fuel: case 0n: (acc, rest) case 1n+p: match ph: case TkCheck{}: match rest: case SNil{}: (acc, "") case SCon{+h, +t}: take_alnum_go(p, TkDec{is_alnum(h)}, rest, acc) case TkDec{ok}: match ok: case False{}: (acc, rest) case True{}: take_alnum_go(p, TkTake{}, rest, acc) case TkTake{}: match rest: case SNil{}: (acc, "") case SCon{+h, +t}: take_alnum_go(p, TkCheck{}, t, String.append(acc, char1(h)))def take_alnum(+s: String) -> String & String: take_alnum_go(Nat.add(Nat.mul(3n, String.length(s)), 3n), TkCheck{}, s, "")def space_pair(sp: Bool, +t: String, +orig: String) -> Bool & String: match sp: case True{}: (True{}, t) case False{}: (False{}, orig)def drop_one_space(+s: String) -> Bool & String: match s: case SNil{}: (False{}, "") case SCon{+h, +t}: space_pair(Char.is_space(h), t, s)def char_eq_head(+s: String, +c: Char) -> Bool: match s: case SNil{}: False{} case SCon{+h, t}: Char.is_eq(h, c)def dig_head(+s: String) -> Bool: match s: case SNil{}: False{} case SCon{+h, t}: Char.is_digit(h)def alpha_head(+s: String) -> Bool: match s: case SNil{}: False{} case SCon{+h, t}: Char.is_alpha(h)# ===== Human token =====# ([1-3]\s+)?[A-Za-z]+\.?(?:\s+of\s+[A-Za-z]+)?\s+\d+(?:(?::|\s)\d+(?:-\d+)?)?def hu_fail() -> String: ""def hu_finish(+tok: String) -> String: tokdef hu_range_empty(empty: Bool, +pre: String, +ed: String) -> String: match empty: case True{}: hu_finish(pre) case False{}: hu_finish(String.append(pre, ed))def hu_range_pair(+pre: String, pair: String & String) -> String: match pair: case (+ed, rest2): hu_range_empty(String.is_empty(ed), pre, ed)def hu_range(+pre: String, +rest: String) -> String: hu_range_pair(pre, take_digits(rest))def hu_after_verse_dash(dash: Bool, +pre: String, +rest: String) -> String: match dash: case True{}: hu_range(String.append(pre, "-"), drop_head(rest)) case False{}: hu_finish(pre)def hu_after_verse(+pre: String, +rest: String) -> String: hu_after_verse_dash(char_eq_head(rest, '-'), pre, rest)def hu_verse_empty(empty: Bool, +pre: String, +vs: String, +rest: String) -> String: match empty: case True{}: hu_finish(pre) case False{}: hu_after_verse(String.append(pre, vs), rest)def hu_verse_pair(+pre: String, pair: String & String) -> String: match pair: case (+vs, +rest2): hu_verse_empty(String.is_empty(vs), pre, vs, rest2)def hu_verse(+pre: String, +rest: String) -> String: hu_verse_pair(pre, take_digits(rest))def hu_cv_space_dig(dig: Bool, +pre: String, +rest: String) -> String: match dig: case True{}: hu_verse(String.append(pre, " "), drop_head(rest)) case False{}: hu_finish(pre)def hu_cv_space(+pre: String, +rest: String) -> String: hu_cv_space_dig(dig_head(drop_head(rest)), pre, rest)def hu_cv_colon(colon: Bool, space: Bool, +pre: String, +rest: String) -> String: match colon: case True{}: hu_verse(String.append(pre, ":"), drop_head(rest)) case False{}: match space: case True{}: hu_cv_space(pre, rest) case False{}: hu_finish(pre)def hu_cv_heads(+pre: String, +rest: String, +h: Char) -> String: hu_cv_colon(Char.is_eq(h, ':'), Char.is_space(h), pre, rest)def hu_after_chap(+pre: String, +rest: String) -> String: match rest: case SNil{}: hu_finish(pre) case SCon{+h, t}: hu_cv_heads(pre, rest, h)def hu_chap_empty(empty: Bool, +pre: String, +ch: String, +rest: String) -> String: match empty: case True{}: hu_fail() case False{}: hu_after_chap(String.append(pre, ch), rest)def hu_chap_pair(+pre: String, pair: String & String) -> String: match pair: case (+ch, +rest2): hu_chap_empty(String.is_empty(ch), pre, ch, rest2)def hu_chap(+pre: String, +rest: String) -> String: hu_chap_pair(pre, take_digits(rest))def hu_need_chap_sp_go(sp: Bool, +pre: String, +rest: String) -> String: match sp: case False{}: hu_fail() case True{}: hu_chap(String.append(pre, " "), rest)def hu_need_chap_sp(+pre: String, pair: Bool & String) -> String: match pair: case (sp, rest2): hu_need_chap_sp_go(sp, pre, rest2)def hu_of_book_empty(empty: Bool, +pre: String, +b2: String, +rest: String) -> String: match empty: case True{}: hu_fail() case False{}: hu_need_chap_sp(String.append(pre, b2), drop_one_space(rest))def hu_of_book_pair(+pre: String, pair: String & String) -> String: match pair: case (+b2, +rest2): hu_of_book_empty(String.is_empty(b2), pre, b2, rest2)def hu_of_book(+pre: String, +rest: String) -> String: hu_of_book_pair(pre, take_alphas(rest))def hu_of_sp2_go(sp: Bool, +pre: String, +rest: String) -> String: match sp: case False{}: hu_fail() case True{}: hu_of_book(String.append(pre, " "), rest)def hu_of_sp2(+pre: String, pair: Bool & String) -> String: match pair: case (sp, rest2): hu_of_sp2_go(sp, pre, rest2)def hu_after_of(+pre: String, +rest: String) -> String: hu_of_sp2(pre, drop_one_space(rest))def hu_of_word_go(is_of: Bool, +pre: String, +rest: String) -> String: match is_of: case True{}: hu_after_of(String.append(pre, " of"), drop_head(drop_head(rest))) case False{}: hu_chap(String.append(pre, " "), rest)def hu_of_word(+pre: String, +rest: String) -> String: hu_of_word_go(String.starts_with(String.to_lower(rest), "of"), pre, rest)def hu_of_sp_go(sp: Bool, +pre: String, +rest: String) -> String: match sp: case False{}: hu_fail() case True{}: hu_of_word(pre, rest)def hu_of_sp(+pre: String, pair: Bool & String) -> String: match pair: case (sp, rest2): hu_of_sp_go(sp, pre, rest2)def hu_after_dot(+pre: String, +rest: String) -> String: hu_of_sp(pre, drop_one_space(rest))def hu_dot_go(dotted: Bool, +pre: String, +rest: String) -> String: match dotted: case True{}: hu_after_dot(String.append(pre, "."), drop_head(rest)) case False{}: hu_after_dot(pre, rest)def hu_after_book(+pre: String, +rest: String) -> String: hu_dot_go(char_eq_head(rest, '.'), pre, rest)def hu_book_empty(empty: Bool, +pre: String, +book: String, +rest: String) -> String: match empty: case True{}: hu_fail() case False{}: hu_after_book(String.append(pre, book), rest)def hu_book_pair(+pre: String, pair: String & String) -> String: match pair: case (+book, +rest2): hu_book_empty(String.is_empty(book), pre, book, rest2)def hu_book(+pre: String, +rest: String) -> String: hu_book_pair(pre, take_alphas(rest))def hu_num_sp_go(sp: Bool, +pre: String, +rest: String) -> String: match sp: case False{}: hu_fail() case True{}: hu_book(String.append(pre, " "), rest)def hu_num_sp(+pre: String, pair: Bool & String) -> String: match pair: case (sp, rest2): hu_num_sp_go(sp, pre, rest2)def hu_start_num(num: Bool, +h: Char, +t: String, +rest: String) -> String: match num: case True{}: hu_num_sp(char1(h), drop_one_space(t)) case False{}: hu_book("", rest)def match_human(+s: String) -> String: match s: case SNil{}: "" case SCon{+h, +t}: hu_start_num(is_book_num(h), h, t, s)# ===== OSIS token =====# [1-3]?[A-Za-z]{2,}\.\d+(?:\.\d+)?(?:-\d+)?(?:\.[A-Za-z0-9]{2,8})?def os_fail() -> String: ""def os_finish(+tok: String) -> String: tokdef suf_ok(+suf: String) -> Bool: Bool.and( Nat.is_ge(String.length(suf), 2n), Nat.is_le(String.length(suf), 8n) )def os_suf_body_go(ok: Bool, +pre: String, +suf: String) -> String: match ok: case True{}: os_finish(String.append(pre, suf)) case False{}: os_finish(pre)def os_suf_body_pair(+pre: String, pair: String & String) -> String: match pair: case (+suf, rest2): os_suf_body_go(suf_ok(suf), pre, suf)def os_suf_body(+pre: String, +rest: String) -> String: os_suf_body_pair(pre, take_alnum(rest))def os_suf_go(dotted: Bool, +pre: String, +rest: String) -> String: match dotted: case True{}: os_suf_body(String.append(pre, "."), drop_head(rest)) case False{}: os_finish(pre)def os_suf(+pre: String, +rest: String) -> String: os_suf_go(char_eq_head(rest, '.'), pre, rest)def os_range_dig_pair(+pre: String, pair: String & String) -> String: match pair: case (+ed, +rest2): os_suf(String.append(pre, ed), rest2)def os_range_dig(+pre: String, +rest: String) -> String: os_range_dig_pair(pre, take_digits(rest))def os_range_go(dig: Bool, +pre: String, +rest: String) -> String: match dig: case True{}: os_range_dig(pre, rest) case False{}: os_finish(pre)def os_after_dash(+pre: String, +rest: String) -> String: os_range_go(dig_head(rest), pre, rest)def os_dash_go(dash: Bool, +pre: String, +rest: String) -> String: match dash: case True{}: os_after_dash(String.append(pre, "-"), drop_head(rest)) case False{}: os_suf(pre, rest)def os_after_verse(+pre: String, +rest: String) -> String: os_dash_go(char_eq_head(rest, '-'), pre, rest)def os_verse_empty(empty: Bool, +pre: String, +vs: String, +rest: String) -> String: match empty: case True{}: os_after_verse(pre, rest) case False{}: os_after_verse(String.append(pre, vs), rest)def os_verse_pair(+pre: String, pair: String & String) -> String: match pair: case (+vs, +rest2): os_verse_empty(String.is_empty(vs), pre, vs, rest2)def os_verse(+pre: String, +rest: String) -> String: os_verse_pair(pre, take_digits(rest))def os_vdot_go(dotted: Bool, +pre: String, +rest: String) -> String: match dotted: case True{}: os_verse(String.append(pre, "."), drop_head(rest)) case False{}: os_after_verse(pre, rest)def os_after_chap(+pre: String, +rest: String) -> String: os_vdot_go(char_eq_head(rest, '.'), pre, rest)def os_chap_empty(empty: Bool, +pre: String, +ch: String, +rest: String) -> String: match empty: case True{}: os_fail() case False{}: os_after_chap(String.append(pre, ch), rest)def os_chap_pair(+pre: String, pair: String & String) -> String: match pair: case (+ch, +rest2): os_chap_empty(String.is_empty(ch), pre, ch, rest2)def os_chap(+pre: String, +rest: String) -> String: os_chap_pair(pre, take_digits(rest))def os_dot_go(dotted: Bool, +pre: String, +rest: String) -> String: match dotted: case False{}: os_fail() case True{}: os_chap(String.append(pre, "."), drop_head(rest))def os_after_book(+pre: String, +rest: String) -> String: os_dot_go(char_eq_head(rest, '.'), pre, rest)def os_book_short(short: Bool, +pre: String, +book: String, +rest: String) -> String: match short: case True{}: os_fail() case False{}: os_after_book(String.append(pre, book), rest)def os_book_pair(+pre: String, pair: String & String) -> String: match pair: case (+book, +rest2): os_book_short(Nat.is_lt(String.length(book), 2n), pre, book, rest2)def os_book(+pre: String, +rest: String) -> String: os_book_pair(pre, take_alphas(rest))def os_num_alpha_go(alpha: Bool, +pre: String, +rest: String, +full: String) -> String: match alpha: case True{}: os_book(pre, rest) case False{}: os_book("", full)def os_start_num(num: Bool, +h: Char, +t: String, +rest: String) -> String: match num: case True{}: os_num_alpha_go(alpha_head(t), char1(h), t, rest) case False{}: os_book("", rest)def match_osis(+s: String) -> String: match s: case SNil{}: "" case SCon{+h, +t}: os_start_num(is_book_num(h), h, t, s)def match_ref_token(+s: String) -> String: pick_nonempty(match_osis(s), match_human(s))# --- URL token ---type UrlPh is Data: UrlCheck{} UrlDec{stop: Bool} UrlTake{}def is_url_stop(+c: Char) -> Bool: Bool.or( Char.is_space(c), Bool.or(Char.is_eq(c, ')'), Char.is_eq(c, ']')) )def take_url_go(fuel: Nat, ph: UrlPh, rest: String, acc: String) -> String: match fuel: case 0n: acc case 1n+p: match ph: case UrlCheck{}: match rest: case SNil{}: acc case SCon{+h, +t}: take_url_go(p, UrlDec{is_url_stop(h)}, rest, acc) case UrlDec{stop}: match stop: case True{}: acc case False{}: take_url_go(p, UrlTake{}, rest, acc) case UrlTake{}: match rest: case SNil{}: acc case SCon{+h, +t}: take_url_go(p, UrlCheck{}, t, String.append(acc, char1(h)))def starts_url(+s: String) -> Bool: Bool.or( String.starts_with(String.to_lower(s), "http://"), Bool.or( String.starts_with(String.to_lower(s), "https://"), String.starts_with(String.to_lower(s), "www.") ) )def match_url_token_go(yes: Bool, +s: String) -> String: match yes: case False{}: "" case True{}: take_url_go( Nat.add(Nat.mul(3n, String.length(s)), 4n), UrlCheck{}, s, "" )def match_url_token(+s: String) -> String: match_url_token_go(starts_url(s), s)def boundary_ok(prev_alnum: Bool) -> Bool: match prev_alnum: case True{}: False{} case False{}: True{}