/*
 * NullableNonterminals.h
 *
 *  Created on: 22. 3. 2014
 *	  Author: Tomas Pecka
 */

#ifndef NULLABLE_NONTERMINALS_H_
#define NULLABLE_NONTERMINALS_H_

#include <alib/set>
#include <alib/deque>
#include <alib/algorithm>

#include <grammar/Grammar.h>

#include <grammar/RawRules.h>

namespace grammar {

namespace properties {

/**
 * Implements algorithms from Melichar, chapter 2.2
 */
class NullableNonterminals {
public:
	/**
	 * Retrieve all nullable nonterminals from grammar
	 * Nullable nonterminal is such nonterminal A for which holds that A ->^* \eps
	 *
	 * Source: Melichar, algorithm 2.4, step 1
	 *
	 * \tparam T the type of the tested grammar
	 * \tparam TerminalSymbolType the type of terminals in the tested grammar
	 * \tparam NonterminalSymbolType the type of nonterminals in the tested grammar
	 *
	 * \param grammar the tested grammar
	 * \return set of nullable nonterminals from the @p grammar
	 */
	template<class T, class TerminalSymbolType = typename grammar::TerminalSymbolTypeOfGrammar < T >, class NonterminalSymbolType = typename grammar::NonterminalSymbolTypeOfGrammar < T > >
	static ext::set < NonterminalSymbolType > getNullableNonterminals ( const T & grammar );
};

template < class T, class TerminalSymbolType, class NonterminalSymbolType >
ext::set < NonterminalSymbolType > NullableNonterminals::getNullableNonterminals ( const T & grammar ) {
	auto rawRules = grammar::RawRules::getRawRules ( grammar );

	ext::deque < ext::set < NonterminalSymbolType > > Ni;

	Ni.push_back ( ext::set < NonterminalSymbolType > { } );
	int i = 0;

	auto testCallback = [ & ] ( const ext::variant < TerminalSymbolType, NonterminalSymbolType > & symb ) {
		return Ni.at ( i - 1 ).count ( symb );
	};

	do {
		i += 1;

		Ni.push_back ( ext::set < NonterminalSymbolType > { } );
		for ( const auto & rule : rawRules ) {
			for ( const auto & rhs : rule.second ) {
				if ( rhs.empty ( ) || std::all_of ( rhs.begin ( ), rhs.end ( ), testCallback ) ) {
					Ni.at ( i ).insert ( rule.first );
				}
			}
		}

	} while ( Ni.at ( i ) != Ni.at ( i - 1 ) );

	return Ni.at(i);
}

} /* namespace properties */

} /* namespace grammar */

#endif /* NULLABLE_NONTERMINALS_H_ */
