use std::marker::PhantomData;
use std::sync::Arc;
use brink_format::Value;
use crate::error::RuntimeError;
use crate::program::Program;
use crate::rng::{FastRng, StoryRng};
use crate::story::{ExternalFnHandler, FlowInstance, FunctionEval, Line, StepOutcome};
use crate::world::{ContextView, FlowLocal, Mode, World};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Budget {
pub steps: u64,
pub lines: usize,
}
impl Default for Budget {
fn default() -> Self {
Self {
steps: 100_000,
lines: 1_000,
}
}
}
#[derive(Debug, Clone)]
pub enum SpeculationStep {
Line(Line),
AwaitingExternal,
}
pub struct Speculation<R: StoryRng = FastRng> {
program: Arc<Program>,
line_tables: Vec<Vec<brink_format::LineEntry>>,
world: World,
local: FlowLocal,
flow: FlowInstance,
lines_advanced: usize,
_rng: PhantomData<R>,
}
impl<R: StoryRng> Speculation<R> {
#[must_use]
pub fn fork_from(
program: Arc<Program>,
world: &World,
local: &FlowLocal,
flow: &FlowInstance,
line_tables: &[Vec<brink_format::LineEntry>],
) -> Self {
Self {
program,
line_tables: line_tables.to_vec(),
world: world.clone(),
local: local.fork(Mode::Sandbox),
flow: flow.clone(),
lines_advanced: 0,
_rng: PhantomData,
}
}
pub fn go_to_path(&mut self, path: &str) -> Result<(), RuntimeError> {
let mut view = ContextView::new(&mut self.world, &mut self.local);
self.flow.choose_path_string(&self.program, &mut view, path)
}
pub fn choose(&mut self, index: usize) -> Result<(), RuntimeError> {
let mut view = ContextView::new(&mut self.world, &mut self.local);
self.flow.choose(&mut view, index)
}
pub fn advance(
&mut self,
budget: Budget,
handler: &dyn ExternalFnHandler,
) -> Result<SpeculationStep, RuntimeError> {
if self.lines_advanced >= budget.lines {
return Err(RuntimeError::LineLimitExceeded(budget.lines));
}
let mut view = ContextView::new(&mut self.world, &mut self.local);
let outcome = self.flow.advance_with_limit::<R>(
&self.program,
&self.line_tables,
&mut view,
handler,
None,
budget.steps,
)?;
if let StepOutcome::Line(_) = &outcome {
self.lines_advanced += 1;
}
Ok(match outcome {
StepOutcome::Line(line) => SpeculationStep::Line(line),
StepOutcome::AwaitingExternal => SpeculationStep::AwaitingExternal,
})
}
pub fn eval_function(
&mut self,
name: &str,
args: &[Value],
handler: &dyn ExternalFnHandler,
) -> Result<FunctionEval, RuntimeError> {
let container_idx = self
.program
.find_address(name)
.ok_or_else(|| RuntimeError::FunctionNotFound(name.to_owned()))?
.0;
let expected = self.program.container(container_idx).param_count;
if args.len() != expected as usize {
return Err(RuntimeError::ArgCountMismatch {
target: name.to_owned(),
expected,
got: args.len(),
});
}
let mut view = ContextView::new(&mut self.world, &mut self.local);
self.flow.begin_function_eval::<R>(
&self.program,
&self.line_tables,
&mut view,
handler,
container_idx,
args,
None,
)
}
pub fn resume_function_eval(
&mut self,
handler: &dyn ExternalFnHandler,
) -> Result<FunctionEval, RuntimeError> {
let mut view = ContextView::new(&mut self.world, &mut self.local);
self.flow.resume_function_eval::<R>(
&self.program,
&self.line_tables,
&mut view,
handler,
None,
)
}
pub fn resolve_external(&mut self, value: Value) {
self.flow.resolve_external(value);
}
#[must_use]
pub fn pending_external_name(&self) -> Option<&str> {
self.flow.pending_external_name(&self.program)
}
#[must_use]
pub fn transcript(&self) -> &[crate::output::OutputPart] {
self.flow.transcript()
}
#[must_use]
pub fn rendered_transcript(&self) -> Vec<(String, Vec<String>)> {
crate::transcript::render_transcript(
self.flow.transcript(),
&self.program,
&self.line_tables,
None,
self.flow.fragments(),
)
}
}