/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; |