1use alloc::boxed::Box;
18use alloc::collections::VecDeque;
19use alloc::format;
20use alloc::string::{String, ToString};
21use alloc::vec::Vec;
22
23use crate::bitset::StateSet;
24use crate::compile::{CompiledChart, ROOT, StateIdx, TransIdx};
25use crate::datamodel::{
26 Datamodel, Environment, EvalError, Event, EventType, SessionInfo, StateQuery, Value,
27};
28use crate::ir::{Binding, Content, Data, Exec, ExecKind, Invoke, Param, Send, StateKind};
29use crate::kernel::{self, History};
30use crate::trace::{MacrostepTrace, MicrostepTrace, TraceInput, TransitionRef};
31
32pub const DEFAULT_MICROSTEP_BUDGET: u32 = 10_000;
34pub const SCXML_EVENT_PROCESSOR: &str = "http://www.w3.org/TR/scxml/#SCXMLEventProcessor";
36pub const SCXML_INVOKE_TYPE: &str = "http://www.w3.org/TR/scxml/";
38
39#[derive(Clone, Debug, PartialEq, Eq)]
41#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
42pub struct Options {
43 pub microstep_budget: u32,
46 pub send_types: Vec<String>,
48 pub invoke_types: Vec<String>,
50}
51
52impl Default for Options {
53 fn default() -> Self {
54 Options {
55 microstep_budget: DEFAULT_MICROSTEP_BUDGET,
56 send_types: alloc::vec![SCXML_EVENT_PROCESSOR.to_string(), "scxml".to_string()],
57 invoke_types: alloc::vec![
58 SCXML_INVOKE_TYPE.to_string(),
59 "http://www.w3.org/TR/scxml".to_string(),
60 "scxml".to_string(),
61 ],
62 }
63 }
64}
65
66#[derive(Clone, Copy, Debug, PartialEq, Eq)]
68#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
69pub enum Status {
70 Running,
72 Terminated,
74}
75
76#[derive(Clone, Debug, PartialEq, Eq)]
78#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
79pub struct Invocation {
80 pub state: StateIdx,
82 pub index: usize,
84 pub invokeid: String,
86 pub autoforward: bool,
88 pub completed: bool,
90}
91
92#[derive(Clone, Debug, PartialEq)]
99#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
100pub struct Session {
101 pub sessionid: String,
103 pub status: Status,
105 pub configuration: StateSet,
107 pub history: History,
109 pub internal_queue: VecDeque<Event>,
111 pub invocations: Vec<Invocation>,
113 pub data_initialized: StateSet,
115 pub send_counter: u64,
117 pub invoke_counter: u64,
119 pub macrosteps: u64,
121 pub options: Options,
123}
124
125impl Session {
126 pub fn is_terminated(&self) -> bool {
128 self.status == Status::Terminated
129 }
130
131 pub fn active(&self, chart: &CompiledChart) -> Vec<String> {
133 chart.ids(&self.configuration)
134 }
135
136 pub fn is_active(&self, chart: &CompiledChart, id: &str) -> bool {
138 chart
139 .index_of(id)
140 .is_some_and(|i| self.configuration.contains(i))
141 }
142}
143
144#[derive(Clone, Debug, Default, PartialEq)]
146#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
147pub struct StartInput {
148 pub info: SessionInfo,
150 pub env: Environment,
152 pub options: Options,
154 #[cfg_attr(feature = "serde", serde(default))]
159 pub data: Vec<(String, Value)>,
160 #[cfg_attr(feature = "serde", serde(default))]
167 pub sessions: Option<Vec<String>>,
168}
169
170#[derive(Clone, Debug, PartialEq)]
172#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
173pub enum InputKind {
174 Event(Event),
176 Cancel,
178}
179
180#[derive(Clone, Debug, PartialEq)]
182#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
183pub struct Input {
184 pub env: Environment,
186 pub kind: InputKind,
188 #[cfg_attr(feature = "serde", serde(default))]
195 pub sessions: Option<Vec<String>>,
196}
197
198impl Input {
199 pub fn event(event: Event, env: Environment) -> Self {
201 Input {
202 env,
203 kind: InputKind::Event(event),
204 sessions: None,
205 }
206 }
207
208 pub fn cancel(env: Environment) -> Self {
210 Input {
211 env,
212 kind: InputKind::Cancel,
213 sessions: None,
214 }
215 }
216}
217
218#[derive(Clone, Debug, PartialEq)]
220#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
221pub struct SendRequest {
222 pub sendid: String,
224 pub event: Event,
227 pub target: Option<String>,
229 pub type_: String,
231 pub delay_ms: u64,
233}
234
235#[derive(Clone, Debug, PartialEq)]
237#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
238pub struct InvokeRequest {
239 pub invokeid: String,
241 pub state: String,
243 pub type_: String,
245 pub src: Option<String>,
247 pub content: Option<Value>,
249 pub params: Value,
251 pub autoforward: bool,
253}
254
255#[derive(Clone, Debug, PartialEq)]
258#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
259pub enum Effect {
260 Send(SendRequest),
262 Cancel {
264 sendid: String,
266 },
267 Invoke(InvokeRequest),
269 CancelInvoke {
272 invokeid: String,
274 },
275 Forward {
277 invokeid: String,
279 event: Event,
281 },
282 Log {
284 label: Option<String>,
286 value: Option<Value>,
288 },
289 SessionDone {
291 data: Value,
293 cancelled: bool,
295 },
296}
297
298#[derive(Clone, Debug, PartialEq)]
300pub struct Outcome {
301 pub session: Session,
303 pub effects: Vec<Effect>,
305 pub trace: MacrostepTrace,
307}
308
309#[derive(Clone, Debug, PartialEq)]
311pub enum StartError {
312 UnsupportedDatamodel {
314 name: String,
316 },
317 DatamodelMismatch {
319 chart: String,
321 given: String,
323 },
324 Datamodel(EvalError),
326 ChartRefused {
329 datamodel: String,
331 problems: Vec<String>,
333 },
334 BudgetExceeded {
336 budget: u32,
338 partial: Box<Outcome>,
340 },
341}
342
343#[derive(Clone, Debug, PartialEq)]
345pub enum StepError {
346 Terminated,
348 BudgetExceeded {
350 budget: u32,
352 partial: Box<Outcome>,
354 },
355}
356
357impl core::fmt::Display for StartError {
358 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
359 match self {
360 StartError::UnsupportedDatamodel { name } => {
361 write!(f, "unsupported datamodel `{name}`")
362 }
363 StartError::DatamodelMismatch { chart, given } => {
364 write!(
365 f,
366 "the chart names datamodel `{chart}` but `{given}` was given"
367 )
368 }
369 StartError::Datamodel(e) => write!(f, "the datamodel failed to initialize: {e}"),
370 StartError::ChartRefused {
371 datamodel,
372 problems,
373 } => {
374 write!(f, "datamodel `{datamodel}` refused the chart")?;
375 for p in problems {
376 write!(f, "\n{p}")?;
377 }
378 Ok(())
379 }
380 StartError::BudgetExceeded { budget, .. } => {
381 write!(
382 f,
383 "the start macrostep exceeded the microstep budget of {budget}"
384 )
385 }
386 }
387 }
388}
389
390impl core::error::Error for StartError {
391 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
392 match self {
393 StartError::Datamodel(e) => Some(e),
394 _ => None,
395 }
396 }
397}
398
399impl core::fmt::Display for StepError {
400 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
401 match self {
402 StepError::Terminated => f.write_str("the session has terminated"),
403 StepError::BudgetExceeded { budget, .. } => {
404 write!(f, "the macrostep exceeded the microstep budget of {budget}")
405 }
406 }
407 }
408}
409
410impl core::error::Error for StepError {}
411
412fn new_session(chart: &CompiledChart, sessionid: String, options: Options) -> Session {
414 Session {
415 sessionid,
416 status: Status::Running,
417 configuration: chart.empty_set(),
418 history: History::new(chart.len()),
419 internal_queue: VecDeque::new(),
420 invocations: Vec::new(),
421 data_initialized: chart.empty_set(),
422 send_counter: 0,
423 invoke_counter: 0,
424 macrosteps: 0,
425 options,
426 }
427}
428
429pub fn start(
436 chart: &CompiledChart,
437 input: StartInput,
438 dm: &mut dyn Datamodel,
439) -> Result<Outcome, StartError> {
440 if dm.name() != chart.datamodel() {
441 return Err(StartError::DatamodelMismatch {
442 chart: chart.datamodel().to_string(),
443 given: dm.name().to_string(),
444 });
445 }
446 dm.init_session(&input.info)
447 .map_err(StartError::Datamodel)?;
448 dm.set_environment(&input.env);
449 let session = new_session(chart, input.info.sessionid.clone(), input.options);
450 let invoker_data = input.data;
451 let mut d = Driver::new(
452 chart,
453 session,
454 dm,
455 TraceInput::Start {
456 sessionid: input.info.sessionid,
457 },
458 );
459 d.sessions = input.sessions;
460 let budget = d.s.options.microstep_budget;
461 match d.run_start(&invoker_data) {
462 Ok(()) => Ok(d.finish()),
463 Err(Budget) => Err(StartError::BudgetExceeded {
464 budget,
465 partial: Box::new(d.finish()),
466 }),
467 }
468}
469
470pub fn step(
475 chart: &CompiledChart,
476 session: Session,
477 input: Input,
478 dm: &mut dyn Datamodel,
479) -> Result<Outcome, StepError> {
480 if session.status == Status::Terminated {
481 return Err(StepError::Terminated);
482 }
483 dm.set_environment(&input.env);
484 let trace_input = match &input.kind {
485 InputKind::Event(e) => TraceInput::Event(e.clone()),
486 InputKind::Cancel => TraceInput::Cancel,
487 };
488 let budget = session.options.microstep_budget;
489 let mut d = Driver::new(chart, session, dm, trace_input);
490 d.sessions = input.sessions;
491 let r = match input.kind {
492 InputKind::Cancel => {
493 d.exit_interpreter(true);
494 Ok(())
495 }
496 InputKind::Event(ev) => d.run_external(ev),
497 };
498 match r {
499 Ok(()) => Ok(d.finish()),
500 Err(Budget) => Err(StepError::BudgetExceeded {
501 budget,
502 partial: Box::new(d.finish()),
503 }),
504 }
505}
506
507struct Budget;
509
510struct Stop;
512
513struct Active<'a> {
515 chart: &'a CompiledChart,
516 config: &'a StateSet,
517}
518
519impl StateQuery for Active<'_> {
520 fn in_state(&self, id: &str) -> bool {
521 self.chart
522 .index_of(id)
523 .is_some_and(|i| self.config.contains(i))
524 }
525
526 fn active_states(&self) -> Option<Vec<String>> {
527 Some(
528 self.chart
529 .states()
530 .iter()
531 .enumerate()
532 .filter(|(i, _)| *i != 0 && self.config.contains(*i))
533 .map(|(_, s)| s.id.clone())
534 .collect(),
535 )
536 }
537}
538
539pub const EXECUTION_ERROR_MESSAGE: &str = "execution error";
541pub const COMMUNICATION_ERROR_MESSAGE: &str = "target session does not exist";
543
544fn platform_error(name: &str, message: &str, sendid: Option<String>) -> Event {
547 let kind = name.strip_prefix("error.").unwrap_or(name);
548 Event {
549 name: name.to_string(),
550 type_: EventType::Platform,
551 sendid,
552 data: Value::Object(Vec::from([
553 (String::from("kind"), Value::String(kind.to_string())),
554 (String::from("message"), Value::String(message.to_string())),
555 ])),
556 ..Event::default()
557 }
558}
559
560struct Capture<'d> {
563 inner: &'d mut dyn Datamodel,
564 last: Option<EvalError>,
565}
566
567impl Capture<'_> {
568 fn note<T>(&mut self, r: Result<T, EvalError>) -> Result<T, EvalError> {
569 self.last = r.as_ref().err().cloned();
570 r
571 }
572}
573
574impl Datamodel for Capture<'_> {
575 fn name(&self) -> &str {
576 self.inner.name()
577 }
578 fn init_session(&mut self, info: &SessionInfo) -> Result<(), EvalError> {
579 let r = self.inner.init_session(info);
580 self.note(r)
581 }
582 fn set_environment(&mut self, env: &Environment) {
583 self.inner.set_environment(env)
584 }
585 fn declare(&mut self, id: &str) -> Result<(), EvalError> {
586 let r = self.inner.declare(id);
587 self.note(r)
588 }
589 fn set_event(&mut self, event: &Event) {
590 self.inner.set_event(event)
591 }
592 fn check_event(&self, event: &Event) -> Result<(), EvalError> {
593 self.inner.check_event(event)
594 }
595 fn eval_cond(&mut self, expr: &str, states: &dyn StateQuery) -> Result<bool, EvalError> {
596 let r = self.inner.eval_cond(expr, states);
597 self.note(r)
598 }
599 fn eval_value(&mut self, expr: &str, states: &dyn StateQuery) -> Result<Value, EvalError> {
600 let r = self.inner.eval_value(expr, states);
601 self.note(r)
602 }
603 fn eval_content(&mut self, content: &str) -> Result<Value, EvalError> {
604 let r = self.inner.eval_content(content);
605 self.note(r)
606 }
607 fn assign_expr(
608 &mut self,
609 location: &str,
610 expr: &str,
611 states: &dyn StateQuery,
612 ) -> Result<(), EvalError> {
613 let r = self.inner.assign_expr(location, expr, states);
614 self.note(r)
615 }
616 fn assign_value(&mut self, location: &str, value: Value) -> Result<(), EvalError> {
617 let r = self.inner.assign_value(location, value);
618 self.note(r)
619 }
620 fn bind_var(&mut self, name: &str, value: Value) -> Result<(), EvalError> {
621 let r = self.inner.bind_var(name, value);
622 self.note(r)
623 }
624 fn is_location(&mut self, location: &str) -> bool {
625 self.last = None;
626 self.inner.is_location(location)
627 }
628 fn exec_script(&mut self, script: &str, states: &dyn StateQuery) -> Result<(), EvalError> {
629 let r = self.inner.exec_script(script, states);
630 self.note(r)
631 }
632 fn foreach_items(
633 &mut self,
634 array: &str,
635 states: &dyn StateQuery,
636 ) -> Result<Vec<Value>, EvalError> {
637 let r = self.inner.foreach_items(array, states);
638 self.note(r)
639 }
640 fn snapshot(&self) -> Value {
641 self.inner.snapshot()
642 }
643 fn restore(&mut self, snapshot: &Value) -> Result<(), EvalError> {
644 let r = self.inner.restore(snapshot);
645 self.note(r)
646 }
647}
648
649pub fn parse_delay(text: &str) -> Option<u64> {
652 let t = text.trim();
653 let (num, factor) = if let Some(n) = t.strip_suffix("ms") {
654 (n.trim(), 1u64)
655 } else if let Some(n) = t.strip_suffix('s') {
656 (n.trim(), 1000u64)
657 } else {
658 (t, 1u64)
659 };
660 if num.is_empty() {
661 return None;
662 }
663 let (int, frac) = match num.split_once('.') {
664 Some((i, f)) => (i, f),
665 None => (num, ""),
666 };
667 if (int.is_empty() && frac.is_empty())
668 || !int.bytes().all(|b| b.is_ascii_digit())
669 || !frac.bytes().all(|b| b.is_ascii_digit())
670 {
671 return None;
672 }
673 let mut ms: u64 = 0;
674 for b in int.bytes() {
675 ms = ms.checked_mul(10)?.checked_add(u64::from(b - b'0'))?;
676 }
677 ms = ms.checked_mul(factor)?;
678 let mut scale = factor;
680 for b in frac.bytes() {
681 scale /= 10;
682 if scale == 0 {
683 break;
684 }
685 ms = ms.checked_add(u64::from(b - b'0') * scale)?;
686 }
687 Some(ms)
688}
689
690fn value_to_string(v: Value) -> Result<String, EvalError> {
691 match v {
692 Value::String(s) => Ok(s),
693 Value::Bool(b) => Ok(if b { "true" } else { "false" }.to_string()),
694 Value::Number(n) => {
695 if n.is_finite() && n == (n as i64) as f64 {
696 Ok(format!("{}", n as i64))
697 } else {
698 Ok(format!("{n}"))
699 }
700 }
701 other => Err(EvalError::new(format!("expected a string, got {other:?}"))),
702 }
703}
704
705struct Driver<'c, 'd> {
707 chart: &'c CompiledChart,
708 s: Session,
709 dm: Capture<'d>,
710 effects: Vec<Effect>,
711 to_invoke: StateSet,
712 running: bool,
713 terminated_now: bool,
714 microsteps: Vec<MicrostepTrace>,
715 used: u32,
716 input: TraceInput,
717 sessions: Option<Vec<String>>,
719}
720
721impl<'c, 'd> Driver<'c, 'd> {
722 fn new(
723 chart: &'c CompiledChart,
724 s: Session,
725 dm: &'d mut dyn Datamodel,
726 input: TraceInput,
727 ) -> Self {
728 Driver {
729 chart,
730 s,
731 dm: Capture {
732 inner: dm,
733 last: None,
734 },
735 effects: Vec::new(),
736 to_invoke: chart.empty_set(),
737 running: true,
738 terminated_now: false,
739 microsteps: Vec::new(),
740 used: 0,
741 input,
742 sessions: None,
743 }
744 }
745
746 fn finish(mut self) -> Outcome {
747 let trace = MacrostepTrace {
748 index: self.s.macrosteps,
749 input: self.input,
750 microsteps: self.microsteps,
751 configuration: self.chart.ids(&self.s.configuration),
752 effects: self.effects.clone(),
753 terminated: self.terminated_now,
754 };
755 self.s.macrosteps += 1;
756 Outcome {
757 session: self.s,
758 effects: self.effects,
759 trace,
760 }
761 }
762
763 fn queue_error(&mut self, sendid: Option<String>) {
768 let last = self.dm.last.take();
769 let message = last
770 .as_ref()
771 .map_or(EXECUTION_ERROR_MESSAGE, |e| e.message.as_str());
772 self.s
773 .internal_queue
774 .push_back(platform_error("error.execution", message, sendid));
775 }
776
777 fn eval_value(&mut self, expr: &str) -> Result<Value, EvalError> {
778 let q = Active {
779 chart: self.chart,
780 config: &self.s.configuration,
781 };
782 self.dm.eval_value(expr, &q)
783 }
784
785 fn eval_string(&mut self, expr: &str) -> Result<String, EvalError> {
786 self.eval_value(expr).and_then(value_to_string)
787 }
788
789 fn eval_cond(&mut self, expr: &str) -> Result<bool, EvalError> {
790 let q = Active {
791 chart: self.chart,
792 config: &self.s.configuration,
793 };
794 self.dm.eval_cond(expr, &q)
795 }
796
797 fn eval_payload(
798 &mut self,
799 namelist: &[String],
800 params: &[Param],
801 content: Option<&Content>,
802 ) -> Result<Value, EvalError> {
803 if let Some(c) = content {
804 return match (&c.expr, &c.body) {
805 (Some(x), _) => self.eval_value(x),
806 (None, Some(b)) => self.dm.eval_content(b),
807 (None, None) => Ok(Value::Undefined),
808 };
809 }
810 if namelist.is_empty() && params.is_empty() {
811 return Ok(Value::Undefined);
812 }
813 let mut pairs = Vec::new();
814 for name in namelist {
815 let v = self.eval_value(name)?;
816 pairs.push((name.clone(), v));
817 }
818 for p in params {
819 let v = match (&p.expr, &p.location) {
820 (Some(x), _) => self.eval_value(x)?,
821 (None, Some(l)) => self.eval_value(l)?,
822 (None, None) => Value::Undefined,
823 };
824 pairs.push((p.name.clone(), v));
825 }
826 Ok(Value::Object(pairs))
827 }
828
829 fn init_data(&mut self, items: &'c [Data]) {
830 self.init_data_with(items, &[]);
831 }
832
833 fn init_data_with(&mut self, items: &'c [Data], given: &[(String, Value)]) {
835 for d in items {
836 let r = if let Some((_, v)) = given.iter().find(|(n, _)| *n == d.id) {
837 self.dm.assign_value(&d.id, v.clone())
838 } else if let Some(e) = &d.expr {
839 self.eval_value(e)
840 .and_then(|v| self.dm.assign_value(&d.id, v))
841 } else if let Some(c) = &d.content {
842 self.dm
843 .eval_content(c)
844 .and_then(|v| self.dm.assign_value(&d.id, v))
845 } else if let Some(src) = &d.src {
846 Err(EvalError::new(format!("data src {src} was not resolved")))
847 } else {
848 Ok(())
849 };
850 if r.is_err() {
851 self.queue_error(None);
852 }
853 }
854 }
855
856 fn exec_block(&mut self, block: &'c [Exec]) -> Result<(), Stop> {
861 for e in block {
862 self.exec(e)?;
863 }
864 Ok(())
865 }
866
867 fn fail<T>(&mut self, sendid: Option<String>) -> Result<T, Stop> {
868 self.queue_error(sendid);
869 Err(Stop)
870 }
871
872 fn exec(&mut self, e: &'c Exec) -> Result<(), Stop> {
873 match &e.kind {
874 ExecKind::Raise { event } => {
875 self.s
876 .internal_queue
877 .push_back(Event::named(event.clone(), EventType::Internal));
878 Ok(())
879 }
880 ExecKind::Send(send) => self.exec_send(send),
881 ExecKind::Cancel { sendid, sendidexpr } => {
882 let id = match (sendid, sendidexpr) {
883 (Some(s), _) => s.clone(),
884 (None, Some(x)) => match self.eval_string(x) {
885 Ok(s) => s,
886 Err(_) => return self.fail(None),
887 },
888 (None, None) => return self.fail(None),
889 };
890 self.effects.push(Effect::Cancel { sendid: id });
891 Ok(())
892 }
893 ExecKind::Log { label, expr } => {
894 let value = match expr {
895 Some(x) => match self.eval_value(x) {
896 Ok(v) => Some(v),
897 Err(_) => return self.fail(None),
898 },
899 None => None,
900 };
901 self.effects.push(Effect::Log {
902 label: label.clone(),
903 value,
904 });
905 Ok(())
906 }
907 ExecKind::Assign {
908 location,
909 expr,
910 content,
911 } => {
912 let r = match (expr, content) {
913 (Some(x), _) => {
914 let q = Active {
915 chart: self.chart,
916 config: &self.s.configuration,
917 };
918 self.dm.assign_expr(location, x, &q)
919 }
920 (None, Some(c)) => self
921 .dm
922 .eval_content(c)
923 .and_then(|v| self.dm.assign_value(location, v)),
924 (None, None) => self.dm.assign_value(location, Value::Undefined),
925 };
926 if r.is_err() {
927 return self.fail(None);
928 }
929 Ok(())
930 }
931 ExecKind::Script(script) => {
932 let r = match &script.body {
933 Some(body) => {
934 let q = Active {
935 chart: self.chart,
936 config: &self.s.configuration,
937 };
938 self.dm.exec_script(body, &q)
939 }
940 None => Err(EvalError::new("script source was not resolved")),
941 };
942 if r.is_err() {
943 return self.fail(None);
944 }
945 Ok(())
946 }
947 ExecKind::If { branches } => {
948 for br in branches {
949 let take = match &br.cond {
950 None => true,
951 Some(c) => match self.eval_cond(c) {
952 Ok(b) => b,
953 Err(_) => return self.fail(None),
956 },
957 };
958 if take {
959 return self.exec_block(&br.body);
960 }
961 }
962 Ok(())
963 }
964 ExecKind::Foreach {
965 array,
966 item,
967 index,
968 body,
969 } => {
970 let items = {
971 let q = Active {
972 chart: self.chart,
973 config: &self.s.configuration,
974 };
975 self.dm.foreach_items(array, &q)
976 };
977 let items = match items {
978 Ok(v) => v,
979 Err(_) => return self.fail(None),
980 };
981 for (i, v) in items.into_iter().enumerate() {
982 if self.dm.bind_var(item, v).is_err() {
983 return self.fail(None);
984 }
985 if let Some(ix) = index
986 && self.dm.bind_var(ix, Value::Number(i as f64)).is_err()
987 {
988 return self.fail(None);
989 }
990 self.exec_block(body)?;
991 }
992 Ok(())
993 }
994 ExecKind::Extension(_) => Ok(()),
996 }
997 }
998
999 fn exec_send(&mut self, send: &'c Send) -> Result<(), Stop> {
1000 let (sendid, visible) = match (&send.id, &send.idlocation) {
1003 (Some(id), _) => (id.clone(), true),
1004 (None, loc) => {
1005 self.s.send_counter += 1;
1006 let id = format!("{}.send.{}", self.s.sessionid, self.s.send_counter);
1007 if let Some(loc) = loc
1008 && self
1009 .dm
1010 .assign_value(loc, Value::String(id.clone()))
1011 .is_err()
1012 {
1013 return self.fail(Some(id));
1014 }
1015 (id, loc.is_some())
1016 }
1017 };
1018 let err_id = Some(sendid.clone());
1019 let name = match (&send.event, &send.eventexpr) {
1021 (Some(n), _) => n.clone(),
1022 (None, Some(x)) => match self.eval_string(x) {
1023 Ok(n) => n,
1024 Err(_) => return self.fail(err_id),
1025 },
1026 (None, None) => String::new(),
1027 };
1028 let target = match (&send.target, &send.targetexpr) {
1029 (Some(t), _) => Some(t.clone()),
1030 (None, Some(x)) => match self.eval_string(x) {
1031 Ok(t) => Some(t),
1032 Err(_) => return self.fail(err_id),
1033 },
1034 (None, None) => None,
1035 };
1036 let type_ = match (&send.type_, &send.typeexpr) {
1037 (Some(t), _) => t.clone(),
1038 (None, Some(x)) => match self.eval_string(x) {
1039 Ok(t) => t,
1040 Err(_) => return self.fail(err_id),
1041 },
1042 (None, None) => SCXML_EVENT_PROCESSOR.to_string(),
1043 };
1044 let delay_text = match (&send.delay, &send.delayexpr) {
1045 (Some(d), _) => Some(d.clone()),
1046 (None, Some(x)) => match self.eval_string(x) {
1047 Ok(d) => Some(d),
1048 Err(_) => return self.fail(err_id),
1049 },
1050 (None, None) => None,
1051 };
1052 let delay_ms = match delay_text {
1053 None => 0,
1054 Some(t) => match parse_delay(&t) {
1055 Some(ms) => ms,
1056 None => return self.fail(err_id),
1057 },
1058 };
1059 let data = match self.eval_payload(&send.namelist, &send.params, send.content.as_ref()) {
1061 Ok(v) => v,
1062 Err(_) => return self.fail(err_id),
1063 };
1064 if !self.s.options.send_types.contains(&type_) {
1068 return self.fail(err_id);
1069 }
1070 let scxml = type_ == SCXML_EVENT_PROCESSOR || type_ == "scxml";
1071 if scxml
1072 && let Some(t) = &target
1073 && !(t.starts_with("#_") || t.contains(':'))
1074 {
1075 return self.fail(err_id);
1076 }
1077 if scxml
1080 && delay_ms == 0
1081 && let (Some(t), Some(known)) = (&target, &self.sessions)
1082 && let Some(id) = t.strip_prefix("#_scxml_")
1083 && !known.iter().any(|k| k == id)
1084 {
1085 self.s.internal_queue.push_back(platform_error(
1086 "error.communication",
1087 COMMUNICATION_ERROR_MESSAGE,
1088 err_id,
1089 ));
1090 return Ok(());
1091 }
1092 let exposed = if visible { Some(sendid.clone()) } else { None };
1093 if target.as_deref() == Some("#_internal") && delay_ms == 0 {
1094 self.s.internal_queue.push_back(Event {
1095 name,
1096 type_: EventType::Internal,
1097 sendid: exposed,
1098 data,
1099 ..Event::default()
1100 });
1101 return Ok(());
1102 }
1103 let event = Event {
1104 name,
1105 type_: EventType::External,
1106 sendid: exposed,
1107 origin: if scxml {
1108 Some(format!("#_scxml_{}", self.s.sessionid))
1109 } else {
1110 None
1111 },
1112 origintype: if scxml {
1113 Some(SCXML_EVENT_PROCESSOR.to_string())
1114 } else {
1115 None
1116 },
1117 invokeid: None,
1118 data,
1119 ..Event::default()
1120 };
1121 self.effects.push(Effect::Send(SendRequest {
1122 sendid,
1123 event,
1124 target,
1125 type_,
1126 delay_ms,
1127 }));
1128 Ok(())
1129 }
1130
1131 fn select(&mut self, event: Option<&str>) -> Vec<TransIdx> {
1134 let chart = self.chart;
1135 let config = &self.s.configuration;
1136 let history = &self.s.history;
1137 let queue = &mut self.s.internal_queue;
1138 let dm = &mut self.dm;
1139 kernel::select_transitions(chart, config, history, event, |t| {
1140 let Some(cond) = chart.transition(t).cond.as_deref() else {
1141 return true;
1142 };
1143 let q = Active { chart, config };
1144 match dm.eval_cond(cond, &q) {
1145 Ok(b) => b,
1146 Err(e) => {
1147 queue.push_back(platform_error("error.execution", &e.message, None));
1148 false
1149 }
1150 }
1151 })
1152 }
1153
1154 fn record(&mut self, event: Option<String>, ts: &[TransIdx]) {
1155 let transitions = ts
1156 .iter()
1157 .filter_map(|t| {
1158 let tr = self.chart.transition(*t);
1159 tr.index.map(|index| TransitionRef {
1160 source: self.chart.state(tr.source).id.clone(),
1161 index,
1162 })
1163 })
1164 .collect();
1165 self.microsteps.push(MicrostepTrace { event, transitions });
1166 }
1167
1168 fn charge(&mut self) -> Result<(), Budget> {
1173 if self.used >= self.s.options.microstep_budget {
1174 return Err(Budget);
1175 }
1176 self.used += 1;
1177 Ok(())
1178 }
1179
1180 fn microstep(&mut self, event: Option<String>, ts: &[TransIdx]) -> Result<(), Budget> {
1181 self.charge()?;
1182 self.record(event, ts);
1183 let chart = self.chart;
1184 let domains = kernel::transition_domains(chart, &self.s.history, ts);
1185 self.exit_states(ts);
1186 for &t in ts {
1187 let _ = self.exec_block(&chart.transition(t).body);
1188 }
1189 self.enter_states(ts, &domains);
1190 Ok(())
1191 }
1192
1193 fn exit_states(&mut self, ts: &[TransIdx]) {
1195 let chart = self.chart;
1196 let exit = kernel::compute_exit_set(chart, &self.s.configuration, &self.s.history, ts);
1197 self.to_invoke.difference_with(&exit);
1198 kernel::record_history(chart, &self.s.configuration, &exit, &mut self.s.history);
1199 for s in exit.to_vec_rev() {
1200 for b in &chart.state(s).onexit {
1201 let _ = self.exec_block(b);
1202 }
1203 self.cancel_invocations(s);
1204 self.s.configuration.remove(s);
1205 }
1206 }
1207
1208 fn cancel_invocations(&mut self, s: StateIdx) {
1209 let mut keep = Vec::new();
1210 for inv in core::mem::take(&mut self.s.invocations) {
1211 if inv.state == s {
1212 if !inv.completed {
1213 self.effects.push(Effect::CancelInvoke {
1214 invokeid: inv.invokeid,
1215 });
1216 }
1217 } else {
1218 keep.push(inv);
1219 }
1220 }
1221 self.s.invocations = keep;
1222 }
1223
1224 fn enter_states(&mut self, ts: &[TransIdx], domains: &[Option<StateIdx>]) {
1226 let chart = self.chart;
1227 let plan = kernel::compute_entry_set(chart, &self.s.history, ts, domains);
1228 let late = chart.binding() == Binding::Late;
1229 for s in plan.states.to_vec() {
1230 self.s.configuration.insert(s);
1231 self.to_invoke.insert(s);
1232 let st = chart.state(s);
1233 if late && !self.s.data_initialized.contains(s) {
1234 self.s.data_initialized.insert(s);
1235 self.init_data(&st.data);
1236 }
1237 for b in &st.onentry {
1238 let _ = self.exec_block(b);
1239 }
1240 if plan.default_entry.contains(s)
1241 && let Some(it) = st.initial
1242 {
1243 let _ = self.exec_block(&chart.transition(it).body);
1244 }
1245 if let Some(&(_, t)) = plan.default_history_content.iter().find(|(p, _)| *p == s) {
1246 let _ = self.exec_block(&chart.transition(t).body);
1247 }
1248 if st.kind == StateKind::Final {
1249 let parent = st.parent.unwrap_or(ROOT);
1250 if parent == ROOT {
1251 self.running = false;
1252 } else {
1253 let data = self.done_data(s);
1254 let pid = &chart.state(parent).id;
1255 self.s.internal_queue.push_back(Event {
1256 name: format!("done.state.{pid}"),
1257 type_: EventType::Platform,
1258 data,
1259 ..Event::default()
1260 });
1261 if let Some(gp) = chart.state(parent).parent
1262 && gp != ROOT
1263 && chart.state(gp).kind == StateKind::Parallel
1264 && chart
1265 .state(gp)
1266 .children
1267 .iter()
1268 .all(|c| kernel::is_in_final_state(chart, &self.s.configuration, *c))
1269 {
1270 let gid = &chart.state(gp).id;
1271 self.s.internal_queue.push_back(Event::named(
1272 format!("done.state.{gid}"),
1273 EventType::Platform,
1274 ));
1275 }
1276 }
1277 }
1278 }
1279 }
1280
1281 fn done_data(&mut self, s: StateIdx) -> Value {
1282 let chart = self.chart;
1283 let Some(dd) = &chart.state(s).donedata else {
1284 return Value::Undefined;
1285 };
1286 match self.eval_payload(&[], &dd.params, dd.content.as_ref()) {
1287 Ok(v) => v,
1288 Err(_) => {
1289 self.queue_error(None);
1290 Value::Undefined
1291 }
1292 }
1293 }
1294
1295 fn run_macrostep(&mut self) -> Result<(), Budget> {
1298 loop {
1299 while self.running {
1300 let mut enabled = self.select(None);
1301 let mut name = None;
1302 if enabled.is_empty() {
1303 match self.s.internal_queue.pop_front() {
1304 None => break,
1305 Some(ev) => {
1306 self.dm.set_event(&ev);
1307 enabled = self.select(Some(&ev.name));
1308 if enabled.is_empty() {
1309 self.charge()?;
1310 self.record(Some(ev.name), &[]);
1311 continue;
1312 }
1313 name = Some(ev.name);
1314 }
1315 }
1316 }
1317 self.microstep(name, &enabled)?;
1318 }
1319 if !self.running {
1320 self.exit_interpreter(false);
1321 return Ok(());
1322 }
1323 self.start_invokes();
1324 if self.s.internal_queue.is_empty() {
1325 return Ok(());
1326 }
1327 }
1328 }
1329
1330 fn run_start(&mut self, invoker_data: &[(String, Value)]) -> Result<(), Budget> {
1331 let chart = self.chart;
1332 for s in 0..chart.len() {
1334 for d in &chart.state(s).data {
1335 if self.dm.declare(&d.id).is_err() {
1336 self.queue_error(None);
1337 }
1338 }
1339 }
1340 match chart.binding() {
1341 Binding::Early => {
1342 for s in 0..chart.len() {
1343 let given = if s == ROOT { invoker_data } else { &[] };
1344 self.init_data_with(&chart.state(s).data, given);
1345 self.s.data_initialized.insert(s);
1346 }
1347 }
1348 Binding::Late => {
1349 self.init_data_with(&chart.state(ROOT).data, invoker_data);
1350 self.s.data_initialized.insert(ROOT);
1351 }
1352 }
1353 if let Some(script) = &chart.chart().script {
1354 let r = match &script.body {
1355 Some(body) => {
1356 let q = Active {
1357 chart,
1358 config: &self.s.configuration,
1359 };
1360 self.dm.exec_script(body, &q)
1361 }
1362 None => Err(EvalError::new("script source was not resolved")),
1363 };
1364 if r.is_err() {
1365 self.queue_error(None);
1366 }
1367 }
1368 if let Some(it) = chart.state(ROOT).initial {
1369 let domains = kernel::transition_domains(chart, &self.s.history, &[it]);
1370 self.enter_states(&[it], &domains);
1371 }
1372 self.run_macrostep()
1373 }
1374
1375 fn run_external(&mut self, ev: Event) -> Result<(), Budget> {
1377 if let Some(rest) = ev.name.strip_prefix("done.invoke.") {
1384 for inv in self.s.invocations.iter_mut() {
1385 if inv.invokeid == rest || ev.invokeid.as_deref() == Some(inv.invokeid.as_str()) {
1386 inv.completed = true;
1387 }
1388 }
1389 }
1390 self.dm.set_event(&ev);
1391 let mut invs = self.s.invocations.clone();
1392 invs.sort_by_key(|i| (i.state, i.index));
1393 let chart = self.chart;
1394 for inv in invs {
1395 if ev.invokeid.as_deref() == Some(inv.invokeid.as_str()) {
1396 let finalize = &chart.state(inv.state).invokes[inv.index].finalize;
1397 let _ = self.exec_block(finalize);
1398 }
1399 if inv.autoforward && !inv.completed {
1400 self.effects.push(Effect::Forward {
1401 invokeid: inv.invokeid.clone(),
1402 event: ev.clone(),
1403 });
1404 }
1405 }
1406 let enabled = self.select(Some(&ev.name));
1407 if enabled.is_empty() {
1408 self.record(Some(ev.name), &[]);
1409 } else {
1410 self.microstep(Some(ev.name), &enabled)?;
1411 }
1412 self.run_macrostep()
1413 }
1414
1415 fn start_invokes(&mut self) {
1416 let chart = self.chart;
1417 for s in self.to_invoke.to_vec() {
1418 for (i, inv) in chart.state(s).invokes.iter().enumerate() {
1419 self.invoke(s, i, inv);
1420 }
1421 }
1422 self.to_invoke.clear();
1423 }
1424
1425 fn invoke(&mut self, s: StateIdx, index: usize, inv: &'c Invoke) {
1426 let chart = self.chart;
1427 let state_id = &chart.state(s).id;
1428 let invokeid = match &inv.id {
1429 Some(id) => id.clone(),
1430 None => {
1431 self.s.invoke_counter += 1;
1432 let id = format!("{}.{}", state_id, self.s.invoke_counter);
1433 if let Some(loc) = &inv.idlocation
1434 && self
1435 .dm
1436 .assign_value(loc, Value::String(id.clone()))
1437 .is_err()
1438 {
1439 self.queue_error(None);
1440 return;
1441 }
1442 id
1443 }
1444 };
1445 let type_ = match (&inv.type_, &inv.typeexpr) {
1446 (Some(t), _) => Ok(t.clone()),
1447 (None, Some(x)) => self.eval_string(x),
1448 (None, None) => Ok(SCXML_INVOKE_TYPE.to_string()),
1449 };
1450 let src = match (&inv.src, &inv.srcexpr) {
1451 (Some(t), _) => Ok(Some(t.clone())),
1452 (None, Some(x)) => self.eval_string(x).map(Some),
1453 (None, None) => Ok(None),
1454 };
1455 let content = match &inv.content {
1456 Some(Content { expr: Some(x), .. }) => self.eval_value(x).map(Some),
1457 Some(Content {
1458 expr: None,
1459 body: Some(b),
1460 ..
1461 }) => Ok(Some(Value::String(b.clone()))),
1462 _ => Ok(None),
1463 };
1464 let params = self.eval_payload(&inv.namelist, &inv.params, None);
1465 let (Ok(type_), Ok(src), Ok(content), Ok(params)) = (type_, src, content, params) else {
1466 self.queue_error(None);
1467 return;
1468 };
1469 if !self.s.options.invoke_types.contains(&type_) {
1470 self.queue_error(None);
1471 return;
1472 }
1473 self.s.invocations.push(Invocation {
1474 state: s,
1475 index,
1476 invokeid: invokeid.clone(),
1477 autoforward: inv.autoforward,
1478 completed: false,
1479 });
1480 self.effects.push(Effect::Invoke(InvokeRequest {
1481 invokeid,
1482 state: state_id.clone(),
1483 type_,
1484 src,
1485 content,
1486 params,
1487 autoforward: inv.autoforward,
1488 }));
1489 }
1490
1491 fn exit_interpreter(&mut self, cancelled: bool) {
1495 let chart = self.chart;
1496 let mut data = Value::Undefined;
1497 for s in self.s.configuration.to_vec_rev() {
1498 for b in &chart.state(s).onexit {
1499 let _ = self.exec_block(b);
1500 }
1501 self.cancel_invocations(s);
1502 self.s.configuration.remove(s);
1503 if chart.state(s).kind == StateKind::Final && chart.state(s).parent == Some(ROOT) {
1504 data = self.done_data(s);
1505 }
1506 }
1507 self.effects.push(Effect::SessionDone { data, cancelled });
1508 self.s.internal_queue.clear();
1509 self.to_invoke.clear();
1510 self.s.status = Status::Terminated;
1511 self.running = false;
1512 self.terminated_now = true;
1513 }
1514}
1515
1516#[cfg(test)]
1517mod error_tests {
1518 use super::*;
1519 use alloc::string::ToString;
1520 use alloc::vec;
1521
1522 #[test]
1523 fn start_error_displays_and_boxes() {
1524 let e = StartError::ChartRefused {
1525 datamodel: "cel".into(),
1526 problems: vec!["state `s` › cond: not bool".into()],
1527 };
1528 assert_eq!(
1529 e.to_string(),
1530 "datamodel `cel` refused the chart\nstate `s` › cond: not bool"
1531 );
1532 let e = StartError::Datamodel(EvalError::new("boom"));
1533 assert_eq!(e.to_string(), "the datamodel failed to initialize: boom");
1534 assert_eq!(core::error::Error::source(&e).unwrap().to_string(), "boom");
1535 fn run() -> Result<(), alloc::boxed::Box<dyn core::error::Error>> {
1536 Err(StartError::UnsupportedDatamodel { name: "x".into() })?
1537 }
1538 assert_eq!(run().unwrap_err().to_string(), "unsupported datamodel `x`");
1539 }
1540
1541 #[test]
1542 fn step_error_displays_and_boxes() {
1543 fn run() -> Result<(), alloc::boxed::Box<dyn core::error::Error>> {
1544 Err(StepError::Terminated)?
1545 }
1546 assert_eq!(run().unwrap_err().to_string(), "the session has terminated");
1547 }
1548}