use std::io::Write;
use std::{collections::VecDeque, error::Error, fmt};
pub mod datamodel;
pub mod event;
pub mod executable_content;
pub mod interpreter;
pub mod parser;
pub mod registry;
pub mod state;
pub mod transition;
use crate::{
datamodel::{DataModelError, SystemVariables},
event::Event,
executable_content::ExecutableContentError,
interpreter::EcmaScriptValue,
parser::{Parser, ParserError},
registry::{Registry, RegistryError},
state::{State, StateError, StateId},
transition::TransitionFingerprint,
};
#[derive(PartialEq)]
pub struct StateChart<'w, W: 'w + Write> {
initial: Option<StateId>,
internal_queue: VecDeque<Event>,
registry: Registry,
sys_vars: SystemVariables,
writer: &'w mut W,
}
pub type StateChartId = String;
#[derive(Debug, PartialEq)]
pub enum StateChartError {
ReceivedUnregisteredEvent(Event),
ExecutableContentError(ExecutableContentError),
ParserError(ParserError),
RegistryError(RegistryError),
StateError(StateError),
}
#[derive(Debug, PartialEq)]
pub struct StateChartBuilder<'w, W: 'w + Write> {
initial: Option<StateId>,
registry: Registry,
sys_vars: SystemVariables,
writer: &'w mut W,
}
#[derive(Debug, PartialEq)]
pub enum StateChartBuilderError {
InitialStateNotRegistered(StateId),
NoStatesRegistered,
DataModelError(DataModelError),
RegistryError(RegistryError),
}
impl<'w, W: 'w + Write> StateChart<'w, W> {
pub fn from(path: &str, writer: &'w mut W) -> Result<Self, StateChartError> {
Parser::new(path, writer)
.map_err(StateChartError::ParserError)?
.parse()
.map_err(StateChartError::ParserError)
}
pub fn active_state_ids(&self) -> Vec<&str> {
self.registry
.get_active_states()
.iter()
.map(|v| v.id())
.collect()
}
pub fn process_external_event(&mut self, event: &Event) -> Result<(), StateChartError> {
if !self.registry.event_is_registered(event) {
return Err(StateChartError::ReceivedUnregisteredEvent(event.clone()));
}
self.sys_vars.set_event(event.clone());
let mut enabled_transition_fingerprints = self.select_transitions(Some(event))?;
while !enabled_transition_fingerprints.is_empty() {
self.process_microstep(enabled_transition_fingerprints)?;
enabled_transition_fingerprints = self.select_transitions(None)?;
if !enabled_transition_fingerprints.is_empty() {
continue;
}
if let Some(internal_event) = self.internal_queue.pop_front() {
self.sys_vars.set_event(internal_event.clone());
enabled_transition_fingerprints = self.select_transitions(Some(&internal_event))?;
continue;
} else {
break; }
}
Ok(())
}
fn select_transitions(
&self,
event: Option<&Event>,
) -> Result<Vec<TransitionFingerprint>, StateChartError> {
let mut enabled_transitions = Vec::new();
for state in &self.registry.get_active_states() {
if let Some(enabled_transition) = state.evaluate_event(event, &self.sys_vars)? {
enabled_transitions.push(enabled_transition.clone());
}
}
Ok(enabled_transitions)
}
fn process_microstep(
&mut self,
enabled_transition_fingerprints: Vec<TransitionFingerprint>,
) -> Result<(), StateChartError> {
let mut exit_sorted_transition_fingerprints = enabled_transition_fingerprints.clone();
exit_sorted_transition_fingerprints.reverse();
for transition_fingerprint in &exit_sorted_transition_fingerprints {
if !self
.registry
.get_transition(transition_fingerprint)?
.target_ids()
.is_empty()
{
let source_state_id = self
.registry
.get_transition(transition_fingerprint)?
.source_id()
.clone();
let source_state = self.registry.get_mut_state(&source_state_id)?;
source_state.exit(
&mut self.sys_vars,
&mut self.internal_queue,
&mut self.writer,
)?;
}
}
for transition_fingerprint in &enabled_transition_fingerprints {
let cur_transition = self.registry.get_transition(transition_fingerprint)?;
for exec_content in cur_transition.executable_content() {
exec_content.execute(
&mut self.sys_vars,
&mut self.internal_queue,
&mut self.writer,
)?;
}
}
for transition_fingerprint in &enabled_transition_fingerprints {
let target_state_ids = self
.registry
.get_transition(transition_fingerprint)?
.target_ids()
.clone();
for state_id in &target_state_ids {
let target_state = self.registry.get_mut_state(state_id)?;
target_state.enter(
&mut self.sys_vars,
&mut self.internal_queue,
&mut self.writer,
)?;
}
}
Ok(())
}
}
impl<'w, W: 'w + Write> StateChartBuilder<'w, W> {
pub fn new(writer: &'w mut W) -> Self {
Self {
initial: Option::default(),
registry: Registry::default(),
sys_vars: SystemVariables::default(),
writer,
}
}
pub fn build(mut self) -> Result<StateChart<'w, W>, StateChartBuilderError> {
if let Some(first_state) = self.registry.get_all_states().first() {
if self.initial.is_none() {
self.initial = Some(first_state.id().to_string());
}
} else {
return Err(StateChartBuilderError::NoStatesRegistered);
}
if let Some(initial_id) = &self.initial {
if let Ok(state) = self.registry.get_mut_state(initial_id.as_ref()) {
state.activate()
} else {
return Err(StateChartBuilderError::InitialStateNotRegistered(
initial_id.clone(),
));
}
} else {
}
Ok(StateChart {
sys_vars: self.sys_vars,
initial: self.initial,
registry: self.registry,
internal_queue: VecDeque::new(),
writer: self.writer,
})
}
pub fn name(mut self, name: &str) -> Self {
self.sys_vars.set_name(String::from(name));
self
}
pub fn state(mut self, state: State) -> Result<Self, StateChartBuilderError> {
self.registry.register_state(state)?;
Ok(self)
}
pub fn initial(mut self, initial: StateId) -> Self {
self.initial = Some(initial);
self
}
pub fn data_member(mut self, id: &str, value: EcmaScriptValue) -> Self {
self.sys_vars.set_data_member(id, value);
self
}
}
impl<'w, W: 'w + Write> fmt::Debug for StateChart<'w, W> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("StateChart")
.field("sys_vars", &self.sys_vars)
.field("initial", &self.initial)
.field("registry", &self.registry)
.field("internal_queue", &self.internal_queue)
.field("writer", &String::from(std::any::type_name::<W>()))
.finish()
}
}
impl Error for StateChartError {}
impl fmt::Display for StateChartError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::ReceivedUnregisteredEvent(event) => {
write!(f, "Received Event '{}', which is unregistered", event)
}
Self::ExecutableContentError(exec_err) => {
write!(
f,
"ExecutableContentError '{:?}' encountered while processing state chart",
exec_err
)
}
Self::ParserError(parse_err) => {
write!(
f,
"ParserError '{:?}' encountered while processing state chart",
parse_err
)
}
Self::RegistryError(registry_err) => {
write!(
f,
"RegistryError '{:?}' encountered while processing state chart",
registry_err
)
}
Self::StateError(state_err) => {
write!(
f,
"StateError '{:?}' encountered while processing state chart",
state_err
)
}
}
}
}
impl From<ExecutableContentError> for StateChartError {
fn from(src: ExecutableContentError) -> Self {
Self::ExecutableContentError(src)
}
}
impl From<RegistryError> for StateChartError {
fn from(src: RegistryError) -> Self {
Self::RegistryError(src)
}
}
impl From<StateError> for StateChartError {
fn from(src: StateError) -> Self {
Self::StateError(src)
}
}
impl Error for StateChartBuilderError {}
impl fmt::Display for StateChartBuilderError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::InitialStateNotRegistered(state_id) => {
write!(
f,
"ID '{}' is in the `initial` vector, but is not registered",
state_id
)
}
Self::NoStatesRegistered => {
write!(f, "No States registered")
}
Self::DataModelError(data_err) => {
write!(
f,
"DataModelError '{:?}' encountered while building state chart",
data_err
)
}
Self::RegistryError(reg_err) => {
write!(
f,
"RegistryError '{:?}' encountered while building state chart",
reg_err
)
}
}
}
}
impl From<DataModelError> for StateChartBuilderError {
fn from(src: DataModelError) -> Self {
Self::DataModelError(src)
}
}
impl From<RegistryError> for StateChartBuilderError {
fn from(src: RegistryError) -> Self {
Self::RegistryError(src)
}
}
#[cfg(test)]
mod tests {
use std::{error::Error, fmt, io};
use crate::{
event::Event, interpreter::EcmaScriptValue, registry::RegistryError, state::StateBuilder,
transition::TransitionBuilder, StateChart, StateChartBuilder, StateChartBuilderError,
StateChartError,
};
type TestResult = Result<(), Box<dyn Error>>;
#[derive(Debug, PartialEq)]
struct GenericError {}
impl Error for GenericError {}
impl fmt::Display for GenericError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
#[test]
fn theia() -> TestResult {
let go_to_non_imaging = Event::from("go_to_non_imaging")?;
let idle_id = String::from("IDLE");
let diagnostic_id = String::from("DIAGNOSTIC");
let non_imaging_id = String::from("NON-IMAGING");
let imaging_standby_id = String::from("IMAGING STANDBY");
let imaging_id = String::from("IMAGING");
let idle_to_non_imaging = TransitionBuilder::new(idle_id.as_str())
.event(&go_to_non_imaging)?
.target_id(non_imaging_id.as_str())?
.build()?;
let idle = StateBuilder::new(idle_id.clone())
.transition(idle_to_non_imaging)?
.build()?;
let diagnostic = StateBuilder::new(diagnostic_id).build()?;
let non_imaging = StateBuilder::new(non_imaging_id.clone()).build()?;
let imaging_standby = StateBuilder::new(imaging_standby_id).build()?;
let imaging = StateBuilder::new(imaging_id).build()?;
let mut dev_null = io::sink();
let mut statechart = StateChartBuilder::new(&mut dev_null)
.name("theia")
.initial(idle.id().to_string())
.state(idle)?
.state(diagnostic)?
.state(non_imaging)?
.state(imaging_standby)?
.state(imaging)?
.build()?;
statechart.process_external_event(&go_to_non_imaging)?;
assert_eq!(
statechart.active_state_ids().contains(&idle_id.as_str()),
false
);
assert_eq!(
statechart
.active_state_ids()
.contains(&non_imaging_id.as_str()),
true
);
Ok(())
}
#[test]
fn duplicate_state_id() -> TestResult {
let duplicate_id = String::from("duplicate");
let duplicate_a = StateBuilder::new(duplicate_id.clone()).build()?;
let duplicate_b = StateBuilder::new(duplicate_id.clone()).build()?;
let mut dev_null = io::sink();
let mut statechart_builder = StateChartBuilder::new(&mut dev_null);
statechart_builder = statechart_builder.state(duplicate_a)?;
assert_eq!(
statechart_builder.state(duplicate_b).unwrap_err(),
StateChartBuilderError::RegistryError(RegistryError::StateAlreadyRegistered(
duplicate_id
)),
"Failed to detect duplicate State ID error."
);
Ok(())
}
#[test]
fn unregistered_event() -> TestResult {
let unregistered_event = Event::from("unregistered")?;
let state = StateBuilder::new(String::from("state")).build()?;
let mut dev_null = io::sink();
let mut statechart = StateChartBuilder::new(&mut dev_null)
.state(state)?
.build()?;
assert_eq!(
statechart.process_external_event(&unregistered_event),
Err(StateChartError::ReceivedUnregisteredEvent(
unregistered_event
)),
"Failed to reject an unregistered event"
);
Ok(())
}
#[test]
fn eventless_transition() -> TestResult {
let start_id = String::from("start");
let end_id = String::from("end");
let event_id = "test.event";
let event = Event::from(event_id)?;
let eventful = TransitionBuilder::new(start_id.as_str())
.event(&event)?
.build()?;
let eventless = TransitionBuilder::new(start_id.as_str())
.target_id(end_id.as_str())?
.build()?;
let start = StateBuilder::new(start_id)
.transition(eventful)?
.transition(eventless)?
.build()?;
let end = StateBuilder::new(end_id.clone()).build()?;
let mut dev_null = io::sink();
let mut statechart = StateChartBuilder::new(&mut dev_null)
.state(start)?
.state(end)?
.build()?;
statechart.process_external_event(&event)?;
assert_eq!(
statechart.active_state_ids().first(),
Some(&end_id.as_str()),
"Failed to trigger eventless transition."
);
Ok(())
}
#[test]
fn end_to_end_scxml() -> TestResult {
let mut dev_null = io::sink();
let mut statechart =
StateChart::<io::Sink>::from("res/examples/01_microwave.scxml", &mut dev_null)?;
let turn_on = Event::from("turn.on")?;
let door_open = Event::from("door.open")?;
let door_close = Event::from("door.close")?;
let time = Event::from("time")?;
statechart.process_external_event(&turn_on)?;
statechart.process_external_event(&door_open)?;
statechart.process_external_event(&door_close)?;
for _ in 0..5 {
statechart.process_external_event(&time)?;
}
eprintln!("*** Active State(s):\n{:#?}", statechart.active_state_ids());
assert_eq!(statechart.active_state_ids(), vec!["off".to_string()]);
Ok(())
}
#[test]
fn if_deeply_nested() -> TestResult {
let mut dev_null = io::sink();
let mut statechart =
StateChart::<io::Sink>::from("res/test_cases/if_deeply_nested.scxml", &mut dev_null)?;
let turn_on = Event::from("turn.on")?;
let turn_off = Event::from("turn.off")?;
statechart.process_external_event(&turn_on)?;
statechart.process_external_event(&turn_off)?;
Ok(())
}
#[test]
fn logging_microwave() -> TestResult {
let verf_buffer = String::from(
"EVENT: Powering on
COND: Cooking
EVENT: Door Opened
Door Closed Status: false
EVENT: Door Closed
Door Closed Status: true
EVENT: Tick: 1
EVENT: Tick: 2
EVENT: Tick: 3
EVENT: Tick: 4
EVENT: Tick: 5
COND: Powering off\n",
);
let mut buffer = Vec::new();
let mut statechart =
StateChart::<Vec<u8>>::from("res/test_cases/logging_microwave.scxml", &mut buffer)?;
let turn_on = Event::from("turn.on")?;
let door_open = Event::from("door.open")?;
let door_close = Event::from("door.close")?;
let time = Event::from("time")?;
statechart.process_external_event(&turn_on)?;
statechart.process_external_event(&door_open)?;
statechart.process_external_event(&door_close)?;
for _ in 0..5 {
statechart.process_external_event(&time)?;
}
assert_eq!(String::from_utf8(buffer)?, verf_buffer);
Ok(())
}
#[test]
fn onentry_onexit_verification() -> TestResult {
let mut dev_null = io::sink();
let mut statechart = StateChart::<io::Sink>::from(
"res/test_cases/onentry_onexit_verf.scxml",
&mut dev_null,
)?;
let turn_on = Event::from("turn.on")?;
let turn_off = Event::from("turn.off")?;
for _ in 0..5 {
statechart.process_external_event(&turn_on)?;
statechart.process_external_event(&turn_off)?;
}
assert_eq!(
statechart.sys_vars.get_data_member("entry_verf").unwrap(),
&EcmaScriptValue::Number(10.0)
);
assert_eq!(
statechart.sys_vars.get_data_member("exit_verf").unwrap(),
&EcmaScriptValue::Number(10.0)
);
Ok(())
}
#[test]
fn raise_verification() -> TestResult {
let verf_buffer = String::from(
"turned on
BEFORE auto.off
turned off\n",
);
let mut buffer = Vec::new();
let mut statechart =
StateChart::<Vec<u8>>::from("res/test_cases/raise_verf.scxml", &mut buffer)?;
let turn_on = Event::from("turn.on")?;
statechart.process_external_event(&turn_on)?;
assert_eq!(String::from_utf8(buffer)?, verf_buffer);
Ok(())
}
}
#[cfg(test)]
mod builder_tests {
use std::{error::Error, io};
use crate::{state::StateBuilder, StateChartBuilder, StateChartBuilderError};
type TestResult = Result<(), Box<dyn Error>>;
#[test]
fn initial_state_not_registered() -> TestResult {
let unregistered = StateBuilder::new(String::from("unregistered")).build()?;
let registered = StateBuilder::new(String::from("registered")).build()?;
let mut dev_null = io::sink();
let mut invalid_builder = StateChartBuilder::new(&mut dev_null);
invalid_builder = invalid_builder
.state(registered)?
.initial(unregistered.id().to_string());
assert_eq!(
invalid_builder.build().unwrap_err(),
StateChartBuilderError::InitialStateNotRegistered(unregistered.id().to_string()),
"Failed to detect unregistered ID in the initial vector"
);
Ok(())
}
#[test]
fn no_states_registered() -> TestResult {
assert_eq!(
StateChartBuilder::new(&mut io::sink()).build().unwrap_err(),
StateChartBuilderError::NoStatesRegistered,
"Failed to catch that no states were registered"
);
Ok(())
}
}