use super::{NormError, NormResult};
use crate::collections::{Map, Set};
use crate::grammar::consts::*;
use crate::grammar::parse_tree::*;
use crate::lexer::dfa::{self, DFAConstructionError, Precedence};
use crate::lexer::nfa::NFAConstructionError::*;
use crate::lexer::re;
use string_cache::DefaultAtom as Atom;
#[cfg(test)]
mod test;
pub fn validate(mut grammar: Grammar) -> NormResult<Grammar> {
let mode = {
let mode = if let Some(enum_token) = grammar.enum_token() {
assert!(
grammar.match_token().is_none(),
"validator permitted both an extern/match section"
);
TokenMode::Extern {
conversions: enum_token
.conversions
.iter()
.map(|conversion| conversion.from.clone())
.collect(),
}
} else {
if cfg!(not(feature = "lexer")) {
return_err!(
Span::default(),
"The `lexer` feature must be specified unless an `extern` lexer is defined"
);
}
TokenMode::Internal {
match_block: MatchBlock::new(grammar.match_token())?,
}
};
let mut validator = Validator {
grammar: &grammar,
mode,
};
validator.validate()?;
validator.mode
};
match mode {
TokenMode::Extern { .. } => {
}
TokenMode::Internal { match_block } => {
construct(&mut grammar, match_block)?;
}
}
Ok(grammar)
}
struct Validator<'grammar> {
grammar: &'grammar Grammar,
mode: TokenMode,
}
enum TokenMode {
Extern { conversions: Set<TerminalString> },
Internal { match_block: MatchBlock },
}
#[derive(Default)]
struct MatchBlock {
match_entries: Vec<MatchEntry>,
match_user_names: Set<TerminalString>,
spans: Map<TerminalLiteral, Span>,
catch_all: bool,
}
impl MatchBlock {
fn new(opt_match_token: Option<&MatchToken>) -> NormResult<Self> {
let mut match_block = Self::default();
if let Some(match_token) = opt_match_token {
for (idx, mc) in match_token.contents.iter().enumerate() {
let precedence = match_token.contents.len() - idx;
for item in &mc.items {
match *item {
MatchItem::Unmapped(ref sym, span) => {
match_block.add_match_entry(
precedence,
sym.clone(),
MatchMapping::Terminal(TerminalString::Literal(sym.clone())),
span,
)?;
}
MatchItem::Mapped(ref sym, ref user, span) => {
match_block.add_match_entry(
precedence,
sym.clone(),
user.clone(),
span,
)?;
}
MatchItem::CatchAll(_) => {
match_block.catch_all = true;
}
}
}
}
} else {
match_block.catch_all = true;
}
Ok(match_block)
}
fn add_match_entry(
&mut self,
match_group_precedence: usize,
sym: TerminalLiteral,
user_name: MatchMapping,
span: Span,
) -> NormResult<()> {
if let Some(_old_span) = self.spans.insert(sym.clone(), span) {
return_err!(span, "multiple match entries for `{}`", sym);
}
if let MatchMapping::Terminal(user_name) = &user_name {
self.match_user_names.insert(user_name.clone());
}
self.match_entries.push(MatchEntry {
precedence: match_group_precedence * 2 + sym.base_precedence(),
match_literal: sym,
user_name,
});
Ok(())
}
fn add_literal_from_grammar(&mut self, sym: TerminalLiteral, span: Span) -> NormResult<()> {
if self
.match_user_names
.contains(&TerminalString::Literal(sym.clone()))
{
return Ok(());
}
if !self.catch_all {
return_err!(
span,
"terminal `{}` does not have a match mapping defined for it",
sym
);
}
self.match_user_names
.insert(TerminalString::Literal(sym.clone()));
self.match_entries.push(MatchEntry {
precedence: sym.base_precedence(),
match_literal: sym.clone(),
user_name: MatchMapping::Terminal(TerminalString::Literal(sym.clone())),
});
self.spans.insert(sym, span);
Ok(())
}
}
impl<'grammar> Validator<'grammar> {
fn validate(&mut self) -> NormResult<()> {
for item in &self.grammar.items {
match *item {
GrammarItem::Use(..) => {}
GrammarItem::MatchToken(..) => {}
GrammarItem::ExternToken(_) => {}
GrammarItem::InternToken(_) => {}
GrammarItem::Nonterminal(ref data) => {
for alternative in &data.alternatives {
self.validate_alternative(alternative)?;
}
}
}
}
Ok(())
}
fn validate_alternative(&mut self, alternative: &Alternative) -> NormResult<()> {
assert!(alternative.condition.is_none()); self.validate_expr(&alternative.expr)?;
Ok(())
}
fn validate_expr(&mut self, expr: &ExprSymbol) -> NormResult<()> {
for symbol in &expr.symbols {
self.validate_symbol(symbol)?;
}
Ok(())
}
fn validate_symbol(&mut self, symbol: &Symbol) -> NormResult<()> {
match symbol.kind {
SymbolKind::Expr(ref expr) => {
self.validate_expr(expr)?;
}
SymbolKind::Terminal(ref term) => {
self.validate_terminal(symbol.span, term)?;
}
SymbolKind::Nonterminal(_) => {}
SymbolKind::Repeat(ref repeat) => {
self.validate_symbol(&repeat.symbol)?;
}
SymbolKind::Choose(ref sym) | SymbolKind::Name(_, ref sym) => {
self.validate_symbol(sym)?;
}
SymbolKind::Lookahead | SymbolKind::Lookbehind | SymbolKind::Error => {}
SymbolKind::AmbiguousId(ref id) => {
panic!("ambiguous id `{}` encountered after name resolution", id)
}
SymbolKind::Macro(..) => {
panic!("macro not removed: {:?}", symbol);
}
}
Ok(())
}
fn validate_terminal(&mut self, span: Span, term: &TerminalString) -> NormResult<()> {
match self.mode {
TokenMode::Extern { ref conversions } => {
if !conversions.contains(term) {
return_err!(
span,
"terminal `{}` does not have a pattern defined for it",
term
);
}
}
TokenMode::Internal {
ref mut match_block,
} => {
match *term {
TerminalString::Bare(_) => assert!(
match_block.match_user_names.contains(term),
"bare terminal without match entry: {}",
term
),
TerminalString::Literal(ref l) => {
match_block.add_literal_from_grammar(l.clone(), span)?
}
TerminalString::Error => (),
}
}
}
Ok(())
}
}
fn construct(grammar: &mut Grammar, match_block: MatchBlock) -> NormResult<()> {
let MatchBlock {
mut match_entries,
spans,
..
} = match_block;
match_entries.sort();
let mut regexs = Vec::with_capacity(match_entries.len());
let mut precedences = Vec::with_capacity(match_entries.len());
for match_entry in &match_entries {
precedences.push(Precedence(match_entry.precedence));
match match_entry.match_literal {
TerminalLiteral::Quoted(ref s) => {
regexs.push(re::parse_literal(&s));
}
TerminalLiteral::Regex(ref s) => {
match re::parse_regex(&s) {
Ok(regex) => regexs.push(regex),
Err(error) => {
let literal_span = spans[&match_entry.match_literal];
return_err!(literal_span, "invalid regular expression: {}", error);
}
}
}
}
}
let dfa = match dfa::build_dfa(®exs, &precedences) {
Ok(dfa) => dfa,
Err(DFAConstructionError::NFAConstructionError { index, error }) => {
let feature = match error {
NamedCaptures => r#"named captures (`(?P<foo>...)`)"#,
NonGreedy => r#""non-greedy" repetitions (`*?` or `+?`)"#,
WordBoundary => r#"word boundaries (`\b` or `\B`)"#,
LineBoundary => r#"line boundaries (`^` or `$`)"#,
TextBoundary => r#"text boundaries (`^` or `$`)"#,
ByteRegex => r#"byte-based matches"#,
};
let literal = &match_entries[index.index()].match_literal;
return_err!(
spans[literal],
"{} are not supported in regular expressions",
feature
)
}
Err(DFAConstructionError::Ambiguity { match0, match1 }) => {
let literal0 = &match_entries[match0.index()].match_literal;
let literal1 = &match_entries[match1.index()].match_literal;
return_err!(
spans[literal0],
"ambiguity detected between the terminal `{}` and the terminal `{}`",
literal0,
literal1
)
}
};
grammar
.items
.push(GrammarItem::InternToken(InternToken { match_entries, dfa }));
let input_lifetime = Lifetime::input();
for parameter in &grammar.type_parameters {
match parameter {
TypeParameter::Lifetime(i) if *i == input_lifetime => {
return_err!(
grammar.span,
"since there is no external token enum specified, \
the `'input` lifetime is implicit and cannot be declared"
);
}
_ => {}
}
}
let input_parameter = Atom::from(INPUT_PARAMETER);
for parameter in &grammar.parameters {
if parameter.name == input_parameter {
return_err!(
grammar.span,
"since there is no external token enum specified, \
the `input` parameter is implicit and cannot be declared"
);
}
}
grammar
.type_parameters
.insert(0, TypeParameter::Lifetime(input_lifetime.clone()));
let parameter = Parameter {
name: input_parameter,
ty: TypeRef::Ref {
lifetime: Some(input_lifetime),
mutable: false,
referent: Box::new(TypeRef::Id(Atom::from("str"))),
},
};
grammar.parameters.push(parameter);
Ok(())
}