fairjax 0.1.0

Fair join pattern matching
Documentation
use crate::parse::{pattern::PatternDefinition, strategy::InputStrategy};
use proc_macro2::Span;
use syn::{Arm, Expr, Result, spanned::Spanned};

#[derive(Clone)]
pub struct CaseDefinition {
    pub index: usize,
    pub strategy: InputStrategy,
    pub pattern: PatternDefinition,
    pub guard: Option<Expr>,
    pub body: Expr,
    pub span: Span,
}

impl CaseDefinition {
    /// Parse match Arm into case object
    pub fn parse(input: Arm, index: usize) -> Result<Self> {
        let span = input.span();
        let Arm {
            attrs,
            pat,
            guard,
            body,
            ..
        } = input;

        // Check if case arm has a strategy attribute
        let strategy = InputStrategy::parse(attrs)?;

        // Parse match scrutinee into pattern object
        let pattern = PatternDefinition::parse(pat)?;

        // Retrieve guard expression if present
        let guard_option = match guard {
            Some((_, expr)) => Some(*expr),
            _ => None,
        };

        // Retrieve body and construct object
        Ok(CaseDefinition {
            index,
            strategy,
            pattern,
            guard: guard_option,
            body: *body,
            span,
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use proc_macro_utils::assert_tokens;
    use quote::ToTokens;
    use syn::{Arm, parse_quote};

    #[test]
    fn test_parse_simple_case() {
        // Define input to test
        let input: Arm = parse_quote! {
            (A(x), B(y)) if x == y => println!("Success!")
        };

        // Define expected result
        let expected_strategy = InputStrategy::Auto;

        // Compute actual result
        let result = CaseDefinition::parse(input, 0).unwrap();

        // Check result
        assert_eq!(expected_strategy, result.strategy);
        assert_tokens!(result.guard.unwrap().to_token_stream(), { x == y });
        assert_tokens!(result.body.to_token_stream(), { println!("Success!") });
    }

    #[test]
    fn test_parse_case_without_guard() {
        // Define input to test
        let input: Arm = parse_quote! {
            (A(x), B(y)) => println!("No guard!")
        };

        // Define expected result
        let expected_strategy = InputStrategy::Auto;

        // Compute actual result
        let result = CaseDefinition::parse(input, 0).unwrap();

        // Check result
        assert_eq!(expected_strategy, result.strategy);
        assert!(result.guard.is_none());
        assert_tokens!(result.body.to_token_stream(), { println!("No guard!") });
    }

    #[test]
    fn test_parse_case_with_strategy_attribute() {
        // Define input to test
        let input: Arm = parse_quote! {
            #[BruteForce]
            (A(x), B(y)) => x + y
        };

        // Define expected result
        let expected_strategy = InputStrategy::BruteForce;

        // Compute actual result
        let result = CaseDefinition::parse(input, 0).unwrap();

        // Check result
        assert_eq!(expected_strategy, result.strategy);
        assert!(result.guard.is_none());
        assert_tokens!(result.body.to_token_stream(), { x + y });
    }

    #[test]
    fn test_parse_case_with_complex_guard() {
        // Define input to test
        let input: Arm = parse_quote! {
            (A(x), B(y)) if x > 0 && y < 10 => x * y
        };

        // Define expected result
        let expected_strategy = InputStrategy::Auto;

        // Compute actual result
        let result = CaseDefinition::parse(input, 0).unwrap();

        // Check result
        assert_eq!(expected_strategy, result.strategy);
        assert_tokens!(result.guard.unwrap().to_token_stream(), { x > 0 && y < 10 });
        assert_tokens!(result.body.to_token_stream(), { x * y });
    }

    #[test]
    fn test_parse_case_with_block_body() {
        // Define input to test
        let input: Arm = parse_quote! {
            (A(x), B(y)) => {
                let sum = x + y;
                println!("Sum: {}", sum);
                sum
            }
        };

        // Define expected result
        let expected_strategy = InputStrategy::Auto;

        // Compute actual result
        let result = CaseDefinition::parse(input, 0).unwrap();

        // Check result
        assert_eq!(expected_strategy, result.strategy);
        assert!(result.guard.is_none());
        assert_tokens!(result.body.to_token_stream(), {
            {
                let sum = x + y;
                println!("Sum: {}", sum);
                sum
            }
        });
    }
}