{-# language CPP #-}
{-# language DataKinds #-}
{-# language TemplateHaskell #-}
{-# language TypeOperators #-}
module Mu.Schema.Conversion.SchemaToTypes (
generateTypesFromSchema
, Namer
) where
import Control.Applicative
import Data.Char
import qualified Data.Map as M
import Data.SOP
import GHC.Generics (Generic)
import Language.Haskell.TH
import Language.Haskell.TH.Datatype
import Mu.Schema.Definition
type Namer = String -> String
generateTypesFromSchema :: Namer -> Name -> Q [Dec]
generateTypesFromSchema namer schemaTyName
= do let schemaTy = ConT schemaTyName
schDef <- typeToSchemaDef schemaTy
case schDef of
Nothing -> fail "schema cannot be parsed"
Just sd -> concat <$> traverse (typeDefToDecl schemaTy namer) sd
typeDefToDecl :: Type -> Namer -> TypeDefB Type String String -> Q [Dec]
typeDefToDecl _schemaTy namer (DRecord name [f])
= do let complete = completeName namer name
fVar <- newName "f"
d <- newtypeD (pure [])
(mkName complete)
[PlainTV fVar]
Nothing
(pure (RecC (mkName complete) [fieldDefToDecl namer complete fVar f]))
[pure (DerivClause Nothing [ConT ''Generic])]
_wTy <- VarT <$> newName "w"
return [d]
typeDefToDecl _schemaTy namer (DRecord name fields)
= do let complete = completeName namer name
fVar <- newName "f"
d <- dataD (pure [])
(mkName complete)
[PlainTV fVar]
Nothing
[pure (RecC (mkName complete) (map (fieldDefToDecl namer complete fVar) fields))]
[pure (DerivClause Nothing [ConT ''Generic])]
_wTy <- VarT <$> newName "w"
return [d]
typeDefToDecl _schemaTy namer (DEnum name choices)
= do let complete = completeName namer name
fVar <- newName "f"
d <- dataD (pure [])
(mkName complete)
[PlainTV fVar]
Nothing
[ pure (RecC (mkName (choiceName complete choicename)) [])
| ChoiceDef choicename <- choices]
[pure (DerivClause Nothing [ConT ''Eq, ConT ''Ord, ConT ''Show, ConT ''Generic])]
_wTy <- VarT <$> newName "w"
return [d]
typeDefToDecl _ _ (DSimple _)
= fail "DSimple is not supported"
fieldDefToDecl :: Namer -> String -> Name -> FieldDefB Type String String -> (Name, Bang, Type)
fieldDefToDecl namer complete fVar (FieldDef name ty)
= ( mkName (fieldName complete name)
, Bang NoSourceUnpackedness NoSourceStrictness
, AppT (VarT fVar) (fieldTypeToDecl namer fVar ty) )
completeName :: Namer -> String -> String
completeName namer name = firstUpper (namer (firstUpper name))
choiceName :: String -> String -> String
choiceName complete cname = firstUpper (complete ++ firstUpper cname)
fieldName :: String -> String -> String
fieldName complete fname = firstLower (complete ++ firstUpper fname)
firstUpper :: String -> String
firstUpper [] = error "Empty names are not allowed"
firstUpper (x:rest) = toUpper x : rest
firstLower :: String -> String
firstLower [] = error "Empty names are not allowed"
firstLower (x:rest) = toLower x : rest
fieldTypeToDecl :: Namer -> Name -> FieldTypeB Type String -> Type
fieldTypeToDecl _namer _fVar TNull
= ConT ''()
fieldTypeToDecl _namer _fVar (TPrimitive t)
= t
fieldTypeToDecl namer fVar (TSchematic nm)
= AppT (ConT (mkName $ completeName namer nm)) (VarT fVar)
fieldTypeToDecl namer fVar (TOption t)
= AppT (ConT ''Maybe) (fieldTypeToDecl namer fVar t)
fieldTypeToDecl namer fVar (TList t)
= AppT ListT (fieldTypeToDecl namer fVar t)
fieldTypeToDecl namer fVar (TMap k v)
= AppT (AppT (ConT ''M.Map) (fieldTypeToDecl namer fVar k)) (fieldTypeToDecl namer fVar v)
fieldTypeToDecl namer fVar (TUnion ts)
= AppT (AppT (ConT ''NS) (ConT ''I)) (fieldTypeUnion namer fVar ts)
fieldTypeUnion :: Namer -> Name -> [FieldTypeB Type String] -> Type
fieldTypeUnion _ _fVar [] = PromotedNilT
fieldTypeUnion namer fVar (t:ts)
= AppT (AppT PromotedConsT (fieldTypeToDecl namer fVar t)) (fieldTypeUnion namer fVar ts)
typeToSchemaDef :: Type -> Q (Maybe (SchemaB Type String String))
typeToSchemaDef toplevelty
= typeToSchemaDef' <$> resolveTypeSynonyms toplevelty
where
typeToSchemaDef' :: Type -> Maybe (SchemaB Type String String)
typeToSchemaDef' expanded
= do types <- tyList expanded
mapM typeToTypeDef types
typeToTypeDef, typeToRecordDef, typeToEnumDef, typeToSimpleType
:: Type -> Maybe (TypeDefB Type String String)
typeToTypeDef t
= typeToRecordDef t <|> typeToEnumDef t <|> typeToSimpleType t
typeToRecordDef t
= do (nm, fields) <- tyD2 'DRecord t
DRecord <$> tyString nm
<*> (mapM typeToFieldDef =<< tyList fields)
typeToEnumDef t
= do (nm, choices) <- tyD2 'DEnum t
DEnum <$> tyString nm
<*> (mapM typeToChoiceDef =<< tyList choices)
typeToSimpleType t
= do innerT <- tyD1 'DSimple t
DSimple <$> typeToFieldType innerT
typeToFieldDef :: Type -> Maybe (FieldDefB Type String String)
typeToFieldDef t
= do (nm, innerTy) <- tyD2 'FieldDef t
FieldDef <$> tyString nm
<*> typeToFieldType innerTy
typeToChoiceDef :: Type -> Maybe (ChoiceDef String)
typeToChoiceDef t
= do nm <- tyD1 'ChoiceDef t
ChoiceDef <$> tyString nm
typeToFieldType :: Type -> Maybe (FieldTypeB Type String)
typeToFieldType t
= TNull <$ tyD0 'TNull t
<|> TPrimitive <$>tyD1 'TPrimitive t
<|> (do sch <- tyD1 'TSchematic t
TSchematic <$> tyString sch)
<|> (do inner <- tyD1 'TOption t
TOption <$> typeToFieldType inner)
<|> (do inner <- tyD1 'TList t
TList <$> typeToFieldType inner)
<|> (do (k,v) <- tyD2 'TMap t
TMap <$> typeToFieldType k <*> typeToFieldType v)
<|> (do inners <- tyD1 'TUnion t
TUnion <$> (mapM typeToFieldType =<< tyList inners))
tyString :: Type -> Maybe String
tyString (SigT t _)
= tyString t
tyString (LitT (StrTyLit s))
= Just s
tyString _
= Nothing
tyList :: Type -> Maybe [Type]
tyList (SigT t _)
= tyList t
tyList PromotedNilT
= Just []
tyList (AppT (AppT PromotedConsT ty) rest)
= (ty :) <$> tyList rest
tyList _ = Nothing
tyD0 :: Name -> Type -> Maybe ()
tyD0 name (SigT t _) = tyD0 name t
tyD0 name (PromotedT c)
| c == name = Just ()
| otherwise = Nothing
tyD0 _ _ = Nothing
tyD1 :: Name -> Type -> Maybe Type
tyD1 name (SigT t _) = tyD1 name t
tyD1 name (AppT (PromotedT c) x)
| c == name = Just x
| otherwise = Nothing
tyD1 _ _ = Nothing
tyD2 :: Name -> Type -> Maybe (Type, Type)
tyD2 name (SigT t _) = tyD2 name t
tyD2 name (AppT (AppT (PromotedT c) x) y)
| c == name = Just (x, y)
| otherwise = Nothing
tyD2 _ _ = Nothing