use crate::symbol::{count_symbols, count_tokens};
use crate::tier::Tier;
use crate::REQUIRED_BLOCKS;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct DensityMetrics {
pub blocks_found: u8,
pub definitions: u16,
pub assignments: u16,
pub quantifiers: u16,
pub lambdas: u16,
pub implications: u16,
pub set_ops: u16,
pub symbol_count: u16,
pub token_count: u16,
}
impl DensityMetrics {
pub fn from_source(source: &str) -> Self {
let blocks_found = REQUIRED_BLOCKS
.iter()
.filter(|block| source.contains(*block))
.count() as u8;
let definitions = source.matches('≜').count() as u16;
let assignments = source.matches('≔').count() as u16;
let quantifiers = source.matches(|c| c == '∀' || c == '∃').count() as u16;
let lambdas = source.matches('λ').count() as u16;
let implications = source.matches(|c| matches!(c, '⇒' | '⇔' | '→' | '↔')).count() as u16;
let set_ops = source.matches(|c| matches!(c, '∈' | '⊆' | '∩' | '∪' | '∅')).count() as u16;
Self {
blocks_found,
definitions,
assignments,
quantifiers,
lambdas,
implications,
set_ops,
symbol_count: count_symbols(source) as u16,
token_count: count_tokens(source) as u16,
}
}
pub fn total_bindings(&self) -> u16 {
self.definitions + self.assignments + self.quantifiers
+ self.lambdas + self.implications + self.set_ops
}
pub fn block_score(&self) -> f32 {
self.blocks_found 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
}
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ValidationResult {
pub valid: bool,
pub tier: Tier,
pub delta: f32,
pub pure_density: f32,
pub ambiguity: f32,
pub error: Option<&'static str>,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub metrics: Option<DensityMetrics>,
}
impl ValidationResult {
pub fn success(tier: Tier, delta: f32, pure_density: f32) -> Self {
Self {
valid: true,
tier,
delta,
pure_density,
ambiguity: 0.01, error: None,
metrics: None,
}
}
pub fn success_with_metrics(tier: Tier, delta: f32, pure_density: f32, metrics: DensityMetrics) -> Self {
Self {
valid: true,
tier,
delta,
pure_density,
ambiguity: 0.01,
error: None,
metrics: Some(metrics),
}
}
pub fn failure(error: &'static str) -> Self {
Self {
valid: false,
tier: Tier::Reject,
delta: 0.0,
pure_density: 0.0,
ambiguity: 0.5,
error: Some(error),
metrics: None,
}
}
}
pub fn validate(source: &str) -> ValidationResult {
let trimmed = source.trim();
if !trimmed.starts_with('𝔸') {
return ValidationResult::failure("Missing AISP header (𝔸)");
}
let metrics = DensityMetrics::from_source(source);
if metrics.blocks_found < 5 {
return ValidationResult::failure("Missing required blocks");
}
let delta = (metrics.block_score() * 0.4) + (metrics.binding_score() * 0.6);
let pure_density = metrics.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: Some(metrics),
};
}
ValidationResult::success_with_metrics(tier, delta, pure_density, metrics)
}
pub fn is_valid(source: &str) -> bool {
validate(source).valid
}
pub fn get_tier(source: &str) -> Tier {
validate(source).tier
}
pub fn get_density(source: &str) -> f32 {
validate(source).delta
}
#[cfg(test)]
mod tests {
use super::*;
const VALID_DOC: &str = 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;τ≜◊⁺⁺⟩"#;
#[test]
fn test_valid_document() {
let result = validate(VALID_DOC);
assert!(result.valid);
assert!(result.tier >= Tier::Silver);
}
#[test]
fn test_missing_header() {
let result = validate("no header here");
assert!(!result.valid);
assert_eq!(result.error, Some("Missing AISP header (𝔸)"));
}
#[test]
fn test_missing_blocks() {
let result = validate("𝔸1.0.test@2026-01-16\nsome content");
assert!(!result.valid);
assert_eq!(result.error, Some("Missing required blocks"));
}
#[test]
fn test_density_metrics() {
let metrics = DensityMetrics::from_source(VALID_DOC);
assert_eq!(metrics.blocks_found, 5);
assert!(metrics.definitions >= 3);
assert!(metrics.quantifiers >= 2);
assert!(metrics.lambdas >= 1);
}
}