{-# 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