use std::collections::HashMap;
use std::num::NonZeroU32;
use crate::orbit::{OrbitGroup, canonical};
use crate::token::{Token, TokenKind};
#[derive(Clone, Copy, Debug)]
pub struct EchoConfig {
pub orbit: OrbitGroup,
pub min_len: usize,
pub max_period_cv: f32,
}
impl Default for EchoConfig {
fn default() -> Self {
EchoConfig { orbit: OrbitGroup::Identity, min_len: 1, max_period_cv: 0.3 }
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct EchoFrame {
pub start: u32,
pub end: u32,
pub keyed: bool,
pub count: u32,
pub back_lag: Option<NonZeroU32>,
pub fwd_lag: Option<NonZeroU32>,
pub period: f32,
pub nth: u32,
}
impl EchoFrame {
#[must_use]
pub fn novel(&self) -> bool {
self.keyed && self.back_lag.is_none()
}
#[must_use]
pub fn echoed(&self) -> bool {
self.keyed && self.count >= 2
}
#[must_use]
pub fn strength(&self) -> f32 {
self.count.saturating_sub(1) as f32
}
}
#[derive(Clone, Debug)]
pub struct EchoField {
pub frames: Vec<EchoFrame>,
pub keyed: usize,
pub distinct: usize,
pub novel: usize,
pub echoed: usize,
}
impl EchoField {
#[must_use]
pub fn novelty(&self) -> f32 {
if self.keyed == 0 { 0.0 } else { self.novel as f32 / self.keyed as f32 }
}
#[must_use]
pub fn echo_rate(&self) -> f32 {
if self.keyed == 0 { 0.0 } else { self.echoed as f32 / self.keyed as f32 }
}
}
pub(crate) fn keyed_kind(kind: TokenKind) -> bool {
!matches!(
kind,
TokenKind::Whitespace
| TokenKind::Punct
| TokenKind::Open(_)
| TokenKind::Close(_)
| TokenKind::Other
)
}
const UNKEYED: u32 = u32::MAX;
fn group_tokens<K: Eq + std::hash::Hash>(
tokens: &[Token],
cfg: &EchoConfig,
frames: &mut [EchoFrame],
mut key_of: impl FnMut(usize) -> K,
) -> (Vec<u32>, Vec<u32>) {
let n = tokens.len();
let keyed = |t: &Token| keyed_kind(t.kind) && t.len() >= cfg.min_len;
let counting = crate::trace::phase("echo: keying, counting the keys");
let mut hashes: Vec<u32> = vec![0; n];
let mut keyed_count = 0u64;
let seen = Repeats::over(n, |table| {
for (i, t) in tokens.iter().enumerate() {
if keyed(t) {
keyed_count += 1;
let h = Repeats::index_of(&key_of(i));
hashes[i] = h;
table.saw(h);
}
}
});
crate::trace::counted("echo: tokens keyed", keyed_count);
drop(counting);
let _grouping = crate::trace::phase("echo: keying, grouping what repeats");
let mut ids: HashMap<K, u32, crate::fxhash::FxFinalBuild> = HashMap::default();
let mut group_of: Vec<u32> = vec![UNKEYED; n];
let mut counts: Vec<u32> = Vec::new();
let (mut walked, mut probed, mut inserted) = (0u64, 0u64, 0u64);
for (i, t) in tokens.iter().enumerate() {
walked += 1;
if !keyed(t) {
continue;
}
frames[i].keyed = true;
if seen.once(hashes[i]) {
group_of[i] = u32::try_from(counts.len()).expect("a group index within the stored width");
counts.push(1);
continue;
}
probed += 1;
let fresh = u32::try_from(counts.len()).expect("a group index within the stored width");
let g = *ids.entry(key_of(i)).or_insert(fresh);
if g == fresh {
inserted += 1;
counts.push(0);
}
counts[g as usize] += 1;
group_of[i] = g;
}
crate::trace::counted("echo: tokens the grouping loop walks", walked);
crate::trace::counted("echo: tokens that reach the map", probed);
crate::trace::counted("echo: map probes that write an entry", inserted);
crate::trace::counted("echo: map probes that find one", probed - inserted);
crate::trace::counted(std::any::type_name::<K>(), probed);
(group_of, counts)
}
struct Repeats {
slots: Vec<u8>,
mask: u64,
}
impl Repeats {
fn over(tokens: usize, fill: impl FnOnce(&mut Self)) -> Self {
let counters = tokens.saturating_mul(2).next_power_of_two().max(64);
let mut table = Repeats {
slots: vec![0u8; counters / 4],
mask: (counters - 1) as u64,
};
fill(&mut table);
table
}
fn index_of<K: std::hash::Hash>(key: &K) -> u32 {
use std::hash::BuildHasher;
let mixed = crate::fxhash::avalanche(crate::fxhash::FxBuild::process().hash_one(key));
(mixed & 0xffff_ffff) as u32
}
fn at(&self, index: u32) -> (usize, u32) {
let at = (u64::from(index) & self.mask) as usize;
(at / 4, ((at % 4) * 2) as u32)
}
fn saw(&mut self, index: u32) {
let (byte, shift) = self.at(index);
let held = (self.slots[byte] >> shift) & 0b11;
if held < 2 {
self.slots[byte] += 1 << shift;
}
}
fn once(&self, index: u32) -> bool {
let (byte, shift) = self.at(index);
(self.slots[byte] >> shift) & 0b11 == 1
}
}
#[must_use]
pub fn analyze(tokens: &[Token], bytes: &[u8]) -> EchoField {
analyze_with(tokens, bytes, &EchoConfig::default())
}
#[must_use]
pub fn analyze_bytes(bytes: &[u8]) -> EchoField {
analyze(&crate::lexer::lex(bytes), bytes)
}
#[must_use]
pub fn analyze_with(tokens: &[Token], bytes: &[u8], cfg: &EchoConfig) -> EchoField {
let framing = crate::trace::phase("echo: a frame a token");
let mut frames: Vec<EchoFrame> =
tokens
.iter()
.map(|t| EchoFrame { start: t.start, end: t.end, ..Default::default() })
.collect();
drop(framing);
let keying = crate::trace::phase("echo: keying the tokens");
let (group_of, counts) = match cfg.orbit {
OrbitGroup::Identity => {
group_tokens(tokens, cfg, &mut frames, |i| &bytes[tokens[i].span()])
}
g => group_tokens(tokens, cfg, &mut frames, |i| {
canonical(&bytes[tokens[i].span()], g).into_bytes()
}),
};
drop(keying);
let laying = crate::trace::phase("echo: laying out the occurrences");
let distinct = counts.len();
let mut starts: Vec<u32> = Vec::with_capacity(distinct + 1);
let mut acc = 0u32;
for &c in &counts {
starts.push(acc);
acc += c;
}
starts.push(acc);
let mut cursor: Vec<u32> = starts[..distinct].to_vec();
let mut flat: Vec<u32> = vec![0; acc as usize];
for (i, &g) in group_of.iter().enumerate() {
if g == UNKEYED {
continue;
}
let slot = &mut cursor[g as usize];
flat[*slot as usize] = i as u32;
*slot += 1;
}
drop(laying);
let _reading = crate::trace::phase("echo: reading the runs back onto the frames");
let mut keyed = 0usize;
let mut novel = 0usize;
let mut echoed = 0usize;
for g in 0..distinct {
let list = &flat[starts[g] as usize..starts[g + 1] as usize];
let count = list.len() as u32;
let lags = list.len().saturating_sub(1);
let lag = |w: usize| {
(tokens[list[w + 1] as usize].start - tokens[list[w] as usize].start) as f32
};
let period = (lags >= 2)
.then(|| {
let mut sum = 0.0f32;
for w in 0..lags {
sum += lag(w);
}
let mean = sum / lags as f32;
let mut spread = 0.0f32;
for w in 0..lags {
let d = lag(w) - mean;
spread += d * d;
}
let var = spread / lags as f32;
(mean > 0.0 && var.sqrt() / mean <= cfg.max_period_cv).then_some(mean)
})
.flatten();
for (j, &slot) in list.iter().enumerate() {
let i = slot as usize;
let f = &mut frames[i];
f.count = count;
f.back_lag = (j > 0).then(|| {
let lag = tokens[i].start - tokens[list[j - 1] as usize].start;
NonZeroU32::new(lag).expect("two occurrences of a key begin at different bytes")
});
f.fwd_lag = (j + 1 < list.len()).then(|| {
let lag = tokens[list[j + 1] as usize].start - tokens[i].start;
NonZeroU32::new(lag).expect("two occurrences of a key begin at different bytes")
});
f.period = period.unwrap_or(0.0);
f.nth = j as u32 + 1;
keyed += 1;
if j == 0 {
novel += 1;
}
if count >= 2 {
echoed += 1;
}
}
}
EchoField { frames, keyed, distinct, novel, echoed }
}
#[derive(Clone, Debug)]
pub struct SuperEcho {
pub key: String,
pub count: u32,
pub period: Option<f32>,
pub first: usize,
}
fn silhouette_letter(code: u32) -> char {
let kind = code >> 16;
if kind == TokenKind::Word.code() {
return 'W';
}
if kind == TokenKind::Number.code() {
return 'N';
}
if kind == TokenKind::Quoted.code() {
return 'Q';
}
if kind == TokenKind::Punct.code() {
let glyph = u8::try_from(code & 0xff).expect("eight bits are a byte");
return char::from(glyph);
}
match TokenKind::bracket_of_code(kind) {
Some((true, crate::token::BracketKind::Paren)) => '(',
Some((true, crate::token::BracketKind::Square)) => '[',
Some((true, crate::token::BracketKind::Brace)) => '{',
Some((false, crate::token::BracketKind::Paren)) => ')',
Some((false, crate::token::BracketKind::Square)) => ']',
Some((false, crate::token::BracketKind::Brace)) => '}',
None => 'T',
}
}
#[must_use]
pub fn analyze_super(bytes: &[u8]) -> Vec<SuperEcho> {
let toks = crate::lexer::lex(bytes);
let units = crate::supertoken::supertokens_from(&toks, bytes);
let ctx = crate::profile::AxisCtx::new(bytes);
let mut occ: HashMap<String, Vec<usize>> = HashMap::new();
let mut cursor = 0usize;
for u in &units {
while cursor < toks.len() && toks[cursor].start() < u.start {
cursor += 1;
}
let lo = cursor;
let mut hi = cursor;
while hi < toks.len() && toks[hi].end() <= u.end {
hi += 1;
}
let shape: crate::profile::ShapeProfile = crate::profile::fold_tokens(
lo,
toks[lo..hi].iter().filter(|t| t.is_significant()).copied().collect::<Vec<_>>().as_slice(),
&ctx,
);
let mut key = String::from(u.role.label());
key.push(':');
for &code in &shape.silhouette {
key.push(silhouette_letter(code));
}
occ.entry(key).or_default().push(u.start);
}
let cfg = EchoConfig::default();
let mut out: Vec<SuperEcho> = occ
.into_iter()
.filter(|(_, starts)| starts.len() >= 2)
.map(|(key, starts)| {
let lags: Vec<f32> = starts.windows(2).map(|w| (w[1] - w[0]) as f32).collect();
let period = (lags.len() >= 2)
.then(|| {
let mean = lags.iter().sum::<f32>() / lags.len() as f32;
let var = lags.iter().map(|l| (l - mean) * (l - mean)).sum::<f32>()
/ lags.len() as f32;
(mean > 0.0 && var.sqrt() / mean <= cfg.max_period_cv).then_some(mean)
})
.flatten();
SuperEcho { key, count: starts.len() as u32, period, first: starts[0] }
})
.collect();
out.sort_by(|a, b| b.count.cmp(&a.count).then(a.first.cmp(&b.first)));
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_frame_is_the_width_the_table_is_counted_at() {
assert_eq!(size_of::<EchoFrame>(), 32, "a frame is {} bytes", size_of::<EchoFrame>());
}
#[test]
fn novel_then_echoed() {
let bytes = b"the whale swam and the whale sang";
let field = analyze_bytes(bytes);
let toks = crate::lexer::lex(bytes);
let whales: Vec<usize> = (0..toks.len())
.filter(|&i| &bytes[toks[i].span()] == b"whale")
.collect();
assert_eq!(whales.len(), 2);
let (a, b) = (field.frames[whales[0]], field.frames[whales[1]]);
assert!(a.novel() && a.echoed(), "first whale is novel and echoed: {a:?}");
assert!(!b.novel() && b.echoed(), "second whale echoes: {b:?}");
assert_eq!(a.count, 2);
assert_eq!(b.back_lag, NonZeroU32::new(toks[whales[1]].start - toks[whales[0]].start));
assert_eq!(a.fwd_lag, b.back_lag);
}
#[test]
fn unique_token_is_novel_never_echoed() {
let field = analyze_bytes(b"one two three");
for f in field.frames.iter().filter(|f| f.keyed) {
assert!(f.novel() && !f.echoed(), "{f:?}");
assert_eq!(f.strength(), 0.0);
}
assert_eq!(field.novelty(), 1.0);
assert_eq!(field.echo_rate(), 0.0);
}
#[test]
fn orbit_quotient_folds_case() {
let bytes = b"Whale and whale";
let exact = analyze_bytes(bytes);
assert_eq!(exact.echoed, 0);
let folded = analyze_with(
&crate::lexer::lex(bytes),
bytes,
&EchoConfig { orbit: OrbitGroup::Case, ..Default::default() },
);
assert_eq!(folded.echoed, 2, "Whale/whale are one key under Case");
}
#[test]
fn regular_recurrence_has_a_period() {
let line = "tick aa bb cc dd ee ".repeat(6);
let field = analyze_bytes(line.as_bytes());
let toks = crate::lexer::lex(line.as_bytes());
let tick = (0..toks.len())
.find(|&i| &line.as_bytes()[toks[i].span()] == b"tick")
.expect("tick present");
let p = field.frames[tick].period;
assert!(p > 0.0, "tick recurs regularly, so it carries a period");
assert!((p - 20.0).abs() < 1.0, "period ~20 bytes, got {p}");
}
#[test]
fn punctuation_is_not_keyed() {
let field = analyze_bytes(b"a , b , c , d");
let toks = crate::lexer::lex(b"a , b , c , d");
for (i, t) in toks.iter().enumerate() {
if t.kind == TokenKind::Punct {
assert!(!field.frames[i].keyed);
assert!(!field.frames[i].echoed());
}
}
}
#[test]
fn super_echo_finds_structural_rhyme() {
let bytes = b"alpha: one\nbravo: two\ndelta: six\n";
let rhymes = analyze_super(bytes);
assert!(
rhymes.iter().any(|r| r.count == 3 && r.key == "kv:W:W"),
"three kv units rhyme structurally as kv:W:W: {rhymes:?}"
);
}
#[test]
fn a_silhouette_spells_its_kinds() {
let letter = |kind: TokenKind, text: &[u8]| {
silhouette_letter(crate::shape::shape_class(kind, text))
};
assert_eq!(letter(TokenKind::Word, b"alpha"), 'W');
assert_eq!(letter(TokenKind::Number, b"42"), 'N');
assert_eq!(letter(TokenKind::Quoted, b"\"bob\""), 'Q');
assert_eq!(letter(TokenKind::Punct, b":"), ':');
assert_eq!(letter(TokenKind::Punct, b","), ',');
assert_eq!(letter(TokenKind::Open(crate::token::BracketKind::Brace), b"{"), '{');
assert_eq!(letter(TokenKind::Close(crate::token::BracketKind::Square), b"]"), ']');
assert_eq!(letter(TokenKind::Ip, b"10.0.0.1"), 'T');
assert_eq!(letter(TokenKind::Email, b"bob@x.com"), 'T');
}
#[test]
fn empty_is_safe() {
let field = analyze_bytes(b"");
assert!(field.frames.is_empty());
assert_eq!(field.novelty(), 0.0);
assert!(analyze_super(b"").is_empty());
}
#[test]
fn a_key_seen_twice_never_reads_as_seen_once() {
let keys: Vec<String> = (0..20_000).map(|i| format!("value_{i}")).collect();
let table = Repeats::over(keys.len(), |t| {
for k in &keys {
t.saw(Repeats::index_of(k));
t.saw(Repeats::index_of(k));
}
});
for k in &keys {
assert!(
!table.once(Repeats::index_of(k)),
"{k} was seen twice and must not read as once"
);
}
}
#[test]
fn a_counter_tells_once_from_more_than_once() {
let table = Repeats::over(64, |t| {
t.saw(11);
t.saw(11);
t.saw(22);
});
assert!(!table.once(11), "counted twice");
assert!(table.once(22), "counted once");
assert!(!table.once(33), "never counted");
}
#[test]
fn a_counter_saturates_and_leaves_its_neighbours_alone() {
let table = Repeats::over(64, |t| {
for _ in 0..9 {
t.saw(7);
}
t.saw(8);
});
assert!(!table.once(7), "nine appearances read as more than once");
assert!(table.once(8), "its neighbour is untouched");
}
}