{-|
Module      : PP.Grammars.LexicalHelper
Description : Add lexical support to other grammars
Copyright   : (c) 2017 Patrick Champion
License     : see LICENSE file
Maintainer  : chlablak@gmail.com
Stability   : provisional
Portability : portable
-}
module PP.Grammars.LexicalHelper
    ( -- *AST helper for other grammars
      LexicalRule(..)
    , LexicalDefinitionList(..)
    , LexicalDefinition(..)
      -- *Helpers
    , lexicalString
    ) where

import           Control.Applicative                    ((<$>), (<*>))
import           Data.Text                              (pack, strip, unpack)
import           PP.Grammar
import qualified PP.Rule                                as R
import           Text.ParserCombinators.Parsec
import           Text.ParserCombinators.Parsec.Language (emptyDef)
import qualified Text.ParserCombinators.Parsec.Token    as Token

-- |Defines a lexical rule represented by a identifier
data LexicalRule = LexicalRule String LexicalDefinitionList
  deriving (Show, Eq)

-- |Defines the definition list for a lexical rule
newtype LexicalDefinitionList = LexicalDefinitionList [LexicalDefinition]
  deriving (Show, Eq)

-- |Lexical rule definition component
data LexicalDefinition
  -- |Regular expression as a terminal string
  = LexicalRegEx String
  | LexicalString String
  -- |Other lexical rule identifier
  | LexicalIdentifier String
    deriving (Show, Eq)

-- |Construct a simple lexical rule
lexicalString :: String -> String -> LexicalRule
lexicalString n s = LexicalRule n $ LexicalDefinitionList [LexicalString s]

-- |Parsing helpers
lexer = Token.makeTokenParser def
  where
    def = emptyDef {
        Token.commentStart = "(*"
      , Token.commentEnd = "*)"
      , Token.commentLine = ""
      , Token.nestedComments = False
      , Token.identStart = letter
      , Token.identLetter = alphaNum <|> oneOf "_- "
      , Token.reservedNames = []
      , Token.reservedOpNames = ["%=", ";", ","]
      , Token.caseSensitive = True
    }

identifier = Token.identifier lexer
reservedOp = Token.reservedOp lexer
stringLiteral = Token.stringLiteral lexer
whiteSpace = Token.whiteSpace lexer

-- |Parser for LexicalRule
lexicalRule :: Parser LexicalRule
lexicalRule = whiteSpace *>
              (LexicalRule <$> (lexicalIdentifier <* reservedOp "%=")
                           <*> (lexicalDefinitionList <* reservedOp ";"))
  <?> "lexical rule"

-- |Parser for LexicalDefinitionList$
lexicalDefinitionList :: Parser LexicalDefinitionList
lexicalDefinitionList = LexicalDefinitionList <$> sepBy1 lexicalDefinition (reservedOp ",")
  <?> "lexical definition list"

-- |Parser for LexicalDefinition
lexicalDefinition :: Parser LexicalDefinition
lexicalDefinition = LexicalRegEx <$> stringLiteral
                <|> LexicalIdentifier <$> lexicalIdentifier
  <?> "lexical definition"

-- |Parser for LexicalIdentifier, helper
lexicalIdentifier :: Parser String
lexicalIdentifier = (unpack . strip . pack) <$> identifier
  <?> "lexical identifier"

-- *Associated InputGrammar instances
instance InputGrammar LexicalRule where
  parser = lexicalRule
  stringify (LexicalRule li xs) = li ++ "%=" ++ stringify xs ++ ";"
  rules (LexicalRule li xs) = R.uniformize [R.Rule li (rules xs ++ [R.Empty])]

instance InputGrammar LexicalDefinitionList where
  parser = lexicalDefinitionList
  stringify (LexicalDefinitionList []) = ""
  stringify (LexicalDefinitionList [x]) = stringify x
  stringify (LexicalDefinitionList (x:xs)) =
    stringify x ++ "," ++ stringify (LexicalDefinitionList xs)
  rules (LexicalDefinitionList xs) = [head (rules x) | x <- xs]

instance InputGrammar LexicalDefinition where
  parser = lexicalDefinition
  stringify (LexicalString x)     = show x
  stringify (LexicalRegEx x)      = show x
  stringify (LexicalIdentifier x) = x
  rules (LexicalRegEx x)      = [R.RegEx x]
  rules (LexicalString x)     = [R.RegExString x]
  rules (LexicalIdentifier x) = [R.NonTerm x]