use alloc::vec::Vec;
use crate::allocation::{AllocationContext, AllocationError, try_push, try_reserve_total_exact};
use crate::bytes::{RuntimeByte, RuntimeStateByteCount, TraceSnapshotByteCount};
use crate::error::TraceSnapshotError;
use crate::program::{ReturnOutput, RuntimeStateSnapshot, StepCount, TraceSnapshotByteLimit};
use crate::rule::{PayloadView, RuleView};
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct RuntimeStateView<'run> {
bytes: &'run [RuntimeByte],
}
impl<'run> RuntimeStateView<'run> {
pub(crate) const fn new(bytes: &'run [RuntimeByte]) -> Self {
Self { bytes }
}
#[must_use]
pub const fn is_empty(self) -> bool {
self.bytes.is_empty()
}
pub fn bytes(self) -> impl Iterator<Item = u8> + 'run {
self.bytes.iter().copied().map(RuntimeByte::materialize)
}
#[must_use]
pub const fn byte_count(self) -> RuntimeStateByteCount {
RuntimeStateByteCount::new(self.bytes.len())
}
pub fn to_vec(self) -> Result<Vec<u8>, AllocationError> {
self.to_vec_with_context(AllocationContext::RuntimeStateView)
}
pub(crate) fn to_vec_with_context(
self,
context: AllocationContext,
) -> Result<Vec<u8>, AllocationError> {
let mut output = Vec::new();
try_reserve_total_exact(&mut output, self.bytes.len(), context)?;
for byte in self.bytes() {
try_push(&mut output, byte, context)?;
}
Ok(output)
}
}
impl core::fmt::Debug for RuntimeStateView<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_list().entries((*self).bytes()).finish()
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum TraceSnapshotEffect {
Continue {
state: RuntimeStateSnapshot,
},
Return {
output: ReturnOutput,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BorrowedTraceEffect<'program, 'run> {
Continue {
state: RuntimeStateView<'run>,
},
Return {
output: PayloadView<'program>,
},
}
impl BorrowedTraceEffect<'_, '_> {
#[must_use]
pub fn byte_count(self) -> TraceSnapshotByteCount {
match self {
Self::Continue { state } => TraceSnapshotByteCount::new(state.byte_count().get()),
Self::Return { output } => TraceSnapshotByteCount::new(output.byte_count().get()),
}
}
#[must_use]
pub fn is_empty(self) -> bool {
match self {
Self::Continue { state } => state.is_empty(),
Self::Return { output } => output.is_empty(),
}
}
fn to_snapshot(
self,
limit: TraceSnapshotByteLimit,
) -> Result<TraceSnapshotEffect, TraceSnapshotError> {
ensure_trace_len(self.byte_count(), limit)?;
match self {
Self::Continue { state } => Ok(TraceSnapshotEffect::Continue {
state: RuntimeStateSnapshot::from_vec(
state.to_vec_with_context(AllocationContext::TraceSnapshot)?,
),
}),
Self::Return { output } => Ok(TraceSnapshotEffect::Return {
output: ReturnOutput::from_vec(
output.to_vec_with_context(AllocationContext::TraceSnapshot)?,
),
}),
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum TraceSnapshotEvent<'program> {
Initial {
state: RuntimeStateSnapshot,
},
Step {
step: StepCount,
rule: RuleView<'program>,
effect: TraceSnapshotEffect,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BorrowedTraceEvent<'program, 'run> {
Initial {
state: RuntimeStateView<'run>,
},
Step {
step: StepCount,
rule: RuleView<'program>,
effect: BorrowedTraceEffect<'program, 'run>,
},
}
impl<'program> BorrowedTraceEvent<'program, '_> {
#[must_use]
pub fn byte_count(self) -> TraceSnapshotByteCount {
match self {
Self::Initial { state } => TraceSnapshotByteCount::new(state.byte_count().get()),
Self::Step { effect, .. } => effect.byte_count(),
}
}
#[must_use]
pub fn is_empty(self) -> bool {
match self {
Self::Initial { state } => state.is_empty(),
Self::Step { effect, .. } => effect.is_empty(),
}
}
pub fn to_snapshot(
self,
limit: TraceSnapshotByteLimit,
) -> Result<TraceSnapshotEvent<'program>, TraceSnapshotError> {
match self {
Self::Initial { state } => {
ensure_trace_len(TraceSnapshotByteCount::new(state.byte_count().get()), limit)?;
Ok(TraceSnapshotEvent::Initial {
state: RuntimeStateSnapshot::from_vec(
state.to_vec_with_context(AllocationContext::TraceSnapshot)?,
),
})
}
Self::Step { step, rule, effect } => Ok(TraceSnapshotEvent::Step {
step,
rule,
effect: effect.to_snapshot(limit)?,
}),
}
}
}
fn ensure_trace_len(
len: TraceSnapshotByteCount,
limit: TraceSnapshotByteLimit,
) -> Result<(), TraceSnapshotError> {
if len.get() > limit.get() {
return Err(TraceSnapshotError::Limit {
limit,
attempted_len: len,
});
}
Ok(())
}