Coverage Report

Created: 2025-09-05 15:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/home/noah/src/ruchy/src/backend/transpiler/patterns.rs
Line
Count
Source
1
//! Pattern matching transpilation
2
3
#![allow(clippy::missing_errors_doc)]
4
#![allow(clippy::only_used_in_recursion)]
5
6
use super::Transpiler;
7
use crate::frontend::ast::{Expr, MatchArm, Pattern};
8
use anyhow::Result;
9
use proc_macro2::TokenStream;
10
use quote::{format_ident, quote};
11
12
impl Transpiler {
13
    /// Transpiles match expressions
14
    ///
15
    /// # Examples
16
    ///
17
    /// ```
18
    /// use ruchy::{Transpiler, Parser};
19
    /// 
20
    /// let transpiler = Transpiler::new();
21
    /// let mut parser = Parser::new(r#"match x { 1 => "one", _ => "other" }"#);
22
    /// let ast = parser.parse().unwrap();
23
    /// 
24
    /// let result = transpiler.transpile(&ast).unwrap();
25
    /// let code = result.to_string();
26
    /// assert!(code.contains("match"));
27
    /// assert!(code.contains("1 =>"));
28
    /// assert!(code.contains("_ =>"));
29
    /// ```
30
6
    pub fn transpile_match(&self, expr: &Expr, arms: &[MatchArm]) -> Result<TokenStream> {
31
6
        let expr_tokens = self.transpile_expr(expr)
?0
;
32
33
6
        let mut arm_tokens = Vec::new();
34
19
        for 
arm13
in arms {
35
13
            let pattern_tokens = self.transpile_pattern(&arm.pattern)
?0
;
36
13
            let body_tokens = self.transpile_expr(&arm.body)
?0
;
37
38
            // Handle pattern guards if present
39
13
            if let Some(
guard_expr1
) = &arm.guard {
40
1
                let guard_tokens = self.transpile_expr(guard_expr)
?0
;
41
1
                arm_tokens.push(quote! {
42
                    #pattern_tokens if #guard_tokens => #body_tokens
43
                });
44
12
            } else {
45
12
                arm_tokens.push(quote! {
46
12
                    #pattern_tokens => #body_tokens
47
12
                });
48
12
            }
49
        }
50
51
6
        Ok(quote! {
52
            match #expr_tokens {
53
                #(#arm_tokens,)*
54
            }
55
        })
56
6
    }
57
58
    /// Transpiles patterns
59
    ///
60
    /// # Examples
61
    ///
62
    /// ```
63
    /// use ruchy::{Transpiler, Parser};
64
    /// 
65
    /// // Wildcard pattern
66
    /// let transpiler = Transpiler::new();
67
    /// let mut parser = Parser::new("match x { _ => 42 }");
68
    /// let ast = parser.parse().unwrap();
69
    /// let result = transpiler.transpile(&ast).unwrap();
70
    /// assert!(result.to_string().contains("_ =>"));
71
    /// ```
72
    ///
73
    /// ```
74
    /// use ruchy::{Transpiler, Parser};
75
    /// 
76
    /// // Identifier pattern
77
    /// let transpiler = Transpiler::new();
78
    /// let mut parser = Parser::new("match x { y => y + 1 }");
79
    /// let ast = parser.parse().unwrap();
80
    /// let result = transpiler.transpile(&ast).unwrap();
81
    /// assert!(result.to_string().contains("y =>"));
82
    /// ```
83
    ///
84
    /// ```
85
    /// use ruchy::{Transpiler, Parser};
86
    /// 
87
    /// // Tuple pattern
88
    /// let transpiler = Transpiler::new();
89
    /// let mut parser = Parser::new("match pair { (a, b) => a + b }");
90
    /// let ast = parser.parse().unwrap();
91
    /// let result = transpiler.transpile(&ast).unwrap();
92
    /// assert!(result.to_string().contains("("));
93
    /// assert!(result.to_string().contains("a"));
94
    /// assert!(result.to_string().contains("b"));
95
    /// ```
96
61
    pub fn transpile_pattern(&self, pattern: &Pattern) -> Result<TokenStream> {
97
61
        match pattern {
98
7
            Pattern::Wildcard => Ok(quote! { _ }),
99
20
            Pattern::Literal(lit) => Ok(Self::transpile_literal(lit)),
100
17
            Pattern::Identifier(name) => {
101
17
                let ident = format_ident!("{}", name);
102
17
                Ok(quote! { #ident })
103
            }
104
1
            Pattern::QualifiedName(path) => {
105
                // Generate qualified path like Ordering::Less
106
4
                let 
segments1
:
Vec<_>1
=
path.iter()1
.
map1
(|s| format_ident!("{}", s)).
collect1
();
107
1
                Ok(quote! { #(#segments)::* })
108
            }
109
4
            Pattern::Tuple(patterns) => {
110
4
                let pattern_tokens: Result<Vec<_>> =
111
8
                    
patterns.iter()4
.
map4
(|p| self.transpile_pattern(p)).
collect4
();
112
4
                let pattern_tokens = pattern_tokens
?0
;
113
4
                Ok(quote! { (#(#pattern_tokens),*) })
114
            }
115
5
            Pattern::List(patterns) => {
116
5
                if patterns.is_empty() {
117
1
                    Ok(quote! { [] })
118
                } else {
119
                    // Check for rest pattern
120
9
                    let 
has_rest4
=
patterns.iter()4
.
any4
(|p| matches!(p, Pattern::Rest | Pattern::RestNamed(_)));
121
122
4
                    if has_rest {
123
                        // Handle patterns with rest
124
3
                        let mut pattern_tokens = Vec::new();
125
9
                        for 
p6
in patterns {
126
6
                            match p {
127
2
                                Pattern::Rest => pattern_tokens.push(quote! { .. }),
128
1
                                Pattern::RestNamed(name) => {
129
1
                                    let name_ident = format_ident!("{}", name);
130
1
                                    pattern_tokens.push(quote! { ..#name_ident });
131
1
                                }
132
3
                                _ => pattern_tokens.push(self.transpile_pattern(p)
?0
),
133
                            }
134
                        }
135
3
                        Ok(quote! { [#(#pattern_tokens),*] })
136
                    } else {
137
                        // Simple list pattern
138
1
                        let pattern_tokens: Result<Vec<_>> =
139
3
                            
patterns.iter()1
.
map1
(|p| self.transpile_pattern(p)).
collect1
();
140
1
                        let pattern_tokens = pattern_tokens
?0
;
141
1
                        Ok(quote! { [#(#pattern_tokens),*] })
142
                    }
143
                }
144
            }
145
3
            Pattern::Struct { name, fields, has_rest } => {
146
3
                let struct_name = format_ident!("{}", name);
147
148
3
                if fields.is_empty() {
149
0
                    Ok(quote! { #struct_name {} })
150
                } else {
151
3
                    let field_patterns: Result<Vec<_>> = fields
152
3
                        .iter()
153
4
                        .
map3
(|field| {
154
4
                            let field_ident = format_ident!("{}", field.name);
155
4
                            if let Some(ref pattern) = field.pattern {
156
4
                                let pattern_tokens = self.transpile_pattern(pattern)
?0
;
157
4
                                Ok(quote! { #field_ident: #pattern_tokens })
158
                            } else {
159
                                // Shorthand field pattern
160
0
                                Ok(quote! { #field_ident })
161
                            }
162
4
                        })
163
3
                        .collect();
164
3
                    let field_patterns = field_patterns
?0
;
165
3
                    if *has_rest {
166
2
                        Ok(quote! { #struct_name { #(#field_patterns),*, .. } })
167
                    } else {
168
1
                        Ok(quote! { #struct_name { #(#field_patterns),* } })
169
                    }
170
                }
171
            }
172
2
            Pattern::Or(patterns) => {
173
2
                let pattern_tokens: Result<Vec<_>> =
174
6
                    
patterns.iter()2
.
map2
(|p| self.transpile_pattern(p)).
collect2
();
175
2
                let pattern_tokens = pattern_tokens
?0
;
176
177
                // Join patterns with |
178
2
                let mut result = TokenStream::new();
179
6
                for (i, tokens) in 
pattern_tokens.iter()2
.
enumerate2
() {
180
6
                    if i > 0 {
181
4
                        result.extend(quote! { | });
182
4
                    
}2
183
6
                    result.extend(tokens.clone());
184
                }
185
2
                Ok(result)
186
            }
187
            Pattern::Range {
188
2
                start,
189
2
                end,
190
2
                inclusive,
191
            } => {
192
2
                let start_tokens = self.transpile_pattern(start)
?0
;
193
2
                let end_tokens = self.transpile_pattern(end)
?0
;
194
195
2
                if *inclusive {
196
1
                    Ok(quote! { #start_tokens..=#end_tokens })
197
                } else {
198
1
                    Ok(quote! { #start_tokens..#end_tokens })
199
                }
200
            }
201
0
            Pattern::Rest => Ok(quote! { .. }),
202
0
            Pattern::RestNamed(name) => {
203
0
                let name_ident = format_ident!("{}", name);
204
0
                Ok(quote! { ..#name_ident })
205
            }
206
0
            Pattern::Ok(pattern) => {
207
0
                let inner = self.transpile_pattern(pattern)?;
208
0
                Ok(quote! { Ok(#inner) })
209
            }
210
0
            Pattern::Err(pattern) => {
211
0
                let inner = self.transpile_pattern(pattern)?;
212
0
                Ok(quote! { Err(#inner) })
213
            }
214
0
            Pattern::Some(pattern) => {
215
0
                let inner = self.transpile_pattern(pattern)?;
216
0
                Ok(quote! { Some(#inner) })
217
            }
218
0
            Pattern::None => Ok(quote! { None }),
219
        }
220
61
    }
221
}
222
223
#[cfg(test)]
224
#[path = "patterns_tests.rs"]
225
mod tests;