{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE Rank2Types #-}
#if __GLASGOW_HASKELL__ >= 800
{-# LANGUAGE TemplateHaskellQuotes #-}
#else
{-# LANGUAGE TemplateHaskell #-}
#endif
module Data.Aeson.Types.Internal
    (
    
      Value(..)
    , Array
    , emptyArray, isEmptyArray
    , Pair
    , Object
    , emptyObject
    
    , Parser
    , Result(..)
    , IResult(..)
    , JSONPathElement(..)
    , JSONPath
    , iparse
    , parse
    , parseEither
    , parseMaybe
    , modifyFailure
    , parserThrowError
    , parserCatchError
    , formatError
    , (<?>)
    
    , object
    
    , Options(
          fieldLabelModifier
        , constructorTagModifier
        , allNullaryToStringTag
        , omitNothingFields
        , sumEncoding
        , unwrapUnaryRecords
        , tagSingleConstructors
        )
    , SumEncoding(..)
    , defaultOptions
    , defaultTaggedObject
    
    , camelTo
    , camelTo2
    
    , DotNetTime(..)
    ) where
import Prelude ()
import Prelude.Compat
import Control.Applicative (Alternative(..))
import Control.Arrow (first)
import Control.DeepSeq (NFData(..))
import Control.Monad (MonadPlus(..), ap)
import Data.Char (isLower, isUpper, toLower, isAlpha, isAlphaNum)
import Data.Data (Data)
import Data.Foldable (foldl')
import Data.HashMap.Strict (HashMap)
import Data.Hashable (Hashable(..))
import Data.List (intercalate)
import Data.Scientific (Scientific)
import Data.Semigroup (Semigroup((<>)))
import Data.String (IsString(..))
import Data.Text (Text, pack, unpack)
import Data.Time (UTCTime)
import Data.Time.Format (FormatTime)
import Data.Typeable (Typeable)
import Data.Vector (Vector)
import GHC.Generics (Generic)
import qualified Control.Monad.Fail as Fail
import qualified Data.HashMap.Strict as H
import qualified Data.Scientific as S
import qualified Data.Vector as V
import qualified Language.Haskell.TH.Syntax as TH
#if !MIN_VERSION_unordered_containers(0,2,6)
import Data.List (sort)
#endif
data JSONPathElement = Key Text
                       
                       
                     | Index {-# UNPACK #-} !Int
                       
                       
                       deriving (Eq, Show, Typeable, Ord)
type JSONPath = [JSONPathElement]
data IResult a = IError JSONPath String
               | ISuccess a
               deriving (Eq, Show, Typeable)
data Result a = Error String
              | Success a
                deriving (Eq, Show, Typeable)
instance NFData JSONPathElement where
  rnf (Key t)   = rnf t
  rnf (Index i) = rnf i
instance (NFData a) => NFData (IResult a) where
    rnf (ISuccess a)      = rnf a
    rnf (IError path err) = rnf path `seq` rnf err
instance (NFData a) => NFData (Result a) where
    rnf (Success a) = rnf a
    rnf (Error err) = rnf err
instance Functor IResult where
    fmap f (ISuccess a)      = ISuccess (f a)
    fmap _ (IError path err) = IError path err
    {-# INLINE fmap #-}
instance Functor Result where
    fmap f (Success a) = Success (f a)
    fmap _ (Error err) = Error err
    {-# INLINE fmap #-}
instance Monad IResult where
    return = pure
    {-# INLINE return #-}
    ISuccess a      >>= k = k a
    IError path err >>= _ = IError path err
    {-# INLINE (>>=) #-}
    fail = Fail.fail
    {-# INLINE fail #-}
instance Fail.MonadFail IResult where
    fail err = IError [] err
    {-# INLINE fail #-}
instance Monad Result where
    return = pure
    {-# INLINE return #-}
    Success a >>= k = k a
    Error err >>= _ = Error err
    {-# INLINE (>>=) #-}
    fail = Fail.fail
    {-# INLINE fail #-}
instance Fail.MonadFail Result where
    fail err = Error err
    {-# INLINE fail #-}
instance Applicative IResult where
    pure  = ISuccess
    {-# INLINE pure #-}
    (<*>) = ap
    {-# INLINE (<*>) #-}
instance Applicative Result where
    pure  = Success
    {-# INLINE pure #-}
    (<*>) = ap
    {-# INLINE (<*>) #-}
instance MonadPlus IResult where
    mzero = fail "mzero"
    {-# INLINE mzero #-}
    mplus a@(ISuccess _) _ = a
    mplus _ b             = b
    {-# INLINE mplus #-}
instance MonadPlus Result where
    mzero = fail "mzero"
    {-# INLINE mzero #-}
    mplus a@(Success _) _ = a
    mplus _ b             = b
    {-# INLINE mplus #-}
instance Alternative IResult where
    empty = mzero
    {-# INLINE empty #-}
    (<|>) = mplus
    {-# INLINE (<|>) #-}
instance Alternative Result where
    empty = mzero
    {-# INLINE empty #-}
    (<|>) = mplus
    {-# INLINE (<|>) #-}
instance Semigroup (IResult a) where
    (<>) = mplus
    {-# INLINE (<>) #-}
instance Monoid (IResult a) where
    mempty  = fail "mempty"
    {-# INLINE mempty #-}
    mappend = (<>)
    {-# INLINE mappend #-}
instance Semigroup (Result a) where
    (<>) = mplus
    {-# INLINE (<>) #-}
instance Monoid (Result a) where
    mempty  = fail "mempty"
    {-# INLINE mempty #-}
    mappend = (<>)
    {-# INLINE mappend #-}
instance Foldable IResult where
    foldMap _ (IError _ _) = mempty
    foldMap f (ISuccess y) = f y
    {-# INLINE foldMap #-}
    foldr _ z (IError _ _) = z
    foldr f z (ISuccess y) = f y z
    {-# INLINE foldr #-}
instance Foldable Result where
    foldMap _ (Error _)   = mempty
    foldMap f (Success y) = f y
    {-# INLINE foldMap #-}
    foldr _ z (Error _)   = z
    foldr f z (Success y) = f y z
    {-# INLINE foldr #-}
instance Traversable IResult where
    traverse _ (IError path err) = pure (IError path err)
    traverse f (ISuccess a)      = ISuccess <$> f a
    {-# INLINE traverse #-}
instance Traversable Result where
    traverse _ (Error err) = pure (Error err)
    traverse f (Success a) = Success <$> f a
    {-# INLINE traverse #-}
type Failure f r   = JSONPath -> String -> f r
type Success a f r = a -> f r
newtype Parser a = Parser {
      runParser :: forall f r.
                   JSONPath
                -> Failure f r
                -> Success a f r
                -> f r
    }
instance Monad Parser where
    m >>= g = Parser $ \path kf ks -> let ks' a = runParser (g a) path kf ks
                                       in runParser m path kf ks'
    {-# INLINE (>>=) #-}
    return = pure
    {-# INLINE return #-}
    fail = Fail.fail
    {-# INLINE fail #-}
instance Fail.MonadFail Parser where
    fail msg = Parser $ \path kf _ks -> kf (reverse path) msg
    {-# INLINE fail #-}
instance Functor Parser where
    fmap f m = Parser $ \path kf ks -> let ks' a = ks (f a)
                                        in runParser m path kf ks'
    {-# INLINE fmap #-}
instance Applicative Parser where
    pure a = Parser $ \_path _kf ks -> ks a
    {-# INLINE pure #-}
    (<*>) = apP
    {-# INLINE (<*>) #-}
instance Alternative Parser where
    empty = fail "empty"
    {-# INLINE empty #-}
    (<|>) = mplus
    {-# INLINE (<|>) #-}
instance MonadPlus Parser where
    mzero = fail "mzero"
    {-# INLINE mzero #-}
    mplus a b = Parser $ \path kf ks -> let kf' _ _ = runParser b path kf ks
                                         in runParser a path kf' ks
    {-# INLINE mplus #-}
instance Semigroup (Parser a) where
    (<>) = mplus
    {-# INLINE (<>) #-}
instance Monoid (Parser a) where
    mempty  = fail "mempty"
    {-# INLINE mempty #-}
    mappend = (<>)
    {-# INLINE mappend #-}
apP :: Parser (a -> b) -> Parser a -> Parser b
apP d e = do
  b <- d
  a <- e
  return (b a)
{-# INLINE apP #-}
type Object = HashMap Text Value
type Array = Vector Value
data Value = Object !Object
           | Array !Array
           | String !Text
           | Number !Scientific
           | Bool !Bool
           | Null
             deriving (Eq, Read, Show, Typeable, Data, Generic)
newtype DotNetTime = DotNetTime {
      fromDotNetTime :: UTCTime
      
    } deriving (Eq, Ord, Read, Show, Typeable, FormatTime)
instance NFData Value where
    rnf (Object o) = rnf o
    rnf (Array a)  = foldl' (\x y -> rnf y `seq` x) () a
    rnf (String s) = rnf s
    rnf (Number n) = rnf n
    rnf (Bool b)   = rnf b
    rnf Null       = ()
instance IsString Value where
    fromString = String . pack
    {-# INLINE fromString #-}
hashValue :: Int -> Value -> Int
#if MIN_VERSION_unordered_containers(0,2,6)
hashValue s (Object o)   = s `hashWithSalt` (0::Int) `hashWithSalt` o
#else
hashValue s (Object o)   = foldl' hashWithSalt
                              (s `hashWithSalt` (0::Int)) assocHashesSorted
  where
    assocHashesSorted = sort [hash k `hashWithSalt` v | (k, v) <- H.toList o]
#endif
hashValue s (Array a)    = foldl' hashWithSalt
                              (s `hashWithSalt` (1::Int)) a
hashValue s (String str) = s `hashWithSalt` (2::Int) `hashWithSalt` str
hashValue s (Number n)   = s `hashWithSalt` (3::Int) `hashWithSalt` n
hashValue s (Bool b)     = s `hashWithSalt` (4::Int) `hashWithSalt` b
hashValue s Null         = s `hashWithSalt` (5::Int)
instance Hashable Value where
    hashWithSalt = hashValue
instance TH.Lift Value where
    lift Null = [| Null |]
    lift (Bool b) = [| Bool b |]
    lift (Number n) = [| Number (S.scientific c e) |]
      where
        c = S.coefficient n
        e = S.base10Exponent n
    lift (String t) = [| String (pack s) |]
      where s = unpack t
    lift (Array a) = [| Array (V.fromList a') |]
      where a' = V.toList a
    lift (Object o) = [| Object (H.fromList . map (first pack) $ o') |]
      where o' = map (first unpack) . H.toList $ o
emptyArray :: Value
emptyArray = Array V.empty
isEmptyArray :: Value -> Bool
isEmptyArray (Array arr) = V.null arr
isEmptyArray _ = False
emptyObject :: Value
emptyObject = Object H.empty
parse :: (a -> Parser b) -> a -> Result b
parse m v = runParser (m v) [] (const Error) Success
{-# INLINE parse #-}
iparse :: (a -> Parser b) -> a -> IResult b
iparse m v = runParser (m v) [] IError ISuccess
{-# INLINE iparse #-}
parseMaybe :: (a -> Parser b) -> a -> Maybe b
parseMaybe m v = runParser (m v) [] (\_ _ -> Nothing) Just
{-# INLINE parseMaybe #-}
parseEither :: (a -> Parser b) -> a -> Either String b
parseEither m v = runParser (m v) [] onError Right
  where onError path msg = Left (formatError path msg)
{-# INLINE parseEither #-}
formatError :: JSONPath -> String -> String
formatError path msg = "Error in " ++ format "$" path ++ ": " ++ msg
  where
    format :: String -> JSONPath -> String
    format pfx []                = pfx
    format pfx (Index idx:parts) = format (pfx ++ "[" ++ show idx ++ "]") parts
    format pfx (Key key:parts)   = format (pfx ++ formatKey key) parts
    formatKey :: Text -> String
    formatKey key
       | isIdentifierKey strKey = "." ++ strKey
       | otherwise              = "['" ++ escapeKey strKey ++ "']"
      where strKey = unpack key
    isIdentifierKey :: String -> Bool
    isIdentifierKey []     = False
    isIdentifierKey (x:xs) = isAlpha x && all isAlphaNum xs
    escapeKey :: String -> String
    escapeKey = concatMap escapeChar
    escapeChar :: Char -> String
    escapeChar '\'' = "\\'"
    escapeChar '\\' = "\\\\"
    escapeChar c    = [c]
type Pair = (Text, Value)
object :: [Pair] -> Value
object = Object . H.fromList
{-# INLINE object #-}
(<?>) :: Parser a -> JSONPathElement -> Parser a
p <?> pathElem = Parser $ \path kf ks -> runParser p (pathElem:path) kf ks
modifyFailure :: (String -> String) -> Parser a -> Parser a
modifyFailure f (Parser p) = Parser $ \path kf ks ->
    p path (\p' m -> kf p' (f m)) ks
parserThrowError :: JSONPath -> String -> Parser a
parserThrowError path' msg = Parser $ \path kf _ks ->
    kf (reverse path ++ path') msg
parserCatchError :: Parser a -> (JSONPath -> String -> Parser a) -> Parser a
parserCatchError (Parser p) handler = Parser $ \path kf ks ->
    p path (\e msg -> runParser (handler e msg) path kf ks) ks
data Options = Options
    { fieldLabelModifier :: String -> String
      
      
    , constructorTagModifier :: String -> String
      
      
    , allNullaryToStringTag :: Bool
      
      
      
      
    , omitNothingFields :: Bool
      
      
      
    , sumEncoding :: SumEncoding
      
    , unwrapUnaryRecords :: Bool
      
      
    , tagSingleConstructors :: Bool
      
      
    }
instance Show Options where
  show (Options f c a o s u t) =
       "Options {"
    ++ intercalate ", "
      [ "fieldLabelModifier =~ " ++ show (f "exampleField")
      , "constructorTagModifier =~ " ++ show (c "ExampleConstructor")
      , "allNullaryToStringTag = " ++ show a
      , "omitNothingFields = " ++ show o
      , "sumEncoding = " ++ show s
      , "unwrapUnaryRecords = " ++ show u
      , "tagSingleConstructors = " ++ show t
      ]
    ++ "}"
data SumEncoding =
    TaggedObject { tagFieldName      :: String
                 , contentsFieldName :: String
                 }
    
    
    
    
    
    
    
    
    
  | UntaggedValue
    
    
    
    
    
    
    
    
    
    
    
    
    
  | ObjectWithSingleField
    
    
    
    
  | TwoElemArray
    
    
    
    
    deriving (Eq, Show)
defaultOptions :: Options
defaultOptions = Options
                 { fieldLabelModifier      = id
                 , constructorTagModifier  = id
                 , allNullaryToStringTag   = True
                 , omitNothingFields       = False
                 , sumEncoding             = defaultTaggedObject
                 , unwrapUnaryRecords      = False
                 , tagSingleConstructors   = False
                 }
defaultTaggedObject :: SumEncoding
defaultTaggedObject = TaggedObject
                      { tagFieldName      = "tag"
                      , contentsFieldName = "contents"
                      }
camelTo :: Char -> String -> String
{-# DEPRECATED camelTo "Use camelTo2 for better results" #-}
camelTo c = lastWasCap True
  where
    lastWasCap :: Bool    
              -> String  
              -> String
    lastWasCap _    []           = []
    lastWasCap prev (x : xs)     = if isUpper x
                                      then if prev
                                             then toLower x : lastWasCap True xs
                                             else c : toLower x : lastWasCap True xs
                                      else x : lastWasCap False xs
camelTo2 :: Char -> String -> String
camelTo2 c = map toLower . go2 . go1
    where go1 "" = ""
          go1 (x:u:l:xs) | isUpper u && isLower l = x : c : u : l : go1 xs
          go1 (x:xs) = x : go1 xs
          go2 "" = ""
          go2 (l:u:xs) | isLower l && isUpper u = l : c : u : go2 xs
          go2 (x:xs) = x : go2 xs