use crate::error::{LimitError, RunError, StateLimitContext, TracedRunError};
use crate::program::{Program, RunLimits, RunResult, StepCount};
use crate::rule::Rule;
use crate::runtime::action::{AppliedRule, StepApplication, apply_matched_rule};
use crate::runtime::budget::StepBudget;
use crate::runtime::input::{InitialStateBytes, RuntimeInput};
use crate::runtime::matcher::{RuleSearch, find_next_match};
use crate::runtime::once::OnceStateSet;
use crate::runtime::rewrite::RewriteScratch;
use crate::runtime::state::State;
use crate::trace::{BorrowedTraceEffect, BorrowedTraceEvent, RuntimeStateView};
use crate::{inspect::PayloadView, inspect::RuleView};
pub struct RunningExecution<'program> {
pub(crate) core: ExecutionCore<'program>,
}
#[derive(Debug, PartialEq, Eq)]
pub(crate) struct ExecutionCore<'program> {
pub(crate) state: State,
pub(crate) scratch: RewriteScratch,
pub(crate) step_budget: StepBudget,
pub(crate) rules: &'program [Rule],
pub(crate) once_states: OnceStateSet,
pub(crate) limits: RunLimits,
}
pub enum ExecutionTransition<'program> {
Applied(AppliedExecution<'program>),
Stable(StableExecution<'program>),
Returned(ReturnedExecution<'program>),
}
pub struct AppliedExecution<'program> {
step: StepCount,
rule: RuleView<'program>,
execution: RunningExecution<'program>,
}
pub struct StableExecution<'program> {
steps: StepCount,
core: ExecutionCore<'program>,
}
#[derive(Clone, Copy)]
pub struct ReturnedExecution<'program> {
step: StepCount,
rule: RuleView<'program>,
output: PayloadView<'program>,
}
pub struct ExecutionStepError<'program> {
error: RunError,
execution: RunningExecution<'program>,
}
impl core::fmt::Debug for RunningExecution<'_> {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter
.debug_struct("RunningExecution")
.field("completed_steps", &self.completed_steps())
.field("state", &self.state())
.finish()
}
}
impl core::fmt::Debug for ExecutionTransition<'_> {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Applied(applied) => formatter.debug_tuple("Applied").field(applied).finish(),
Self::Stable(stable) => formatter.debug_tuple("Stable").field(stable).finish(),
Self::Returned(returned) => formatter.debug_tuple("Returned").field(returned).finish(),
}
}
}
impl core::fmt::Debug for AppliedExecution<'_> {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter
.debug_struct("AppliedExecution")
.field("step", &self.step())
.field("rule", &self.rule())
.field("state", &self.state())
.finish()
}
}
impl core::fmt::Debug for StableExecution<'_> {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter
.debug_struct("StableExecution")
.field("steps", &self.steps())
.field("state", &self.state())
.finish()
}
}
impl core::fmt::Debug for ReturnedExecution<'_> {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter
.debug_struct("ReturnedExecution")
.field("step", &self.step())
.field("rule", &self.rule())
.field("output", &self.output())
.finish()
}
}
impl core::fmt::Debug for ExecutionStepError<'_> {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
formatter
.debug_struct("ExecutionStepError")
.field("error", &self.error())
.field("execution", &self.execution())
.finish()
}
}
impl<'program> ExecutionCore<'program> {
pub(crate) fn new(
program: &'program Program,
input: &RuntimeInput,
limits: RunLimits,
) -> Result<Self, RunError> {
let input = InitialStateBytes::materialize(input, limits)?;
let state = State::from_input(input);
let once_states = OnceStateSet::new(program.once_slot_count())?;
Ok(Self {
state,
scratch: RewriteScratch::new(),
step_budget: StepBudget::new(limits.step_limit()),
rules: program.rule_slice(),
once_states,
limits,
})
}
pub(crate) const fn completed_steps(&self) -> StepCount {
self.step_budget.completed_steps()
}
pub(crate) fn state(&self) -> RuntimeStateView<'_> {
self.state.view()
}
pub(crate) fn into_stable_result(self, steps: StepCount) -> Result<RunResult, RunError> {
Ok(RunResult::stable(self.state.into_snapshot()?, steps))
}
}
impl<'program> RunningExecution<'program> {
pub(crate) fn new(
program: &'program Program,
input: &RuntimeInput,
limits: RunLimits,
) -> Result<Self, RunError> {
Ok(Self {
core: ExecutionCore::new(program, input, limits)?,
})
}
#[must_use]
pub const fn completed_steps(&self) -> StepCount {
self.core.completed_steps()
}
#[must_use]
pub fn state(&self) -> RuntimeStateView<'_> {
self.core.state()
}
pub fn with_limits(mut self, limits: RunLimits) -> Result<Self, RunError> {
let completed_steps = self.core.step_budget.completed_steps();
let state_len = self.core.state.byte_count();
if completed_steps.get() > limits.step_limit().get() {
return Err(LimitError::step(limits.step_limit(), completed_steps, state_len).into());
}
if state_len.get() > limits.state_byte_limit().get() {
return Err(LimitError::state(
StateLimitContext::CurrentState,
limits.state_byte_limit(),
state_len,
)
.into());
}
self.core.limits = limits;
self.core.step_budget = self.core.step_budget.with_limit(limits.step_limit());
Ok(self)
}
#[expect(
clippy::result_large_err,
reason = "ExecutionStepError preserves the uncommitted execution by value without allocating on the error path"
)]
pub fn step(mut self) -> Result<ExecutionTransition<'program>, ExecutionStepError<'program>> {
let applied = {
let ExecutionCore {
state,
scratch,
step_budget,
rules,
once_states,
limits,
} = &mut self.core;
let matched = match find_next_match(rules, once_states, state) {
RuleSearch::Matched(matched) => matched,
RuleSearch::Stable => {
let steps = step_budget.completed_steps();
return Ok(ExecutionTransition::Stable(StableExecution {
steps,
core: self.core,
}));
}
};
apply_matched_rule(state, scratch, step_budget, once_states, *limits, matched)
};
let applied = match applied {
Ok(applied) => applied,
Err(error) => return Err(ExecutionStepError::new(error, self)),
};
Ok(applied.into_transition(self))
}
pub fn finish(mut self) -> Result<RunResult, RunError> {
loop {
match self.step() {
Ok(ExecutionTransition::Applied(applied)) => {
self = applied.into_running();
}
Ok(ExecutionTransition::Stable(stable)) => {
return stable.into_result();
}
Ok(ExecutionTransition::Returned(returned)) => {
return returned.into_result();
}
Err(error) => return Err(error.into_error()),
}
}
}
}
impl<'program> AppliedRule<'program> {
fn into_transition(
self,
execution: RunningExecution<'program>,
) -> ExecutionTransition<'program> {
match self.effect {
StepApplication::Continue => ExecutionTransition::Applied(AppliedExecution {
step: self.step,
rule: self.rule,
execution,
}),
StepApplication::Return(output) => ExecutionTransition::Returned(ReturnedExecution {
step: self.step,
rule: self.rule,
output,
}),
}
}
}
impl<'program> AppliedExecution<'program> {
#[must_use]
pub const fn step(&self) -> StepCount {
self.step
}
#[must_use]
pub const fn rule(&self) -> RuleView<'program> {
self.rule
}
#[must_use]
pub fn state(&self) -> RuntimeStateView<'_> {
self.execution.state()
}
#[must_use]
pub fn into_running(self) -> RunningExecution<'program> {
self.execution
}
}
impl StableExecution<'_> {
#[must_use]
pub const fn steps(&self) -> StepCount {
self.steps
}
#[must_use]
pub fn state(&self) -> RuntimeStateView<'_> {
self.core.state()
}
pub fn into_result(self) -> Result<RunResult, RunError> {
self.core.into_stable_result(self.steps)
}
}
impl<'program> ReturnedExecution<'program> {
#[must_use]
pub const fn step(&self) -> StepCount {
self.step
}
#[must_use]
pub const fn rule(&self) -> RuleView<'program> {
self.rule
}
#[must_use]
pub const fn output(&self) -> PayloadView<'program> {
self.output
}
pub fn into_result(self) -> Result<RunResult, RunError> {
Ok(RunResult::from_return(
ExecutionCore::materialize_return_output(self.output)?,
self.step,
))
}
}
impl<'program> ExecutionStepError<'program> {
fn new(error: RunError, execution: RunningExecution<'program>) -> Self {
Self { error, execution }
}
#[must_use]
pub const fn error(&self) -> &RunError {
&self.error
}
#[must_use]
pub const fn execution(&self) -> &RunningExecution<'program> {
&self.execution
}
#[must_use]
pub fn into_execution(self) -> RunningExecution<'program> {
self.execution
}
#[must_use]
pub fn into_error(self) -> RunError {
self.error
}
}
impl core::fmt::Display for ExecutionStepError<'_> {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.error.fmt(formatter)
}
}
impl core::error::Error for ExecutionStepError<'_> {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
Some(&self.error)
}
}
impl<'program> RunningExecution<'program> {
pub(crate) fn run_with_borrowed_trace<F, E>(
mut self,
mut trace: F,
) -> Result<RunResult, TracedRunError<E>>
where
F: for<'run> FnMut(BorrowedTraceEvent<'program, 'run>) -> Result<(), E>,
{
trace(BorrowedTraceEvent::Initial {
state: self.state(),
})
.map_err(TracedRunError::Trace)?;
loop {
match self.step() {
Ok(ExecutionTransition::Applied(applied)) => {
Self::emit_step_trace(
&mut trace,
applied.step(),
applied.rule(),
BorrowedTraceEffect::Continue {
state: applied.state(),
},
)?;
self = applied.into_running();
}
Ok(ExecutionTransition::Stable(stable)) => {
return stable.into_result().map_err(TracedRunError::Run);
}
Ok(ExecutionTransition::Returned(returned)) => {
Self::emit_step_trace(
&mut trace,
returned.step(),
returned.rule(),
BorrowedTraceEffect::Return {
output: returned.output(),
},
)?;
return returned.into_result().map_err(TracedRunError::Run);
}
Err(error) => return Err(TracedRunError::Run(error.into_error())),
}
}
}
fn emit_step_trace<F, E>(
trace: &mut F,
step: StepCount,
rule: RuleView<'program>,
effect: BorrowedTraceEffect<'program, '_>,
) -> Result<(), TracedRunError<E>>
where
F: for<'run> FnMut(BorrowedTraceEvent<'program, 'run>) -> Result<(), E>,
{
trace(BorrowedTraceEvent::Step { step, rule, effect }).map_err(TracedRunError::Trace)
}
}