use super::unicode_data as ud;
use super::{CodepointClass, HirExpr, HirLookaround, HirLookaroundKind, HirRepeat};
pub fn grapheme_cluster() -> HirExpr {
HirExpr::Alt(vec![
concat(vec![class(ud::GCB_CR), class(ud::GCB_LF)]),
concat(vec![class(ud::GCB_CR), not_followed_by(class(ud::GCB_LF))]),
class_of(&[ud::GCB_LF, ud::GCB_CONTROL]),
concat(vec![
star(class(ud::GCB_PREPEND)),
core(),
star(postcore()),
not_followed_by(postcore()),
]),
])
}
fn postcore() -> HirExpr {
class_of(&[ud::GCB_EXTEND, ud::GCB_ZWJ, ud::GCB_SPACINGMARK])
}
fn core() -> HirExpr {
HirExpr::Alt(vec![
hangul_syllable(),
concat(vec![
class(ud::GCB_REGIONAL_INDICATOR),
class(ud::GCB_REGIONAL_INDICATOR),
]),
conjunct_cluster(),
pictographic_sequence(),
HirExpr::UnicodeCpClass(CodepointClass::new(
union(&[&fallback_excluded()[..], ud::INCB_CONSONANT]),
true,
)),
concat(vec![
class(ud::INCB_CONSONANT),
not_followed_by(conjunct_link()),
]),
concat(vec![
class(ud::GCB_REGIONAL_INDICATOR),
not_followed_by(class(ud::GCB_REGIONAL_INDICATOR)),
]),
concat(vec![
plus(class(ud::GCB_PREPEND)),
not_followed_by(not_class_of(&[ud::GCB_CR, ud::GCB_LF, ud::GCB_CONTROL])),
]),
])
}
fn hangul_syllable() -> HirExpr {
let l = || class(ud::GCB_L);
let v = || class(ud::GCB_V);
let t = || class(ud::GCB_T);
HirExpr::Alt(vec![
concat(vec![
star(l()),
HirExpr::Alt(vec![
concat(vec![plus(v()), not_followed_by(v())]),
concat(vec![class(ud::GCB_LV), star(v()), not_followed_by(v())]),
class(ud::GCB_LVT),
]),
star(t()),
not_followed_by(t()),
]),
concat(vec![
plus(l()),
not_followed_by(class_of(&[ud::GCB_L, ud::GCB_V, ud::GCB_LV, ud::GCB_LVT])),
]),
concat(vec![plus(t()), not_followed_by(t())]),
])
}
fn conjunct_cluster() -> HirExpr {
concat(vec![
class(ud::INCB_CONSONANT),
plus(conjunct_link()),
not_followed_by(conjunct_link()),
])
}
fn fallback_excluded() -> Vec<(u32, u32)> {
union(&[
ud::GCB_CR,
ud::GCB_LF,
ud::GCB_CONTROL,
ud::GCB_REGIONAL_INDICATOR,
ud::GCB_PREPEND,
ud::GCB_L,
ud::GCB_V,
ud::GCB_T,
ud::GCB_LV,
ud::GCB_LVT,
ud::PROP_EXTENDED_PICTOGRAPHIC,
])
}
fn conjunct_link() -> HirExpr {
let linker_extend = || class_of(&[ud::INCB_EXTEND, ud::INCB_LINKER]);
concat(vec![
star(linker_extend()),
class(ud::INCB_LINKER),
star(linker_extend()),
class(ud::INCB_CONSONANT),
])
}
fn pictographic_sequence() -> HirExpr {
let pict = || class(ud::PROP_EXTENDED_PICTOGRAPHIC);
let join = || {
concat(vec![
star(class(ud::GCB_EXTEND)),
class(ud::GCB_ZWJ),
pict(),
])
};
concat(vec![
pict(),
star(join()),
not_followed_by(join()),
])
}
fn class(ranges: &'static [(u32, u32)]) -> HirExpr {
HirExpr::UnicodeCpClass(CodepointClass::new(ranges.to_vec(), false))
}
fn class_of(tables: &[&[(u32, u32)]]) -> HirExpr {
HirExpr::UnicodeCpClass(CodepointClass::new(union(tables), false))
}
fn not_class_of(tables: &[&[(u32, u32)]]) -> HirExpr {
HirExpr::UnicodeCpClass(CodepointClass::new(union(tables), true))
}
fn union(tables: &[&[(u32, u32)]]) -> Vec<(u32, u32)> {
let mut ranges: Vec<(u32, u32)> = tables.iter().flat_map(|t| t.iter().copied()).collect();
ranges.sort_unstable();
let mut merged: Vec<(u32, u32)> = Vec::with_capacity(ranges.len());
for (start, end) in ranges {
match merged.last_mut() {
Some(last) if start <= last.1.saturating_add(1) => last.1 = last.1.max(end),
_ => merged.push((start, end)),
}
}
merged
}
fn not_followed_by(expr: HirExpr) -> HirExpr {
HirExpr::Lookaround(Box::new(HirLookaround {
expr,
kind: HirLookaroundKind::NegativeLookahead,
}))
}
fn concat(exprs: Vec<HirExpr>) -> HirExpr {
HirExpr::Concat(exprs)
}
fn star(expr: HirExpr) -> HirExpr {
repeat(expr, 0)
}
fn plus(expr: HirExpr) -> HirExpr {
repeat(expr, 1)
}
fn repeat(expr: HirExpr, min: u32) -> HirExpr {
HirExpr::Repeat(Box::new(HirRepeat {
expr,
min,
max: None,
greedy: true,
}))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn union_is_sorted_and_disjoint() {
let merged = union(&[ud::GCB_EXTEND, ud::GCB_ZWJ, ud::GCB_SPACINGMARK]);
for pair in merged.windows(2) {
assert!(
pair[0].1 < pair[1].0,
"ranges {:?} and {:?} overlap or touch",
pair[0],
pair[1]
);
}
}
#[test]
fn union_covers_every_input_codepoint() {
let tables: &[&[(u32, u32)]] = &[ud::GCB_EXTEND, ud::GCB_ZWJ];
let merged = union(tables);
for table in tables {
for &(start, end) in *table {
for cp in [start, end] {
assert!(
merged.iter().any(|&(s, e)| cp >= s && cp <= e),
"U+{cp:04X} lost from the union"
);
}
}
}
}
}