use super::conclude::concluded;
use super::types::{
Holding, NO_SEQUENCE_DRIVEN, Order, StatePredicate, TemporalDemand, TemporalDriveReading,
TemporalDriveStanding, TransitionContract,
};
use crate::generate::{
ByteSource, CommandDecode, CommandSequence, GenerationHalt, GenerationPlan,
SequencePrecondition, drive,
};
use crate::report::{FailureClass, TrialConclusion};
use core::cmp::Ordering;
#[must_use]
#[track_caller]
pub fn holds_over_history<State, Command>(
contract: &TransitionContract<State, Command>,
commands: &[Command],
) -> TrialConclusion {
let history = driven_history(contract, commands);
for claim in contract.claims() {
let holding = demand_holding(claim.demand(), &history);
match concluded(holding, FailureClass::PropertyDisagreement, claim.cause()) {
TrialConclusion::Passed => {}
refused @ TrialConclusion::Refused(_) => return refused,
}
}
TrialConclusion::Passed
}
#[must_use]
#[track_caller]
pub fn holds_over_drive<State, Command>(
contract: &TransitionContract<State, Command>,
plan: &GenerationPlan,
source: &ByteSource,
decode: CommandDecode<Command>,
precondition: SequencePrecondition<Command>,
) -> TemporalDriveReading<Command> {
let generated = drive(plan, source, decode, precondition);
if generated.sequences().is_empty() {
let empty = concluded(
Holding::Fails,
FailureClass::RefusedByCheck,
NO_SEQUENCE_DRIVEN,
);
let standing = TemporalDriveStanding::Concluded(empty);
return TemporalDriveReading::from_drive(generated, 0usize, standing);
}
let (evaluated, break_found) = first_break(contract, generated.sequences());
let standing = match break_found {
Some(refused) => TemporalDriveStanding::Concluded(refused),
None if generated.halt() == GenerationHalt::CaseBudgetMet => {
TemporalDriveStanding::Concluded(TrialConclusion::Passed)
}
None => TemporalDriveStanding::Incomplete,
};
TemporalDriveReading::from_drive(generated, evaluated, standing)
}
#[track_caller]
fn first_break<State, Command>(
contract: &TransitionContract<State, Command>,
sequences: &[CommandSequence<Command>],
) -> (usize, Option<TrialConclusion>) {
let mut evaluated = 0usize;
for sequence in sequences {
evaluated = evaluated.saturating_add(1usize);
match holds_over_history(contract, sequence.commands()) {
TrialConclusion::Passed => {}
refused @ TrialConclusion::Refused(_) => return (evaluated, Some(refused)),
}
}
(evaluated, None)
}
fn driven_history<State, Command>(
contract: &TransitionContract<State, Command>,
commands: &[Command],
) -> Vec<State> {
let apply = contract.apply();
let mut history: Vec<State> = Vec::with_capacity(commands.len().saturating_add(1));
let mut state = (contract.opening())();
for command in commands {
let next = apply(&state, command);
history.push(state);
state = next;
}
history.push(state);
history
}
fn demand_holding<State>(demand: &TemporalDemand<State>, history: &[State]) -> Holding {
match *demand {
TemporalDemand::Always(predicate) => everywhere(predicate, history),
TemporalDemand::Never(predicate) => nowhere(predicate, history),
TemporalDemand::Eventually(predicate) => somewhere(predicate, history),
TemporalDemand::OnceHoldingAlwaysHolding(predicate) => latched(predicate, history),
TemporalDemand::NeverDecreases(order) => never_decreasing(order, history),
}
}
fn everywhere<State>(predicate: StatePredicate<State>, history: &[State]) -> Holding {
history.iter().map(predicate).fold(Holding::Holds, both)
}
fn nowhere<State>(predicate: StatePredicate<State>, history: &[State]) -> Holding {
history
.iter()
.map(predicate)
.map(opposite)
.fold(Holding::Holds, both)
}
fn somewhere<State>(predicate: StatePredicate<State>, history: &[State]) -> Holding {
history.iter().map(predicate).fold(Holding::Fails, either)
}
fn latched<State>(predicate: StatePredicate<State>, history: &[State]) -> Holding {
let mut seen = Holding::Fails;
for state in history {
match (seen, predicate(state)) {
(Holding::Holds, Holding::Fails) => return Holding::Fails,
(Holding::Fails, Holding::Holds) => seen = Holding::Holds,
(Holding::Holds, Holding::Holds) | (Holding::Fails, Holding::Fails) => {}
}
}
Holding::Holds
}
fn never_decreasing<State>(order: Order<State>, history: &[State]) -> Holding {
history
.iter()
.zip(history.iter().skip(1))
.map(|(earlier, later)| match order(earlier, later) {
Ordering::Less | Ordering::Equal => Holding::Holds,
Ordering::Greater => Holding::Fails,
})
.fold(Holding::Holds, both)
}
const fn both(left: Holding, right: Holding) -> Holding {
match (left, right) {
(Holding::Holds, Holding::Holds) => Holding::Holds,
(Holding::Fails, _) | (Holding::Holds, Holding::Fails) => Holding::Fails,
}
}
const fn either(left: Holding, right: Holding) -> Holding {
match (left, right) {
(Holding::Holds, _) | (Holding::Fails, Holding::Holds) => Holding::Holds,
(Holding::Fails, Holding::Fails) => Holding::Fails,
}
}
const fn opposite(holding: Holding) -> Holding {
match holding {
Holding::Holds => Holding::Fails,
Holding::Fails => Holding::Holds,
}
}