{-# Language OverloadedStrings #-}
{-# Language ScopedTypeVariables #-}
{-# Language GeneralizedNewtypeDeriving #-}
module E.Template
(
Tem(..)
, txt
, val
, ref
, PlainValue(..)
, PlainContent(..)
, EncValue(..)
, EncContent(..)
, ValName(..)
, ValRef(..)
, Args(..)
, ArgName(..)
, ArgValue(..)
, AlgName(..)
, arg
, lookupArg
, normalize
, parseMaybe
, parseArgNV
, parseArgs
, parsePlainValue
, parseValRef
, parseTem
, Serialize(..)
) where
import Control.Applicative
import Control.Arrow ((***))
import Control.Monad (MonadPlus)
import Data.Functor (void)
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as Map
import Data.Hashable
import Data.Text (Text)
import qualified Data.Text as Text
import Data.Aeson hiding (encode, Value, Result, Success)
import Data.Semigroup
import Data.Attoparsec.Text
import Data.Attoparsec.Combinator
import Text.Parser.Token (commaSep)
data Tem
= Nil
| {-# UNPACK #-} !Text `Txt` !Tem
| !PlainValue `Val` !Tem
| !ValRef `Ref` !Tem
deriving (Eq,Show)
txt :: Text -> Tem
txt t = t `Txt` Nil
{-# INLINE txt #-}
val :: PlainValue -> Tem
val p = p `Val` Nil
{-# INLINE val #-}
ref :: ValRef -> Tem
ref p = p `Ref` Nil
{-# INLINE ref #-}
instance Semigroup Tem where
Nil <> t = t
t <> Nil = t
text `Txt` s <> t = text `Txt` (s <> t)
plain `Val` s <> t = plain `Val` (s <> t)
r `Ref` s <> t = r `Ref` (s <> t)
{-# INLINE (<>) #-}
instance Monoid Tem where
mempty = Nil
{-# INLINE mempty #-}
mappend = (<>)
{-# INLINE mappend #-}
data PlainValue = PlainValue !ValName !AlgName !Args !PlainContent
deriving (Eq,Show)
data EncValue = EncValue !AlgName !Args !EncContent
deriving (Eq,Show)
instance FromJSON EncValue where
parseJSON = withObject "EncValue" $ \o -> EncValue
<$> (AlgName <$> o .: "alg")
<*> (o .:? "args" .!= mempty)
<*> (EncContent <$> o .: "value")
instance ToJSON EncValue where
toJSON (EncValue algName args content) =
object $
[ "alg" .= unAlgName algName
, "value" .= unEncContent content
] ++
if args == mempty
then []
else [ "args" .= toJSON args ]
data PlainContent = PlainContent { unPlainContent :: {-# UNPACK #-} !Text }
deriving (Eq,Show)
data EncContent = EncContent { unEncContent :: {-# UNPACK #-} !Text }
deriving (Eq,Show)
newtype ValName = ValName { unValName :: Text }
deriving (Ord,Eq,Show,Hashable,FromJSONKey,ToJSONKey)
newtype AlgName = AlgName { unAlgName :: Text }
deriving (Ord,Eq,Show,Hashable)
data Args = Args { unArgs :: HashMap ArgName ArgValue }
deriving (Eq,Show)
instance FromJSON Args where
parseJSON = fmap (Args . Map.fromList . map (ArgName *** ArgValue) . Map.toList) . parseJSON
instance ToJSON Args where
toJSON = object . map (\(k, v) -> unArgName k .= unArgValue v) . Map.toList . unArgs
instance Semigroup Args where
Args xs <> Args ys = Args (xs <> ys)
instance Monoid Args where
mappend = (<>)
mempty = Args mempty
arg :: ArgName -> ArgValue -> Args
arg n v = Args (Map.singleton n v)
data ArgName = ArgName { unArgName :: {-# UNPACK #-} !Text }
deriving (Eq,Show)
instance Hashable ArgName where
hashWithSalt s (ArgName t) = s + hash t
data ArgValue = ArgValue { unArgValue :: {-# UNPACK #-} !Text }
deriving (Eq,Show)
lookupArg :: Text -> Args -> Maybe ArgValue
lookupArg k m = Map.lookup (ArgName k) (unArgs m)
{-# INLINE lookupArg #-}
data ValRef = ValRef { unValRef :: !ValName }
deriving (Eq,Show)
normalize :: Tem -> Tem
normalize Nil = Nil
normalize (Val v rest) = Val v (normalize rest)
normalize (Ref r rest) = Ref r (normalize rest)
normalize (Txt t (Txt s rest)) = normalize (Txt (t `Text.append` s) rest)
normalize (Txt t rest) = Txt t (normalize rest)
parseArgs :: Parser Args
parseArgs = Args . Map.fromList <$> commaSep parseArgNV
{-# INLINE parseArgs #-}
parseArgNV :: Parser (ArgName, ArgValue)
parseArgNV = (,)
<$> (ArgName <$> word )
<*> (ArgValue <$> (char '=' *> word))
word :: Parser Text
word = Text.pack <$> (spaces *> some (letter <|> digit) <* spaces)
{-# INLINE word #-}
textTill' :: MonadPlus f => f Char -> f b -> f Text
textTill' p end = Text.pack <$> manyTill' p end
{-# INLINE textTill' #-}
spaces :: Parser [Char]
spaces = many space
{-# INLINE spaces #-}
{-# ANN module ("HLint: ignore Use String" :: String) #-}
parseMaybe :: Parser a -> Text -> Maybe a
parseMaybe p t =
case parseOnly p t of
Right r -> Just r
_ -> Nothing
parsePlainValue :: Parser PlainValue
parsePlainValue = PlainValue
<$> (ValName <$> (string "{{P|" *> spaces *> word <* sep))
<*> (AlgName <$> (word <* sep))
<*> parseArgs <* sep
<*> (PlainContent <$> textTill' anyChar (string "}}"))
where
sep = char '|'
parseValRef :: Parser ValRef
parseValRef = ValRef . ValName <$>
(string "{{E|" *> spaces *> textTill' (letter <|> digit) (spaces <* string "}}"))
parseTem :: Parser Tem
parseTem = parseNil <|> parseVal <|> parseRef <|> parseTxt
where
parseNil = Nil <$ endOfInput
parseVal = Val <$> parsePlainValue <*> parseTem
parseRef = Ref <$> parseValRef <*> parseTem
parseTxt = Txt <$> textTill' anyChar (lookAhead (void parsePlainValue <|> void parseValRef) <|> endOfInput) <*> parseTem
class Serialize a where
encode :: a -> Text
decode :: Text -> Maybe a
instance Serialize Args where
encode (Args args) = commas (equals <$> Map.toList args)
where
commas :: [Text] -> Text = Text.intercalate ", "
equals :: (ArgName, ArgValue) -> Text = \(ArgName n, ArgValue v) -> n `Text.append` " = " `Text.append` v
decode = parseMaybe parseArgs
instance Serialize Tem where
encode Nil = ""
encode (Txt t template) = t `Text.append` encode template
encode (Val (PlainValue valName algName args content) template) = Text.concat
[ "{{P|"
, unValName valName
, "|"
, unAlgName algName
, "|"
, encode args
, "|"
, unPlainContent content
, "}}"
, encode template
]
encode (Ref (ValRef (ValName vn)) template) = Text.concat
[ "{{E|"
, vn
, "}}"
, encode template
]
decode = parseMaybe parseTem