use super::types::{
ByteDraw, ByteSource, CaseIndex, CaseWidth, CommandDecode, CommandSequence, GeneratedSequences,
GenerationCensus, GenerationDisposition, GenerationHalt, GenerationPlan, PreconditionVerdict,
RejectionAllowance, SequencePrecondition, StreamCursor,
};
use arbitrary::{Arbitrary, Unstructured};
enum CaseDecoding<Command> {
Decoded(Vec<Command>),
Refused,
ContractViolated,
}
enum CaseOutcome<Command> {
Admitted(CommandSequence<Command>),
EmptyHanded(GenerationDisposition),
ContractViolated,
}
enum DriveProgress {
Continuing,
Halted,
}
struct DriveState<Command> {
census: GenerationCensus,
sequences: Vec<CommandSequence<Command>>,
cursor: StreamCursor,
spent: u64,
rejections: u32,
halt: GenerationHalt,
}
impl<Command> DriveState<Command> {
fn opening(plan: &GenerationPlan) -> Self {
Self {
census: GenerationCensus::over(plan.population()),
sequences: Vec::new(),
cursor: StreamCursor::opening(),
spent: 0,
rejections: 0,
halt: GenerationHalt::CaseBudgetMet,
}
}
fn advance(
&mut self,
ordinal: u32,
plan: &GenerationPlan,
source: &ByteSource,
decode: CommandDecode<Command>,
precondition: SequencePrecondition<Command>,
) -> DriveProgress {
let remaining = plan.bytes().bytes().saturating_sub(self.spent);
if remaining == 0 {
return self.stop(
GenerationDisposition::GenerationBudgetExhausted,
GenerationHalt::ByteBudgetExhausted,
);
}
let case = CaseIndex::at(ordinal);
let width = drawn_width(plan.progression().width_at(case), remaining);
let ByteDraw::Drawn { bytes: input, next } = source.draw(self.cursor, width) else {
return self.stop(
GenerationDisposition::BytesInsufficient,
GenerationHalt::SourceExhausted,
);
};
self.cursor = next;
self.spent = self
.spent
.saturating_add(u64::try_from(input.len()).unwrap_or(u64::MAX));
self.record(
case_outcome(case, input, decode, precondition),
plan.rejection_allowance(),
)
}
fn record(
&mut self,
outcome: CaseOutcome<Command>,
allowance: RejectionAllowance,
) -> DriveProgress {
match outcome {
CaseOutcome::Admitted(sequence) => self.record_admitted(sequence),
CaseOutcome::EmptyHanded(disposition) => {
self.record_empty_handed(disposition, allowance)
}
CaseOutcome::ContractViolated => self.stop(
GenerationDisposition::GeneratorContractViolated,
GenerationHalt::GeneratorContractViolated,
),
}
}
fn record_admitted(&mut self, sequence: CommandSequence<Command>) -> DriveProgress {
self.census.count(GenerationDisposition::Generated);
self.sequences.push(sequence);
DriveProgress::Continuing
}
fn record_empty_handed(
&mut self,
disposition: GenerationDisposition,
allowance: RejectionAllowance,
) -> DriveProgress {
self.census.count(disposition);
self.rejections = self.rejections.saturating_add(1);
if allowance_spent(allowance, self.rejections) {
self.halt = GenerationHalt::RejectionAllowanceSpent;
return DriveProgress::Halted;
}
DriveProgress::Continuing
}
fn stop(&mut self, disposition: GenerationDisposition, halt: GenerationHalt) -> DriveProgress {
self.census.count(disposition);
self.halt = halt;
DriveProgress::Halted
}
fn finish(self) -> GeneratedSequences<Command> {
GeneratedSequences::produced(self.sequences, self.census, self.halt)
}
}
enum CommandDecoding<Command> {
Decoded(Command),
Refused,
ContractViolated,
}
pub fn decode_arbitrary<Command>(source: &mut Unstructured<'_>) -> arbitrary::Result<Command>
where
Command: for<'bytes> Arbitrary<'bytes>,
{
Command::arbitrary(source)
}
#[must_use]
pub const fn admit_every_sequence<Command>(_commands: &[Command]) -> PreconditionVerdict {
PreconditionVerdict::Admitted
}
#[must_use]
pub fn drive<Command>(
plan: &GenerationPlan,
source: &ByteSource,
decode: CommandDecode<Command>,
precondition: SequencePrecondition<Command>,
) -> GeneratedSequences<Command> {
let mut state = DriveState::opening(plan);
for ordinal in 0..plan.cases().cases() {
if matches!(
state.advance(ordinal, plan, source, decode, precondition),
DriveProgress::Halted
) {
break;
}
}
state.finish()
}
fn allowance_spent(allowance: RejectionAllowance, rejections: u32) -> bool {
match allowance {
RejectionAllowance::NoRejections => rejections > 0,
RejectionAllowance::AtMost(admitted) => rejections >= admitted.get(),
}
}
fn drawn_width(width: CaseWidth, remaining: u64) -> usize {
let ceiling = usize::try_from(remaining).unwrap_or(usize::MAX);
width.bytes().min(ceiling)
}
fn case_outcome<Command>(
case: CaseIndex,
input: Vec<u8>,
decode: CommandDecode<Command>,
precondition: SequencePrecondition<Command>,
) -> CaseOutcome<Command> {
let commands = match decode_case(&input, decode) {
CaseDecoding::ContractViolated => return CaseOutcome::ContractViolated,
CaseDecoding::Refused => {
return CaseOutcome::EmptyHanded(GenerationDisposition::GeneratorRefused);
}
CaseDecoding::Decoded(commands) => commands,
};
match precondition(&commands) {
PreconditionVerdict::Rejected => {
CaseOutcome::EmptyHanded(GenerationDisposition::PreconditionRejected)
}
PreconditionVerdict::Admitted => {
CaseOutcome::Admitted(CommandSequence::generated(case, commands, input))
}
}
}
fn decode_case<Command>(input: &[u8], decode: CommandDecode<Command>) -> CaseDecoding<Command> {
let mut source = Unstructured::new(input);
let mut commands: Vec<Command> = Vec::new();
while !source.is_empty() {
match decode_one(&mut source, decode) {
CommandDecoding::Decoded(command) => commands.push(command),
CommandDecoding::Refused => return after_refusal(commands),
CommandDecoding::ContractViolated => return CaseDecoding::ContractViolated,
}
}
CaseDecoding::Decoded(commands)
}
fn decode_one<Command>(
source: &mut Unstructured<'_>,
decode: CommandDecode<Command>,
) -> CommandDecoding<Command> {
let before = source.len();
match decode(source) {
Ok(command) if source.len() < before => CommandDecoding::Decoded(command),
Ok(_) => CommandDecoding::ContractViolated,
Err(_) => CommandDecoding::Refused,
}
}
fn after_refusal<Command>(commands: Vec<Command>) -> CaseDecoding<Command> {
if commands.is_empty() {
CaseDecoding::Refused
} else {
CaseDecoding::Decoded(commands)
}
}