Skip to main content

logicaffeine_compile/optimize/
bta.rs

1//! Binding-Time Analysis (BTA)
2//!
3//! Classifies every variable and expression as Static (S) — value known at compile
4//! time — or Dynamic (D) — value depends on runtime input. This classification
5//! drives function specialization in the partial evaluator.
6//!
7//! BTA is polyvariant: the same function analyzed at different call sites with
8//! different argument divisions produces different results.
9
10use std::collections::{HashMap, HashSet};
11use std::hash::{Hash, Hasher};
12
13use crate::analysis::DiscoveryPass;
14use crate::arena::Arena;
15use crate::arena_ctx::AstContext;
16use crate::ast::stmt::{BinaryOpKind, Block, Expr, Literal, Stmt, TypeExpr};
17use crate::drs::WorldState;
18use crate::error::ParseError;
19use crate::intern::{Interner, Symbol};
20use crate::lexer::Lexer;
21use crate::parser::Parser;
22
23// =============================================================================
24// Core Types
25// =============================================================================
26
27#[derive(Debug, Clone)]
28pub enum BindingTime {
29    Static(Literal),
30    Dynamic,
31}
32
33impl PartialEq for BindingTime {
34    fn eq(&self, other: &Self) -> bool {
35        match (self, other) {
36            (BindingTime::Static(a), BindingTime::Static(b)) => match (a, b) {
37                (Literal::Float(x), Literal::Float(y)) => x.to_bits() == y.to_bits(),
38                _ => a == b,
39            },
40            (BindingTime::Dynamic, BindingTime::Dynamic) => true,
41            _ => false,
42        }
43    }
44}
45
46impl Eq for BindingTime {}
47
48impl Hash for BindingTime {
49    fn hash<H: Hasher>(&self, state: &mut H) {
50        std::mem::discriminant(self).hash(state);
51        match self {
52            BindingTime::Static(lit) => hash_literal(lit, state),
53            BindingTime::Dynamic => {}
54        }
55    }
56}
57
58fn hash_literal<H: Hasher>(lit: &Literal, state: &mut H) {
59    std::mem::discriminant(lit).hash(state);
60    match lit {
61        Literal::Number(n) => n.hash(state),
62        Literal::Float(f) => f.to_bits().hash(state),
63        Literal::Text(s) => s.hash(state),
64        Literal::Boolean(b) => b.hash(state),
65        Literal::Nothing => {}
66        Literal::Char(c) => c.hash(state),
67        Literal::Duration(d) => d.hash(state),
68        Literal::Date(d) => d.hash(state),
69        Literal::Moment(m) => m.hash(state),
70        Literal::Span { months, days } => {
71            months.hash(state);
72            days.hash(state);
73        }
74        Literal::Time(t) => t.hash(state),
75    }
76}
77
78impl BindingTime {
79    pub fn is_static(&self) -> bool {
80        matches!(self, BindingTime::Static(_))
81    }
82
83    pub fn is_dynamic(&self) -> bool {
84        matches!(self, BindingTime::Dynamic)
85    }
86}
87
88pub type Division = HashMap<Symbol, BindingTime>;
89
90#[derive(Debug, Clone, PartialEq)]
91pub struct BtaResult {
92    pub division: Division,
93    pub return_bt: BindingTime,
94}
95
96pub type BtaCache = HashMap<(Symbol, Vec<BindingTime>), BtaResult>;
97
98// =============================================================================
99// Expression Analysis
100// =============================================================================
101
102pub fn analyze_expr(expr: &Expr, division: &Division) -> BindingTime {
103    match expr {
104        Expr::Literal(lit) => BindingTime::Static(lit.clone()),
105
106        Expr::Identifier(sym) => {
107            division.get(sym).cloned().unwrap_or(BindingTime::Dynamic)
108        }
109
110        Expr::BinaryOp { op, left, right } => {
111            let bt_left = analyze_expr(left, division);
112            let bt_right = analyze_expr(right, division);
113            match (&bt_left, &bt_right) {
114                (BindingTime::Static(l), BindingTime::Static(r)) => {
115                    match eval_literal_binop(*op, l, r) {
116                        Some(result) => BindingTime::Static(result),
117                        None => BindingTime::Dynamic,
118                    }
119                }
120                _ => BindingTime::Dynamic,
121            }
122        }
123
124        Expr::Not { operand } => {
125            match analyze_expr(operand, division) {
126                BindingTime::Static(Literal::Boolean(b)) => {
127                    BindingTime::Static(Literal::Boolean(!b))
128                }
129                _ => BindingTime::Dynamic,
130            }
131        }
132
133        Expr::Length { .. } => BindingTime::Dynamic,
134        Expr::Index { .. } => BindingTime::Dynamic,
135
136        Expr::Call { .. } => BindingTime::Dynamic,
137
138        _ => BindingTime::Dynamic,
139    }
140}
141
142fn eval_literal_binop(op: BinaryOpKind, left: &Literal, right: &Literal) -> Option<Literal> {
143    match (op, left, right) {
144        // Fold only when it fits i64; overflow → None so the exact runtime promotes.
145        (BinaryOpKind::Add, Literal::Number(a), Literal::Number(b)) => {
146            a.checked_add(*b).map(Literal::Number)
147        }
148        (BinaryOpKind::Subtract, Literal::Number(a), Literal::Number(b)) => {
149            a.checked_sub(*b).map(Literal::Number)
150        }
151        (BinaryOpKind::Multiply, Literal::Number(a), Literal::Number(b)) => {
152            a.checked_mul(*b).map(Literal::Number)
153        }
154        (BinaryOpKind::Divide, Literal::Number(a), Literal::Number(b)) if *b != 0 => {
155            a.checked_div(*b).map(Literal::Number)
156        }
157        (BinaryOpKind::Modulo, Literal::Number(a), Literal::Number(b)) if *b != 0 => {
158            a.checked_rem(*b).map(Literal::Number)
159        }
160        (BinaryOpKind::Eq, Literal::Number(a), Literal::Number(b)) => {
161            Some(Literal::Boolean(a == b))
162        }
163        (BinaryOpKind::NotEq, Literal::Number(a), Literal::Number(b)) => {
164            Some(Literal::Boolean(a != b))
165        }
166        (BinaryOpKind::Lt, Literal::Number(a), Literal::Number(b)) => {
167            Some(Literal::Boolean(a < b))
168        }
169        (BinaryOpKind::Gt, Literal::Number(a), Literal::Number(b)) => {
170            Some(Literal::Boolean(a > b))
171        }
172        (BinaryOpKind::LtEq, Literal::Number(a), Literal::Number(b)) => {
173            Some(Literal::Boolean(a <= b))
174        }
175        (BinaryOpKind::GtEq, Literal::Number(a), Literal::Number(b)) => {
176            Some(Literal::Boolean(a >= b))
177        }
178        (BinaryOpKind::Add, Literal::Float(a), Literal::Float(b)) => {
179            Some(Literal::Float(a + b))
180        }
181        (BinaryOpKind::Subtract, Literal::Float(a), Literal::Float(b)) => {
182            Some(Literal::Float(a - b))
183        }
184        (BinaryOpKind::Multiply, Literal::Float(a), Literal::Float(b)) => {
185            Some(Literal::Float(a * b))
186        }
187        (BinaryOpKind::Divide, Literal::Float(a), Literal::Float(b)) if *b != 0.0 => {
188            Some(Literal::Float(a / b))
189        }
190        (BinaryOpKind::And, Literal::Boolean(a), Literal::Boolean(b)) => {
191            Some(Literal::Boolean(*a && *b))
192        }
193        (BinaryOpKind::Or, Literal::Boolean(a), Literal::Boolean(b)) => {
194            Some(Literal::Boolean(*a || *b))
195        }
196        _ => None,
197    }
198}
199
200// =============================================================================
201// Statement / Block Analysis
202// =============================================================================
203
204fn analyze_block<'a>(
205    stmts: &[Stmt<'a>],
206    division: &mut Division,
207    funcs: &HashMap<Symbol, FuncDef<'a>>,
208    cache: &mut BtaCache,
209) -> BindingTime {
210    let mut return_bt = BindingTime::Dynamic;
211    for stmt in stmts {
212        if let Some(bt) = analyze_stmt(stmt, division, funcs, cache) {
213            return_bt = bt;
214        }
215    }
216    return_bt
217}
218
219fn analyze_stmt<'a>(
220    stmt: &Stmt<'a>,
221    division: &mut Division,
222    funcs: &HashMap<Symbol, FuncDef<'a>>,
223    cache: &mut BtaCache,
224) -> Option<BindingTime> {
225    match stmt {
226        Stmt::Let { var, value, .. } => {
227            let bt = analyze_expr(value, division);
228            division.insert(*var, bt);
229            None
230        }
231        Stmt::Set { target, value } => {
232            let bt = analyze_expr(value, division);
233            division.insert(*target, bt);
234            None
235        }
236        Stmt::Return { value } => {
237            let bt = match value {
238                Some(expr) => analyze_expr(expr, division),
239                None => BindingTime::Static(Literal::Nothing),
240            };
241            Some(bt)
242        }
243        Stmt::Show { .. } => None,
244        Stmt::Call { function, args } => {
245            for arg in args {
246                analyze_expr(arg, division);
247            }
248            None
249        }
250        other => {
251            // A variable rebound from a runtime source (channel receive, Mount, console
252            // read, peer message, …) is Dynamic regardless of any prior static binding.
253            for var in effectful_bound_vars(other) {
254                division.insert(var, BindingTime::Dynamic);
255            }
256            None
257        }
258    }
259}
260
261// =============================================================================
262// Function Definition Storage
263// =============================================================================
264
265struct FuncDef<'a> {
266    params: Vec<(Symbol, &'a TypeExpr<'a>)>,
267    body: Block<'a>,
268}
269
270pub fn analyze_function_bt<'a>(
271    func_name: Symbol,
272    params: &[(Symbol, &'a TypeExpr<'a>)],
273    body: &[Stmt<'a>],
274    arg_bts: &[BindingTime],
275    funcs: &HashMap<Symbol, FuncDef<'a>>,
276    cache: &mut BtaCache,
277) -> BtaResult {
278    let cache_key = (func_name, arg_bts.to_vec());
279    if let Some(cached) = cache.get(&cache_key) {
280        return cached.clone();
281    }
282
283    let mut division = Division::new();
284    for (i, (param_sym, _)) in params.iter().enumerate() {
285        if let Some(bt) = arg_bts.get(i) {
286            division.insert(*param_sym, bt.clone());
287        } else {
288            division.insert(*param_sym, BindingTime::Dynamic);
289        }
290    }
291
292    let return_bt = analyze_block(body, &mut division, funcs, cache);
293
294    let result = BtaResult {
295        division,
296        return_bt,
297    };
298    cache.insert(cache_key, result.clone());
299    result
300}
301
302// =============================================================================
303// SCC-Ordered Analysis
304// =============================================================================
305
306pub fn analyze_with_sccs<'a>(
307    stmts: &[Stmt<'a>],
308    interner: &Interner,
309) -> BtaCache {
310    use crate::analysis::callgraph::CallGraph;
311
312    let cg = CallGraph::build(stmts, interner);
313
314    let mut funcs: HashMap<Symbol, FuncDef<'a>> = HashMap::new();
315    for stmt in stmts {
316        if let Stmt::FunctionDef { name, params, body, is_native: false, .. } = stmt {
317            funcs.insert(*name, FuncDef { params: params.clone(), body });
318        }
319    }
320
321    let mut cache = BtaCache::new();
322
323    // Process SCCs in topological order (sccs are already in reverse topo order from Kosaraju)
324    for scc in cg.sccs.iter().rev() {
325        if scc.len() == 1 {
326            // Non-recursive (or self-recursive): analyze once
327            let sym = scc[0];
328            if let Some(func_def) = funcs.get(&sym) {
329                let arg_bts: Vec<BindingTime> = func_def.params.iter()
330                    .map(|_| BindingTime::Dynamic)
331                    .collect();
332                analyze_function_bt(sym, &func_def.params, func_def.body, &arg_bts, &funcs, &mut cache);
333            }
334        } else {
335            // Mutually recursive SCC: iterate to fixed point
336            for _iteration in 0..10 {
337                let mut changed = false;
338                for &sym in scc {
339                    if let Some(func_def) = funcs.get(&sym) {
340                        let arg_bts: Vec<BindingTime> = func_def.params.iter()
341                            .map(|_| BindingTime::Dynamic)
342                            .collect();
343                        let key = (sym, arg_bts.clone());
344                        let old = cache.get(&key).cloned();
345                        let result = analyze_function_bt(sym, &func_def.params, func_def.body, &arg_bts, &funcs, &mut cache);
346                        if old.as_ref() != Some(&result) {
347                            changed = true;
348                        }
349                    }
350                }
351                if !changed {
352                    break;
353                }
354            }
355        }
356    }
357
358    cache
359}
360
361// =============================================================================
362// Test-Friendly Wrapper
363// =============================================================================
364
365pub struct BtaEnv {
366    interner: Interner,
367    main_stmts: Vec<BtaStmt>,
368    functions: Vec<BtaFunc>,
369}
370
371#[derive(Clone)]
372struct BtaStmt {
373    kind: BtaStmtKind,
374}
375
376#[derive(Clone)]
377enum BtaStmtKind {
378    Let { var: Symbol, value: BtaExpr },
379    Set { target: Symbol, value: BtaExpr },
380    Return { value: Option<BtaExpr> },
381    Show,
382    Call { function: Symbol, args: Vec<BtaExpr> },
383    If { cond: BtaExpr, then_block: Vec<BtaStmt>, else_block: Option<Vec<BtaStmt>> },
384    While { cond: BtaExpr, body: Vec<BtaStmt> },
385    /// A statement that binds one or more variables to a *runtime* value (a channel
386    /// receive, Mount, console read, peer message, …). Each named variable becomes
387    /// Dynamic, overwriting any prior static binding.
388    ClobberDynamic { vars: Vec<Symbol> },
389    Other,
390}
391
392#[derive(Clone)]
393enum BtaExpr {
394    Literal(Literal),
395    Identifier(Symbol),
396    BinaryOp { op: BinaryOpKind, left: Box<BtaExpr>, right: Box<BtaExpr> },
397    Not { operand: Box<BtaExpr> },
398    Length { collection: Box<BtaExpr> },
399    Index { collection: Box<BtaExpr>, index: Box<BtaExpr> },
400    Call { function: Symbol, args: Vec<BtaExpr> },
401    Other,
402}
403
404#[derive(Clone)]
405struct BtaFunc {
406    name: Symbol,
407    params: Vec<(Symbol, bool)>, // (name, is_collection_type)
408    body: Vec<BtaStmt>,
409}
410
411struct BtaContext {
412    cache: BtaCache,
413    on_stack: HashSet<(Symbol, Vec<BindingTime>)>,
414}
415
416fn convert_expr(expr: &Expr) -> BtaExpr {
417    match expr {
418        Expr::Literal(lit) => BtaExpr::Literal(lit.clone()),
419        Expr::Identifier(sym) => BtaExpr::Identifier(*sym),
420        Expr::BinaryOp { op, left, right } => BtaExpr::BinaryOp {
421            op: *op,
422            left: Box::new(convert_expr(left)),
423            right: Box::new(convert_expr(right)),
424        },
425        Expr::Not { operand } => BtaExpr::Not {
426            operand: Box::new(convert_expr(operand)),
427        },
428        Expr::Length { collection } => BtaExpr::Length {
429            collection: Box::new(convert_expr(collection)),
430        },
431        Expr::Index { collection, index } => BtaExpr::Index {
432            collection: Box::new(convert_expr(collection)),
433            index: Box::new(convert_expr(index)),
434        },
435        Expr::Call { function, args } => BtaExpr::Call {
436            function: *function,
437            args: args.iter().map(|a| convert_expr(a)).collect(),
438        },
439        _ => BtaExpr::Other,
440    }
441}
442
443/// The variables a statement binds to a runtime value. These must be clobbered to
444/// Dynamic in any binding-time division — a prior `Static` binding is stale once the
445/// variable is rebound from a channel, the filesystem, the console, or a peer.
446fn effectful_bound_vars(stmt: &Stmt) -> Vec<Symbol> {
447    match stmt {
448        Stmt::ReceivePipe { var, .. }
449        | Stmt::TryReceivePipe { var, .. }
450        | Stmt::ReadFrom { var, .. }
451        | Stmt::Mount { var, .. }
452        | Stmt::CreatePipe { var, .. }
453        | Stmt::LetPeerAgent { var, .. } => vec![*var],
454        Stmt::AwaitMessage { into, .. } => vec![*into],
455        Stmt::LaunchTaskWithHandle { handle, .. } => vec![*handle],
456        Stmt::Spawn { name, .. } => vec![*name],
457        _ => Vec::new(),
458    }
459}
460
461fn convert_stmt(stmt: &Stmt) -> BtaStmt {
462    let clobbered = effectful_bound_vars(stmt);
463    if !clobbered.is_empty() {
464        return BtaStmt { kind: BtaStmtKind::ClobberDynamic { vars: clobbered } };
465    }
466    let kind = match stmt {
467        Stmt::Let { var, value, .. } => BtaStmtKind::Let {
468            var: *var,
469            value: convert_expr(value),
470        },
471        Stmt::Set { target, value } => BtaStmtKind::Set {
472            target: *target,
473            value: convert_expr(value),
474        },
475        Stmt::Return { value } => BtaStmtKind::Return {
476            value: value.map(|v| convert_expr(v)),
477        },
478        Stmt::Show { .. } => BtaStmtKind::Show,
479        Stmt::Call { function, args } => BtaStmtKind::Call {
480            function: *function,
481            args: args.iter().map(|a| convert_expr(a)).collect(),
482        },
483        Stmt::If { cond, then_block, else_block } => BtaStmtKind::If {
484            cond: convert_expr(cond),
485            then_block: then_block.iter().map(|s| convert_stmt(s)).collect(),
486            else_block: else_block.map(|eb| eb.iter().map(|s| convert_stmt(s)).collect()),
487        },
488        Stmt::While { cond, body, .. } => BtaStmtKind::While {
489            cond: convert_expr(cond),
490            body: body.iter().map(|s| convert_stmt(s)).collect(),
491        },
492        _ => BtaStmtKind::Other,
493    };
494    BtaStmt { kind }
495}
496
497fn convert_block(block: &[Stmt]) -> Vec<BtaStmt> {
498    block.iter().map(|s| convert_stmt(s)).collect()
499}
500
501fn is_collection_type(ty: &TypeExpr) -> bool {
502    match ty {
503        TypeExpr::Generic { .. } => true,
504        _ => false,
505    }
506}
507
508fn analyze_bta_expr(
509    expr: &BtaExpr,
510    division: &Division,
511    functions: &[BtaFunc],
512    ctx: &mut BtaContext,
513) -> BindingTime {
514    match expr {
515        BtaExpr::Literal(lit) => BindingTime::Static(lit.clone()),
516        BtaExpr::Identifier(sym) => {
517            division.get(sym).cloned().unwrap_or(BindingTime::Dynamic)
518        }
519        BtaExpr::BinaryOp { op, left, right } => {
520            let bt_left = analyze_bta_expr(left, division, functions, ctx);
521            let bt_right = analyze_bta_expr(right, division, functions, ctx);
522            match (&bt_left, &bt_right) {
523                (BindingTime::Static(l), BindingTime::Static(r)) => {
524                    match eval_literal_binop(*op, l, r) {
525                        Some(result) => BindingTime::Static(result),
526                        None => BindingTime::Dynamic,
527                    }
528                }
529                _ => BindingTime::Dynamic,
530            }
531        }
532        BtaExpr::Not { operand } => {
533            match analyze_bta_expr(operand, division, functions, ctx) {
534                BindingTime::Static(Literal::Boolean(b)) => {
535                    BindingTime::Static(Literal::Boolean(!b))
536                }
537                _ => BindingTime::Dynamic,
538            }
539        }
540        BtaExpr::Length { .. } => BindingTime::Dynamic,
541        BtaExpr::Index { .. } => BindingTime::Dynamic,
542        BtaExpr::Call { function, args } => {
543            let arg_bts: Vec<BindingTime> = args.iter()
544                .map(|a| analyze_bta_expr(a, division, functions, ctx))
545                .collect();
546
547            let cache_key = (*function, arg_bts.clone());
548
549            if let Some(cached) = ctx.cache.get(&cache_key) {
550                return cached.return_bt.clone();
551            }
552
553            if ctx.on_stack.contains(&cache_key) {
554                return BindingTime::Dynamic;
555            }
556
557            let func = match functions.iter().find(|f| f.name == *function) {
558                Some(f) => f.clone(),
559                None => return BindingTime::Dynamic,
560            };
561
562            ctx.on_stack.insert(cache_key.clone());
563
564            let mut func_div = Division::new();
565            for (i, (param_sym, is_collection)) in func.params.iter().enumerate() {
566                if *is_collection {
567                    func_div.insert(*param_sym, BindingTime::Dynamic);
568                } else if let Some(bt) = arg_bts.get(i) {
569                    func_div.insert(*param_sym, bt.clone());
570                } else {
571                    func_div.insert(*param_sym, BindingTime::Dynamic);
572                }
573            }
574
575            let return_bt = analyze_bta_block(&func.body, &mut func_div, functions, ctx);
576
577            ctx.on_stack.remove(&cache_key);
578
579            let result = BtaResult {
580                division: func_div,
581                return_bt: return_bt.clone(),
582            };
583            ctx.cache.insert(cache_key, result);
584
585            return_bt
586        }
587        BtaExpr::Other => BindingTime::Dynamic,
588    }
589}
590
591fn analyze_bta_block(
592    stmts: &[BtaStmt],
593    division: &mut Division,
594    functions: &[BtaFunc],
595    ctx: &mut BtaContext,
596) -> BindingTime {
597    analyze_bta_block_opt(stmts, division, functions, ctx)
598        .unwrap_or(BindingTime::Dynamic)
599}
600
601fn analyze_bta_block_opt(
602    stmts: &[BtaStmt],
603    division: &mut Division,
604    functions: &[BtaFunc],
605    ctx: &mut BtaContext,
606) -> Option<BindingTime> {
607    for stmt in stmts {
608        if let Some(bt) = analyze_bta_stmt(stmt, division, functions, ctx) {
609            return Some(bt);
610        }
611    }
612    None
613}
614
615fn join_bt(a: &BindingTime, b: &BindingTime) -> BindingTime {
616    match (a, b) {
617        (BindingTime::Static(l1), BindingTime::Static(l2)) => {
618            if l1 == l2 {
619                a.clone()
620            } else {
621                BindingTime::Dynamic
622            }
623        }
624        _ => BindingTime::Dynamic,
625    }
626}
627
628fn join_divisions(a: &Division, b: &Division) -> Division {
629    let mut joined = Division::new();
630    for (sym, bt_a) in a {
631        if let Some(bt_b) = b.get(sym) {
632            joined.insert(*sym, join_bt(bt_a, bt_b));
633        }
634    }
635    // Variables only in one branch get their value from that branch,
636    // but since the other branch doesn't define them, treat as Dynamic
637    for (sym, _) in b {
638        if !a.contains_key(sym) {
639            joined.insert(*sym, BindingTime::Dynamic);
640        }
641    }
642    for (sym, _) in a {
643        if !b.contains_key(sym) {
644            joined.insert(*sym, BindingTime::Dynamic);
645        }
646    }
647    joined
648}
649
650fn analyze_bta_stmt(
651    stmt: &BtaStmt,
652    division: &mut Division,
653    functions: &[BtaFunc],
654    ctx: &mut BtaContext,
655) -> Option<BindingTime> {
656    match &stmt.kind {
657        BtaStmtKind::Let { var, value } => {
658            let bt = analyze_bta_expr(value, division, functions, ctx);
659            division.insert(*var, bt);
660            None
661        }
662        BtaStmtKind::Set { target, value } => {
663            let bt = analyze_bta_expr(value, division, functions, ctx);
664            division.insert(*target, bt);
665            None
666        }
667        BtaStmtKind::Return { value } => {
668            let bt = match value {
669                Some(expr) => analyze_bta_expr(expr, division, functions, ctx),
670                None => BindingTime::Static(Literal::Nothing),
671            };
672            Some(bt)
673        }
674        BtaStmtKind::Show => None,
675        BtaStmtKind::Call { .. } => None,
676        BtaStmtKind::If { cond, then_block, else_block } => {
677            let cond_bt = analyze_bta_expr(cond, division, functions, ctx);
678            match cond_bt {
679                BindingTime::Static(Literal::Boolean(true)) => {
680                    analyze_bta_block_opt(then_block, division, functions, ctx)
681                }
682                BindingTime::Static(Literal::Boolean(false)) => {
683                    if let Some(else_b) = else_block {
684                        analyze_bta_block_opt(else_b, division, functions, ctx)
685                    } else {
686                        None
687                    }
688                }
689                _ => {
690                    let snapshot = division.clone();
691
692                    let then_ret = analyze_bta_block_opt(then_block, division, functions, ctx);
693                    let then_div = division.clone();
694
695                    *division = snapshot;
696                    let else_ret = if let Some(else_b) = else_block {
697                        analyze_bta_block_opt(else_b, division, functions, ctx)
698                    } else {
699                        None
700                    };
701                    let else_div = division.clone();
702
703                    *division = join_divisions(&then_div, &else_div);
704                    match (then_ret, else_ret) {
705                        (Some(t), Some(e)) => Some(join_bt(&t, &e)),
706                        _ => None,
707                    }
708                }
709            }
710        }
711        BtaStmtKind::While { cond, body } => {
712            let max_iterations = 256;
713            for _ in 0..max_iterations {
714                let cond_bt = analyze_bta_expr(cond, division, functions, ctx);
715
716                match cond_bt {
717                    BindingTime::Static(Literal::Boolean(false)) => break,
718                    BindingTime::Static(Literal::Boolean(true)) => {
719                        // Static true: unroll this iteration directly
720                        analyze_bta_block(body, division, functions, ctx);
721                    }
722                    _ => {
723                        // Dynamic condition: fixed-point with join
724                        let snapshot = division.clone();
725                        let mut body_div = snapshot.clone();
726                        analyze_bta_block(body, &mut body_div, functions, ctx);
727
728                        let joined = join_divisions(&snapshot, &body_div);
729                        if joined == *division {
730                            break;
731                        }
732                        *division = joined;
733                    }
734                }
735            }
736            None
737        }
738        BtaStmtKind::ClobberDynamic { vars } => {
739            for var in vars {
740                division.insert(*var, BindingTime::Dynamic);
741            }
742            None
743        }
744        BtaStmtKind::Other => None,
745    }
746}
747
748impl BtaEnv {
749    pub fn analyze_source(source: &str) -> Result<Self, ParseError> {
750        let mut interner = Interner::new();
751        let mut lexer = Lexer::new(source, &mut interner);
752        let tokens = lexer.tokenize();
753
754        let type_registry = {
755            let mut discovery = DiscoveryPass::new(&tokens, &mut interner);
756            let result = discovery.run_full();
757            result.types
758        };
759
760        let mut world_state = WorldState::new();
761        let expr_arena = Arena::new();
762        let term_arena = Arena::new();
763        let np_arena = Arena::new();
764        let sym_arena = Arena::new();
765        let role_arena = Arena::new();
766        let pp_arena = Arena::new();
767        let stmt_arena = Arena::new();
768        let imperative_expr_arena = Arena::new();
769        let type_expr_arena = Arena::new();
770
771        let ast_ctx = AstContext::with_types(
772            &expr_arena,
773            &term_arena,
774            &np_arena,
775            &sym_arena,
776            &role_arena,
777            &pp_arena,
778            &stmt_arena,
779            &imperative_expr_arena,
780            &type_expr_arena,
781        );
782
783        let mut parser = Parser::new(tokens, &mut world_state, &mut interner, ast_ctx, type_registry);
784        let stmts = parser.parse_program()?;
785
786        let mut main_stmts = Vec::new();
787        let mut functions = Vec::new();
788
789        for stmt in &stmts {
790            match stmt {
791                Stmt::FunctionDef { name, params, body, is_native, .. } => {
792                    if !is_native {
793                        let param_info: Vec<(Symbol, bool)> = params.iter().map(|(sym, ty)| {
794                            (*sym, is_collection_type(ty))
795                        }).collect();
796                        functions.push(BtaFunc {
797                            name: *name,
798                            params: param_info,
799                            body: convert_block(body),
800                        });
801                    }
802                }
803                _ => {
804                    main_stmts.push(convert_stmt(stmt));
805                }
806            }
807        }
808
809        Ok(BtaEnv {
810            interner,
811            main_stmts,
812            functions,
813        })
814    }
815
816    pub fn analyze_main(&mut self) -> BtaResult {
817        let mut division = Division::new();
818        let mut ctx = BtaContext {
819            cache: BtaCache::new(),
820            on_stack: HashSet::new(),
821        };
822        let return_bt = analyze_bta_block(&self.main_stmts, &mut division, &self.functions, &mut ctx);
823        BtaResult { division, return_bt }
824    }
825
826    pub fn analyze_function(&mut self, func_name: &str, arg_bts: Vec<BindingTime>) -> BtaResult {
827        let func_sym = self.interner.lookup(func_name)
828            .unwrap_or_else(|| panic!("Function '{}' not found in interner", func_name));
829
830        let func = self.functions.iter()
831            .find(|f| f.name == func_sym)
832            .unwrap_or_else(|| panic!("Function '{}' not found", func_name))
833            .clone();
834
835        let mut division = Division::new();
836        for (i, (param_sym, is_collection)) in func.params.iter().enumerate() {
837            if *is_collection {
838                division.insert(*param_sym, BindingTime::Dynamic);
839            } else if let Some(bt) = arg_bts.get(i) {
840                division.insert(*param_sym, bt.clone());
841            } else {
842                division.insert(*param_sym, BindingTime::Dynamic);
843            }
844        }
845
846        let mut ctx = BtaContext {
847            cache: BtaCache::new(),
848            on_stack: HashSet::new(),
849        };
850        let return_bt = analyze_bta_block(&func.body, &mut division, &self.functions, &mut ctx);
851        BtaResult { division, return_bt }
852    }
853
854    pub fn lookup(&self, name: &str) -> Option<Symbol> {
855        self.interner.lookup(name)
856    }
857}