mod config_arithmetic;
mod config_types;
pub use config_arithmetic::*;
pub use config_types::*;
use proc_macro2::{Ident, Span};
use syn::{Expr, Lit, parse::Parse, parse::ParseStream};
impl Parse for TypeConfig {
fn parse(input: ParseStream) -> syn::Result<Self> {
let content;
syn::bracketed!(content in input);
let mut constraints = Vec::new();
let mut constraint_types = Vec::new();
while !content.is_empty() {
let paren_content;
syn::parenthesized!(paren_content in content);
let type_name: Ident = paren_content.parse()?;
paren_content.parse::<syn::Token![,]>()?;
let bracket_content;
syn::bracketed!(bracket_content in &paren_content);
let mut conditions = Vec::new();
while !bracket_content.is_empty() {
let expr: Expr = bracket_content.parse()?;
let condition = match &expr {
Expr::Lit(syn::ExprLit {
lit: Lit::Str(s), ..
}) => normalize_condition(&s.value()),
_ => {
return Err(syn::Error::new_spanned(
expr,
"Expected string literal for validation condition",
));
}
};
conditions.push(condition);
if !bracket_content.is_empty() {
bracket_content.parse::<syn::Token![,]>()?;
}
}
let (sign, bounds, excludes_zero) = parse_type_properties(&conditions);
constraints.push(ConstraintDef {
name: type_name.clone(),
neg_constraint_name: None, raw_conditions: conditions,
sign,
bounds,
excludes_zero,
});
let type_name_clone = type_name.clone();
constraint_types.push(TypeDef {
type_name,
float_types: vec![
Ident::new("f32", Span::call_site()),
Ident::new("f64", Span::call_site()),
],
constraint_name: type_name_clone,
});
let _ = content.parse::<syn::Token![,]>();
}
let constraints_data: Vec<_> = constraints
.iter()
.map(|c| (c.name.clone(), c.raw_conditions.clone()))
.collect();
for constraint in &mut constraints {
let negated_conditions = negate_conditions(&constraint.raw_conditions);
for (other_name, other_raw) in &constraints_data {
if conditions_match(&negated_conditions, other_raw) {
constraint.neg_constraint_name = Some(other_name.clone());
break;
}
}
}
let arithmetic_results = compute_all_arithmetic_results(&constraints);
let mut type_aliases = Vec::new();
if input.parse::<syn::Token![,]>().is_ok() {
let alias_content;
syn::bracketed!(alias_content in input);
while !alias_content.is_empty() {
let paren_content;
syn::parenthesized!(paren_content in alias_content);
let original_name: Ident = paren_content.parse()?;
paren_content.parse::<syn::Token![,]>()?;
let alias_name: Ident = paren_content.parse()?;
type_aliases.push(TypeAliasDef {
original_name,
alias_name,
});
if !alias_content.is_empty() {
alias_content.parse::<syn::Token![,]>()?;
}
}
for alias_def in &type_aliases {
let original_exists = constraints
.iter()
.any(|c| c.name == alias_def.original_name);
if !original_exists {
return Err(syn::Error::new_spanned(
&alias_def.original_name,
format!(
"Alias references non-existent type '{}'",
alias_def.original_name
),
));
}
let alias_conflicts = constraints.iter().any(|c| c.name == alias_def.alias_name);
if alias_conflicts {
return Err(syn::Error::new_spanned(
&alias_def.alias_name,
format!(
"Alias name '{}' conflicts with existing type",
alias_def.alias_name
),
));
}
}
}
Ok(TypeConfig {
constraints,
constraint_types,
arithmetic_results,
type_aliases,
})
}
}
fn normalize_condition(condition: &str) -> String {
format!("value {}", condition.trim())
}
#[expect(clippy::option_if_let_else)] fn negate_conditions(conditions: &[String]) -> Vec<String> {
conditions
.iter()
.map(|cond| {
let cond = cond.strip_prefix("value ").unwrap_or(cond);
if let Some(rest) = cond.strip_prefix(">=") {
let rest = rest.trim();
if let Some(num) = rest.strip_prefix('-') {
format!("<= {}", num)
} else {
format!("<= -{}", rest)
}
} else if let Some(rest) = cond.strip_prefix("<=") {
let rest = rest.trim();
if let Some(num) = rest.strip_prefix('-') {
format!(">= {}", num)
} else {
format!(">= -{}", rest)
}
} else if let Some(rest) = cond.strip_prefix(">") {
let rest = rest.trim();
if let Some(num) = rest.strip_prefix('-') {
format!("< {}", num)
} else {
format!("< -{}", rest)
}
} else if let Some(rest) = cond.strip_prefix("<") {
let rest = rest.trim();
if let Some(num) = rest.strip_prefix('-') {
format!("> {}", num)
} else {
format!("> -{}", rest)
}
} else if cond.starts_with("!=") {
cond.to_string()
} else {
cond.to_string()
}
})
.map(|s| normalize_condition(&s)) .collect()
}
fn normalize_float_repr(s: String) -> String {
let s = s.replace("-0.0", "0.0");
let s = s.replace("- 0.0", "0.0");
s.replace("= -0.0", "= 0.0")
}
fn conditions_match(a: &[String], b: &[String]) -> bool {
let a_normalized: Vec<String> = a.iter().map(|s| normalize_float_repr(s.clone())).collect();
let b_normalized: Vec<String> = b.iter().map(|s| normalize_float_repr(s.clone())).collect();
let mut a_sorted = a_normalized;
let mut b_sorted = b_normalized;
a_sorted.sort();
b_sorted.sort();
a_sorted == b_sorted
}
fn parse_type_properties(conditions: &[String]) -> (Sign, Bounds, bool) {
let mut lower_bound: Option<f64> = None;
let mut upper_bound: Option<f64> = None;
let mut excludes_zero = false;
let mut has_positive_constraint = false;
let mut has_negative_constraint = false;
for cond in conditions {
let cond = cond.strip_prefix("value ").unwrap_or(cond);
if let Some(rest) = cond.strip_prefix(">=") {
let val = parse_float_value(rest.trim());
lower_bound = Some(lower_bound.map_or(val, |v| v.max(val)));
if val >= 0.0 {
has_positive_constraint = true;
}
} else if let Some(rest) = cond.strip_prefix("<=") {
let val = parse_float_value(rest.trim());
upper_bound = Some(upper_bound.map_or(val, |v| v.min(val)));
if val <= 0.0 {
has_negative_constraint = true;
}
} else if let Some(rest) = cond.strip_prefix(">") {
let val = parse_float_value(rest.trim());
lower_bound = Some(lower_bound.map_or(val, |v| v.max(val)));
if val >= 0.0 {
has_positive_constraint = true;
excludes_zero = true;
}
} else if let Some(rest) = cond.strip_prefix("<") {
let val = parse_float_value(rest.trim());
upper_bound = Some(upper_bound.map_or(val, |v| v.min(val)));
if val <= 0.0 {
has_negative_constraint = true;
excludes_zero = true;
}
} else if let Some(rest) = cond.strip_prefix("!=") {
let val = parse_float_value(rest.trim());
if val == 0.0 {
excludes_zero = true;
}
}
}
let sign = if has_positive_constraint && !has_negative_constraint {
Sign::Positive
} else if has_negative_constraint && !has_positive_constraint {
Sign::Negative
} else {
Sign::Any
};
let bounds = Bounds {
lower: lower_bound,
upper: upper_bound,
};
(sign, bounds, excludes_zero)
}
fn parse_float_value(s: &str) -> f64 {
match s {
"PI" => core::f64::consts::PI,
"-PI" => -core::f64::consts::PI,
_ => s.parse().unwrap_or(0.0),
}
}
impl TypeConfig {
pub fn find_type_by_constraints(
&self,
sign: Sign,
bounds: &Bounds,
excludes_zero: bool,
) -> Option<Ident> {
self.constraints
.iter()
.find(|c| c.sign == sign && c.bounds == *bounds && c.excludes_zero == excludes_zero)
.map(|c| c.name.clone())
}
}