1use 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#[derive(Clone, Debug, PartialEq, Eq)]
29pub struct ControlPrompt {
30 pub prompt: Ref,
32 pub input: Ref,
34 pub result_shape: Ref,
36}
37
38impl ControlPrompt {
39 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#[derive(Clone, Debug, PartialEq, Eq)]
51pub struct ControlCapture {
52 pub prompt: Ref,
54 pub continuation: Ref,
56 pub value: Ref,
58 pub result_shape: Ref,
60 pub multishot: bool,
62}
63
64impl ControlCapture {
65 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 pub fn multishot(mut self) -> Self {
78 self.multishot = true;
79 self
80 }
81}
82
83#[derive(Clone, Debug, PartialEq, Eq)]
85pub struct ControlAbort {
86 pub prompt: Ref,
88 pub value: Ref,
90 pub result_shape: Ref,
92}
93
94impl ControlAbort {
95 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#[derive(Clone, Debug, PartialEq, Eq)]
107pub struct ControlResume {
108 pub continuation: Ref,
110 pub value: Ref,
112 pub result_shape: Ref,
114}
115
116impl ControlResume {
117 pub fn new(continuation: Ref, value: Ref, result_shape: Ref) -> Self {
119 Self {
120 continuation,
121 value,
122 result_shape,
123 }
124 }
125}
126
127pub trait ControlPolicy: Send + Sync {
133 fn name(&self) -> &'static str;
135
136 fn enter_prompt(&self, _cx: &mut Cx, _prompt: &ControlPrompt) -> Result<()> {
138 Ok(())
139 }
140
141 fn capture(&self, cx: &mut Cx, _capture: &ControlCapture) -> Result<Ref> {
143 unsupported_control_result(cx, self.name(), effect_control_capture_kind())
144 }
145
146 fn abort(&self, cx: &mut Cx, _abort: &ControlAbort) -> Result<Ref> {
148 unsupported_control_result(cx, self.name(), effect_control_abort_kind())
149 }
150
151 fn resume(&self, cx: &mut Cx, _resume: &ControlResume) -> Result<Ref> {
153 unsupported_control_result(cx, self.name(), effect_control_resume_kind())
154 }
155}
156
157pub type ControlPolicyRef = Arc<dyn ControlPolicy>;
159
160#[derive(Default)]
162pub struct NoopControlPolicy;
163
164impl ControlPolicy for NoopControlPolicy {
165 fn name(&self) -> &'static str {
166 "noop-control"
167 }
168}
169
170pub 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
183pub 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
192pub 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
201pub 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
210pub 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
223pub 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
251pub 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
264pub 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
277pub 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
289pub 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
301pub 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
313pub 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
329pub 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
338pub 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
359pub fn control_prompt_status() -> Symbol {
361 control_symbol("prompt")
362}
363
364pub fn control_capture_status() -> Symbol {
366 control_symbol("capture")
367}
368
369pub fn control_captured_status() -> Symbol {
371 control_symbol("captured")
372}
373
374pub fn control_aborted_status() -> Symbol {
376 control_symbol("aborted")
377}
378
379pub fn control_resumed_status() -> Symbol {
381 control_symbol("resumed")
382}
383
384pub fn control_unsupported_status() -> Symbol {
386 control_symbol("unsupported")
387}
388
389pub fn default_control_prompt() -> Ref {
391 Ref::Symbol(control_symbol("default-prompt"))
392}
393
394pub 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;