use crate::production::{production_table, Production, ProductionError};
use crate::symbol::{sentential_form, Symbol, SymbolError};
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use std::error::Error;
use std::fmt;
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub enum GrammarError {
WrongNonTerminals,
WrongTerminals,
WrongStartSymbol(Symbol),
SymbolError(SymbolError),
ProductionError(ProductionError),
NoStartSymbol(Option<String>),
MultipleStartSymbols(Production),
}
impl fmt::Display for GrammarError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
GrammarError::WrongNonTerminals => write!(
f,
"GrammarError: non non terminal symbols as non terminal symbols for grammar"
),
GrammarError::WrongTerminals => write!(
f,
"GrammarError: non terminal symbols as terminal symbols in grammar"
),
GrammarError::WrongStartSymbol(s) => write!(
f,
"GrammarError: start symbol should be a valid non terminal symbol, received \"{}\" instead", s
),
GrammarError::NoStartSymbol(cause) => {
write!(f, "GrammarError: grammar has no start symbol, cause: {}", cause.clone().unwrap_or("unspecified".to_string()))
},
GrammarError::MultipleStartSymbols(p) => {
write!(f, "GrammarError: grammar has multiple start symbols in production {}", p)
},
GrammarError::SymbolError(e) => {
write!(f, "GrammarError: symbol error encountered = {}", e)
},
GrammarError::ProductionError(e) => {
write!(f, "GrammarError: production error encountered = {}", e)
}
}
}
}
impl Error for GrammarError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
GrammarError::SymbolError(e) => Some(e),
GrammarError::ProductionError(e) => Some(e),
_ => None,
}
}
}
impl std::convert::From<ProductionError> for GrammarError {
fn from(e: ProductionError) -> Self {
GrammarError::ProductionError(e)
}
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub struct Grammar {
n: HashSet<Symbol>,
t: HashSet<Symbol>,
p: Vec<Production>,
s: Symbol,
}
impl fmt::Display for Grammar {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"n: {}\nt: {}\np:\n{}\ns: {}",
sentential_form(self.n.clone()),
sentential_form(self.t.clone()),
production_table(self.p.clone()),
self.s
)
}
}
impl std::convert::TryFrom<&str> for Grammar {
type Error = GrammarError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Grammar::from_string(value)
}
}
impl Grammar {
pub fn new<S, P>(n: S, t: S, p: P, s: Symbol) -> Result<Grammar, GrammarError>
where
S: IntoIterator<Item = Symbol>,
P: IntoIterator<Item = Production>,
{
let mut n: HashSet<Symbol> = n.into_iter().collect();
let t: HashSet<Symbol> = t.into_iter().collect();
let p: Vec<Production> = p.into_iter().collect();
if !s.is_non_terminal() {
return Err(GrammarError::WrongStartSymbol(s));
}
n.insert(s.clone());
if !n.iter().all(|s: &Symbol| s.is_non_terminal()) {
return Err(GrammarError::WrongNonTerminals);
}
if !t.iter().all(|s: &Symbol| s.is_terminal()) {
return Err(GrammarError::WrongTerminals);
}
Ok(Grammar {
n: n,
t: t,
p: p,
s: s,
})
}
pub fn new_from_string<'a, I>(n: I, t: I, p: I, s: &str) -> Result<Grammar, GrammarError>
where
I: IntoIterator<Item = &'a str>,
{
let n: Result<HashSet<Symbol>, SymbolError> =
n.into_iter().try_fold(HashSet::new(), |mut acc, s| {
let s = Symbol::new(s)?;
acc.insert(s);
Ok(acc)
});
let t: Result<HashSet<Symbol>, SymbolError> =
t.into_iter().try_fold(HashSet::new(), |mut acc, s| {
let s = Symbol::new(s)?;
acc.insert(s);
Ok(acc)
});
let p = Production::from_iter(p);
let s = Symbol::new(s);
match (n, t, p, s) {
(Ok(n), Ok(t), Ok(p), Ok(s)) => Grammar::new(n, t, p, s),
(Err(_), _, _, _) => Err(GrammarError::WrongNonTerminals),
(_, Err(_), _, _) => Err(GrammarError::WrongTerminals),
(_, _, Err(e), _) => Err(GrammarError::ProductionError(e)),
(_, _, _, Err(e)) => Err(GrammarError::SymbolError(e)),
}
}
pub fn n(&self) -> HashSet<Symbol> {
self.n.clone()
}
pub fn t(&self) -> HashSet<Symbol> {
self.t.clone()
}
pub fn p(&self) -> Vec<Production> {
self.p.clone()
}
pub fn s(&self) -> Symbol {
self.s.clone()
}
pub fn from_string(string: &str) -> Result<Grammar, GrammarError> {
let p = Production::from_string(string)?;
let (n, t): (HashSet<Symbol>, HashSet<Symbol>) = p
.iter()
.map(|p| p.symbols())
.fold(HashSet::new(), |mut acc, s| {
acc.extend(s);
acc
})
.into_iter()
.partition(|s| s.is_non_terminal());
let s: Symbol;
if let Some(p) = p.iter().next() {
let lhs = p.lhs();
let mut lhs = lhs.iter();
if let Some(lhs) = lhs.next() {
s = lhs.clone()
} else {
return Err(GrammarError::NoStartSymbol(Some(
"first production rule does not have a lhs".to_string(),
)));
}
if let Some(_) = lhs.next() {
return Err(GrammarError::MultipleStartSymbols(p.clone()));
}
} else {
return Err(GrammarError::NoStartSymbol(Some(
"found no production rules".to_string(),
)));
}
Grammar::new(n, t, p, s)
}
pub fn alternatives<I>(&self, symbols: I) -> Vec<Vec<Symbol>>
where
I: IntoIterator<Item = Symbol>,
{
let symbols: Vec<Symbol> = symbols.into_iter().collect();
let mut alternatives: Vec<Vec<Symbol>> = Vec::new();
for p in &self.p {
if &p.lhs() == &symbols {
alternatives.push(p.rhs())
}
}
alternatives
}
pub fn restrict_to<I>(&self, symbols: I) -> Result<Grammar, GrammarError>
where
I: IntoIterator<Item = Symbol>,
{
let symbols: HashSet<Symbol> = symbols.into_iter().collect();
let n: HashSet<Symbol> = symbols.intersection(&self.n).cloned().collect();
let t: HashSet<Symbol> = symbols.intersection(&self.t).cloned().collect();
if !symbols.contains(&self.s) {
return Err(GrammarError::NoStartSymbol(Some(
"restricting the grammar lead to a grammar without start symbol".to_string(),
)));
}
let p: Vec<Production> = self
.p
.iter()
.filter(|p: &&Production| p.symbols().difference(&symbols).count() == 0)
.cloned()
.collect();
Grammar::new(n, t, p, self.s.clone())
}
pub fn productives(&self) -> HashSet<Symbol> {
self.productives_from(self.t.clone())
}
fn productives_from(&self, productives: HashSet<Symbol>) -> HashSet<Symbol> {
let mut productives_next: HashSet<Symbol> = productives.clone();
for p in &self.p {
let rhs: HashSet<Symbol> = p.rhs().into_iter().collect();
if rhs.is_subset(&productives_next) {
let lhs: HashSet<Symbol> = p.lhs().into_iter().collect();
productives_next = productives_next.union(&lhs).cloned().collect();
}
}
if productives == productives_next {
productives_next
} else {
self.productives_from(productives_next)
}
}
pub fn reachable(&self) -> HashSet<Symbol> {
let from: HashSet<Symbol> = vec![self.s()].into_iter().collect();
self.reachable_from(from)
}
pub fn reachable_from<I>(&self, reached: I) -> HashSet<Symbol>
where
I: IntoIterator<Item = Symbol>,
{
let reached: HashSet<Symbol> = reached.into_iter().collect();
let mut reached_next: HashSet<Symbol> = reached.clone();
for p in &self.p {
let lhs: HashSet<Symbol> = p.lhs().into_iter().collect();
if lhs.is_subset(&reached_next) {
let rhs: HashSet<Symbol> = p.rhs().into_iter().collect();
reached_next = reached_next.union(&rhs).cloned().collect();
}
}
if reached == reached_next {
reached_next
} else {
self.reachable_from(reached_next)
}
}
}
pub fn grammar(string: &str) -> Grammar {
Grammar::from_string(string).unwrap()
}
pub fn grammar_quadruple<'a, I>(n: I, t: I, p: I, s: &str) -> Grammar
where
I: IntoIterator<Item = &'a str>,
{
Grammar::new_from_string(n, t, p, s).unwrap()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::production::production;
use crate::symbol::symbol;
use crate::tokenizer::TokenizerError;
use std::convert::TryFrom;
use std::fmt::Write;
#[test]
fn n() {
let g = super::grammar("S -> A");
assert_eq!(
g.n(),
vec![symbol("S"), symbol("A")]
.into_iter()
.collect::<HashSet<Symbol>>(),
"Grammar returned non terminals are wrong"
);
}
#[test]
fn t() {
let g = super::grammar("S -> a");
assert_eq!(
g.t(),
vec![symbol("a")].into_iter().collect::<HashSet<Symbol>>(),
"Grammar returned terminals are wrong"
);
}
#[test]
fn p() {
let g = super::grammar("S -> A\nA -> a");
assert_eq!(
g.p(),
vec![production("S", "A"), production("A", "a")],
"Grammar returned productions are wrong"
);
}
#[test]
fn s() {
let g = super::grammar("S -> A");
assert_eq!(g.s(), symbol("S"), "Grammar returned start symbol is wrong");
}
#[test]
fn from_string() {
let s_check: Symbol = symbol("S");
let n_check: HashSet<Symbol> = vec![symbol("S"), symbol("A"), symbol("B")]
.into_iter()
.collect();
let t_check: HashSet<Symbol> = vec![symbol("a"), symbol("b")].into_iter().collect();
let p_check: Vec<Production> = vec![
production("S", "A B"),
production("A", "a"),
production("A", "B"),
production("B", "b"),
];
let g = Grammar::from_string("S -> A B\nA -> a | B\nB -> b").unwrap();
assert_eq!(g.s, s_check, "Parsed start symbol is not the one expected");
assert_eq!(
g.n, n_check,
"Parsed non terminal symbols are not those expected"
);
assert_eq!(
g.t, t_check,
"Parsed terminal symbols are not those expected"
);
assert_eq!(
g.p, p_check,
"Parsed production rules are not those expected"
);
}
#[test]
fn from_string_no_rhs() {
let result = Grammar::from_string("S ->\n -> a | B\nB -> b");
assert!(result.is_err());
let e = result.unwrap_err();
assert_eq!(
e,
GrammarError::ProductionError(ProductionError::FormatError(
TokenizerError::ProductionNoRhs("S".to_string())
)),
"Creation of grammar from test input returned the wrong error",
);
}
#[test]
fn from_string_no_start_symbol_no_production() {
let result = Grammar::from_string("");
assert!(result.is_err());
let e = result.unwrap_err();
assert_eq!(
e,
GrammarError::NoStartSymbol(Some("found no production rules".to_string())),
"Creation of grammar from test input returned the wrong error",
);
}
#[test]
fn from_string_no_lhs() {
let result = Grammar::from_string(" -> B");
assert!(result.is_err());
let e = result.unwrap_err();
assert_eq!(
e,
GrammarError::ProductionError(ProductionError::FormatError(
TokenizerError::ProductionNoLhs
)),
"Creation of grammar from test input returned the wrong error",
);
}
#[test]
fn from_string_multiple_start_symbols() {
let result = Grammar::from_string("A B -> C");
assert!(result.is_err());
let e = result.unwrap_err();
assert_eq!(
e,
GrammarError::MultipleStartSymbols(production("A B", "C")),
"Creation of grammar from test input returned the wrong error",
);
}
#[test]
fn alternatives() {
let g = Grammar::from_string("S -> A B\nA -> a | B\nB -> b").unwrap();
let a_check = vec![vec![symbol("a")], vec![symbol("B")]];
assert_eq!(
g.alternatives(vec![symbol("A")]),
a_check,
"Alternatives are not the one expected"
);
}
#[test]
fn alternatives_empty() {
let g = Grammar::from_string("S -> A B\nA -> a | B\nB -> b").unwrap();
assert!(
g.alternatives(vec![symbol("a")]).is_empty(),
"Alternatives are not empty when they should"
);
}
#[test]
fn restrict_to() {
let g_restricted = Grammar::from_string("S -> A\nA -> a | B\nB -> b")
.unwrap()
.restrict_to(vec![symbol("S"), symbol("A"), symbol("a")])
.unwrap();
let s_check: Symbol = symbol("S");
let n_check: HashSet<Symbol> = vec![symbol("S"), symbol("A")].into_iter().collect();
let t_check: HashSet<Symbol> = vec![symbol("a")].into_iter().collect();
let p_check: Vec<Production> = vec![production("S", "A"), production("A", "a")];
assert_eq!(
g_restricted.s, s_check,
"Restricted grammar start symbol is not the one expected"
);
assert_eq!(
g_restricted.n, n_check,
"Restricted grammar non terminal symbols are not those expected"
);
assert_eq!(
g_restricted.t, t_check,
"Restricted grammar terminal symbols are not those expected"
);
assert_eq!(
g_restricted.p, p_check,
"Restricted grammar production rules are not those expected"
);
}
#[test]
fn restrict_to_panic_start_symbol() {
let result = Grammar::from_string("S -> A\nA -> a | B\nB -> b")
.unwrap()
.restrict_to(vec![symbol("A"), symbol("a")]);
assert!(
result.is_err(),
"Restricting grammar should return an error"
);
let e = result.unwrap_err();
assert_eq!(
e,
GrammarError::NoStartSymbol(Some(
"restricting the grammar lead to a grammar without start symbol".to_string()
)),
"Restricting grammar returned the wrong error"
);
}
#[test]
fn new() {
let s_check: Symbol = symbol("S");
let n_check: HashSet<Symbol> = vec![symbol("S"), symbol("A")].into_iter().collect();
let t_check: HashSet<Symbol> = vec![symbol("a")].into_iter().collect();
let p_check: Vec<Production> = vec![production("S", "A"), production("A", "a")];
let g = Grammar::new(
n_check.clone(),
t_check.clone(),
p_check.clone(),
s_check.clone(),
)
.unwrap();
assert_eq!(
g.s, s_check,
"new grammar start symbol is not the one expected"
);
assert_eq!(
g.n, n_check,
"New grammar non terminal symbols are not those expected"
);
assert_eq!(
g.t, t_check,
"New grammar terminal symbols are not those expected"
);
assert_eq!(
g.p, p_check,
"New grammar production rules are not those expected"
);
}
#[test]
fn new_wrong_start_symbol() {
let s_check: Symbol = symbol("s");
let n_check: HashSet<Symbol> = vec![symbol("S"), symbol("A")].into_iter().collect();
let t_check: HashSet<Symbol> = vec![symbol("a")].into_iter().collect();
let p_check: Vec<Production> = vec![production("S", "A"), production("A", "a")];
let result = Grammar::new(
n_check.clone(),
t_check.clone(),
p_check.clone(),
s_check.clone(),
);
assert!(
result.is_err(),
"Grammar creation with wrong start symbol should return an error"
);
let e = result.unwrap_err();
assert_eq!(
e,
GrammarError::WrongStartSymbol(s_check.clone()),
"Grammar creation from string with wrong non terminal symbols returned a wrong error"
);
}
#[test]
fn new_wrong_non_terminals() {
let s_check: Symbol = symbol("S");
let n_check: HashSet<Symbol> = vec![symbol("s"), symbol("A")].into_iter().collect();
let t_check: HashSet<Symbol> = vec![symbol("a")].into_iter().collect();
let p_check: Vec<Production> = vec![production("S", "A"), production("A", "a")];
let result = Grammar::new(
n_check.clone(),
t_check.clone(),
p_check.clone(),
s_check.clone(),
);
assert!(
result.is_err(),
"Grammar creation with wrong non terminal symbols should return an error"
);
let e = result.unwrap_err();
assert_eq!(
e,
GrammarError::WrongNonTerminals,
"Grammar creation from string with wrong non terminal symbols returned a wrong error"
);
}
#[test]
fn new_wrong_terminals() {
let s_check: Symbol = symbol("S");
let n_check: HashSet<Symbol> = vec![symbol("S"), symbol("A")].into_iter().collect();
let t_check: HashSet<Symbol> = vec![symbol("S")].into_iter().collect();
let p_check: Vec<Production> = vec![production("S", "A"), production("A", "a")];
let result = Grammar::new(
n_check.clone(),
t_check.clone(),
p_check.clone(),
s_check.clone(),
);
assert!(
result.is_err(),
"Grammar creation with wrong terminal symbols should return an error"
);
let e = result.unwrap_err();
assert_eq!(
e,
GrammarError::WrongTerminals,
"Grammar creation from string with wrong non terminal symbols returned a wrong error"
);
}
#[test]
fn new_from_string() {
let s_check: Symbol = symbol("S");
let n_check: HashSet<Symbol> = vec![symbol("S"), symbol("A")].into_iter().collect();
let t_check: HashSet<Symbol> = vec![symbol("a")].into_iter().collect();
let p_check: Vec<Production> = vec![production("S", "A"), production("A", "a")];
let g = Grammar::new_from_string(vec!["S", "A"], vec!["a"], vec!["S -> A\nA -> a"], "S")
.unwrap();
assert_eq!(
g.s, s_check,
"new grammar start symbol is not the one expected"
);
assert_eq!(
g.n, n_check,
"New grammar non terminal symbols are not those expected"
);
assert_eq!(
g.t, t_check,
"New grammar terminal symbols are not those expected"
);
assert_eq!(
g.p, p_check,
"New grammar production rules are not those expected"
);
}
#[test]
fn new_from_string_wrong_start_symbol() {
let start_symbol = "\n";
let result = Grammar::new_from_string(
vec!["S", "A"],
vec!["a"],
vec!["S -> A\nA -> a"],
start_symbol,
);
assert!(
result.is_err(),
"Grammar creation from string with wrong start symbol should return an error"
);
let e = result.unwrap_err();
assert_eq!(
e,
GrammarError::SymbolError(SymbolError::InvalidSymbol(start_symbol.to_string())),
"Grammar creation from string with wrong non terminal symbols returned a wrong error"
);
}
#[test]
fn new_from_string_wrong_non_terminal_symbols() {
let result =
Grammar::new_from_string(vec!["\n", "A"], vec!["a"], vec!["S -> A\nA -> a"], "S");
assert!(
result.is_err(),
"Grammar creation from string with wrong non terminal symbols should return an error"
);
let e = result.unwrap_err();
assert_eq!(
e,
GrammarError::WrongNonTerminals,
"Grammar creation from string with wrong non terminal symbols returned a wrong error"
);
}
#[test]
fn new_from_string_wrong_terminal_symbols() {
let result =
Grammar::new_from_string(vec!["S", "A"], vec!["A"], vec!["S -> A\nA -> a"], "S");
assert!(
result.is_err(),
"Grammar creation from string with wrong terminal symbols should return an error"
);
let e = result.unwrap_err();
assert_eq!(
e,
GrammarError::WrongTerminals,
"Grammar creation from string with wrong terminal symbols returned a wrong error"
);
}
#[test]
fn reachable() {
let g: Grammar = super::grammar(
"
S -> A | B
A -> a
B -> b
C -> c
",
);
let symbols_check: HashSet<Symbol> = vec![
symbol("S"),
symbol("A"),
symbol("B"),
symbol("a"),
symbol("B"),
symbol("b"),
]
.into_iter()
.collect();
assert_eq!(
g.reachable(),
symbols_check,
"Reachable symbols from grammar are not those expected"
);
}
#[test]
fn reachable_from() {
let g: Grammar = super::grammar(
"
S -> A | B
A -> a
B -> b
C -> c
",
);
let symbols_check: HashSet<Symbol> = vec![symbol("A"), symbol("a")].into_iter().collect();
let from: HashSet<Symbol> = vec![symbol("A")].into_iter().collect();
assert_eq!(
g.reachable_from(from),
symbols_check,
"Reachable symbols from grammar are not those expected"
);
}
#[test]
fn productives() {
let g: Grammar = super::grammar(
"
S -> A | B
B -> b
C -> c
D -> d
",
);
let symbols_check: HashSet<Symbol> = vec![
symbol("S"),
symbol("B"),
symbol("b"),
symbol("C"),
symbol("c"),
symbol("D"),
symbol("d"),
]
.into_iter()
.collect();
assert_eq!(
g.productives(),
symbols_check,
"Productives symbols from grammar are not those expected"
);
}
#[test]
fn productives_from() {
let g: Grammar = super::grammar(
"
S -> A | B
B -> b
C -> c
D -> d
",
);
let symbols_check: HashSet<Symbol> = vec![symbol("S"), symbol("B"), symbol("b")]
.into_iter()
.collect();
let from: HashSet<Symbol> = vec![symbol("b")].into_iter().collect();
assert_eq!(
g.productives_from(from),
symbols_check,
"Productives symbols from grammar are not those expected"
);
}
#[test]
fn grammar_display() {
let mut buf = String::new();
let result = write!(buf, "{}", super::grammar("A -> a\nB -> b"));
assert!(result.is_ok());
}
#[test]
fn grammar_try_from() {
let result = Grammar::try_from("A -> a");
assert!(result.is_ok());
}
#[test]
fn grammar_try_from_error() {
let result = Grammar::try_from(" -> a");
assert!(result.is_err());
}
#[test]
fn grammar() {
let s_check: Symbol = symbol("S");
let n_check: HashSet<Symbol> = vec![symbol("S"), symbol("A"), symbol("B")]
.into_iter()
.collect();
let t_check: HashSet<Symbol> = vec![symbol("a"), symbol("b")].into_iter().collect();
let p_check: Vec<Production> = vec![
production("S", "A B"),
production("A", "a"),
production("A", "B"),
production("B", "b"),
];
let g = super::grammar("S -> A B\nA -> a | B\nB -> b");
assert_eq!(g.s, s_check, "Parsed start symbol is not the one expected");
assert_eq!(
g.n, n_check,
"Parsed non terminal symbols are not those expected"
);
assert_eq!(
g.t, t_check,
"Parsed terminal symbols are not those expected"
);
assert_eq!(
g.p, p_check,
"Parsed production rules are not those expected"
);
}
#[test]
fn grammar_quadruple() {
let s_check: Symbol = symbol("S");
let n_check: HashSet<Symbol> = vec![symbol("S"), symbol("A")].into_iter().collect();
let t_check: HashSet<Symbol> = vec![symbol("a")].into_iter().collect();
let p_check: Vec<Production> = vec![production("S", "A"), production("A", "a")];
let g = super::grammar_quadruple(vec!["S", "A"], vec!["a"], vec!["S -> A\nA -> a"], "S");
assert_eq!(
g.s, s_check,
"new grammar start symbol is not the one expected"
);
assert_eq!(
g.n, n_check,
"New grammar non terminal symbols are not those expected"
);
assert_eq!(
g.t, t_check,
"New grammar terminal symbols are not those expected"
);
assert_eq!(
g.p, p_check,
"New grammar production rules are not those expected"
);
}
#[test]
fn grammar_error_display_wrong_non_terminals() {
let mut buf = String::new();
let result = write!(buf, "{}", GrammarError::WrongNonTerminals);
assert!(result.is_ok());
assert_eq!(
buf,
"GrammarError: non non terminal symbols as non terminal symbols for grammar"
)
}
#[test]
fn grammar_error_display_wrong_terminals() {
let mut buf = String::new();
let result = write!(buf, "{}", GrammarError::WrongTerminals);
assert!(result.is_ok());
assert_eq!(
buf,
"GrammarError: non terminal symbols as terminal symbols in grammar"
)
}
#[test]
fn grammar_error_display_wrong_start_symbol() {
let mut buf = String::new();
let symbol = symbol("a");
let result = write!(buf, "{}", GrammarError::WrongStartSymbol(symbol.clone()));
assert!(result.is_ok());
assert_eq!(
buf,
format!(
"GrammarError: start symbol should be a valid non terminal symbol, received \"{}\" instead", symbol
)
)
}
#[test]
fn grammar_error_display_no_start_symbol() {
let mut buf = String::new();
let cause = "cause";
let result = write!(
buf,
"{}",
GrammarError::NoStartSymbol(Some(cause.to_string()))
);
assert!(result.is_ok());
assert_eq!(
buf,
format!(
"GrammarError: grammar has no start symbol, cause: {}",
cause.clone()
)
)
}
#[test]
fn grammar_error_display_multiple_start_symbols() {
let mut buf = String::new();
let p = production("A B", "C");
let result = write!(buf, "{}", GrammarError::MultipleStartSymbols(p.clone()));
assert!(result.is_ok());
assert_eq!(
buf,
format!(
"GrammarError: grammar has multiple start symbols in production {}",
p
)
)
}
#[test]
fn grammar_error_display_symbol_error() {
let mut buf = String::new();
let e = SymbolError::EmptySymbol;
let result = write!(buf, "{}", GrammarError::SymbolError(e.clone()));
assert!(result.is_ok());
assert_eq!(
buf,
format!("GrammarError: symbol error encountered = {}", e)
)
}
#[test]
fn grammar_error_display_production_error() {
let mut buf = String::new();
let e = ProductionError::NoLhs;
let result = write!(buf, "{}", GrammarError::ProductionError(e.clone()));
assert!(result.is_ok());
assert_eq!(
buf,
format!("GrammarError: production error encountered = {}", e)
)
}
#[test]
fn grammar_error_source() {
assert!(GrammarError::ProductionError(ProductionError::NoLhs)
.source()
.is_some());
assert!(GrammarError::SymbolError(SymbolError::EmptySymbol)
.source()
.is_some());
}
#[test]
fn grammar_error_source_none() {
assert!(GrammarError::MultipleStartSymbols(production("A", "B"))
.source()
.is_none());
assert!(GrammarError::NoStartSymbol(None).source().is_none());
assert!(GrammarError::WrongNonTerminals.source().is_none());
assert!(GrammarError::WrongTerminals.source().is_none());
assert!(GrammarError::WrongStartSymbol(symbol("a"))
.source()
.is_none());
}
}