use super::spec::Behavior;
pub(super) fn split_regex<'p>(
piece: &'p str,
re: ®exr::Regex,
behavior: Behavior,
invert: bool,
out: &mut Vec<&'p str>,
) {
let matches: Vec<(usize, usize)> = re.find_iter(piece).map(|m| (m.start(), m.end())).collect();
let delims: Vec<(usize, usize)> = if invert {
let mut d = Vec::new();
let mut last = 0;
for &(s, e) in &matches {
if s > last {
d.push((last, s));
}
last = e;
}
if last < piece.len() {
d.push((last, piece.len()));
}
d
} else {
matches
};
let mut segs: Vec<Segment> = Vec::with_capacity(delims.len() * 2 + 1);
let mut last = 0;
for (s, e) in delims {
if s > last {
segs.push(Segment {
start: last,
end: s,
delimiter: false,
});
}
if e > s {
segs.push(Segment {
start: s,
end: e,
delimiter: true,
});
}
last = e;
}
if last < piece.len() {
segs.push(Segment {
start: last,
end: piece.len(),
delimiter: false,
});
}
emit_segments(piece, &segs, behavior, out);
}
#[derive(Debug, Clone, Copy)]
struct Segment {
start: usize,
end: usize,
delimiter: bool,
}
fn emit_segments<'p>(piece: &'p str, segs: &[Segment], behavior: Behavior, out: &mut Vec<&'p str>) {
let mut push = |start: usize, end: usize| out.push(&piece[start..end]);
match behavior {
Behavior::Isolated => {
for seg in segs {
push(seg.start, seg.end);
}
}
Behavior::Removed => {
for seg in segs.iter().filter(|seg| !seg.delimiter) {
push(seg.start, seg.end);
}
}
Behavior::MergedWithPrevious => {
let mut open: Option<(usize, usize)> = None;
for seg in segs {
match (&mut open, seg.delimiter) {
(Some((_, end)), true) => *end = seg.end,
_ => {
if let Some((start, end)) = open.replace((seg.start, seg.end)) {
push(start, end);
}
}
}
}
if let Some((start, end)) = open {
push(start, end);
}
}
Behavior::MergedWithNext => {
let mut pending: Option<usize> = None;
for seg in segs {
if seg.delimiter {
pending.get_or_insert(seg.start);
} else {
push(pending.take().unwrap_or(seg.start), seg.end);
}
}
if let Some(start) = pending {
push(start, segs[segs.len() - 1].end);
}
}
Behavior::Contiguous => {
let mut run: Option<(usize, usize)> = None;
for seg in segs {
if seg.delimiter {
match &mut run {
Some((_, end)) => *end = seg.end,
None => run = Some((seg.start, seg.end)),
}
} else {
if let Some((start, end)) = run.take() {
push(start, end);
}
push(seg.start, seg.end);
}
}
if let Some((start, end)) = run {
push(start, end);
}
}
}
}
pub(super) fn split_digits<'p>(piece: &'p str, individual: bool, out: &mut Vec<&'p str>) {
let mut start: Option<usize> = None;
let mut run_is_digit = false;
for (i, c) in piece.char_indices() {
let d = c.is_numeric();
if let Some(open) = start {
if d != run_is_digit || (d && individual) {
out.push(&piece[open..i]);
start = None;
}
}
let open = *start.get_or_insert(i);
run_is_digit = d;
if d && individual {
out.push(&piece[open..i + c.len_utf8()]);
start = None;
}
}
if let Some(open) = start {
out.push(&piece[open..]);
}
}
pub(super) fn split_punctuation<'p>(piece: &'p str, behavior: Behavior, out: &mut Vec<&'p str>) {
let mut segs: Vec<Segment> = Vec::new();
let mut content_start = 0;
let mut i = 0;
for c in piece.chars() {
let len = c.len_utf8();
if is_punctuation(c) {
if i > content_start {
segs.push(Segment {
start: content_start,
end: i,
delimiter: false,
});
}
segs.push(Segment {
start: i,
end: i + len,
delimiter: true,
});
content_start = i + len;
}
i += len;
}
if i > content_start {
segs.push(Segment {
start: content_start,
end: i,
delimiter: false,
});
}
emit_segments(piece, &segs, behavior, out);
}
fn is_punctuation(c: char) -> bool {
if c.is_ascii() {
return c.is_ascii_punctuation();
}
use unicode_general_category::{get_general_category, GeneralCategory::*};
matches!(
get_general_category(c),
ConnectorPunctuation
| DashPunctuation
| ClosePunctuation
| FinalPunctuation
| InitialPunctuation
| OtherPunctuation
| OpenPunctuation
)
}