module TH.API.Input
( generateInput ) where
import Data.Aeson ((.:), (.:?), (.=), FromJSON, parseJSON, ToJSON, toJSON)
import qualified Data.Aeson as Aeson (object, Value(..))
import Data.Char
import Data.Data
import qualified Data.Map as Map (fromList)
import Data.Maybe (catMaybes)
import Data.Time.Calendar (Day)
import Control.Monad
import Control.Applicative((<*>), (<|>), empty, pure)
import Language.Haskell.TH
import System.FilePath (takeBaseName)
import Helper.Name
import Data.OpenDataTable
import Data.TH.API
import Data.TH.Convert
import Data.TH.Object
import Language.JavaScript.Interpret (Primitive)
generateInput :: FilePath -> OpenDataTable -> Q APIInput
generateInput xml opentable = do
let base = camelCase . takeBaseName $ xml
name = "Input" ++ base
tName = mkName name
selects = [s | (SelectBinding s) <- openDataTableBindings opentable]
cons <- forM selects $ generateConstructor name
let dec = DataD [] tName [] cons [''Data, ''Eq, ''Read, ''Show, ''Typeable]
ins <- generateInstances dec opentable
return $ APIInput tName (ConT tName) (xml, opentable) (dec:ins)
generateConstructor :: String -> Select -> Q Con
generateConstructor name select = do
let infos = [info | (InputKey info) <- selectInputs select]
fields <- forM infos $ \info -> do
let fieldBase = (toLower . head $ name):(tail name)
fieldHsName = toName $ inputInfoId info
fieldTail = (toUpper . head $ fieldHsName):(tail fieldHsName)
fieldName = fieldBase ++ fieldTail
fieldStrict = NotStrict
fieldType = haskellType info
return (mkName fieldName, fieldStrict, fieldType)
return $ RecC (mkName name) fields
where
toName = map replace
replace '-' = '_'
replace a = a
generateInstances :: Dec -> OpenDataTable -> Q [Dec]
generateInstances dec opentable = do
toJSONI <- generateToJSONInstance dec opentable
fromJSONI <- generateFromJSONInstance dec opentable
objectI <- generateObjectInstance dec opentable
return [toJSONI, fromJSONI, objectI]
generateFromJSONInstance :: Dec -> OpenDataTable -> Q Dec
generateFromJSONInstance dec opentable = do
let DataD _ name _ _ _ = dec
insName = ''FromJSON
insType = ConT insName
varType = ConT name
decType = AppT insType varType
ctx = []
parseJSOND <- generateParseJSONFunction dec opentable
let decs = [parseJSOND]
return $ InstanceD ctx decType decs
generateParseJSONFunction :: Dec -> OpenDataTable -> Q Dec
generateParseJSONFunction dec opentable = do
let DataD _ _ _ cons _ = dec
let selects = [s | (SelectBinding s) <- openDataTableBindings opentable]
let v = mkName "v"
alt <- [| (<|>) |]
alts <- mapM (uncurry $ generateParseJSONAlternative v) $ zip cons selects
let parse = foldr (\a b -> AppE (AppE alt a) b) (VarE 'empty) alts
parseC = Clause [ConP 'Aeson.Object [VarP v]] (NormalB parse) []
let failC = Clause [WildP] (NormalB (VarE 'mzero)) []
return $ FunD 'parseJSON [parseC, failC]
generateParseJSONAlternative :: Name -> Con -> Select -> Q Exp
generateParseJSONAlternative v (RecC name _) select = do
let infos = [i | (InputKey i) <- selectInputs select]
construct <- [| (.:) |]
mconstruct <- [| (.:?) |]
star <- [| (<*>) |]
let con = AppE (VarE 'pure) (ConE name)
var = VarE v
stars = star:stars
ops = stars
app a (InputInfo {..}, op) =
AppE (AppE op a) (AppE (AppE (if inputInfoRequired then construct else mconstruct) var) (LitE $ StringL inputInfoId))
body = foldl app con $ zip infos ops
return body
generateParseJSONAlternative _ _ _ = error "Invalid constructor"
generateToJSONInstance :: Dec -> OpenDataTable -> Q Dec
generateToJSONInstance dec opentable = do
let DataD _ name _ _ _ = dec
insName = ''ToJSON
insType = ConT insName
varType = ConT name
decType = AppT insType varType
ctx = []
toJSOND <- generateToJSONFunction dec opentable
let decs = [toJSOND]
return $ InstanceD ctx decType decs
generateToJSONFunction :: Dec -> OpenDataTable -> Q Dec
generateToJSONFunction dec opentable = do
let DataD _ _ _ cons _ = dec
let selects = [s | (SelectBinding s) <- openDataTableBindings opentable]
clauses <- mapM (uncurry generateToJSONClause) $ zip cons selects
return $ FunD 'toJSON clauses
generateToJSONClause :: Con -> Select -> Q Clause
generateToJSONClause con select = do
let infos = [i | (InputKey i) <- selectInputs select]
RecC conName fields = con
pair <- [| (.=) |]
let arg = mkName "x"
var = VarE arg
pat = AsP arg (RecP conName [])
list = map
(\((fieldName, _, _), info) -> do
let isRequired = inputInfoRequired info
if isRequired
then
AppE
(ConE 'Just)
(AppE
(AppE pair (LitE . StringL $ inputInfoId info))
(AppE (VarE fieldName) var))
else
AppE
(AppE
(VarE 'fmap)
(AppE pair (LitE . StringL $ inputInfoId info)))
(AppE (VarE fieldName) var))
$ zip fields infos
return $ Clause [pat]
(NormalB $ AppE
(VarE 'Aeson.object)
(AppE
(VarE 'catMaybes)
$ ListE list)) []
generateObjectInstance :: Dec -> OpenDataTable -> Q Dec
generateObjectInstance dec opentable = do
let DataD _ name _ _ _ = dec
insName = ''Object
insType = ConT insName
varType = ConT name
decType = AppT (AppT (AppT insType varType) (ConT ''String)) (ConT ''Primitive)
ctx = []
toObjectD <- generateToObjectFunction dec opentable
let decs = [toObjectD]
return $ InstanceD ctx decType decs
generateToObjectFunction :: Dec -> OpenDataTable -> Q Dec
generateToObjectFunction dec opentable = do
let DataD _ _ _ cons _ = dec
let selects = [s | (SelectBinding s) <- openDataTableBindings opentable]
clauses <- mapM (uncurry generateToObjectClause) $ zip cons selects
return $ FunD 'toObject clauses
generateToObjectClause :: Con -> Select -> Q Clause
generateToObjectClause con select = do
let infos = [i | (InputKey i) <- selectInputs select]
RecC conName fields = con
pair <- [| \k v -> (k, convert v) |]
let arg = mkName "x"
var = VarE arg
pat = AsP arg (RecP conName [])
list = map
(\((fieldName, _, _), info) -> do
let isRequired = inputInfoRequired info
if isRequired
then
AppE
(ConE 'Just)
(AppE
(AppE pair (LitE . StringL $ inputInfoId info))
(AppE (VarE fieldName) var))
else
AppE
(AppE
(VarE 'fmap)
(AppE pair (LitE . StringL $ inputInfoId info)))
(AppE (VarE fieldName) var))
$ zip fields infos
return $ Clause [pat]
(NormalB $ AppE
(VarE 'Map.fromList)
(AppE
(VarE 'catMaybes)
$ ListE list)) []
haskellType :: InputInfo -> Type
haskellType info = contextType dataType
where
dataType =
case (inputInfoType info) of
InputTypeBool -> ConT ''Bool
InputTypeDate -> ConT ''Day
InputTypeDouble -> ConT ''Double
InputTypeFloat -> ConT ''Float
InputTypeInt -> ConT ''Int
InputTypeString -> ConT ''String
contextType t =
case (inputInfoRequired info) of
True -> t
False -> AppT (ConT ''Maybe) t