Skip to main content

sim_lib_control/
nonlocal.rs

1use sim_kernel::{
2    Cx, Error, Ref, Result, Symbol,
3    control::{ControlAbort, abort, default_control_result_shape},
4};
5
6use crate::ControlResultValue;
7
8/// The flavor of a non-local exit: loop break, loop continue, or block return.
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub enum NonLocalExitKind {
11    /// Exit the enclosing labeled loop.
12    Break,
13    /// Continue to the next iteration of the enclosing labeled loop.
14    Next,
15    /// Return from the enclosing labeled block.
16    Return,
17}
18
19/// A labeled escape target: a label symbol bound to a control prompt.
20///
21/// Names a dynamic-extent landing site so a [`NonLocalExit`] can abort to the
22/// matching prompt by label.
23#[derive(Clone, Debug, PartialEq, Eq)]
24pub struct LabeledPrompt {
25    label: Symbol,
26    prompt: Ref,
27}
28
29impl LabeledPrompt {
30    /// Binds `label` to the escape `prompt`.
31    pub fn new(label: Symbol, prompt: Ref) -> Self {
32        Self { label, prompt }
33    }
34
35    /// Returns the escape label.
36    pub fn label(&self) -> &Symbol {
37        &self.label
38    }
39
40    /// Returns the prompt this label aborts to.
41    pub fn prompt(&self) -> &Ref {
42        &self.prompt
43    }
44}
45
46/// A pending non-local exit: a kind, a target label, and a carried value.
47///
48/// Resolved by [`escape_to_label`], which aborts to the [`LabeledPrompt`] whose
49/// label matches.
50#[derive(Clone, Debug, PartialEq, Eq)]
51pub struct NonLocalExit {
52    kind: NonLocalExitKind,
53    label: Symbol,
54    value: Ref,
55}
56
57impl NonLocalExit {
58    /// Builds a non-local exit of `kind` targeting `label` and carrying
59    /// `value`.
60    pub fn new(kind: NonLocalExitKind, label: Symbol, value: Ref) -> Self {
61        Self { kind, label, value }
62    }
63
64    /// Builds a [`NonLocalExitKind::Break`] exit to `label` with `value`.
65    pub fn break_to(label: Symbol, value: Ref) -> Self {
66        Self::new(NonLocalExitKind::Break, label, value)
67    }
68
69    /// Builds a [`NonLocalExitKind::Next`] exit to `label` with `value`.
70    pub fn next_to(label: Symbol, value: Ref) -> Self {
71        Self::new(NonLocalExitKind::Next, label, value)
72    }
73
74    /// Builds a [`NonLocalExitKind::Return`] exit to `label` with `value`.
75    pub fn return_to(label: Symbol, value: Ref) -> Self {
76        Self::new(NonLocalExitKind::Return, label, value)
77    }
78
79    /// Returns the exit kind.
80    pub fn kind(&self) -> NonLocalExitKind {
81        self.kind
82    }
83
84    /// Returns the target label.
85    pub fn label(&self) -> &Symbol {
86        &self.label
87    }
88
89    /// Returns the value carried out of the block.
90    pub fn value(&self) -> &Ref {
91        &self.value
92    }
93}
94
95/// Performs `exit` by aborting to the matching [`LabeledPrompt`] in `prompts`.
96///
97/// Searches `prompts` innermost-first for the exit's label and aborts to it,
98/// returning the [`ControlResultValue`] the abort produces. Fails with
99/// [`Error::Eval`](sim_kernel::Error::Eval) when no labeled prompt matches.
100pub fn escape_to_label(
101    cx: &mut Cx,
102    prompts: &[LabeledPrompt],
103    exit: NonLocalExit,
104) -> Result<ControlResultValue> {
105    let prompt = prompts
106        .iter()
107        .rev()
108        .find(|prompt| prompt.label() == exit.label())
109        .ok_or_else(|| Error::Eval(format!("no labeled prompt for {}", exit.label())))?;
110    let result = abort(
111        cx,
112        ControlAbort::new(
113            prompt.prompt().clone(),
114            exit.value().clone(),
115            default_control_result_shape(),
116        ),
117    )?;
118    Ok(ControlResultValue::new(result))
119}