{-# OPTIONS_GHC -fno-warn-orphans #-} {-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE LambdaCase #-} {-| Module : Foreign.Lua.Aeson Copyright : © 2017 Albert Krewinkel License : MIT Maintainer : Albert Krewinkel Stability : experimental Portability : portable Glue to hslua for aeson values. This provides a @StackValue@ instance for aeson's @Value@ type. The following conventions are used: - @Null@ values are encoded as the special global @_NULL@. Using @Nil@ would cause problems with null-containing arrays. - Objects are converted to tables in a straight-forward way. - Arrays are converted to lua tables. Array-length is included as the value at index 0. This makes it possible to distinguish between empty arrays and empty objects. -} module Foreign.Lua.Aeson ( registerNull ) where #if MIN_VERSION_base(4,8,0) #else import Control.Applicative ((<$>), (<*>), (*>), (<*)) #endif import Data.HashMap.Lazy (HashMap) import Data.Hashable (Hashable) import Data.Scientific (Scientific, toRealFloat, fromFloatDigits) import Data.Vector (Vector, fromList, toList) import Foreign.Lua hiding (newstate, toList) import qualified Foreign.Lua as Lua import qualified Data.Aeson as Aeson import qualified Data.HashMap.Lazy as HashMap import qualified Data.Vector as Vector -- Scientific instance ToLuaStack Scientific where push n = pushnumber (toRealFloat n) instance FromLuaStack Scientific where peek n = fromFloatDigits <$> (peek n :: Lua LuaNumber) -- Vector instance (ToLuaStack a) => ToLuaStack (Vector a) where push v = pushvector v instance (FromLuaStack a) => FromLuaStack (Vector a) where peek i = tovector i -- HashMap instance (Eq a, Hashable a, ToLuaStack a, ToLuaStack b) => ToLuaStack (HashMap a b) where push h = pushTextHashMap h instance (Eq a, Hashable a, FromLuaStack a, FromLuaStack b) => FromLuaStack (HashMap a b) where peek i = HashMap.fromList <$> pairsFromTable i -- | Hslua StackValue instance for the Aeson Value data type. instance ToLuaStack Aeson.Value where push = \case Aeson.Object o -> push o Aeson.Number n -> push n Aeson.String s -> push s Aeson.Array a -> push a Aeson.Bool b -> push b Aeson.Null -> getglobal "_NULL" instance FromLuaStack Aeson.Value where peek i = do ltype' <- ltype i case ltype' of TypeBoolean -> Aeson.Bool <$> peek i TypeNumber -> Aeson.Number <$> peek i TypeString -> Aeson.String <$> peek i TypeTable -> do rawgeti i 0 isInt <- isnumber (-1) pop 1 if isInt then Aeson.Array <$> peek i else do rawlen' <- rawlen i if rawlen' > 0 then Aeson.Array <$> peek i else do isNull <- isLuaNull i if isNull then return Aeson.Null else Aeson.Object <$> peek i TypeNil -> return Aeson.Null _ -> error $ "Unexpected type: " ++ (show ltype') -- | Create a new lua state suitable for use with aeson values. This behaves -- like @newstate@ in hslua, but initializes the @_NULL@ global. That variable -- is used to encode null values. registerNull :: Lua () registerNull = do createtable 0 0 setglobal "_NULL" -- | Check if the value under the given index is rawequal to @_NULL@. isLuaNull :: StackIndex -> Lua Bool isLuaNull i = do let i' = if i < 0 then i - 1 else i getglobal "_NULL" rawequal i' (-1) <* pop 1 -- | Push a vector unto the stack. pushvector :: ToLuaStack a => Vector a -> Lua () pushvector v = do pushList . toList $ v push (fromIntegral (Vector.length v) :: LuaInteger) rawseti (-2) 0 -- | Try reading the value under the given index as a vector. tovector :: FromLuaStack a => StackIndex -> Lua (Vector a) tovector = fmap fromList . Lua.toList -- | Push a hashmap unto the stack. pushTextHashMap :: (ToLuaStack a, ToLuaStack b) => HashMap a b -> Lua () pushTextHashMap hm = do let xs = HashMap.toList hm let addValue (k, v) = push k *> push v *> rawset (-3) newtable mapM_ addValue xs