Skip to main content

aver/ir/
body.rs

1use crate::ast::{Expr, FnBody, FnDef, Stmt};
2
3use super::{
4    CallLowerCtx, CallPlan, ForwardCallPlan, LeafOp, classify_call_plan,
5    classify_forward_call_plan, classify_leaf_op, classify_match_dispatch_plan,
6};
7
8/// Minimal body-level semantic IR shared across backends.
9///
10/// This first slice is intentionally narrow: it only recognizes single-expression
11/// function bodies and simple binding blocks ending in a tail expression.
12/// That is enough to start driving backend emission from a body plan instead of
13/// re-discovering call/leaf structure inside each emitter.
14#[derive(Debug, Clone, PartialEq)]
15pub enum BodyExprPlan<'a> {
16    Expr(&'a Expr),
17    Leaf(LeafOp<'a>),
18    Call { target: CallPlan, args: &'a [Expr] },
19    ForwardCall(ForwardCallPlan),
20}
21
22#[derive(Debug, Clone, PartialEq)]
23pub struct BodyBindingPlan<'a> {
24    pub name: &'a str,
25    pub expr: BodyExprPlan<'a>,
26}
27
28#[derive(Debug, Clone, PartialEq)]
29pub enum BodyPlan<'a> {
30    SingleExpr(BodyExprPlan<'a>),
31    Block {
32        stmts: &'a [Stmt],
33        bindings: Vec<BodyBindingPlan<'a>>,
34        tail: BodyExprPlan<'a>,
35    },
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum ThinKind {
40    Leaf,
41    Direct,
42    Forward,
43    Dispatch,
44    Tail,
45}
46
47#[derive(Debug, Clone, PartialEq)]
48pub struct ThinBodyPlan<'a> {
49    pub params: &'a [(String, String)],
50    pub body: BodyPlan<'a>,
51    pub kind: ThinKind,
52}
53
54pub trait ThinBodyCtx: CallLowerCtx {
55    fn find_fn_def<'a>(&'a self, name: &str) -> Option<&'a FnDef>;
56}
57
58pub fn classify_body_expr_plan<'a>(expr: &'a Expr, ctx: &impl CallLowerCtx) -> BodyExprPlan<'a> {
59    if let Some(leaf) = classify_leaf_op(expr, ctx) {
60        return BodyExprPlan::Leaf(leaf);
61    }
62
63    if let Some(plan) = classify_forward_call_plan(expr, ctx) {
64        return BodyExprPlan::ForwardCall(plan);
65    }
66
67    if let Expr::FnCall(fn_expr, args) = expr {
68        let target = classify_call_plan(fn_expr, ctx);
69        if !matches!(target, CallPlan::Dynamic) {
70            return BodyExprPlan::Call { target, args };
71        }
72    }
73
74    BodyExprPlan::Expr(expr)
75}
76
77pub fn classify_body_plan<'a>(body: &'a FnBody, ctx: &impl CallLowerCtx) -> Option<BodyPlan<'a>> {
78    let stmts = body.stmts();
79    let (tail_stmt, prefix) = stmts.split_last()?;
80
81    let Stmt::Expr(tail_expr) = tail_stmt else {
82        return None;
83    };
84
85    if prefix.is_empty() {
86        return Some(BodyPlan::SingleExpr(classify_body_expr_plan(
87            tail_expr, ctx,
88        )));
89    }
90
91    let mut bindings = Vec::with_capacity(prefix.len());
92    for stmt in prefix {
93        let Stmt::Binding(name, _type_ann, expr) = stmt else {
94            return None;
95        };
96        bindings.push(BodyBindingPlan {
97            name,
98            expr: classify_body_expr_plan(expr, ctx),
99        });
100    }
101
102    Some(BodyPlan::Block {
103        stmts,
104        bindings,
105        tail: classify_body_expr_plan(tail_expr, ctx),
106    })
107}
108
109pub fn classify_thin_body_plan<'a>(
110    name: &str,
111    ctx: &'a impl ThinBodyCtx,
112) -> Option<ThinBodyPlan<'a>> {
113    let fd = ctx.find_fn_def(name)?;
114    classify_thin_fn_def(fd, ctx)
115}
116
117pub fn classify_thin_fn_def<'a>(
118    fd: &'a FnDef,
119    ctx: &impl CallLowerCtx,
120) -> Option<ThinBodyPlan<'a>> {
121    let body = classify_body_plan(&fd.body, ctx)?;
122    Some(ThinBodyPlan {
123        params: &fd.params,
124        kind: classify_thin_kind(&body, ctx)?,
125        body,
126    })
127}
128
129fn classify_thin_kind(plan: &BodyPlan<'_>, ctx: &impl CallLowerCtx) -> Option<ThinKind> {
130    match plan {
131        BodyPlan::SingleExpr(expr) => classify_thin_expr_kind(expr, ctx),
132        BodyPlan::Block { bindings, tail, .. } => {
133            if bindings
134                .iter()
135                .all(|binding| body_expr_is_thin_binding(&binding.expr))
136            {
137                classify_thin_expr_kind(tail, ctx)
138            } else {
139                None
140            }
141        }
142    }
143}
144
145fn classify_thin_expr_kind(plan: &BodyExprPlan<'_>, ctx: &impl CallLowerCtx) -> Option<ThinKind> {
146    match plan {
147        BodyExprPlan::Leaf(_) => Some(ThinKind::Leaf),
148        BodyExprPlan::Call { .. } => Some(ThinKind::Direct),
149        BodyExprPlan::ForwardCall(_) => Some(ThinKind::Forward),
150        BodyExprPlan::Expr(expr) => match expr {
151            Expr::Match { arms, .. } if classify_match_dispatch_plan(arms, ctx).is_some() => {
152                Some(ThinKind::Dispatch)
153            }
154            Expr::TailCall(_) => Some(ThinKind::Tail),
155            _ => None,
156        },
157    }
158}
159
160fn body_expr_is_thin_binding(plan: &BodyExprPlan<'_>) -> bool {
161    match plan {
162        BodyExprPlan::Leaf(_) | BodyExprPlan::Call { .. } | BodyExprPlan::ForwardCall(_) => true,
163        BodyExprPlan::Expr(expr) => matches!(
164            expr,
165            Expr::Literal(_) | Expr::Ident(_) | Expr::Constructor(_, _)
166        ),
167    }
168}