ron-hs-0.4.0: src/Data/Ron/Value.hs
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE CPP #-}
#if __GLASGOW_HASKELL__ >= 800
-- a) THQ works on cross-compilers and unregisterised GHCs
-- b) may make compilation faster as no dynamic loading is ever needed (not sure about this)
-- c) removes one hindrance to have code inferred as SafeHaskell safe
{-# LANGUAGE TemplateHaskellQuotes #-}
#else
{-# LANGUAGE TemplateHaskell #-}
#endif
module Data.Ron.Value
( Value (..)
) where
import Control.Applicative (liftA2)
import Control.DeepSeq (NFData (rnf))
import Data.Hashable (Hashable, hashWithSalt)
import Data.Foldable (foldl')
import Data.Map.Strict (Map)
import Data.Text (Text)
import Data.Scientific (Scientific, fromFloatDigits)
import Data.Vector (Vector)
import Test.QuickCheck (sized, Arbitrary, arbitrary, Gen, oneof, shuffle, choose, elements, listOf)
import qualified Data.Map.Strict as Map
import qualified Data.Text as Text
import qualified Data.Vector as Vector
import qualified Language.Haskell.TH.Syntax as TH
-- Alternatives:
-- Integer -> Int, Int64
-- Scientific -> Double
-- Map -> HashMap, List of tuples, Vector of tuples (especially for record)
-- Vector -> List (for tuples)
-- NonEmpty for tuple and record? Or unify unit with both of them
--
-- | A RON value represented in haskell. This is the intermediate
-- representation between string and your own type.
--
-- @()@ is represented as @Unit ""@. Anonymous records and tuples are
-- similarily represented by having an empty name.
--
-- 'Tuple' and 'Record' contents are guaranteed to be not empty when parsed,
-- since the empty contents denote a 'Unit'. Attempting to dump an empty tuple
-- or record may produce unexpected results, so don't do that, use 'Unit's instead.
data Value
= Integral !Integer
| Floating !Scientific
| Char !Char
| String !Text
| List !(Vector Value)
| Map !(Map Value Value)
| Unit !Text
| Tuple !Text !(Vector Value)
| Record !Text !(Map Text Value)
deriving (Eq, Show, Ord)
--- Hashable ---
hashValue :: Int -> Value -> Int
hashValue s (Integral x) = s `hashWithSalt` (0::Int) `hashWithSalt` x
hashValue s (Floating x) = s `hashWithSalt` (1::Int) `hashWithSalt` x
hashValue s (Char x) = s `hashWithSalt` (2::Int) `hashWithSalt` x
hashValue s (String x) = s `hashWithSalt` (3::Int) `hashWithSalt` x
hashValue s (List xs) = foldl' hashWithSalt
(s `hashWithSalt` (4::Int))
xs
hashValue s (Map xs) = Map.foldlWithKey'
(\acc k v -> acc `hashWithSalt` k `hashWithSalt` v)
(s `hashWithSalt` (5::Int))
xs
hashValue s (Unit n) = s `hashWithSalt` (6::Int) `hashWithSalt` n
hashValue s (Tuple n xs) = foldl' hashWithSalt
(s `hashWithSalt` (7::Int) `hashWithSalt` n)
xs
hashValue s (Record n xs) = Map.foldlWithKey'
(\acc k v -> acc `hashWithSalt` k `hashWithSalt` v)
(s `hashWithSalt` (8::Int) `hashWithSalt` n)
xs
instance Hashable Value where
hashWithSalt = hashValue
--- Template Haskell ---
instance TH.Lift Value where
lift (Integral x) = [| Integral x |]
lift (Floating x) = [| Floating x |]
lift (Char x) = [| Char x |]
lift (String x) = [| String (Text.pack s) |]
where s = Text.unpack x
lift (List x) = [| List (Vector.fromList a) |]
where a = Vector.toList x
lift (Map x) = [| Map (Map.fromAscList m) |]
where m = Map.toAscList x
lift (Unit x) = [| Unit (Text.pack s) |]
where s = Text.unpack x
lift (Tuple n x) = [| Tuple (Text.pack s) (Vector.fromList a)|]
where s = Text.unpack n
a = Vector.toList x
lift (Record n x) = [| Record (Text.pack s) (Map.fromAscList m) |]
where s = Text.unpack n
m = Map.toAscList x
#if MIN_VERSION_template_haskell(2,17,0)
liftTyped = TH.unsafeCodeCoerce . TH.lift
#elif MIN_VERSION_template_haskell(2,16,0)
liftTyped = TH.unsafeTExpCoerce . TH.lift
#endif
--- QuickCheck
instance Arbitrary Value where
arbitrary = sized arbValue
-- what the hell is 'shrink'?
arbValue :: Int -> Gen Value
arbValue = arbValue' True
arbValue' :: Bool -> Int -> Gen Value
arbValue' allowFloat size
| size <= 0 = oneof $
[ Integral <$> arbitrary
, Char <$> arbitrary
, String <$> arbText
, Unit <$> arbIdentifier
] <> if allowFloat then [Floating . fromFloatDigits <$> arbitrary @Double] else []
| otherwise = oneof
[ List <$> arbList size
, Map <$> arbMap size
, Tuple "" <$> arbTuple size
, Record "" <$> arbRecord size
, liftA2 Tuple arbIdentifier (arbTuple size)
, liftA2 Record arbIdentifier (arbRecord size)
]
arbText :: Gen Text
arbText = Text.pack <$> arbitrary
arbIdentifier :: Gen Text
arbIdentifier = do
h <- arbStartIdentifier
t <- listOf arbKeyword
pure . Text.pack $ h:t
where
starts = ['a'..'z'] <> ['A'..'Z'] <> ['_']
keyword = starts <> ['0'..'9'] <> ['\'']
arbStartIdentifier = elements starts
arbKeyword = elements keyword
arbList :: Int -> Gen (Vector Value)
arbList size = do
sizes <- arbPartition (size - 1)
Vector.fromList <$> traverse arbValue sizes
arbMap :: Int -> Gen (Map Value Value)
arbMap size = do
sizes <- arbPartition (size - 1)
let makeElem s = liftA2 (,) (arbValue' False 0) (arbValue s)
Map.fromList <$> traverse makeElem sizes
arbTuple :: Int -> Gen (Vector Value)
arbTuple size = do
sizes <- arbPartition (size - 1)
firstSize <- if size <= 1 then pure 0 else choose (1, size-1)
first <- arbValue firstSize
Vector.fromList . (first:) <$> traverse arbValue sizes
arbRecord :: Int -> Gen (Map Text Value)
arbRecord size = do
sizes <- arbPartition (size - 1)
let makeElem s = liftA2 (,) arbIdentifier (arbValue s)
firstSize <- if size <= 1 then pure 0 else choose (1, size-1)
first <- makeElem firstSize
Map.fromList . (first:) <$> traverse makeElem sizes
-- | Produces new sizes for recursion
arbPartition :: Int -> Gen [Int]
arbPartition size = case compare size 1 of
LT -> pure []
EQ -> pure [1]
GT -> shuffle =<< go size []
where
go 0 xs = pure xs
go !s xs = do
x <- choose (1, s)
go (s - 1) (x:xs)
--- Deepseq
instance NFData Value where
rnf (Integral x) = rnf x
rnf (Floating x) = rnf x
rnf (Char x) = rnf x
rnf (String x) = rnf x
rnf (List x) = rnf x
rnf (Map x) = rnf x
rnf (Unit n) = rnf n
rnf (Tuple n x) = rnf n `seq` rnf x
rnf (Record n x) = rnf n `seq` rnf x