Skip to main content

sim_kernel/
control.rs

1//! Control policy: the contract for delimited prompts, capture, and resume.
2//!
3//! The kernel defines the prompt/capture/abort/resume records and the
4//! [`ControlPolicy`] trait; libraries implement the concrete control behavior.
5
6use std::sync::Arc;
7
8use crate::{
9    capability::{
10        CapabilityName, control_capture_capability, control_multishot_capability,
11        control_prompt_capability, control_resume_capability,
12    },
13    datum::Datum,
14    datum_store::DatumStore,
15    effect::{
16        Effect, effect_abort_op_key, effect_control_abort_kind, effect_control_capture_kind,
17        effect_control_prompt_kind, effect_control_resume_kind, effect_resume_op_key,
18        resolve_effect,
19    },
20    env::Cx,
21    error::{Diagnostic, Result, Severity},
22    id::Symbol,
23    op::core_any_ref,
24    ref_id::{ContentId, Coordinate, HandleId, Ref},
25};
26
27/// Record describing a delimited control prompt to enter.
28#[derive(Clone, Debug, PartialEq, Eq)]
29pub struct ControlPrompt {
30    /// Reference identifying the prompt boundary.
31    pub prompt: Ref,
32    /// Input value supplied to the prompt body.
33    pub input: Ref,
34    /// Shape the prompt result must satisfy.
35    pub result_shape: Ref,
36}
37
38impl ControlPrompt {
39    /// Build a prompt record from its boundary, input, and result shape.
40    pub fn new(prompt: Ref, input: Ref, result_shape: Ref) -> Self {
41        Self {
42            prompt,
43            input,
44            result_shape,
45        }
46    }
47}
48
49/// Record describing a capture of the continuation up to a prompt.
50#[derive(Clone, Debug, PartialEq, Eq)]
51pub struct ControlCapture {
52    /// Reference identifying the prompt boundary captured up to.
53    pub prompt: Ref,
54    /// Reference to the captured continuation.
55    pub continuation: Ref,
56    /// Value delivered to the captured continuation.
57    pub value: Ref,
58    /// Shape the resumed result must satisfy.
59    pub result_shape: Ref,
60    /// Whether the continuation may be resumed more than once.
61    pub multishot: bool,
62}
63
64impl ControlCapture {
65    /// Build a single-shot capture record.
66    pub fn new(prompt: Ref, continuation: Ref, value: Ref, result_shape: Ref) -> Self {
67        Self {
68            prompt,
69            continuation,
70            value,
71            result_shape,
72            multishot: false,
73        }
74    }
75
76    /// Mark the capture as resumable more than once.
77    pub fn multishot(mut self) -> Self {
78        self.multishot = true;
79        self
80    }
81}
82
83/// Record describing an abort that unwinds to a prompt with a value.
84#[derive(Clone, Debug, PartialEq, Eq)]
85pub struct ControlAbort {
86    /// Reference identifying the prompt boundary to unwind to.
87    pub prompt: Ref,
88    /// Value delivered as the prompt result.
89    pub value: Ref,
90    /// Shape the prompt result must satisfy.
91    pub result_shape: Ref,
92}
93
94impl ControlAbort {
95    /// Build an abort record from its prompt, value, and result shape.
96    pub fn new(prompt: Ref, value: Ref, result_shape: Ref) -> Self {
97        Self {
98            prompt,
99            value,
100            result_shape,
101        }
102    }
103}
104
105/// Record describing a resume of a captured continuation with a value.
106#[derive(Clone, Debug, PartialEq, Eq)]
107pub struct ControlResume {
108    /// Reference to the continuation being resumed.
109    pub continuation: Ref,
110    /// Value delivered to the resumed continuation.
111    pub value: Ref,
112    /// Shape the resumed result must satisfy.
113    pub result_shape: Ref,
114}
115
116impl ControlResume {
117    /// Build a resume record from its continuation, value, and result shape.
118    pub fn new(continuation: Ref, value: Ref, result_shape: Ref) -> Self {
119        Self {
120            continuation,
121            value,
122            result_shape,
123        }
124    }
125}
126
127/// Policy implementing delimited control: prompts, capture, abort, and resume.
128///
129/// The kernel defines this contract and the records it consumes; libraries
130/// supply the concrete continuation machinery. Unsupported operations report an
131/// "unsupported" control result rather than failing hard.
132pub trait ControlPolicy: Send + Sync {
133    /// Stable name identifying the policy in diagnostics.
134    fn name(&self) -> &'static str;
135
136    /// Enter a prompt boundary; the default is a no-op.
137    fn enter_prompt(&self, _cx: &mut Cx, _prompt: &ControlPrompt) -> Result<()> {
138        Ok(())
139    }
140
141    /// Capture the continuation up to a prompt; defaults to unsupported.
142    fn capture(&self, cx: &mut Cx, _capture: &ControlCapture) -> Result<Ref> {
143        unsupported_control_result(cx, self.name(), effect_control_capture_kind())
144    }
145
146    /// Abort to a prompt with a value; defaults to unsupported.
147    fn abort(&self, cx: &mut Cx, _abort: &ControlAbort) -> Result<Ref> {
148        unsupported_control_result(cx, self.name(), effect_control_abort_kind())
149    }
150
151    /// Resume a captured continuation with a value; defaults to unsupported.
152    fn resume(&self, cx: &mut Cx, _resume: &ControlResume) -> Result<Ref> {
153        unsupported_control_result(cx, self.name(), effect_control_resume_kind())
154    }
155}
156
157/// Shared, reference-counted handle to a [`ControlPolicy`].
158pub type ControlPolicyRef = Arc<dyn ControlPolicy>;
159
160/// Control policy that supports prompts but rejects capture, abort, and resume.
161#[derive(Default)]
162pub struct NoopControlPolicy;
163
164impl ControlPolicy for NoopControlPolicy {
165    fn name(&self) -> &'static str {
166        "noop-control"
167    }
168}
169
170/// Run `body` inside a control prompt, emitting the prompt effect first.
171pub fn prompt<F>(cx: &mut Cx, prompt: ControlPrompt, body: F) -> Result<Ref>
172where
173    F: FnOnce(&mut Cx) -> Result<Ref>,
174{
175    let effect = prompt_effect(&prompt);
176    resolve_effect(cx, effect, |cx, _effect| {
177        let policy = cx.control_policy_ref();
178        policy.enter_prompt(cx, &prompt)?;
179        body(cx)
180    })
181}
182
183/// Capture the continuation up to a prompt via the active control policy.
184pub fn capture(cx: &mut Cx, capture: ControlCapture) -> Result<Ref> {
185    let effect = capture_effect(cx, &capture)?;
186    resolve_effect(cx, effect, |cx, _effect| {
187        let policy = cx.control_policy_ref();
188        policy.capture(cx, &capture)
189    })
190}
191
192/// Abort to a prompt with a value via the active control policy.
193pub fn abort(cx: &mut Cx, abort: ControlAbort) -> Result<Ref> {
194    let effect = abort_effect(&abort);
195    resolve_effect(cx, effect, |cx, _effect| {
196        let policy = cx.control_policy_ref();
197        policy.abort(cx, &abort)
198    })
199}
200
201/// Resume a captured continuation with a value via the active control policy.
202pub fn resume(cx: &mut Cx, resume: ControlResume) -> Result<Ref> {
203    let effect = resume_effect(&resume);
204    resolve_effect(cx, effect, |cx, _effect| {
205        let policy = cx.control_policy_ref();
206        policy.resume(cx, &resume)
207    })
208}
209
210/// Build the capability-gated effect that requests a control prompt.
211pub fn prompt_effect(prompt: &ControlPrompt) -> Effect {
212    Effect::new(
213        effect_control_prompt_kind(),
214        prompt.prompt.clone(),
215        prompt.input.clone(),
216        prompt.result_shape.clone(),
217        effect_resume_op_key(),
218        effect_abort_op_key(),
219    )
220    .requiring(control_prompt_capability())
221}
222
223/// Build the capability-gated effect that requests a continuation capture.
224pub fn capture_effect(cx: &mut Cx, capture: &ControlCapture) -> Result<Effect> {
225    let input = intern_control_input(
226        cx,
227        control_capture_status(),
228        vec![
229            (
230                Symbol::new("continuation"),
231                ref_datum(capture.continuation.clone()),
232            ),
233            (Symbol::new("value"), ref_datum(capture.value.clone())),
234            (Symbol::new("multishot"), Datum::Bool(capture.multishot)),
235        ],
236    )?;
237    Ok(Effect::new(
238        effect_control_capture_kind(),
239        capture.prompt.clone(),
240        input,
241        capture.result_shape.clone(),
242        effect_resume_op_key(),
243        effect_abort_op_key(),
244    )
245    .with_requirements(control_requirements(
246        control_capture_capability(),
247        capture.multishot,
248    )))
249}
250
251/// Build the capability-gated effect that requests an abort.
252pub fn abort_effect(abort: &ControlAbort) -> Effect {
253    Effect::new(
254        effect_control_abort_kind(),
255        abort.prompt.clone(),
256        abort.value.clone(),
257        abort.result_shape.clone(),
258        effect_resume_op_key(),
259        effect_abort_op_key(),
260    )
261    .requiring(control_capture_capability())
262}
263
264/// Build the capability-gated effect that requests a resume.
265pub fn resume_effect(resume: &ControlResume) -> Effect {
266    Effect::new(
267        effect_control_resume_kind(),
268        resume.continuation.clone(),
269        resume.value.clone(),
270        resume.result_shape.clone(),
271        effect_resume_op_key(),
272        effect_abort_op_key(),
273    )
274    .requiring(control_resume_capability())
275}
276
277/// Intern a control result recording a captured continuation and value.
278pub fn captured_control_result(cx: &mut Cx, continuation: Ref, value: Ref) -> Result<Ref> {
279    intern_control_result(
280        cx,
281        control_captured_status(),
282        vec![
283            (Symbol::new("continuation"), ref_datum(continuation)),
284            (Symbol::new("value"), ref_datum(value)),
285        ],
286    )
287}
288
289/// Intern a control result recording an abort to a prompt with a value.
290pub fn aborted_control_result(cx: &mut Cx, prompt: Ref, value: Ref) -> Result<Ref> {
291    intern_control_result(
292        cx,
293        control_aborted_status(),
294        vec![
295            (Symbol::new("prompt"), ref_datum(prompt)),
296            (Symbol::new("value"), ref_datum(value)),
297        ],
298    )
299}
300
301/// Intern a control result recording a resumed continuation and value.
302pub fn resumed_control_result(cx: &mut Cx, continuation: Ref, value: Ref) -> Result<Ref> {
303    intern_control_result(
304        cx,
305        control_resumed_status(),
306        vec![
307            (Symbol::new("continuation"), ref_datum(continuation)),
308            (Symbol::new("value"), ref_datum(value)),
309        ],
310    )
311}
312
313/// Intern an "unsupported" control result and push its diagnostic, for
314/// policies that do not implement `operation`.
315pub fn unsupported_control_result(
316    cx: &mut Cx,
317    policy: &'static str,
318    operation: Symbol,
319) -> Result<Ref> {
320    let diagnostic = unsupported_control_diagnostic(policy, operation);
321    cx.push_diagnostic(diagnostic.clone());
322    intern_control_result(
323        cx,
324        control_unsupported_status(),
325        vec![(Symbol::new("diagnostic"), diagnostic_datum(diagnostic))],
326    )
327}
328
329/// Build the diagnostic reported when `policy` cannot perform `operation`.
330pub fn unsupported_control_diagnostic(policy: &'static str, operation: Symbol) -> Diagnostic {
331    let mut diagnostic = Diagnostic::error(format!(
332        "control policy {policy} does not support {operation}"
333    ));
334    diagnostic.code = Some(control_unsupported_status());
335    diagnostic
336}
337
338/// Read the status symbol of an interned control result, if `result` is one.
339pub fn control_result_status(cx: &Cx, result: &Ref) -> Result<Option<Symbol>> {
340    let Ref::Content(id) = result else {
341        return Ok(None);
342    };
343    let Some(Datum::Node { tag, fields }) = cx.datum_store().get(id)? else {
344        return Ok(None);
345    };
346    if tag != &control_result_tag() {
347        return Ok(None);
348    }
349    Ok(fields.iter().find_map(|(field, value)| {
350        if field == &Symbol::new("status")
351            && let Datum::Symbol(status) = value
352        {
353            return Some(status.clone());
354        }
355        None
356    }))
357}
358
359/// Status symbol naming a prompt-entry control operation.
360pub fn control_prompt_status() -> Symbol {
361    control_symbol("prompt")
362}
363
364/// Status symbol naming a capture control operation.
365pub fn control_capture_status() -> Symbol {
366    control_symbol("capture")
367}
368
369/// Status symbol for a control result that captured a continuation.
370pub fn control_captured_status() -> Symbol {
371    control_symbol("captured")
372}
373
374/// Status symbol for a control result that aborted to a prompt.
375pub fn control_aborted_status() -> Symbol {
376    control_symbol("aborted")
377}
378
379/// Status symbol for a control result that resumed a continuation.
380pub fn control_resumed_status() -> Symbol {
381    control_symbol("resumed")
382}
383
384/// Status symbol for a control result the policy did not support.
385pub fn control_unsupported_status() -> Symbol {
386    control_symbol("unsupported")
387}
388
389/// The default prompt boundary reference.
390pub fn default_control_prompt() -> Ref {
391    Ref::Symbol(control_symbol("default-prompt"))
392}
393
394/// The default control result shape (the open `core` any shape).
395pub fn default_control_result_shape() -> Ref {
396    core_any_ref()
397}
398
399fn control_requirements(primary: CapabilityName, multishot: bool) -> Vec<CapabilityName> {
400    let mut requires = vec![primary];
401    if multishot {
402        requires.push(control_multishot_capability());
403    }
404    requires
405}
406
407fn intern_control_input(
408    cx: &mut Cx,
409    operation: Symbol,
410    mut fields: Vec<(Symbol, Datum)>,
411) -> Result<Ref> {
412    fields.insert(0, (Symbol::new("operation"), Datum::Symbol(operation)));
413    let id = cx.datum_store_mut().intern(Datum::Node {
414        tag: control_input_tag(),
415        fields,
416    })?;
417    Ok(Ref::Content(id))
418}
419
420fn intern_control_result(
421    cx: &mut Cx,
422    status: Symbol,
423    mut fields: Vec<(Symbol, Datum)>,
424) -> Result<Ref> {
425    fields.insert(0, (Symbol::new("status"), Datum::Symbol(status)));
426    let id = cx.datum_store_mut().intern(Datum::Node {
427        tag: control_result_tag(),
428        fields,
429    })?;
430    Ok(Ref::Content(id))
431}
432
433fn diagnostic_datum(diagnostic: Diagnostic) -> Datum {
434    Datum::Node {
435        tag: core_symbol("Diagnostic"),
436        fields: vec![
437            (
438                Symbol::new("severity"),
439                Datum::Symbol(severity_symbol(diagnostic.severity)),
440            ),
441            (Symbol::new("message"), Datum::String(diagnostic.message)),
442            (
443                Symbol::new("code"),
444                diagnostic.code.map_or(Datum::Nil, Datum::Symbol),
445            ),
446        ],
447    }
448}
449
450fn severity_symbol(severity: Severity) -> Symbol {
451    match severity {
452        Severity::Error => core_symbol("error"),
453        Severity::Warning => core_symbol("warning"),
454        Severity::Info => core_symbol("info"),
455        Severity::Note => core_symbol("note"),
456    }
457}
458
459fn ref_datum(reference: Ref) -> Datum {
460    match reference {
461        Ref::Symbol(symbol) => Datum::Node {
462            tag: core_symbol("ref"),
463            fields: vec![
464                (Symbol::new("kind"), Datum::Symbol(core_symbol("symbol"))),
465                (Symbol::new("symbol"), Datum::Symbol(symbol)),
466            ],
467        },
468        Ref::Content(content) => Datum::Node {
469            tag: core_symbol("ref"),
470            fields: vec![
471                (Symbol::new("kind"), Datum::Symbol(core_symbol("content"))),
472                (Symbol::new("content"), content_id_datum(content)),
473            ],
474        },
475        Ref::Handle(handle) => Datum::Node {
476            tag: core_symbol("ref"),
477            fields: vec![
478                (Symbol::new("kind"), Datum::Symbol(core_symbol("handle"))),
479                (Symbol::new("id"), handle_id_datum(handle)),
480            ],
481        },
482        Ref::Coord(coordinate) => coordinate_datum(coordinate),
483    }
484}
485
486fn coordinate_datum(coordinate: Coordinate) -> Datum {
487    Datum::Node {
488        tag: core_symbol("ref"),
489        fields: vec![
490            (Symbol::new("kind"), Datum::Symbol(core_symbol("coord"))),
491            (Symbol::new("space"), Datum::Symbol(coordinate.space)),
492            (Symbol::new("ordinal"), content_id_datum(coordinate.ordinal)),
493        ],
494    }
495}
496
497fn content_id_datum(content: ContentId) -> Datum {
498    Datum::Node {
499        tag: core_symbol("content-id"),
500        fields: vec![
501            (Symbol::new("algorithm"), Datum::Symbol(content.algorithm)),
502            (Symbol::new("bytes"), Datum::Bytes(content.bytes.to_vec())),
503        ],
504    }
505}
506
507fn handle_id_datum(handle: HandleId) -> Datum {
508    Datum::Bytes(handle.0.to_be_bytes().to_vec())
509}
510
511fn control_input_tag() -> Symbol {
512    core_symbol("ControlInput")
513}
514
515fn control_result_tag() -> Symbol {
516    core_symbol("ControlResult")
517}
518
519fn control_symbol(name: &str) -> Symbol {
520    Symbol::qualified("control", name)
521}
522
523fn core_symbol(name: &str) -> Symbol {
524    Symbol::qualified("core", name)
525}
526
527#[cfg(test)]
528mod tests;