use crate::ast::stmt::{BinaryOpKind, Expr, Stmt, TypeExpr};
use crate::intern::{Interner, Symbol};
pub(crate) struct EntryGuard {
pub arr: Symbol,
pub lo: Symbol,
pub hi: Symbol,
}
pub(crate) fn detect_entry_guard(
params: &[(Symbol, &TypeExpr)],
body: &[Stmt],
interner: &Interner,
) -> Option<EntryGuard> {
let arr = params
.iter()
.find_map(|(s, ty)| is_seq_type(ty, interner).then_some(*s))?;
let param_is = |s: Symbol| params.iter().any(|(p, _)| *p == s);
let (lo, hi) = find_partition_bounds(body, ¶m_is)?;
if lo == hi {
return None;
}
if !body_indexes(body) || !is_pure(body) {
return None;
}
Some(EntryGuard { arr, lo, hi })
}
fn is_seq_type(ty: &TypeExpr, interner: &Interner) -> bool {
matches!(ty, TypeExpr::Generic { base, .. } if {
matches!(interner.resolve(*base), "Seq" | "List" | "Vec")
})
}
fn find_partition_bounds(
body: &[Stmt],
param_is: &dyn Fn(Symbol) -> bool,
) -> Option<(Symbol, Symbol)> {
let mut counter_init: Vec<(Symbol, Symbol)> = Vec::new();
collect_counter_inits(body, param_is, &mut counter_init);
find_loop_bound(body, &counter_init, param_is)
}
fn collect_counter_inits(
stmts: &[Stmt],
param_is: &dyn Fn(Symbol) -> bool,
out: &mut Vec<(Symbol, Symbol)>,
) {
for s in stmts {
match s {
Stmt::Let { var, value: Expr::Identifier(src), .. } if param_is(*src) => {
out.push((*var, *src));
}
Stmt::If { then_block, else_block, .. } => {
collect_counter_inits(then_block, param_is, out);
if let Some(e) = else_block {
collect_counter_inits(e, param_is, out);
}
}
Stmt::While { body, .. } | Stmt::Repeat { body, .. } => {
collect_counter_inits(body, param_is, out)
}
_ => {}
}
}
}
fn find_loop_bound(
stmts: &[Stmt],
counter_init: &[(Symbol, Symbol)],
param_is: &dyn Fn(Symbol) -> bool,
) -> Option<(Symbol, Symbol)> {
for s in stmts {
match s {
Stmt::While { cond, body, .. } => {
if let Expr::BinaryOp {
op: BinaryOpKind::Lt | BinaryOpKind::LtEq,
left: Expr::Identifier(c),
right: Expr::Identifier(hi),
} = cond
{
if param_is(*hi) {
if let Some((_, lo)) = counter_init.iter().find(|(cc, _)| cc == c) {
return Some((*lo, *hi));
}
}
}
if let Some(r) = find_loop_bound(body, counter_init, param_is) {
return Some(r);
}
}
Stmt::If { then_block, else_block, .. } => {
if let Some(r) = find_loop_bound(then_block, counter_init, param_is) {
return Some(r);
}
if let Some(e) = else_block {
if let Some(r) = find_loop_bound(e, counter_init, param_is) {
return Some(r);
}
}
}
Stmt::Repeat { body, .. } => {
if let Some(r) = find_loop_bound(body, counter_init, param_is) {
return Some(r);
}
}
_ => {}
}
}
None
}
fn body_indexes(stmts: &[Stmt]) -> bool {
stmts.iter().any(stmt_indexes)
}
fn stmt_indexes(s: &Stmt) -> bool {
match s {
Stmt::SetIndex { .. } => true,
Stmt::Let { value, .. } | Stmt::Set { value, .. } => expr_indexes(value),
Stmt::Return { value: Some(e) } => expr_indexes(e),
Stmt::If { cond, then_block, else_block, .. } => {
expr_indexes(cond)
|| body_indexes(then_block)
|| else_block.as_ref().is_some_and(|e| body_indexes(e))
}
Stmt::While { cond, body, .. } => expr_indexes(cond) || body_indexes(body),
Stmt::Repeat { body, .. } => body_indexes(body),
Stmt::Push { value, .. } | Stmt::Add { value, .. } => expr_indexes(value),
_ => false,
}
}
fn expr_indexes(e: &Expr) -> bool {
match e {
Expr::Index { .. } => true,
Expr::BinaryOp { left, right, .. } => expr_indexes(left) || expr_indexes(right),
Expr::Not { operand } => expr_indexes(operand),
Expr::Call { args, .. } => args.iter().any(|a| expr_indexes(a)),
Expr::Length { collection } => expr_indexes(collection),
_ => false,
}
}
fn is_pure(stmts: &[Stmt]) -> bool {
stmts.iter().all(stmt_is_pure)
}
fn stmt_is_pure(s: &Stmt) -> bool {
match s {
Stmt::Show { .. }
| Stmt::Give { .. }
| Stmt::WriteFile { .. }
| Stmt::ReadFrom { .. }
| Stmt::Sleep { .. }
| Stmt::Listen { .. }
| Stmt::ConnectTo { .. }
| Stmt::SendMessage { .. }
| Stmt::StreamMessage { .. }
| Stmt::AwaitMessage { .. }
| Stmt::Spawn { .. }
| Stmt::Inspect { .. }
| Stmt::Mount { .. }
| Stmt::LaunchTask { .. }
| Stmt::LaunchTaskWithHandle { .. }
| Stmt::CreatePipe { .. }
| Stmt::SendPipe { .. }
| Stmt::ReceivePipe { .. }
| Stmt::TrySendPipe { .. }
| Stmt::TryReceivePipe { .. } => false,
Stmt::If { then_block, else_block, .. } => {
is_pure(then_block) && else_block.as_ref().map_or(true, |e| is_pure(e))
}
Stmt::While { body, .. } | Stmt::Repeat { body, .. } => is_pure(body),
_ => true,
}
}