dead-simple-json-0.1.2: src/Text/DeadSimpleJSON/TH.hs
{-# LANGUAGE Haskell2010,
TemplateHaskell #-}
{-# OPTIONS -Wall -O2 -fno-warn-name-shadowing #-}
-- | Template Haskell syntax sugar for working with 'JSON' data.
--
-- For using this module, you need to declare a LANGUAGE
-- pragma like the following:
--
-- > {-# LANGUAGE Haskell2010, TemplateHaskell, QuasiQuotes #-}
module Text.DeadSimpleJSON.TH (
-- * Query JSON objects
jsq,
-- * Create JSON objects
json,
jsonF,
-- * Include strings
s,
sF
) where
import Prelude hiding (True, False)
import qualified Prelude
import Data.Char
import qualified Data.Map as M
import qualified Data.Vector as V
import Text.DeadSimpleJSON (parse)
import Text.DeadSimpleJSON.Convert (Convert (..))
import Text.DeadSimpleJSON.Query
import Text.DeadSimpleJSON.Types
import Language.Haskell.TH
import Language.Haskell.TH.Quote
s :: QuasiQuoter
-- ^ A QuasiQuoter on raw strings.
--
-- The definition is basically:
--
-- > s = QuasiQuoter {
-- > quoteExp = return . LitE . StringL
-- > }
s = QuasiQuoter {
quoteExp = return . LitE . StringL,
quotePat = \_ -> fail "illegal string QuasiQuote (allowed as expression only, used as a pattern)",
quoteType = \_ -> fail "illegal string QuasiQuote (allowed as expression only, used as a type)",
quoteDec = \_ -> fail "illegal string QuasiQuote (allowed as expression only, used as a dec)"
}
sF :: QuasiQuoter
-- ^ A QuasiQuoter which includes raw strings from files.
--
-- The following example will include the contents of @file.txt@
-- as a @String@.
--
-- > let str = [sF|file.txt|]
--
-- Note that every character inside the brackets is treated
-- as part of the file name, that is @[sF| file.txt |]@ is not
-- the same as the above example (it will try to find a file which
-- name includes space characters).
sF = quoteFile s
jsonF :: QuasiQuoter
-- ^ A QuasiQuoter which includes JSON data from files.
--
-- The following example will include the contents of @data.json@
-- as 'JSON'.
--
-- > let str = [jsonF|data.json|]
--
-- Note that every character inside the brackets is treated
-- as part of the file name, that is @[jsonF| data.json |]@ is not
-- the same as the above example (it will try to find a file which
-- name includes space characters).
jsonF = quoteFile json
json :: QuasiQuoter
-- ^ A QuasiQuoter which includes JSON data.
--
-- The type of the expression is 'JSON'.
json = QuasiQuoter {
quoteExp = jsonQuoter,
quotePat = \_ -> fail "illegal json QuasiQuote (allowed as expression only, used as a pattern)",
quoteType = \_ -> fail "illegal json QuasiQuote (allowed as expression only, used as a type)",
quoteDec = \_ -> fail "illegal json QuasiQuote (allowed as expression only, used as a dec)"
}
jsonQuoter :: String -> Q Exp
jsonQuoter = either (fail . show) buildJSON . parse
where
buildJSON (JSON json) = do
json' <- buildJSON' json
return (AppE (ConE 'JSON) json')
buildJSON' (String s) = return $ AppE (ConE 'String) (LitE (StringL s))
buildJSON' (Number n e) = return $ AppE (AppE (ConE 'Number) (LitE (IntegerL n))) (LitE (IntegerL e))
buildJSON' (Object obj) = do
m <- mapM (\(k, v) -> do { x <- buildJSON' v; return $ TupE [LitE (StringL k), x] }) (M.toList obj)
return $ AppE (ConE 'Object) (AppE (VarE 'M.fromList) (ListE m))
buildJSON' (Array arr) = do
v <- mapM buildJSON' (V.toList arr)
return $ AppE (ConE 'Array) (AppE (VarE 'V.fromList) (ListE v))
buildJSON' True = return $ ConE 'True
buildJSON' False = return $ ConE 'False
buildJSON' Null = return $ ConE 'Null
jsq :: QuasiQuoter
-- ^ A QuasiQuoter which queries a json object using JavaScript notation.
--
-- Suppose obj contains a json object of type JSON:
--
-- > [jsq| obj.prop.list[3] |]
--
-- The above will query the object in obj as if it was JavaScript.
--
-- The type of the expression is polymorphic: @Convert a => a@.
--
-- You will need to specify the type of the query, like so:
--
-- > [jsq| obj.prop.list |] :: [Integer]
--
-- For possible conversions, see the instances for 'Convert'.
jsq = QuasiQuoter {
quoteExp = jsqQuoter,
quotePat = \_ -> fail "illegal jsq QuasiQuote (allowed as expression only, used as a pattern)",
quoteType = \_ -> fail "illegal jsq QuasiQuote (allowed as expression only, used as a type)",
quoteDec = \_ -> fail "illegal jsq QuasiQuote (allowed as expression only, used as a dec)"
}
jsqQuoter :: String -> Q Exp
jsqQuoter = either (fail . show) buildQuery . mkQuery' . filter (not . isSpace)
where
buildQuery (Field s e) = do
q <- buildQuery' e
return $ AppE ((AppE (VarE 'query)) q) (VarE (mkName s))
buildQuery _ = do
report Prelude.False "Warning: Empty JSON Query"
[| convert $ Object M.empty |]
buildQuery' (Field s e) = do
exp <- buildQuery' e
return $ AppE (AppE (ConE 'Field) (LitE (StringL s))) exp
buildQuery' (Index i e) = do
exp <- buildQuery' e
return $ AppE (AppE (ConE 'Index) (LitE (IntegerL (fromIntegral i)))) exp
buildQuery' (Read) = return $ ConE 'Read