1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
#![allow(clippy::unit_arg)]
mod schema;
mod utils;

use scdlang::{self as scdlang, prelude::*, Scdlang};
pub use schema::*;

use from_pest::FromPest;
use serde::{Deserialize, Serialize};
use std::{error, fmt};
use utils::pairs;

#[derive(Default, Serialize, Deserialize)]
pub struct Machine<'a> {
	#[serde(flatten)]
	schema: StateChart,

	#[serde(skip)]
	builder: Scdlang<'a>,
}

/** Finally, found the downside of AST as a direct structure 😏
 * Because it doesn't have a process of breaking down the semantics into specific structure,
 * it's too much hasle for adding simple process like converting state name from PascalCase into camelCase
 */
impl<'a> Parser<'a> for Machine<'a> {
	fn configure(&mut self) -> &mut Builder<'a> {
		&mut self.builder
	}

	fn parse(&mut self, source: &str) -> Result<(), DynError> {
		let ast = Self::try_parse(source, self.builder.to_owned())?;
		Ok(self.schema.states = ast.schema.states)
	}

	fn insert_parse(&mut self, source: &str) -> Result<(), DynError> {
		let ast = Self::try_parse(source, self.builder.to_owned())?;
		Ok(self.schema.states.extend(ast.schema.states))
	}

	fn try_parse(source: &str, builder: Scdlang<'a>) -> Result<Self, DynError> {
		let mut parse_tree = scdlang::parse(&source)?;

		let schema = if pairs::is_expression(&parse_tree) {
			let line = &format!(r#"expression("{line}")"#, line = parse_tree.as_str());
			StateChart::from_pest(&mut parse_tree).expect(line)
		} else {
			StateChart::default()
		};

		Ok(Machine { schema, builder })
	}
}

impl fmt::Display for Machine<'_> {
	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
		write!(f, "{}", serde_json::to_string_pretty(&self.schema).map_err(|_| fmt::Error)?)
	}
}

type DynError = Box<dyn error::Error>;

#[cfg(test)]
mod test {
	use super::*;
	use assert_json_diff::assert_json_eq;
	use serde_json::json;

	#[test]
	#[ignore]
	fn transient_transition() -> Result<(), DynError> {
		let mut machine = Machine::default();
		machine.parse("A -> B")?;

		Ok(assert_json_eq!(
			json!({
				"states": {
					"A": {
						"on": {
							"": "B"
						}
					}
				}
			}),
			json!(machine)
		))
	}

	#[test]
	#[ignore] // 🤔 seems it's difficult to support this, maybe I should drop AST parser 😕
	fn eventful_transition() -> Result<(), DynError> {
		let mut machine = Machine::default();
		machine.parse(
			"A -> B @ C
			A -> D @ E",
		)?;

		Ok(assert_json_eq!(
			json!({
				"states": {
					"A": {
						"on": {
							"C":"B",
							"E":"D"
						}
					}
				}
			}),
			json!(machine)
		))
	}
}