/home/noah/src/ruchy/src/runtime/pattern_matching.rs
Line | Count | Source |
1 | | //! Shared pattern matching utilities |
2 | | //! Extracted to reduce duplication across interpreter and REPL |
3 | | |
4 | | use crate::frontend::ast::{Literal, Pattern}; |
5 | | use crate::runtime::Value; |
6 | | |
7 | | /// Match a literal pattern against a value |
8 | | /// |
9 | | /// # Examples |
10 | | /// |
11 | | /// ``` |
12 | | /// use ruchy::runtime::pattern_matching::match_literal_pattern; |
13 | | /// use ruchy::runtime::Value; |
14 | | /// use ruchy::frontend::ast::Literal; |
15 | | /// |
16 | | /// let value = Value::Int(42); |
17 | | /// let pattern = Literal::Integer(42); |
18 | | /// assert!(match_literal_pattern(&value, &pattern)); |
19 | | /// |
20 | | /// let pattern2 = Literal::Integer(43); |
21 | | /// assert!(!match_literal_pattern(&value, &pattern2)); |
22 | | /// ``` |
23 | 0 | pub fn match_literal_pattern(value: &Value, literal: &Literal) -> bool { |
24 | 0 | match (value, literal) { |
25 | 0 | (Value::Unit, Literal::Unit) => true, |
26 | 0 | (Value::Int(v), Literal::Integer(p)) => v == p, |
27 | 0 | (Value::Float(v), Literal::Float(p)) => (v - p).abs() < f64::EPSILON, |
28 | 0 | (Value::String(v), Literal::String(p)) => v == p, |
29 | 0 | (Value::Bool(v), Literal::Bool(p)) => v == p, |
30 | 0 | (Value::Char(v), Literal::Char(p)) => v == p, |
31 | 0 | _ => false, |
32 | | } |
33 | 0 | } |
34 | | |
35 | | /// Helper function to match collection patterns (tuple or list) |
36 | 0 | fn match_collection_patterns(patterns: &[Pattern], values: &[Value]) -> Option<Vec<(String, Value)>> { |
37 | 0 | if patterns.len() != values.len() { |
38 | 0 | return None; |
39 | 0 | } |
40 | | |
41 | 0 | let mut bindings = Vec::new(); |
42 | 0 | for (pat, val) in patterns.iter().zip(values.iter()) { |
43 | 0 | let sub_bindings = match_pattern(pat, val)?; |
44 | 0 | bindings.extend(sub_bindings); |
45 | | } |
46 | 0 | Some(bindings) |
47 | 0 | } |
48 | | |
49 | | /// Match a pattern against a value, returning bindings if successful |
50 | 0 | pub fn match_pattern(pattern: &Pattern, value: &Value) -> Option<Vec<(String, Value)>> { |
51 | 0 | match pattern { |
52 | 0 | Pattern::Wildcard => Some(vec![]), |
53 | 0 | Pattern::Identifier(name) => Some(vec![(name.clone(), value.clone())]), |
54 | 0 | Pattern::Literal(lit) => match_literal_pattern_helper(value, lit), |
55 | 0 | Pattern::Tuple(patterns) => match_tuple_pattern_helper(patterns, value), |
56 | 0 | Pattern::List(patterns) => match_list_pattern_helper(patterns, value), |
57 | 0 | Pattern::Struct { fields, .. } => match_struct_pattern_helper(fields, value), |
58 | 0 | Pattern::Or(patterns) => match_or_pattern_helper(patterns, value), |
59 | 0 | Pattern::Rest => Some(vec![]), |
60 | 0 | Pattern::RestNamed(name) => Some(vec![(name.clone(), value.clone())]), |
61 | 0 | Pattern::Range { start, end, inclusive } => match_range_pattern_helper(start, end, *inclusive, value), |
62 | 0 | Pattern::QualifiedName(_) => None, |
63 | 0 | Pattern::Some(inner_pattern) => match_some_pattern_helper(inner_pattern, value), |
64 | 0 | Pattern::None => match_none_pattern_helper(value), |
65 | 0 | Pattern::Ok(_) | Pattern::Err(_) => None, |
66 | 0 | Pattern::WithDefault { pattern, .. } => { |
67 | | // For default patterns, we match the inner pattern |
68 | | // Default handling is done at the destructuring level |
69 | 0 | match_pattern(pattern, value) |
70 | | } |
71 | | } |
72 | 0 | } |
73 | | |
74 | | /// Check if two values are equal (for pattern matching) |
75 | 0 | pub fn values_equal(v1: &Value, v2: &Value) -> bool { |
76 | 0 | match (v1, v2) { |
77 | 0 | (Value::Unit, Value::Unit) => true, |
78 | 0 | (Value::Int(a), Value::Int(b)) => a == b, |
79 | 0 | (Value::Float(a), Value::Float(b)) => (a - b).abs() < f64::EPSILON, |
80 | 0 | (Value::String(a), Value::String(b)) => a == b, |
81 | 0 | (Value::Bool(a), Value::Bool(b)) => a == b, |
82 | 0 | (Value::Char(a), Value::Char(b)) => a == b, |
83 | 0 | (Value::List(a), Value::List(b)) => { |
84 | 0 | a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| values_equal(x, y)) |
85 | | } |
86 | 0 | (Value::Tuple(a), Value::Tuple(b)) => { |
87 | 0 | a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| values_equal(x, y)) |
88 | | } |
89 | 0 | (Value::Object(f1), Value::Object(f2)) => { |
90 | 0 | f1.len() == f2.len() && |
91 | 0 | f1.iter().all(|(k, v)| f2.get(k).is_some_and(|v2| values_equal(v, v2))) |
92 | | } |
93 | 0 | (Value::Range { start: s1, end: e1, inclusive: i1 }, |
94 | 0 | Value::Range { start: s2, end: e2, inclusive: i2 }) => { |
95 | 0 | s1 == s2 && e1 == e2 && i1 == i2 |
96 | | } |
97 | 0 | _ => false, |
98 | | } |
99 | 0 | } |
100 | | |
101 | | /// Helper for matching literal patterns (complexity: 2) |
102 | 0 | fn match_literal_pattern_helper(value: &Value, lit: &Literal) -> Option<Vec<(String, Value)>> { |
103 | 0 | if match_literal_pattern(value, lit) { |
104 | 0 | Some(vec![]) |
105 | | } else { |
106 | 0 | None |
107 | | } |
108 | 0 | } |
109 | | |
110 | | /// Helper for matching tuple patterns (complexity: 3) |
111 | 0 | fn match_tuple_pattern_helper(patterns: &[Pattern], value: &Value) -> Option<Vec<(String, Value)>> { |
112 | 0 | if let Value::Tuple(values) = value { |
113 | 0 | match_collection_patterns(patterns, values) |
114 | | } else { |
115 | 0 | None |
116 | | } |
117 | 0 | } |
118 | | |
119 | | /// Helper for matching list patterns (complexity: 3) |
120 | 0 | fn match_list_pattern_helper(patterns: &[Pattern], value: &Value) -> Option<Vec<(String, Value)>> { |
121 | 0 | if let Value::List(values) = value { |
122 | 0 | match_collection_patterns(patterns, values) |
123 | | } else { |
124 | 0 | None |
125 | | } |
126 | 0 | } |
127 | | |
128 | | /// Helper for matching struct patterns (complexity: 8) |
129 | 0 | fn match_struct_pattern_helper(fields: &[crate::frontend::ast::StructPatternField], value: &Value) -> Option<Vec<(String, Value)>> { |
130 | 0 | if let Value::Object(obj_fields) = value { |
131 | 0 | let mut bindings = Vec::new(); |
132 | | |
133 | 0 | for field in fields { |
134 | 0 | let field_value = obj_fields.get(&field.name)?; |
135 | | |
136 | 0 | if let Some(ref field_pattern) = field.pattern { |
137 | 0 | let field_bindings = match_pattern(field_pattern, field_value)?; |
138 | 0 | bindings.extend(field_bindings); |
139 | 0 | } else { |
140 | 0 | bindings.push((field.name.clone(), field_value.clone())); |
141 | 0 | } |
142 | | } |
143 | | |
144 | 0 | Some(bindings) |
145 | | } else { |
146 | 0 | None |
147 | | } |
148 | 0 | } |
149 | | |
150 | | /// Helper for matching or patterns (complexity: 4) |
151 | 0 | fn match_or_pattern_helper(patterns: &[Pattern], value: &Value) -> Option<Vec<(String, Value)>> { |
152 | 0 | for pat in patterns { |
153 | 0 | if let Some(bindings) = match_pattern(pat, value) { |
154 | 0 | return Some(bindings); |
155 | 0 | } |
156 | | } |
157 | 0 | None |
158 | 0 | } |
159 | | |
160 | | /// Helper for matching range patterns (complexity: 9) |
161 | 0 | fn match_range_pattern_helper(start: &Pattern, end: &Pattern, inclusive: bool, value: &Value) -> Option<Vec<(String, Value)>> { |
162 | 0 | if let Value::Int(val) = value { |
163 | 0 | let start_val = if let Pattern::Literal(Literal::Integer(n)) = start { |
164 | 0 | *n |
165 | | } else { |
166 | 0 | return None; |
167 | | }; |
168 | | |
169 | 0 | let end_val = if let Pattern::Literal(Literal::Integer(n)) = end { |
170 | 0 | *n |
171 | | } else { |
172 | 0 | return None; |
173 | | }; |
174 | | |
175 | 0 | let val = *val; |
176 | 0 | let in_range = if inclusive { |
177 | 0 | val >= start_val && val <= end_val |
178 | | } else { |
179 | 0 | val >= start_val && val < end_val |
180 | | }; |
181 | | |
182 | 0 | if in_range { |
183 | 0 | Some(Vec::new()) |
184 | | } else { |
185 | 0 | None |
186 | | } |
187 | | } else { |
188 | 0 | None |
189 | | } |
190 | 0 | } |
191 | | |
192 | | /// Helper for matching Some patterns (complexity: 6) |
193 | 0 | fn match_some_pattern_helper(inner_pattern: &Pattern, value: &Value) -> Option<Vec<(String, Value)>> { |
194 | 0 | if let Value::EnumVariant { variant_name, data, .. } = value { |
195 | 0 | if variant_name == "Some" { |
196 | 0 | if let Some(ref variant_data) = data { |
197 | 0 | if !variant_data.is_empty() { |
198 | 0 | return match_pattern(inner_pattern, &variant_data[0]); |
199 | 0 | } |
200 | 0 | } |
201 | 0 | } |
202 | 0 | } |
203 | 0 | None |
204 | 0 | } |
205 | | |
206 | | /// Helper for matching None patterns (complexity: 4) |
207 | 0 | fn match_none_pattern_helper(value: &Value) -> Option<Vec<(String, Value)>> { |
208 | 0 | if let Value::EnumVariant { variant_name, .. } = value { |
209 | 0 | if variant_name == "None" { |
210 | 0 | return Some(Vec::new()); |
211 | 0 | } |
212 | 0 | } |
213 | 0 | None |
214 | 0 | } |