use crate::ast::stmt::{BinaryOpKind, Block, Expr, Literal, Pattern, Stmt};
use crate::codegen::peephole::{
body_modifies_var, body_mutates_collection, collect_expr_symbols, is_counter_increment,
is_simple_expr,
};
use crate::intern::Symbol;
#[derive(Debug, Clone, Copy)]
pub(crate) struct CountedLoop<'a> {
pub var: Symbol,
pub start: &'a Expr<'a>,
pub limit: &'a Expr<'a>,
pub inclusive: bool,
pub body_without_increment: Block<'a>,
}
pub(crate) fn extract_counted_while<'a>(
stmts: &[&Stmt<'a>],
idx: usize,
) -> Option<(CountedLoop<'a>, usize)> {
if idx + 1 >= stmts.len() {
return None;
}
let (counter_sym, counter_start_expr) = match stmts[idx] {
Stmt::Let { var, value, .. } => {
if is_simple_expr(value) {
(*var, *value)
} else {
return None;
}
}
Stmt::Set { target, value } => {
if is_simple_expr(value) {
(*target, *value)
} else {
return None;
}
}
_ => return None,
};
let (body, limit_expr, is_exclusive): (Block<'a>, &'a Expr<'a>, bool) = match stmts[idx + 1] {
Stmt::While { cond, body, .. } => match cond {
Expr::BinaryOp { op: BinaryOpKind::LtEq, left, right } => match left {
Expr::Identifier(sym) if *sym == counter_sym => (*body, *right, false),
_ => return None,
},
Expr::BinaryOp { op: BinaryOpKind::Lt, left, right } => match left {
Expr::Identifier(sym) if *sym == counter_sym => (*body, *right, true),
_ => return None,
},
_ => return None,
},
_ => return None,
};
if body.is_empty() {
return None;
}
if !is_counter_increment(&body[body.len() - 1], counter_sym) {
return None;
}
let body_without_increment = &body[..body.len() - 1];
if body_modifies_var(body_without_increment, counter_sym) {
return None;
}
if !is_simple_expr(limit_expr) {
return None;
}
let mut limit_syms = Vec::new();
collect_expr_symbols(limit_expr, &mut limit_syms);
for sym in &limit_syms {
if body_modifies_var(body_without_increment, *sym)
|| body_mutates_collection(body_without_increment, *sym)
{
return None;
}
}
Some((
CountedLoop {
var: counter_sym,
start: counter_start_expr,
limit: limit_expr,
inclusive: !is_exclusive,
body_without_increment,
},
2,
))
}
pub(crate) fn extract_counted_repeat<'a>(stmt: &Stmt<'a>) -> Option<CountedLoop<'a>> {
if let Stmt::Repeat { pattern, iterable, body } = stmt {
if let Pattern::Identifier(var) = pattern {
if let Expr::Range { start, end } = iterable {
return Some(CountedLoop {
var: *var,
start: *start,
limit: *end,
inclusive: true,
body_without_increment: *body,
});
}
}
}
None
}
pub(crate) fn const_eval_i64(expr: &Expr) -> Option<i64> {
match expr {
Expr::Literal(Literal::Number(n)) => Some(*n),
Expr::BinaryOp { op, left, right } => {
let l = const_eval_i64(left)?;
let r = const_eval_i64(right)?;
match op {
BinaryOpKind::Add => l.checked_add(r),
BinaryOpKind::Subtract => l.checked_sub(r),
BinaryOpKind::Multiply => l.checked_mul(r),
BinaryOpKind::Divide => {
if r == 0 {
None
} else {
l.checked_div(r)
}
}
BinaryOpKind::Modulo => {
if r == 0 {
None
} else {
l.checked_rem(r)
}
}
_ => None,
}
}
_ => None,
}
}