lalrpop 0.4.0

convenient LR(1) parser generator
//! Resolves identifiers to decide if they are macros, terminals, or
//! nonterminals.

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

use grammar::parse_tree::*;
use grammar::repr;
use intern::{intern, InternedString};
use util::{Map, Multimap, Sep, set};

#[cfg(test)]
mod test;

pub fn validate(grammar: &Grammar) -> NormResult<()> {
    let globals = ScopeChain {
        previous: None,
        nonterminals: grammar.items
                             .iter()
                             .filter_map(|item| item.as_nonterminal())
                             .map(|nt| (nt.name, Def::TopLevel(nt.args.len())))
                             .collect()
    };

    let extern_token: Option<&ExternToken> =
        grammar.items
               .iter()
               .filter_map(|item| item.as_extern_token())
               .next();

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

    validator.validate()
}

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

#[derive(Copy, Clone, Debug)]
enum Def {
    TopLevel(usize), // argument is the number of macro arguments
    MacroArg
}

#[derive(Debug)]
struct ScopeChain<'scope> {
    previous: Option<&'scope ScopeChain<'scope>>,
    nonterminals: Map<NonterminalString, Def>,
}

impl<'grammar> Validator<'grammar> {
    fn validate(&self) -> NormResult<()> {
        if let Some(ref algorithm) = self.grammar.algorithm {
            match repr::Algorithm::from_str(algorithm.text) {
                Some(_) => { }
                None => {
                    return_err!(
                        algorithm.span,
                        "unrecognized algorithm `{}`", algorithm.text);
                }
            }
        }

        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 token 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);
                        }
                    }

                    match data.enum_token.type_name {
                        TypeRef::Id(_) | TypeRef::Nominal { .. } => { /* OK */ }
                        _ => {
                            return_err!(
                                data.enum_token.type_span,
                                "expected a nominal type here, like `Token` or `foo::Token<T>`");
                        }
                    }
                }
                GrammarItem::Nonterminal(ref data) => {
                    let nonterminals = try!(self.validate_macro_args(data.span, &data.args));
                    let locals = ScopeChain {
                        previous: Some(&self.globals),
                        nonterminals: nonterminals,
                    };
                    for alternative in &data.alternatives {
                        try!(self.validate_alternative(&locals, alternative));
                    }
                }
            }
        }
        Ok(())
    }

    fn validate_macro_args(&self,
                           span: Span,
                           args: &[NonterminalString])
                           -> NormResult<Map<NonterminalString, Def>> {
        for (index, arg) in args.iter().enumerate() {
            if args[..index].contains(&arg) {
                return_err!(span, "multiple macro arguments declared with the name `{}`", arg);
            }
        }
        Ok(args.iter().map(|&nt| (nt, Def::MacroArg)).collect())
    }

    fn validate_alternative(&self,
                            scope: &ScopeChain,
                            alternative: &Alternative)
                            -> NormResult<()> {
        if let Some(ref condition) = alternative.condition {
            let def = try!(self.validate_nt(scope, condition.span, condition.lhs));
            match def {
                Def::MacroArg => { /* OK */ }
                Def::TopLevel(_) => {
                    return_err!(condition.span,
                                "only macro arguments can be used in conditions, not `{}`",
                                condition.lhs);
                }
            }
        }

        try!(self.validate_expr(scope, &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,
                     scope: &ScopeChain,
                     expr: &ExprSymbol)
                     -> NormResult<()> {
        for symbol in &expr.symbols {
            try!(self.validate_symbol(scope, symbol));
        }

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

        let named: Multimap<InternedString, &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,
                       scope: &ScopeChain,
                       symbol: &Symbol)
                       -> NormResult<()> {
        match symbol.kind {
            SymbolKind::Expr(ref expr) => {
                try!(self.validate_expr(scope, expr));
            }
            SymbolKind::Terminal(_) => {
                /* see postvalidate! */
            }
            SymbolKind::Nonterminal(nt) => {
                try!(self.validate_nt(scope, symbol.span, nt));
            }
            SymbolKind::Macro(ref msym) => {
                debug_assert!(msym.args.len() > 0);
                let def = try!(self.validate_nt(scope, symbol.span, msym.name));
                match def {
                    Def::MacroArg => {
                        return_err!(symbol.span, "`{}` is not a macro", msym.name)
                    }
                    Def::TopLevel(arity) => {
                        if arity == 0 {
                            return_err!(symbol.span, "`{}` is not a macro", msym.name);
                        } else if arity != msym.args.len() {
                            return_err!(symbol.span,
                                        "wrong number of arguments to `{}`: \
                                         expected {}, found {}",
                                        msym.name, arity, msym.args.len());
                        }
                    }
                }

                for arg in &msym.args {
                    try!(self.validate_symbol(scope, arg));
                }
            }
            SymbolKind::Repeat(ref repeat) => {
                try!(self.validate_symbol(scope, &repeat.symbol));
            }
            SymbolKind::Choose(ref sym) | SymbolKind::Name(_, ref sym) => {
                try!(self.validate_symbol(scope, sym));
            }
            SymbolKind::Lookahead | SymbolKind::Lookbehind => {
                if self.extern_token.is_none() {
                    return_err!(symbol.span,
                                "lookahead/lookbehind not permitted without \
                                 an extern token declaration");
                }

                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(())
    }

    fn validate_nt(&self,
                   scope: &ScopeChain,
                   span: Span,
                   nt: NonterminalString)
                   -> NormResult<Def> {
        match scope.def(nt) {
            Some(def) => Ok(def),
            None => return_err!(span, "no definition found for nonterminal `{}`", nt)
        }
    }
}

impl<'scope> ScopeChain<'scope> {
    fn def(&self, nt: NonterminalString) -> Option<Def> {
        self.nonterminals.get(&nt)
                         .cloned()
                         .or_else(|| self.previous.and_then(|s| s.def(nt)))
    }
}