module Happy.Frontend.ParamRules(expand_rules, Prod1(..), Rule1(..)) where

import Happy.Frontend.AbsSyn
import Control.Monad.Writer
import Control.Monad.Except(throwError)
import Control.Monad.Trans.Except
import Data.List(partition,intersperse)
import qualified Data.Set as S
import qualified Data.Map as M    -- XXX: Make it work with old GHC.

-- | Desugar parameterized productions into non-parameterized ones
--
-- This transformation is fairly straightforward: we walk through every rule
-- and collect every possible instantiation of parameterized productions. Then,
-- we generate a new non-parametrized rule for each of these.
expand_rules :: [Rule e] -> Either String [Rule1 e]
expand_rules rs = do let (funs,rs1) = split_rules rs
                     (as,is) <- runM2 (mapM (`inst_rule` []) rs1)
                     bs <- make_insts funs (S.toList is) S.empty
                     return (as++bs)

type RuleName = String

data Inst     = Inst RuleName [RuleName] deriving (Eq, Ord)
newtype Funs e = Funs (M.Map RuleName (Rule e))

-- | Similar to 'Rule', but `Term`'s have been flattened into `RuleName`'s
data Rule1 e  = Rule1 RuleName [Prod1 e] (Maybe (String, Subst))

-- | Similar to 'Prod', but `Term`'s have been flattened into `RuleName`'s
data Prod1 e  = Prod1 [RuleName] e Int Prec

inst_name :: Inst -> RuleName
inst_name (Inst f [])  = f
--inst_name (Inst f xs)  = f ++ "(" ++ concat (intersperse "," xs) ++ ")"
inst_name (Inst f xs)  = f ++ "__" ++ concat (intersperse "__" xs) ++ "__"


-- | A renaming substitution used when we instantiate a parameterized rule.
type Subst    = [(RuleName,RuleName)]
type M1       = Writer (S.Set Inst)
type M2       = ExceptT String M1

-- | Collects the instances arising from a term.
from_term :: Subst -> Term -> M1 RuleName
from_term s (App f [])  = return $ case lookup f s of
                            Just g  -> g
                            Nothing -> f

from_term s (App f ts)  = do xs <- from_terms s ts
                             let i = Inst f xs
                             tell (S.singleton i)
                             return $ inst_name i

-- | Collects the instances arising from a list of terms.
from_terms :: Subst -> [Term] -> M1 [RuleName]
from_terms s ts = mapM (from_term s) ts

-- XXX: perhaps change the line to the line of the instance
inst_prod :: Subst -> Prod e -> M1 (Prod1 e)
inst_prod s (Prod ts c l p)  = do xs <- from_terms s ts
                                  return (Prod1 xs c l p)

inst_rule :: Rule e -> [RuleName] -> M2 (Rule1 e)
inst_rule (Rule x xs ps t) ts  = do s <- build xs ts []
                                    ps1 <- lift $ mapM (inst_prod s) ps
                                    let y = inst_name (Inst x ts)
                                    return (Rule1 y ps1 (fmap (\x' -> (x',s)) t))
  where build (x':xs') (t':ts') m = build xs' ts' ((x',t'):m)
        build [] [] m  = return m
        build xs' [] _  = err ("Need " ++ show (length xs') ++ " more arguments")
        build _ ts' _   = err (show (length ts') ++ " arguments too many.")

        err m = throwError ("In " ++ inst_name (Inst x ts) ++ ": " ++ m)

make_rule :: Funs e -> Inst -> M2 (Rule1 e)
make_rule (Funs funs) (Inst f xs) =
  case M.lookup f funs of
    Just r  -> inst_rule r xs
    Nothing -> throwError ("Undefined rule: " ++ f)

runM2 :: ExceptT e (Writer w) a -> Either e (a, w)
runM2 m = case runWriter (runExceptT m) of
            (Left e,_)   -> Left e
            (Right a,xs) -> Right (a,xs)

make_insts :: Funs e -> [Inst] -> S.Set Inst -> Either String [Rule1 e]
make_insts _ [] _ = return []
make_insts funs is done =
  do (as,ws) <- runM2 (mapM (make_rule funs) is)
     let done1 = S.union (S.fromList is) done
     let is1 = filter (not . (`S.member` done1)) (S.toList ws)
     bs <- make_insts funs is1 done1
     return (as++bs)


split_rules :: [Rule e] -> (Funs e,[Rule e])
split_rules rs = let (xs,ys) = partition has_args rs
                 in (Funs (M.fromList [ (x,r) | r@(Rule x _ _ _) <- xs ]),ys)
  where has_args (Rule _ args _ _) = not (null args)
