1use crate::ast::{BinOp, Expr, Literal, MatchArm, Pattern, Spanned};
2
3use super::{CallLowerCtx, SemanticConstructor, WrapperKind, classify_constructor_name};
4
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub enum DispatchLiteral {
7 Int(i64),
8 Float(String),
9 Bool(bool),
10 Str(String),
11 Unit,
12}
13
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub enum SemanticDispatchPattern {
16 Literal(DispatchLiteral),
17 EmptyList,
18 NoneValue,
19 WrapperTag(WrapperKind),
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub struct BoolMatchShape {
24 pub true_arm_index: usize,
25 pub false_arm_index: usize,
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum BoolCompareOp {
30 Eq,
31 Lt,
32 Gt,
33}
34
35#[derive(Debug, Clone, Copy, PartialEq)]
36pub enum BoolSubjectPlan<'a> {
37 Expr(&'a Expr),
38 Compare {
39 lhs: &'a Spanned<Expr>,
40 rhs: &'a Spanned<Expr>,
41 op: BoolCompareOp,
42 invert: bool,
43 },
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub struct ListMatchShape {
48 pub empty_arm_index: usize,
49 pub cons_arm_index: usize,
50}
51
52#[derive(Debug, Clone, PartialEq, Eq)]
53pub enum DispatchBindingPlan {
54 None,
55 WrapperPayload(String),
56}
57
58#[derive(Debug, Clone, PartialEq, Eq)]
59pub struct DispatchArmPlan {
60 pub pattern: SemanticDispatchPattern,
61 pub arm_index: usize,
62 pub binding: DispatchBindingPlan,
63}
64
65#[derive(Debug, Clone, PartialEq, Eq)]
66pub struct DispatchDefaultPlan {
67 pub arm_index: usize,
68 pub binding_name: Option<String>,
69}
70
71#[derive(Debug, Clone, PartialEq, Eq)]
72pub struct DispatchTableShape {
73 pub entries: Vec<DispatchArmPlan>,
74 pub default_arm: Option<DispatchDefaultPlan>,
75}
76
77#[derive(Debug, Clone, PartialEq, Eq)]
78pub enum MatchDispatchPlan {
79 Bool(BoolMatchShape),
80 List(ListMatchShape),
81 Table(DispatchTableShape),
82}
83
84pub fn classify_bool_match_shape_from_patterns(patterns: &[&Pattern]) -> Option<BoolMatchShape> {
85 if patterns.len() != 2 {
86 return None;
87 }
88
89 match (patterns[0], patterns[1]) {
90 (Pattern::Literal(Literal::Bool(true)), Pattern::Literal(Literal::Bool(false))) => {
91 Some(BoolMatchShape {
92 true_arm_index: 0,
93 false_arm_index: 1,
94 })
95 }
96 (Pattern::Literal(Literal::Bool(false)), Pattern::Literal(Literal::Bool(true))) => {
97 Some(BoolMatchShape {
98 true_arm_index: 1,
99 false_arm_index: 0,
100 })
101 }
102 (Pattern::Literal(Literal::Bool(true)), Pattern::Wildcard | Pattern::Ident(_)) => {
103 Some(BoolMatchShape {
104 true_arm_index: 0,
105 false_arm_index: 1,
106 })
107 }
108 _ => None,
109 }
110}
111
112pub fn classify_list_match_shape_from_patterns(patterns: &[&Pattern]) -> Option<ListMatchShape> {
113 if patterns.len() != 2 {
114 return None;
115 }
116
117 match (patterns[0], patterns[1]) {
118 (Pattern::EmptyList, Pattern::Cons(_, _)) => Some(ListMatchShape {
119 empty_arm_index: 0,
120 cons_arm_index: 1,
121 }),
122 (Pattern::Cons(_, _), Pattern::EmptyList) => Some(ListMatchShape {
123 empty_arm_index: 1,
124 cons_arm_index: 0,
125 }),
126 _ => None,
127 }
128}
129
130pub fn classify_dispatch_table_shape_from_patterns(
131 patterns: &[&Pattern],
132 ctx: &impl CallLowerCtx,
133) -> Option<DispatchTableShape> {
134 if patterns.len() < 2 {
135 return None;
136 }
137
138 let has_default = matches!(patterns.last(), Some(Pattern::Wildcard | Pattern::Ident(_)));
139 let dispatchable_end = if has_default {
140 patterns.len() - 1
141 } else {
142 patterns.len()
143 };
144
145 let mut entries = Vec::new();
146 for (arm_index, pattern) in patterns[..dispatchable_end].iter().enumerate() {
147 let semantic = classify_dispatch_pattern(pattern, ctx)?;
148 entries.push(DispatchArmPlan {
149 binding: classify_dispatch_binding(pattern, &semantic),
150 pattern: semantic,
151 arm_index,
152 });
153 }
154
155 if entries.len() < 2 {
156 return None;
157 }
158
159 Some(DispatchTableShape {
160 entries,
161 default_arm: has_default
162 .then(|| classify_default_arm_plan(patterns[patterns.len() - 1], patterns.len() - 1)),
163 })
164}
165
166pub fn classify_match_dispatch_plan_from_patterns(
167 patterns: &[&Pattern],
168 ctx: &impl CallLowerCtx,
169) -> Option<MatchDispatchPlan> {
170 if let Some(shape) = classify_bool_match_shape_from_patterns(patterns) {
171 return Some(MatchDispatchPlan::Bool(shape));
172 }
173
174 if let Some(shape) = classify_list_match_shape_from_patterns(patterns) {
175 return Some(MatchDispatchPlan::List(shape));
176 }
177
178 classify_dispatch_table_shape_from_patterns(patterns, ctx).map(MatchDispatchPlan::Table)
179}
180
181pub fn classify_dispatch_pattern(
182 pattern: &Pattern,
183 ctx: &impl CallLowerCtx,
184) -> Option<SemanticDispatchPattern> {
185 match pattern {
186 Pattern::Literal(Literal::BigInt(_)) => None,
190 Pattern::Literal(lit) => Some(SemanticDispatchPattern::Literal(dispatch_literal_from_ast(
191 lit,
192 ))),
193 Pattern::EmptyList => Some(SemanticDispatchPattern::EmptyList),
194 Pattern::Constructor(name, bindings) => match classify_constructor_name(name, ctx) {
195 SemanticConstructor::NoneValue if bindings.is_empty() => {
196 Some(SemanticDispatchPattern::NoneValue)
197 }
198 SemanticConstructor::Wrapper(kind) if bindings.len() <= 1 => {
199 Some(SemanticDispatchPattern::WrapperTag(kind))
200 }
201 _ => None,
202 },
203 _ => None,
204 }
205}
206
207pub fn classify_bool_match_shape(arms: &[MatchArm]) -> Option<BoolMatchShape> {
208 let patterns: Vec<&Pattern> = arms.iter().map(|arm| &arm.pattern).collect();
209 classify_bool_match_shape_from_patterns(&patterns)
210}
211
212pub fn classify_bool_subject_plan(subject: &Expr) -> BoolSubjectPlan<'_> {
213 let Expr::BinOp(op, lhs, rhs) = subject else {
214 return BoolSubjectPlan::Expr(subject);
215 };
216
217 match op {
218 BinOp::Eq => BoolSubjectPlan::Compare {
219 lhs,
220 rhs,
221 op: BoolCompareOp::Eq,
222 invert: false,
223 },
224 BinOp::Lt => BoolSubjectPlan::Compare {
225 lhs,
226 rhs,
227 op: BoolCompareOp::Lt,
228 invert: false,
229 },
230 BinOp::Gt => BoolSubjectPlan::Compare {
231 lhs,
232 rhs,
233 op: BoolCompareOp::Gt,
234 invert: false,
235 },
236 BinOp::Neq => BoolSubjectPlan::Compare {
237 lhs,
238 rhs,
239 op: BoolCompareOp::Eq,
240 invert: true,
241 },
242 BinOp::Gte => BoolSubjectPlan::Compare {
243 lhs,
244 rhs,
245 op: BoolCompareOp::Lt,
246 invert: true,
247 },
248 BinOp::Lte => BoolSubjectPlan::Compare {
249 lhs,
250 rhs,
251 op: BoolCompareOp::Gt,
252 invert: true,
253 },
254 BinOp::Add | BinOp::Sub | BinOp::Mul | BinOp::Div => BoolSubjectPlan::Expr(subject),
255 }
256}
257
258pub fn classify_list_match_shape(arms: &[MatchArm]) -> Option<ListMatchShape> {
259 let patterns: Vec<&Pattern> = arms.iter().map(|arm| &arm.pattern).collect();
260 classify_list_match_shape_from_patterns(&patterns)
261}
262
263pub fn classify_dispatch_table_shape(
264 arms: &[MatchArm],
265 ctx: &impl CallLowerCtx,
266) -> Option<DispatchTableShape> {
267 let patterns: Vec<&Pattern> = arms.iter().map(|arm| &arm.pattern).collect();
268 classify_dispatch_table_shape_from_patterns(&patterns, ctx)
269}
270
271pub fn classify_match_dispatch_plan(
272 arms: &[MatchArm],
273 ctx: &impl CallLowerCtx,
274) -> Option<MatchDispatchPlan> {
275 let patterns: Vec<&Pattern> = arms.iter().map(|arm| &arm.pattern).collect();
276 classify_match_dispatch_plan_from_patterns(&patterns, ctx)
277}
278
279fn dispatch_literal_from_ast(lit: &Literal) -> DispatchLiteral {
280 match lit {
281 Literal::Int(i) => DispatchLiteral::Int(*i),
282 Literal::Float(f) => DispatchLiteral::Float(f.to_string()),
283 Literal::Bool(b) => DispatchLiteral::Bool(*b),
284 Literal::Str(s) => DispatchLiteral::Str(s.clone()),
285 Literal::Unit => DispatchLiteral::Unit,
286 Literal::BigInt(_) => {
289 unreachable!("BigInt literal patterns are excluded from dispatch tables")
290 }
291 }
292}
293
294fn classify_dispatch_binding(
295 pattern: &Pattern,
296 semantic: &SemanticDispatchPattern,
297) -> DispatchBindingPlan {
298 match (pattern, semantic) {
299 (Pattern::Constructor(_, bindings), SemanticDispatchPattern::WrapperTag(_))
300 if !bindings.is_empty() && bindings[0] != "_" =>
301 {
302 DispatchBindingPlan::WrapperPayload(bindings[0].clone())
303 }
304 _ => DispatchBindingPlan::None,
305 }
306}
307
308fn classify_default_arm_plan(pattern: &Pattern, arm_index: usize) -> DispatchDefaultPlan {
309 let binding_name = match pattern {
310 Pattern::Ident(name) if name != "_" => Some(name.clone()),
311 _ => None,
312 };
313
314 DispatchDefaultPlan {
315 arm_index,
316 binding_name,
317 }
318}