use std::collections::HashMap;
use super::ir::{
IREffectDeclaration, IREffectOperation, IRHandlerClause, IRHandlerFrame, IRPerform,
Instruction,
};
use super::value::Value;
#[derive(Debug)]
pub enum EffectRuntimeError {
UnhandledEffect { effect: String, operation: String },
UnknownOperation { effect: String, operation: String },
MultipleResumeInClause { operation: String },
NoDischarge { operation: String },
ForwardWithoutOuterHandler { effect: String },
Internal(String),
}
impl std::fmt::Display for EffectRuntimeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
EffectRuntimeError::UnhandledEffect { effect, operation } => write!(
f,
"unhandled effect at runtime: {effect}.{operation} (compiler bug — D9 should reject)"
),
EffectRuntimeError::UnknownOperation { effect, operation } => write!(
f,
"unknown operation: {effect} has no '{operation}' (compiler bug)"
),
EffectRuntimeError::MultipleResumeInClause { operation } => write!(
f,
"handler clause '{operation}' invoked resume more than once (compiler bug — D10 should reject)"
),
EffectRuntimeError::NoDischarge { operation } => write!(
f,
"handler clause '{operation}' finished without resume/abort/forward (compiler bug — D10 should reject)"
),
EffectRuntimeError::ForwardWithoutOuterHandler { effect } => write!(
f,
"forward of '{effect}' has no enclosing outer handler"
),
EffectRuntimeError::Internal(s) => write!(f, "internal runtime error: {s}"),
}
}
}
impl std::error::Error for EffectRuntimeError {}
#[derive(Debug, Clone, PartialEq)]
pub enum ExecutionResult {
Completed(Value),
Aborted(Value),
}
#[derive(Debug)]
enum BlockOutcome {
Done(Value),
Aborted(Value),
Forwarded {
effect: String,
operation: String,
args: Vec<Value>,
},
}
#[derive(Debug)]
enum ClauseOutcome {
Resumed(Value),
Aborted(Value),
Forwarded {
effect: String,
operation: String,
args: Vec<Value>,
},
}
pub struct EffectRuntime {
effects: HashMap<String, IREffectDeclaration>,
globals: HashMap<String, Value>,
trace: Vec<TraceEvent>,
tracing_enabled: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub enum TraceEvent {
EnterFrame { frame_id: u32, effects: Vec<String> },
ExitFrame { frame_id: u32 },
Perform { effect: String, operation: String, state_id: u32 },
Resume { frame_id: u32, value: Value },
Abort { frame_id: u32, value: Value },
Forward { effect: String, operation: String, source_frame_id: u32 },
}
impl Default for EffectRuntime {
fn default() -> Self {
Self::new()
}
}
impl EffectRuntime {
pub fn new() -> Self {
Self {
effects: HashMap::new(),
globals: HashMap::new(),
trace: Vec::new(),
tracing_enabled: false,
}
}
pub fn register_effect(&mut self, eff: IREffectDeclaration) {
self.effects.insert(eff.name.clone(), eff);
}
pub fn bind_global(&mut self, name: impl Into<String>, value: Value) {
self.globals.insert(name.into(), value);
}
pub fn enable_tracing(&mut self) {
self.tracing_enabled = true;
}
pub fn take_trace(&mut self) -> Vec<TraceEvent> {
std::mem::take(&mut self.trace)
}
fn record(&mut self, event: TraceEvent) {
if self.tracing_enabled {
self.trace.push(event);
}
}
pub fn run(&mut self, block: &[Instruction]) -> Result<ExecutionResult, EffectRuntimeError> {
let mut stack: Vec<HandlerFrameRef<'_>> = Vec::new();
match self.dispatch_block(block, &mut stack)? {
BlockOutcome::Done(v) => Ok(ExecutionResult::Completed(v)),
BlockOutcome::Aborted(v) => Ok(ExecutionResult::Aborted(v)),
BlockOutcome::Forwarded { effect, .. } => {
Err(EffectRuntimeError::UnhandledEffect {
effect,
operation: "<forwarded>".to_string(),
})
}
}
}
fn resolve_args(&self, args: &[String]) -> Vec<Value> {
args.iter()
.map(|s| {
let v = Value::from_argument_text(s);
if let Value::Symbol(ref name) = v {
if let Some(bound) = self.globals.get(name) {
return bound.clone();
}
}
v
})
.collect()
}
fn dispatch_block<'a>(
&mut self,
block: &'a [Instruction],
stack: &mut Vec<HandlerFrameRef<'a>>,
) -> Result<BlockOutcome, EffectRuntimeError> {
let mut last_value = Value::Unit;
let mut i = 0;
while i < block.len() {
match &block[i] {
Instruction::Passthrough => {
i += 1;
}
Instruction::HandlerFrame(frame) => {
let outcome = self.dispatch_handler_frame(frame, stack)?;
match outcome {
BlockOutcome::Done(v) => {
last_value = v;
i += 1;
}
BlockOutcome::Aborted(v) => return Ok(BlockOutcome::Aborted(v)),
BlockOutcome::Forwarded { effect, operation, args } => {
return Ok(BlockOutcome::Forwarded { effect, operation, args });
}
}
}
Instruction::Perform(perf) => {
let outcome = self.dispatch_perform(perf, stack)?;
match outcome {
BlockOutcome::Done(v) => {
last_value = v;
i += 1;
}
BlockOutcome::Aborted(v) => return Ok(BlockOutcome::Aborted(v)),
BlockOutcome::Forwarded { effect, operation, args } => {
return Ok(BlockOutcome::Forwarded { effect, operation, args });
}
}
}
Instruction::Resume(_) | Instruction::Abort(_) | Instruction::Forward(_) => {
return Err(EffectRuntimeError::Internal(format!(
"control-flow opcode {:?} appeared outside a handler clause body",
std::mem::discriminant(&block[i]),
)));
}
}
}
Ok(BlockOutcome::Done(last_value))
}
fn dispatch_handler_frame<'a>(
&mut self,
frame: &'a IRHandlerFrame,
stack: &mut Vec<HandlerFrameRef<'a>>,
) -> Result<BlockOutcome, EffectRuntimeError> {
self.record(TraceEvent::EnterFrame {
frame_id: frame.frame_id,
effects: frame.effect_names.clone(),
});
stack.push(HandlerFrameRef { frame });
let result = self.dispatch_block(&frame.body, stack);
stack.pop();
self.record(TraceEvent::ExitFrame { frame_id: frame.frame_id });
result
}
fn dispatch_perform<'a>(
&mut self,
perf: &IRPerform,
stack: &mut Vec<HandlerFrameRef<'a>>,
) -> Result<BlockOutcome, EffectRuntimeError> {
self.record(TraceEvent::Perform {
effect: perf.effect_name.clone(),
operation: perf.operation_name.clone(),
state_id: perf.state_id,
});
let start = stack.len();
let frame_idx = self.find_handler_index(stack, &perf.effect_name, start)?;
let args = self.resolve_args(&perf.arguments);
self.dispatch_clause_for(perf.effect_name.as_str(), perf.operation_name.as_str(), &args, stack, frame_idx)
}
fn find_handler_index<'a>(
&self,
stack: &[HandlerFrameRef<'a>],
effect_name: &str,
start_exclusive: usize,
) -> Result<usize, EffectRuntimeError> {
for i in (0..start_exclusive).rev() {
if stack[i].frame.effect_names.iter().any(|e| e == effect_name) {
return Ok(i);
}
}
Err(EffectRuntimeError::UnhandledEffect {
effect: effect_name.to_string(),
operation: String::new(),
})
}
fn dispatch_clause_for<'a>(
&mut self,
effect_name: &str,
operation_name: &str,
args: &[Value],
stack: &mut Vec<HandlerFrameRef<'a>>,
frame_idx: usize,
) -> Result<BlockOutcome, EffectRuntimeError> {
let frame_ref = stack[frame_idx];
let frame = frame_ref.frame;
let clause = frame
.clauses
.iter()
.find(|c| c.operation_name == operation_name)
.ok_or_else(|| EffectRuntimeError::UnknownOperation {
effect: effect_name.to_string(),
operation: operation_name.to_string(),
})?;
let saved: Vec<(String, Option<Value>)> = clause
.parameter_names
.iter()
.map(|name| (name.clone(), self.globals.get(name).cloned()))
.collect();
for (name, value) in clause.parameter_names.iter().zip(args.iter()) {
self.globals.insert(name.clone(), value.clone());
}
let clause_result = self.dispatch_clause_body(clause, stack);
for (name, prior) in saved {
match prior {
Some(v) => {
self.globals.insert(name, v);
}
None => {
self.globals.remove(&name);
}
}
}
match clause_result? {
ClauseOutcome::Resumed(v) => Ok(BlockOutcome::Done(v)),
ClauseOutcome::Aborted(v) => Ok(BlockOutcome::Aborted(v)),
ClauseOutcome::Forwarded { effect, operation, args } => {
let outer_idx_result = self.find_handler_index(stack, &effect, frame_idx);
let args_vec = args.clone();
match outer_idx_result {
Ok(outer_idx) => {
self.dispatch_clause_for(&effect, &operation, &args_vec, stack, outer_idx)
}
Err(_) => Ok(BlockOutcome::Forwarded { effect, operation, args }),
}
}
}
}
fn dispatch_clause_body<'a>(
&mut self,
clause: &'a IRHandlerClause,
stack: &mut Vec<HandlerFrameRef<'a>>,
) -> Result<ClauseOutcome, EffectRuntimeError> {
for instr in &clause.body {
match instr {
Instruction::Resume(r) => {
let value = self.resolve_value_expr(&r.value_expr);
self.record(TraceEvent::Resume {
frame_id: r.frame_id,
value: value.clone(),
});
return Ok(ClauseOutcome::Resumed(value));
}
Instruction::Abort(a) => {
let value = self.resolve_value_expr(&a.value_expr);
self.record(TraceEvent::Abort {
frame_id: a.frame_id,
value: value.clone(),
});
return Ok(ClauseOutcome::Aborted(value));
}
Instruction::Forward(fwd) => {
self.record(TraceEvent::Forward {
effect: fwd.effect_name.clone(),
operation: fwd.operation_name.clone(),
source_frame_id: fwd.source_frame_id,
});
let args = self.resolve_args(&fwd.arguments);
return Ok(ClauseOutcome::Forwarded {
effect: fwd.effect_name.clone(),
operation: fwd.operation_name.clone(),
args,
});
}
Instruction::HandlerFrame(inner_frame) => {
match self.dispatch_handler_frame(inner_frame, stack)? {
BlockOutcome::Done(_) => continue,
BlockOutcome::Aborted(v) => {
return Ok(ClauseOutcome::Aborted(v));
}
BlockOutcome::Forwarded { effect, operation, args } => {
return Ok(ClauseOutcome::Forwarded { effect, operation, args });
}
}
}
Instruction::Perform(perf) => {
match self.dispatch_perform(perf, stack)? {
BlockOutcome::Done(_) => continue,
BlockOutcome::Aborted(v) => return Ok(ClauseOutcome::Aborted(v)),
BlockOutcome::Forwarded { effect, operation, args } => {
return Ok(ClauseOutcome::Forwarded { effect, operation, args });
}
}
}
Instruction::Passthrough => {
continue;
}
}
}
Err(EffectRuntimeError::NoDischarge {
operation: clause.operation_name.clone(),
})
}
fn resolve_value_expr(&self, expr: &str) -> Value {
if expr.is_empty() {
return Value::Unit;
}
let v = Value::from_argument_text(expr);
if let Value::Symbol(ref name) = v {
if let Some(bound) = self.globals.get(name) {
return bound.clone();
}
}
v
}
pub fn lookup_operation(
&self,
effect: &str,
operation: &str,
) -> Option<&IREffectOperation> {
self.effects
.get(effect)?
.operations
.iter()
.find(|op| op.name == operation)
}
pub fn effects(&self) -> &HashMap<String, IREffectDeclaration> {
&self.effects
}
}
#[derive(Clone, Copy)]
struct HandlerFrameRef<'a> {
frame: &'a IRHandlerFrame,
}