hydra-0.1.0: src/main/haskell/Hydra/Ext/Haskell/Coder.hs
module Hydra.Ext.Haskell.Coder (printModule) where
import Hydra.All
import Hydra.CoreDecoding
import Hydra.Adapters.Coders
import Hydra.Ext.Haskell.Language
import Hydra.Ext.Haskell.Utils
import qualified Hydra.Ext.Haskell.Ast as H
import qualified Hydra.Lib.Strings as Strings
import qualified Hydra.Impl.Haskell.Dsl.Types as Types
import Hydra.Impl.Haskell.Dsl.Terms
import Hydra.Util.Codetree.Script
import Hydra.Ext.Haskell.Serde
import Hydra.Ext.Haskell.Settings
import qualified Control.Monad as CM
import qualified Data.List as L
import qualified Data.Map as M
import qualified Data.Set as S
import qualified Data.Maybe as Y
constantDecls :: Context m -> Namespaces -> Name -> Type m -> [H.DeclarationWithComments]
constantDecls cx namespaces name@(Name nm) typ = if useCoreImport
then toDecl (Name "hydra/core.Name") nameDecl:(toDecl (Name "hydra/core.FieldName") <$> fieldDecls)
else []
where
lname = localNameOfEager name
toDecl n (k, v) = H.DeclarationWithComments decl Nothing
where
decl = H.DeclarationValueBinding $
H.ValueBindingSimple $ H.ValueBinding_Simple pat rhs Nothing
pat = H.PatternApplication $ H.Pattern_Application (simpleName k) []
rhs = H.RightHandSide $ H.ExpressionApplication $ H.Expression_Application
(H.ExpressionVariable $ elementReference namespaces n)
(H.ExpressionLiteral $ H.LiteralString v)
nameDecl = ("_" ++ lname, nm)
fieldsOf t = case stripType t of
TypeRecord rt -> rowTypeFields rt
TypeUnion rt -> rowTypeFields rt
_ -> []
fieldDecls = toConstant <$> fieldsOf (snd $ unpackLambdaType cx typ)
toConstant (FieldType (FieldName fname) _) = ("_" ++ lname ++ "_" ++ fname, fname)
constructModule :: (Ord m, Read m, Show m)
=> Module m
-> M.Map (Type m) (Coder (Context m) (Context m) (Term m) H.Expression)
-> [(Element m, TypedTerm m)] -> GraphFlow m H.Module
constructModule mod coders pairs = do
cx <- getState
decls <- L.concat <$> CM.mapM (createDeclarations cx) pairs
let mc = moduleDescription mod
return $ H.Module (Just $ H.ModuleHead mc (importName $ h $ moduleNamespace mod) []) imports decls
where
h (Namespace name) = name
createDeclarations cx pair@(el, TypedTerm typ term) = if isType cx typ
then toTypeDeclarations namespaces el term
else toDataDeclarations coders namespaces pair
namespaces = namespacesForModule mod
importName name = H.ModuleName $ L.intercalate "." (capitalize <$> Strings.splitOn "/" name)
imports = domainImports ++ standardImports
where
domainImports = toImport <$> M.toList (namespacesMapping namespaces)
where
toImport (Namespace name, alias) = H.Import True (importName name) (Just alias) Nothing
standardImports = toImport . H.ModuleName <$> Y.catMaybes [
Just "Data.List",
Just "Data.Map",
Just "Data.Set"{-,
if useCoreImport && moduleNamespace g /= Namespace "hydra/core"
then Just "Hydra.Core"
else Nothing-}]
where
toImport name = H.Import False name Nothing Nothing
encodeAdaptedType :: (Ord m, Read m, Show m) => Namespaces -> Type m -> GraphFlow m H.Type
encodeAdaptedType namespaces typ = adaptType haskellLanguage typ >>= encodeType namespaces
encodeFunction :: (Eq m, Ord m, Read m, Show m) => Namespaces -> Function m -> GraphFlow m H.Expression
encodeFunction namespaces fun = case fun of
FunctionElimination e -> case e of
EliminationElement -> pure $ hsvar "id"
EliminationList fun -> do
let lhs = hsvar "foldl"
rhs <- encodeTerm namespaces fun
return $ hsapp lhs rhs
EliminationNominal name -> pure $ H.ExpressionVariable $ elementReference namespaces $
qname (namespaceOfEager name) $ newtypeAccessorName name
EliminationOptional (OptionalCases nothing just) -> do
nothingRhs <- H.CaseRhs <$> encodeTerm namespaces nothing
let nothingAlt = H.Alternative (H.PatternName $ simpleName "Nothing") nothingRhs Nothing
justAlt <- do
-- Note: some of the following could be brought together with FunctionCases
let v0 = "v"
let rhsTerm = simplifyTerm $ apply just (variable v0)
let v1 = if S.member (Variable v0) $ freeVariablesInTerm rhsTerm then v0 else "_"
let lhs = H.PatternApplication $ H.Pattern_Application (rawName "Just") [H.PatternName $ rawName v1]
rhs <- H.CaseRhs <$> encodeTerm namespaces rhsTerm
return $ H.Alternative lhs rhs Nothing
return $ H.ExpressionCase $ H.Expression_Case (hsvar "x") [nothingAlt, justAlt]
EliminationRecord (Projection dn fname) -> return $ H.ExpressionVariable $ recordFieldReference namespaces dn fname
EliminationUnion (CaseStatement dn fields) -> hslambda "x" <$> caseExpr -- note: could use a lambda case here
where
caseExpr = do
rt <- withSchemaContext $ requireUnionType False dn
let fieldMap = M.fromList $ (\f -> (fieldTypeName f, f)) <$> rowTypeFields rt
H.ExpressionCase <$> (H.Expression_Case (hsvar "x") <$> CM.mapM (toAlt fieldMap) fields)
toAlt fieldMap (Field fn fun') = do
let v0 = "v"
let raw = apply fun' (variable v0)
let rhsTerm = simplifyTerm raw
let v1 = if isFreeIn (Variable v0) rhsTerm then "_" else v0
let hname = unionFieldReference namespaces dn fn
args <- case M.lookup fn fieldMap of
Just (FieldType _ ft) -> case stripType ft of
TypeRecord (RowType _ Nothing []) -> pure []
_ -> pure [H.PatternName $ rawName v1]
Nothing -> fail $ "field " ++ show fn ++ " not found in " ++ show dn
let lhs = H.PatternApplication $ H.Pattern_Application hname args
rhs <- H.CaseRhs <$> encodeTerm namespaces rhsTerm
return $ H.Alternative lhs rhs Nothing
FunctionLambda (Lambda (Variable v) body) -> hslambda v <$> encodeTerm namespaces body
FunctionPrimitive name -> pure $ H.ExpressionVariable $ hsPrimitiveReference name
_ -> fail $ "unexpected function: " ++ show fun
encodeLiteral :: Literal -> GraphFlow m H.Expression
encodeLiteral av = case av of
LiteralBoolean b -> pure $ hsvar $ if b then "True" else "False"
LiteralFloat fv -> case fv of
FloatValueFloat32 f -> pure $ hslit $ H.LiteralFloat f
FloatValueFloat64 f -> pure $ hslit $ H.LiteralDouble f
_ -> unexpected "floating-point number" fv
LiteralInteger iv -> case iv of
IntegerValueBigint i -> pure $ hslit $ H.LiteralInteger i
IntegerValueInt32 i -> pure $ hslit $ H.LiteralInt i
_ -> unexpected "integer" iv
LiteralString s -> pure $ hslit $ H.LiteralString s
_ -> unexpected "literal value" av
encodeTerm :: (Eq m, Ord m, Read m, Show m) => Namespaces -> Term m -> GraphFlow m H.Expression
encodeTerm namespaces term = do
case stripTerm term of
TermApplication (Application fun arg) -> case stripTerm fun of
TermFunction (FunctionElimination EliminationElement) -> encode arg
_ -> hsapp <$> encode fun <*> encode arg
TermElement name -> pure $ H.ExpressionVariable $ elementReference namespaces name
TermFunction f -> encodeFunction namespaces f
TermList els -> H.ExpressionList <$> CM.mapM encode els
TermLiteral v -> encodeLiteral v
TermNominal (Named tname term') -> if newtypesNotTypedefs
then hsapp <$> pure (H.ExpressionVariable $ elementReference namespaces tname) <*> encode term'
else encode term'
TermOptional m -> case m of
Nothing -> pure $ hsvar "Nothing"
Just t -> hsapp (hsvar "Just") <$> encode t
TermProduct terms -> H.ExpressionTuple <$> (CM.mapM encode terms)
TermRecord (Record sname fields) -> do
if L.null fields -- TODO: too permissive; not all empty record types are the unit type
then pure $ H.ExpressionTuple []
else do
let typeName = typeNameForRecord sname
updates <- CM.mapM toFieldUpdate fields
return $ H.ExpressionConstructRecord $ H.Expression_ConstructRecord (rawName typeName) updates
where
toFieldUpdate (Field fn ft) = H.FieldUpdate (recordFieldReference namespaces sname fn) <$> encode ft
TermUnion (Union sname (Field fn ft)) -> do
let lhs = H.ExpressionVariable $ unionFieldReference namespaces sname fn
case stripTerm ft of
TermRecord (Record _ []) -> pure lhs
_ -> hsapp lhs <$> encode ft
TermVariable (Variable v) -> pure $ hsvar v
_ -> fail $ "unexpected term: " ++ show term
where
encode = encodeTerm namespaces
encodeType :: Show m => Namespaces -> Type m -> GraphFlow m H.Type
encodeType namespaces typ = case stripType typ of
TypeApplication (ApplicationType lhs rhs) -> toTypeApplication <$>
CM.sequence [encode lhs, encode rhs]
TypeElement et -> encode et
TypeFunction (FunctionType dom cod) -> H.TypeFunction <$> (H.Type_Function <$> encode dom <*> encode cod)
TypeLambda (LambdaType (VariableType v) body) -> toTypeApplication <$> CM.sequence [
encode body,
pure $ H.TypeVariable $ simpleName v]
TypeList lt -> H.TypeList <$> encode lt
TypeLiteral lt -> H.TypeVariable . rawName <$> case lt of
LiteralTypeBoolean -> pure "Bool"
LiteralTypeFloat ft -> case ft of
FloatTypeFloat32 -> pure "Float"
FloatTypeFloat64 -> pure "Double"
_ -> fail $ "unexpected floating-point type: " ++ show ft
LiteralTypeInteger it -> case it of
IntegerTypeBigint -> pure "Integer"
IntegerTypeInt32 -> pure "Int"
_ -> fail $ "unexpected integer type: " ++ show it
LiteralTypeString -> pure "String"
_ -> fail $ "unexpected literal type: " ++ show lt
TypeMap (MapType kt vt) -> toTypeApplication <$> CM.sequence [
pure $ H.TypeVariable $ rawName "Map",
encode kt,
encode vt]
TypeNominal name -> nominal name
TypeOptional ot -> toTypeApplication <$> CM.sequence [
pure $ H.TypeVariable $ rawName "Maybe",
encode ot]
TypeProduct types -> H.TypeTuple <$> (CM.mapM encode types)
TypeRecord rt -> case rowTypeFields rt of
[] -> pure $ H.TypeTuple [] -- TODO: too permissive; not all empty record types are the unit type
_ -> nominal $ rowTypeTypeName rt
TypeSet st -> toTypeApplication <$> CM.sequence [
pure $ H.TypeVariable $ rawName "Set",
encode st]
TypeUnion rt -> nominal $ rowTypeTypeName rt
TypeVariable (VariableType v) -> pure $ H.TypeVariable $ simpleName v
_ -> fail $ "unexpected type: " ++ show typ
where
encode = encodeType namespaces
nominal name = pure $ H.TypeVariable $ elementReference namespaces name
moduleToHaskellModule :: (Ord m, Read m, Show m) => Module m -> GraphFlow m H.Module
moduleToHaskellModule mod = transformModule haskellLanguage (encodeTerm namespaces) constructModule mod
where
namespaces = namespacesForModule mod
printModule :: (Ord m, Read m, Show m) => Module m -> GraphFlow m (M.Map FilePath String)
printModule mod = do
hsmod <- moduleToHaskellModule mod
let s = printExpr $ parenthesize $ toTree hsmod
return $ M.fromList [(namespaceToFilePath True (FileExtension "hs") $ moduleNamespace mod, s)]
toDataDeclarations :: (Ord m, Show m)
=> M.Map (Type m) (Coder (Context m) (Context m) (Term m) H.Expression) -> Namespaces
-> (Element m, TypedTerm m) -> GraphFlow m [H.DeclarationWithComments]
toDataDeclarations coders namespaces (el, TypedTerm typ term) = do
let coder = Y.fromJust $ M.lookup typ coders
rhs <- H.RightHandSide <$> coderEncode coder term
let hname = simpleName $ localNameOfEager $ elementName el
let pat = H.PatternApplication $ H.Pattern_Application hname []
htype <- encodeType namespaces typ
let decl = H.DeclarationTypedBinding $ H.TypedBinding
(H.TypeSignature hname htype)
(H.ValueBindingSimple $ rewriteValueBinding $ H.ValueBinding_Simple pat rhs Nothing)
cx <- getState
comments <- annotationClassTermDescription (contextAnnotations cx) term
return [H.DeclarationWithComments decl comments]
where
rewriteValueBinding vb = case vb of
H.ValueBinding_Simple (H.PatternApplication (H.Pattern_Application name args)) rhs bindings -> case rhs of
H.RightHandSide (H.ExpressionLambda (H.Expression_Lambda vars body)) -> rewriteValueBinding $
H.ValueBinding_Simple
(H.PatternApplication (H.Pattern_Application name (args ++ vars))) (H.RightHandSide body) bindings
_ -> vb
toTypeDeclarations :: (Ord m, Read m, Show m)
=> Namespaces -> Element m -> Term m -> GraphFlow m [H.DeclarationWithComments]
toTypeDeclarations namespaces el term = do
cx <- getState
let lname = localNameOfEager $ elementName el
let hname = simpleName lname
t <- decodeType term
isSer <- isSerializable
let deriv = H.Deriving $ if isSer
then rawName <$> ["Eq", "Ord", "Read", "Show"]
else []
let (vars, t') = unpackLambdaType cx t
let hd = declHead hname $ L.reverse vars
decl <- case stripType t' of
TypeRecord rt -> do
cons <- recordCons lname $ rowTypeFields rt
return $ H.DeclarationData $ H.DataDeclaration H.DataDeclaration_KeywordData [] hd [cons] [deriv]
TypeUnion rt -> do
cons <- CM.mapM (unionCons lname) $ rowTypeFields rt
return $ H.DeclarationData $ H.DataDeclaration H.DataDeclaration_KeywordData [] hd cons [deriv]
_ -> if newtypesNotTypedefs
then do
cons <- newtypeCons el t'
return $ H.DeclarationData $ H.DataDeclaration H.DataDeclaration_KeywordNewtype [] hd [cons] [deriv]
else do
htype <- encodeAdaptedType namespaces t
return $ H.DeclarationType (H.TypeDeclaration hd htype)
comments <- annotationClassTermDescription (contextAnnotations cx) term
return $ [H.DeclarationWithComments decl comments] ++ constantDecls cx namespaces (elementName el) t
where
isSerializable = do
deps <- typeDependencies (elementName el)
let allVariants = S.fromList $ L.concat (variants <$> M.elems deps)
return $ not $ S.member TypeVariantFunction allVariants
where
variants typ = typeVariant <$> foldOverType TraversalOrderPre (\m t -> t:m) [] typ
declHead name vars = case vars of
[] -> H.DeclarationHeadSimple name
((VariableType h):rest) -> H.DeclarationHeadApplication $
H.DeclarationHead_Application (declHead name rest) (H.Variable $ simpleName h)
newtypeCons el typ = do
cx <- getState
let hname = simpleName $ newtypeAccessorName $ elementName el
htype <- encodeAdaptedType namespaces typ
comments <- annotationClassTypeDescription (contextAnnotations cx) typ
let hfield = H.FieldWithComments (H.Field hname htype) comments
return $ H.ConstructorWithComments
(H.ConstructorRecord $ H.Constructor_Record (simpleName $ localNameOfEager $ elementName el) [hfield]) Nothing
recordCons lname fields = do
hFields <- CM.mapM toField fields
return $ H.ConstructorWithComments (H.ConstructorRecord $ H.Constructor_Record (simpleName lname) hFields) Nothing
where
toField (FieldType (FieldName fname) ftype) = do
let hname = simpleName $ decapitalize lname ++ capitalize fname
htype <- encodeAdaptedType namespaces ftype
cx <- getState
comments <- annotationClassTypeDescription (contextAnnotations cx) ftype
return $ H.FieldWithComments (H.Field hname htype) comments
unionCons lname (FieldType (FieldName fname) ftype) = do
cx <- getState
comments <- annotationClassTypeDescription (contextAnnotations cx) ftype
let nm = capitalize lname ++ capitalize fname
typeList <- if stripType ftype == Types.unit
then pure []
else do
htype <- encodeAdaptedType namespaces ftype
return [htype]
return $ H.ConstructorWithComments (H.ConstructorOrdinary $ H.Constructor_Ordinary (simpleName nm) typeList) comments