use super::*;
#[derive(Default)]
pub struct MoveUpAndSwap;
impl Tactic for MoveUpAndSwap {
fn apply(&mut self, builder: &mut SolutionBuilder) -> TacticResult {
let trace_target = builder.trace_target().clone().with_topic("solver:move-up-and-swap");
if builder.requires_copies() || builder.arity() < 2 {
log::trace!(
target: &trace_target,
"cannot apply tactic when there are required copies ({}) or fewer than 2 operands \
({})",
builder.requires_copies(),
builder.arity()
);
return Err(TacticError::PreconditionFailed);
}
let expected0 = builder.unwrap_expected(0);
let actual0 = builder.unwrap_current(0);
if actual0 == expected0 {
let Some(expected1) = builder.get_expected(1) else {
log::trace!(
target: &trace_target,
"top two operands on the stack are already in position, returning possible \
solution"
);
return Ok(());
};
let move_from = builder.unwrap_current_position(&expected1);
if move_from == 1 {
log::trace!(target: &trace_target, "abandoning tactic because operand at index 1 is already in position");
return Err(TacticError::NotApplicable);
}
log::trace!(
target: &trace_target,
"moving {expected1:?} to top of stack from index {move_from}, then swapping with \
{expected0:?}"
);
builder.movup(move_from);
builder.swap(1);
log::trace!(target: &trace_target, "returning possible solution");
return Ok(());
}
let mut descending_pair = None;
let mut last_pos = None;
for (operand_pos, operand) in builder.stack().iter().rev().enumerate() {
if let Some(expected_pos) = builder.get_expected_position(operand) {
let current = (operand_pos as u8, expected_pos);
let last_operand_pos = last_pos.replace(current);
if let Some(last @ (_, last_expected_pos)) = last_operand_pos {
if expected_pos >= last_expected_pos {
continue;
}
descending_pair = Some((last, current));
break;
}
}
}
if let Some((b, a)) = descending_pair {
let (b_actual, b_expected) = b;
let (a_actual, a_expected) = a;
debug_assert!(b_expected > a_expected);
if a_expected == 0 {
log::trace!(
target: &trace_target,
"moving {:?} to the top of stack, shifting {:?} down",
builder.stack()[a_actual as usize],
builder.stack()[0]
);
builder.movup(a_actual);
} else {
if b_actual > 0 {
log::trace!(
target: &trace_target,
"moving {:?} to the top of stack, shifting {:?} down",
builder.stack()[b_actual as usize],
builder.stack()[0]
);
builder.movup(b_actual);
}
let expected0_at = builder.unwrap_current_position(&expected0);
log::trace!(
target: &trace_target,
"moving {:?} to the top of stack, shifting {:?} down",
builder.stack()[expected0_at as usize],
builder.stack()[0]
);
builder.movup(expected0_at);
}
Ok(())
} else {
log::trace!(
target: &trace_target,
"abandoning tactic because by implication, operands are in order, and an unused \
operand must need eviction"
);
Err(TacticError::NotApplicable)
}
}
}