-- | Nullable nonterminals
{-# LANGUAGE ScopedTypeVariables #-}
module Data.Cfg.Nullable(nullables) where

import Control.Monad(guard)
import Data.Cfg.Cfg
import Data.Cfg.FixedPoint(fixedPoint)
import qualified Data.Set as S

-- | Returns the nonterminals in the grammar that can produce the
-- empty string.
nullables :: forall cfg t nt . (Cfg cfg t nt, Ord nt)
	  => cfg t nt -> S.Set nt
nullables cfg = fixedPoint go S.empty
    where
    go :: S.Set nt -> S.Set nt
    go knownNullables = calculatedNullables
	where
	isKnownNullable :: V t nt -> Bool
	isKnownNullable (NT nm) = nm `S.member` knownNullables
	isKnownNullable _ = False

	calculatedNullables :: S.Set nt
	calculatedNullables = S.fromList $ do
	    nt <- S.toList $ nonterminals cfg
	    let rhss = S.toList $ productionRules cfg nt
	    guard $ any (all isKnownNullable) rhss
            return nt