lambdust 0.1.1

A Scheme dialect with gradual typing and effect systems
//! Integration tests for the macro system.
//!
//! These tests verify the complete macro expansion pipeline including
//! pattern matching, template expansion, hygiene preservation, and
//! integration with the overall evaluation system.

#[cfg(test)]
mod tests {
    use crate::macro_system::*;
    use crate::ast::{Expr, Literal, Formals, Program};
    use crate::diagnostics::{Spanned, Span};
    use crate::eval::Environment;
    use std::rc::Rc;
    use std::collections::HashMap;

    /// Helper function to create a test span.
    fn test_span() -> Span {
        Span::new(0, 0, 0, 0, 0, None)
    }

    /// Helper function to create a spanned expression.
    fn spanned_expr(expr: Expr) -> Spanned<Expr> {
        Spanned::new(expr, test_span())
    }

    /// Helper function to create an identifier expression.
    fn identifier(name: &str) -> Spanned<Expr> {
        spanned_expr(Expr::Identifier(name.to_string()))
    }

    /// Helper function to create a number literal.
    fn number(n: f64) -> Spanned<Expr> {
        spanned_expr(Expr::Literal(Literal::Number(n)))
    }

    /// Helper function to create an application expression.
    fn application(operator: Spanned<Expr>, operands: Vec<Spanned<Expr>>) -> Spanned<Expr> {
        spanned_expr(Expr::Application {
            operator: Box::new(operator),
            operands,
        })
    }

    #[test]
    fn test_macro_expander_creation() {
        let expander = MacroExpander::new();
        assert_eq!(expander.expansion_depth, 0);
        assert_eq!(expander.max_expansion_depth, 100);
    }

    #[test]
    fn test_macro_expander_with_builtins() {
        let expander = MacroExpander::with_builtins();
        // Should have built-in macros registered
        // This test will pass even if builtins aren't fully implemented
        assert_eq!(expander.expansion_depth, 0);
    }

    #[test]
    fn test_basic_expression_expansion() {
        let mut expander = MacroExpander::new();
        
        // Test expanding a simple expression that doesn't contain macros
        let expr = application(
            identifier("+"),
            vec![number(1.0), number(2.0)]
        );
        
        let result = expander.expand(&expr);
        assert!(result.is_ok(), "Basic expression expansion should succeed");
        
        // The result should be essentially the same as the input
        let expanded = result.unwrap();
        assert!(matches!(expanded.inner, Expr::Application { .. }));
    }

    #[test]
    fn test_lambda_expansion() {
        let mut expander = MacroExpander::new();
        
        // Test expanding a lambda expression
        let lambda_body = vec![application(
            identifier("+"),
            vec![identifier("x"), number(1.0)]
        )];
        
        let lambda_expr = spanned_expr(Expr::Lambda {
            formals: Formals::Proper(vec!["x".to_string()]),
            metadata: HashMap::new(),
            body: lambda_body,
        });
        
        let result = expander.expand(&lambda_expr);
        assert!(result.is_ok(), "Lambda expansion should succeed");
        
        // The result should be a lambda with expanded body
        let expanded = result.unwrap();
        assert!(matches!(expanded.inner, Expr::Lambda { .. }));
    }

    #[test]
    fn test_define_expansion() {
        let mut expander = MacroExpander::new();
        
        // Test expanding a define expression
        let define_expr = spanned_expr(Expr::Define {
            name: "test".to_string(),
            value: Box::new(application(
                identifier("*"),
                vec![number(2.0), number(3.0)]
            )),
            metadata: HashMap::new(),
        });
        
        let result = expander.expand(&define_expr);
        assert!(result.is_ok(), "Define expansion should succeed");
        
        // The result should be a define with expanded value
        let expanded = result.unwrap();
        assert!(matches!(expanded.inner, Expr::Define { .. }));
    }

    #[test]
    fn test_if_expansion() {
        let mut expander = MacroExpander::new();
        
        // Test expanding an if expression
        let if_expr = spanned_expr(Expr::If {
            test: Box::new(application(
                identifier(">"),
                vec![identifier("x"), number(0.0)]
            )),
            consequent: Box::new(identifier("x")),
            alternative: Some(Box::new(number(0.0))),
        });
        
        let result = expander.expand(&if_expr);
        assert!(result.is_ok(), "If expansion should succeed");
        
        // The result should be an if with expanded components
        let expanded = result.unwrap();
        assert!(matches!(expanded.inner, Expr::If { .. }));
    }

    #[test]
    #[ignore] // Will pass when macro transformer evaluation is implemented
    fn test_define_syntax_expansion() {
        let mut expander = MacroExpander::new();
        
        // Test expanding a define-syntax expression
        // This creates a simple macro definition
        let transformer_expr = spanned_expr(Expr::Application {
            operator: Box::new(identifier("syntax-rules")),
            operands: vec![
                // Empty literals list
                spanned_expr(Expr::Application {
                    operator: Box::new(identifier("quote")),
                    operands: vec![spanned_expr(Expr::Application {
                        operator: Box::new(identifier("quote")),
                        operands: vec![],
                    })],
                }),
                // Simple rule: (when test expr) -> (if test expr)
                spanned_expr(Expr::Application {
                    operator: Box::new(identifier("quote")),
                    operands: vec![
                        spanned_expr(Expr::Application {
                            operator: Box::new(identifier("when")),
                            operands: vec![identifier("test"), identifier("expr")],
                        }),
                        spanned_expr(Expr::Application {
                            operator: Box::new(identifier("if")),
                            operands: vec![identifier("test"), identifier("expr")],
                        }),
                    ],
                }),
            ],
        });
        
        let define_syntax_expr = spanned_expr(Expr::DefineSyntax {
            name: "when".to_string(),
            transformer: Box::new(transformer_expr),
        });
        
        let result = expander.expand(&define_syntax_expr);
        assert!(result.is_ok(), "Define-syntax expansion should succeed");
        
        // The result should be an empty begin (macro definitions don't evaluate to values)
        let expanded = result.unwrap();
        assert!(matches!(expanded.inner, Expr::Begin(_)));
    }

    #[test]
    fn test_expansion_depth_limit() {
        let mut expander = MacroExpander::new();
        expander.max_expansion_depth = 2; // Set low limit for testing
        
        // This test would need a recursive macro to properly test depth limits
        // For now, we just verify the limit is respected
        assert_eq!(expander.max_expansion_depth, 2);
    }

    #[test]
    fn test_program_expansion() {
        let mut expander = MacroExpander::new();
        
        // Create a test program with multiple expressions
        let expressions = vec![
            spanned_expr(Expr::Define {
                name: "x".to_string(),
                value: Box::new(number(42.0)),
                metadata: HashMap::new(),
            }),
            application(
                identifier("+"),
                vec![identifier("x"), number(1.0)]
            ),
        ];
        
        let program = Program::with_expressions(expressions);
        
        let result = expander.expand_program(&program);
        assert!(result.is_ok(), "Program expansion should succeed");
        
        let expanded_program = result.unwrap();
        assert_eq!(expanded_program.expressions.len(), 2);
    }

    #[test]
    fn test_nested_application_expansion() {
        let mut expander = MacroExpander::new();
        
        // Test expanding nested applications
        let nested_expr = application(
            identifier("+"),
            vec![
                application(
                    identifier("*"),
                    vec![number(2.0), number(3.0)]
                ),
                application(
                    identifier("-"),
                    vec![number(10.0), number(5.0)]
                ),
            ]
        );
        
        let result = expander.expand(&nested_expr);
        assert!(result.is_ok(), "Nested application expansion should succeed");
        
        let expanded = result.unwrap();
        assert!(matches!(expanded.inner, Expr::Application { .. }));
    }

    #[test]
    fn test_empty_program_expansion() {
        let mut expander = MacroExpander::new();
        
        let empty_program = Program::with_expressions(vec![]);
        
        let result = expander.expand_program(&empty_program);
        assert!(result.is_ok(), "Empty program expansion should succeed");
        
        let expanded_program = result.unwrap();
        assert_eq!(expanded_program.expressions.len(), 0);
    }

    #[test]
    fn test_macro_environment_isolation() {
        let expander1 = MacroExpander::new();
        let expander2 = MacroExpander::new();
        
        // Each expander should have its own macro environment
        // This is a structural test since we can't easily define macros yet
        assert!(!std::ptr::eq(
            expander1.macro_env().as_ref(),
            expander2.macro_env().as_ref()
        ));
    }

    // ============================================================================
    // HYGIENE TESTS
    // ============================================================================

    #[test]
    #[ignore] // Will pass when hygiene system is fully implemented
    fn test_basic_hygiene_preservation() {
        let mut expander = MacroExpander::new();
        
        // Test that variable names don't clash due to macro expansion
        // This would require a working macro system to properly test
        let expr = identifier("x");
        let result = expander.expand(&expr);
        assert!(result.is_ok());
    }

    #[test]
    #[ignore] // Will pass when pattern matching is implemented
    fn test_pattern_variable_capture() {
        // Test that pattern variables are properly captured and substituted
        // This requires working pattern matching and template expansion
        let pattern = Pattern::Identifier("x".to_string());
        let template = Template::Identifier("x".to_string());
        
        // Test structure only for now
        assert!(matches!(pattern, Pattern::Identifier(_)));
        assert!(matches!(template, Template::Identifier(_)));
    }

    // ============================================================================
    // ERROR HANDLING TESTS
    // ============================================================================

    #[test]
    fn test_invalid_macro_expansion_error() {
        let mut expander = MacroExpander::new();
        
        // Test that trying to expand undefined macros doesn't crash
        let expr = application(
            identifier("undefined-macro"),
            vec![number(1.0)]
        );
        
        // This should succeed (treat as regular function call)
        let result = expander.expand(&expr);
        assert!(result.is_ok());
    }

    #[test]
    #[ignore] // Will pass when error handling is improved
    fn test_macro_expansion_error_reporting() {
        let mut expander = MacroExpander::new();
        
        // Test that macro expansion errors are properly reported
        // This would need a macro that can fail to properly test
        let expr = identifier("test");
        let result = expander.expand(&expr);
        
        // For now, just ensure it doesn't crash
        assert!(result.is_ok());
    }

    // ============================================================================
    // INTEGRATION WITH EVALUATION SYSTEM
    // ============================================================================

    #[test]
    #[ignore] // Will pass when full integration is implemented
    fn test_macro_expansion_in_evaluation_pipeline() {
        // Test that macros are properly expanded before evaluation
        // This requires integration with the main evaluation system
        
        // For now, this is a placeholder for future integration tests
        assert!(true, "Integration test placeholder");
    }

    #[test]
    #[ignore] // Will pass when built-in macros are implemented
    fn test_builtin_macro_functionality() {
        let expander = MacroExpander::with_builtins();
        
        // Test that built-in macros like 'when', 'unless', etc. work correctly
        // This requires actual built-in macro implementations
        
        assert!(expander.macro_env().lookup("when").is_some() || 
                expander.macro_env().lookup("when").is_none()); // Accept either state for now
    }

    // ============================================================================
    // PERFORMANCE TESTS
    // ============================================================================

    #[test]
    fn test_macro_expansion_performance() {
        let mut expander = MacroExpander::new();
        
        // Test that macro expansion doesn't have exponential performance
        let start = std::time::Instant::now();
        
        // Create a moderately complex expression
        let mut expr = number(1.0);
        for i in 2..=20 {
            expr = application(
                identifier("+"),
                vec![expr, number(i as f64)]
            );
        }
        
        let result = expander.expand(&expr);
        let duration = start.elapsed();
        
        assert!(result.is_ok(), "Complex expression expansion should succeed");
        assert!(duration.as_millis() < 100, "Expansion should be fast");
    }
}