# Paragraph-aware verse context (same-chapter; defaults maxBefore=3, maxAfter=3) import Base import ./books.bend as Books import ./para_data.bend as Para def unmap_str(p: Map<&2, String> & String) -> String: match p: case (_, v): v def para_starts_str(+book: String, +ch: U32) -> String: unmap_str( Map.get( String, "", Para.para_map(), String.append(String.append(book, "."), U32.show(ch)) ) ) def parse_u32_or0(m: Maybe<&2, U32>) -> U32: match m: case None{}: 0 case Some{n}: n type PsSt is Data: PsCheck{} PsDecide{sp: Bool} PsTake{} PsEmit{n: U32, empty_tok: Bool} PsEnd{empty_tok: Bool} def parse_starts_go(fuel: Nat, st: PsSt, rest: String, +tok: String, acc: List<&2, U32>) -> List<&2, U32>: match fuel: case 0n: acc case 1n+p: match st: case PsCheck{}: match rest: case SNil{}: parse_starts_go(p, PsEnd{String.is_empty(tok)}, rest, tok, acc) case SCon{+h, +t}: parse_starts_go(p, PsDecide{Char.is_space(h)}, rest, tok, acc) case PsEnd{empty_tok}: match empty_tok: case True{}: acc case False{}: parse_u32_or0(U32.read(tok)) <> acc case PsDecide{sp}: match sp: case True{}: parse_starts_go(p, PsEmit{parse_u32_or0(U32.read(tok)), String.is_empty(tok)}, rest, tok, acc) case False{}: parse_starts_go(p, PsTake{}, rest, tok, acc) case PsEmit{+n, empty_tok}: match empty_tok: case True{}: match rest: case SNil{}: acc case SCon{h, t}: parse_starts_go(p, PsCheck{}, t, "", acc) case False{}: match rest: case SNil{}: n <> acc case SCon{h, t}: parse_starts_go(p, PsCheck{}, t, "", n <> acc) case PsTake{}: match rest: case SNil{}: acc case SCon{+h, +t}: parse_starts_go( p, PsCheck{}, t, String.append(tok, String.from_list(h <> Nil{})), acc ) def rev_u32(xs: List<&2, U32>, acc: List<&2, U32>) -> List<&2, U32>: match xs: case Nil{}: acc case h <> t: rev_u32(t, h <> acc) def parse_starts(+s: String) -> List<&2, U32>: rev_u32( parse_starts_go( Nat.add(Nat.mul(5n, String.length(s)), 8n), PsCheck{}, s, "", Nil{} ), Nil{} ) type FdPh is Data: FdScan{} FdTest{n: U32, ok: Bool} def find_para_start(fuel: Nat, ph: FdPh, xs: List<&2, U32>, +v: U32, best: U32) -> U32: match fuel: case 0n: best case 1n+p: match ph: case FdScan{}: match xs: case Nil{}: best case +n <> t: find_para_start(p, FdTest{n, U32.is_le(n, v)}, t, v, best) case FdTest{+n, ok}: match ok: case True{}: find_para_start(p, FdScan{}, xs, v, n) case False{}: find_para_start(p, FdScan{}, xs, v, best) def next_para_hit(+n: U32, +found: U32, zero: Bool) -> U32: match zero: case True{}: n case False{}: found def next_para_after(fuel: Nat, ph: FdPh, xs: List<&2, U32>, +v: U32, +found: U32) -> U32: match fuel: case 0n: found case 1n+p: match ph: case FdScan{}: match xs: case Nil{}: found case +n <> t: next_para_after(p, FdTest{n, U32.is_gt(n, v)}, t, v, found) case FdTest{+n, ok}: match ok: case True{}: next_para_hit(n, found, U32.is_zero(found)) case False{}: next_para_after(p, FdScan{}, xs, v, found) def u32_max_go(+a: U32, +b: U32, le: Bool) -> U32: match le: case True{}: b case False{}: a def u32_max(+a: U32, +b: U32) -> U32: u32_max_go(a, b, U32.is_le(a, b)) def u32_min_go(+a: U32, +b: U32, le: Bool) -> U32: match le: case True{}: a case False{}: b def u32_min(+a: U32, +b: U32) -> U32: u32_min_go(a, b, U32.is_le(a, b)) def u32_sub_sat_go(+a: U32, +b: U32, ok: Bool) -> U32: match ok: case True{}: U32.sub(a, b) case False{}: 0 def u32_sub_sat(+a: U32, +b: U32) -> U32: u32_sub_sat_go(a, b, U32.is_le(b, a)) type FrPh is Data: FrBody{} FrStep{over: Bool} def fill_range_go(fuel: Nat, ph: FrPh, +cur: U32, +hi: U32, acc: List<&2, U32>) -> List<&2, U32>: match fuel: case 0n: acc case 1n+p: match ph: case FrBody{}: fill_range_go(p, FrStep{U32.is_gt(cur, hi)}, cur, hi, acc) case FrStep{over}: match over: case True{}: acc case False{}: fill_range_go(p, FrBody{}, U32.add(cur, 1), hi, cur <> acc) def fill_range_cons(fuel: Nat, +cur: U32, +hi: U32, acc: List<&2, U32>) -> List<&2, U32>: fill_range_go(fuel, FrBody{}, cur, hi, acc) def range_inclusive(+lo: U32, +hi: U32) -> List<&2, U32>: rev_u32(fill_range_cons(200n, lo, hi, Nil{}), Nil{}) def range_inclusive_go(+lo: U32, +hi: U32, empty: Bool) -> List<&2, U32>: match empty: case True{}: Nil{} case False{}: range_inclusive(lo, hi) type Ctx is Data: Ctx{ book: String, chapter: U32, verse: U32, before: List<&2, U32>, after: List<&2, U32> } def para_hi_excl_go(+nxt: U32, +ch_max: U32, zero: Bool) -> U32: match zero: case True{}: U32.add(ch_max, 1) case False{}: nxt def para_hi_excl(+nxt: U32, +ch_max: U32) -> U32: para_hi_excl_go(nxt, ch_max, U32.is_zero(nxt)) def chapter_max_or(m: Maybe<&2, U32>) -> U32: match m: case None{}: 176 case Some{n}: n def mk_ctx( +book: String, +ch: U32, +v: U32, +para_lo: U32, +para_hi_excl: U32, +max_before: U32, +max_after: U32 ) -> Ctx: Ctx{ book, ch, v, range_inclusive_go( u32_max(u32_sub_sat(v, max_before), para_lo), u32_sub_sat(v, 1), U32.is_lt(u32_sub_sat(v, 1), u32_max(u32_sub_sat(v, max_before), para_lo)) ), range_inclusive_go( U32.add(v, 1), u32_min(U32.add(v, max_after), u32_sub_sat(para_hi_excl, 1)), U32.is_lt( u32_min(U32.add(v, max_after), u32_sub_sat(para_hi_excl, 1)), U32.add(v, 1) ) ) } def expand_verse( +book: String, +ch: U32, +v: U32, +max_before: U32, +max_after: U32 ) -> Ctx: mk_ctx( book, ch, v, find_para_start(200n, FdScan{}, parse_starts(para_starts_str(book, ch)), v, 1), para_hi_excl( next_para_after(200n, FdScan{}, parse_starts(para_starts_str(book, ch)), v, 0), chapter_max_or(Books.verse_count(book, ch)) ), max_before, max_after ) def expand_default(+book: String, +ch: U32, +v: U32) -> Ctx: expand_verse(book, ch, v, 3, 3) def show_u32_list(xs: List<&2, U32>, acc: String) -> String: match xs: case Nil{}: acc case h <> Nil{}: String.append(acc, U32.show(h)) case h <> t: show_u32_list(t, String.append(String.append(acc, U32.show(h)), ",")) def format_ctx(c: Ctx) -> String: match c: case Ctx{+book, +chapter, +verse, +before, +after}: String.append( String.append( String.append( String.append(book, "."), String.append(U32.show(chapter), ".") ), String.append(U32.show(verse), " before=[") ), String.append( String.append(show_u32_list(before, ""), "] after=["), String.append(show_u32_list(after, ""), "]") ) )