1use crate::ast::{Expr, Literal};
2
3use super::{CallLowerCtx, CallPlan, classify_call_plan, expr_to_dotted_name};
4
5#[derive(Debug, Clone, Copy, PartialEq)]
10pub enum LeafOp<'a> {
11 FieldAccess {
12 object: &'a Expr,
13 field_name: &'a str,
14 },
15 MapGet {
16 map: &'a Expr,
17 key: &'a Expr,
18 },
19 MapSet {
20 map: &'a Expr,
21 key: &'a Expr,
22 value: &'a Expr,
23 },
24 VectorNew {
25 size: &'a Expr,
26 fill: &'a Expr,
27 },
28 VectorSetOrDefaultSameVector {
29 vector: &'a Expr,
30 index: &'a Expr,
31 value: &'a Expr,
32 },
33 VectorGetOrDefaultLiteral {
34 vector: &'a Expr,
35 index: &'a Expr,
36 default_literal: &'a Literal,
37 },
38 IntModOrDefaultLiteral {
40 a: &'a Expr,
41 b: &'a Expr,
42 default_literal: &'a Literal,
43 },
44 ListIndexGet {
46 list: &'a Expr,
47 index: &'a Expr,
48 },
49}
50
51pub fn classify_leaf_op<'a>(expr: &'a Expr, ctx: &impl CallLowerCtx) -> Option<LeafOp<'a>> {
52 match expr {
53 Expr::Attr(object, field_name) => classify_field_access(expr, object.as_ref(), field_name),
54 Expr::FnCall(fn_expr, args) => classify_leaf_call(fn_expr, args, ctx),
55 _ => None,
56 }
57}
58
59fn classify_field_access<'a>(
60 full_expr: &'a Expr,
61 object: &'a Expr,
62 field_name: &'a str,
63) -> Option<LeafOp<'a>> {
64 if expr_to_dotted_name(full_expr).is_some_and(|dotted| {
67 dotted
68 .chars()
69 .next()
70 .is_some_and(|first| first.is_uppercase())
71 }) {
72 return None;
73 }
74
75 Some(LeafOp::FieldAccess { object, field_name })
76}
77
78fn classify_leaf_call<'a>(
79 fn_expr: &'a Expr,
80 args: &'a [Expr],
81 ctx: &impl CallLowerCtx,
82) -> Option<LeafOp<'a>> {
83 match classify_call_plan(fn_expr, ctx) {
84 CallPlan::Builtin(name) => match name.as_str() {
85 "Map.get" if args.len() == 2 => Some(LeafOp::MapGet {
86 map: &args[0],
87 key: &args[1],
88 }),
89 "Map.set" if args.len() == 3 => Some(LeafOp::MapSet {
90 map: &args[0],
91 key: &args[1],
92 value: &args[2],
93 }),
94 "Vector.new" if args.len() == 2 => Some(LeafOp::VectorNew {
95 size: &args[0],
96 fill: &args[1],
97 }),
98 "Vector.get" if args.len() == 2 => classify_list_index_get(&args[0], &args[1], ctx),
99 "Option.withDefault" if args.len() == 2 => {
100 classify_vector_set_or_default(&args[0], &args[1], ctx)
101 .or_else(|| classify_vector_get_or_default(&args[0], &args[1], ctx))
102 }
103 "Result.withDefault" if args.len() == 2 => {
104 classify_int_mod_or_default(&args[0], &args[1], ctx)
105 }
106 _ => None,
107 },
108 _ => None,
109 }
110}
111
112fn classify_vector_set_or_default<'a>(
113 option_expr: &'a Expr,
114 default_expr: &'a Expr,
115 ctx: &impl CallLowerCtx,
116) -> Option<LeafOp<'a>> {
117 let Expr::FnCall(inner_callee, inner_args) = option_expr else {
118 return None;
119 };
120 if inner_args.len() != 3 {
121 return None;
122 }
123
124 match classify_call_plan(inner_callee, ctx) {
125 CallPlan::Builtin(name) if name == "Vector.set" && default_expr == &inner_args[0] => {
126 Some(LeafOp::VectorSetOrDefaultSameVector {
127 vector: &inner_args[0],
128 index: &inner_args[1],
129 value: &inner_args[2],
130 })
131 }
132 _ => None,
133 }
134}
135
136fn classify_vector_get_or_default<'a>(
137 option_expr: &'a Expr,
138 default_expr: &'a Expr,
139 ctx: &impl CallLowerCtx,
140) -> Option<LeafOp<'a>> {
141 let default_literal = match default_expr {
142 Expr::Literal(lit) => lit,
143 _ => return None,
144 };
145
146 let Expr::FnCall(inner_callee, inner_args) = option_expr else {
147 return None;
148 };
149 if inner_args.len() != 2 {
150 return None;
151 }
152
153 match classify_call_plan(inner_callee, ctx) {
154 CallPlan::Builtin(name) if name == "Vector.get" => {
155 Some(LeafOp::VectorGetOrDefaultLiteral {
156 vector: &inner_args[0],
157 index: &inner_args[1],
158 default_literal,
159 })
160 }
161 _ => None,
162 }
163}
164
165fn classify_list_index_get<'a>(
166 vector_expr: &'a Expr,
167 index: &'a Expr,
168 ctx: &impl CallLowerCtx,
169) -> Option<LeafOp<'a>> {
170 let Expr::FnCall(inner_callee, inner_args) = vector_expr else {
172 return None;
173 };
174 if inner_args.len() != 1 {
175 return None;
176 }
177 match classify_call_plan(inner_callee, ctx) {
178 CallPlan::Builtin(name) if name == "Vector.fromList" => Some(LeafOp::ListIndexGet {
179 list: &inner_args[0],
180 index,
181 }),
182 _ => None,
183 }
184}
185
186fn classify_int_mod_or_default<'a>(
187 result_expr: &'a Expr,
188 default_expr: &'a Expr,
189 ctx: &impl CallLowerCtx,
190) -> Option<LeafOp<'a>> {
191 let default_literal = match default_expr {
192 Expr::Literal(lit) => lit,
193 _ => return None,
194 };
195
196 let Expr::FnCall(inner_callee, inner_args) = result_expr else {
197 return None;
198 };
199 if inner_args.len() != 2 {
200 return None;
201 }
202
203 match classify_call_plan(inner_callee, ctx) {
204 CallPlan::Builtin(name) if name == "Int.mod" => Some(LeafOp::IntModOrDefaultLiteral {
205 a: &inner_args[0],
206 b: &inner_args[1],
207 default_literal,
208 }),
209 _ => None,
210 }
211}