mod candidate;
mod compiled;
mod context;
mod scanner;
pub(crate) use candidate::{
Candidate, EnclosureSlot, Judge, Judgment, PendingCandidate, PendingEnclosure, TerminatorKind,
};
pub(crate) use compiled::CompiledRules;
pub(crate) use context::{window_around, ContextBuf, CONTEXT_CHARS, WINDOW_CHARS};
pub(crate) use scanner::scan_chunk;
use crate::domain::types::DepthVec;
use smallvec::SmallVec;
pub(crate) type CandidateVec = SmallVec<[Candidate; 8]>;
pub(crate) type PendingVec = SmallVec<[PendingCandidate; 4]>;
pub(crate) type PendingEncVec = SmallVec<[PendingEnclosure; 2]>;
pub(crate) type ToggleVec = SmallVec<[(usize, EnclosureSlot); 4]>;
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct PartialState {
pub boundaries: CandidateVec,
pub pending: PendingVec,
pub pending_enc: PendingEncVec,
pub deltas: DepthVec,
pub parity: u32,
pub head_ctx: ContextBuf,
pub tail_ctx: ContextBuf,
pub chunk_len: usize,
}
impl PartialState {
pub(crate) fn identity() -> Self {
Self {
boundaries: CandidateVec::new(),
pending: PendingVec::new(),
pending_enc: PendingEncVec::new(),
deltas: DepthVec::new(),
parity: 0,
head_ctx: ContextBuf::empty(),
tail_ctx: ContextBuf::empty(),
chunk_len: 0,
}
}
fn chars_before(&self, p: usize) -> usize {
if p <= self.head_ctx.byte_len() {
self.head_ctx.as_str()[..p].chars().count()
} else {
CONTEXT_CHARS
}
}
fn chars_after(&self, p: usize) -> usize {
let tail_start = self.chunk_len - self.tail_ctx.byte_len();
if p >= tail_start {
self.tail_ctx.as_str()[p - tail_start..].chars().count()
} else {
CONTEXT_CHARS
}
}
fn window_available(&self, p: usize) -> bool {
self.chars_before(p) >= WINDOW_CHARS && self.chars_after(p) >= WINDOW_CHARS
}
#[cfg(test)]
pub(crate) fn combine_with<J: Judge>(&self, other: &Self, judge: &J) -> Self {
let mut acc = self.clone();
acc.absorb(other, judge);
acc
}
pub(crate) fn absorb<J: Judge>(&mut self, other: &Self, judge: &J) -> ToggleVec {
let left_len = self.chunk_len;
let left_deltas = self.deltas.clone();
let left_parity = self.parity;
let mut joint = [0u8; 2 * CONTEXT_CHARS * 4];
let lt = self.tail_ctx.as_str().as_bytes();
let rh = other.head_ctx.as_str().as_bytes();
joint[..lt.len()].copy_from_slice(lt);
joint[lt.len()..lt.len() + rh.len()].copy_from_slice(rh);
let joint_str = std::str::from_utf8(&joint[..lt.len() + rh.len()])
.expect("context buffers hold valid UTF-8");
let joint_start = left_len - self.tail_ctx.byte_len();
if other.deltas.len() > self.deltas.len() {
self.deltas.resize(other.deltas.len(), 0);
}
for (i, d) in other.deltas.iter().enumerate() {
self.deltas[i] += d;
}
self.parity ^= other.parity;
self.head_ctx = ContextBuf::compose_head(&self.head_ctx, &other.head_ctx);
self.tail_ctx = ContextBuf::compose_tail(&self.tail_ctx, &other.tail_ctx);
self.chunk_len += other.chunk_len;
let mut toggles = ToggleVec::new();
let prior_enc = std::mem::take(&mut self.pending_enc);
let right_enc = other.pending_enc.iter().map(|pe| PendingEnclosure {
local_offset: pe.local_offset + left_len,
..*pe
});
for pe in prior_enc.into_iter().chain(right_enc) {
if self.window_available(pe.local_offset) {
let (window, pos) = resolve_window(joint_str, joint_start, pe.local_offset);
if !judge.suppress_enclosure(window, pos, pe.ch) {
toggles.push((pe.local_offset, pe.slot));
}
} else {
self.pending_enc.push(pe);
}
}
for &(_, slot) in &toggles {
apply_slot_to_totals(&mut self.deltas, &mut self.parity, slot);
}
for c in &other.boundaries {
let mut c = rebase_candidate(c, left_len, &left_deltas, left_parity);
adjust_for_toggles(
&mut c.local_depths,
&mut c.local_parity,
c.local_offset,
&toggles,
);
self.boundaries.push(c);
}
let prior_pending = std::mem::take(&mut self.pending);
let right_pending = other
.pending
.iter()
.map(|p| rebase_pending_candidate(p, left_len, &left_deltas, left_parity));
for mut pc in prior_pending.into_iter().chain(right_pending) {
adjust_for_toggles(
&mut pc.local_depths,
&mut pc.local_parity,
pc.local_offset,
&toggles,
);
if self.window_available(pc.local_offset) {
let (window, pos) = resolve_window(joint_str, joint_start, pc.local_offset);
if let Judgment::Boundary(flags) = judge.judge(window, pos, pc.kind) {
insert_sorted(&mut self.boundaries, pc.confirm(flags));
}
} else {
self.pending.push(pc);
}
}
toggles
}
#[cfg(test)]
pub(crate) fn resolve_edges<J: Judge>(self, judge: &J) -> Self {
self.resolve_edges_full(judge).0
}
pub(crate) fn resolve_edges_full<J: Judge>(mut self, judge: &J) -> (Self, ToggleVec) {
let pending_enc = std::mem::take(&mut self.pending_enc);
let mut toggles = ToggleVec::new();
for pe in pending_enc {
let p = pe.local_offset;
let (buf, buf_start) = if self.chars_before(p) < WINDOW_CHARS {
(self.head_ctx.as_str(), 0)
} else {
let tail_start = self.chunk_len - self.tail_ctx.byte_len();
debug_assert!(p >= tail_start, "pending enclosure outside both buffers");
(self.tail_ctx.as_str(), tail_start)
};
let (window, pos) = window_around(buf, p - buf_start, WINDOW_CHARS);
if !judge.suppress_enclosure(window, pos, pe.ch) {
toggles.push((p, pe.slot));
}
}
if !toggles.is_empty() {
for &(_, slot) in &toggles {
apply_slot_to_totals(&mut self.deltas, &mut self.parity, slot);
}
for c in &mut self.boundaries {
adjust_for_toggles(
&mut c.local_depths,
&mut c.local_parity,
c.local_offset,
&toggles,
);
}
for pc in &mut self.pending {
adjust_for_toggles(
&mut pc.local_depths,
&mut pc.local_parity,
pc.local_offset,
&toggles,
);
}
}
let pending = std::mem::take(&mut self.pending);
for pc in pending {
let p = pc.local_offset;
let (buf, buf_start) = if self.chars_before(p) < WINDOW_CHARS {
(self.head_ctx.as_str(), 0)
} else {
let tail_start = self.chunk_len - self.tail_ctx.byte_len();
debug_assert!(p >= tail_start, "pending candidate outside both buffers");
(self.tail_ctx.as_str(), tail_start)
};
let (window, pos) = window_around(buf, p - buf_start, WINDOW_CHARS);
if let Judgment::Boundary(flags) = judge.judge(window, pos, pc.kind) {
self.boundaries.push(pc.confirm(flags));
}
}
self.boundaries.sort_unstable_by_key(|c| c.local_offset);
(self, toggles)
}
}
fn resolve_window(joint_str: &str, joint_start: usize, p: usize) -> (&str, usize) {
debug_assert!(
p >= joint_start && p <= joint_start + joint_str.len(),
"resolvable pending item must lie inside the joint window"
);
let (window, pos) = window_around(joint_str, p - joint_start, WINDOW_CHARS);
debug_assert_eq!(
window.chars().count(),
2 * WINDOW_CHARS,
"a window resolved at combine is never clipped (Window Availability)"
);
(window, pos)
}
pub(crate) fn rebase_candidate(
c: &Candidate,
left_len: usize,
left_deltas: &DepthVec,
left_parity: u32,
) -> Candidate {
Candidate {
local_offset: c.local_offset + left_len,
local_depths: rebase_depths(&c.local_depths, left_deltas),
local_parity: c.local_parity ^ left_parity,
flags: c.flags,
}
}
fn rebase_pending_candidate(
p: &PendingCandidate,
left_len: usize,
left_deltas: &DepthVec,
left_parity: u32,
) -> PendingCandidate {
PendingCandidate {
local_offset: p.local_offset + left_len,
local_depths: rebase_depths(&p.local_depths, left_deltas),
local_parity: p.local_parity ^ left_parity,
kind: p.kind,
}
}
fn rebase_depths(depths: &DepthVec, left_deltas: &DepthVec) -> DepthVec {
let len = depths.len().max(left_deltas.len());
(0..len)
.map(|i| depths.get(i).copied().unwrap_or(0) + left_deltas.get(i).copied().unwrap_or(0))
.collect()
}
fn insert_sorted(boundaries: &mut CandidateVec, c: Candidate) {
let pos = boundaries.partition_point(|b| b.local_offset < c.local_offset);
boundaries.insert(pos, c);
}
fn apply_slot_to_totals(deltas: &mut DepthVec, parity: &mut u32, slot: EnclosureSlot) {
match slot {
EnclosureSlot::Asym { index, delta } => {
let i = index as usize;
if deltas.len() <= i {
deltas.resize(i + 1, 0);
}
deltas[i] += i32::from(delta);
}
EnclosureSlot::Sym { bit } => *parity ^= 1 << bit,
}
}
pub(crate) fn adjust_for_toggles(
depths: &mut DepthVec,
parity: &mut u32,
offset: usize,
toggles: &[(usize, EnclosureSlot)],
) {
for &(q, slot) in toggles {
if q < offset {
match slot {
EnclosureSlot::Asym { index, delta } => {
let i = index as usize;
if depths.len() <= i {
depths.resize(i + 1, 0);
}
depths[i] += i32::from(delta);
}
EnclosureSlot::Sym { bit } => *parity ^= 1 << bit,
}
}
}
}
#[cfg(test)]
mod tests;