1use std::sync::Arc;
2
3use sim_kernel::{
4 Args, Callable, ClassRef, Cx, Error, Expr, NumberLiteral, Object, ObjectCompat, RawArgs, Ref,
5 Result, Symbol, Value,
6 control::{
7 ControlAbort, ControlCapture, ControlPrompt, ControlResume, abort, capture,
8 default_control_result_shape, prompt, resume,
9 },
10};
11
12use crate::model::{ContinuationValue, ControlResultValue};
13
14#[derive(Clone)]
20pub struct ControlFunction {
21 kind: ControlFunctionKind,
22}
23
24#[derive(Clone, Copy)]
25enum ControlFunctionKind {
26 Prompt,
27 Capture,
28 Abort,
29 Resume,
30 PhysicalSensingTrace,
31}
32
33impl ControlFunction {
34 pub fn prompt() -> Self {
36 Self {
37 kind: ControlFunctionKind::Prompt,
38 }
39 }
40
41 pub fn capture() -> Self {
43 Self {
44 kind: ControlFunctionKind::Capture,
45 }
46 }
47
48 pub fn abort() -> Self {
50 Self {
51 kind: ControlFunctionKind::Abort,
52 }
53 }
54
55 pub fn resume() -> Self {
57 Self {
58 kind: ControlFunctionKind::Resume,
59 }
60 }
61
62 pub fn physical_sensing_trace() -> Self {
64 Self {
65 kind: ControlFunctionKind::PhysicalSensingTrace,
66 }
67 }
68
69 pub fn symbol(&self) -> Symbol {
71 self.kind.symbol()
72 }
73}
74
75impl Object for ControlFunction {
76 fn display(&self, _cx: &mut Cx) -> Result<String> {
77 Ok(format!("#<function {}>", self.kind.symbol()))
78 }
79
80 fn as_any(&self) -> &dyn std::any::Any {
81 self
82 }
83}
84
85impl ObjectCompat for ControlFunction {
86 fn class(&self, cx: &mut Cx) -> Result<ClassRef> {
87 cx.resolve_class(&Symbol::qualified("core", "Function"))
88 }
89
90 fn as_callable(&self) -> Option<&dyn Callable> {
91 Some(self)
92 }
93}
94
95impl Callable for ControlFunction {
96 fn call(&self, cx: &mut Cx, args: Args) -> Result<Value> {
97 self.kind.call(cx, args.into_vec())
98 }
99
100 fn call_exprs(&self, cx: &mut Cx, args: RawArgs) -> Result<Value> {
101 let values = args
102 .into_exprs()
103 .into_iter()
104 .map(|expr| cx.eval_expr(expr))
105 .collect::<Result<Vec<_>>>()?;
106 self.kind.call(cx, values)
107 }
108}
109
110impl ControlFunctionKind {
111 fn symbol(self) -> Symbol {
112 match self {
113 Self::Prompt => prompt_symbol(),
114 Self::Capture => capture_symbol(),
115 Self::Abort => abort_symbol(),
116 Self::Resume => resume_symbol(),
117 Self::PhysicalSensingTrace => physical_sensing_trace_symbol(),
118 }
119 }
120
121 fn call(self, cx: &mut Cx, args: Vec<Value>) -> Result<Value> {
122 match self {
123 Self::Prompt => call_prompt(cx, args),
124 Self::Capture => call_capture(cx, args),
125 Self::Abort => call_abort(cx, args),
126 Self::Resume => call_resume(cx, args),
127 Self::PhysicalSensingTrace => call_physical_sensing_trace(cx, args),
128 }
129 }
130}
131
132pub fn prompt_symbol() -> Symbol {
134 Symbol::qualified("control", "prompt")
135}
136
137pub fn capture_symbol() -> Symbol {
139 Symbol::qualified("control", "capture")
140}
141
142pub fn abort_symbol() -> Symbol {
144 Symbol::qualified("control", "abort")
145}
146
147pub fn resume_symbol() -> Symbol {
149 Symbol::qualified("control", "resume")
150}
151
152pub fn physical_sensing_trace_symbol() -> Symbol {
154 Symbol::qualified("control", "physical-sensing-trace")
155}
156
157fn call_prompt(cx: &mut Cx, args: Vec<Value>) -> Result<Value> {
158 let refs = refs_from_args(cx, args, "control/prompt")?;
159 let [prompt_ref, value_ref] = refs.as_slice() else {
160 return Err(arity_error("control/prompt", "prompt value"));
161 };
162 let prompt_ref = prompt_ref.clone();
163 let value_ref = value_ref.clone();
164 let result = prompt(
165 cx,
166 ControlPrompt::new(
167 prompt_ref,
168 value_ref.clone(),
169 default_control_result_shape(),
170 ),
171 |_cx| Ok(value_ref),
172 )?;
173 control_result_value(cx, result)
174}
175
176fn call_capture(cx: &mut Cx, args: Vec<Value>) -> Result<Value> {
177 let multishot = optional_bool_arg(cx, args.get(3))?;
178 let refs = refs_from_args(cx, args.into_iter().take(3).collect(), "control/capture")?;
179 let [prompt_ref, continuation_ref, value_ref] = refs.as_slice() else {
180 return Err(arity_error(
181 "control/capture",
182 "prompt continuation value [multishot]",
183 ));
184 };
185 let mut request = ControlCapture::new(
186 prompt_ref.clone(),
187 continuation_ref.clone(),
188 value_ref.clone(),
189 default_control_result_shape(),
190 );
191 if multishot {
192 request = request.multishot();
193 }
194 let capture_result = capture(cx, request)?;
195 cx.factory().opaque(Arc::new(ContinuationValue::new(
196 continuation_ref.clone(),
197 capture_result,
198 multishot,
199 )))
200}
201
202fn call_abort(cx: &mut Cx, args: Vec<Value>) -> Result<Value> {
203 let refs = refs_from_args(cx, args, "control/abort")?;
204 let [prompt_ref, value_ref] = refs.as_slice() else {
205 return Err(arity_error("control/abort", "prompt value"));
206 };
207 let prompt_ref = prompt_ref.clone();
208 let value_ref = value_ref.clone();
209 let result = abort(
210 cx,
211 ControlAbort::new(prompt_ref, value_ref, default_control_result_shape()),
212 )?;
213 control_result_value(cx, result)
214}
215
216fn call_resume(cx: &mut Cx, args: Vec<Value>) -> Result<Value> {
217 if args.len() != 2 {
218 return Err(arity_error("control/resume", "continuation value"));
219 }
220 let continuation = continuation_ref(cx, &args[0])?;
221 let value = value_ref(cx, &args[1], "control/resume value")?;
222 let result = resume(
223 cx,
224 ControlResume::new(continuation, value, default_control_result_shape()),
225 )?;
226 control_result_value(cx, result)
227}
228
229fn call_physical_sensing_trace(cx: &mut Cx, args: Vec<Value>) -> Result<Value> {
230 if !args.is_empty() {
231 return Err(arity_error(
232 "control/physical-sensing-trace",
233 "no arguments",
234 ));
235 }
236 cx.factory().expr(physical_sensing_trace_expr())
237}
238
239fn physical_sensing_trace_expr() -> Expr {
240 list(vec![
241 atom_or_i64("physical-sensing-trace"),
242 list(vec![
243 atom_or_i64("id"),
244 atom_or_i64("a30-021-physical-sensing"),
245 ]),
246 list(vec![
247 atom_or_i64("fixture"),
248 list(vec![
249 atom_or_i64("source"),
250 atom_or_i64("synthetic-sensor-stream"),
251 ]),
252 list(vec![atom_or_i64("media"), atom_or_i64("copied-no")]),
253 list(vec![atom_or_i64("device"), atom_or_i64("live-device-none")]),
254 ]),
255 list(vec![
256 atom_or_i64("sensor-stream"),
257 list(vec![
258 atom_or_i64("runner"),
259 atom_or_i64("fake-sensor-stream"),
260 ]),
261 list(vec![
262 atom_or_i64("frame"),
263 atom_or_i64("1"),
264 atom_or_i64("position"),
265 atom_or_i64("22"),
266 atom_or_i64("velocity"),
267 atom_or_i64("3"),
268 ]),
269 list(vec![
270 atom_or_i64("frame"),
271 atom_or_i64("2"),
272 atom_or_i64("position"),
273 atom_or_i64("24"),
274 atom_or_i64("velocity"),
275 atom_or_i64("2"),
276 ]),
277 list(vec![
278 atom_or_i64("frame"),
279 atom_or_i64("3"),
280 atom_or_i64("position"),
281 atom_or_i64("26"),
282 atom_or_i64("velocity"),
283 atom_or_i64("1"),
284 ]),
285 ]),
286 list(vec![
287 atom_or_i64("temporal-average"),
288 list(vec![atom_or_i64("window"), atom_or_i64("3")]),
289 list(vec![atom_or_i64("position"), atom_or_i64("24")]),
290 list(vec![atom_or_i64("velocity"), atom_or_i64("2")]),
291 ]),
292 list(vec![
293 atom_or_i64("controller"),
294 list(vec![atom_or_i64("kind"), atom_or_i64("proportional")]),
295 list(vec![atom_or_i64("setpoint"), atom_or_i64("30")]),
296 list(vec![atom_or_i64("gain"), atom_or_i64("2")]),
297 list(vec![atom_or_i64("deadband"), atom_or_i64("2")]),
298 list(vec![atom_or_i64("hysteresis"), atom_or_i64("enabled")]),
299 ]),
300 list(vec![
301 atom_or_i64("control-output"),
302 list(vec![atom_or_i64("error"), atom_or_i64("6")]),
303 list(vec![atom_or_i64("command"), atom_or_i64("increase-12")]),
304 list(vec![atom_or_i64("clamped"), atom_or_i64("no")]),
305 list(vec![
306 atom_or_i64("next-state"),
307 atom_or_i64("approach-setpoint"),
308 ]),
309 ]),
310 list(vec![
311 atom_or_i64("answer"),
312 atom_or_i64("increase-actuator-by-12"),
313 ]),
314 list(vec![
315 atom_or_i64("effect-ledger"),
316 list(vec![
317 atom_or_i64("effect"),
318 atom_or_i64("read-fake-sensor-stream"),
319 atom_or_i64("deterministic"),
320 ]),
321 list(vec![
322 atom_or_i64("effect"),
323 atom_or_i64("average-window-three"),
324 atom_or_i64("pass"),
325 ]),
326 list(vec![
327 atom_or_i64("effect"),
328 atom_or_i64("apply-deadband"),
329 atom_or_i64("active"),
330 ]),
331 list(vec![
332 atom_or_i64("effect"),
333 atom_or_i64("emit-control-output"),
334 atom_or_i64("increase-12"),
335 ]),
336 ]),
337 ])
338}
339
340fn list(items: Vec<Expr>) -> Expr {
341 Expr::List(items)
342}
343
344fn atom_or_i64(name: &str) -> Expr {
345 if name.as_bytes().iter().all(u8::is_ascii_digit) {
346 return Expr::Number(NumberLiteral {
347 domain: Symbol::qualified("numbers", "i64"),
348 canonical: name.to_owned(),
349 });
350 }
351 Expr::Symbol(Symbol::new(name))
352}
353
354fn refs_from_args(cx: &mut Cx, args: Vec<Value>, context: &'static str) -> Result<Vec<Ref>> {
355 args.iter()
356 .map(|value| value_ref(cx, value, context))
357 .collect()
358}
359
360fn continuation_ref(cx: &mut Cx, value: &Value) -> Result<Ref> {
361 if let Some(continuation) = value.object().downcast_ref::<ContinuationValue>() {
362 return Ok(continuation.continuation().clone());
363 }
364 value_ref(cx, value, "control continuation")
365}
366
367fn value_ref(cx: &mut Cx, value: &Value, context: &'static str) -> Result<Ref> {
368 if let Some(result) = value.object().downcast_ref::<ControlResultValue>() {
369 return Ok(result.reference().clone());
370 }
371 let expr = value.object().as_expr(cx)?;
372 match expr {
373 Expr::Symbol(symbol) => Ok(Ref::Symbol(symbol)),
374 _ => Err(Error::TypeMismatch {
375 expected: context,
376 found: "non-ref value",
377 }),
378 }
379}
380
381fn optional_bool_arg(cx: &mut Cx, value: Option<&Value>) -> Result<bool> {
382 let Some(value) = value else {
383 return Ok(false);
384 };
385 match value.object().as_expr(cx)? {
386 Expr::Bool(value) => Ok(value),
387 _ => Err(Error::TypeMismatch {
388 expected: "bool",
389 found: "non-bool",
390 }),
391 }
392}
393
394fn control_result_value(cx: &mut Cx, reference: Ref) -> Result<Value> {
395 cx.factory()
396 .opaque(Arc::new(ControlResultValue::new(reference)))
397}
398
399fn arity_error(function: &'static str, expected: &'static str) -> Error {
400 Error::Eval(format!("{function} expects {expected}"))
401}
402
403#[cfg(test)]
404mod tests {
405 use sim_kernel::{Expr, NumberLiteral, Symbol};
406
407 use super::atom_or_i64;
408
409 #[test]
410 fn atom_or_i64_preserves_fixture_atom_policy() {
411 assert_eq!(atom_or_i64("ready"), Expr::Symbol(Symbol::new("ready")));
412 assert_eq!(
413 atom_or_i64("42"),
414 Expr::Number(NumberLiteral {
415 domain: Symbol::qualified("numbers", "i64"),
416 canonical: "42".to_owned(),
417 })
418 );
419 }
420}