///|
/// Apply UAX #14 revision 57 in rule order at a significant boundary.
/// LB1, LB9, and LB10 are resolved in prepare_units; LB2/LB3 in the cursor.
/// Context summaries make each decision constant-time, including long runs
/// of spaces, numeric punctuation, combining marks, and regional indicators.
fn boundary(units : Array[BreakUnit], index : Int32) -> BreakOpportunity? {
let left = units[index - 1]
let right = units[index]
let a = left.class_
let b = right.class_
let before = class_at(units, index - 2)
let after = class_at(units, index + 1)
// LB4-LB6: mandatory breaks, with CR LF kept together.
if a == @ucd.CR && b == @ucd.LF {
return None
}
if a is (BK | CR | LF | NL) {
return Some(Mandatory)
}
if b is (BK | CR | LF | NL) {
return None
}
// LB7-LB8a: explicit spaces and zero-width controls precede LB9.
if b is (SP | ZW) {
return None
}
let base_index = left.space_base
let base = class_at(units, base_index)
if base == @ucd.ZW {
return Some(Allowed)
}
if left.ends_with_zwj {
return None
}
// LB11-LB12a.
if a == @ucd.WJ || b == @ucd.WJ || a == @ucd.GL {
return None
}
if b == @ucd.GL && !(a is (SP | HY | HH)) {
return None
}
// LB13-LB14.
if b is (CL | CP | EX | SY) {
return None
}
if base == @ucd.OP {
return None
}
// LB15a: initial quote after start, a hard break, or permitted context.
if base == @ucd.QU &&
units[base_index].initial_quote &&
class_at(units, base_index - 1)
is (XX | BK | CR | LF | NL | OP | QU | GL | SP | ZW) {
return None
}
// LB15b: final quote before end or the listed following context.
if b == @ucd.QU &&
right.final_quote &&
after
is (XX
| SP
| GL
| WJ
| CL
| QU
| CP
| EX
| IS
| SY
| BK
| CR
| LF
| NL
| ZW) {
return None
}
// LB15c-LB15d.
if a == @ucd.SP && b == @ucd.IS && after == @ucd.NU {
return Some(Allowed)
}
if b == @ucd.IS {
return None
}
// LB16-LB18.
if base is (CL | CP) && b == @ucd.NS {
return None
}
if base == @ucd.B2 && b == @ucd.B2 {
return None
}
if a == @ucd.SP {
return Some(Allowed)
}
// LB19-LB19a: unresolved quotes and their East Asian surroundings.
if (b == @ucd.QU && !right.initial_quote) ||
(a == @ucd.QU && !left.final_quote) {
return None
}
if b == @ucd.QU &&
(
!left.east_asian ||
!units.get(index + 1).map(u => u.east_asian).unwrap_or(false)
) {
return None
}
if a == @ucd.QU &&
(
!right.east_asian ||
!units.get(index - 2).map(u => u.east_asian).unwrap_or(false)
) {
return None
}
// LB20-LB20a.
if a == @ucd.CB || b == @ucd.CB {
return Some(Allowed)
}
if a is (HY | HH) &&
b is (AL | HL) &&
before is (XX | BK | CR | LF | NL | SP | ZW | CB | GL) {
return None
}
// LB21-LB22.
if b is (BA | HH | HY | NS) || a == @ucd.BB {
return None
}
if before == @ucd.HL && a is (HY | HH) && b != @ucd.HL {
return None
}
if a == @ucd.SY && b == @ucd.HL {
return None
}
if b == @ucd.IN {
return None
}
// LB23-LB24.
if (a is (AL | HL) && b == @ucd.NU) || (a == @ucd.NU && b is (AL | HL)) {
return None
}
if (a == @ucd.PR && b is (ID | EB | EM)) ||
(a is (ID | EB | EM) && b == @ucd.PO) {
return None
}
if (a is (PR | PO) && b is (AL | HL)) || (a is (AL | HL) && b is (PR | PO)) {
return None
}
// LB25: numeric prefixes/suffixes, parentheses, decimals and separators.
if b is (PO | PR) &&
(
left.numeric ||
(
a is (CL | CP) &&
units.get(index - 2).map(u => u.numeric).unwrap_or(false)
)
) {
return None
}
if a is (PO | PR) &&
(
b == @ucd.NU ||
(
b == @ucd.OP &&
(
after == @ucd.NU ||
(after == @ucd.IS && class_at(units, index + 2) == @ucd.NU)
)
)
) {
return None
}
if b == @ucd.NU && (a is (HY | IS) || left.numeric) {
return None
}
// LB26-LB27: Korean syllable blocks.
if (a == @ucd.JL && b is (JL | JV | H2 | H3)) ||
(a is (JV | H2) && b is (JV | JT)) ||
(a is (JT | H3) && b == @ucd.JT) {
return None
}
if (a is (JL | JV | JT | H2 | H3) && b == @ucd.PO) ||
(a == @ucd.PR && b is (JL | JV | JT | H2 | H3)) {
return None
}
// LB28-LB28a: alphabetics and Brahmic orthographic syllables.
if a is (AL | HL) && b is (AL | HL) {
return None
}
if a == @ucd.AP && right.aksara() {
return None
}
if left.aksara() && b is (VF | VI) {
return None
}
if a == @ucd.VI &&
(b == @ucd.AK || right.code_point == 0x25CC) &&
units.get(index - 2).map(u => u.aksara()).unwrap_or(false) {
return None
}
if left.aksara() && right.aksara() && after == @ucd.VF {
return None
}
// LB29-LB30.
if a == @ucd.IS && b is (AL | HL) {
return None
}
if (a is (AL | HL | NU) && b == @ucd.OP && !right.east_asian) ||
(a == @ucd.CP && !left.east_asian && b is (AL | HL | NU)) {
return None
}
// LB30a-LB30b.
if a == @ucd.RI && b == @ucd.RI && left.regional_count % 2 == 1 {
return None
}
if b == @ucd.EM && (a == @ucd.EB || left.potential_emoji) {
return None
}
// LB31.
Some(Allowed)
}