#[cfg(test)]
mod tests {
use crate::ast::{Expr, Literal};
use crate::diagnostics::{Span, Spanned};
use crate::macro_system::*;
use crate::eval::Environment;
use std::rc::Rc;
fn make_spanned<T>(value: T) -> Spanned<T> {
Spanned::new(value, Span::new(0, 1))
}
fn create_test_syntax_rules(literals: Vec<String>, rules: Vec<(Pattern, Template)>) -> SyntaxRulesTransformer {
SyntaxRulesTransformer {
literals,
rules: rules.into_iter().map(|(pattern, template)| SyntaxRule { pattern, template }).collect(),
name: Some("test-macro".to_string()),
definition_env: Rc::new(Environment::new(None, 0)),
custom_ellipsis: None,
srfi_149_mode: false, }
}
#[test]
fn test_basic_syntax_rules_parsing() {
let syntax_rules_expr = make_spanned(Expr::Application {
operator: Box::new(make_spanned(Expr::Identifier("syntax-rules".to_string()))),
operands: vec![
make_spanned(Expr::List(vec![])),
make_spanned(Expr::List(vec![
make_spanned(Expr::List(vec![
make_spanned(Expr::Identifier("test-macro".to_string())),
make_spanned(Expr::Identifier("x".to_string())),
])),
make_spanned(Expr::Identifier("x".to_string())),
])),
],
});
let env = Rc::new(Environment::new(None, 0));
let result = parse_syntax_rules(&syntax_rules_expr, env);
assert!(result.is_ok());
let transformer = result.unwrap();
assert!(transformer.literals.is_empty());
assert_eq!(transformer.rules.len(), 1);
}
#[test]
fn test_syntax_rules_with_literals() {
let syntax_rules_expr = make_spanned(Expr::Application {
operator: Box::new(make_spanned(Expr::Identifier("syntax-rules".to_string()))),
operands: vec![
make_spanned(Expr::List(vec![
make_spanned(Expr::Identifier("else".to_string())),
])),
make_spanned(Expr::List(vec![
make_spanned(Expr::List(vec![
make_spanned(Expr::Identifier("test-macro".to_string())),
make_spanned(Expr::Identifier("else".to_string())),
make_spanned(Expr::Identifier("x".to_string())),
])),
make_spanned(Expr::Identifier("x".to_string())),
])),
make_spanned(Expr::List(vec![
make_spanned(Expr::List(vec![
make_spanned(Expr::Identifier("test-macro".to_string())),
make_spanned(Expr::Identifier("y".to_string())),
make_spanned(Expr::Identifier("x".to_string())),
])),
make_spanned(Expr::Identifier("y".to_string())),
])),
],
});
let env = Rc::new(Environment::new(None, 0));
let result = parse_syntax_rules(&syntax_rules_expr, env);
assert!(result.is_ok());
let transformer = result.unwrap();
assert_eq!(transformer.literals, vec!["else"]);
assert_eq!(transformer.rules.len(), 2);
}
#[test]
fn test_ellipsis_pattern_parsing() {
let syntax_rules_expr = make_spanned(Expr::Application {
operator: Box::new(make_spanned(Expr::Identifier("syntax-rules".to_string()))),
operands: vec![
make_spanned(Expr::List(vec![])),
make_spanned(Expr::List(vec![
make_spanned(Expr::List(vec![
make_spanned(Expr::Identifier("test-macro".to_string())),
make_spanned(Expr::Identifier("x".to_string())),
make_spanned(Expr::Identifier("y".to_string())),
make_spanned(Expr::Identifier("...".to_string())),
])),
make_spanned(Expr::List(vec![
make_spanned(Expr::Identifier("list".to_string())),
make_spanned(Expr::Identifier("x".to_string())),
make_spanned(Expr::Identifier("y".to_string())),
make_spanned(Expr::Identifier("...".to_string())),
])),
])),
],
});
let env = Rc::new(Environment::new(None, 0));
let result = parse_syntax_rules(&syntax_rules_expr, env);
assert!(result.is_ok());
let transformer = result.unwrap();
assert_eq!(transformer.rules.len(), 1);
match &transformer.rules[0].pattern {
Pattern::List(_) => {}, _ => panic!("Expected list pattern"),
}
}
#[test]
fn test_multiple_rule_expansion() {
let pattern1 = Pattern::list(vec![
Pattern::identifier("test-macro"),
Pattern::identifier("special"),
Pattern::variable("x"),
]);
let template1 = Template::list(vec![
Template::identifier("special-case"),
Template::variable("x"),
]);
let pattern2 = Pattern::list(vec![
Pattern::identifier("test-macro"),
Pattern::variable("x"),
]);
let template2 = Template::list(vec![
Template::identifier("general-case"),
Template::variable("x"),
]);
let transformer = create_test_syntax_rules(
vec![],
vec![(pattern1, template1), (pattern2, template2)],
);
let input1 = make_spanned(Expr::Application {
operator: Box::new(make_spanned(Expr::Identifier("test-macro".to_string()))),
operands: vec![
make_spanned(Expr::Identifier("special".to_string())),
make_spanned(Expr::Identifier("value".to_string())),
],
});
let result1 = expand_syntax_rules(&transformer, &input1);
assert!(result1.is_ok());
let input2 = make_spanned(Expr::Application {
operator: Box::new(make_spanned(Expr::Identifier("test-macro".to_string()))),
operands: vec![
make_spanned(Expr::Identifier("value".to_string())),
],
});
let result2 = expand_syntax_rules(&transformer, &input2);
assert!(result2.is_ok());
}
#[test]
fn test_hygiene_preservation() {
let mut expander = MacroExpander::new();
let pattern = Pattern::list(vec![
Pattern::identifier("let-macro"),
Pattern::variable("x"),
Pattern::variable("body"),
]);
let template = Template::list(vec![
Template::identifier("let"),
Template::list(vec![
Template::list(vec![
Template::identifier("temp"),
Template::variable("x"),
])
]),
Template::variable("body"),
]);
let transformer = MacroTransformer {
pattern,
template,
definition_env: Rc::new(Environment::new(None, 0)),
name: Some("let-macro".to_string()),
source: None,
};
expander.define_macro("let-macro".to_string(), transformer);
let input_expr = make_spanned(Expr::Application {
operator: Box::new(make_spanned(Expr::Identifier("let-macro".to_string()))),
operands: vec![
make_spanned(Expr::Literal(Literal::Number(42.0))),
make_spanned(Expr::Identifier("temp".to_string())),
],
});
let result = expander.expand(&input_expr);
assert!(result.is_ok());
}
#[test]
fn test_builtin_macro_presence() {
let expander = MacroExpander::with_builtins();
assert!(expander.macro_env().lookup("let").is_some());
assert!(expander.macro_env().lookup("let*").is_some());
assert!(expander.macro_env().lookup("letrec").is_some());
assert!(expander.macro_env().lookup("cond").is_some());
assert!(expander.macro_env().lookup("case").is_some());
assert!(expander.macro_env().lookup("and").is_some());
assert!(expander.macro_env().lookup("or").is_some());
assert!(expander.macro_env().lookup("when").is_some());
assert!(expander.macro_env().lookup("unless").is_some());
assert!(expander.macro_env().lookup("case-lambda").is_some());
assert!(expander.macro_env().lookup("cond-expand").is_some());
assert!(expander.macro_env().lookup("assert").is_some());
}
#[test]
fn test_pattern_validation() {
let literals = vec!["else".to_string()];
let valid_pattern = Pattern::list(vec![
Pattern::identifier("macro"),
Pattern::variable("x"),
Pattern::identifier("else"),
]);
assert!(validate_pattern(&valid_pattern, &literals).is_ok());
let invalid_pattern = Pattern::variable("else");
assert!(validate_pattern(&invalid_pattern, &literals).is_err());
let duplicate_pattern = Pattern::list(vec![
Pattern::variable("x"),
Pattern::variable("x"),
]);
assert!(validate_pattern(&duplicate_pattern, &literals).is_err());
}
#[test]
fn test_template_variable_validation() {
use std::collections::HashSet;
let mut pattern_vars = HashSet::new();
pattern_vars.insert("x".to_string());
pattern_vars.insert("y".to_string());
let ellipsis_vars = HashSet::new();
let valid_template = Template::list(vec![
Template::identifier("result"),
Template::variable("x"),
Template::variable("y"),
]);
assert!(validate_template(&valid_template, &pattern_vars, &ellipsis_vars).is_ok());
let invalid_template = Template::list(vec![
Template::identifier("result"),
Template::variable("unbound"),
]);
assert!(validate_template(&invalid_template, &pattern_vars, &ellipsis_vars).is_err());
}
#[test]
fn test_macro_expansion_recursion_prevention() {
let mut expander = MacroExpander::new();
let pattern = Pattern::list(vec![
Pattern::identifier("recursive-macro"),
Pattern::variable("x"),
]);
let template = Template::list(vec![
Template::identifier("recursive-macro"),
Template::variable("x"),
]);
let transformer = MacroTransformer {
pattern,
template,
definition_env: Rc::new(Environment::new(None, 0)),
name: Some("recursive-macro".to_string()),
source: None,
};
expander.define_macro("recursive-macro".to_string(), transformer);
let input_expr = make_spanned(Expr::Application {
operator: Box::new(make_spanned(Expr::Identifier("recursive-macro".to_string()))),
operands: vec![
make_spanned(Expr::Literal(Literal::Number(42.0))),
],
});
let result = expander.expand(&input_expr);
assert!(result.is_err());
let error_msg = format!("{:?}", result.unwrap_err());
assert!(error_msg.contains("recursive") || error_msg.contains("Recursive"));
}
#[test]
fn test_nested_macro_expansion() {
let mut expander = MacroExpander::with_builtins();
let input_expr = make_spanned(Expr::Application {
operator: Box::new(make_spanned(Expr::Identifier("when".to_string()))),
operands: vec![
make_spanned(Expr::Literal(Literal::Boolean(true))),
make_spanned(Expr::Application {
operator: Box::new(make_spanned(Expr::Identifier("let".to_string()))),
operands: vec![
make_spanned(Expr::List(vec![
make_spanned(Expr::List(vec![
make_spanned(Expr::Identifier("x".to_string())),
make_spanned(Expr::Literal(Literal::Number(42.0))),
])),
])),
make_spanned(Expr::Identifier("x".to_string())),
],
}),
],
});
let result = expander.expand(&input_expr);
assert!(result.is_ok());
let expanded = result.unwrap();
assert!(is_fully_expanded(&expanded.inner));
}
fn is_fully_expanded(expr: &Expr) -> bool {
match expr {
Expr::Application { operator, operands } => {
if let Expr::Identifier(name) = &operator.inner {
let macro_names = vec!["let", "let*", "letrec", "cond", "case", "and", "or", "when", "unless"];
if macro_names.contains(&name.as_str()) {
return false;
}
}
is_fully_expanded(&operator.inner) && operands.iter().all(|op| is_fully_expanded(&op.inner))
}
Expr::Lambda { body, .. } => {
body.iter().all(|expr| is_fully_expanded(&expr.inner))
}
Expr::If { test, consequent, alternative } => {
is_fully_expanded(&test.inner)
&& is_fully_expanded(&consequent.inner)
&& alternative.as_ref().map_or(true, |alt| is_fully_expanded(&alt.inner))
}
Expr::Define { value, .. } => is_fully_expanded(&value.inner),
Expr::Set { value, .. } => is_fully_expanded(&value.inner),
Expr::Begin(exprs) => exprs.iter().all(|expr| is_fully_expanded(&expr.inner)),
_ => true, }
}
}