use crate::error::{RunError, TracedRunError};
use crate::program::{Program, RunLimits, RunResult, StepCount};
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::RuntimeRules;
use crate::runtime::rewrite::RewriteScratch;
use crate::runtime::state::State;
use crate::trace::{BorrowedTraceEffect, BorrowedTraceEvent, RuntimeStateView};
use crate::{inspect::PayloadView, inspect::RuleView};
#[derive(Debug, PartialEq, Eq)]
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) runtime_rules: RuntimeRules<'program>,
pub(crate) limits: RunLimits,
}
#[derive(Debug, PartialEq, Eq)]
pub enum ExecutionTransition<'program> {
Applied(AppliedExecution<'program>),
Stable(StableExecution<'program>),
Returned(ReturnedExecution<'program>),
}
#[derive(Debug, PartialEq, Eq)]
pub struct AppliedExecution<'program> {
step: StepCount,
rule: RuleView<'program>,
execution: RunningExecution<'program>,
}
#[derive(Debug, PartialEq, Eq)]
pub struct StableExecution<'program> {
steps: StepCount,
core: ExecutionCore<'program>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ReturnedExecution<'program> {
step: StepCount,
rule: RuleView<'program>,
output: PayloadView<'program>,
}
#[derive(Debug, PartialEq, Eq)]
pub struct ExecutionStepError<'program> {
error: RunError,
execution: RunningExecution<'program>,
}
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 runtime_rules = RuntimeRules::new(program.rule_slice())?;
Ok(Self {
state,
scratch: RewriteScratch::new(),
step_budget: StepBudget::new(limits.step_limit()),
runtime_rules,
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()
}
#[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,
runtime_rules,
limits,
} = &mut self.core;
let matched = match find_next_match(runtime_rules, 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, *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()),
}
}
}
#[cfg(test)]
pub(crate) fn find_next_match(&mut self) -> RuleSearch<'program, '_> {
find_next_match(&mut self.core.runtime_rules, &self.core.state)
}
}
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.view(),
execution,
}),
StepApplication::Return(output) => ExecutionTransition::Returned(ReturnedExecution {
step: self.step,
rule: self.rule.view(),
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)
}
}