use super::super::*;
use crate::ast::{Expr, Literal};
use crate::diagnostics::{Span, Spanned};
use crate::eval::Environment;
use std::collections::HashMap;
use std::rc::Rc;
fn spanned_expr(expr: Expr) -> Spanned<Expr> {
Spanned::new(expr, Span::new(0, 1))
}
fn spanned_id(name: &str) -> Spanned<Expr> {
spanned_expr(Expr::Identifier(name.to_string()))
}
fn spanned_lit(value: f64) -> Spanned<Expr> {
spanned_expr(Expr::Literal(Literal::Number(value)))
}
fn spanned_list(elements: Vec<Spanned<Expr>>) -> Spanned<Expr> {
spanned_expr(Expr::List(elements))
}
#[test]
fn test_multiple_consecutive_ellipses_parsing() {
let inner_template = spanned_list(vec![
spanned_id("a"),
spanned_id("b"),
spanned_id("..."),
]);
let outer_template = spanned_list(vec![
inner_template,
spanned_id("..."),
]);
let template = parse_template(&outer_template, "...").unwrap();
match template {
Template::NestedEllipsis { depth, .. } => {
assert_eq!(depth, 2);
}
_ => panic!("Expected NestedEllipsis template, got {:?}", template),
}
}
#[test]
fn test_triple_consecutive_ellipses() {
let innermost = spanned_list(vec![
spanned_id("a"),
spanned_id("b"),
spanned_id("..."),
]);
let middle = spanned_list(vec![
innermost,
spanned_id("..."),
]);
let outermost = spanned_list(vec![
middle,
spanned_id("..."),
]);
let template = parse_template(&outermost, "...").unwrap();
match template {
Template::NestedEllipsis { depth, .. } => {
assert_eq!(depth, 3);
}
_ => panic!("Expected NestedEllipsis template with depth 3, got {:?}", template),
}
}
#[test]
fn test_extra_ellipses_detection() {
let pattern = Pattern::List(vec![
Pattern::Variable("a".to_string()),
Pattern::Variable("b".to_string()),
Pattern::Variable("c".to_string()),
]);
let template = Template::Ellipsis {
templates: vec![],
ellipsis_template: Box::new(Template::List(vec![
Template::Variable("a".to_string()),
Template::Variable("b".to_string()),
Template::Variable("c".to_string()),
])),
rest: None,
};
assert_eq!(pattern.ellipsis_depth(), 0);
assert_eq!(template.ellipsis_depth(), 1);
assert!(template.needs_extra_ellipses(pattern.ellipsis_depth()));
}
#[test]
fn test_ambiguity_resolution() {
let inner_pattern = Pattern::List(vec![
Pattern::Variable("x".to_string()),
Pattern::Variable("y".to_string()),
]);
let pattern = Pattern::Ellipsis {
patterns: vec![],
ellipsis_pattern: Box::new(Pattern::Variable("x".to_string())),
rest: Some(Box::new(Pattern::Ellipsis {
patterns: vec![],
ellipsis_pattern: Box::new(inner_pattern),
rest: None,
})),
};
let var_depths = pattern.variable_depths();
assert_eq!(var_depths.get("x"), Some(&1));
assert_eq!(var_depths.get("y"), Some(&2));
}
#[test]
fn test_srfi_149_mode_flag() {
let env = Rc::new(Environment::new(None, 0));
let mut transformer_enabled = SyntaxRulesTransformer {
literals: vec![],
rules: vec![],
name: None,
definition_env: env.clone(),
custom_ellipsis: None,
srfi_149_mode: true,
};
let mut transformer_disabled = SyntaxRulesTransformer {
literals: vec![],
rules: vec![],
name: None,
definition_env: env,
custom_ellipsis: None,
srfi_149_mode: false,
};
assert!(transformer_enabled.is_srfi_149_enabled());
assert!(!transformer_disabled.is_srfi_149_enabled());
transformer_enabled = transformer_enabled.with_srfi_149_mode(false);
transformer_disabled = transformer_disabled.with_srfi_149_mode(true);
assert!(!transformer_enabled.is_srfi_149_enabled());
assert!(transformer_disabled.is_srfi_149_enabled());
}
#[test]
fn test_ellipsis_depth_calculation() {
let single = Template::Ellipsis {
templates: vec![],
ellipsis_template: Box::new(Template::Variable("a".to_string())),
rest: None,
};
assert_eq!(single.ellipsis_depth(), 1);
let nested = Template::Ellipsis {
templates: vec![],
ellipsis_template: Box::new(single),
rest: None,
};
assert_eq!(nested.ellipsis_depth(), 2);
let srfi149_nested = Template::NestedEllipsis {
templates: vec![],
nested_template: Box::new(Template::Variable("a".to_string())),
depth: 3,
rest: None,
};
assert_eq!(srfi149_nested.ellipsis_depth(), 3);
let extra = Template::ExtraEllipsis {
base_template: Box::new(Template::Variable("a".to_string())),
extra_depth: 2,
};
assert_eq!(extra.ellipsis_depth(), 2);
}
#[test]
fn test_pattern_matching_with_ellipsis() {
let pattern = Pattern::Ellipsis {
patterns: vec![Pattern::Variable("first".to_string())],
ellipsis_pattern: Box::new(Pattern::Variable("rest".to_string())),
rest: None,
};
let input = spanned_list(vec![
spanned_id("a"),
spanned_id("b"),
spanned_id("c"),
spanned_id("d"),
]);
let bindings = pattern.match_expr(&input).unwrap();
assert!(bindings.get("first").is_some());
assert!(bindings.get_ellipsis("rest").is_some());
let rest_bindings = bindings.get_ellipsis("rest").unwrap();
assert_eq!(rest_bindings.len(), 3);
}
#[test]
fn test_template_expansion_basic() {
let mut bindings = PatternBindings::new();
bindings.bind("x".to_string(), spanned_id("hello"));
bindings.bind_ellipsis("ys".to_string(), vec![
spanned_id("world"),
spanned_lit(42.0),
]);
let template = Template::List(vec![
Template::Variable("x".to_string()),
Template::Ellipsis {
templates: vec![],
ellipsis_template: Box::new(Template::Variable("ys".to_string())),
rest: None,
},
]);
let result = template.expand(&bindings, Span::new(0, 1)).unwrap();
match result.inner {
Expr::List(elements) => {
assert_eq!(elements.len(), 3);
}
_ => panic!("Expected expanded list, got {:?}", result.inner),
}
}
#[test]
fn test_error_handling_excessive_depth() {
let template = Template::NestedEllipsis {
templates: vec![],
nested_template: Box::new(Template::Variable("x".to_string())),
depth: 15, rest: None,
};
let bindings = PatternBindings::new();
let result = template.expand(&bindings, Span::new(0, 1));
assert!(result.is_err());
let error_message = format!("{}", result.unwrap_err());
assert!(error_message.contains("exceeds maximum"));
}
#[test]
fn test_error_handling_zero_depth() {
let template = Template::NestedEllipsis {
templates: vec![],
nested_template: Box::new(Template::Variable("x".to_string())),
depth: 0, rest: None,
};
let bindings = PatternBindings::new();
let result = template.expand(&bindings, Span::new(0, 1));
assert!(result.is_err());
let error_message = format!("{}", result.unwrap_err());
assert!(error_message.contains("cannot be zero"));
}
#[test]
fn test_error_handling_unbound_variables() {
let template = Template::NestedEllipsis {
templates: vec![],
nested_template: Box::new(Template::Variable("unbound".to_string())),
depth: 2,
rest: None,
};
let bindings = PatternBindings::new(); let result = template.expand(&bindings, Span::new(0, 1));
assert!(result.is_err());
let error_message = format!("{}", result.unwrap_err());
assert!(error_message.contains("no ellipsis bindings found"));
}
#[test]
fn test_integration_with_srfi_46() {
let template_expr = spanned_list(vec![
spanned_id("a"),
spanned_id(":::"),
spanned_id(":::"), ]);
let template = parse_template(&template_expr, ":::").unwrap();
match template {
Template::NestedEllipsis { depth, .. } => {
assert_eq!(depth, 2);
}
_ => panic!("Expected NestedEllipsis with custom ellipsis"),
}
}
#[test]
fn test_r7rs_backward_compatibility() {
let standard_template = spanned_list(vec![
spanned_id("a"),
spanned_id("..."),
]);
let template = parse_template(&standard_template, "...").unwrap();
match template {
Template::Ellipsis { .. } => {
}
_ => panic!("Expected standard Ellipsis template for R7RS compatibility"),
}
}
#[test]
fn test_complex_nested_expansion() {
let mut bindings = PatternBindings::new();
bindings.bind_ellipsis("items".to_string(), vec![
spanned_list(vec![spanned_id("a"), spanned_lit(1.0)]),
spanned_list(vec![spanned_id("b"), spanned_lit(2.0)]),
spanned_list(vec![spanned_id("c"), spanned_lit(3.0)]),
]);
let template = Template::NestedEllipsis {
templates: vec![],
nested_template: Box::new(Template::Variable("items".to_string())),
depth: 2,
rest: None,
};
let result = template.expand(&bindings, Span::new(0, 1));
assert!(result.is_ok());
}
#[test]
fn test_performance_characteristics() {
let large_binding: Vec<Spanned<Expr>> = (0..1000)
.map(|i| spanned_lit(i as f64))
.collect();
let mut bindings = PatternBindings::new();
bindings.bind_ellipsis("large".to_string(), large_binding);
let template = Template::Ellipsis {
templates: vec![],
ellipsis_template: Box::new(Template::Variable("large".to_string())),
rest: None,
};
let start = std::time::Instant::now();
let result = template.expand(&bindings, Span::new(0, 1));
let duration = start.elapsed();
assert!(result.is_ok());
assert!(duration.as_millis() < 100); }
#[test]
fn test_variable_depth_analysis() {
let pattern = Pattern::Ellipsis {
patterns: vec![Pattern::Variable("outer".to_string())],
ellipsis_pattern: Box::new(Pattern::Ellipsis {
patterns: vec![],
ellipsis_pattern: Box::new(Pattern::Variable("inner".to_string())),
rest: None,
}),
rest: Some(Box::new(Pattern::Variable("final".to_string()))),
};
let depths = pattern.variable_depths();
assert_eq!(depths.get("outer"), Some(&0)); assert_eq!(depths.get("inner"), Some(&2)); assert_eq!(depths.get("final"), Some(&0)); }
#[test]
fn test_complete_srfi149_macro() {
let env = Rc::new(Environment::new(None, 0));
let pattern = Pattern::List(vec![
Pattern::Identifier("nested-map".to_string()),
Pattern::Variable("f".to_string()),
Pattern::Ellipsis {
patterns: vec![],
ellipsis_pattern: Box::new(Pattern::List(vec![
Pattern::Variable("a".to_string()),
Pattern::Ellipsis {
patterns: vec![],
ellipsis_pattern: Box::new(Pattern::Variable("b".to_string())),
rest: None,
},
])),
rest: None,
},
]);
let template = Template::NestedEllipsis {
templates: vec![],
nested_template: Box::new(Template::List(vec![
Template::Variable("f".to_string()),
Template::Variable("a".to_string()),
Template::Ellipsis {
templates: vec![],
ellipsis_template: Box::new(Template::Variable("b".to_string())),
rest: None,
},
])),
depth: 2,
rest: None,
};
let rule = SyntaxRule { pattern, template };
let transformer = SyntaxRulesTransformer {
literals: vec![],
rules: vec![rule],
name: Some("nested-map".to_string()),
definition_env: env,
custom_ellipsis: None,
srfi_149_mode: true, };
let input = spanned_list(vec![
spanned_id("nested-map"),
spanned_id("+"),
spanned_list(vec![
spanned_list(vec![spanned_lit(1.0), spanned_lit(2.0), spanned_lit(3.0)]),
spanned_list(vec![spanned_lit(4.0), spanned_lit(5.0), spanned_lit(6.0)]),
]),
]);
let result = expand_syntax_rules(&transformer, &input);
assert!(result.is_ok());
}