~/bend-docscommunity

linkify.bend source

linkify.bend on the hub · documented module

# bible-linkify surface (pure Bend): protect-aware Markdown walker + plain matcherimport Baseimport ./route.bend as Routeimport ./protect.bend as Protimport ./parse.bend as Pdef char1(+c: Char) -> String:  String.from_list(c <> Nil{})# Linkify options (baseUrl / src / rewriteExisting), matching bible-linkify.type LinkOpts is Data:  LinkOpts{+base: String, +src: String, rewrite: Bool}def default_opts() -> LinkOpts:  LinkOpts{Route.default_base(), "", False{}}def opts_base(+base: String, +src: String, rewrite: Bool) -> LinkOpts:  LinkOpts{Route.normalize_base(base), src, rewrite}def linkify_parts(+visible: String, +canonical: String) -> String:  Route.markdown_route_link(visible, canonical)def linkify_parts_qv(+visible: String, +canonical: String, +translation: String) -> String:  Route.markdown_route_link_qv(visible, canonical, translation)def linkify_parts_opts(  +visible: String,  +canonical: String,  +translation: String,  +opts: LinkOpts) -> String:  match opts:    case LinkOpts{+base, +src, rewrite}:      Route.markdown_route_link_base_qvs(base, visible, canonical, translation, src)# Peek trailing " (ESV)" after a match — does not consume; code or "".type TvPh is Data:  TvSkipSp{}  TvSkipDec{sp: Bool}  TvNeedOpen{}  TvNeedOpenDec{is_open: Bool}  TvCode{+acc: String, +letters: Nat, +digits: Nat}  TvCls{+acc: String, +letters: Nat, +digits: Nat, is_close: Bool, ch: Char}  TvAl{+acc: String, +letters: Nat, +digits: Nat, ch: Char, is_alpha: Bool, dig0: Bool}  TvDg{+acc: String, +letters: Nat, +digits: Nat, ch: Char, is_digit: Bool}  TvDone{+acc: String, +letters: Nat, +digits: Nat}  TvCheck{+acc: String, ok: Bool}def tv_letter_ok(+letters: Nat) -> Bool:  Bool.and(Nat.is_ge(letters, 2n), Nat.is_le(letters, 8n))def tv_digit_ok(+digits: Nat) -> Bool:  Nat.is_le(digits, 2n)def peek_translation_go(fuel: Nat, ph: TvPh, rest: String) -> String:  match fuel:    case 0n:      ""    case 1n+p:      match ph:        case TvSkipSp{}:          match rest:            case SNil{}:              ""            case SCon{+h, +t}:              peek_translation_go(p, TvSkipDec{Char.is_space(h)}, SCon{h, t})        case TvSkipDec{sp}:          match sp:            case True{}:              match rest:                case SNil{}:                  ""                case SCon{h, t}:                  peek_translation_go(p, TvSkipSp{}, t)            case False{}:              peek_translation_go(p, TvNeedOpen{}, rest)        case TvNeedOpen{}:          match rest:            case SNil{}:              ""            case SCon{+h, +t}:              peek_translation_go(p, TvNeedOpenDec{Char.is_eq(h, '(')}, SCon{h, t})        case TvNeedOpenDec{is_open}:          match is_open:            case True{}:              match rest:                case SNil{}:                  ""                case SCon{h, t}:                  peek_translation_go(p, TvCode{"", 0n, 0n}, t)            case False{}:              ""        case TvCode{+acc, +letters, +digits}:          match rest:            case SNil{}:              ""            case SCon{+h, +t}:              peek_translation_go(                p,                TvCls{acc, letters, digits, Char.is_eq(h, ')'), h},                SCon{h, t}              )        case TvCls{+acc, +letters, +digits, is_close, +ch}:          match is_close:            case True{}:              peek_translation_go(p, TvDone{acc, letters, digits}, rest)            case False{}:              peek_translation_go(                p,                TvAl{acc, letters, digits, ch, Char.is_alpha(ch), Nat.is_eq(digits, 0n)},                rest              )        case TvAl{+acc, +letters, +digits, +ch, is_alpha, dig0}:          match is_alpha:            case True{}:              match dig0:                case True{}:                  match rest:                    case SNil{}:                      ""                    case SCon{h, t}:                      peek_translation_go(                        p,                        TvCode{String.append(acc, char1(ch)), Nat.add(letters, 1n), digits},                        t                      )                case False{}:                  ""            case False{}:              peek_translation_go(p, TvDg{acc, letters, digits, ch, Char.is_digit(ch)}, rest)        case TvDg{+acc, +letters, +digits, +ch, is_digit}:          match is_digit:            case True{}:              match rest:                case SNil{}:                  ""                case SCon{h, t}:                  peek_translation_go(                    p,                    TvCode{String.append(acc, char1(ch)), letters, Nat.add(digits, 1n)},                    t                  )            case False{}:              ""        case TvDone{+acc, +letters, +digits}:          peek_translation_go(            p,            TvCheck{acc, Bool.and(tv_letter_ok(letters), tv_digit_ok(digits))},            rest          )        case TvCheck{+acc, ok}:          match ok:            case True{}:              String.to_upper(acc)            case False{}:              ""def peek_translation(+suffix: String) -> String:  peek_translation_go(    Nat.add(Nat.mul(3n, String.length(suffix)), 16n),    TvSkipSp{},    suffix  )def is_newline(+c: Char) -> Bool:  match c:    case Chr{+code}:      U32.is_eq(code, 10)def is_alnum(+c: Char) -> Bool:  Bool.or(Char.is_alpha(c), Char.is_digit(c))def is_wrapper_lead(+c: Char) -> Bool:  Bool.or(    Char.is_eq(c, '('),    Bool.or(      Char.is_eq(c, '['),      Bool.or(        Char.is_eq(c, '{'),        Bool.or(          Char.is_eq(c, '"'),          Bool.or(Char.is_eq(c, '\''), Char.is_eq(c, '`'))        )      )    )  )def is_wrapper_trail(+c: Char) -> Bool:  Bool.or(    Char.is_eq(c, ')'),    Bool.or(      Char.is_eq(c, ']'),      Bool.or(        Char.is_eq(c, '}'),        Bool.or(          Char.is_eq(c, '"'),          Bool.or(            Char.is_eq(c, '\''),            Bool.or(              Char.is_eq(c, '`'),              Bool.or(                Char.is_eq(c, ','),                Bool.or(                  Char.is_eq(c, ';'),                  Bool.or(                    Char.is_eq(c, '.'),                    Bool.or(Char.is_eq(c, '!'), Char.is_eq(c, '?'))                  )                )              )            )          )        )      )    )  )# --- Passage signal ---type SigPh is Data:  SigScan{has_a: Bool, has_d: Bool}  SigBoth{has_a: Bool, has_d: Bool, both: Bool}def has_signal_go(fuel: Nat, ph: SigPh, rest: String) -> Bool:  match fuel:    case 0n:      match ph:        case SigScan{has_a, has_d}:          Bool.and(has_a, has_d)        case SigBoth{has_a, has_d, both}:          both    case 1n+p:      match ph:        case SigBoth{has_a, has_d, both}:          match both:            case True{}:              True{}            case False{}:              match rest:                case SNil{}:                  False{}                case SCon{+h, +t}:                  has_signal_go(                    p,                    SigScan{                      Bool.or(has_a, Char.is_alpha(h)),                      Bool.or(has_d, Char.is_digit(h))                    },                    t                  )        case SigScan{+has_a, +has_d}:          has_signal_go(p, SigBoth{has_a, has_d, Bool.and(has_a, has_d)}, rest)def has_passage_signal(+s: String) -> Bool:  has_signal_go(Nat.add(Nat.mul(4n, String.length(s)), 8n), SigScan{False{}, False{}}, s)# --- Trim wrappers: drop lead wrappers, reverse, drop lead (ex-trail), reverse ---type TrPh is Data:  TrLead{}  TrLeadDec{lead: Bool}def is_wrapper_either(+c: Char) -> Bool:  Bool.or(is_wrapper_lead(c), is_wrapper_trail(c))def trim_pass_go(fuel: Nat, ph: TrPh, rest: String) -> String:  match fuel:    case 0n:      rest    case 1n+p:      match ph:        case TrLead{}:          match rest:            case SNil{}:              ""            case SCon{+h, +t}:              trim_pass_go(p, TrLeadDec{is_wrapper_either(h)}, SCon{h, t})        case TrLeadDec{lead}:          match lead:            case True{}:              match rest:                case SNil{}:                  ""                case SCon{h, t}:                  trim_pass_go(p, TrLead{}, t)            case False{}:              restdef trim_pass(+s: String) -> String:  trim_pass_go(Nat.add(Nat.mul(3n, String.length(s)), 8n), TrLead{}, s)def trim_wrappers(+s: String) -> String:  String.reverse(trim_pass(String.reverse(trim_pass(s))))# --- Candidate take ---def is_colon_dot_dash(+c: Char) -> Bool:  match c:    case Chr{+code}:      Bool.or(U32.is_eq(code, 58), U32.is_eq(code, 45))def is_cand_char(+c: Char) -> Bool:  Bool.or(    is_alnum(c),    Bool.or(Char.is_space(c), is_colon_dot_dash(c))  )type TcPh is Data:  TcCheck{+n: Nat, has_d: Bool}  TcGe{+n: Nat, has_d: Bool, ge: Bool}  TcOk{+n: Nat, has_d: Bool, ok: Bool}  TcSp{+n: Nat, has_d: Bool, is_sp: Bool}  TcSpDig{+n: Nat}  TcSpNod{+n: Nat}  TcPeek{+n: Nat, next_alpha: Bool}  TcTake{+n: Nat, has_d: Bool}  TcTakeDig{+n: Nat, has_d: Bool, dig: Bool}def pick_sp(has_d: Bool, +n: Nat) -> TcPh:  match has_d:    case True{}:      TcSpDig{n}    case False{}:      TcSpNod{n}def take_cand_go(fuel: Nat, ph: TcPh, +rest: String, acc: String) -> String:  match fuel:    case 0n:      acc    case 1n+p:      match ph:        case TcCheck{+n, +has_d}:          take_cand_go(p, TcGe{n, has_d, Nat.is_ge(n, 40n)}, rest, acc)        case TcGe{+n, +has_d, ge}:          match ge:            case True{}:              acc            case False{}:              match rest:                case SNil{}:                  acc                case SCon{+h, +t}:                  take_cand_go(p, TcOk{n, has_d, is_cand_char(h)}, SCon{h, t}, acc)        case TcOk{+n, +has_d, ok}:          match ok:            case False{}:              acc            case True{}:              match rest:                case SNil{}:                  acc                case SCon{+h, +t}:                  take_cand_go(p, TcSp{n, has_d, Char.is_space(h)}, SCon{h, t}, acc)        case TcSp{+n, +has_d, is_sp}:          match is_sp:            case True{}:              take_cand_go(p, pick_sp(has_d, n), rest, acc)            case False{}:              take_cand_go(p, TcTake{n, has_d}, rest, acc)        case TcSpDig{+n}:          match rest:            case SNil{}:              acc            case SCon{h, +t}:              match t:                case SNil{}:                  acc                case SCon{+h2, t2}:                  take_cand_go(p, TcPeek{n, Char.is_alpha(h2)}, rest, acc)        case TcSpNod{+n}:          take_cand_go(p, TcTake{n, False{}}, rest, acc)        case TcPeek{+n, next_alpha}:          match next_alpha:            case True{}:              acc            case False{}:              take_cand_go(p, TcTake{n, True{}}, rest, acc)        case TcTake{+n, +has_d}:          match rest:            case SNil{}:              acc            case SCon{+h, +t}:              take_cand_go(p, TcTakeDig{n, has_d, Char.is_digit(h)}, SCon{h, t}, acc)        case TcTakeDig{+n, +has_d, dig}:          match rest:            case SNil{}:              acc            case SCon{+h, +t}:              take_cand_go(                p,                TcCheck{Nat.add(n, 1n), Bool.or(has_d, dig)},                t,                String.append(acc, char1(h))              )def take_candidate(+s: String) -> String:  take_cand_go(Nat.add(Nat.mul(6n, String.length(s)), 16n), TcCheck{0n, False{}}, s, "")type VisCan is Data:  VisCan{visible: String, canonical: String}type CandGate is Data:  CandNone{}  CandSome{visible: String, canonical: String}def cand_pick(empty: Bool, +visible: String, +canonical: String) -> CandGate:  match empty:    case True{}:      CandNone{}    case False{}:      CandSome{visible, canonical}def cand_from(+visible: String, +canonical: String) -> CandGate:  cand_pick(String.is_empty(canonical), visible, canonical)def try_parse_visible_gated(g: CandGate) -> Maybe<&2, VisCan>:  match g:    case CandNone{}:      None{}    case CandSome{+visible, +canonical}:      Some{VisCan{visible, canonical}}def try_parse_visible(+visible: String) -> Maybe<&2, VisCan>:  try_parse_visible_gated(    cand_from(String.trim(visible), P.try_canonical(String.trim(visible)))  )def drop_last_rev(rev: String) -> String:  match rev:    case SNil{}:      ""    case SCon{h, t}:      tdef drop_last(s: String) -> String:  String.reverse(drop_last_rev(String.reverse(s)))type TwPh is Data:  TwTry{s: String}  TwEmpty{s: String, empty: Bool}  TwShort{s: String, short: Bool}  TwParsed{s: String, m: Maybe<&2, VisCan>}def try_windows_go(fuel: Nat, ph: TwPh) -> Maybe<&2, VisCan>:  match fuel:    case 0n:      None{}    case 1n+p:      match ph:        case TwTry{+s}:          try_windows_go(p, TwEmpty{s, String.is_empty(s)})        case TwEmpty{+s, empty}:          match empty:            case True{}:              None{}            case False{}:              try_windows_go(p, TwShort{s, Nat.is_lt(String.length(s), 3n)})        case TwShort{+s, short}:          match short:            case True{}:              None{}            case False{}:              try_windows_go(p, TwParsed{s, try_parse_visible(trim_wrappers(s))})        case TwParsed{+s, m}:          match m:            case Some{vc}:              Some{vc}            case None{}:              try_windows_go(p, TwTry{drop_last(s)})def try_windows(+raw: String) -> Maybe<&2, VisCan>:  try_windows_go(Nat.add(Nat.mul(3n, String.length(raw)), 8n), TwTry{raw})# --- Urlish ---type ExPh is Data:  ExGo{}  ExDec{sp: Bool}def expand_token_left(fuel: Nat, ph: ExPh, +left_rev: String, acc: String) -> String:  match fuel:    case 0n:      acc    case 1n+p:      match ph:        case ExGo{}:          match left_rev:            case SNil{}:              acc            case SCon{+h, +t}:              expand_token_left(p, ExDec{Char.is_space(h)}, left_rev, acc)        case ExDec{sp}:          match sp:            case True{}:              acc            case False{}:              match left_rev:                case SNil{}:                  acc                case SCon{+h, +t}:                  expand_token_left(p, ExGo{}, t, String.append(char1(h), acc))def expand_token_right(fuel: Nat, ph: ExPh, +right: String, acc: String) -> String:  match fuel:    case 0n:      acc    case 1n+p:      match ph:        case ExGo{}:          match right:            case SNil{}:              acc            case SCon{+h, +t}:              expand_token_right(p, ExDec{Char.is_space(h)}, right, acc)        case ExDec{sp}:          match sp:            case True{}:              acc            case False{}:              match right:                case SNil{}:                  acc                case SCon{+h, +t}:                  expand_token_right(p, ExGo{}, t, String.append(acc, char1(h)))def token_around(+prefix_rev: String, +matched: String, +suffix: String) -> String:  String.append(    expand_token_left(Nat.add(String.length(prefix_rev), 4n), ExGo{}, prefix_rev, ""),    String.append(      matched,      expand_token_right(Nat.add(String.length(suffix), 4n), ExGo{}, suffix, "")    )  )def is_urlish_token(+tok: String) -> Bool:  Bool.or(    String.contains(String.to_lower(tok), "http://"),    Bool.or(      String.contains(String.to_lower(tok), "https://"),      String.contains(String.to_lower(tok), "www.")    )  )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 looks_like_ref_start(+c: Char) -> Bool:  Bool.or(Char.is_alpha(c), is_book_num(c))type NaPh is Data:  NaDec{alnum: Bool}def not_alnum_next_go(ph: NaPh) -> Bool:  match ph:    case NaDec{alnum}:      match alnum:        case True{}:          False{}        case False{}:          True{}def not_alnum_next(next_suffix: String) -> Bool:  match next_suffix:    case SNil{}:      True{}    case SCon{+h, t}:      not_alnum_next_go(NaDec{is_alnum(h)})def standalone_from_prev(prev_alnum: Bool, +next_suffix: String) -> Bool:  match prev_alnum:    case True{}:      False{}    case False{}:      not_alnum_next(next_suffix)def standalone_ok(+prev_rev: String, +next_suffix: String) -> Bool:  match prev_rev:    case SNil{}:      standalone_from_prev(False{}, next_suffix)    case SCon{+h, t}:      standalone_from_prev(is_alnum(h), next_suffix)# --- Plain linkify ---type PlPh is Data:  PlScan{}  PlNotRef{}  PlRefStart{}  PlRefCont{}  PlBoundOk{prev_alnum: Bool}  PlTry{}  PlTried{m: Maybe<&2, VisCan>}  PlAfterTry{visible: String, canonical: String, empty: Bool}  PlStand{visible: String, canonical: String, ok: Bool}  PlUrl{visible: String, canonical: String, bad: Bool}  PlEmit{ch: Char}def pick_pl_ref(at_start: Bool) -> PlPh:  match at_start:    case True{}:      PlRefStart{}    case False{}:      PlRefCont{}def pick_pl(is_ref: Bool, at_start: Bool) -> PlPh:  match is_ref:    case False{}:      PlNotRef{}    case True{}:      pick_pl_ref(at_start)def plain_append_link(  +out: String,  +visible: String,  +canonical: String,  +opts: LinkOpts) -> String:  String.append(out, linkify_parts_opts(visible, canonical, "", opts))def plain_append_link_at(  +out: String,  +visible: String,  +canonical: String,  +suffix: String,  +opts: LinkOpts) -> String:  String.append(    out,    linkify_parts_opts(visible, canonical, peek_translation(suffix), opts)  )def linkify_plain_go(  fuel: Nat,  ph: PlPh,  +rest: String,  +prev_rev: String,  out: String,  +opts: LinkOpts) -> String:  match fuel:    case 0n:      out    case 1n+p:      match ph:        case PlScan{}:          match rest:            case SNil{}:              out            case SCon{+h, +t}:              linkify_plain_go(                p,                pick_pl(looks_like_ref_start(h), String.is_empty(prev_rev)),                SCon{h, t},                prev_rev,                out,                opts              )        case PlNotRef{}:          match rest:            case SNil{}:              out            case SCon{+h, +t}:              linkify_plain_go(p, PlEmit{h}, SCon{h, t}, prev_rev, out, opts)        case PlRefStart{}:          linkify_plain_go(p, PlTry{}, rest, prev_rev, out, opts)        case PlRefCont{}:          match prev_rev:            case SNil{}:              linkify_plain_go(p, PlTry{}, rest, prev_rev, out, opts)            case SCon{+phc, pt}:              linkify_plain_go(p, PlBoundOk{is_alnum(phc)}, rest, prev_rev, out, opts)        case PlBoundOk{prev_alnum}:          match prev_alnum:            case True{}:              match rest:                case SNil{}:                  out                case SCon{+h, +t}:                  linkify_plain_go(p, PlEmit{h}, SCon{h, t}, prev_rev, out, opts)            case False{}:              linkify_plain_go(p, PlTry{}, rest, prev_rev, out, opts)        case PlTry{}:          match rest:            case SNil{}:              out            case SCon{+h0, +t0}:              linkify_plain_go(                p,                PlTried{try_windows(take_candidate(SCon{h0, t0}))},                SCon{h0, t0},                prev_rev,                out,                opts              )        case PlTried{m}:          match m:            case None{}:              match rest:                case SNil{}:                  out                case SCon{+h, +t}:                  linkify_plain_go(p, PlEmit{h}, SCon{h, t}, prev_rev, out, opts)            case Some{vc}:              match vc:                case VisCan{+visible, +canonical}:                  linkify_plain_go(                    p,                    PlScan{},                    Prot.str_drop(String.length(visible), rest),                    String.append(String.reverse(visible), prev_rev),                    plain_append_link_at(                      out,                      visible,                      canonical,                      Prot.str_drop(String.length(visible), rest),                      opts                    ),                    opts                  )        case PlAfterTry{+visible, +canonical, +empty}:          match empty:            case True{}:              match rest:                case SNil{}:                  out                case SCon{+h, +t}:                  linkify_plain_go(p, PlEmit{h}, SCon{h, t}, prev_rev, out, opts)            case False{}:              linkify_plain_go(                p,                PlStand{                  visible,                  canonical,                  standalone_ok(prev_rev, Prot.str_drop(String.length(visible), rest))                },                rest,                prev_rev,                out,                opts              )        case PlStand{+visible, +canonical, +ok}:          match ok:            case False{}:              match rest:                case SNil{}:                  out                case SCon{+h, +t}:                  linkify_plain_go(p, PlEmit{h}, SCon{h, t}, prev_rev, out, opts)            case True{}:              linkify_plain_go(                p,                PlUrl{                  visible,                  canonical,                  is_urlish_token(                    token_around(prev_rev, visible, Prot.str_drop(String.length(visible), rest))                  )                },                rest,                prev_rev,                out,                opts              )        case PlUrl{+visible, +canonical, +bad}:          match bad:            case True{}:              match rest:                case SNil{}:                  out                case SCon{+h, +t}:                  linkify_plain_go(p, PlEmit{h}, SCon{h, t}, prev_rev, out, opts)            case False{}:              linkify_plain_go(                p,                PlScan{},                Prot.str_drop(String.length(visible), rest),                String.append(String.reverse(visible), prev_rev),                plain_append_link_at(                  out,                  visible,                  canonical,                  Prot.str_drop(String.length(visible), rest),                  opts                ),                opts              )        case PlEmit{+ch}:          match rest:            case SNil{}:              out            case SCon{h, +t}:              linkify_plain_go(                p,                PlScan{},                t,                SCon{ch, prev_rev},                String.append(out, char1(ch)),                opts              )type LpGate is Data:  LpGate{ok: Bool}type FullHit2 is Data:  FullMiss2{}  FullExact{visible: String, canonical: String}  FullPrefix{visible: String, canonical: String, rest: String}def classify_rest(exact: Bool, +visible: String, +canonical: String, +rest: String) -> FullHit2:  match exact:    case True{}:      FullExact{visible, canonical}    case False{}:      FullPrefix{visible, canonical, rest}def classify_hit(m: Maybe<&2, VisCan>, +text: String) -> FullHit2:  match m:    case None{}:      FullMiss2{}    case Some{vc}:      match vc:        case VisCan{+visible, +canonical}:          classify_rest(            Nat.is_eq(String.length(visible), String.length(text)),            visible,            canonical,            Prot.str_drop(String.length(visible), text)          )def linkify_plain_from_hit(h: FullHit2, +text: String, +opts: LinkOpts) -> String:  match h:    case FullExact{+visible, +canonical}:      plain_append_link("", visible, canonical, opts)    case FullPrefix{+visible, +canonical, +rest}:      String.append(        plain_append_link_at("", visible, canonical, rest, opts),        linkify_plain_go(          Nat.add(Nat.mul(8n, String.length(rest)), 64n),          PlScan{},          rest,          String.reverse(visible),          "",          opts        )      )    case FullMiss2{}:      linkify_plain_go(        Nat.add(Nat.mul(8n, String.length(text)), 64n),        PlScan{},        text,        "",        "",        opts      )def linkify_plain_gated(g: LpGate, +text: String, +opts: LinkOpts) -> String:  match g:    case LpGate{ok}:      match ok:        case False{}:          text        case True{}:          linkify_plain_from_hit(            classify_hit(try_windows(take_candidate(text)), text),            text,            opts          )def linkify_plain_opts(+text: String, +opts: LinkOpts) -> String:  linkify_plain_gated(LpGate{has_passage_signal(text)}, text, opts)def linkify_plain(+text: String) -> String:  linkify_plain_opts(text, default_opts())# --- Extract label from [label](url); "" if image or malformed ---type MlPh is Data:  MlHead{}  MlHeadDec{is_bang: Bool, is_br: Bool}  MlLab{+acc: String}  MlLabDec{+acc: String, nl: Bool, is_br: Bool, ch: Char}  MlMid{+label: String}  MlMidDec{+label: String, is_par: Bool}  MlUrl{+label: String}  MlUrlDec{+label: String, nl: Bool, is_par: Bool}  MlDone{+label: String, empty: Bool}def md_label_go(fuel: Nat, ph: MlPh, rest: String) -> String:  match fuel:    case 0n:      ""    case 1n+p:      match ph:        case MlHead{}:          match rest:            case SNil{}:              ""            case SCon{+h, +t}:              md_label_go(                p,                MlHeadDec{Char.is_eq(h, '!'), Char.is_eq(h, '[')},                SCon{h, t}              )        case MlHeadDec{is_bang, is_br}:          match is_bang:            case True{}:              ""            case False{}:              match is_br:                case True{}:                  match rest:                    case SNil{}:                      ""                    case SCon{h, t}:                      md_label_go(p, MlLab{""}, t)                case False{}:                  ""        case MlLab{+acc}:          match rest:            case SNil{}:              ""            case SCon{+h, +t}:              md_label_go(                p,                MlLabDec{acc, is_newline(h), Char.is_eq(h, ']'), h},                SCon{h, t}              )        case MlLabDec{+acc, nl, is_br, +ch}:          match nl:            case True{}:              ""            case False{}:              match is_br:                case True{}:                  match rest:                    case SNil{}:                      ""                    case SCon{h, t}:                      md_label_go(p, MlMid{acc}, t)                case False{}:                  match rest:                    case SNil{}:                      ""                    case SCon{h, t}:                      md_label_go(p, MlLab{String.append(acc, char1(ch))}, t)        case MlMid{+label}:          match rest:            case SNil{}:              ""            case SCon{+h, +t}:              md_label_go(p, MlMidDec{label, Char.is_eq(h, '(')}, SCon{h, t})        case MlMidDec{+label, is_par}:          match is_par:            case True{}:              match rest:                case SNil{}:                  ""                case SCon{h, t}:                  md_label_go(p, MlUrl{label}, t)            case False{}:              ""        case MlUrl{+label}:          match rest:            case SNil{}:              ""            case SCon{+h, +t}:              md_label_go(                p,                MlUrlDec{label, is_newline(h), Char.is_eq(h, ')')},                SCon{h, t}              )        case MlUrlDec{+label, nl, is_par}:          match nl:            case True{}:              ""            case False{}:              match is_par:                case True{}:                  match rest:                    case SNil{}:                      ""                    case SCon{h, +t}:                      md_label_go(p, MlDone{label, String.is_empty(t)}, t)                case False{}:                  match rest:                    case SNil{}:                      ""                    case SCon{h, t}:                      md_label_go(p, MlUrl{label}, t)        case MlDone{+label, empty}:          match empty:            case True{}:              label            case False{}:              ""def md_extract_label(+token: String) -> String:  md_label_go(    Nat.add(Nat.mul(4n, String.length(token)), 16n),    MlHead{},    token  )type RwEq is Data:  RwEq{+visible: String, +canonical: String, eq: Bool}def rewrite_from_eq(g: RwEq, +opts: LinkOpts) -> String:  match g:    case RwEq{+visible, +canonical, eq}:      match eq:        case True{}:          linkify_parts_opts(visible, canonical, "", opts)        case False{}:          ""def rewrite_from_parse(  +label: String,  m: Maybe<&2, VisCan>,  +opts: LinkOpts) -> String:  match m:    case None{}:      ""    case Some{vc}:      match vc:        case VisCan{+visible, +canonical}:          rewrite_from_eq(RwEq{visible, canonical, String.eq(visible, label)}, opts)type RwPick is Data:  RwPick{+token: String, +repl: String, empty: Bool}def maybe_rewrite_pick_go(g: RwPick) -> String:  match g:    case RwPick{+token, +repl, empty}:      match empty:        case True{}:          token        case False{}:          repldef maybe_rewrite_pick(+token: String, +repl: String) -> String:  maybe_rewrite_pick_go(RwPick{token, repl, String.is_empty(repl)})def maybe_rewrite_md_link(+token: String, +opts: LinkOpts) -> String:  maybe_rewrite_pick(    token,    rewrite_from_parse(      String.trim(md_extract_label(token)),      try_parse_visible(String.trim(md_extract_label(token))),      opts    )  )def is_md_protect(+rest: String, +plen: Nat) -> Bool:  Bool.and(Nat.is_gt(plen, 0n), Nat.is_eq(Prot.md_link_len(rest), plen))def want_rewrite_md(+opts: LinkOpts, +rest: String, +plen: Nat) -> Bool:  match opts:    case LinkOpts{base, src, rewrite}:      match rewrite:        case False{}:          False{}        case True{}:          is_md_protect(rest, plen)# --- Markdown walker ---type MdWalk is Data:  MwCheck{at_bol: Bool, at_doc: Bool}  MwDecide{at_bol: Bool, plen: Nat, z: Bool}  MwProtect{len: Nat, at_bol: Bool}  MwProtDec{len: Nat, at_bol: Bool, z: Bool, nl: Bool}  MwPlainEmit{at_bol: Bool}  MwFlush{at_bol: Bool, plen: Nat, z: Bool}  MwTryRw{at_bol: Bool, plen: Nat, do_rw: Bool}def linkify_md_go(  fuel: Nat,  ph: MdWalk,  +rest: String,  plain: String,  out: String,  +opts: LinkOpts) -> String:  match fuel:    case 0n:      String.append(out, linkify_plain_opts(plain, opts))    case 1n+p:      match ph:        case MwCheck{+at_bol, +at_doc}:          match rest:            case SNil{}:              String.append(out, linkify_plain_opts(plain, opts))            case SCon{+h, +t}:              linkify_md_go(                p,                MwDecide{                  at_bol,                  Prot.frontmatter_or_protect(at_doc, at_bol, SCon{h, t}),                  False{}                },                SCon{h, t},                plain,                out,                opts              )        case MwDecide{+at_bol, +plen, z}:          linkify_md_go(            p,            MwFlush{at_bol, plen, Nat.is_eq(plen, 0n)},            rest,            plain,            out,            opts          )        case MwFlush{+at_bol, +plen, z}:          match z:            case True{}:              linkify_md_go(p, MwPlainEmit{at_bol}, rest, plain, out, opts)            case False{}:              linkify_md_go(                p,                MwTryRw{at_bol, plen, want_rewrite_md(opts, rest, plen)},                rest,                plain,                out,                opts              )        case MwTryRw{+at_bol, +plen, do_rw}:          match do_rw:            case True{}:              linkify_md_go(                p,                MwCheck{False{}, False{}},                Prot.str_drop(plen, rest),                "",                String.append(                  String.append(out, linkify_plain_opts(plain, opts)),                  maybe_rewrite_md_link(Prot.str_take(plen, rest), opts)                ),                opts              )            case False{}:              linkify_md_go(                p,                MwProtect{plen, at_bol},                rest,                "",                String.append(out, linkify_plain_opts(plain, opts)),                opts              )        case MwProtect{+len, +at_bol}:          match rest:            case SNil{}:              out            case SCon{+h, +t}:              linkify_md_go(                p,                MwProtDec{len, at_bol, Nat.is_eq(len, 0n), is_newline(h)},                SCon{h, t},                plain,                out,                opts              )        case MwProtDec{+len, +at_bol, z, nl}:          match z:            case True{}:              linkify_md_go(p, MwCheck{at_bol, False{}}, rest, plain, out, opts)            case False{}:              match rest:                case SNil{}:                  out                case SCon{+h, +t}:                  linkify_md_go(                    p,                    MwProtect{Nat.sub(len, 1n), nl},                    t,                    plain,                    String.append(out, char1(h)),                    opts                  )        case MwPlainEmit{+at_bol}:          match rest:            case SNil{}:              String.append(out, linkify_plain_opts(plain, opts))            case SCon{+h, +t}:              linkify_md_go(                p,                MwCheck{is_newline(h), False{}},                t,                String.append(plain, char1(h)),                out,                opts              )def linkify_markdown_opts(+source: String, +opts: LinkOpts) -> String:  linkify_md_go(    Nat.add(Nat.mul(10n, String.length(source)), 64n),    MwCheck{True{}, True{}},    source,    "",    "",    opts  )def linkify_markdown(+source: String) -> String:  linkify_markdown_opts(source, default_opts())def linkify_markdown_src(+source: String, +src: String) -> String:  linkify_markdown_opts(source, opts_base(Route.default_base(), src, False{}))def linkify_markdown_rewrite(+source: String) -> String:  linkify_markdown_opts(source, opts_base(Route.default_base(), "", True{}))def opts_cli(+src: String, rewrite: Bool) -> LinkOpts:  opts_base(Route.default_base(), src, rewrite)def linkify_markdown_cli(+source: String, +src: String, rewrite: Bool) -> String:  linkify_markdown_opts(source, opts_cli(src, rewrite))def show_links_rest(xs: List<&2, String>) -> IO(Unit):  match xs:    case Nil{}:      IO.print("")    case h <> t:      do IO<Unit>:        IO.print(h)        IO.print("\n")        show_links_rest(t)def show_links(xs: List<&2, String>) -> IO(Unit):  match xs:    case Nil{}:      IO.print("(no links)\n")    case h <> t:      do IO<Unit>:        IO.print(h)        IO.print("\n")        show_links_rest(t)