use crate::symbol::is_aisp_char;
use crate::tier::Tier;
use crate::validate::ValidationResult;
use crate::REQUIRED_BLOCKS;
#[derive(Debug, Clone)]
pub struct StreamState {
pub bytes_processed: usize,
pub symbol_count: u32,
pub token_count: u32,
pub definitions: u16,
pub assignments: u16,
pub quantifiers: u16,
pub lambdas: u16,
pub implications: u16,
pub set_ops: u16,
pub blocks_found: u8,
pub has_header: bool,
partial_token: String,
}
impl Default for StreamState {
fn default() -> Self {
Self::new()
}
}
impl StreamState {
pub fn new() -> Self {
Self {
bytes_processed: 0,
symbol_count: 0,
token_count: 0,
definitions: 0,
assignments: 0,
quantifiers: 0,
lambdas: 0,
implications: 0,
set_ops: 0,
blocks_found: 0,
has_header: false,
partial_token: String::new(),
}
}
pub fn total_bindings(&self) -> u32 {
(self.definitions + self.assignments + self.quantifiers
+ self.lambdas + self.implications + self.set_ops) as u32
}
pub fn block_score(&self) -> f32 {
self.blocks_found.count_ones() as f32 / 5.0
}
pub fn binding_score(&self) -> f32 {
(self.total_bindings() as f32 / 20.0).min(1.0)
}
pub fn pure_density(&self) -> f32 {
if self.token_count > 0 {
self.symbol_count as f32 / self.token_count as f32
} else {
0.0
}
}
pub fn delta(&self) -> f32 {
(self.block_score() * 0.4) + (self.binding_score() * 0.6)
}
}
pub struct StreamValidator {
state: StreamState,
max_size: usize,
}
impl Default for StreamValidator {
fn default() -> Self {
Self::new()
}
}
impl StreamValidator {
pub fn new() -> Self {
Self::with_max_size(crate::DEFAULT_MAX_SIZE)
}
pub fn with_max_size(max_size: usize) -> Self {
Self {
state: StreamState::new(),
max_size: max_size.min(crate::ABSOLUTE_MAX_SIZE),
}
}
pub fn state(&self) -> &StreamState {
&self.state
}
pub fn bytes_processed(&self) -> usize {
self.state.bytes_processed
}
pub fn is_overflow(&self) -> bool {
self.state.bytes_processed > self.max_size
}
pub fn feed(&mut self, chunk: &str) -> Result<(), &'static str> {
let new_size = self.state.bytes_processed + chunk.len();
if new_size > self.max_size {
return Err("Document exceeds maximum size");
}
self.state.bytes_processed = new_size;
self.process_chunk(chunk);
Ok(())
}
fn process_chunk(&mut self, chunk: &str) {
if !self.state.has_header && self.state.bytes_processed == chunk.len() {
self.state.has_header = chunk.trim_start().starts_with('𝔸');
}
for c in chunk.chars() {
if is_aisp_char(c) {
self.state.symbol_count += 1;
match c {
'≜' => self.state.definitions += 1,
'≔' => self.state.assignments += 1,
'∀' | '∃' => self.state.quantifiers += 1,
'λ' => self.state.lambdas += 1,
'⇒' | '⇔' | '→' | '↔' => self.state.implications += 1,
'∈' | '⊆' | '∩' | '∪' | '∅' => self.state.set_ops += 1,
_ => {}
}
}
}
let mut combined = std::mem::take(&mut self.state.partial_token);
combined.push_str(chunk);
let tokens: Vec<&str> = combined.split_whitespace().collect();
if !tokens.is_empty() {
let complete_count = if combined.ends_with(char::is_whitespace) {
tokens.len()
} else {
tokens.len().saturating_sub(1)
};
self.state.token_count += complete_count as u32;
if !combined.ends_with(char::is_whitespace) {
if let Some(last) = tokens.last() {
self.state.partial_token = (*last).to_string();
}
}
}
for (i, block) in REQUIRED_BLOCKS.iter().enumerate() {
if chunk.contains(block) {
self.state.blocks_found |= 1 << i;
}
}
}
pub fn finish(mut self) -> ValidationResult {
if !self.state.partial_token.is_empty() {
self.state.token_count += 1;
}
if !self.state.has_header {
return ValidationResult::failure("Missing AISP header (𝔸)");
}
if self.state.blocks_found.count_ones() < 5 {
return ValidationResult::failure("Missing required blocks");
}
let delta = self.state.delta();
let pure_density = self.state.pure_density();
let tier = Tier::from_delta(delta);
if tier == Tier::Reject {
return ValidationResult {
valid: false,
tier,
delta,
pure_density,
ambiguity: 0.5,
error: Some("Document density too low (δ < 0.20)"),
metrics: None,
};
}
ValidationResult::success(tier, delta, pure_density)
}
pub fn reset(&mut self) {
self.state = StreamState::new();
}
}
pub fn validate_streaming(source: &str) -> ValidationResult {
let mut validator = StreamValidator::new();
if let Err(e) = validator.feed(source) {
return ValidationResult::failure(e);
}
validator.finish()
}
pub fn validate_with_progress<F>(source: &str, mut progress: F) -> ValidationResult
where
F: FnMut(usize, usize),
{
let total = source.len();
let mut validator = StreamValidator::new();
const CHUNK_SIZE: usize = 4096;
let mut offset = 0;
while offset < source.len() {
let end = (offset + CHUNK_SIZE).min(source.len());
let chunk_end = source[offset..end]
.char_indices()
.last()
.map(|(i, c)| offset + i + c.len_utf8())
.unwrap_or(end);
let chunk = &source[offset..chunk_end];
if let Err(e) = validator.feed(chunk) {
return ValidationResult::failure(e);
}
progress(chunk_end, total);
offset = chunk_end;
}
validator.finish()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_streaming_validation() {
let mut validator = StreamValidator::new();
validator.feed("𝔸1.0.test@2026-01-16\n").unwrap();
validator.feed("γ≔example\n").unwrap();
validator.feed("⟦Ω:Meta⟧{ ∀D:Ambig(D)<0.02 }\n").unwrap();
validator.feed("⟦Σ:Types⟧{ T≜ℕ }\n").unwrap();
validator.feed("⟦Γ:Rules⟧{ ∀x:T:x≥0 }\n").unwrap();
validator.feed("⟦Λ:Funcs⟧{ f≜λx.x }\n").unwrap();
validator.feed("⟦Ε⟧⟨δ≜0.75;φ≜100;τ≜◊⁺⁺⟩").unwrap();
let result = validator.finish();
assert!(result.valid);
assert!(result.tier >= Tier::Silver);
}
#[test]
fn test_streaming_overflow() {
let mut validator = StreamValidator::with_max_size(100);
let large_chunk = "x".repeat(200);
assert!(validator.feed(&large_chunk).is_err());
}
#[test]
fn test_convenience_function() {
let doc = r#"𝔸1.0.test@2026-01-16
γ≔example
⟦Ω:Meta⟧{ ∀D:Ambig(D)<0.02 }
⟦Σ:Types⟧{ T≜ℕ }
⟦Γ:Rules⟧{ ∀x:T:x≥0 }
⟦Λ:Funcs⟧{ f≜λx.x }
⟦Ε⟧⟨δ≜0.75;φ≜100;τ≜◊⁺⁺⟩"#;
let result = validate_streaming(doc);
assert!(result.valid);
}
}