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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
mod core;

pub(crate) mod cache;

mod error;
pub mod external;
pub mod semantics;
pub mod utils;

pub use crate::core::{parse, Scdlang};
pub use error::Error;
pub use external::Parser as Transpiler;

/// A prelude providing convenient access to commonly-used features of scdlang core parser.
pub mod prelude {
	pub use super::{external::*, utils::naming::*};
	pub use pest::Parser as PestParser;
}

/** A helper module for aliasing several generated [`Rule`] which alias of [`pest::RuleType`]

# Examples
```ignore
use scdlang::{parse, grammar::*};

let pair = parse("A -> B")?;
match pair.as_rule() {
	Name::state => print!("state {}", pair.as_str()),
	Symbol::arrow::right | Symbol::arrow::left => print!(" {} ", pair.as_str()),
	_ => unreachable!()
}
```

[`Rule`]: grammar/enum.Rule.html
[`pest::RuleType`]: https://docs.rs/pest/latest/pest/trait.RuleType.html */
pub mod grammar {
	pub use super::core::Rule;

	#[allow(non_snake_case)]
	#[rustfmt::skip]
	/** Enum variants that represent a symbol (e.g `->`, `@`) 
	
	Each of them follow [unicode name](http://xahlee.info/comp/unicode_index.html) */
	pub mod Symbol {
		pub use super::Rule::TriggerAt as at;

		pub mod arrow {
			pub use crate::core::Rule::{
				TransitionTo as right,
				TransitionFrom as left,
				TransitionToggle as both,
			};
		}

		pub mod double_arrow {
			pub use crate::core::Rule::{
				LoopTo as right,
				LoopFrom as left,
			};
		}

		pub mod tail_arrow {
			pub use crate::core::Rule::{
				TransientLoopTo as right,
				TransientLoopFrom as left,
			};
		}
	}

	#[allow(non_snake_case)]
	#[rustfmt::skip]
	/// Enum variants that represent a name (e.g `state`, `event`)
	pub mod Name {
		pub use super::Rule::{
			StateName as state,
			EventName as event
		};
	}
}

#[cfg(test)]
pub mod test {
	use super::*;
	use grammar::Rule;
	use pest::error::Error;

	const BASE_ISSUES: &str = "https://github.com/DrSensor/scdlang";
	pub fn issue(id: isize) {
		println!("{}/issues/{}", BASE_ISSUES, id)
	}

	pub fn expression(expression: &str) -> Result<&str, Error<Rule>> {
		Ok(crate::parse(expression)?.as_str())
	}

	pub fn correct_expressions(expr_list: &[&str]) -> Result<(), String> {
		for expression in expr_list {
			if let Err(expr) = test::expression(expression) {
				eprintln!("{}", expr.to_string()); // TODO: remove this after Rust test error reporting is better 😅
				return Err(expr.to_string());
			}
		}
		Ok(())
	}

	pub fn wrong_expressions(expr_list: &[&str]) -> Result<(), String> {
		for expression in expr_list {
			if let Ok(expr) = test::expression(expression) {
				return Err(String::from(expr));
			}
		}
		Ok(())
	}

	pub mod parse {
		use super::*;
		use crate::{cache, error::Error, prelude::*};
		use pest::{
			error::ErrorVariant,
			iterators::{Pair, Pairs},
		};
		pub type Result = std::result::Result<(), Error>;

		pub fn expression<'a>(text: &'a str, callback: impl Fn(Pair<'a, Rule>) -> Result) -> Result {
			let declaration = Scdlang::parse_from(text)?;
			for expression in declaration {
				if let Rule::expression = expression.as_rule() {
					callback(expression)?
				}
			}
			cache::clear()?.shrink()
		}

		pub fn from<'a>(text: &'a str, callback: impl FnOnce(Pairs<'a, Rule>) -> Result) -> Result {
			let declaration = Scdlang::parse_from(text)?;
			callback(declaration)?;
			cache::clear()?.shrink()
		}

		pub fn error(text: &str, callback: impl Fn(&str, ErrorVariant<Rule>) -> Result) -> Result {
			let mut parser = Scdlang::new();
			parser.auto_clear_cache(false);
			for line in text.trim().lines() {
				if let Error::Parse(error) = parser.iter_from(line).expect_err(&format!("No Error for {}", line)) {
					callback(line.trim(), error.variant)?;
					cache::clear()?.shrink()?;
				} else {
					panic!("No Error for {}", line);
				}
			}
			Ok(())
		}
	}
}