{-# language BangPatterns #-}
{-# language MagicHash #-}
{-# language UnboxedTuples #-}

module Json.Path
  ( Path(..)
    -- * Encoding
  , encode
  , builderUtf8
    -- * Lookup
  , query
  , query'
    -- * Reverse
  , reverse
  ) where

import Prelude hiding (reverse)

import Json (Value(Object,Array,Null),Member(Member))
import Data.Primitive (ByteArray(ByteArray))
import Data.Text.Short (ShortText)
import Data.Bytes.Builder (Builder)
import Data.ByteString.Short.Internal (ShortByteString(SBS))

import qualified Data.Bytes.Chunks as ByteChunks
import qualified Data.Bytes.Builder as Builder
import qualified Data.Primitive as PM
import qualified Data.Text.Short.Unsafe as TS

-- | A path to an object.
data Path
  = Key {-# UNPACK #-} !ShortText !Path
    -- ^ JSON path element of a key into an object, \"object.key\".
  | Index {-# UNPACK #-} !Int !Path
    -- ^ JSON path element of an index into an array, \"array[index]\".
    -- Negative numbers result in undefined behavior.
  | Nil
  deriving (Path -> Path -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Path -> Path -> Bool
$c/= :: Path -> Path -> Bool
== :: Path -> Path -> Bool
$c== :: Path -> Path -> Bool
Eq,Int -> Path -> ShowS
[Path] -> ShowS
Path -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Path] -> ShowS
$cshowList :: [Path] -> ShowS
show :: Path -> String
$cshow :: Path -> String
showsPrec :: Int -> Path -> ShowS
$cshowsPrec :: Int -> Path -> ShowS
Show)

-- | Encode a path.
--
-- >>> encode (Key "foo" $ Index 5 $ Key "bar" $ Nil)
-- $.foo[5].bar
encode :: Path -> ShortText
encode :: Path -> ShortText
encode Path
p = ByteArray -> ShortText
ba2st (Chunks -> ByteArray
ByteChunks.concatU (Int -> Builder -> Chunks
Builder.run Int
128 (Path -> Builder
builderUtf8 Path
p)))

builderUtf8 :: Path -> Builder
builderUtf8 :: Path -> Builder
builderUtf8 Path
p0 = Char -> Builder
Builder.ascii Char
'$' forall a. Semigroup a => a -> a -> a
<> Path -> Builder
go Path
p0 where
  go :: Path -> Builder
go Path
Nil = forall a. Monoid a => a
mempty
  go (Key ShortText
k Path
p) = Char -> Builder
Builder.ascii Char
'.' forall a. Semigroup a => a -> a -> a
<> ShortText -> Builder
Builder.shortTextUtf8 ShortText
k forall a. Semigroup a => a -> a -> a
<> Path -> Builder
go Path
p
  go (Index Int
i Path
p) =
       Char -> Builder
Builder.ascii Char
'['
    forall a. Semigroup a => a -> a -> a
<> Word -> Builder
Builder.wordDec (forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
i)
    forall a. Semigroup a => a -> a -> a
<> Char -> Builder
Builder.ascii Char
']'
    forall a. Semigroup a => a -> a -> a
<> Path -> Builder
go Path
p

-- | Search for an element at the given path. Returns 'Nothing' if
-- anything in the path is missing.
query :: Path -> Value -> Maybe Value
query :: Path -> Value -> Maybe Value
query = Path -> Value -> Maybe Value
go where
  go :: Path -> Value -> Maybe Value
go Path
Nil Value
v = forall a. a -> Maybe a
Just Value
v
  go (Key ShortText
k Path
p) (Object SmallArray Member
mbrs) = forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr
    (\(Member ShortText
key Value
val) Maybe Value
other -> if ShortText
key forall a. Eq a => a -> a -> Bool
== ShortText
k
      then forall a. a -> Maybe a
Just Value
val
      else Maybe Value
other
    ) forall a. Maybe a
Nothing SmallArray Member
mbrs forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Path -> Value -> Maybe Value
go Path
p
  go (Index Int
i Path
p) (Array SmallArray Value
vs) = if Int
i forall a. Ord a => a -> a -> Bool
< forall a. SmallArray a -> Int
PM.sizeofSmallArray SmallArray Value
vs
    then
      let !(# Value
e #) = forall a. SmallArray a -> Int -> (# a #)
PM.indexSmallArray## SmallArray Value
vs Int
i
       in Path -> Value -> Maybe Value
go Path
p Value
e
    else forall a. Maybe a
Nothing
  go Path
_ Value
_ = forall a. Maybe a
Nothing

-- | Variant of 'query' that returns 'Null' if anything in the path
-- is missing.
query' :: Path -> Value -> Value
query' :: Path -> Value -> Value
query' = Path -> Value -> Value
go where
  go :: Path -> Value -> Value
go Path
Nil Value
v = Value
v
  go (Key ShortText
k Path
p) (Object SmallArray Member
mbrs) = Path -> Value -> Value
go Path
p forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr
    (\(Member ShortText
key Value
val) Value
other -> if ShortText
key forall a. Eq a => a -> a -> Bool
== ShortText
k
      then Value
val
      else Value
other
    ) Value
Null SmallArray Member
mbrs
  go (Index Int
i Path
p) (Array SmallArray Value
vs) = if Int
i forall a. Ord a => a -> a -> Bool
< forall a. SmallArray a -> Int
PM.sizeofSmallArray SmallArray Value
vs
    then
      let !(# Value
e #) = forall a. SmallArray a -> Int -> (# a #)
PM.indexSmallArray## SmallArray Value
vs Int
i
       in Path -> Value -> Value
go Path
p Value
e
    else Value
Null
  go Path
_ Value
_ = Value
Null

ba2st :: ByteArray -> ShortText
ba2st :: ByteArray -> ShortText
ba2st (ByteArray ByteArray#
x) = ShortByteString -> ShortText
TS.fromShortByteStringUnsafe (ByteArray# -> ShortByteString
SBS ByteArray#
x)

reverse :: Path -> Path
reverse :: Path -> Path
reverse = Path -> Path -> Path
go Path
Nil where
  go :: Path -> Path -> Path
go !Path
acc Path
Nil = Path
acc
  go !Path
acc (Key ShortText
k Path
xs) = Path -> Path -> Path
go (ShortText -> Path -> Path
Key ShortText
k Path
acc) Path
xs
  go !Path
acc (Index Int
i Path
xs) = Path -> Path -> Path
go (Int -> Path -> Path
Index Int
i Path
acc) Path
xs