----------------------------------------------------------------------------- -- -- Module : Language.Elm.TH.Json -- Copyright : Copyright: (c) 2011-2013 Joey Eremondi -- License : BSD3 -- -- Maintainer : joey.eremondi@usask.ca -- Stability : experimental -- Portability : portable -- -- | -- ----------------------------------------------------------------------------- module Language.Elm.TH.Json where {-# LANGUAGE TemplateHaskell, QuasiQuotes, MultiWayIf #-} import Language.Haskell.TH.Syntax import Data.Aeson.TH import qualified SourceSyntax.Module as M import qualified SourceSyntax.Declaration as D import qualified SourceSyntax.Expression as E import qualified SourceSyntax.Literal as L import qualified SourceSyntax.Location as Lo import qualified SourceSyntax.Pattern as P import qualified SourceSyntax.Type as T import Data.List (isPrefixOf) import Language.Haskell.TH.Desugar.Sweeten import Language.Haskell.TH.Desugar import Language.Elm.TH.Util --import Parse.Expression (makeFunction) import Control.Applicative import Control.Monad import Control.Monad.State (StateT) import qualified Control.Monad.State as S -- |Helper function to apply arguments to a function applyArgs :: Exp -> [Exp] -> Exp applyArgs fun args = foldl (\ accumFun nextArg -> AppE accumFun nextArg) fun args fnComp = VarE $ mkName "." -- | Helper function to generate a the names X1 .. Xn with some prefix X nNames :: Int -> String -> SQ [Name] nNames n base = do let varStrings = map (\n -> base ++ show n) [1..n] mapM liftNewName varStrings -- | Variable for the getter function getting the nth variable from a Json varNamed :: Exp varNamed = VarE (mkName "JsonUtil.varNamed") -- | Expression getting a named subvariable from a JSON object getVarNamed :: String -> Exp getVarNamed nstr = AppE (AppE varNamed jsonArgExp ) (LitE $ StringL nstr) -- | Filter function to test if a dec is a data -- Also filters out decs which types that can't be serialized, such as functions isData :: Dec -> Bool isData dec = (isData' dec) && (canSerial dec) where isData' DataD{} = True isData' NewtypeD{} = True isData' TySynD{} = True isData' _ = False canSerial (DataD _ _ _ ctors _) = all canSerialCtor ctors canSerial (NewtypeD _ _ _ ctor _) = canSerialCtor ctor canSerial (TySynD _ _ ty) = canSerialType ty --can't serialize if type variables --TODO is this true? canSerial _ = False canSerialCtor (NormalC _ types) = all (canSerialType) (map snd types) canSerialCtor (RecC _ types) = all (canSerialType) (map (\(_,_,c)->c) types) canSerialType (ArrowT) = False canSerialType t = all canSerialType (subTypes t) --General helper functions jsonArgName :: Name jsonArgName = mkName "jsonArg" jsonArgPat :: Pat jsonArgPat = VarP jsonArgName jsonArgExp :: Exp jsonArgExp = VarE jsonArgName fromJsonName :: Name -> Name fromJsonName name = mkName $ "fromJson_" ++ nameToString name toJsonName :: Name -> Name toJsonName name = mkName $ "toJson_" ++ nameToString name makeFromJson :: [Dec] -> SQ [Dec] makeFromJson allDecs = do let decs = filter isData allDecs mapM fromJsonForDec decs -- | Given a type, and an expression for an argument of type Json -- return the expression which applies the proper fromJson function to that expression fromJsonForType :: Type -> SQ Exp --Type name not covered by Prelude fromJsonForType (ConT name) = case (nameToString name) of "Int" -> return $ VarE $ mkName "JsonUtil.intFromJson" "Bool" -> return $ VarE $ mkName "JsonUtil.boolFromJson" "Float" -> return $ VarE $ mkName "JsonUtil.floatFromJson" "Double" -> return $ VarE $ mkName "JsonUtil.floatFromJson" "String" -> return $ VarE $ mkName "JsonUtil.stringFromJson" _ -> return $ VarE $ fromJsonName name fromJsonForType (AppT ListT t) = do subExp <- fromJsonForType t return $ AppE (VarE $ mkName "JsonUtil.listFromJson") subExp fromJsonForType (AppT (ConT name) t) = do subExp <- fromJsonForType t case (nameToString name) of "Maybe" -> return $ AppE (VarE $ mkName "JsonUtil.maybeFromJson") subExp fromJsonForType (AppT (AppT (ConT name) t1) t2) = do sub1 <- fromJsonForType t1 sub2 <- fromJsonForType t2 case (nameToString name) of "Data.Map.Map" -> return $ applyArgs (VarE $ mkName "JsonUtil.dictFromJson") [sub1, sub2] s -> error $ "Unsupported json type " ++ s fromJsonForType t | isTupleType t = do let tList = tupleTypeToList t let n = length tList --Generate the lambda to convert the list into a tuple subFunList <- mapM fromJsonForType tList argNames <- mapM (liftNewName . ("x" ++) . show) [1 .. n] let argValues = map VarE argNames let argPat = ListP $ map VarP argNames let lambdaBody = TupE $ zipWith AppE subFunList argValues let lambda = LamE [argPat] lambdaBody let makeList = VarE $ mkName "makeList" return $ InfixE (Just lambda) fnComp (Just makeList) | otherwise = error $ "Can't make Json for type " ++ (show t) -- |Given a type declaration, generate the function declaration -- Which takes a Json object to a value of that type fromJsonForDec :: Dec -> SQ Dec --Special case: we only have one ctor, so we don't use a tag fromJsonForDec dec@(DataD _ name _ [ctor] _deriving) = do Match _pat fnBody _decs <- fromMatchForCtor 1 ctor let argPat = jsonArgPat let fnName = fromJsonName name let fnClause = Clause [argPat] fnBody [] return $ FunD fnName [fnClause] fromJsonForDec dec@(DataD _ name _ ctors _deriving) = do let argTagExpression = AppE (VarE $ mkName "JsonUtil.getTag") jsonArgExp let numCtors = length ctors ctorMatches <- mapM (fromMatchForCtor numCtors) ctors let fnExp = CaseE argTagExpression ctorMatches let argPat = jsonArgPat let fnName = fromJsonName name let fnBody = NormalB fnExp let fnClause = Clause [argPat] fnBody [] return $ FunD fnName [fnClause] fromJsonForDec (NewtypeD cxt name tyBindings ctor nameList) = fromJsonForDec $ DataD cxt name tyBindings [ctor] nameList fromJsonForDec dec@(TySynD name _tyvars ty) = do let fnName = fromJsonName name fnBody <- NormalB <$> fromJsonForType ty let fnClause = Clause [] fnBody [] return $ FunD fnName [fnClause] fromMatchForCtor :: Int -> Con -> SQ Match fromMatchForCtor numCtors (NormalC name strictTypes) = do let types = map snd strictTypes let leftHandSide = LitP $ StringL $ nameToString name let ctorExp = VarE name --Exp in TH, list in Haskell contentListExpr <- NormalB <$> unpackContents numCtors jsonArgExp fromJsonFunctions <- mapM fromJsonForType types let intNames = map (("subVar" ++) . show) [1 .. length types] subDataNames <- mapM liftNewName intNames --We unpack each json var into its own named variable, so we can unpack them into different types let subDataListPattern = ListP $ map VarP subDataNames --let subDataExprs = map VarE subDataNames let unJsonedExprList = zipWith AppE fromJsonFunctions (map VarE subDataNames) let letExp = LetE [ValD subDataListPattern contentListExpr []] (applyArgs ctorExp unJsonedExprList) let rightHandSide = NormalB $ letExp return $ Match leftHandSide rightHandSide [] fromMatchForCtor _numCtors (RecC name vstList) = do let nameTypes = map (\(a,_,b)->(nameToString a,b)) vstList let matchPat = LitP $ StringL $ nameToString name (subNames, subDecs) <- unzip <$> mapM getSubJsonRecord nameTypes let body = NormalB $ if null subNames then applyArgs subNames ctorExp else LetE subDecs (applyArgs subNames ctorExp) return $ Match matchPat body [] where ctorExp = ConE name applyArgs t accum = foldl (\ accum h -> AppE accum (VarE h)) accum t -- | Generate a declaration, and a name bound in that declaration, -- Which unpacks a value of the given type from the nth field of a JSON object getSubJsonRecord :: (String, Type) -> SQ (Name, Dec) -- We need special cases for lists and tuples, to unpack them --TODO recursive case getSubJsonRecord (field, t) = do funToApply <- fromJsonForType t subName <- liftNewName "subVar" let subLeftHand = VarP subName let subRightHand = NormalB $ AppE funToApply (getVarNamed field) return (subName, ValD subLeftHand subRightHand []) unpackContents :: Int -> Exp -> SQ Exp unpackContents numCtors jsonValue = return $ applyArgs (VarE $ mkName "JsonUtil.unpackContents") [LitE $ IntegerL $ toInteger numCtors, jsonValue] makeToJson allDecs = do let decs = filter isData allDecs mapM toJsonForDec decs toJsonForType :: Type -> SQ Exp toJsonForType (ConT name) = case (nameToString name) of "Int" -> return $ VarE $ mkName "JsonUtil.intToJson" "Bool" -> return $ VarE $ mkName "JsonUtil.boolToJson" "Float" -> return $ VarE $ mkName "JsonUtil.floatToJson" "Double" -> return $ VarE $ mkName "JsonUtil.floatToJson" "String" -> return $ VarE $ mkName "JsonUtil.stringToJson" _ -> return $ VarE $ toJsonName name toJsonForType (AppT (AppT (ConT name) t1) t2) = do sub1 <- toJsonForType t1 sub2 <- toJsonForType t2 case (nameToString name) of "Data.Map.Map" -> return $ applyArgs (VarE $ mkName "JsonUtil.dictToJson") [sub1, sub2] s -> error $ "Unsupported json type " ++ s toJsonForType (AppT ListT t) = do subExp <- toJsonForType t return $ AppE (VarE $ mkName "JsonUtil.listToJson") subExp toJsonForType (AppT (ConT name) t) = do subExp <- toJsonForType t case (nameToString name) of "Maybe" -> return $ AppE (VarE $ mkName "JsonUtil.maybeToJson") subExp toJsonForType t | isTupleType t = do let tList = tupleTypeToList t let n = length tList --Generate the lambda to convert the list into a tuple subFunList <- mapM toJsonForType tList argNames <- mapM (liftNewName . ("x" ++) . show) [1 .. n] let argValues = map VarE argNames let argPat = TupP $ map VarP argNames --Get each tuple element as Json, then wrap them in a Json Array let listExp = AppE (VarE $ mkName "Json.Array") (ListE $ zipWith AppE subFunList argValues) return $ LamE [argPat] listExp toJsonForDec :: Dec -> SQ Dec toJsonForDec dec@(DataD _ name _ ctors _deriving) = do let argPat = jsonArgPat let argExp = jsonArgExp let numCtors = length ctors ctorMatches <- mapM (toMatchForCtor numCtors) ctors let fnExp = CaseE jsonArgExp ctorMatches let fnName = toJsonName name let fnBody = NormalB fnExp let fnClause = Clause [argPat] fnBody [] return $ FunD fnName [fnClause] toJsonForDec (NewtypeD cxt name tyBindings ctor nameList) = toJsonForDec $ DataD cxt name tyBindings [ctor] nameList toJsonForDec dec@(TySynD name _tyvars ty) = do let fnName = toJsonName name fnBody <- NormalB <$> toJsonForType ty let fnClause = Clause [] fnBody [] return $ FunD fnName [fnClause] toJsonForDec dec = error $ "Unknown dec type" ++ (show dec) toMatchForCtor :: Int -> Con -> SQ Match toMatchForCtor numCtors (NormalC name strictTypes) = do let types = map snd strictTypes let numStrings = map (("subVar_" ++) . show) [1 .. length types] subDataNames <- mapM liftNewName numStrings let subDataPats = map VarP subDataNames let leftHandSide = ConP name subDataPats let subDataExprs = map VarE subDataNames toJsonFunctions <- mapM toJsonForType types let contentsList = ListE $ zipWith AppE toJsonFunctions subDataExprs jsonValueExp <- packContents numCtors name contentsList let rightHandSide = NormalB jsonValueExp return $ Match leftHandSide rightHandSide [] --TODO is there ever a record with 0 args? toMatchForCtor _numCtors (RecC name vstList) = do let (adtNames, _, types) = unzip3 vstList let n = length types jsonNames <- nNames n "jsonVar" let adtPats = map VarP adtNames let matchPat = ConP name adtPats jsonDecs <- mapM makeSubJsonRecord (zip3 types adtNames jsonNames) dictName <- liftNewName "objectDict" dictDec <- makeRecordDict name dictName jsonNames let ret = AppE (VarE $ mkName "Json.Object") (VarE dictName) let body = NormalB $ LetE (jsonDecs ++ [dictDec]) ret return $ Match matchPat body [] -- | Generate the declaration of a dictionary mapping field names to values -- to be used with the JSON Object constructor makeRecordDict :: Name -> Name -> [Name] -> SQ Dec makeRecordDict ctorName dictName jsonNames = do let leftSide = VarP dictName let jsonExps = map VarE jsonNames let fieldNames = map (LitE . StringL . show) [1 .. (length jsonNames)] let tuples = map (\(field, json) -> TupE [field, json]) (zip fieldNames jsonExps) let ctorExp = LitE $ StringL $ nameToString ctorName let ctorTuple = TupE [LitE $ StringL "tag", AppE (VarE (mkName "Json.String")) ctorExp ] let tupleList = ListE $ [ctorTuple] ++ tuples let rightSide = NormalB $ AppE (VarE $ mkName "Data.Map.fromList") tupleList return $ ValD leftSide rightSide [] -- | Generate the declaration of a value converted to Json -- given the name of an ADT value to convert makeSubJsonRecord :: (Type, Name, Name) -> SQ Dec -- We need special cases for lists and tuples, to unpack them --TODO recursive case makeSubJsonRecord (t, adtName, jsonName) = do funToApply <- toJsonForType t let subLeftHand = VarP jsonName let subRightHand = NormalB $ AppE funToApply (VarE adtName) return $ ValD subLeftHand subRightHand [] packContents :: Int -> Name -> Exp -> SQ Exp packContents numCtors name contentList = do return $ applyArgs (VarE $ mkName "JsonUtil.packContents") [LitE $ IntegerL $ toInteger numCtors, LitE $ StringL $ nameToString name, contentList]