Skip to main content

aver/ir/
leaf.rs

1use crate::ast::{Expr, Literal};
2
3use super::{CallLowerCtx, CallPlan, classify_call_plan, expr_to_dotted_name};
4
5/// Small expression-shaped leaf operations shared across backends.
6///
7/// These are still semantic plans, not backend instructions: they recognize
8/// common Aver expression shapes whose meaning is clearer than the raw AST.
9#[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    /// Fused `Result.withDefault(Int.mod(a, b), literal)` → skip Result allocation.
39    IntModOrDefaultLiteral {
40        a: &'a Expr,
41        b: &'a Expr,
42        default_literal: &'a Literal,
43    },
44    /// Fused `Vector.get(Vector.fromList(list), index)` → skip AverVector allocation.
45    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    // Uppercase dotted paths are static module/type/builtin references, not
65    // runtime record field access.
66    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    // Match Vector.get(Vector.fromList(list), index)
171    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}