artlr_syntax 0.3.0

Another Rust tool for syntax analysis
Documentation
use std::collections::{HashMap, HashSet};
use std::hash::Hash;
use std::iter::FromIterator;

use crate::grammar::context_free_grammar_production::ContextFreeGrammarProduction;

pub struct ContextFreeGrammar<T> {
    epsilon_symbol: T,
    initial_symbol: T,
    non_terminal_symbols_set: HashSet<T>,
    productions: HashMap<T, Vec<ContextFreeGrammarProduction<T>>>,
    terminal_symbols_set: HashSet<T>,
}

impl<T> ContextFreeGrammar<T> {
    pub fn get_epsilon_symbol(&self) -> &T {
        &self.epsilon_symbol
    }

    pub fn get_initial_symbol(&self) -> &T {
        &self.initial_symbol
    }
}

impl<T: Clone> ContextFreeGrammar<T> {
    pub fn get_non_terminal_symbols(&self) -> Vec<T> {
        self.non_terminal_symbols_set
            .iter()
            .map(|symbol| symbol.clone())
            .collect()
    }

    pub fn get_terminal_symbols(&self) -> Vec<T> {
        self.terminal_symbols_set
            .iter()
            .map(|symbol| symbol.clone())
            .collect()
    }
}

impl<T: Eq + Hash> ContextFreeGrammar<T> {
    pub fn get_productions(&self, symbol: &T) -> Option<&Vec<ContextFreeGrammarProduction<T>>> {
        self.productions.get(symbol)
    }

    pub fn is_non_terminal(&self, symbol: &T) -> bool {
        self.non_terminal_symbols_set.contains(symbol)
    }

    pub fn is_terminal(&self, symbol: &T) -> bool {
        self.terminal_symbols_set.contains(symbol)
    }
}

impl<T: Clone + Eq + Hash> ContextFreeGrammar<T> {
    pub fn new(
        epsilon_symbol: T,
        initial_symbol: T,
        productions: Vec<ContextFreeGrammarProduction<T>>,
    ) -> Self {
        let productions_hash_map: HashMap<T, Vec<ContextFreeGrammarProduction<T>>> =
            Self::new_process_productions_map(productions);

        Self::new_check_productions_map(&epsilon_symbol, &productions_hash_map);

        let non_terminal_symbols: Vec<T> = productions_hash_map.keys().map(|key| key.clone()).collect();
        let non_terminal_symbols_set: HashSet<T> = HashSet::from_iter(non_terminal_symbols.into_iter());
        let terminal_symbols_set: HashSet<T> = ContextFreeGrammar::build_terminal_symbols_set(
            &non_terminal_symbols_set,
            &productions_hash_map,
        );

        ContextFreeGrammar {
            epsilon_symbol,
            initial_symbol,
            non_terminal_symbols_set,
            productions: productions_hash_map,
            terminal_symbols_set,
        }
    }

    fn build_terminal_symbols_set(
        non_terminal_symbols_set: &HashSet<T>,
        productions_hash_map: &HashMap<T, Vec<ContextFreeGrammarProduction<T>>>,
    ) -> HashSet<T> {
        let mut terminal_symbols_set: HashSet<T> = HashSet::new();

        productions_hash_map.values().for_each(|productions| {
            productions.iter().for_each(|production| {
                production.output.iter().for_each(|symbol| {
                    if !non_terminal_symbols_set.contains(symbol) {
                        terminal_symbols_set.insert(symbol.clone());
                    }
                });
            });
        });

        terminal_symbols_set
    }

    fn new_check_productions_map(
        epsilon_symbol: &T,
        productions_map: &HashMap<T, Vec<ContextFreeGrammarProduction<T>>>,
    ) -> () {
        for (symbol, productions) in productions_map {
            if symbol.eq(epsilon_symbol) {
                panic!("Expected epsilon symbol not to generate any symbols!");
            }

            for production in productions {
                if production.output.len() == 0 {
                    panic!("Expected production to have at least one symbol");
                } else {
                    if production.output.len() > 1 && production.output.contains(epsilon_symbol) {
                        panic!("Expected epsilon production not to have additional symbol");
                    }
                }
            }
        }
    }

    fn new_process_productions_map(productions: Vec<ContextFreeGrammarProduction<T>>) -> HashMap<T, Vec<ContextFreeGrammarProduction<T>>> {
        let mut productions_hash_map: HashMap<T, Vec<ContextFreeGrammarProduction<T>>> =
            HashMap::new();

        for production in productions {
            let current_productions: Option<&mut Vec<ContextFreeGrammarProduction<T>>> =
                productions_hash_map.get_mut(&production.input);

            match current_productions {
                None => {
                    productions_hash_map.insert(production.input.clone(), vec![production]);
                }
                Some(productions) => productions.push(production),
            }
        }

        productions_hash_map
    }
}