#![allow(clippy::panic)] use crate::frontend::ast::{Expr, ExprKind, Literal};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct DeBruijnIndex(pub usize);
#[derive(Debug, Clone, PartialEq)]
pub enum CoreExpr {
Var(DeBruijnIndex),
Lambda {
param_name: Option<String>, body: Box<CoreExpr>,
},
App(Box<CoreExpr>, Box<CoreExpr>),
Let {
name: Option<String>, value: Box<CoreExpr>,
body: Box<CoreExpr>,
},
Literal(CoreLiteral),
Prim(PrimOp, Vec<CoreExpr>),
}
#[derive(Debug, Clone, PartialEq)]
pub enum CoreLiteral {
Integer(i64),
Float(f64),
String(String),
Bool(bool),
Char(char),
Unit,
}
#[derive(Debug, Clone, PartialEq)]
pub enum PrimOp {
Add,
Sub,
Mul,
Div,
Mod,
Pow,
Eq,
Ne,
Lt,
Le,
Gt,
Ge,
And,
Or,
Not,
NullCoalesce,
Concat,
ArrayNew,
ArrayIndex,
ArrayLen,
If,
}
#[derive(Debug, Clone)]
struct DeBruijnContext {
bindings: Vec<String>,
}
impl DeBruijnContext {
fn new() -> Self {
Self {
bindings: Vec::new(),
}
}
fn push(&mut self, name: String) {
self.bindings.push(name);
}
fn pop(&mut self) {
self.bindings.pop();
}
fn lookup(&self, name: &str) -> Option<DeBruijnIndex> {
self.bindings
.iter()
.rev()
.position(|n| n == name)
.map(DeBruijnIndex)
}
}
pub struct AstNormalizer {
context: DeBruijnContext,
}
impl Default for AstNormalizer {
fn default() -> Self {
Self::new()
}
}
impl AstNormalizer {
#[must_use]
pub fn new() -> Self {
Self {
context: DeBruijnContext::new(),
}
}
pub fn normalize(&mut self, expr: &Expr) -> CoreExpr {
self.desugar_and_convert(expr)
}
#[allow(clippy::too_many_lines)] fn desugar_and_convert(&mut self, expr: &Expr) -> CoreExpr {
match &expr.kind {
ExprKind::Literal(lit) => Self::convert_literal(lit),
ExprKind::Identifier(name) => {
if let Some(idx) = self.context.lookup(name) {
CoreExpr::Var(idx)
} else {
panic!("Unbound variable: {name}");
}
}
ExprKind::Binary { left, op, right } => {
use crate::frontend::ast::BinaryOp;
let l = self.desugar_and_convert(left);
let r = self.desugar_and_convert(right);
let prim = match op {
BinaryOp::Add => PrimOp::Add,
BinaryOp::Subtract => PrimOp::Sub,
BinaryOp::Multiply => PrimOp::Mul,
BinaryOp::Divide => PrimOp::Div,
BinaryOp::Modulo => PrimOp::Mod,
BinaryOp::Power => PrimOp::Pow,
BinaryOp::Equal => PrimOp::Eq,
BinaryOp::NotEqual => PrimOp::Ne,
BinaryOp::Less => PrimOp::Lt,
BinaryOp::LessEqual => PrimOp::Le,
BinaryOp::Greater => PrimOp::Gt,
BinaryOp::GreaterEqual => PrimOp::Ge,
BinaryOp::Gt => PrimOp::Gt, BinaryOp::And => PrimOp::And,
BinaryOp::Or => PrimOp::Or,
BinaryOp::NullCoalesce => PrimOp::NullCoalesce,
BinaryOp::BitwiseAnd
| BinaryOp::BitwiseOr
| BinaryOp::BitwiseXor
| BinaryOp::LeftShift
| BinaryOp::RightShift => {
panic!("Bitwise operations not yet supported in core language")
}
BinaryOp::Send => {
panic!("Actor operations not yet supported in core language")
}
BinaryOp::In => {
panic!("Containment 'in' operator not yet supported in core language")
}
};
CoreExpr::Prim(prim, vec![l, r])
}
ExprKind::Let {
name, value, body, ..
} => {
let val = self.desugar_and_convert(value);
self.context.push(name.clone());
let bod = self.desugar_and_convert(body);
self.context.pop();
CoreExpr::Let {
name: Some(name.clone()),
value: Box::new(val),
body: Box::new(bod),
}
}
ExprKind::Lambda { params, body } => {
let mut result = self.desugar_and_convert(body);
for param in params.iter().rev() {
self.context.push(param.name());
result = CoreExpr::Lambda {
param_name: Some(param.name()),
body: Box::new(result),
};
}
for _ in params {
self.context.pop();
}
result
}
ExprKind::Function {
name, params, body, ..
} => {
for param in params {
self.context.push(param.name());
}
let body_core = self.desugar_and_convert(body);
for _ in params {
self.context.pop();
}
let mut lambda_body = body_core;
for param in params.iter().rev() {
lambda_body = CoreExpr::Lambda {
param_name: Some(param.name()),
body: Box::new(lambda_body),
};
}
CoreExpr::Let {
name: Some(name.clone()),
value: Box::new(lambda_body),
body: Box::new(CoreExpr::Literal(CoreLiteral::Unit)), }
}
ExprKind::Call { func, args } => {
let mut result = self.desugar_and_convert(func);
for arg in args {
let arg_core = self.desugar_and_convert(arg);
result = CoreExpr::App(Box::new(result), Box::new(arg_core));
}
result
}
ExprKind::If {
condition,
then_branch,
else_branch,
} => {
let cond = self.desugar_and_convert(condition);
let then_b = self.desugar_and_convert(then_branch);
let else_b = else_branch
.as_ref()
.map_or(CoreExpr::Literal(CoreLiteral::Unit), |e| {
self.desugar_and_convert(e)
});
CoreExpr::Prim(PrimOp::If, vec![cond, then_b, else_b])
}
ExprKind::List(elements) => {
let mut result = CoreExpr::Prim(PrimOp::ArrayNew, vec![]);
for elem in elements {
let elem_core = self.desugar_and_convert(elem);
result = CoreExpr::Prim(PrimOp::ArrayNew, vec![result, elem_core]);
}
result
}
ExprKind::Block(exprs) => {
if exprs.is_empty() {
CoreExpr::Literal(CoreLiteral::Unit)
} else if exprs.len() == 1 {
self.desugar_and_convert(&exprs[0])
} else {
if let Some(last) = exprs.last() {
self.desugar_and_convert(last)
} else {
CoreExpr::Literal(CoreLiteral::Unit)
}
}
}
_ => {
panic!("Unsupported expression kind in normalizer: {:?}", expr.kind);
}
}
}
fn convert_literal(lit: &Literal) -> CoreExpr {
CoreExpr::Literal(match lit {
Literal::Integer(i, _) => CoreLiteral::Integer(*i),
Literal::Float(f) => CoreLiteral::Float(*f),
Literal::String(s) => CoreLiteral::String(s.clone()),
Literal::Bool(b) => CoreLiteral::Bool(*b),
Literal::Char(c) => CoreLiteral::Char(*c),
Literal::Byte(b) => CoreLiteral::Integer(i64::from(*b)), Literal::Unit => CoreLiteral::Unit,
Literal::Null => CoreLiteral::Unit,
Literal::Atom(_) => CoreLiteral::Unit, })
}
}
impl CoreExpr {
#[must_use]
pub fn is_normalized(&self) -> bool {
match self {
CoreExpr::Var(_) | CoreExpr::Literal(_) => true,
CoreExpr::Lambda { body, .. } => body.is_normalized(),
CoreExpr::App(f, x) => f.is_normalized() && x.is_normalized(),
CoreExpr::Let { value, body, .. } => value.is_normalized() && body.is_normalized(),
CoreExpr::Prim(_, args) => args.iter().all(CoreExpr::is_normalized),
}
}
#[must_use]
pub fn is_closed(&self) -> bool {
self.is_closed_at(0)
}
fn is_closed_at(&self, depth: usize) -> bool {
match self {
CoreExpr::Var(DeBruijnIndex(idx)) => *idx < depth,
CoreExpr::Lambda { body, .. } => body.is_closed_at(depth + 1),
CoreExpr::App(f, x) => f.is_closed_at(depth) && x.is_closed_at(depth),
CoreExpr::Let { value, body, .. } => {
value.is_closed_at(depth) && body.is_closed_at(depth + 1)
}
CoreExpr::Literal(_) => true,
CoreExpr::Prim(_, args) => args.iter().all(|a| a.is_closed_at(depth)),
}
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
use super::*;
use crate::Parser;
#[test]
fn test_debruijn_index_new() {
let idx = DeBruijnIndex(0);
assert_eq!(idx.0, 0);
}
#[test]
fn test_debruijn_index_debug() {
let idx = DeBruijnIndex(5);
let debug = format!("{:?}", idx);
assert!(debug.contains("DeBruijnIndex"));
}
#[test]
fn test_debruijn_index_clone() {
let idx = DeBruijnIndex(3);
let cloned = idx.clone();
assert_eq!(idx, cloned);
}
#[test]
fn test_debruijn_index_eq() {
assert_eq!(DeBruijnIndex(0), DeBruijnIndex(0));
assert_ne!(DeBruijnIndex(0), DeBruijnIndex(1));
}
#[test]
fn test_debruijn_index_hash() {
use std::collections::HashSet;
let mut set = HashSet::new();
set.insert(DeBruijnIndex(0));
set.insert(DeBruijnIndex(1));
assert_eq!(set.len(), 2);
}
#[test]
fn test_core_literal_integer() {
let lit = CoreLiteral::Integer(42);
assert!(matches!(lit, CoreLiteral::Integer(42)));
}
#[test]
fn test_core_literal_float() {
let lit = CoreLiteral::Float(3.14);
assert!(matches!(lit, CoreLiteral::Float(f) if (f - 3.14).abs() < f64::EPSILON));
}
#[test]
fn test_core_literal_string() {
let lit = CoreLiteral::String("hello".to_string());
assert!(matches!(lit, CoreLiteral::String(s) if s == "hello"));
}
#[test]
fn test_core_literal_bool() {
let lit_true = CoreLiteral::Bool(true);
let lit_false = CoreLiteral::Bool(false);
assert!(matches!(lit_true, CoreLiteral::Bool(true)));
assert!(matches!(lit_false, CoreLiteral::Bool(false)));
}
#[test]
fn test_core_literal_char() {
let lit = CoreLiteral::Char('x');
assert!(matches!(lit, CoreLiteral::Char('x')));
}
#[test]
fn test_core_literal_unit() {
let lit = CoreLiteral::Unit;
assert!(matches!(lit, CoreLiteral::Unit));
}
#[test]
fn test_core_literal_clone() {
let lit = CoreLiteral::Integer(42);
let cloned = lit.clone();
assert_eq!(lit, cloned);
}
#[test]
fn test_core_literal_debug() {
let lit = CoreLiteral::Integer(42);
let debug = format!("{:?}", lit);
assert!(debug.contains("Integer"));
}
#[test]
fn test_primop_clone() {
let op = PrimOp::Add;
let cloned = op.clone();
assert_eq!(op, cloned);
}
#[test]
fn test_primop_debug() {
let op = PrimOp::Mul;
let debug = format!("{:?}", op);
assert!(debug.contains("Mul"));
}
#[test]
fn test_primop_eq() {
assert_eq!(PrimOp::Add, PrimOp::Add);
assert_ne!(PrimOp::Add, PrimOp::Sub);
}
#[test]
fn test_primop_all_variants() {
let _add = PrimOp::Add;
let _sub = PrimOp::Sub;
let _mul = PrimOp::Mul;
let _div = PrimOp::Div;
let _mod_ = PrimOp::Mod;
let _pow = PrimOp::Pow;
let _eq = PrimOp::Eq;
let _ne = PrimOp::Ne;
let _lt = PrimOp::Lt;
let _le = PrimOp::Le;
let _gt = PrimOp::Gt;
let _ge = PrimOp::Ge;
let _and = PrimOp::And;
let _or = PrimOp::Or;
let _not = PrimOp::Not;
let _null_coalesce = PrimOp::NullCoalesce;
let _concat = PrimOp::Concat;
let _array_new = PrimOp::ArrayNew;
let _array_index = PrimOp::ArrayIndex;
let _array_len = PrimOp::ArrayLen;
let _if = PrimOp::If;
}
#[test]
fn test_core_expr_var() {
let expr = CoreExpr::Var(DeBruijnIndex(0));
assert!(matches!(expr, CoreExpr::Var(_)));
}
#[test]
fn test_core_expr_lambda() {
let expr = CoreExpr::Lambda {
param_name: Some("x".to_string()),
body: Box::new(CoreExpr::Literal(CoreLiteral::Unit)),
};
assert!(matches!(expr, CoreExpr::Lambda { .. }));
}
#[test]
fn test_core_expr_app() {
let expr = CoreExpr::App(
Box::new(CoreExpr::Literal(CoreLiteral::Unit)),
Box::new(CoreExpr::Literal(CoreLiteral::Unit)),
);
assert!(matches!(expr, CoreExpr::App(_, _)));
}
#[test]
fn test_core_expr_let() {
let expr = CoreExpr::Let {
name: Some("x".to_string()),
value: Box::new(CoreExpr::Literal(CoreLiteral::Integer(42))),
body: Box::new(CoreExpr::Literal(CoreLiteral::Unit)),
};
assert!(matches!(expr, CoreExpr::Let { .. }));
}
#[test]
fn test_core_expr_literal() {
let expr = CoreExpr::Literal(CoreLiteral::Integer(42));
assert!(matches!(expr, CoreExpr::Literal(_)));
}
#[test]
fn test_core_expr_prim() {
let expr = CoreExpr::Prim(PrimOp::Add, vec![]);
assert!(matches!(expr, CoreExpr::Prim(_, _)));
}
#[test]
fn test_core_expr_clone() {
let expr = CoreExpr::Literal(CoreLiteral::Integer(42));
let cloned = expr.clone();
assert_eq!(expr, cloned);
}
#[test]
fn test_core_expr_debug() {
let expr = CoreExpr::Literal(CoreLiteral::Integer(42));
let debug = format!("{:?}", expr);
assert!(debug.contains("Literal"));
}
#[test]
fn test_is_normalized_var() {
let expr = CoreExpr::Var(DeBruijnIndex(0));
assert!(expr.is_normalized());
}
#[test]
fn test_is_normalized_literal() {
let expr = CoreExpr::Literal(CoreLiteral::Integer(42));
assert!(expr.is_normalized());
}
#[test]
fn test_is_normalized_lambda() {
let expr = CoreExpr::Lambda {
param_name: Some("x".to_string()),
body: Box::new(CoreExpr::Literal(CoreLiteral::Unit)),
};
assert!(expr.is_normalized());
}
#[test]
fn test_is_normalized_app() {
let expr = CoreExpr::App(
Box::new(CoreExpr::Literal(CoreLiteral::Unit)),
Box::new(CoreExpr::Literal(CoreLiteral::Unit)),
);
assert!(expr.is_normalized());
}
#[test]
fn test_is_normalized_let() {
let expr = CoreExpr::Let {
name: Some("x".to_string()),
value: Box::new(CoreExpr::Literal(CoreLiteral::Integer(42))),
body: Box::new(CoreExpr::Literal(CoreLiteral::Unit)),
};
assert!(expr.is_normalized());
}
#[test]
fn test_is_normalized_prim() {
let expr = CoreExpr::Prim(
PrimOp::Add,
vec![
CoreExpr::Literal(CoreLiteral::Integer(1)),
CoreExpr::Literal(CoreLiteral::Integer(2)),
],
);
assert!(expr.is_normalized());
}
#[test]
fn test_is_closed_literal() {
let expr = CoreExpr::Literal(CoreLiteral::Integer(42));
assert!(expr.is_closed());
}
#[test]
fn test_is_closed_free_var() {
let expr = CoreExpr::Var(DeBruijnIndex(0));
assert!(!expr.is_closed());
}
#[test]
fn test_is_closed_lambda_bound() {
let expr = CoreExpr::Lambda {
param_name: Some("x".to_string()),
body: Box::new(CoreExpr::Var(DeBruijnIndex(0))),
};
assert!(expr.is_closed());
}
#[test]
fn test_is_closed_lambda_free() {
let expr = CoreExpr::Lambda {
param_name: Some("x".to_string()),
body: Box::new(CoreExpr::Var(DeBruijnIndex(1))),
};
assert!(!expr.is_closed());
}
#[test]
fn test_is_closed_let_bound() {
let expr = CoreExpr::Let {
name: Some("x".to_string()),
value: Box::new(CoreExpr::Literal(CoreLiteral::Integer(42))),
body: Box::new(CoreExpr::Var(DeBruijnIndex(0))),
};
assert!(expr.is_closed());
}
#[test]
fn test_is_closed_let_free_in_value() {
let expr = CoreExpr::Let {
name: Some("x".to_string()),
value: Box::new(CoreExpr::Var(DeBruijnIndex(0))),
body: Box::new(CoreExpr::Literal(CoreLiteral::Integer(42))),
};
assert!(!expr.is_closed());
}
#[test]
fn test_is_closed_app() {
let expr = CoreExpr::App(
Box::new(CoreExpr::Literal(CoreLiteral::Unit)),
Box::new(CoreExpr::Literal(CoreLiteral::Unit)),
);
assert!(expr.is_closed());
}
#[test]
fn test_is_closed_app_free() {
let expr = CoreExpr::App(
Box::new(CoreExpr::Var(DeBruijnIndex(0))),
Box::new(CoreExpr::Literal(CoreLiteral::Unit)),
);
assert!(!expr.is_closed());
}
#[test]
fn test_is_closed_prim() {
let expr = CoreExpr::Prim(
PrimOp::Add,
vec![
CoreExpr::Literal(CoreLiteral::Integer(1)),
CoreExpr::Literal(CoreLiteral::Integer(2)),
],
);
assert!(expr.is_closed());
}
#[test]
fn test_is_closed_prim_free() {
let expr = CoreExpr::Prim(
PrimOp::Add,
vec![
CoreExpr::Var(DeBruijnIndex(0)),
CoreExpr::Literal(CoreLiteral::Integer(2)),
],
);
assert!(!expr.is_closed());
}
#[test]
fn test_normalizer_new() {
let normalizer = AstNormalizer::new();
drop(normalizer);
}
#[test]
fn test_normalizer_default() {
let normalizer = AstNormalizer::default();
drop(normalizer);
}
#[test]
fn test_normalize_integer_literal() {
let input = "42";
let mut parser = Parser::new(input);
let ast = parser.parse().expect("Failed to parse");
let mut normalizer = AstNormalizer::new();
let core = normalizer.normalize(&ast);
assert!(matches!(core, CoreExpr::Literal(CoreLiteral::Integer(42))));
}
#[test]
fn test_normalize_float_literal() {
let input = "3.14";
let mut parser = Parser::new(input);
let ast = parser.parse().expect("Failed to parse");
let mut normalizer = AstNormalizer::new();
let core = normalizer.normalize(&ast);
assert!(matches!(core, CoreExpr::Literal(CoreLiteral::Float(_))));
}
#[test]
fn test_normalize_string_literal() {
let input = r#""hello""#;
let mut parser = Parser::new(input);
let ast = parser.parse().expect("Failed to parse");
let mut normalizer = AstNormalizer::new();
let core = normalizer.normalize(&ast);
assert!(matches!(core, CoreExpr::Literal(CoreLiteral::String(_))));
}
#[test]
fn test_normalize_bool_literal() {
let input = "true";
let mut parser = Parser::new(input);
let ast = parser.parse().expect("Failed to parse");
let mut normalizer = AstNormalizer::new();
let core = normalizer.normalize(&ast);
assert!(matches!(core, CoreExpr::Literal(CoreLiteral::Bool(true))));
}
#[test]
fn test_normalize_unit_literal() {
let input = "()";
let mut parser = Parser::new(input);
let ast = parser.parse().expect("Failed to parse");
let mut normalizer = AstNormalizer::new();
let core = normalizer.normalize(&ast);
assert!(matches!(core, CoreExpr::Literal(CoreLiteral::Unit)));
}
#[test]
fn test_normalize_binary_add() {
let input = "1 + 2";
let mut parser = Parser::new(input);
let ast = parser.parse().expect("Failed to parse");
let mut normalizer = AstNormalizer::new();
let core = normalizer.normalize(&ast);
assert!(matches!(core, CoreExpr::Prim(PrimOp::Add, _)));
}
#[test]
fn test_normalize_binary_sub() {
let input = "5 - 3";
let mut parser = Parser::new(input);
let ast = parser.parse().expect("Failed to parse");
let mut normalizer = AstNormalizer::new();
let core = normalizer.normalize(&ast);
assert!(matches!(core, CoreExpr::Prim(PrimOp::Sub, _)));
}
#[test]
fn test_normalize_binary_mul() {
let input = "2 * 3";
let mut parser = Parser::new(input);
let ast = parser.parse().expect("Failed to parse");
let mut normalizer = AstNormalizer::new();
let core = normalizer.normalize(&ast);
assert!(matches!(core, CoreExpr::Prim(PrimOp::Mul, _)));
}
#[test]
fn test_normalize_binary_div() {
let input = "10 / 2";
let mut parser = Parser::new(input);
let ast = parser.parse().expect("Failed to parse");
let mut normalizer = AstNormalizer::new();
let core = normalizer.normalize(&ast);
assert!(matches!(core, CoreExpr::Prim(PrimOp::Div, _)));
}
#[test]
fn test_normalize_binary_mod() {
let input = "10 % 3";
let mut parser = Parser::new(input);
let ast = parser.parse().expect("Failed to parse");
let mut normalizer = AstNormalizer::new();
let core = normalizer.normalize(&ast);
assert!(matches!(core, CoreExpr::Prim(PrimOp::Mod, _)));
}
#[test]
fn test_normalize_binary_comparison() {
for (input, expected_op) in [
("1 == 2", PrimOp::Eq),
("1 != 2", PrimOp::Ne),
("1 < 2", PrimOp::Lt),
("1 <= 2", PrimOp::Le),
("1 > 2", PrimOp::Gt),
("1 >= 2", PrimOp::Ge),
] {
let mut parser = Parser::new(input);
let ast = parser.parse().expect("Failed to parse");
let mut normalizer = AstNormalizer::new();
let core = normalizer.normalize(&ast);
match core {
CoreExpr::Prim(op, _) => assert_eq!(op, expected_op),
_ => panic!("Expected Prim expression"),
}
}
}
#[test]
fn test_normalize_binary_logical() {
let input_and = "true && false";
let mut parser = Parser::new(input_and);
let ast = parser.parse().expect("Failed to parse");
let mut normalizer = AstNormalizer::new();
let core = normalizer.normalize(&ast);
assert!(matches!(core, CoreExpr::Prim(PrimOp::And, _)));
let input_or = "true || false";
let mut parser = Parser::new(input_or);
let ast = parser.parse().expect("Failed to parse");
let mut normalizer = AstNormalizer::new();
let core = normalizer.normalize(&ast);
assert!(matches!(core, CoreExpr::Prim(PrimOp::Or, _)));
}
#[test]
fn test_normalize_if_expression() {
let input = "if true { 1 } else { 2 }";
let mut parser = Parser::new(input);
let ast = parser.parse().expect("Failed to parse");
let mut normalizer = AstNormalizer::new();
let core = normalizer.normalize(&ast);
assert!(matches!(core, CoreExpr::Prim(PrimOp::If, _)));
}
#[test]
fn test_normalize_if_without_else() {
let input = "if true { 1 }";
let mut parser = Parser::new(input);
let ast = parser.parse().expect("Failed to parse");
let mut normalizer = AstNormalizer::new();
let core = normalizer.normalize(&ast);
assert!(matches!(core, CoreExpr::Prim(PrimOp::If, _)));
}
#[test]
fn test_normalize_let_statement() {
let input = "let x = 10 in x + 1";
let mut parser = Parser::new(input);
let ast = parser.parse().expect("Failed to parse");
let mut normalizer = AstNormalizer::new();
let core = normalizer.normalize(&ast);
assert!(matches!(core, CoreExpr::Let { .. }));
assert!(core.is_normalized());
}
#[test]
fn test_normalize_lambda() {
let input = "fun add(x, y) { x + y }";
let mut parser = Parser::new(input);
let ast = parser.parse().expect("Failed to parse");
let mut normalizer = AstNormalizer::new();
let core = normalizer.normalize(&ast);
assert!(matches!(core, CoreExpr::Let { .. }));
assert!(core.is_normalized());
}
#[test]
fn test_idempotent_normalization() {
let inputs = vec!["42", "let x = 10 in x + 1", "fun f(x) { x * 2 }"];
for input in inputs {
let mut parser = Parser::new(input);
if let Ok(ast) = parser.parse() {
let mut normalizer1 = AstNormalizer::new();
let core1 = normalizer1.normalize(&ast);
let mut normalizer2 = AstNormalizer::new();
let core2 = normalizer2.normalize(&ast);
assert_eq!(core1, core2, "Normalization should be deterministic");
}
}
}
#[test]
fn test_normalize_block_empty() {
let input = "()";
let mut parser = Parser::new(input);
let ast = parser.parse().expect("Failed to parse");
let mut normalizer = AstNormalizer::new();
let core = normalizer.normalize(&ast);
assert!(matches!(core, CoreExpr::Literal(CoreLiteral::Unit)));
}
#[test]
fn test_normalize_block_single() {
let input = "{ 42 }";
let mut parser = Parser::new(input);
let ast = parser.parse().expect("Failed to parse");
let mut normalizer = AstNormalizer::new();
let core = normalizer.normalize(&ast);
assert!(matches!(core, CoreExpr::Literal(CoreLiteral::Integer(42))));
}
#[test]
fn test_normalize_block_multiple() {
let input = "{ 1; 2; 3 }";
let mut parser = Parser::new(input);
let ast = parser.parse().expect("Failed to parse");
let mut normalizer = AstNormalizer::new();
let core = normalizer.normalize(&ast);
assert!(matches!(core, CoreExpr::Literal(CoreLiteral::Integer(3))));
}
#[test]
fn test_normalize_complex_expression() {
let input = "1 + 2 * 3";
let mut parser = Parser::new(input);
let ast = parser.parse().expect("Failed to parse");
let mut normalizer = AstNormalizer::new();
let core = normalizer.normalize(&ast);
assert!(matches!(core, CoreExpr::Prim(PrimOp::Add, _)));
assert!(core.is_normalized());
assert!(core.is_closed());
}
}
#[cfg(test)]
mod property_tests_canonical_ast {
use proptest::proptest;
proptest! {
#[test]
fn test_new_never_panics(input: String) {
let _input = if input.len() > 100 { &input[..100] } else { &input[..] };
let _ = std::panic::catch_unwind(|| {
});
}
}
}