{-|
Module      : PP.Grammar
Description : Common behavior for defined grammars
Copyright   : (c) 2017 Patrick Champion
License     : see LICENSE file
Maintainer  : chlablak@gmail.com
Stability   : provisional
Portability : portable
-}
module PP.Grammar
    ( To
    , InputGrammar(..)
    , rules'
    ) where

import           Control.Exception
import           Data.Either
import           PP.Rule           (Rule)
import qualified Text.Parsec       as P

-- |Syntactic sugar
-- For exemple: `case PP.parseAst input :: (PP.To Ebnf.Syntax) of ...`
type To ast = Either P.ParseError ast

-- |Type class for grammars
class (Eq ast, Show ast) => InputGrammar ast where
  -- |Entry parser
  parser :: P.Parsec String () ast
  -- |Parse String to AST
  parseAst :: String -> To ast
  parseAst = P.parse parser ""
  -- |AST to String
  stringify :: ast -> String
  stringify = show
  -- |AST to canonical rules
  rules :: ast -> [Rule]
  -- |Transform terminals to lexical rules
  lexify :: ast -> ast
  lexify = id

-- |Exception-safe version of `rules`
rules' :: (InputGrammar ast) => ast -> IO (Either String [Rule])
rules' ast = do
    a <- try (evaluate $ rules ast) :: IO (Either SomeException [Rule])
    case a of
        Left e  -> return $ Left $ head $ lines $ displayException e
        Right r -> return $ Right r