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(cx.fresh_handle(), &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(cx.fresh_handle(), &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(cx.fresh_handle(), &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(id: crate::HandleId, prompt: &ControlPrompt) -> Effect {
212 Effect::new(
213 id,
214 effect_control_prompt_kind(),
215 prompt.prompt.clone(),
216 prompt.input.clone(),
217 prompt.result_shape.clone(),
218 effect_resume_op_key(),
219 effect_abort_op_key(),
220 )
221 .requiring(control_prompt_capability())
222}
223
224pub fn capture_effect(cx: &mut Cx, capture: &ControlCapture) -> Result<Effect> {
226 let input = intern_control_input(
227 cx,
228 control_capture_status(),
229 vec![
230 (
231 Symbol::new("continuation"),
232 ref_datum(capture.continuation.clone()),
233 ),
234 (Symbol::new("value"), ref_datum(capture.value.clone())),
235 (Symbol::new("multishot"), Datum::Bool(capture.multishot)),
236 ],
237 )?;
238 Ok(Effect::new(
239 cx.fresh_handle(),
240 effect_control_capture_kind(),
241 capture.prompt.clone(),
242 input,
243 capture.result_shape.clone(),
244 effect_resume_op_key(),
245 effect_abort_op_key(),
246 )
247 .with_requirements(control_requirements(
248 control_capture_capability(),
249 capture.multishot,
250 )))
251}
252
253pub fn abort_effect(id: crate::HandleId, abort: &ControlAbort) -> Effect {
255 Effect::new(
256 id,
257 effect_control_abort_kind(),
258 abort.prompt.clone(),
259 abort.value.clone(),
260 abort.result_shape.clone(),
261 effect_resume_op_key(),
262 effect_abort_op_key(),
263 )
264 .requiring(control_capture_capability())
265}
266
267pub fn resume_effect(id: crate::HandleId, resume: &ControlResume) -> Effect {
269 Effect::new(
270 id,
271 effect_control_resume_kind(),
272 resume.continuation.clone(),
273 resume.value.clone(),
274 resume.result_shape.clone(),
275 effect_resume_op_key(),
276 effect_abort_op_key(),
277 )
278 .requiring(control_resume_capability())
279}
280
281pub fn captured_control_result(cx: &mut Cx, continuation: Ref, value: Ref) -> Result<Ref> {
283 intern_control_result(
284 cx,
285 control_captured_status(),
286 vec![
287 (Symbol::new("continuation"), ref_datum(continuation)),
288 (Symbol::new("value"), ref_datum(value)),
289 ],
290 )
291}
292
293pub fn aborted_control_result(cx: &mut Cx, prompt: Ref, value: Ref) -> Result<Ref> {
295 intern_control_result(
296 cx,
297 control_aborted_status(),
298 vec![
299 (Symbol::new("prompt"), ref_datum(prompt)),
300 (Symbol::new("value"), ref_datum(value)),
301 ],
302 )
303}
304
305pub fn resumed_control_result(cx: &mut Cx, continuation: Ref, value: Ref) -> Result<Ref> {
307 intern_control_result(
308 cx,
309 control_resumed_status(),
310 vec![
311 (Symbol::new("continuation"), ref_datum(continuation)),
312 (Symbol::new("value"), ref_datum(value)),
313 ],
314 )
315}
316
317pub fn unsupported_control_result(
320 cx: &mut Cx,
321 policy: &'static str,
322 operation: Symbol,
323) -> Result<Ref> {
324 let diagnostic = unsupported_control_diagnostic(policy, operation);
325 cx.push_diagnostic(diagnostic.clone());
326 intern_control_result(
327 cx,
328 control_unsupported_status(),
329 vec![(Symbol::new("diagnostic"), diagnostic_datum(diagnostic))],
330 )
331}
332
333pub fn unsupported_control_diagnostic(policy: &'static str, operation: Symbol) -> Diagnostic {
335 let mut diagnostic = Diagnostic::error(format!(
336 "control policy {policy} does not support {operation}"
337 ));
338 diagnostic.code = Some(control_unsupported_status());
339 diagnostic
340}
341
342pub fn control_result_status(cx: &Cx, result: &Ref) -> Result<Option<Symbol>> {
344 let Ref::Content(id) = result else {
345 return Ok(None);
346 };
347 let Some(Datum::Node { tag, fields }) = cx.datum_store().get(id)? else {
348 return Ok(None);
349 };
350 if tag != &control_result_tag() {
351 return Ok(None);
352 }
353 Ok(fields.iter().find_map(|(field, value)| {
354 if field == &Symbol::new("status")
355 && let Datum::Symbol(status) = value
356 {
357 return Some(status.clone());
358 }
359 None
360 }))
361}
362
363pub fn control_prompt_status() -> Symbol {
365 control_symbol("prompt")
366}
367
368pub fn control_capture_status() -> Symbol {
370 control_symbol("capture")
371}
372
373pub fn control_captured_status() -> Symbol {
375 control_symbol("captured")
376}
377
378pub fn control_aborted_status() -> Symbol {
380 control_symbol("aborted")
381}
382
383pub fn control_resumed_status() -> Symbol {
385 control_symbol("resumed")
386}
387
388pub fn control_unsupported_status() -> Symbol {
390 control_symbol("unsupported")
391}
392
393pub fn default_control_prompt() -> Ref {
395 Ref::Symbol(control_symbol("default-prompt"))
396}
397
398pub fn default_control_result_shape() -> Ref {
400 core_any_ref()
401}
402
403fn control_requirements(primary: CapabilityName, multishot: bool) -> Vec<CapabilityName> {
404 let mut requires = vec![primary];
405 if multishot {
406 requires.push(control_multishot_capability());
407 }
408 requires
409}
410
411fn intern_control_input(
412 cx: &mut Cx,
413 operation: Symbol,
414 mut fields: Vec<(Symbol, Datum)>,
415) -> Result<Ref> {
416 fields.insert(0, (Symbol::new("operation"), Datum::Symbol(operation)));
417 let id = cx.datum_store_mut().intern(Datum::Node {
418 tag: control_input_tag(),
419 fields,
420 })?;
421 Ok(Ref::Content(id))
422}
423
424fn intern_control_result(
425 cx: &mut Cx,
426 status: Symbol,
427 mut fields: Vec<(Symbol, Datum)>,
428) -> Result<Ref> {
429 fields.insert(0, (Symbol::new("status"), Datum::Symbol(status)));
430 let id = cx.datum_store_mut().intern(Datum::Node {
431 tag: control_result_tag(),
432 fields,
433 })?;
434 Ok(Ref::Content(id))
435}
436
437fn diagnostic_datum(diagnostic: Diagnostic) -> Datum {
438 Datum::Node {
439 tag: core_symbol("Diagnostic"),
440 fields: vec![
441 (
442 Symbol::new("severity"),
443 Datum::Symbol(severity_symbol(diagnostic.severity)),
444 ),
445 (Symbol::new("message"), Datum::String(diagnostic.message)),
446 (
447 Symbol::new("code"),
448 diagnostic.code.map_or(Datum::Nil, Datum::Symbol),
449 ),
450 ],
451 }
452}
453
454fn severity_symbol(severity: Severity) -> Symbol {
455 match severity {
456 Severity::Error => core_symbol("error"),
457 Severity::Warning => core_symbol("warning"),
458 Severity::Info => core_symbol("info"),
459 Severity::Note => core_symbol("note"),
460 }
461}
462
463fn ref_datum(reference: Ref) -> Datum {
464 match reference {
465 Ref::Symbol(symbol) => Datum::Node {
466 tag: core_symbol("ref"),
467 fields: vec![
468 (Symbol::new("kind"), Datum::Symbol(core_symbol("symbol"))),
469 (Symbol::new("symbol"), Datum::Symbol(symbol)),
470 ],
471 },
472 Ref::Content(content) => Datum::Node {
473 tag: core_symbol("ref"),
474 fields: vec![
475 (Symbol::new("kind"), Datum::Symbol(core_symbol("content"))),
476 (Symbol::new("content"), content_id_datum(content)),
477 ],
478 },
479 Ref::Handle(handle) => Datum::Node {
480 tag: core_symbol("ref"),
481 fields: vec![
482 (Symbol::new("kind"), Datum::Symbol(core_symbol("handle"))),
483 (Symbol::new("id"), handle_id_datum(handle)),
484 ],
485 },
486 Ref::Coord(coordinate) => coordinate_datum(coordinate),
487 }
488}
489
490fn coordinate_datum(coordinate: Coordinate) -> Datum {
491 Datum::Node {
492 tag: core_symbol("ref"),
493 fields: vec![
494 (Symbol::new("kind"), Datum::Symbol(core_symbol("coord"))),
495 (Symbol::new("space"), Datum::Symbol(coordinate.space)),
496 (Symbol::new("ordinal"), content_id_datum(coordinate.ordinal)),
497 ],
498 }
499}
500
501fn content_id_datum(content: ContentId) -> Datum {
502 Datum::Node {
503 tag: core_symbol("content-id"),
504 fields: vec![
505 (Symbol::new("algorithm"), Datum::Symbol(content.algorithm)),
506 (Symbol::new("bytes"), Datum::Bytes(content.bytes.to_vec())),
507 ],
508 }
509}
510
511fn handle_id_datum(handle: HandleId) -> Datum {
512 Datum::Bytes(handle.0.to_be_bytes().to_vec())
513}
514
515fn control_input_tag() -> Symbol {
516 core_symbol("ControlInput")
517}
518
519fn control_result_tag() -> Symbol {
520 core_symbol("ControlResult")
521}
522
523fn control_symbol(name: &str) -> Symbol {
524 Symbol::qualified("control", name)
525}
526
527fn core_symbol(name: &str) -> Symbol {
528 Symbol::qualified("core", name)
529}
530
531#[cfg(test)]
532mod tests;