use alloc::vec::Vec;
use crate::allocation::{
AllocationContext, AllocationError, RequestedCapacity, try_push, try_reserve_total_exact,
};
use crate::error::{RunError, RunInvariantError};
use crate::inspect::OnceRuleCount as PublicOnceRuleCount;
use crate::rule::{OnceRuleCount, Rule, RuleAvailability};
#[derive(Debug)]
pub(crate) struct OnceStateSet {
states: Vec<OnceRuleState>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum OnceRuleState {
Fresh,
Consumed,
}
#[derive(Debug)]
pub(super) enum MatchedRuleCommit<'once> {
Always,
Once(OnceMatchPermit<'once>),
}
#[derive(Debug)]
pub(super) struct OnceMatchPermit<'once> {
state: &'once mut OnceRuleState,
}
impl<'once> OnceMatchPermit<'once> {
const fn new(state: &'once mut OnceRuleState) -> Self {
Self { state }
}
fn commit(self) {
*self.state = OnceRuleState::Consumed;
}
}
impl MatchedRuleCommit<'_> {
pub(super) fn commit(self) {
match self {
Self::Always => {}
Self::Once(commit) => commit.commit(),
}
}
}
impl OnceStateSet {
pub(crate) fn new(rules: &[Rule]) -> Result<Self, AllocationError> {
let once_rule_count = OnceRuleCount::new(
rules
.iter()
.filter(|rule| rule.availability().is_once())
.count(),
);
let mut states = Vec::new();
try_reserve_total_exact(
&mut states,
RequestedCapacity::from_once_rule_count(once_rule_count),
AllocationContext::RuntimeOnceRuleState,
)?;
for _ in 0..once_rule_count.get() {
try_push(
&mut states,
OnceRuleState::Fresh,
AllocationContext::RuntimeOnceRuleState,
)?;
}
Ok(Self { states })
}
pub(super) fn is_available(&self, rule: &Rule) -> Result<bool, RunError> {
match rule.availability() {
RuleAvailability::Always => Ok(true),
RuleAvailability::Once(slot) => {
let available_slots = PublicOnceRuleCount::new(self.states.len());
let Some(state) = self.states.get(slot.get()) else {
return Err(RunInvariantError::MissingOnceRuleState {
rule: rule.position(),
available_slots,
}
.into());
};
match state {
OnceRuleState::Fresh => Ok(true),
OnceRuleState::Consumed => Ok(false),
}
}
}
}
pub(super) fn reserve_available_commit(
&mut self,
rule: &Rule,
) -> Result<MatchedRuleCommit<'_>, RunError> {
match rule.availability() {
RuleAvailability::Always => Ok(MatchedRuleCommit::Always),
RuleAvailability::Once(slot) => {
let available_slots = PublicOnceRuleCount::new(self.states.len());
let Some(state) = self.states.get_mut(slot.get()) else {
return Err(RunInvariantError::MissingOnceRuleState {
rule: rule.position(),
available_slots,
}
.into());
};
match state {
OnceRuleState::Fresh => {
Ok(MatchedRuleCommit::Once(OnceMatchPermit::new(state)))
}
OnceRuleState::Consumed => Err(RunInvariantError::MissingOnceRuleState {
rule: rule.position(),
available_slots,
}
.into()),
}
}
}
}
}