use alloc::vec::Vec;
use core::slice;
use crate::allocation::{
AllocationContext, AllocationError, RequestedCapacity, try_push, try_reserve_total_exact,
};
use crate::error::RuleRuntimeStateError;
use crate::inspect::OnceRuleCount;
use crate::program::RuleScan;
use crate::rule::{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) enum OnceRuleReadiness<'once> {
Available(MatchedRuleCommit<'once>),
Consumed,
}
#[derive(Debug)]
pub(super) struct OnceMatchPermit<'once> {
state: &'once mut OnceRuleState,
linearity: OnceMatchPermitLinearity,
}
#[derive(Debug)]
pub(crate) struct RuntimeRule<'program, 'once> {
rule: &'program Rule,
availability: RuntimeRuleAvailability<'once>,
}
pub(super) struct RuntimeRulesMut<'program, 'once> {
rules: slice::Iter<'program, Rule>,
once_states: slice::IterMut<'once, OnceRuleState>,
}
#[derive(Debug)]
enum RuntimeRuleAvailability<'once> {
Always,
Once(&'once mut OnceRuleState),
}
#[derive(Debug)]
struct OnceMatchPermitLinearity;
impl OnceMatchPermitLinearity {
const fn new() -> Self {
Self
}
}
impl<'once> OnceMatchPermit<'once> {
fn new(state: &'once mut OnceRuleState) -> Self {
Self {
state,
linearity: OnceMatchPermitLinearity::new(),
}
}
}
impl MatchedRuleCommit<'_> {
pub(super) fn commit(self) {
match self {
Self::Always => {}
Self::Once(commit) => commit.commit(),
}
}
}
impl OnceStateSet {
pub(crate) fn new(once_count: OnceRuleCount) -> Result<Self, AllocationError> {
let mut states = Vec::new();
try_reserve_total_exact(
&mut states,
RequestedCapacity::from_once_rule_count(once_count),
AllocationContext::RuntimeOnceRuleState,
)?;
for _ in 0..once_count.get() {
try_push(
&mut states,
OnceRuleState::Fresh,
AllocationContext::RuntimeOnceRuleState,
)?;
}
Ok(Self { states })
}
pub(super) fn runtime_rule_mut<'program, 'once>(
&'once mut self,
rule: &'program Rule,
) -> Result<RuntimeRule<'program, 'once>, RuleRuntimeStateError> {
let availability = match rule.availability() {
RuleAvailability::Always => RuntimeRuleAvailability::Always,
RuleAvailability::Once(slot) => {
let state = self.states.get_mut(slot.index()).ok_or_else(|| {
RuleRuntimeStateError::missing_once_rule_state(rule.position())
})?;
RuntimeRuleAvailability::Once(state)
}
};
Ok(RuntimeRule { rule, availability })
}
pub(super) fn runtime_rules_mut<'program, 'once>(
&'once mut self,
rules: RuleScan<'program>,
) -> RuntimeRulesMut<'program, 'once> {
RuntimeRulesMut {
rules: rules.iter(),
once_states: self.states.iter_mut(),
}
}
}
impl<'program, 'once> Iterator for RuntimeRulesMut<'program, 'once> {
type Item = Result<RuntimeRule<'program, 'once>, RuleRuntimeStateError>;
fn next(&mut self) -> Option<Self::Item> {
let rule = self.rules.next()?;
let availability = match rule.availability() {
RuleAvailability::Always => RuntimeRuleAvailability::Always,
RuleAvailability::Once(_) => match self.once_states.next() {
Some(state) => RuntimeRuleAvailability::Once(state),
None => {
return Some(Err(RuleRuntimeStateError::missing_once_rule_state(
rule.position(),
)));
}
},
};
Some(Ok(RuntimeRule { rule, availability }))
}
}
impl<'program, 'once> RuntimeRule<'program, 'once> {
pub(super) const fn rule(&self) -> &'program Rule {
self.rule
}
pub(super) fn readiness(self) -> OnceRuleReadiness<'once> {
match self.availability {
RuntimeRuleAvailability::Always => {
OnceRuleReadiness::Available(MatchedRuleCommit::Always)
}
RuntimeRuleAvailability::Once(state) => match state {
OnceRuleState::Fresh => OnceRuleReadiness::Available(MatchedRuleCommit::Once(
OnceMatchPermit::new(state),
)),
OnceRuleState::Consumed => OnceRuleReadiness::Consumed,
},
}
}
}
impl OnceMatchPermit<'_> {
fn commit(self) {
let Self {
state,
linearity: _linearity,
} = self;
*state = OnceRuleState::Consumed;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::RunStepError;
use crate::test_support::{TestFailure, TestResult, ensure_eq, parse_program};
#[test]
fn missing_once_rule_state_is_runtime_step_error() -> TestResult {
let program = parse_program("(once)a=b")?;
let rule = program
.rule_scan()
.iter()
.next()
.ok_or(TestFailure::message("expected parsed rule"))?;
let mut once_states = OnceStateSet { states: Vec::new() };
let Err(RunStepError::RuleRuntimeState(error)) = once_states
.runtime_rule_mut(rule)
.map_err(RunStepError::from)
else {
return Err(TestFailure::message("expected missing once-state error"));
};
ensure_eq!(error.rule(), rule.position())
}
}