books.bend source
books.bend on the hub · documented module
import Baseimport ./books_data.bend as Dimport ./books_maps.bend as Mdef char1(+c: Char) -> String: String.from_list(c <> Nil{})type SpSt is Data: SpCheck{} SpDecide{keep: Bool} SpTake{}def is_alnum(+c: Char) -> Bool: Bool.or(Char.is_alpha(c), Char.is_digit(c))def strip_spaces_go(fuel: Nat, st: SpSt, rest: String, acc: String) -> String: match fuel: case 0n: acc case 1n+p: match st: case SpCheck{}: match rest: case SNil{}: acc case SCon{+h, +t}: strip_spaces_go(p, SpDecide{is_alnum(h)}, rest, acc) case SpDecide{keep}: match keep: case True{}: strip_spaces_go(p, SpTake{}, rest, acc) case False{}: match rest: case SNil{}: acc case SCon{h, t}: strip_spaces_go(p, SpCheck{}, t, acc) case SpTake{}: match rest: case SNil{}: acc case SCon{+h, +t}: strip_spaces_go(p, SpCheck{}, t, String.append(acc, char1(h)))def strip_spaces(+s: String) -> String: strip_spaces_go( Nat.add(Nat.mul(3n, String.length(s)), 3n), SpCheck{}, s, "" )def alias_key(+raw: String) -> String: strip_spaces(String.to_lower(raw))def unmap_str(p: Map<&2, String> & String) -> String: match p: case (_, v): vdef unmap_u32(p: Map<&2, U32> & U32) -> U32: match p: case (_, v): vdef unhas_str(p: Map<&2, String> & Bool) -> Bool: match p: case (_, b): bdef unhas_u32(p: Map<&2, U32> & Bool) -> Bool: match p: case (_, b): bdef map_get_str(+m: Map<&2, String>, +key: String) -> String: unmap_str(Map.get(String, "", m, key))def map_get_u32(+m: Map<&2, U32>, +key: String) -> U32: unmap_u32(Map.get(U32, 0, m, key))def map_has_str(+m: Map<&2, String>, +key: String) -> Bool: unhas_str(Map.has(&2, String, m, key))def map_has_u32(+m: Map<&2, U32>, +key: String) -> Bool: unhas_u32(Map.has(&2, U32, m, key))def exact_alias_hit(+key: String, hit: Bool) -> Maybe<&2, String>: match hit: case False{}: None{} case True{}: Some{map_get_str(M.alias_map(), key)}def exact_alias_nonempty(+key: String, empty: Bool) -> Maybe<&2, String>: match empty: case True{}: None{} case False{}: exact_alias_hit(key, map_has_str(M.alias_map(), key))def exact_alias(+key: String) -> Maybe<&2, String>: exact_alias_nonempty(key, String.is_empty(key))def nat_abs_diff_go(a: Nat, b: Nat, le: Bool) -> Nat: match le: case True{}: Nat.sub(b, a) case False{}: Nat.sub(a, b)def nat_abs_diff(+a: Nat, +b: Nat) -> Nat: nat_abs_diff_go(a, b, Nat.is_le(a, b))# Single recursive one-edit automaton (no mutual recursion)type OePh is Data: OeGate{over: Bool} OeBody{} OeEq{same: Bool} OeLen{c: Cmp} OeHoldA{a: String, b: String, ta: String, tb: String, diffs: Nat}def one_edit_go(fuel: Nat, ph: OePh, +a: String, +b: String, +diffs: Nat) -> Bool: match fuel: case 0n: False{} case 1n+p: match ph: case OeGate{over}: match over: case True{}: False{} case False{}: one_edit_go(p, OeBody{}, a, b, diffs) case OeBody{}: match a b: case SNil{} SNil{}: Nat.is_le(diffs, 1n) case SNil{} SCon{hb, tb}: one_edit_go(p, OeGate{Nat.is_gt(Nat.add(diffs, 1n), 1n)}, a, tb, Nat.add(diffs, 1n)) case SCon{ha, ta} SNil{}: one_edit_go(p, OeGate{Nat.is_gt(Nat.add(diffs, 1n), 1n)}, ta, b, Nat.add(diffs, 1n)) case SCon{+ha, +ta} SCon{+hb, +tb}: one_edit_go( p, OeHoldA{SCon{ha, ta}, SCon{hb, tb}, ta, tb, diffs}, SCon{ha, ta}, SCon{hb, tb}, diffs ) case OeHoldA{+aa, +bb, +ta, +tb, +d0}: match aa bb: case SCon{+ha, _} SCon{+hb, _}: one_edit_go(p, OeEq{Char.is_eq(ha, hb)}, aa, bb, d0) case _ _: False{} case OeEq{same}: match same: case True{}: match a b: case SCon{ha, ta} SCon{hb, tb}: one_edit_go(p, OeBody{}, ta, tb, diffs) case _ _: False{} case False{}: one_edit_go(p, OeLen{Nat.cmp(String.length(a), String.length(b))}, a, b, diffs) case OeLen{c}: match c: case EQ{}: match a b: case SCon{ha, ta} SCon{hb, tb}: one_edit_go(p, OeGate{Nat.is_gt(Nat.add(diffs, 1n), 1n)}, ta, tb, Nat.add(diffs, 1n)) case _ _: False{} case GT{}: match a: case SCon{ha, ta}: one_edit_go(p, OeGate{Nat.is_gt(Nat.add(diffs, 1n), 1n)}, ta, b, Nat.add(diffs, 1n)) case SNil{}: False{} case LT{}: match b: case SCon{hb, tb}: one_edit_go(p, OeGate{Nat.is_gt(Nat.add(diffs, 1n), 1n)}, a, tb, Nat.add(diffs, 1n)) case SNil{}: False{}def one_edit_apart_go(+a: String, +b: String, far: Bool) -> Bool: match far: case True{}: False{} case False{}: one_edit_go( Nat.add(Nat.mul(4n, Nat.add(String.length(a), String.length(b))), 8n), OeGate{False{}}, a, b, 0n )def one_edit_apart(+a: String, +b: String) -> Bool: one_edit_apart_go(a, b, Nat.is_gt(nat_abs_diff(String.length(a), String.length(b)), 1n))def first_chars_match(+a: String, +b: String) -> Bool: match a: case SNil{}: False{} case SCon{+ca, ta}: match b: case SNil{}: False{} case SCon{+cb, tb}: Char.is_eq(ca, cb)def starts_digit(s: String) -> Bool: match s: case SNil{}: False{} case SCon{h, t}: Char.is_digit(h)def bool_eq(a: Bool, b: Bool) -> Bool: Cmp.is_eq(Bool.cmp(a, b))type HaSt is Data: HaCheck{} HaDecide{is_a: Bool}def has_alpha_go(fuel: Nat, st: HaSt, rest: String) -> Bool: match fuel: case 0n: False{} case 1n+p: match st: case HaCheck{}: match rest: case SNil{}: False{} case SCon{+h, +t}: has_alpha_go(p, HaDecide{Char.is_alpha(h)}, t) case HaDecide{is_a}: match is_a: case True{}: True{} case False{}: has_alpha_go(p, HaCheck{}, rest)def has_alpha(+s: String) -> Bool: has_alpha_go(Nat.add(Nat.mul(2n, String.length(s)), 2n), HaCheck{}, s)def fuzzy_ok_key(+key: String) -> Bool: Bool.and(Nat.is_ge(String.length(key), 4n), has_alpha(key))type FzPh is Data: FzScan{} FzTest{code: String, same_first: Bool, digit_ok: Bool, near: Bool}def fuzzy_find(fuel: Nat, ph: FzPh, xs: List<D.Alias>, +key: String) -> Maybe<&2, String>: match fuel: case 0n: None{} case 1n+p: match ph: case FzScan{}: match xs: case Nil{}: None{} case D.Alias{+k, c} <> t: fuzzy_find( p, FzTest{ c, first_chars_match(key, k), bool_eq(starts_digit(key), starts_digit(k)), one_edit_apart(key, k) }, t, key ) case FzTest{+code, same_first, digit_ok, near}: match same_first: case False{}: fuzzy_find(p, FzScan{}, xs, key) case True{}: match digit_ok: case False{}: fuzzy_find(p, FzScan{}, xs, key) case True{}: match near: case False{}: fuzzy_find(p, FzScan{}, xs, key) case True{}: Some{code}def resolve_fuzzy_go(+key: String, ok: Bool) -> Maybe<&2, String>: match ok: case False{}: None{} case True{}: fuzzy_find(600n, FzScan{}, M.fuzzy_alias_table(), key)def resolve_fuzzy(+key: String) -> Maybe<&2, String>: resolve_fuzzy_go(key, fuzzy_ok_key(key))def resolve_alias_key_go(+key: String, exact: Maybe<&2, String>) -> Maybe<&2, String>: match exact: case Some{c}: Some{c} case None{}: resolve_fuzzy(key)def resolve_alias_key(+key: String) -> Maybe<&2, String>: resolve_alias_key_go(key, exact_alias(key))def resolve_alias(+raw: String) -> Maybe<&2, String>: resolve_alias_key(alias_key(raw))def chapter_count_go(+book: String, hit: Bool) -> Maybe<&2, U32>: match hit: case False{}: None{} case True{}: Some{map_get_u32(M.chapter_map(), book)}def chapter_count(+book: String) -> Maybe<&2, U32>: chapter_count_go(book, map_has_u32(M.chapter_map(), book))def verse_key(+book: String, +ch: U32) -> String: String.append(String.append(book, "."), U32.show(ch))def verse_count_go(+key: String, hit: Bool) -> Maybe<&2, U32>: match hit: case False{}: None{} case True{}: Some{map_get_u32(M.verse_map(), key)}def verse_count(+book: String, +ch: U32) -> Maybe<&2, U32>: verse_count_go(verse_key(book, ch), map_has_u32(M.verse_map(), verse_key(book, ch)))def display_name_go(+code: String, hit: Bool) -> String: match hit: case False{}: code case True{}: map_get_str(M.name_map(), code)def display_name(+code: String) -> String: display_name_go(code, map_has_str(M.name_map(), code))def book_names_list() -> List<D.BookName>: D.book_names()