lalrpop 0.12.2

convenient LR(1) parser generator
Documentation
//! Validate checks some basic safety conditions.

use super::{NormResult, NormError};
use super::norm_util::{self, Symbols};

use grammar::consts::*;
use grammar::parse_tree::*;
use grammar::repr as r;
use intern::{intern, InternedString};
use collections::{Multimap, set};
use util::Sep;

#[cfg(test)]
mod test;

pub fn validate(grammar: &Grammar) -> NormResult<()> {
    let extern_token: Option<&ExternToken> =
        grammar.items
               .iter()
               .filter_map(|item| item.as_extern_token())
               .next();

    let validator = Validator {
        grammar: grammar,
        extern_token: extern_token,
    };

    validator.validate()
}

struct Validator<'grammar> {
    grammar: &'grammar Grammar,
    extern_token: Option<&'grammar ExternToken>,
}

impl<'grammar> Validator<'grammar> {
    fn validate(&self) -> NormResult<()> {
        let allowed_names = vec![intern(LALR),
                                 intern(TABLE_DRIVEN),
                                 intern(RECURSIVE_ASCENT),
                                 intern(TEST_ALL)];
        for annotation in &self.grammar.annotations {
            if !allowed_names.contains(&annotation.id) {
                return_err!(annotation.id_span,
                            "unrecognized annotation `{}`",
                            annotation.id);
            }
        }

        for item in &self.grammar.items {
            match *item {
                GrammarItem::Use(..) => { }
                GrammarItem::ExternToken(ref data) => {
                    if data.span != self.extern_token.unwrap().span {
                        return_err!(
                            data.span,
                            "multiple extern definitions are not permitted");
                    }

                    let allowed_names = vec![intern(LOCATION), intern(ERROR)];
                    let mut new_names = set();
                    for associated_type in &data.associated_types {
                        if !allowed_names.contains(&associated_type.type_name) {
                            return_err!(
                                associated_type.type_span,
                                "associated type `{}` not recognized, \
                                 try one of the following: {}",
                                associated_type.type_name,
                                Sep(", ", &allowed_names));
                        } else if !new_names.insert(associated_type.type_name) {
                            return_err!(
                                associated_type.type_span,
                                "associated type `{}` already specified",
                                associated_type.type_name);
                        }
                    }
                }
                GrammarItem::Nonterminal(ref data) => {
                    let inline_annotation = intern(INLINE);
                    let known_annotations = vec![inline_annotation];
                    let mut found_annotations = set();
                    for annotation in &data.annotations {
                        if !known_annotations.contains(&annotation.id) {
                            return_err!(annotation.id_span,
                                        "unrecognized annotation `{}`",
                                        annotation.id);
                        } else if !found_annotations.insert(annotation.id) {
                            return_err!(annotation.id_span,
                                        "duplicate annotation `{}`",
                                        annotation.id);
                        } else if annotation.id == inline_annotation && data.public {
                            return_err!(annotation.id_span,
                                        "public items cannot be marked #[inline]");
                        }
                    }

                    for alternative in &data.alternatives {
                        try!(self.validate_alternative(alternative));
                    }
                }
                GrammarItem::InternToken(..) => { }
            }
        }
        Ok(())
    }

    fn validate_alternative(&self,
                            alternative: &Alternative)
                            -> NormResult<()> {
        try!(self.validate_expr(&alternative.expr));

        match norm_util::analyze_expr(&alternative.expr) {
            Symbols::Named(syms) => {
                if alternative.action.is_none() {
                    let sym =
                        syms.iter()
                            .map(|&(_, _, sym)| sym)
                            .next()
                            .unwrap();
                    return_err!(
                        sym.span,
                        "named symbols (like `{}`) require a custom action",
                        sym);
                }
            }
            Symbols::Anon(_) => { }
        }

        Ok(())
    }

    fn validate_expr(&self,
                     expr: &ExprSymbol)
                     -> NormResult<()> {
        for symbol in &expr.symbols {
            try!(self.validate_symbol(symbol));
        }

        let chosen: Vec<&Symbol> =
            expr.symbols.iter()
                        .filter(|sym| match sym.kind {
                            SymbolKind::Choose(_) => true,
                            _ => false,
                        })
                        .collect();

        let named: Multimap<InternedString, Vec<&Symbol>> =
            expr.symbols.iter()
                        .filter_map(|sym| match sym.kind {
                            SymbolKind::Name(nt, _) => Some((nt, sym)),
                            _ => None,
                        })
                        .collect();

        if !chosen.is_empty() && !named.is_empty() {
            return_err!(chosen[0].span,
                        "anonymous symbols like this one cannot be combined with \
                         named symbols like `{}`",
                        named.into_iter().next().unwrap().1[0]);
        }

        for (name, syms) in named.into_iter() {
            if syms.len() > 1{
                return_err!(syms[1].span,
                            "multiple symbols named `{}` are not permitted",
                            name);
            }
        }

        Ok(())
    }

    fn validate_symbol(&self,
                       symbol: &Symbol)
                       -> NormResult<()> {
        match symbol.kind {
            SymbolKind::Expr(ref expr) => {
                try!(self.validate_expr(expr));
            }
            SymbolKind::AmbiguousId(_) => {
                /* see resolve */
            }
            SymbolKind::Terminal(_) => {
                /* see postvalidate! */
            }
            SymbolKind::Nonterminal(_) => {
                /* see resolve */
            }
            SymbolKind::Error => {
                let mut algorithm = r::Algorithm::default();
                read_algorithm(&self.grammar.annotations, &mut algorithm);
                if algorithm.codegen == r::LrCodeGeneration::RecursiveAscent {
                    return_err!(symbol.span, "error recovery is not yet supported by recursive ascent parsers");
                }
            }
            SymbolKind::Macro(ref msym) => {
                debug_assert!(msym.args.len() > 0);
                for arg in &msym.args {
                    try!(self.validate_symbol(arg));
                }
            }
            SymbolKind::Repeat(ref repeat) => {
                try!(self.validate_symbol(&repeat.symbol));
            }
            SymbolKind::Choose(ref sym) | SymbolKind::Name(_, ref sym) => {
                try!(self.validate_symbol(sym));
            }
            SymbolKind::Lookahead | SymbolKind::Lookbehind => {
                // if using an internal tokenizer, lookahead/lookbehind are ok.
                if let Some(extern_token) = self.extern_token {
                    if extern_token.enum_token.is_some() {
                        // otherwise, the Location type must be specified.
                        let loc = intern(LOCATION);
                        if self.extern_token.unwrap().associated_type(loc).is_none() {
                            return_err!(
                                symbol.span,
                                "lookahead/lookbehind require you to declare the type of \
                                 a location; add a `type {} = ..` statement to the extern token \
                                 block",
                                LOCATION);
                        }
                    }
                }
            }
        }

        Ok(())
    }
}