use crate::token::{Token, TokenKind};
#[derive(Clone, Copy, Debug)]
pub struct MagnitudeConfig {
pub energy_window: usize,
pub jump_threshold: f32,
pub outlier_sigma: f32,
}
impl Default for MagnitudeConfig {
fn default() -> Self {
Self {
energy_window: 16,
jump_threshold: 3.0,
outlier_sigma: 2.5,
}
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct MagnitudeFrame {
pub magnitude: f32,
pub gradient: f32,
pub energy: f32,
}
#[derive(Clone, Debug, Default)]
pub struct MagnitudeField {
pub n_tokens: usize,
pub spans: Vec<(usize, usize)>,
pub frames: Vec<MagnitudeFrame>,
pub jumps: Vec<usize>,
pub total_energy: f32,
mean: f32,
std: f32,
outlier_sigma: f32,
}
impl MagnitudeField {
fn token_at(&self, byte: usize) -> Option<usize> {
if self.spans.is_empty() {
return None;
}
let i = self.spans.partition_point(|&(s, _)| s <= byte);
Some(i.saturating_sub(1))
}
#[must_use]
pub fn magnitude_at(&self, byte: usize) -> f32 {
self.token_at(byte)
.and_then(|i| self.frames.get(i))
.map_or(0.0, |f| f.magnitude)
}
#[must_use]
pub fn gradient_at(&self, byte: usize) -> f32 {
self.token_at(byte)
.and_then(|i| self.frames.get(i))
.map_or(0.0, |f| f.gradient)
}
#[must_use]
pub fn energy_of(&self, start: usize, end: usize) -> f32 {
self.spans
.iter()
.zip(&self.frames)
.filter(|((s, e), _)| *s < end && *e > start)
.map(|(_, f)| f.magnitude * f.magnitude)
.sum()
}
#[must_use]
pub fn outliers(&self) -> Vec<usize> {
if self.std <= f32::EPSILON {
return Vec::new();
}
self.frames
.iter()
.enumerate()
.filter(|(_, f)| ((f.magnitude - self.mean) / self.std).abs() >= self.outlier_sigma)
.map(|(i, _)| i)
.collect()
}
}
fn numeric_value(s: &str) -> Option<f64> {
let s = s.trim();
if !s.is_empty() && s.bytes().all(|b| b.is_ascii_digit()) {
match s.parse::<u64>() {
Ok(v) => return Some(v as f64),
Err(too_long) => {
debug_assert_eq!(*too_long.kind(), std::num::IntErrorKind::PosOverflow, "{s:?}");
}
}
}
let t: std::borrow::Cow<'_, str> = if s.contains('_') {
std::borrow::Cow::Owned(s.chars().filter(|&c| c != '_').collect())
} else {
std::borrow::Cow::Borrowed(s)
};
if let Some(hex) = t.strip_prefix("0x").or_else(|| t.strip_prefix("0X")) {
return u64::from_str_radix(hex, 16).ok().map(|v| v as f64);
}
t.parse::<f64>().ok()
}
static LOG2_OF_LENGTH: std::sync::LazyLock<[f32; 257]> =
std::sync::LazyLock::new(|| std::array::from_fn(|n| (n.max(1) as f32).log2()));
#[must_use]
pub fn token_magnitude(kind: TokenKind, tok_bytes: &[u8]) -> f32 {
match kind {
TokenKind::Number => {
let s = String::from_utf8_lossy(tok_bytes);
match numeric_value(&s) {
Some(v) if v.abs() > 0.0 => v.abs().log10() as f32,
_ => 0.0,
}
}
_ => {
let n = tok_bytes.len();
match LOG2_OF_LENGTH.get(n) {
Some(&m) => m,
None => (n as f32).log2(),
}
}
}
}
#[must_use]
pub fn analyze(tokens: &[Token], bytes: &[u8]) -> MagnitudeField {
analyze_with(tokens, bytes, &MagnitudeConfig::default())
}
#[must_use]
pub fn analyze_bytes(bytes: &[u8]) -> MagnitudeField {
let toks = crate::tokutil::lex_sig(bytes);
analyze(&toks, bytes)
}
#[must_use]
pub fn analyze_with(tokens: &[Token], bytes: &[u8], cfg: &MagnitudeConfig) -> MagnitudeField {
let n = tokens.len();
let mut field = MagnitudeField {
n_tokens: n,
spans: Vec::with_capacity(n),
frames: Vec::with_capacity(n),
jumps: Vec::new(),
total_energy: 0.0,
mean: 0.0,
std: 0.0,
outlier_sigma: cfg.outlier_sigma,
};
if n == 0 {
return field;
}
let mags: Vec<f32> = tokens
.iter()
.map(|t| token_magnitude(t.kind, &bytes[t.span()]))
.collect();
for t in tokens {
field.spans.push((t.start(), t.end()));
}
let sum: f32 = mags.iter().copied().sum();
let mean = sum / n as f32;
let var: f32 = mags.iter().map(|&m| (m - mean) * (m - mean)).sum::<f32>() / n as f32;
field.mean = mean;
field.std = var.sqrt();
field.total_energy = mags.iter().map(|&m| m * m).sum();
let mut frames: Vec<MagnitudeFrame> = Vec::with_capacity(n);
for i in 0..n {
let gradient = if i > 0 { mags[i] - mags[i - 1] } else { 0.0 };
if i > 0 && gradient.abs() >= cfg.jump_threshold {
field.jumps.push(tokens[i].start());
}
let lo = i.saturating_sub(cfg.energy_window.saturating_sub(1));
let energy: f32 = mags[lo..=i].iter().map(|&m| m * m).sum();
frames.push(MagnitudeFrame {
magnitude: mags[i],
gradient,
energy,
});
}
field.frames = frames;
field
}
#[cfg(test)]
mod tests {
use super::*;
fn field(s: &str) -> MagnitudeField {
analyze_bytes(s.as_bytes())
}
#[test]
fn scale_outlier_is_detected() {
let f = field("x = 5 ; y = 6 ; z = 5000000000 ; w = 7");
assert!(!f.jumps.is_empty(), "the 5 -> 5e9 jump should register");
assert!(
!f.outliers().is_empty(),
"the huge value should be a scale outlier"
);
}
#[test]
fn magnitude_ramp_has_steady_positive_gradient() {
let f = field("1 10 100 1000 10000 100000");
let num_grads: Vec<f32> = f
.frames
.iter()
.filter(|fr| fr.gradient.abs() > 0.01)
.map(|fr| fr.gradient)
.collect();
assert!(
!num_grads.is_empty() && num_grads.iter().all(|&g| g > 0.0),
"a magnitude ramp should show positive gradient, got {num_grads:?}"
);
}
#[test]
fn energy_is_higher_in_the_heavy_region() {
let f = field("1 2 3 4 999999999 888888888 777777777 666666666");
let light = f.energy_of(0, 7); let heavy = f.energy_of(8, 50); assert!(
heavy > light,
"the large-number region should carry more energy ({heavy}) than the small ({light})"
);
}
#[test]
fn a_length_s_magnitude_is_its_log2_whether_the_table_holds_it_or_not() {
let bytes = vec![b'x'; 2048];
for n in 0..=2048usize {
let got = token_magnitude(TokenKind::Word, &bytes[..n]);
let want = (n.max(1) as f32).log2();
assert_eq!(got.to_bits(), want.to_bits(), "length {n}");
}
}
#[test]
fn a_number_s_value_is_the_same_whichever_path_parses_it() {
let cases: [(&str, Option<f64>); 24] = [
("0", Some(0.0)),
("7", Some(7.0)),
("007", Some(7.0)),
(" 42 ", Some(42.0)),
("1000", Some(1000.0)),
("1_000_000", Some(1_000_000.0)),
("9007199254740993", Some(9_007_199_254_740_992.0)),
("18446744073709551615", Some(18_446_744_073_709_551_615.0)),
("18446744073709551616", Some(18_446_744_073_709_551_616.0)),
("99999999999999999999999", Some(1e23)),
("3.25", Some(3.25)),
("-2", Some(-2.0)),
("+7", Some(7.0)),
("1e5", Some(1e5)),
("2.5E-3", Some(2.5e-3)),
("0x1F", Some(31.0)),
("0X1f", Some(31.0)),
("0xffff_ffff", Some(4_294_967_295.0)),
("inf", Some(f64::INFINITY)),
("", None),
("abc", None),
("1..2", None),
("١٢", None),
("0x", None),
];
for (text, want) in cases {
assert_eq!(numeric_value(text).map(f64::to_bits), want.map(f64::to_bits), "{text:?}");
}
assert!(numeric_value("nan").is_some_and(f64::is_nan));
}
#[test]
fn number_value_drives_magnitude_not_length() {
let f = field("9 1000000");
assert!(
f.frames[1].magnitude > f.frames[0].magnitude + 4.0,
"1e6 should be ~6 orders, 9 ~0.95"
);
}
#[test]
fn empty_is_safe() {
let f = field("");
assert_eq!(f.n_tokens, 0);
assert!(f.frames.is_empty());
assert!(f.jumps.is_empty());
assert!(f.outliers().is_empty());
assert_eq!(f.magnitude_at(0), 0.0);
}
}