use std::sync::Arc;
use crate::{
capability::{
CapabilityName, control_capture_capability, control_multishot_capability,
control_prompt_capability, control_resume_capability,
},
datum::Datum,
datum_store::DatumStore,
effect::{
Effect, effect_abort_op_key, effect_control_abort_kind, effect_control_capture_kind,
effect_control_prompt_kind, effect_control_resume_kind, effect_resume_op_key,
resolve_effect,
},
env::Cx,
error::{Diagnostic, Result, Severity},
id::Symbol,
op::core_any_ref,
ref_id::{ContentId, Coordinate, HandleId, Ref},
};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ControlPrompt {
pub prompt: Ref,
pub input: Ref,
pub result_shape: Ref,
}
impl ControlPrompt {
pub fn new(prompt: Ref, input: Ref, result_shape: Ref) -> Self {
Self {
prompt,
input,
result_shape,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ControlCapture {
pub prompt: Ref,
pub continuation: Ref,
pub value: Ref,
pub result_shape: Ref,
pub multishot: bool,
}
impl ControlCapture {
pub fn new(prompt: Ref, continuation: Ref, value: Ref, result_shape: Ref) -> Self {
Self {
prompt,
continuation,
value,
result_shape,
multishot: false,
}
}
pub fn multishot(mut self) -> Self {
self.multishot = true;
self
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ControlAbort {
pub prompt: Ref,
pub value: Ref,
pub result_shape: Ref,
}
impl ControlAbort {
pub fn new(prompt: Ref, value: Ref, result_shape: Ref) -> Self {
Self {
prompt,
value,
result_shape,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ControlResume {
pub continuation: Ref,
pub value: Ref,
pub result_shape: Ref,
}
impl ControlResume {
pub fn new(continuation: Ref, value: Ref, result_shape: Ref) -> Self {
Self {
continuation,
value,
result_shape,
}
}
}
pub trait ControlPolicy: Send + Sync {
fn name(&self) -> &'static str;
fn enter_prompt(&self, _cx: &mut Cx, _prompt: &ControlPrompt) -> Result<()> {
Ok(())
}
fn capture(&self, cx: &mut Cx, _capture: &ControlCapture) -> Result<Ref> {
unsupported_control_result(cx, self.name(), effect_control_capture_kind())
}
fn abort(&self, cx: &mut Cx, _abort: &ControlAbort) -> Result<Ref> {
unsupported_control_result(cx, self.name(), effect_control_abort_kind())
}
fn resume(&self, cx: &mut Cx, _resume: &ControlResume) -> Result<Ref> {
unsupported_control_result(cx, self.name(), effect_control_resume_kind())
}
}
pub type ControlPolicyRef = Arc<dyn ControlPolicy>;
#[derive(Default)]
pub struct NoopControlPolicy;
impl ControlPolicy for NoopControlPolicy {
fn name(&self) -> &'static str {
"noop-control"
}
}
pub fn prompt<F>(cx: &mut Cx, prompt: ControlPrompt, body: F) -> Result<Ref>
where
F: FnOnce(&mut Cx) -> Result<Ref>,
{
let effect = prompt_effect(&prompt);
resolve_effect(cx, effect, |cx, _effect| {
let policy = cx.control_policy_ref();
policy.enter_prompt(cx, &prompt)?;
body(cx)
})
}
pub fn capture(cx: &mut Cx, capture: ControlCapture) -> Result<Ref> {
let effect = capture_effect(cx, &capture)?;
resolve_effect(cx, effect, |cx, _effect| {
let policy = cx.control_policy_ref();
policy.capture(cx, &capture)
})
}
pub fn abort(cx: &mut Cx, abort: ControlAbort) -> Result<Ref> {
let effect = abort_effect(&abort);
resolve_effect(cx, effect, |cx, _effect| {
let policy = cx.control_policy_ref();
policy.abort(cx, &abort)
})
}
pub fn resume(cx: &mut Cx, resume: ControlResume) -> Result<Ref> {
let effect = resume_effect(&resume);
resolve_effect(cx, effect, |cx, _effect| {
let policy = cx.control_policy_ref();
policy.resume(cx, &resume)
})
}
pub fn prompt_effect(prompt: &ControlPrompt) -> Effect {
Effect::new(
effect_control_prompt_kind(),
prompt.prompt.clone(),
prompt.input.clone(),
prompt.result_shape.clone(),
effect_resume_op_key(),
effect_abort_op_key(),
)
.requiring(control_prompt_capability())
}
pub fn capture_effect(cx: &mut Cx, capture: &ControlCapture) -> Result<Effect> {
let input = intern_control_input(
cx,
control_capture_status(),
vec![
(
Symbol::new("continuation"),
ref_datum(capture.continuation.clone()),
),
(Symbol::new("value"), ref_datum(capture.value.clone())),
(Symbol::new("multishot"), Datum::Bool(capture.multishot)),
],
)?;
Ok(Effect::new(
effect_control_capture_kind(),
capture.prompt.clone(),
input,
capture.result_shape.clone(),
effect_resume_op_key(),
effect_abort_op_key(),
)
.with_requirements(control_requirements(
control_capture_capability(),
capture.multishot,
)))
}
pub fn abort_effect(abort: &ControlAbort) -> Effect {
Effect::new(
effect_control_abort_kind(),
abort.prompt.clone(),
abort.value.clone(),
abort.result_shape.clone(),
effect_resume_op_key(),
effect_abort_op_key(),
)
.requiring(control_capture_capability())
}
pub fn resume_effect(resume: &ControlResume) -> Effect {
Effect::new(
effect_control_resume_kind(),
resume.continuation.clone(),
resume.value.clone(),
resume.result_shape.clone(),
effect_resume_op_key(),
effect_abort_op_key(),
)
.requiring(control_resume_capability())
}
pub fn captured_control_result(cx: &mut Cx, continuation: Ref, value: Ref) -> Result<Ref> {
intern_control_result(
cx,
control_captured_status(),
vec![
(Symbol::new("continuation"), ref_datum(continuation)),
(Symbol::new("value"), ref_datum(value)),
],
)
}
pub fn aborted_control_result(cx: &mut Cx, prompt: Ref, value: Ref) -> Result<Ref> {
intern_control_result(
cx,
control_aborted_status(),
vec![
(Symbol::new("prompt"), ref_datum(prompt)),
(Symbol::new("value"), ref_datum(value)),
],
)
}
pub fn resumed_control_result(cx: &mut Cx, continuation: Ref, value: Ref) -> Result<Ref> {
intern_control_result(
cx,
control_resumed_status(),
vec![
(Symbol::new("continuation"), ref_datum(continuation)),
(Symbol::new("value"), ref_datum(value)),
],
)
}
pub fn unsupported_control_result(
cx: &mut Cx,
policy: &'static str,
operation: Symbol,
) -> Result<Ref> {
let diagnostic = unsupported_control_diagnostic(policy, operation);
cx.push_diagnostic(diagnostic.clone());
intern_control_result(
cx,
control_unsupported_status(),
vec![(Symbol::new("diagnostic"), diagnostic_datum(diagnostic))],
)
}
pub fn unsupported_control_diagnostic(policy: &'static str, operation: Symbol) -> Diagnostic {
let mut diagnostic = Diagnostic::error(format!(
"control policy {policy} does not support {operation}"
));
diagnostic.code = Some(control_unsupported_status());
diagnostic
}
pub fn control_result_status(cx: &Cx, result: &Ref) -> Result<Option<Symbol>> {
let Ref::Content(id) = result else {
return Ok(None);
};
let Some(Datum::Node { tag, fields }) = cx.datum_store().get(id)? else {
return Ok(None);
};
if tag != &control_result_tag() {
return Ok(None);
}
Ok(fields.iter().find_map(|(field, value)| {
if field == &Symbol::new("status")
&& let Datum::Symbol(status) = value
{
return Some(status.clone());
}
None
}))
}
pub fn control_prompt_status() -> Symbol {
control_symbol("prompt")
}
pub fn control_capture_status() -> Symbol {
control_symbol("capture")
}
pub fn control_captured_status() -> Symbol {
control_symbol("captured")
}
pub fn control_aborted_status() -> Symbol {
control_symbol("aborted")
}
pub fn control_resumed_status() -> Symbol {
control_symbol("resumed")
}
pub fn control_unsupported_status() -> Symbol {
control_symbol("unsupported")
}
pub fn default_control_prompt() -> Ref {
Ref::Symbol(control_symbol("default-prompt"))
}
pub fn default_control_result_shape() -> Ref {
core_any_ref()
}
fn control_requirements(primary: CapabilityName, multishot: bool) -> Vec<CapabilityName> {
let mut requires = vec![primary];
if multishot {
requires.push(control_multishot_capability());
}
requires
}
fn intern_control_input(
cx: &mut Cx,
operation: Symbol,
mut fields: Vec<(Symbol, Datum)>,
) -> Result<Ref> {
fields.insert(0, (Symbol::new("operation"), Datum::Symbol(operation)));
let id = cx.datum_store_mut().intern(Datum::Node {
tag: control_input_tag(),
fields,
})?;
Ok(Ref::Content(id))
}
fn intern_control_result(
cx: &mut Cx,
status: Symbol,
mut fields: Vec<(Symbol, Datum)>,
) -> Result<Ref> {
fields.insert(0, (Symbol::new("status"), Datum::Symbol(status)));
let id = cx.datum_store_mut().intern(Datum::Node {
tag: control_result_tag(),
fields,
})?;
Ok(Ref::Content(id))
}
fn diagnostic_datum(diagnostic: Diagnostic) -> Datum {
Datum::Node {
tag: core_symbol("Diagnostic"),
fields: vec![
(
Symbol::new("severity"),
Datum::Symbol(severity_symbol(diagnostic.severity)),
),
(Symbol::new("message"), Datum::String(diagnostic.message)),
(
Symbol::new("code"),
diagnostic.code.map_or(Datum::Nil, Datum::Symbol),
),
],
}
}
fn severity_symbol(severity: Severity) -> Symbol {
match severity {
Severity::Error => core_symbol("error"),
Severity::Warning => core_symbol("warning"),
Severity::Info => core_symbol("info"),
Severity::Note => core_symbol("note"),
}
}
fn ref_datum(reference: Ref) -> Datum {
match reference {
Ref::Symbol(symbol) => Datum::Node {
tag: core_symbol("ref"),
fields: vec![
(Symbol::new("kind"), Datum::Symbol(core_symbol("symbol"))),
(Symbol::new("symbol"), Datum::Symbol(symbol)),
],
},
Ref::Content(content) => Datum::Node {
tag: core_symbol("ref"),
fields: vec![
(Symbol::new("kind"), Datum::Symbol(core_symbol("content"))),
(Symbol::new("content"), content_id_datum(content)),
],
},
Ref::Handle(handle) => Datum::Node {
tag: core_symbol("ref"),
fields: vec![
(Symbol::new("kind"), Datum::Symbol(core_symbol("handle"))),
(Symbol::new("id"), handle_id_datum(handle)),
],
},
Ref::Coord(coordinate) => coordinate_datum(coordinate),
}
}
fn coordinate_datum(coordinate: Coordinate) -> Datum {
Datum::Node {
tag: core_symbol("ref"),
fields: vec![
(Symbol::new("kind"), Datum::Symbol(core_symbol("coord"))),
(Symbol::new("space"), Datum::Symbol(coordinate.space)),
(Symbol::new("ordinal"), content_id_datum(coordinate.ordinal)),
],
}
}
fn content_id_datum(content: ContentId) -> Datum {
Datum::Node {
tag: core_symbol("content-id"),
fields: vec![
(Symbol::new("algorithm"), Datum::Symbol(content.algorithm)),
(Symbol::new("bytes"), Datum::Bytes(content.bytes.to_vec())),
],
}
}
fn handle_id_datum(handle: HandleId) -> Datum {
Datum::Bytes(handle.0.to_be_bytes().to_vec())
}
fn control_input_tag() -> Symbol {
core_symbol("ControlInput")
}
fn control_result_tag() -> Symbol {
core_symbol("ControlResult")
}
fn control_symbol(name: &str) -> Symbol {
Symbol::qualified("control", name)
}
fn core_symbol(name: &str) -> Symbol {
Symbol::qualified("core", name)
}
#[cfg(test)]
mod tests;