use crate::ast::stmt::{BinaryOpKind, Expr, Literal, Stmt};
use logicaffeine_base::intern::{Interner, Symbol};
use std::collections::{HashMap, HashSet};
#[derive(Clone, Debug)]
pub(crate) struct WorklistInfo {
pub tail_var: String,
pub capacity: String,
pub elem_default: String,
}
pub(crate) fn detect_worklists(
body: &[Stmt],
de_rc: &HashSet<Symbol>,
interner: &Interner,
) -> HashMap<Symbol, WorklistInfo> {
let mut out = HashMap::new();
for stmt in body {
let Stmt::Let { var, value, .. } = stmt else { continue };
if !is_new_empty_int_seq(value) {
continue;
}
let q = *var;
if !de_rc.contains(&q) {
continue;
}
if let Some(info) = analyze_worklist(q, body, interner) {
out.insert(q, info);
}
}
out
}
pub(crate) fn is_new_empty_int_seq(value: &Expr) -> bool {
matches!(value, Expr::New { type_name, type_args, init_fields }
if init_fields.is_empty()
&& is_seq_name(*type_name)
&& type_args.len() == 1
&& is_int_type(&type_args[0]))
}
fn is_seq_name(_s: Symbol) -> bool {
true
}
fn is_int_type(t: &crate::ast::stmt::TypeExpr) -> bool {
use crate::ast::stmt::TypeExpr;
matches!(t, TypeExpr::Primitive(_) | TypeExpr::Named(_))
}
fn analyze_worklist(q: Symbol, body: &[Stmt], interner: &Interner) -> Option<WorklistInfo> {
let mut refs = QRefs::default();
if !walk(body, q, &mut Vec::new(), false, &mut refs) {
return None;
}
if refs.disqualified || refs.pushes.is_empty() {
return None;
}
let mut seed_count: i64 = 0;
let mut visited: Option<Symbol> = None;
let mut sentinel: Option<i64> = None;
for p in &refs.pushes {
match &p.guard {
None => seed_count += 1,
Some(g) => {
if visited.get_or_insert(g.array) != &g.array {
return None;
}
if sentinel.get_or_insert(g.sentinel) != &g.sentinel {
return None;
}
}
}
}
let visited = visited?;
let sentinel = sentinel?;
let size = visited_set_size(body, visited, sentinel, interner)?;
let capacity = format!("(({size} as i64).max(0) as usize) + {seed_count}");
let tail_var = format!("__{}_tail", interner.resolve(q));
Some(WorklistInfo { tail_var, capacity, elem_default: "0i64".to_string() })
}
#[derive(Clone)]
struct Guard {
array: Symbol,
sentinel: i64,
}
struct PushRef {
guard: Option<Guard>,
}
#[derive(Default)]
struct QRefs {
pushes: Vec<PushRef>,
disqualified: bool,
}
fn walk(stmts: &[Stmt], q: Symbol, guards: &mut Vec<Guard>, in_loop: bool, refs: &mut QRefs) -> bool {
for s in stmts {
if !walk_stmt(s, q, guards, in_loop, refs) {
refs.disqualified = true;
return false;
}
}
true
}
fn walk_stmt(s: &Stmt, q: Symbol, guards: &mut Vec<Guard>, in_loop: bool, refs: &mut QRefs) -> bool {
match s {
Stmt::Push { collection, value } => {
if expr_uses(value, q) {
return false;
}
if let Expr::Identifier(c) = collection {
if *c == q {
let guard = guards.last().cloned();
if guard.is_none() && in_loop {
return false;
}
refs.pushes.push(PushRef { guard });
return true;
}
}
!expr_uses(collection, q)
}
Stmt::Let { value, .. } => ok_read_only(value, q),
Stmt::Set { target: _, value } => ok_read_only(value, q),
Stmt::SetIndex { collection, index, value } => {
if names_collection(collection, q) {
return false;
}
ok_read_only(index, q) && ok_read_only(value, q)
}
Stmt::SetField { object, value, .. } => ok_read_only(object, q) && ok_read_only(value, q),
Stmt::If { cond, then_block, else_block } => {
if expr_uses(cond, q) {
return false; }
let g = visited_guard(cond);
if let Some(g) = g {
guards.push(g);
}
let ok_then = walk(then_block, q, guards, in_loop, refs);
if visited_guard(cond).is_some() {
guards.pop();
}
let ok_else = match else_block {
Some(eb) => walk(eb, q, guards, in_loop, refs),
None => true,
};
ok_then && ok_else
}
Stmt::While { cond, body, .. } => {
if expr_uses_outside_length(cond, q) {
return false;
}
let mut inner = guards.clone();
walk(body, q, &mut inner, true, refs)
}
Stmt::Repeat { body, .. } => walk(body, q, &mut guards.clone(), true, refs),
Stmt::Return { value: Some(v) } => ok_read_only(v, q),
Stmt::Return { value: None } => true,
Stmt::Show { object, recipient } | Stmt::Give { object, recipient } => {
ok_read_only(object, q) && ok_read_only(recipient, q)
}
Stmt::Call { args, .. } => args.iter().all(|a| ok_read_only(a, q)),
Stmt::RuntimeAssert { condition, .. } => ok_read_only(condition, q),
Stmt::Assert { .. } | Stmt::Trust { .. } | Stmt::Break => true,
Stmt::Inspect { target, .. } => ok_read_only(target, q),
Stmt::Pop { collection, .. }
| Stmt::Remove { collection, .. }
| Stmt::Add { collection, .. } => !names_collection(collection, q),
_ => !stmt_mentions(s, q),
}
}
pub(crate) fn ok_read_only(e: &Expr, q: Symbol) -> bool {
match e {
Expr::Identifier(s) => *s != q, Expr::Index { collection, index } => {
let coll_ok = match &**collection {
Expr::Identifier(s) if *s == q => true,
other => ok_read_only(other, q),
};
coll_ok && ok_read_only(index, q)
}
Expr::Length { collection } => match &**collection {
Expr::Identifier(s) if *s == q => true,
other => ok_read_only(other, q),
},
Expr::BinaryOp { left, right, .. } => ok_read_only(left, q) && ok_read_only(right, q),
Expr::Not { operand } => ok_read_only(operand, q),
Expr::Call { args, .. } => args.iter().all(|a| ok_read_only(a, q)),
Expr::CallExpr { callee, args } => ok_read_only(callee, q) && args.iter().all(|a| ok_read_only(a, q)),
Expr::Copy { expr } | Expr::Give { value: expr } => ok_read_only(expr, q),
Expr::Contains { collection, value } => !names_collection(collection, q) && ok_read_only(value, q),
Expr::Slice { collection, start, end } => {
!names_collection(collection, q) && ok_read_only(start, q) && ok_read_only(end, q)
}
Expr::Range { start, end } => ok_read_only(start, q) && ok_read_only(end, q),
Expr::Union { left, right } | Expr::Intersection { left, right } => {
ok_read_only(left, q) && ok_read_only(right, q)
}
Expr::List(items) | Expr::Tuple(items) => items.iter().all(|i| ok_read_only(i, q)),
Expr::FieldAccess { object, .. } => ok_read_only(object, q),
Expr::OptionSome { value } => ok_read_only(value, q),
Expr::InterpolatedString(parts) => parts.iter().all(|p| match p {
crate::ast::stmt::StringPart::Expr { value, .. } => ok_read_only(value, q),
_ => true,
}),
_ => !expr_uses(e, q),
}
}
pub(crate) fn names_collection(e: &Expr, q: Symbol) -> bool {
matches!(e, Expr::Identifier(s) if *s == q)
}
pub(crate) fn const_eval(e: &Expr) -> Option<i64> {
match e {
Expr::Literal(Literal::Number(k)) => Some(*k),
Expr::BinaryOp { op, left, right } => {
let (a, b) = (const_eval(left)?, const_eval(right)?);
match op {
BinaryOpKind::Add => a.checked_add(b),
BinaryOpKind::Subtract => a.checked_sub(b),
BinaryOpKind::Multiply => a.checked_mul(b),
_ => None,
}
}
_ => None,
}
}
fn visited_guard(cond: &Expr) -> Option<Guard> {
let Expr::BinaryOp { op: BinaryOpKind::Eq, left, right } = cond else { return None };
let (idx_side, sentinel) = match (const_eval(left), const_eval(right)) {
(None, Some(s)) => (&**left, s),
(Some(s), None) => (&**right, s),
_ => return None,
};
let Expr::Index { collection, .. } = idx_side else { return None };
let Expr::Identifier(array) = &**collection else { return None };
Some(Guard { array: *array, sentinel })
}
fn visited_set_size(
body: &[Stmt],
m: Symbol,
sentinel: i64,
interner: &Interner,
) -> Option<String> {
if !m_monotone(body, m, sentinel) {
return None;
}
find_sentinel_build_size(body, m, sentinel, interner)
}
fn m_monotone(stmts: &[Stmt], m: Symbol, sentinel: i64) -> bool {
fn rec(stmts: &[Stmt], m: Symbol, sentinel: i64, ok: &mut bool) {
for s in stmts {
match s {
Stmt::SetIndex { collection, value, .. } if names_collection(collection, m) => {
if !is_above_sentinel(value, m, sentinel) {
*ok = false;
}
}
Stmt::Push { collection, value } if names_collection(collection, m) => {
if !is_const_eq(value, sentinel) && !is_above_sentinel(value, m, sentinel) {
*ok = false;
}
}
Stmt::Set { target, .. } if *target == m => *ok = false, Stmt::Pop { collection, .. } | Stmt::Remove { collection, .. }
if names_collection(collection, m) => *ok = false,
Stmt::Call { args, .. } => {
if args.iter().any(|a| matches!(a, Expr::Identifier(s) if *s == m)) {
*ok = false; }
}
Stmt::If { then_block, else_block, .. } => {
rec(then_block, m, sentinel, ok);
if let Some(eb) = else_block {
rec(eb, m, sentinel, ok);
}
}
Stmt::While { body, .. } | Stmt::Repeat { body, .. } => rec(body, m, sentinel, ok),
_ => {}
}
}
}
let mut ok = true;
rec(stmts, m, sentinel, &mut ok);
ok
}
fn is_above_sentinel(value: &Expr, m: Symbol, sentinel: i64) -> bool {
if let Some(k) = const_eval(value) {
return k > sentinel;
}
if let Expr::BinaryOp { op: BinaryOpKind::Add, left, right } = value {
let reads_m = |e: &Expr| matches!(e, Expr::Index { collection, .. } if names_collection(collection, m));
let pos = |e: &Expr| matches!(const_eval(e), Some(c) if c > 0);
return (reads_m(left) && pos(right)) || (pos(left) && reads_m(right));
}
false
}
fn is_const_eq(value: &Expr, sentinel: i64) -> bool {
const_eval(value) == Some(sentinel)
}
fn find_sentinel_build_size(
stmts: &[Stmt],
m: Symbol,
sentinel: i64,
interner: &Interner,
) -> Option<String> {
for s in stmts {
match s {
Stmt::While { cond, body, .. } => {
if let Some(bound) = build_loop_bound(cond, body, m, sentinel, interner) {
return Some(bound);
}
if let Some(b) = find_sentinel_build_size(body, m, sentinel, interner) {
return Some(b);
}
}
Stmt::If { then_block, else_block, .. } => {
if let Some(b) = find_sentinel_build_size(then_block, m, sentinel, interner) {
return Some(b);
}
if let Some(eb) = else_block {
if let Some(b) = find_sentinel_build_size(eb, m, sentinel, interner) {
return Some(b);
}
}
}
_ => {}
}
}
None
}
fn build_loop_bound(
cond: &Expr,
body: &[Stmt],
m: Symbol,
sentinel: i64,
interner: &Interner,
) -> Option<String> {
let Expr::BinaryOp { op: BinaryOpKind::Lt, left: _, right } = cond else { return None };
let pushes_sentinel = body.iter().any(|s| matches!(
s, Stmt::Push { collection, value }
if names_collection(collection, m) && is_const_eq(value, sentinel)));
if !pushes_sentinel {
return None;
}
bound_rust_expr(right, interner)
}
pub(crate) fn bound_rust_expr(e: &Expr, interner: &Interner) -> Option<String> {
match e {
Expr::Identifier(s) => Some(sanitize_ident(interner.resolve(*s))),
Expr::Literal(Literal::Number(k)) => Some(k.to_string()),
_ => None,
}
}
pub(crate) fn sanitize_ident(name: &str) -> String {
let mut out = String::with_capacity(name.len());
for ch in name.chars() {
if ch.is_alphanumeric() || ch == '_' {
out.push(ch);
} else {
out.push('_');
}
}
if out.chars().next().map_or(true, |c| c.is_ascii_digit()) {
out.insert(0, '_');
}
out
}
fn expr_uses(e: &Expr, q: Symbol) -> bool {
!ok_read_only_noidx(e, q)
}
fn ok_read_only_noidx(e: &Expr, q: Symbol) -> bool {
let mut uses = false;
visit_idents(e, &mut |s| {
if s == q {
uses = true;
}
});
!uses
}
fn expr_uses_outside_length(cond: &Expr, q: Symbol) -> bool {
match cond {
Expr::Length { collection } => match &**collection {
Expr::Identifier(s) if *s == q => false,
other => expr_uses_outside_length(other, q),
},
Expr::BinaryOp { left, right, .. } => {
expr_uses_outside_length(left, q) || expr_uses_outside_length(right, q)
}
Expr::Not { operand } => expr_uses_outside_length(operand, q),
Expr::Identifier(s) => *s == q,
Expr::Index { collection, index } => {
(matches!(&**collection, Expr::Identifier(s) if *s == q))
|| expr_uses_outside_length(collection, q)
|| expr_uses_outside_length(index, q)
}
_ => expr_uses(cond, q),
}
}
fn stmt_mentions(s: &Stmt, q: Symbol) -> bool {
let mut found = false;
for_each_stmt_expr(s, &mut |e| {
visit_idents(e, &mut |sym| {
if sym == q {
found = true;
}
});
});
found
}
pub(crate) fn visit_idents(e: &Expr, f: &mut impl FnMut(Symbol)) {
match e {
Expr::Identifier(s) => f(*s),
Expr::BinaryOp { left, right, .. }
| Expr::Union { left, right }
| Expr::Intersection { left, right }
| Expr::Range { start: left, end: right } => {
visit_idents(left, f);
visit_idents(right, f);
}
Expr::Not { operand } => visit_idents(operand, f),
Expr::Index { collection, index } => {
visit_idents(collection, f);
visit_idents(index, f);
}
Expr::Length { collection } | Expr::Copy { expr: collection } | Expr::Give { value: collection }
| Expr::OptionSome { value: collection } | Expr::FieldAccess { object: collection, .. } => {
visit_idents(collection, f)
}
Expr::Contains { collection, value } => {
visit_idents(collection, f);
visit_idents(value, f);
}
Expr::Slice { collection, start, end } => {
visit_idents(collection, f);
visit_idents(start, f);
visit_idents(end, f);
}
Expr::Call { args, .. } => args.iter().for_each(|a| visit_idents(a, f)),
Expr::CallExpr { callee, args } => {
visit_idents(callee, f);
args.iter().for_each(|a| visit_idents(a, f));
}
Expr::List(items) | Expr::Tuple(items) => items.iter().for_each(|i| visit_idents(i, f)),
Expr::New { init_fields, .. } => init_fields.iter().for_each(|(_, e)| visit_idents(e, f)),
Expr::NewVariant { fields, .. } => fields.iter().for_each(|(_, e)| visit_idents(e, f)),
Expr::WithCapacity { value, capacity } => {
visit_idents(value, f);
visit_idents(capacity, f);
}
Expr::InterpolatedString(parts) => parts.iter().for_each(|p| {
if let crate::ast::stmt::StringPart::Expr { value, .. } = p {
visit_idents(value, f);
}
}),
_ => {}
}
}
pub(crate) fn for_each_stmt_expr(s: &Stmt, f: &mut impl FnMut(&Expr)) {
match s {
Stmt::Let { value, .. } | Stmt::Set { value, .. }
| Stmt::Return { value: Some(value) } | Stmt::Inspect { target: value, .. } => f(value),
Stmt::Show { object, recipient } | Stmt::Give { object, recipient } => {
f(object);
f(recipient);
}
Stmt::Push { collection, value } | Stmt::Add { collection, value } => {
f(collection);
f(value);
}
Stmt::Pop { collection, .. } | Stmt::Remove { collection, .. } => f(collection),
Stmt::SetIndex { collection, index, value } => {
f(collection);
f(index);
f(value);
}
Stmt::SetField { object, value, .. } => {
f(object);
f(value);
}
Stmt::If { cond, .. } | Stmt::While { cond, .. } => f(cond),
Stmt::Call { args, .. } => args.iter().for_each(|a| f(a)),
Stmt::RuntimeAssert { condition, .. } => f(condition),
_ => {}
}
}