{-|
Module      : PP.Builders.Lalr
Description : Builder for LALR parsers
Copyright   : (c) 2017 Patrick Champion
License     : see LICENSE file
Maintainer  : chlablak@gmail.com
Stability   : provisional
Portability : portable
-}
module PP.Builders.Lalr
    ( LalrItem(..)
    ) where

import           Control.Monad
import           Data.Binary
import qualified Data.List       as L
import qualified Data.Map.Strict as Map
import           Data.Maybe
import qualified Data.Set        as Set
import qualified Data.Vector     as Vector
import           PP.Builder
import           PP.Builders.Lr1
import           PP.Rule

-- |LALR item
data LalrItem = LalrItem Rule Int Rule
  deriving (Eq, Ord)

instance Show LalrItem where
  show (LalrItem (Rule a xs) p la) =
    "[" ++ a ++ " -> " ++ right xs p ++ "; " ++ show la ++ "]"
    where
      right :: [Rule] -> Int -> String
      right [] _     = ""
      right xs 0     = "*," ++ right xs (-1)
      right [x] _    = show x
      right (x:xs) p = show x ++ "," ++ right xs (p - 1)

instance Binary LalrItem where
  put (LalrItem r p la) = put r >> put p >> put la
  get = liftM3 LalrItem get get get

-- |LrBuilder instance for LalrItem
instance LrBuilder LalrItem where
  collection rs fs = fusion (collection rs fs :: LrCollection Lr1Item)
  -- Dragon Book (2nd edition, fr), page 243, algorithm 4.56 (without step 1)
  table c = case actions of
    Right act -> Right $ Map.union (Map.fromList act) (Map.fromList gotos)
    Left err  -> Left err
    where
      actions = let act = shifts ++ reduces ++ accepts in
        case conflict [] [] act of
          [] -> Right act
          xs -> Left xs
      conflict _ con [] = con
      conflict acc con ((k, v):xs) = case L.lookup k acc of
        Nothing -> conflict ((k,v):acc) con xs
        Just v2 -> conflict acc ((show k ++ " conflict: " ++ show v ++ " with " ++ show v2) : con) xs
      shifts = [((i, s), LrShift $ fromJust j)
              | i <- [0..(Vector.length c - 1)]
              , let is = c Vector.! i
              , s <- symbol is
              , term s
              , let j = next gs i s
              , isJust j]
      reduces = [((i, la), LrReduce r)
               | i <- [0..(Vector.length c - 1)]
               , let is = c Vector.! i
               , x@(LalrItem r@(Rule s _) _ la) <- reductibles is
               , s /= "__start"]
      accepts = [((i, Empty), LrAccept)
               | i <- [0..(Vector.length c - 1)]
               , let is = c Vector.! i
               , acc is]
      gotos = [((i, s), LrGoto $ fromJust j)
             | i <- [0..(Vector.length c - 1)]
             , let is = c Vector.! i
             , s <- symbol is
             , nonTerm s
             , let j = next gs i s
             , isJust j]
      term (Term _)      = True
      term (TermToken _) = True
      term _             = False
      reductibles is = [x | x <- Set.toList is, reductible x]
      reductible (LalrItem (Rule _ xs) p _) = L.length xs == p + 1
      acc = not . Set.null . Set.filter
        (\(LalrItem (Rule s _) p la) -> s == "__start" && p == 1 && la == Empty)
      nonTerm (NonTerm _) = True
      nonTerm _           = False
      gs = gotoSet c

-- |Construct the GOTO table
type GotoSet = Map.Map (Int, Rule) Int
gotoSet :: LrCollection LalrItem -> GotoSet
gotoSet c = Map.fromList [((i, s), fromJust j)
                        | i <- [0..(Vector.length c - 1)]
                        , let is = c Vector.! i
                        , s <- symbol is
                        , let j = goto c i s
                        , isJust j]

-- |Get the next items set
next :: GotoSet -> Int -> Rule -> Maybe Int
next gs i r = Map.lookup (i, r) gs

-- |Find the next possible symbols
symbol :: LrSet LalrItem -> [Rule]
symbol is = L.sort $ L.nub [x
                          | LalrItem (Rule _ xs) p _ <- Set.toList is
                          , let x = xs !! p
                          , x /= Empty]

-- |Find the next set
goto :: LrCollection LalrItem -> Int -> Rule -> Maybe Int
goto c i = goto' (c Vector.! i)
  where
    goto' is r = case list is r of
      []    -> Nothing
      (x:_) -> find $ inc x
    find x = Vector.findIndex (Set.member x) c
    list is r = [x | x <- Set.toList is, accept x r]
    accept (LalrItem (Rule _ xs) p _) r = xs !! p == r
    inc (LalrItem r p la) = LalrItem r (p + 1) la

-- |Compute the LALR collection from a LR(1) collection
-- Dragon Book (2nd edition, fr), page 246, example 4.60
fusion :: LrCollection Lr1Item -> LrCollection LalrItem
fusion lr1 = Vector.foldl' fusion' Vector.empty lalr
  where
    fusion' acc is = case core acc is of
      []  -> acc
      [_] -> Vector.snoc acc is
      xs  -> Vector.snoc acc (Set.unions xs)
    core acc is = [isx | isx <- Vector.toList lalr, same isx is, unique isx acc]
    unique is acc = L.null [0 | isx <- Vector.toList acc, same is isx]
    same isa isb = component isa == component isb
    component = Set.toList . Set.map (\(LalrItem r p _) -> (r, p))
    lalr = toLalrItem lr1

-- |Transform Lr1Item into LalrItem
toLalrItem :: LrCollection Lr1Item -> LrCollection LalrItem
toLalrItem = Vector.map (Set.map (\(Lr1Item r p la) -> LalrItem r p la))
