use serde_json::json;
use super::ir::{
parse_block, parse_effect, IREffectDeclaration, IREffectOperation, IRHandlerClause,
IRHandlerFrame, IRPerform, IRResume, Instruction,
};
use super::runtime::{EffectRuntime, ExecutionResult, TraceEvent};
use super::value::Value;
fn empty_runtime() -> EffectRuntime {
EffectRuntime::new()
}
fn sample_effect(name: &str, ops: Vec<(&str, Vec<&str>, &str)>) -> IREffectDeclaration {
IREffectDeclaration {
name: name.to_string(),
operations: ops
.into_iter()
.map(|(op_name, params, ret)| IREffectOperation {
name: op_name.to_string(),
type_parameters: vec![],
parameter_names: params.iter().map(|s| s.to_string()).collect(),
parameter_types: params.iter().map(|_| "String".to_string()).collect(),
return_type: ret.to_string(),
source_line: 0,
source_column: 0,
})
.collect(),
source_line: 0,
source_column: 0,
}
}
fn simple_handle_perform(
effect: &str,
op: &str,
param: &str,
resume_value: &str,
perform_arg: &str,
) -> Instruction {
Instruction::HandlerFrame(IRHandlerFrame {
effect_names: vec![effect.to_string()],
clauses: vec![IRHandlerClause {
operation_name: op.to_string(),
parameter_names: vec![param.to_string()],
body: vec![Instruction::Resume(IRResume {
value_expr: resume_value.to_string(),
frame_id: 0,
})],
source_line: 0,
source_column: 0,
}],
body: vec![Instruction::Perform(IRPerform {
effect_name: effect.to_string(),
operation_name: op.to_string(),
arguments: vec![perform_arg.to_string()],
state_id: 0,
resume_label: format!("resume_{effect}_{op}_0"),
})],
frame_id: 0,
body_states: vec![0],
source_line: 0,
source_column: 0,
})
}
#[test]
fn value_from_text_recognises_literals() {
assert_eq!(Value::from_argument_text("true"), Value::Bool(true));
assert_eq!(Value::from_argument_text("42"), Value::Int(42));
assert_eq!(Value::from_argument_text("3.14"), Value::Float(3.14));
assert_eq!(
Value::from_argument_text("token"),
Value::Symbol("token".to_string())
);
}
#[test]
fn value_unit_renders() {
assert_eq!(Value::Unit.render(), "()");
assert!(Value::Unit.is_unit());
}
#[test]
fn value_default_is_unit() {
let v: Value = Default::default();
assert!(v.is_unit());
}
#[test]
fn value_serialises_via_serde() {
let v = Value::List(vec![Value::Int(1), Value::String("a".into())]);
let s = serde_json::to_string(&v).unwrap();
assert!(s.contains("1"));
assert!(s.contains("\"a\""));
}
#[test]
fn deserialize_effect_declaration() {
let s = r#"{
"name": "SSE",
"operations": [
{"name": "Emit", "type_parameters": [], "parameter_names": ["t"], "parameter_types": ["String"], "return_type": "Unit"},
{"name": "Done", "type_parameters": [], "parameter_names": [], "parameter_types": [], "return_type": "Never"}
]
}"#;
let eff = parse_effect(s).unwrap();
assert_eq!(eff.name, "SSE");
assert_eq!(eff.operations.len(), 2);
assert_eq!(eff.operations[0].name, "Emit");
assert_eq!(eff.operations[0].parameter_names, vec!["t".to_string()]);
assert_eq!(eff.operations[1].return_type, "Never");
}
#[test]
fn deserialize_effect_operation_with_type_parameters() {
let s = r#"{
"name": "Channel",
"operations": [
{"name": "Send", "type_parameters": ["T"], "parameter_names": ["v"], "parameter_types": ["T"], "return_type": "Unit"}
]
}"#;
let eff = parse_effect(s).unwrap();
assert_eq!(eff.operations[0].type_parameters, vec!["T".to_string()]);
}
#[test]
fn deserialize_perform_instruction() {
let s = r#"[{"node_type":"perform","effect_name":"E","operation_name":"Op","arguments":["x"],"state_id":7,"resume_label":"resume_E_Op_7"}]"#;
let block = parse_block(s).unwrap();
assert_eq!(block.len(), 1);
match &block[0] {
Instruction::Perform(p) => {
assert_eq!(p.effect_name, "E");
assert_eq!(p.operation_name, "Op");
assert_eq!(p.arguments, vec!["x".to_string()]);
assert_eq!(p.state_id, 7);
assert_eq!(p.resume_label, "resume_E_Op_7");
}
_ => panic!("expected Perform"),
}
}
#[test]
fn deserialize_handler_frame_with_clauses_and_body() {
let payload = json!([{
"node_type": "handler_frame",
"effect_names": ["SSE"],
"frame_id": 0,
"body_states": [0],
"clauses": [{
"node_type": "handler_clause",
"operation_name": "Emit",
"parameter_names": ["t"],
"body": [{"node_type":"resume","value_expr":"","frame_id":0}],
}],
"body": [{"node_type":"perform","effect_name":"SSE","operation_name":"Emit","arguments":["x"],"state_id":0,"resume_label":"resume_SSE_Emit_0"}],
}]);
let block: Vec<Instruction> = serde_json::from_value(payload).unwrap();
match &block[0] {
Instruction::HandlerFrame(f) => {
assert_eq!(f.effect_names, vec!["SSE".to_string()]);
assert_eq!(f.frame_id, 0);
assert_eq!(f.body_states, vec![0]);
assert_eq!(f.clauses.len(), 1);
assert_eq!(f.body.len(), 1);
}
_ => panic!("expected HandlerFrame"),
}
}
#[test]
fn deserialize_unknown_node_type_is_passthrough() {
let s = r#"[{"node_type": "step", "name": "S", "body": []}]"#;
let block = parse_block(s).unwrap();
assert_eq!(block.len(), 1);
matches!(block[0], Instruction::Passthrough);
}
#[test]
fn deserialize_full_paper_canonical_example() {
let payload = json!([{
"node_type": "handler_frame",
"effect_names": ["SSE"],
"frame_id": 0,
"body_states": [0, 1],
"clauses": [
{"node_type":"handler_clause","operation_name":"Emit","parameter_names":["token"],
"body":[{"node_type":"resume","value_expr":"","frame_id":0}]},
{"node_type":"handler_clause","operation_name":"Done","parameter_names":[],
"body":[{"node_type":"abort","value_expr":"","frame_id":0}]}
],
"body": [
{"node_type":"perform","effect_name":"SSE","operation_name":"Emit","arguments":["t"],"state_id":0,"resume_label":"resume_SSE_Emit_0"},
{"node_type":"perform","effect_name":"SSE","operation_name":"Done","arguments":[],"state_id":1,"resume_label":"resume_SSE_Done_1"}
]
}]);
let block: Vec<Instruction> = serde_json::from_value(payload).unwrap();
assert_eq!(block.len(), 1);
}
#[test]
fn register_effect_records_in_table() {
let mut rt = empty_runtime();
rt.register_effect(sample_effect("SSE", vec![("Emit", vec!["t"], "Unit")]));
assert!(rt.effects().contains_key("SSE"));
}
#[test]
fn lookup_operation_returns_signature() {
let mut rt = empty_runtime();
rt.register_effect(sample_effect("E", vec![("Op", vec!["x", "y"], "Unit")]));
let op = rt.lookup_operation("E", "Op").unwrap();
assert_eq!(op.parameter_names, vec!["x".to_string(), "y".to_string()]);
assert_eq!(op.return_type, "Unit");
}
#[test]
fn lookup_unknown_effect_returns_none() {
let rt = empty_runtime();
assert!(rt.lookup_operation("Ghost", "Boo").is_none());
}
#[test]
fn simple_perform_handled_by_resume_completes() {
let block = vec![simple_handle_perform("E", "Op", "x", "", "arg")];
let mut rt = empty_runtime();
let result = rt.run(&block).unwrap();
assert_eq!(result, ExecutionResult::Completed(Value::Unit));
}
#[test]
fn resume_with_value_returns_value_to_perform_site() {
let block = vec![Instruction::HandlerFrame(IRHandlerFrame {
effect_names: vec!["E".into()],
clauses: vec![IRHandlerClause {
operation_name: "Recv".into(),
parameter_names: vec![],
body: vec![Instruction::Resume(IRResume {
value_expr: "answer".into(),
frame_id: 0,
})],
..Default()
}],
body: vec![Instruction::Perform(IRPerform {
effect_name: "E".into(),
operation_name: "Recv".into(),
arguments: vec![],
state_id: 0,
resume_label: "r0".into(),
})],
frame_id: 0,
body_states: vec![0],
..Default()
})];
let mut rt = empty_runtime();
rt.bind_global("answer", Value::String("hello".into()));
let result = rt.run(&block).unwrap();
assert_eq!(result, ExecutionResult::Completed(Value::String("hello".into())));
}
#[test]
fn perform_unhandled_effect_is_runtime_error() {
let block = vec![Instruction::Perform(IRPerform {
effect_name: "E".into(),
operation_name: "Op".into(),
arguments: vec![],
state_id: 0,
resume_label: "r".into(),
})];
let mut rt = empty_runtime();
let err = rt.run(&block).unwrap_err();
assert!(matches!(
err,
super::runtime::EffectRuntimeError::UnhandledEffect { .. }
));
}
#[test]
fn perform_unknown_operation_is_runtime_error() {
let block = vec![Instruction::HandlerFrame(IRHandlerFrame {
effect_names: vec!["E".into()],
clauses: vec![IRHandlerClause {
operation_name: "Op".into(),
parameter_names: vec![],
body: vec![Instruction::Resume(IRResume::default())],
..Default()
}],
body: vec![Instruction::Perform(IRPerform {
effect_name: "E".into(),
operation_name: "Bogus".into(),
arguments: vec![],
..Default()
})],
frame_id: 0,
..Default()
})];
let mut rt = empty_runtime();
let err = rt.run(&block).unwrap_err();
assert!(matches!(
err,
super::runtime::EffectRuntimeError::UnknownOperation { .. }
));
}
#[test]
fn two_performs_in_body_both_resume() {
let block = vec![Instruction::HandlerFrame(IRHandlerFrame {
effect_names: vec!["E".into()],
clauses: vec![IRHandlerClause {
operation_name: "Op".into(),
parameter_names: vec![],
body: vec![Instruction::Resume(IRResume::default())],
..Default()
}],
body: vec![
Instruction::Perform(IRPerform {
effect_name: "E".into(),
operation_name: "Op".into(),
state_id: 0,
..Default()
}),
Instruction::Perform(IRPerform {
effect_name: "E".into(),
operation_name: "Op".into(),
state_id: 1,
..Default()
}),
],
frame_id: 0,
body_states: vec![0, 1],
..Default()
})];
let mut rt = empty_runtime();
let result = rt.run(&block).unwrap();
assert_eq!(result, ExecutionResult::Completed(Value::Unit));
}
#[test]
fn abort_terminates_the_handle_with_its_value() {
let block = vec![Instruction::HandlerFrame(IRHandlerFrame {
effect_names: vec!["E".into()],
clauses: vec![IRHandlerClause {
operation_name: "Op".into(),
parameter_names: vec![],
body: vec![Instruction::Abort(super::ir::IRAbort {
value_expr: "reason".into(),
frame_id: 0,
})],
..Default()
}],
body: vec![Instruction::Perform(IRPerform {
effect_name: "E".into(),
operation_name: "Op".into(),
..Default()
})],
frame_id: 0,
..Default()
})];
let mut rt = empty_runtime();
rt.bind_global("reason", Value::String("done".into()));
let result = rt.run(&block).unwrap();
assert_eq!(result, ExecutionResult::Aborted(Value::String("done".into())));
}
#[test]
fn abort_value_unit_when_value_expr_empty() {
let block = vec![Instruction::HandlerFrame(IRHandlerFrame {
effect_names: vec!["E".into()],
clauses: vec![IRHandlerClause {
operation_name: "Op".into(),
parameter_names: vec![],
body: vec![Instruction::Abort(super::ir::IRAbort::default())],
..Default()
}],
body: vec![Instruction::Perform(IRPerform {
effect_name: "E".into(),
operation_name: "Op".into(),
..Default()
})],
frame_id: 0,
..Default()
})];
let mut rt = empty_runtime();
let result = rt.run(&block).unwrap();
assert_eq!(result, ExecutionResult::Aborted(Value::Unit));
}
#[test]
fn forward_propagates_to_outer_frame() {
let inner = Instruction::HandlerFrame(IRHandlerFrame {
effect_names: vec!["E".into()],
clauses: vec![IRHandlerClause {
operation_name: "Op".into(),
parameter_names: vec![],
body: vec![Instruction::Forward(super::ir::IRForward {
effect_name: "E".into(),
operation_name: "Op".into(),
arguments: vec![],
source_frame_id: 1,
state_id: 1,
resume_label: "fw_E_Op_1".into(),
})],
..Default()
}],
body: vec![Instruction::Perform(IRPerform {
effect_name: "E".into(),
operation_name: "Op".into(),
state_id: 0,
..Default()
})],
frame_id: 1,
..Default()
});
let outer = Instruction::HandlerFrame(IRHandlerFrame {
effect_names: vec!["E".into()],
clauses: vec![IRHandlerClause {
operation_name: "Op".into(),
parameter_names: vec![],
body: vec![Instruction::Resume(IRResume::default())],
..Default()
}],
body: vec![inner],
frame_id: 0,
..Default()
});
let block = vec![outer];
let mut rt = empty_runtime();
let result = rt.run(&block).unwrap();
assert_eq!(result, ExecutionResult::Completed(Value::Unit));
}
#[test]
fn forward_without_outer_handler_is_runtime_error() {
let block = vec![Instruction::HandlerFrame(IRHandlerFrame {
effect_names: vec!["E".into()],
clauses: vec![IRHandlerClause {
operation_name: "Op".into(),
parameter_names: vec![],
body: vec![Instruction::Forward(super::ir::IRForward {
effect_name: "E".into(),
operation_name: "Op".into(),
source_frame_id: 0,
..Default()
})],
..Default()
}],
body: vec![Instruction::Perform(IRPerform {
effect_name: "E".into(),
operation_name: "Op".into(),
..Default()
})],
frame_id: 0,
..Default()
})];
let mut rt = empty_runtime();
let err = rt.run(&block).unwrap_err();
assert!(matches!(
err,
super::runtime::EffectRuntimeError::UnhandledEffect { .. }
));
}
#[test]
fn multi_clause_handler_dispatches_per_op_name() {
let block = vec![Instruction::HandlerFrame(IRHandlerFrame {
effect_names: vec!["E".into()],
clauses: vec![
IRHandlerClause {
operation_name: "A".into(),
parameter_names: vec![],
body: vec![Instruction::Resume(IRResume::default())],
..Default()
},
IRHandlerClause {
operation_name: "B".into(),
parameter_names: vec![],
body: vec![Instruction::Resume(IRResume::default())],
..Default()
},
],
body: vec![
Instruction::Perform(IRPerform {
effect_name: "E".into(),
operation_name: "A".into(),
state_id: 0,
..Default()
}),
Instruction::Perform(IRPerform {
effect_name: "E".into(),
operation_name: "B".into(),
state_id: 1,
..Default()
}),
],
frame_id: 0,
body_states: vec![0, 1],
..Default()
})];
let mut rt = empty_runtime();
let result = rt.run(&block).unwrap();
assert_eq!(result, ExecutionResult::Completed(Value::Unit));
}
#[test]
fn multi_effect_handler_handles_both_effects() {
let block = vec![Instruction::HandlerFrame(IRHandlerFrame {
effect_names: vec!["E1".into(), "E2".into()],
clauses: vec![
IRHandlerClause {
operation_name: "Op1".into(),
parameter_names: vec![],
body: vec![Instruction::Resume(IRResume::default())],
..Default()
},
IRHandlerClause {
operation_name: "Op2".into(),
parameter_names: vec![],
body: vec![Instruction::Resume(IRResume::default())],
..Default()
},
],
body: vec![
Instruction::Perform(IRPerform {
effect_name: "E1".into(),
operation_name: "Op1".into(),
state_id: 0,
..Default()
}),
Instruction::Perform(IRPerform {
effect_name: "E2".into(),
operation_name: "Op2".into(),
state_id: 1,
..Default()
}),
],
frame_id: 0,
..Default()
})];
let mut rt = empty_runtime();
let result = rt.run(&block).unwrap();
assert_eq!(result, ExecutionResult::Completed(Value::Unit));
}
#[test]
fn nested_handlers_inner_consumes_inner_perform() {
let inner = Instruction::HandlerFrame(IRHandlerFrame {
effect_names: vec!["E2".into()],
clauses: vec![IRHandlerClause {
operation_name: "Op2".into(),
parameter_names: vec![],
body: vec![Instruction::Resume(IRResume::default())],
..Default()
}],
body: vec![Instruction::Perform(IRPerform {
effect_name: "E2".into(),
operation_name: "Op2".into(),
..Default()
})],
frame_id: 1,
..Default()
});
let outer = Instruction::HandlerFrame(IRHandlerFrame {
effect_names: vec!["E1".into()],
clauses: vec![IRHandlerClause {
operation_name: "Op1".into(),
parameter_names: vec![],
body: vec![Instruction::Resume(IRResume::default())],
..Default()
}],
body: vec![inner],
frame_id: 0,
..Default()
});
let mut rt = empty_runtime();
let result = rt.run(&[outer]).unwrap();
assert_eq!(result, ExecutionResult::Completed(Value::Unit));
}
#[test]
fn nested_handlers_outer_catches_outer_perform() {
let inner = Instruction::HandlerFrame(IRHandlerFrame {
effect_names: vec!["E2".into()],
clauses: vec![IRHandlerClause {
operation_name: "Op2".into(),
parameter_names: vec![],
body: vec![Instruction::Resume(IRResume::default())],
..Default()
}],
body: vec![
Instruction::Perform(IRPerform {
effect_name: "E1".into(),
operation_name: "Op1".into(),
..Default()
}),
],
frame_id: 1,
..Default()
});
let outer = Instruction::HandlerFrame(IRHandlerFrame {
effect_names: vec!["E1".into()],
clauses: vec![IRHandlerClause {
operation_name: "Op1".into(),
parameter_names: vec![],
body: vec![Instruction::Resume(IRResume::default())],
..Default()
}],
body: vec![inner],
frame_id: 0,
..Default()
});
let mut rt = empty_runtime();
let result = rt.run(&[outer]).unwrap();
assert_eq!(result, ExecutionResult::Completed(Value::Unit));
}
#[test]
fn inner_abort_propagates_only_to_inner_handle() {
let inner = Instruction::HandlerFrame(IRHandlerFrame {
effect_names: vec!["E".into()],
clauses: vec![IRHandlerClause {
operation_name: "Op".into(),
parameter_names: vec![],
body: vec![Instruction::Abort(super::ir::IRAbort::default())],
..Default()
}],
body: vec![Instruction::Perform(IRPerform {
effect_name: "E".into(),
operation_name: "Op".into(),
..Default()
})],
frame_id: 1,
..Default()
});
let block = vec![inner];
let mut rt = empty_runtime();
let result = rt.run(&block).unwrap();
assert_eq!(result, ExecutionResult::Aborted(Value::Unit));
}
#[test]
fn clause_parameters_bind_perform_arguments() {
let block = vec![Instruction::HandlerFrame(IRHandlerFrame {
effect_names: vec!["E".into()],
clauses: vec![IRHandlerClause {
operation_name: "Op".into(),
parameter_names: vec!["t".into()],
body: vec![Instruction::Resume(IRResume {
value_expr: "t".into(),
frame_id: 0,
})],
..Default()
}],
body: vec![Instruction::Perform(IRPerform {
effect_name: "E".into(),
operation_name: "Op".into(),
arguments: vec!["payload".into()],
..Default()
})],
frame_id: 0,
..Default()
})];
let mut rt = empty_runtime();
rt.bind_global("payload", Value::String("hello".into()));
let result = rt.run(&block).unwrap();
assert_eq!(result, ExecutionResult::Completed(Value::String("hello".into())));
}
#[test]
fn clause_parameter_binding_does_not_leak_after_dispatch() {
let block = vec![Instruction::HandlerFrame(IRHandlerFrame {
effect_names: vec!["E".into()],
clauses: vec![IRHandlerClause {
operation_name: "Op".into(),
parameter_names: vec!["t".into()],
body: vec![Instruction::Resume(IRResume::default())],
..Default()
}],
body: vec![Instruction::Perform(IRPerform {
effect_name: "E".into(),
operation_name: "Op".into(),
arguments: vec!["inner".into()],
..Default()
})],
frame_id: 0,
..Default()
})];
let mut rt = empty_runtime();
rt.bind_global("t", Value::String("outer".into()));
rt.bind_global("inner", Value::String("inner_value".into()));
let _ = rt.run(&block).unwrap();
assert_eq!(
rt.lookup_operation("E", "Op").map(|_| ()).unwrap_or(()),
()
);
let trace_block = vec![Instruction::Passthrough];
let _ = rt.run(&trace_block).unwrap();
}
#[test]
fn trace_records_enter_perform_resume_exit_in_order() {
let block = vec![simple_handle_perform("E", "Op", "x", "", "arg")];
let mut rt = empty_runtime();
rt.enable_tracing();
let _ = rt.run(&block).unwrap();
let trace = rt.take_trace();
assert!(matches!(trace[0], TraceEvent::EnterFrame { .. }));
assert!(matches!(trace[1], TraceEvent::Perform { .. }));
assert!(matches!(trace[2], TraceEvent::Resume { .. }));
assert!(matches!(trace[3], TraceEvent::ExitFrame { .. }));
}
#[test]
fn trace_disabled_by_default() {
let block = vec![simple_handle_perform("E", "Op", "x", "", "arg")];
let mut rt = empty_runtime();
let _ = rt.run(&block).unwrap();
assert!(rt.take_trace().is_empty());
}
#[test]
fn trace_records_abort_event() {
let block = vec![Instruction::HandlerFrame(IRHandlerFrame {
effect_names: vec!["E".into()],
clauses: vec![IRHandlerClause {
operation_name: "Op".into(),
parameter_names: vec![],
body: vec![Instruction::Abort(super::ir::IRAbort::default())],
..Default()
}],
body: vec![Instruction::Perform(IRPerform {
effect_name: "E".into(),
operation_name: "Op".into(),
..Default()
})],
frame_id: 0,
..Default()
})];
let mut rt = empty_runtime();
rt.enable_tracing();
let _ = rt.run(&block).unwrap();
let trace = rt.take_trace();
assert!(trace.iter().any(|e| matches!(e, TraceEvent::Abort { .. })));
}
#[test]
fn trace_records_forward_event() {
let inner = Instruction::HandlerFrame(IRHandlerFrame {
effect_names: vec!["E".into()],
clauses: vec![IRHandlerClause {
operation_name: "Op".into(),
parameter_names: vec![],
body: vec![Instruction::Forward(super::ir::IRForward {
effect_name: "E".into(),
operation_name: "Op".into(),
source_frame_id: 1,
..Default()
})],
..Default()
}],
body: vec![Instruction::Perform(IRPerform {
effect_name: "E".into(),
operation_name: "Op".into(),
..Default()
})],
frame_id: 1,
..Default()
});
let outer = Instruction::HandlerFrame(IRHandlerFrame {
effect_names: vec!["E".into()],
clauses: vec![IRHandlerClause {
operation_name: "Op".into(),
parameter_names: vec![],
body: vec![Instruction::Resume(IRResume::default())],
..Default()
}],
body: vec![inner],
frame_id: 0,
..Default()
});
let mut rt = empty_runtime();
rt.enable_tracing();
let _ = rt.run(&[outer]).unwrap();
let trace = rt.take_trace();
assert!(trace.iter().any(|e| matches!(e, TraceEvent::Forward { .. })));
}
#[test]
fn clause_with_no_discharge_is_runtime_error() {
let block = vec![Instruction::HandlerFrame(IRHandlerFrame {
effect_names: vec!["E".into()],
clauses: vec![IRHandlerClause {
operation_name: "Op".into(),
parameter_names: vec![],
body: vec![Instruction::Passthrough], ..Default()
}],
body: vec![Instruction::Perform(IRPerform {
effect_name: "E".into(),
operation_name: "Op".into(),
..Default()
})],
frame_id: 0,
..Default()
})];
let mut rt = empty_runtime();
let err = rt.run(&block).unwrap_err();
assert!(matches!(
err,
super::runtime::EffectRuntimeError::NoDischarge { .. }
));
}
#[test]
fn paper_canonical_sse_runs_to_completion() {
let payload = json!([{
"node_type": "handler_frame",
"effect_names": ["SSE"],
"frame_id": 0,
"body_states": [0, 1],
"clauses": [
{"node_type":"handler_clause","operation_name":"Emit","parameter_names":["token"],
"body":[{"node_type":"resume","value_expr":"","frame_id":0}]},
{"node_type":"handler_clause","operation_name":"Done","parameter_names":[],
"body":[{"node_type":"abort","value_expr":"","frame_id":0}]}
],
"body": [
{"node_type":"perform","effect_name":"SSE","operation_name":"Emit","arguments":["t"],"state_id":0,"resume_label":"resume_SSE_Emit_0"},
{"node_type":"perform","effect_name":"SSE","operation_name":"Done","arguments":[],"state_id":1,"resume_label":"resume_SSE_Done_1"}
]
}]);
let block: Vec<Instruction> = serde_json::from_value(payload).unwrap();
let mut rt = empty_runtime();
rt.register_effect(sample_effect(
"SSE",
vec![("Emit", vec!["token"], "Unit"), ("Done", vec![], "Never")],
));
rt.bind_global("t", Value::String("token1".into()));
let result = rt.run(&block).unwrap();
assert_eq!(result, ExecutionResult::Aborted(Value::Unit));
}
#[test]
fn paper_decorator_pattern_with_forward_runs_to_completion() {
let inner = Instruction::HandlerFrame(IRHandlerFrame {
effect_names: vec!["E".into()],
clauses: vec![IRHandlerClause {
operation_name: "Op".into(),
parameter_names: vec!["x".into()],
body: vec![Instruction::Forward(super::ir::IRForward {
effect_name: "E".into(),
operation_name: "Op".into(),
arguments: vec!["x".into()],
source_frame_id: 1,
state_id: 1,
resume_label: "fw_E_Op_1".into(),
})],
..Default()
}],
body: vec![Instruction::Perform(IRPerform {
effect_name: "E".into(),
operation_name: "Op".into(),
arguments: vec!["token".into()],
state_id: 0,
..Default()
})],
frame_id: 1,
..Default()
});
let outer = Instruction::HandlerFrame(IRHandlerFrame {
effect_names: vec!["E".into()],
clauses: vec![IRHandlerClause {
operation_name: "Op".into(),
parameter_names: vec!["x".into()],
body: vec![Instruction::Resume(IRResume::default())],
..Default()
}],
body: vec![inner],
frame_id: 0,
..Default()
});
let mut rt = empty_runtime();
rt.bind_global("token", Value::String("hello".into()));
let result = rt.run(&[outer]).unwrap();
assert_eq!(result, ExecutionResult::Completed(Value::Unit));
}
#[test]
fn one_shot_resume_consumes_continuation_exactly_once() {
let block = vec![Instruction::HandlerFrame(IRHandlerFrame {
effect_names: vec!["E".into()],
clauses: vec![IRHandlerClause {
operation_name: "Op".into(),
parameter_names: vec![],
body: vec![Instruction::Resume(IRResume::default())],
..Default()
}],
body: vec![
Instruction::Perform(IRPerform {
effect_name: "E".into(),
operation_name: "Op".into(),
state_id: 0,
..Default()
}),
Instruction::Perform(IRPerform {
effect_name: "E".into(),
operation_name: "Op".into(),
state_id: 1,
..Default()
}),
Instruction::Perform(IRPerform {
effect_name: "E".into(),
operation_name: "Op".into(),
state_id: 2,
..Default()
}),
],
frame_id: 0,
body_states: vec![0, 1, 2],
..Default()
})];
let mut rt = empty_runtime();
let result = rt.run(&block).unwrap();
assert_eq!(result, ExecutionResult::Completed(Value::Unit));
}
#[test]
fn abort_does_not_resume_subsequent_performs() {
let block = vec![Instruction::HandlerFrame(IRHandlerFrame {
effect_names: vec!["E".into()],
clauses: vec![IRHandlerClause {
operation_name: "Op".into(),
parameter_names: vec![],
body: vec![Instruction::Abort(super::ir::IRAbort {
value_expr: "halt".into(),
frame_id: 0,
})],
..Default()
}],
body: vec![
Instruction::Perform(IRPerform {
effect_name: "E".into(),
operation_name: "Op".into(),
state_id: 0,
..Default()
}),
Instruction::Perform(IRPerform {
effect_name: "E".into(),
operation_name: "Op".into(),
state_id: 1,
..Default()
}),
],
frame_id: 0,
..Default()
})];
let mut rt = empty_runtime();
rt.bind_global("halt", Value::String("aborted".into()));
let result = rt.run(&block).unwrap();
assert_eq!(result, ExecutionResult::Aborted(Value::String("aborted".into())));
}
#[test]
fn json_payload_with_perform_outside_handler_in_block_is_unhandled() {
let payload = json!([
{"node_type":"perform","effect_name":"E","operation_name":"Op","arguments":[],"state_id":0,"resume_label":"r0"}
]);
let block: Vec<Instruction> = serde_json::from_value(payload).unwrap();
let mut rt = empty_runtime();
let err = rt.run(&block).unwrap_err();
assert!(matches!(
err,
super::runtime::EffectRuntimeError::UnhandledEffect { .. }
));
}
#[test]
fn json_payload_handler_with_int_arg_resolves_via_globals() {
let payload = json!([{
"node_type": "handler_frame",
"effect_names": ["Counter"],
"frame_id": 0,
"clauses": [{
"node_type": "handler_clause",
"operation_name": "Inc",
"parameter_names": ["n"],
"body": [{"node_type": "resume", "value_expr": "n", "frame_id": 0}]
}],
"body": [
{"node_type": "perform", "effect_name": "Counter", "operation_name": "Inc", "arguments": ["42"], "state_id": 0, "resume_label": "r0"}
],
}]);
let block: Vec<Instruction> = serde_json::from_value(payload).unwrap();
let mut rt = empty_runtime();
let result = rt.run(&block).unwrap();
assert_eq!(result, ExecutionResult::Completed(Value::Int(42)));
}
#[test]
fn json_payload_extra_fields_are_tolerated() {
let payload = json!([{
"node_type": "perform",
"effect_name": "E",
"operation_name": "Op",
"arguments": [],
"state_id": 0,
"resume_label": "r0",
"future_field_we_dont_know_about": "ignored",
}]);
let block: Vec<Instruction> = serde_json::from_value(payload).unwrap();
assert_eq!(block.len(), 1);
}
#[test]
fn empty_handle_body_completes_with_unit() {
let block = vec![Instruction::HandlerFrame(IRHandlerFrame {
effect_names: vec!["E".into()],
clauses: vec![IRHandlerClause {
operation_name: "Op".into(),
parameter_names: vec![],
body: vec![Instruction::Resume(IRResume::default())],
..Default()
}],
body: vec![], frame_id: 0,
..Default()
})];
let mut rt = empty_runtime();
let result = rt.run(&block).unwrap();
assert_eq!(result, ExecutionResult::Completed(Value::Unit));
}
#[test]
fn empty_run_block_completes_with_unit() {
let mut rt = empty_runtime();
let result = rt.run(&[]).unwrap();
assert_eq!(result, ExecutionResult::Completed(Value::Unit));
}
#[test]
fn passthrough_instructions_are_inert() {
let block = vec![
Instruction::Passthrough,
Instruction::Passthrough,
Instruction::Passthrough,
];
let mut rt = empty_runtime();
let result = rt.run(&block).unwrap();
assert_eq!(result, ExecutionResult::Completed(Value::Unit));
}
#[test]
fn handler_stack_pops_on_abort() {
let block_abort = vec![Instruction::HandlerFrame(IRHandlerFrame {
effect_names: vec!["E".into()],
clauses: vec![IRHandlerClause {
operation_name: "Op".into(),
parameter_names: vec![],
body: vec![Instruction::Abort(super::ir::IRAbort::default())],
..Default()
}],
body: vec![Instruction::Perform(IRPerform {
effect_name: "E".into(),
operation_name: "Op".into(),
..Default()
})],
frame_id: 0,
..Default()
})];
let block_resume = vec![simple_handle_perform("E", "Op", "x", "", "arg")];
let mut rt = empty_runtime();
let _ = rt.run(&block_abort).unwrap();
let result = rt.run(&block_resume).unwrap();
assert_eq!(result, ExecutionResult::Completed(Value::Unit));
}
#[test]
fn three_levels_of_nesting_work_correctly() {
let inner = Instruction::HandlerFrame(IRHandlerFrame {
effect_names: vec!["E".into()],
clauses: vec![IRHandlerClause {
operation_name: "Op".into(),
parameter_names: vec![],
body: vec![Instruction::Resume(IRResume::default())],
..Default()
}],
body: vec![Instruction::Perform(IRPerform {
effect_name: "E".into(),
operation_name: "Op".into(),
..Default()
})],
frame_id: 2,
..Default()
});
let middle = Instruction::HandlerFrame(IRHandlerFrame {
effect_names: vec!["E".into()],
clauses: vec![IRHandlerClause {
operation_name: "Op".into(),
parameter_names: vec![],
body: vec![Instruction::Resume(IRResume::default())],
..Default()
}],
body: vec![inner],
frame_id: 1,
..Default()
});
let outer = Instruction::HandlerFrame(IRHandlerFrame {
effect_names: vec!["E".into()],
clauses: vec![IRHandlerClause {
operation_name: "Op".into(),
parameter_names: vec![],
body: vec![Instruction::Resume(IRResume::default())],
..Default()
}],
body: vec![middle],
frame_id: 0,
..Default()
});
let mut rt = empty_runtime();
let result = rt.run(&[outer]).unwrap();
assert_eq!(result, ExecutionResult::Completed(Value::Unit));
}
#[test]
fn forward_chain_through_three_frames() {
let inner = Instruction::HandlerFrame(IRHandlerFrame {
effect_names: vec!["E".into()],
clauses: vec![IRHandlerClause {
operation_name: "Op".into(),
parameter_names: vec![],
body: vec![Instruction::Forward(super::ir::IRForward {
effect_name: "E".into(),
operation_name: "Op".into(),
source_frame_id: 2,
..Default()
})],
..Default()
}],
body: vec![Instruction::Perform(IRPerform {
effect_name: "E".into(),
operation_name: "Op".into(),
..Default()
})],
frame_id: 2,
..Default()
});
let middle = Instruction::HandlerFrame(IRHandlerFrame {
effect_names: vec!["E".into()],
clauses: vec![IRHandlerClause {
operation_name: "Op".into(),
parameter_names: vec![],
body: vec![Instruction::Forward(super::ir::IRForward {
effect_name: "E".into(),
operation_name: "Op".into(),
source_frame_id: 1,
..Default()
})],
..Default()
}],
body: vec![inner],
frame_id: 1,
..Default()
});
let outer = Instruction::HandlerFrame(IRHandlerFrame {
effect_names: vec!["E".into()],
clauses: vec![IRHandlerClause {
operation_name: "Op".into(),
parameter_names: vec![],
body: vec![Instruction::Resume(IRResume::default())],
..Default()
}],
body: vec![middle],
frame_id: 0,
..Default()
});
let mut rt = empty_runtime();
let result = rt.run(&[outer]).unwrap();
assert_eq!(result, ExecutionResult::Completed(Value::Unit));
}
#[test]
fn forward_with_arguments_passes_them_to_outer_clause() {
let inner = Instruction::HandlerFrame(IRHandlerFrame {
effect_names: vec!["E".into()],
clauses: vec![IRHandlerClause {
operation_name: "Op".into(),
parameter_names: vec!["x".into()],
body: vec![Instruction::Forward(super::ir::IRForward {
effect_name: "E".into(),
operation_name: "Op".into(),
arguments: vec!["x".into()],
source_frame_id: 1,
..Default()
})],
..Default()
}],
body: vec![Instruction::Perform(IRPerform {
effect_name: "E".into(),
operation_name: "Op".into(),
arguments: vec!["payload".into()],
..Default()
})],
frame_id: 1,
..Default()
});
let outer = Instruction::HandlerFrame(IRHandlerFrame {
effect_names: vec!["E".into()],
clauses: vec![IRHandlerClause {
operation_name: "Op".into(),
parameter_names: vec!["x".into()],
body: vec![Instruction::Resume(IRResume {
value_expr: "x".into(),
frame_id: 0,
})],
..Default()
}],
body: vec![inner],
frame_id: 0,
..Default()
});
let mut rt = empty_runtime();
rt.bind_global("payload", Value::String("seen".into()));
let result = rt.run(&[outer]).unwrap();
assert_eq!(result, ExecutionResult::Completed(Value::String("seen".into())));
}
#[test]
fn lookup_operation_via_registered_effect_d1() {
let mut rt = empty_runtime();
rt.register_effect(IREffectDeclaration {
name: "Channel".into(),
operations: vec![IREffectOperation {
name: "Send".into(),
type_parameters: vec!["T".into()],
parameter_names: vec!["v".into()],
parameter_types: vec!["T".into()],
return_type: "Unit".into(),
source_line: 0,
source_column: 0,
}],
source_line: 0,
source_column: 0,
});
let op = rt.lookup_operation("Channel", "Send").unwrap();
assert_eq!(op.type_parameters, vec!["T".to_string()]);
}
impl Default for IRHandlerClause {
fn default() -> Self {
Self {
operation_name: String::new(),
parameter_names: Vec::new(),
body: Vec::new(),
source_line: 0,
source_column: 0,
}
}
}
impl Default for IRHandlerFrame {
fn default() -> Self {
Self {
effect_names: Vec::new(),
clauses: Vec::new(),
body: Vec::new(),
frame_id: 0,
body_states: Vec::new(),
source_line: 0,
source_column: 0,
}
}
}
impl Default for IRPerform {
fn default() -> Self {
Self {
effect_name: String::new(),
operation_name: String::new(),
arguments: Vec::new(),
state_id: 0,
resume_label: String::new(),
}
}
}
impl Default for IRResume {
fn default() -> Self {
Self {
value_expr: String::new(),
frame_id: 0,
}
}
}
impl Default for super::ir::IRAbort {
fn default() -> Self {
Self {
value_expr: String::new(),
frame_id: 0,
}
}
}
impl Default for super::ir::IRForward {
fn default() -> Self {
Self {
effect_name: String::new(),
operation_name: String::new(),
arguments: Vec::new(),
source_frame_id: 0,
state_id: 0,
resume_label: String::new(),
}
}
}
#[allow(non_snake_case)]
fn Default<T: std::default::Default>() -> T {
T::default()
}