extern crate roxmltree;
extern crate uuid;
use std::io::{Read, Write};
use std::{error::Error, fmt, fs};
use crate::{
datamodel::{DataModelError, SystemVariables},
event::{Event, EventError},
executable_content::{BranchTableEntry, ExecutableContent},
interpreter::{Interpreter, InterpreterError},
registry::RegistryError,
state::{State, StateBuilder, StateBuilderError},
transition::{Transition, TransitionBuilder, TransitionBuilderError},
StateChart, StateChartBuilder, StateChartBuilderError,
};
use roxmltree::Node;
use uuid::Uuid;
const VALID_SCXML_NAMESPACE: &str = "http://www.w3.org/2005/07/scxml";
const VALID_SCXML_VERSION: &str = "1.0";
const VALID_DATAMODEL: &str = "ecmascript";
#[derive(Debug, PartialEq)]
pub struct Parser<'w, W: 'w + Write> {
path: String,
content: String,
statechart_builder: StateChartBuilder<'w, W>,
}
#[derive(Debug, PartialEq)]
pub enum ParserError {
AssignWithoutLocation(String ),
AssignWithoutExpr(String ),
ElseIfAfterElse(String ),
ElseIfWithoutCond(String ),
IfWithoutCond(String ),
InitialNodeChildless(String ),
InitialTransitionTargetless(String ),
InvalidScxmlChild(String ),
InvalidScxmlNamespace(String ),
InvalidScxmlVersion(String ),
InvalidDataModel(String ),
InvalidDataItem(String ),
RaiseWithoutEvent(String ),
StateHasNoId(String ),
DataModelError(DataModelError),
EventError(EventError),
InterpreterError(InterpreterError),
IoError(std::io::ErrorKind),
RegistryError(RegistryError),
RoxmlTreeError(roxmltree::Error),
StateBuilderError(StateBuilderError),
StateChartBuilderError(StateChartBuilderError),
TransitionBuilderError(TransitionBuilderError),
}
pub enum ValidElementClass {
State,
DataModel,
}
impl<'w, W: 'w + Write> Parser<'w, W> {
pub fn new(path: &str, writer: &'w mut W) -> Result<Self, ParserError> {
let mut file = fs::File::open(path)?;
let mut file_contents = String::new();
file.read_to_string(&mut file_contents)?;
Ok(Self {
path: path.to_string(),
content: file_contents,
statechart_builder: StateChartBuilder::new(writer),
})
}
pub fn parse(mut self) -> Result<StateChart<'w, W>, ParserError> {
let parsed_content = roxmltree::Document::parse(self.content.as_str())?;
Self::validate_structure(&parsed_content)?;
if let Some(initial) = parsed_content.root_element().attribute("initial") {
self.statechart_builder = self.statechart_builder.initial(initial.to_string());
}
if let Some(chart_name) = parsed_content.root_element().attribute("name") {
self.statechart_builder = self.statechart_builder.name(chart_name);
} else {
self.statechart_builder = self
.statechart_builder
.name(Uuid::new_v4().to_string().as_str());
}
for child in parsed_content
.root_element()
.children()
.filter(|v| v.is_element())
{
match child.tag_name().name() {
"datamodel" => {
Self::parse_datamodel(child, &mut self.statechart_builder.sys_vars)?;
}
"final" => {
unimplemented!("Final states are not supported.")
}
"parallel" => {
unimplemented!("Parallel States not yet supported.")
}
"script" => {
unimplemented!("Scripting is not yet supported.")
}
"state" => {
self.statechart_builder =
self.statechart_builder.state(Self::parse_state(child)?)?;
}
unexpected => {
return Err(ParserError::InvalidScxmlChild(unexpected.to_string()));
}
}
}
self.statechart_builder
.build()
.map_err(ParserError::StateChartBuilderError)
}
fn validate_structure(document: &roxmltree::Document) -> Result<(), ParserError> {
let root_node = document.root_element();
if let Some(namespace) = root_node.tag_name().namespace() {
if namespace != VALID_SCXML_NAMESPACE {
return Err(ParserError::InvalidScxmlNamespace(namespace.to_string()));
}
} else {
return Err(ParserError::InvalidScxmlNamespace(String::new()));
}
if let Some(version) = root_node.attribute("version") {
if version != VALID_SCXML_VERSION {
return Err(ParserError::InvalidScxmlVersion(version.to_string()));
}
} else {
return Err(ParserError::InvalidScxmlVersion(String::new()));
}
if let Some(datamodel) = root_node.attribute("datamodel") {
if datamodel != VALID_DATAMODEL {
return Err(ParserError::InvalidDataModel(datamodel.to_string()));
}
}
Ok(())
}
fn parse_datamodel(
element: roxmltree::Node,
sys_vars: &mut SystemVariables,
) -> Result<(), ParserError> {
for child in element.children().filter(|v| v.is_element()) {
if let (Some(id), Some(expr_str)) = (child.attribute("id"), child.attribute("expr")) {
let interpreter = Interpreter::new(expr_str);
let expr_value = interpreter.interpret(sys_vars)?;
sys_vars.set_data_member(id, expr_value);
} else {
return Err(ParserError::InvalidDataItem(format!("{:?}", element)));
}
}
Ok(())
}
fn parse_state(element: roxmltree::Node) -> Result<State, ParserError> {
let mut initial_specified = false;
let state_id = element
.attribute("id")
.ok_or_else(|| ParserError::StateHasNoId(format!("{:?}", element)))?;
let mut state_builder = StateBuilder::new(state_id.to_string());
if let Some(initial) = element.attribute("initial") {
state_builder = state_builder.initial(initial.to_string());
initial_specified = true;
}
for child in element.children().filter(|v| v.is_element()) {
if child.tag_name().name() == "onentry" {
for onentry_child in child.children().filter(|v| v.is_element()) {
state_builder =
state_builder.onentry(Self::parse_for_executable_content(onentry_child)?);
}
}
if child.tag_name().name() == "onexit" {
for onexit_child in child.children().filter(|v| v.is_element()) {
state_builder =
state_builder.onexit(Self::parse_for_executable_content(onexit_child)?);
}
}
if child.tag_name().name() == "transition" {
state_builder =
state_builder.transition(Self::parse_transition(child, state_id)?)?;
}
if !initial_specified && child.tag_name().name() == "initial" {
for grandchild in child.children().filter(|v| v.is_element()) {
if let Some(initial) = grandchild.attribute("target") {
state_builder = state_builder.initial(initial.to_string());
initial_specified = true;
} else {
return Err(ParserError::InitialTransitionTargetless(format!(
"{:?}",
element
)));
}
}
if !initial_specified {
return Err(ParserError::InitialNodeChildless(format!("{:?}", element)));
}
}
if child.tag_name().name() == "state" {
state_builder = state_builder.substate(Self::parse_state(child)?)?;
}
}
state_builder
.build()
.map_err(ParserError::StateBuilderError)
}
fn parse_transition(
element: roxmltree::Node,
parent_id: &str,
) -> Result<Transition, ParserError> {
let mut transition_builder = TransitionBuilder::new(parent_id);
if let Some(event_ids) = element.attribute("event").map(|v| v.split_whitespace()) {
for event_id in event_ids {
let event = Event::from(event_id)?;
transition_builder = transition_builder.event(&event)?;
}
}
if let Some(cond_str) = element.attribute("cond") {
transition_builder = transition_builder.cond(cond_str)?;
}
if let Some(target_id) = element.attribute("target") {
transition_builder = transition_builder.target_id(target_id)?;
}
for child in element.children().filter(|v| v.is_element()) {
transition_builder =
transition_builder.executable_content(Self::parse_for_executable_content(child)?);
}
transition_builder
.build()
.map_err(ParserError::TransitionBuilderError)
}
fn parse_for_executable_content(element: Node) -> Result<ExecutableContent, ParserError> {
if element.tag_name().name() == "assign" {
return Self::parse_assign(element);
}
if element.tag_name().name() == "if" {
return Self::parse_if(element);
}
if element.tag_name().name() == "log" {
return Ok(Self::parse_log(element));
}
if element.tag_name().name() == "raise" {
Self::parse_raise(element)
} else {
unimplemented!(
"Parsing of this Executable Content element '{}' is not yet supported",
element.tag_name().name()
)
}
}
fn parse_assign(element: Node) -> Result<ExecutableContent, ParserError> {
if let Some(location) = element.attribute("location") {
if let Some(expr) = element.attribute("expr") {
Ok(ExecutableContent::Assign(
location.to_string(),
expr.to_string(),
))
} else if element.has_children() {
unimplemented!("Multi-line ECMAScript expression are not yet supported");
}
else {
Err(ParserError::AssignWithoutExpr(format!("{:?}", element)))
}
}
else {
Err(ParserError::AssignWithoutLocation(format!("{:?}", element)))
}
}
fn parse_if(element: Node) -> Result<ExecutableContent, ParserError> {
if let Some(cond) = element.attribute("cond") {
let mut branch_table = Vec::new();
let mut else_encountered = false;
let mut cur_entry = BranchTableEntry::new(cond.to_string(), Vec::new());
for child in element.children().filter(|v| v.is_element()) {
if child.tag_name().name() == "elseif" {
if else_encountered {
return Err(ParserError::ElseIfAfterElse(format!("{:?}", child)));
}
branch_table.push(cur_entry);
if let Some(child_cond) = child.attribute("cond") {
cur_entry = BranchTableEntry::new(child_cond.to_string(), Vec::new());
continue;
} else {
return Err(ParserError::ElseIfWithoutCond(format!("{:?}", child)));
}
}
if child.tag_name().name() == "else" {
else_encountered = true;
branch_table.push(cur_entry);
cur_entry = BranchTableEntry::new("true".to_string(), Vec::new());
continue;
}
cur_entry.push_exec_content(Self::parse_for_executable_content(child)?);
}
branch_table.push(cur_entry);
Ok(ExecutableContent::BranchTable(branch_table))
}
else {
Err(ParserError::IfWithoutCond(format!("{:?}", element)))
}
}
fn parse_log(element: Node) -> ExecutableContent {
let mut label = String::new();
let mut expr = String::new();
if let Some(label_str) = element.attribute("label") {
label.push_str(label_str);
}
if let Some(expr_str) = element.attribute("expr") {
expr.push_str(expr_str);
}
ExecutableContent::Log(label, expr)
}
fn parse_raise(element: Node) -> Result<ExecutableContent, ParserError> {
if let Some(event_str) = element.attribute("event") {
Ok(ExecutableContent::Raise(Event::from(event_str)?))
} else {
Err(ParserError::RaiseWithoutEvent(format!("{:?}", element)))
}
}
}
impl Error for ParserError {}
impl fmt::Display for ParserError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::AssignWithoutLocation(parent_assign) => {
write!(f, "Assignment '{}' is missing a 'location'", parent_assign)
}
Self::AssignWithoutExpr(parent_assign) => {
write!(f, "Assignment '{}' is missing an 'expr'", parent_assign)
}
Self::ElseIfAfterElse(parent_elseif) => {
write!(f, "ElseIf '{}' is exists after Else'", parent_elseif)
}
Self::ElseIfWithoutCond(parent_elseif) => {
write!(f, "ElseIf '{}' is missing a 'cond'", parent_elseif)
}
Self::IfWithoutCond(parent_if) => {
write!(f, "If '{}' is missing a 'cond'", parent_if)
}
Self::InitialNodeChildless(parent_state) => {
write!(
f,
"State '{}' contains a childless <initial> element",
parent_state
)
}
Self::InitialTransitionTargetless(parent_state) => {
write!(
f,
"State '{}' contains an <initial> element with a targetless <transition>",
parent_state
)
}
Self::InvalidScxmlChild(name) => {
write!(f, "Invalid Child '{}' of top-level SCXML element", name)
}
Self::InvalidScxmlNamespace(namespace) => {
write!(
f,
"Invalid SCXML Namespace '{}', expected '{}'",
namespace, VALID_SCXML_NAMESPACE
)
}
Self::InvalidScxmlVersion(version) => {
write!(
f,
"Invalid SCXML Version '{}', expected '{}'",
version, VALID_SCXML_VERSION
)
}
Self::InvalidDataModel(datamodel) => {
write!(
f,
"Invalid SCXML Datamodel '{}', expected '{}'",
datamodel, VALID_DATAMODEL
)
}
Self::InvalidDataItem(node_id) => {
write!(f, "Invalid Data Item '{:?}'", node_id)
}
Self::RaiseWithoutEvent(parent_raise) => {
write!(f, "Raise '{}' is missing an 'event'", parent_raise)
}
Self::StateHasNoId(node_id) => {
write!(
f,
"Node ID {:?} is a <state> element with no 'id' attribute",
node_id
)
}
Self::DataModelError(data_error) => {
write!(
f,
"DataModelError '{:?}' encountered while parsing",
data_error
)
}
Self::EventError(event_error) => {
write!(
f,
"EventError '{:?}' encountered while parsing",
event_error
)
}
Self::InterpreterError(interp_error) => {
write!(
f,
"InterpreterError '{:?}' encountered while parsing",
interp_error
)
}
Self::IoError(io_err_kind) => {
write!(
f,
"IoError of kind '{:?}' encountered when attempting to create Parser object",
io_err_kind
)
}
Self::RegistryError(reg_error) => {
write!(
f,
"RegistryError '{:?}' encountered while parsing",
reg_error
)
}
Self::RoxmlTreeError(roxmltree_error) => {
write!(
f,
"roxmltree::Error '{:?}' encountered while parsing",
roxmltree_error
)
}
Self::StateBuilderError(sb_error) => {
write!(
f,
"StateBuilderError '{:?}' encountered while parsing",
sb_error
)
}
Self::StateChartBuilderError(scb_error) => {
write!(
f,
"StateChartBuilderError '{:?}' encountered while parsing",
scb_error
)
}
Self::TransitionBuilderError(tb_error) => {
write!(
f,
"TransitionBuilderError '{:?}' encountered while parsing",
tb_error
)
}
}
}
}
impl From<DataModelError> for ParserError {
fn from(src: DataModelError) -> Self {
Self::DataModelError(src)
}
}
impl From<EventError> for ParserError {
fn from(src: EventError) -> Self {
Self::EventError(src)
}
}
impl From<InterpreterError> for ParserError {
fn from(src: InterpreterError) -> Self {
Self::InterpreterError(src)
}
}
impl From<std::io::Error> for ParserError {
fn from(src: std::io::Error) -> Self {
Self::IoError(src.kind())
}
}
impl From<RegistryError> for ParserError {
fn from(src: RegistryError) -> Self {
Self::RegistryError(src)
}
}
impl From<roxmltree::Error> for ParserError {
fn from(src: roxmltree::Error) -> Self {
Self::RoxmlTreeError(src)
}
}
impl From<StateBuilderError> for ParserError {
fn from(src: StateBuilderError) -> Self {
Self::StateBuilderError(src)
}
}
impl From<StateChartBuilderError> for ParserError {
fn from(src: StateChartBuilderError) -> Self {
Self::StateChartBuilderError(src)
}
}
impl From<TransitionBuilderError> for ParserError {
fn from(src: TransitionBuilderError) -> Self {
Self::TransitionBuilderError(src)
}
}
#[cfg(test)]
mod tests {
use std::error::Error;
use std::io;
use crate::{
event::{Event, EventError},
parser::{Parser, ParserError},
registry::RegistryError,
state::StateBuilderError,
transition::TransitionBuilderError,
StateChartBuilderError,
};
type TestResult = Result<(), Box<dyn Error>>;
#[test]
fn assign_without_location() -> TestResult {
let mut dev_null = io::sink();
let assign_string = "Element { tag_name: {http://www.w3.org/2005/07/scxml}assign, attributes: [Attribute { name: expr, value: \"true\" }], namespaces: [Namespace { name: None, uri: \"http://www.w3.org/2005/07/scxml\" }] }".to_string();
let parser = Parser::new(
"res/test_cases/assign_without_location.scxml",
&mut dev_null,
)?;
assert_eq!(
parser.parse().unwrap_err(),
ParserError::AssignWithoutLocation(assign_string)
);
Ok(())
}
#[test]
fn assign_without_expr() -> TestResult {
let mut dev_null = io::sink();
let assign_string = "Element { tag_name: {http://www.w3.org/2005/07/scxml}assign, attributes: [Attribute { name: location, value: \"door_closed\" }], namespaces: [Namespace { name: None, uri: \"http://www.w3.org/2005/07/scxml\" }] }".to_string();
let parser = Parser::new("res/test_cases/assign_without_expr.scxml", &mut dev_null)?;
assert_eq!(
parser.parse().unwrap_err(),
ParserError::AssignWithoutExpr(assign_string)
);
Ok(())
}
#[test]
fn elseif_without_cond() -> TestResult {
let mut dev_null = io::sink();
let elseif_string = "Element { tag_name: {http://www.w3.org/2005/07/scxml}elseif, attributes: [], namespaces: [Namespace { name: None, uri: \"http://www.w3.org/2005/07/scxml\" }] }".to_string();
let parser = Parser::new("res/test_cases/elseif_without_cond.scxml", &mut dev_null)?;
assert_eq!(
parser.parse().unwrap_err(),
ParserError::ElseIfWithoutCond(elseif_string)
);
Ok(())
}
#[test]
fn elseif_after_else() -> TestResult {
let mut dev_null = io::sink();
let elseif_string = "Element { tag_name: {http://www.w3.org/2005/07/scxml}elseif, attributes: [Attribute { name: cond, value: \"true\" }], namespaces: [Namespace { name: None, uri: \"http://www.w3.org/2005/07/scxml\" }] }".to_string();
let parser = Parser::new("res/test_cases/elseif_after_else.scxml", &mut dev_null)?;
assert_eq!(
parser.parse().unwrap_err(),
ParserError::ElseIfAfterElse(elseif_string)
);
Ok(())
}
#[test]
fn if_without_cond() -> TestResult {
let mut dev_null = io::sink();
let if_string = "Element { tag_name: {http://www.w3.org/2005/07/scxml}if, attributes: [], namespaces: [Namespace { name: None, uri: \"http://www.w3.org/2005/07/scxml\" }] }".to_string();
let parser = Parser::new("res/test_cases/if_without_cond.scxml", &mut dev_null)?;
assert_eq!(
parser.parse().unwrap_err(),
ParserError::IfWithoutCond(if_string)
);
Ok(())
}
#[test]
fn initial_node_errors() -> TestResult {
let mut dev_null_a = io::sink();
let mut dev_null_b = io::sink();
let state_string = "Element { tag_name: {http://www.w3.org/2005/07/scxml}state, attributes: [Attribute { name: id, value: \"on\" }], namespaces: [Namespace { name: None, uri: \"http://www.w3.org/2005/07/scxml\" }] }".to_string();
let parser_childless = Parser::new(
"res/test_cases/initial_node_childless.scxml",
&mut dev_null_a,
)?;
let parser_targetless = Parser::new(
"res/test_cases/initial_transition_targetless.scxml",
&mut dev_null_b,
)?;
assert_eq!(
parser_childless.parse().unwrap_err(),
ParserError::InitialNodeChildless(state_string.clone())
);
assert_eq!(
parser_targetless.parse().unwrap_err(),
ParserError::InitialTransitionTargetless(state_string)
);
Ok(())
}
#[test]
fn invalid_scxml_child() -> TestResult {
let mut dev_null = io::sink();
let parser = Parser::new("res/test_cases/invalid_scxml_child.scxml", &mut dev_null)?;
assert_eq!(
parser.parse().unwrap_err(),
ParserError::InvalidScxmlChild("invalid".to_string())
);
Ok(())
}
#[test]
fn namespace() -> TestResult {
let mut dev_null_a = io::sink();
let mut dev_null_b = io::sink();
let parser_a = Parser::new(
"res/test_cases/invalid_scxml_namespace_empty.scxml",
&mut dev_null_a,
)?;
let parser_b = Parser::new(
"res/test_cases/invalid_scxml_namespace_invalid.scxml",
&mut dev_null_b,
)?;
assert_eq!(
parser_a.parse().unwrap_err(),
ParserError::InvalidScxmlNamespace(String::new())
);
assert_eq!(
parser_b.parse().unwrap_err(),
ParserError::InvalidScxmlNamespace("http://invalid.namespace".to_string())
);
Ok(())
}
#[test]
fn version() -> TestResult {
let mut dev_null_a = io::sink();
let mut dev_null_b = io::sink();
let parser_a = Parser::new(
"res/test_cases/invalid_scxml_version_empty.scxml",
&mut dev_null_a,
)?;
let parser_b = Parser::new(
"res/test_cases/invalid_scxml_version_invalid.scxml",
&mut dev_null_b,
)?;
assert_eq!(
parser_a.parse().unwrap_err(),
ParserError::InvalidScxmlVersion(String::new())
);
assert_eq!(
parser_b.parse().unwrap_err(),
ParserError::InvalidScxmlVersion("2.0".to_string())
);
Ok(())
}
#[test]
fn datamodel() -> TestResult {
let mut dev_null_a = io::sink();
let mut dev_null_b = io::sink();
let parser_a = Parser::new("res/test_cases/datamodel_empty.scxml", &mut dev_null_a)?;
let parser_b = Parser::new("res/test_cases/datamodel_invalid.scxml", &mut dev_null_b)?;
assert_eq!(parser_a.parse().is_ok(), true);
assert_eq!(
parser_b.parse().unwrap_err(),
ParserError::InvalidDataModel("someotherscript".to_string())
);
Ok(())
}
#[test]
fn data_item_invalid() -> TestResult {
let mut dev_null = io::sink();
let state_string = "Element { tag_name: {http://www.w3.org/2005/07/scxml}datamodel, attributes: [], namespaces: [Namespace { name: None, uri: \"http://www.w3.org/2005/07/scxml\" }] }".to_string();
let parser = Parser::new("res/test_cases/data_item_invalid.scxml", &mut dev_null)?;
assert_eq!(
parser.parse().unwrap_err(),
ParserError::InvalidDataItem(state_string)
);
Ok(())
}
#[test]
fn no_id_state() -> TestResult {
let mut dev_null = io::sink();
let state_string = "Element { tag_name: {http://www.w3.org/2005/07/scxml}state, attributes: [], namespaces: [Namespace { name: None, uri: \"http://www.w3.org/2005/07/scxml\" }] }".to_string();
let parser = Parser::new("res/test_cases/state_no_id.scxml", &mut dev_null)?;
assert_eq!(
parser.parse().unwrap_err(),
ParserError::StateHasNoId(state_string)
);
Ok(())
}
#[test]
fn event_error() -> TestResult {
let mut dev_null = io::sink();
let parser = Parser::new("res/test_cases/event_invalid_id.scxml", &mut dev_null)?;
assert_eq!(
parser.parse().unwrap_err(),
ParserError::EventError(EventError::IdContainsDuplicates("turn.on.on".to_string()))
);
Ok(())
}
#[test]
fn io_error() -> TestResult {
assert_eq!(
Parser::new("does_not_exist.scxml", &mut io::sink()).unwrap_err(),
ParserError::IoError(std::io::ErrorKind::NotFound)
);
Ok(())
}
#[test]
fn raise_without_event() -> TestResult {
let mut dev_null = io::sink();
let parser = Parser::new("res/test_cases/raise_without_event.scxml", &mut dev_null)?;
assert_eq!(
parser.parse().unwrap_err(),
ParserError::RaiseWithoutEvent("Element { tag_name: {http://www.w3.org/2005/07/scxml}raise, attributes: [], namespaces: [Namespace { name: None, uri: \"http://www.w3.org/2005/07/scxml\" }] }".to_string())
);
Ok(())
}
#[test]
fn registry_error() -> TestResult {
let mut dev_null = io::sink();
let parser = Parser::new(
"res/test_cases/registry_invalid_dup_state.scxml",
&mut dev_null,
)?;
assert_eq!(
parser.parse().unwrap_err(),
ParserError::StateChartBuilderError(StateChartBuilderError::RegistryError(
RegistryError::StateAlreadyRegistered("duplicate".to_string())
))
);
Ok(())
}
#[test]
fn roxmltree_error() -> TestResult {
let mut dev_null = io::sink();
let parser = Parser::new("res/test_cases/completely_empty.scxml", &mut dev_null)?;
assert_eq!(
parser.parse().unwrap_err(),
ParserError::RoxmlTreeError(roxmltree::Error::NoRootNode)
);
Ok(())
}
#[test]
fn state_builder_error() -> TestResult {
let mut dev_null = io::sink();
let parser = Parser::new("res/test_cases/state_invalid_initial.scxml", &mut dev_null)?;
assert_eq!(
parser.parse().unwrap_err(),
ParserError::StateBuilderError(StateBuilderError::InitialSetWithoutChildStates(
"dne".to_string()
))
);
Ok(())
}
#[test]
fn statechart_builder_error() -> TestResult {
let mut dev_null = io::sink();
let parser = Parser::new(
"res/test_cases/statechart_invalid_no_states.scxml",
&mut dev_null,
)?;
assert_eq!(
parser.parse().unwrap_err(),
ParserError::StateChartBuilderError(StateChartBuilderError::NoStatesRegistered)
);
Ok(())
}
#[test]
fn transition_builder_error() -> TestResult {
let mut dev_null = io::sink();
let parser = Parser::new(
"res/test_cases/transition_invalid_dup_event.scxml",
&mut dev_null,
)?;
assert_eq!(
parser.parse().unwrap_err(),
ParserError::TransitionBuilderError(TransitionBuilderError::DuplicateEventId(
Event::from("turn.on")?
))
);
Ok(())
}
#[test]
fn microwave() -> TestResult {
let mut dev_null = io::sink();
let parser = Parser::new("res/examples/01_microwave.scxml", &mut dev_null)?;
let mut statechart = parser.parse()?;
eprintln!(
"*** Initial Active State(s):\n{:#?}",
statechart.active_state_ids()
);
eprintln!("*** Initial Data Model:\n{:#?}", statechart.sys_vars);
eprintln!("*** Sending 'turn.on' Event...");
let turn_on = Event::from("turn.on")?;
statechart.process_external_event(&turn_on)?;
eprintln!("*** Active State(s):\n{:#?}", statechart.active_state_ids());
assert_eq!(
statechart.active_state_ids(),
vec!["on".to_string(), "cooking".to_string()],
);
eprintln!("*** Sending 'door.open' Event...");
let door_open = Event::from("door.open")?;
statechart.process_external_event(&door_open)?;
eprintln!("*** Active State(s):\n{:#?}", statechart.active_state_ids());
assert_eq!(
statechart.active_state_ids(),
vec!["on".to_string(), "idle".to_string()],
);
eprintln!("*** Sending 'door.close' Event...");
let door_close = Event::from("door.close")?;
statechart.process_external_event(&door_close)?;
eprintln!("*** Active State(s):\n{:#?}", statechart.active_state_ids());
assert_eq!(
statechart.active_state_ids(),
vec!["on".to_string(), "cooking".to_string()],
);
let time = Event::from("time")?;
for _ in 0..5 {
eprintln!("*** Sending 'time' Event...");
statechart.process_external_event(&time)?;
eprintln!(
"*** Current Active State(s):\n{:#?}",
statechart.active_state_ids()
);
}
eprintln!("*** Current Data Model:\n{:#?}", statechart.sys_vars);
Ok(())
}
}