hydra-0.14.0: src/main/haskell/Hydra/Sources/Haskell/Testing.hs
-- | Haskell test code generation codec in Hydra DSL.
-- This module provides DSL versions of Haskell test codec functions for HSpec-based generation tests.
module Hydra.Sources.Haskell.Testing where
-- Standard imports for term-level sources outside of the kernel
import Hydra.Kernel
import Hydra.Sources.Libraries
import Hydra.Dsl.Meta.Lib.Strings as Strings
import Hydra.Dsl.Meta.Phantoms as Phantoms
import qualified Hydra.Dsl.Annotations as Annotations
import qualified Hydra.Dsl.Bootstrap as Bootstrap
import qualified Hydra.Dsl.LiteralTypes as LiteralTypes
import qualified Hydra.Dsl.Literals as Literals
import qualified Hydra.Dsl.Paths as Paths
import qualified Hydra.Dsl.Ast as Ast
import qualified Hydra.Dsl.Meta.Base as MetaBase
import qualified Hydra.Dsl.Coders as Coders
import qualified Hydra.Dsl.Util as Util
import qualified Hydra.Dsl.Meta.Context as Ctx
import qualified Hydra.Dsl.Meta.Core as Core
import qualified Hydra.Dsl.Errors as Error
import qualified Hydra.Dsl.Meta.Graph as Graph
import qualified Hydra.Dsl.Json.Model as Json
import qualified Hydra.Dsl.Meta.Lib.Chars as Chars
import qualified Hydra.Dsl.Meta.Lib.Eithers as Eithers
import qualified Hydra.Dsl.Meta.Lib.Equality as Equality
import qualified Hydra.Dsl.Meta.Lib.Lists as Lists
import qualified Hydra.Dsl.Meta.Lib.Literals as Literals
import qualified Hydra.Dsl.Meta.Lib.Logic as Logic
import qualified Hydra.Dsl.Meta.Lib.Maps as Maps
import qualified Hydra.Dsl.Meta.Lib.Math as Math
import qualified Hydra.Dsl.Meta.Lib.Maybes as Maybes
import qualified Hydra.Dsl.Meta.Lib.Pairs as Pairs
import qualified Hydra.Dsl.Meta.Lib.Sets as Sets
import qualified Hydra.Dsl.Module as Module
import qualified Hydra.Dsl.Meta.Terms as MetaTerms
import qualified Hydra.Dsl.Meta.Testing as Testing
import qualified Hydra.Dsl.Topology as Topology
import qualified Hydra.Dsl.Meta.Types as MetaTypes
import qualified Hydra.Dsl.Typing as Typing
import qualified Hydra.Dsl.Util as Util
import qualified Hydra.Dsl.Meta.Variants as Variants
import qualified Hydra.Dsl.Prims as Prims
import qualified Hydra.Dsl.Meta.Tabular as Tabular
import qualified Hydra.Dsl.Terms as Terms
import qualified Hydra.Dsl.Tests as Tests
import qualified Hydra.Dsl.Types as Types
import qualified Hydra.Sources.Decode.Core as DecodeCore
import qualified Hydra.Sources.Encode.Core as EncodeCore
import qualified Hydra.Sources.Kernel.Terms.Adapt as Adapt
import qualified Hydra.Sources.Kernel.Terms.All as KernelTerms
import qualified Hydra.Sources.Kernel.Terms.Annotations as Annotations
import qualified Hydra.Sources.Kernel.Terms.Arity as Arity
import qualified Hydra.Sources.Kernel.Terms.Checking as Checking
import qualified Hydra.Sources.Kernel.Terms.Constants as Constants
import qualified Hydra.Sources.Kernel.Terms.Extract.Core as ExtractCore
import qualified Hydra.Sources.Kernel.Terms.Extract.Util as ExtractUtil
import qualified Hydra.Sources.Kernel.Terms.Formatting as Formatting
import qualified Hydra.Sources.Kernel.Terms.Inference as Inference
import qualified Hydra.Sources.Kernel.Terms.Languages as Languages
import qualified Hydra.Sources.Kernel.Terms.Lexical as Lexical
import qualified Hydra.Sources.Kernel.Terms.Literals as Literals
import qualified Hydra.Sources.Kernel.Terms.Names as Names
import qualified Hydra.Sources.Kernel.Terms.Reduction as Reduction
import qualified Hydra.Sources.Kernel.Terms.Reflect as Reflect
import qualified Hydra.Sources.Kernel.Terms.Rewriting as Rewriting
import qualified Hydra.Sources.Kernel.Terms.Schemas as Schemas
import qualified Hydra.Sources.Kernel.Terms.Serialization as Serialization
import qualified Hydra.Sources.Kernel.Terms.Show.Paths as ShowPaths
import qualified Hydra.Sources.Kernel.Terms.Show.Errors as ShowError
import qualified Hydra.Sources.Kernel.Terms.Show.Core as ShowCore
import qualified Hydra.Sources.Kernel.Terms.Show.Graph as ShowGraph
import qualified Hydra.Sources.Kernel.Terms.Show.Meta as ShowMeta
import qualified Hydra.Sources.Kernel.Terms.Show.Typing as ShowTyping
import qualified Hydra.Sources.Kernel.Terms.Sorting as Sorting
import qualified Hydra.Sources.Kernel.Terms.Substitution as Substitution
import qualified Hydra.Sources.Kernel.Terms.Templates as Templates
import qualified Hydra.Sources.Kernel.Terms.Unification as Unification
import qualified Hydra.Sources.Kernel.Types.All as KernelTypes
import Prelude hiding ((++))
import qualified Data.Int as I
import qualified Data.List as L
import qualified Data.Map as M
import qualified Data.Set as S
import qualified Data.Maybe as Y
-- Additional imports
import qualified Hydra.Ext.Haskell.Syntax as H
import qualified Hydra.Sources.Haskell.Syntax as HaskellSyntax
import qualified Hydra.Sources.Haskell.Coder as HaskellCoderSource
import qualified Hydra.Sources.Haskell.Serde as HaskellSerdeSource
import qualified Hydra.Sources.Haskell.Utils as HaskellUtilsSource
import qualified Hydra.Sources.Test.Utils as TestUtils
import qualified Hydra.Sources.Kernel.Terms.Serialization as SerializationSource
define :: String -> TTerm a -> TBinding a
define = definitionInModule module_
ns :: Namespace
ns = Namespace "hydra.ext.haskell.testing"
module_ :: Module
module_ = Module ns elements
[Namespace "hydra.ext.haskell.coder", HaskellSerdeSource.ns, HaskellUtilsSource.ns,
SerializationSource.ns, TestUtils.ns, Formatting.ns, Names.ns,
Inference.ns, Constants.ns, Rewriting.ns, Substitution.ns, Schemas.ns, ShowError.ns, Lexical.ns]
(HaskellSyntax.ns:KernelTypes.kernelTypesNamespaces) $
Just "Haskell test code generation codec for HSpec-based generation tests"
where
elements = [
toTermDefinition addNamespacesToNamespaces,
toTermDefinition buildNamespacesForTestGroup,
toTermDefinition buildTestModuleWithCodec,
toTermDefinition collectNames,
toTermDefinition collectTestCases,
toTermDefinition containsTriviallyPolymorphic,
toTermDefinition extractEncodedTermVariableNames,
toTermDefinition extractTestTerms,
toTermDefinition findHaskellImports,
toTermDefinition generateHaskellTestFile,
toTermDefinition generateTestCaseWithCodec,
toTermDefinition generateTestFileWithCodec,
toTermDefinition generateTestGroupHierarchy,
toTermDefinition generateTypeAnnotationFor,
toTermDefinition haskellImportTemplate,
toTermDefinition haskellModuleTemplate,
toTermDefinition haskellTestCaseTemplate,
toTermDefinition haskellTestCodec,
toTermDefinition haskellTestGroupTemplate,
toTermDefinition indentContinuationLines,
toTermDefinition namespaceToModuleName,
toTermDefinition termToHaskell,
toTermDefinition tryInferTypeOf,
toTermDefinition typeToHaskell]
-- | Add namespaces from a set of names to existing namespaces
addNamespacesToNamespaces :: TBinding (Namespaces H.ModuleName -> S.Set Name -> Namespaces H.ModuleName)
addNamespacesToNamespaces = define "addNamespacesToNamespaces" $
doc "Add namespaces from a set of names to existing namespaces" $
lambda "ns0" $ lambda "names" $ lets [
"newNamespaces">: Sets.fromList (Maybes.cat (Lists.map Names.namespaceOf (Sets.toList (var "names")))),
"toModuleName">: lambda "namespace" $
wrap H._ModuleName (Formatting.capitalize @@ Lists.last (Strings.splitOn (string ".") (unwrap _Namespace @@ var "namespace"))),
"newMappings">: Maps.fromList (Lists.map (lambda "ns_" $ pair (var "ns_") (var "toModuleName" @@ var "ns_")) (Sets.toList (var "newNamespaces")))] $
record _Namespaces [
_Namespaces_focus>>: project _Namespaces _Namespaces_focus @@ var "ns0",
_Namespaces_mapping>>: Maps.union (project _Namespaces _Namespaces_mapping @@ var "ns0") (var "newMappings")]
-- | Build namespaces for a test group including encoded term references
buildNamespacesForTestGroup :: TBinding (Module -> TestGroup -> Graph -> Either String (Namespaces H.ModuleName))
buildNamespacesForTestGroup = define "buildNamespacesForTestGroup" $
doc "Build namespaces for a test group including encoded term references" $
lambda "mod" $ lambda "tgroup" $ lambda "graph_" $ lets [
"testCases_">: collectTestCases @@ var "tgroup",
"testTerms">: Lists.concat (Lists.map extractTestTerms (var "testCases_")),
"testBindings">: Lists.map
(lambda "term" $
record _Binding [
_Binding_name>>: wrap _Name (string "_test_"),
_Binding_term>>: var "term",
_Binding_type>>: nothing])
(var "testTerms"),
"tempModule">: record _Module [
_Module_namespace>>: Module.moduleNamespace (var "mod"),
_Module_definitions>>: Lists.map ("b" ~> Module.definitionTerm (Module.termDefinition
(Core.bindingName $ var "b") (Core.bindingTerm $ var "b")
(Core.bindingType $ var "b")))
(var "testBindings"),
_Module_termDependencies>>: project _Module _Module_termDependencies @@ var "mod",
_Module_typeDependencies>>: project _Module _Module_typeDependencies @@ var "mod",
_Module_description>>: project _Module _Module_description @@ var "mod"]] $
Eithers.bind
(Eithers.bimap
(lambda "ic" $ ShowError.error_ @@ Ctx.inContextObject (var "ic"))
(lambda "a" $ var "a")
(HaskellUtilsSource.namespacesForModule @@ var "tempModule" @@ asTerm Lexical.emptyContext @@ var "graph_"))
(lambda "baseNamespaces" $ lets [
"encodedNames">: Sets.unions (Lists.map (lambda "t" $ extractEncodedTermVariableNames @@ var "graph_" @@ var "t") (var "testTerms"))] $
right (addNamespacesToNamespaces @@ var "baseNamespaces" @@ var "encodedNames"))
-- | Build the complete test module using a TestCodec
buildTestModuleWithCodec :: TBinding (TestCodec -> Module -> TestGroup -> String -> Namespaces H.ModuleName -> String)
buildTestModuleWithCodec = define "buildTestModuleWithCodec" $
doc "Build the complete test module using a TestCodec" $
lambda "codec" $ lambda "testModule" $ lambda "testGroup" $ lambda "testBody" $ lambda "namespaces" $ lets [
"ns_">: Module.moduleNamespace (var "testModule"),
"specNs">: wrap _Namespace (Strings.cat2 (unwrap _Namespace @@ var "ns_") (string "Spec")),
"moduleNameString">: project _TestCodec _TestCodec_formatModuleName @@ var "codec" @@ var "specNs",
"groupName_">: project _TestGroup _TestGroup_name @@ var "testGroup",
"domainImports">: project _TestCodec _TestCodec_findImports @@ var "codec" @@ Sets.empty,
"standardImports">: list [
string "import Hydra.Kernel",
string "import qualified Test.Hspec as H",
string "import qualified Data.List as L",
string "import qualified Data.Map as M",
string "import qualified Data.Set as S",
string "import qualified Data.Maybe as Y"],
"allImports">: Lists.concat2 (var "standardImports") (var "domainImports"),
"debugComments">: list [
string "-- DEBUG: Focus namespace = (see generated module)",
string "-- DEBUG: Namespace mappings: (see generated module)"],
"header">: Strings.intercalate (string "\n") (Lists.concat (list [
list [
Strings.cat2 (string "-- ") (asTerm Constants.warningAutoGeneratedFile),
string ""],
var "debugComments",
list [
string "",
Strings.cat (list [string "module ", var "moduleNameString", string " where"]),
string ""],
var "allImports",
list [
string "",
string "spec :: H.Spec",
Strings.cat (list [string "spec = H.describe ", Literals.showString (var "groupName_"), string " $ do"])]]))] $
Strings.cat (list [var "header", string "\n", var "testBody", string "\n"])
-- | Collect variable names from encoded terms within a single term node
collectNames :: TBinding (Graph -> S.Set Name -> Term -> S.Set Name)
collectNames = define "collectNames" $
doc "Collect variable names from encoded terms within a single term node" $
lambda "graf" $ lambda "names" $ lambda "t" $
Logic.ifElse (Schemas.isEncodedTerm @@ (Rewriting.deannotateTerm @@ var "t"))
(Eithers.either_
(lambda "_" $ var "names")
(lambda "decodedTerm" $
Sets.union (var "names") (Rewriting.termDependencyNames @@ true @@ true @@ true @@ var "decodedTerm"))
(Eithers.bimap
(lambda "_e" $ var "_e")
(lambda "_a" $ var "_a")
(decoderFor _Term @@ var "graf" @@ var "t")))
(var "names")
-- | Collect all test cases from a test group (recursively)
collectTestCases :: TBinding (TestGroup -> [TestCaseWithMetadata])
collectTestCases = define "collectTestCases" $
doc "Collect all test cases from a test group recursively" $
lambda "tg" $
Lists.concat2
(project _TestGroup _TestGroup_cases @@ var "tg")
(Lists.concat (Lists.map collectTestCases (project _TestGroup _TestGroup_subgroups @@ var "tg")))
-- | Check if a term contains any trivially polymorphic sub-terms
containsTriviallyPolymorphic :: TBinding (Term -> Bool)
containsTriviallyPolymorphic = define "containsTriviallyPolymorphic" $
doc "Check if a term contains any trivially polymorphic sub-terms" $
lambda "term" $
cases _Term (var "term") (Just false) [
_Term_list>>: lambda "xs" $
Logic.or (Lists.null (var "xs"))
(Lists.foldl (binaryFunction Logic.or) false (Lists.map containsTriviallyPolymorphic (var "xs"))),
_Term_set>>: lambda "s" $
Logic.or (Sets.null (var "s"))
(Lists.foldl (binaryFunction Logic.or) false (Lists.map containsTriviallyPolymorphic (Sets.toList (var "s")))),
_Term_map>>: lambda "m" $
Logic.or (Maps.null (var "m"))
(Logic.or
(Lists.foldl (binaryFunction Logic.or) false (Lists.map containsTriviallyPolymorphic (Maps.keys (var "m"))))
(Lists.foldl (binaryFunction Logic.or) false (Lists.map containsTriviallyPolymorphic (Lists.map (lambda "p" $ Pairs.second (var "p")) (Maps.toList (var "m")))))),
_Term_maybe>>: lambda "mx" $
Maybes.maybe true containsTriviallyPolymorphic (var "mx"),
_Term_either>>: lambda "_" $ true,
_Term_union>>: lambda "inj" $
containsTriviallyPolymorphic @@ (project _Field _Field_term @@ (project _Injection _Injection_field @@ var "inj")),
_Term_pair>>: lambda "p" $
Logic.or
(containsTriviallyPolymorphic @@ Pairs.first (var "p"))
(containsTriviallyPolymorphic @@ Pairs.second (var "p")),
_Term_record>>: lambda "rec" $
Lists.foldl (binaryFunction Logic.or) false
(Lists.map (lambda "f" $ containsTriviallyPolymorphic @@ (project _Field _Field_term @@ var "f"))
(project _Record _Record_fields @@ var "rec")),
_Term_application>>: lambda "app" $
Logic.or
(containsTriviallyPolymorphic @@ (Core.applicationFunction (var "app")))
(containsTriviallyPolymorphic @@ (Core.applicationArgument (var "app")))]
-- | Extract all variable names from term-encoded terms in a given term
extractEncodedTermVariableNames :: TBinding (Graph -> Term -> S.Set Name)
extractEncodedTermVariableNames = define "extractEncodedTermVariableNames" $
doc "Extract all variable names from term-encoded terms in a given term" $
lambda "graf" $ lambda "term" $
Rewriting.foldOverTerm @@ inject _TraversalOrder _TraversalOrder_pre unit @@ (collectNames @@ var "graf") @@ Sets.empty @@ var "term"
-- | Extract terms from a test case
extractTestTerms :: TBinding (TestCaseWithMetadata -> [Term])
extractTestTerms = define "extractTestTerms" $
doc "Extract input and output terms from a test case" $
lambda "tcm" $
cases _TestCase (project _TestCaseWithMetadata _TestCaseWithMetadata_case @@ var "tcm") (Just (list ([] :: [TTerm Term]))) [
_TestCase_delegatedEvaluation>>: lambda "delCase" $
list [
project _DelegatedEvaluationTestCase _DelegatedEvaluationTestCase_input @@ var "delCase",
project _DelegatedEvaluationTestCase _DelegatedEvaluationTestCase_output @@ var "delCase"]]
-- | Find necessary imports for Haskell based on referenced names
findHaskellImports :: TBinding (Namespaces H.ModuleName -> S.Set Name -> [String])
findHaskellImports = define "findHaskellImports" $
doc "Find necessary imports for Haskell based on referenced names" $
lambda "namespaces" $ lambda "names_" $ lets [
"mapping_">: project _Namespaces _Namespaces_mapping @@ var "namespaces",
"filtered">: Maps.filterWithKey
(lambda "ns_" $ lambda "_v" $
Logic.not (Equality.equal
(Lists.head (Strings.splitOn (string "hydra.test.") (unwrap _Namespace @@ var "ns_")))
(string "")))
(var "mapping_")] $
Lists.map
(lambda "entry" $ Strings.cat (list [
string "import qualified ",
Strings.intercalate (string ".") (Lists.map Formatting.capitalize (Strings.splitOn (string ".") (unwrap _Namespace @@ Pairs.first (var "entry")))),
string " as ",
unwrap H._ModuleName @@ Pairs.second (var "entry")]))
(Maps.toList (var "filtered"))
-- | Generate a Haskell test file for a test group, with type inference and namespace building
generateHaskellTestFile :: TBinding (Module -> TestGroup -> Graph -> Either String (String, String))
generateHaskellTestFile = define "generateHaskellTestFile" $
doc "Generate a Haskell test file for a test group, with type inference and namespace building" $
lambda "testModule" $ lambda "testGroup" $ lambda "g" $
Eithers.bind
(buildNamespacesForTestGroup @@ var "testModule" @@ var "testGroup" @@ var "g")
(lambda "namespaces" $
generateTestFileWithCodec @@ (haskellTestCodec @@ var "namespaces") @@ var "testModule" @@ var "testGroup" @@ var "namespaces" @@ var "g")
-- | Generate a single test case using a TestCodec
generateTestCaseWithCodec :: TBinding (Graph -> Namespaces H.ModuleName -> TestCodec -> Int -> TestCaseWithMetadata -> Either String [String])
generateTestCaseWithCodec = define "generateTestCaseWithCodec" $
doc "Generate a single test case using a TestCodec" $
lambda "g" $ lambda "namespaces" $ lambda "codec" $ lambda "depth" $ lambda "tcm" $ lets [
"name_">: project _TestCaseWithMetadata _TestCaseWithMetadata_name @@ var "tcm",
"tcase">: project _TestCaseWithMetadata _TestCaseWithMetadata_case @@ var "tcm"] $
cases _TestCase (var "tcase") (Just (right (list ([] :: [TTerm String])))) [
_TestCase_delegatedEvaluation>>: lambda "delCase" $ lets [
"input_">: project _DelegatedEvaluationTestCase _DelegatedEvaluationTestCase_input @@ var "delCase",
"output_">: project _DelegatedEvaluationTestCase _DelegatedEvaluationTestCase_output @@ var "delCase",
"formattedName">: project _TestCodec _TestCodec_formatTestName @@ var "codec" @@ var "name_",
"continuationIndent">: Math.add (Math.mul (var "depth") (int32 2)) (int32 4)] $
Eithers.bind
(project _TestCodec _TestCodec_encodeTerm @@ var "codec" @@ var "input_" @@ var "g")
(lambda "inputCode" $
Eithers.bind
(project _TestCodec _TestCodec_encodeTerm @@ var "codec" @@ var "output_" @@ var "g")
(lambda "outputCode" $
Eithers.bind
(generateTypeAnnotationFor @@ var "g" @@ var "namespaces" @@ var "input_" @@ var "output_")
(lambda "typeAnnotation" $ lets [
"indentedInputCode">: indentContinuationLines @@ var "continuationIndent" @@ var "inputCode",
"indentedOutputCode">: indentContinuationLines @@ var "continuationIndent" @@ var "outputCode",
"finalOutputCode">: Maybes.maybe (var "indentedOutputCode")
(lambda "anno" $ Strings.cat2 (var "indentedOutputCode") (var "anno"))
(var "typeAnnotation")] $
right (list [
Strings.cat (list [string "H.it ", Literals.showString (var "formattedName"), string " $ H.shouldBe"]),
Strings.cat (list [string " (", var "indentedInputCode", string ")"]),
Strings.cat (list [string " (", var "finalOutputCode", string ")"])]))))]
-- | Generate a test file using a TestCodec
generateTestFileWithCodec :: TBinding (TestCodec -> Module -> TestGroup -> Namespaces H.ModuleName -> Graph -> Either String (String, String))
generateTestFileWithCodec = define "generateTestFileWithCodec" $
doc "Generate a complete test file using a TestCodec" $
lambda "codec" $ lambda "testModule" $ lambda "testGroup" $ lambda "namespaces" $ lambda "g" $
Eithers.map
(lambda "testBody" $ lets [
"testModuleContent">: buildTestModuleWithCodec @@ var "codec" @@ var "testModule" @@ var "testGroup" @@ var "testBody" @@ var "namespaces",
"ext">: unwrap _FileExtension @@ (project _TestCodec _TestCodec_fileExtension @@ var "codec"),
"ns_">: Module.moduleNamespace (var "testModule"),
"specNs">: wrap _Namespace (Strings.cat2 (unwrap _Namespace @@ var "ns_") (string "Spec")),
"filePath">: Names.namespaceToFilePath @@ Util.caseConventionPascal @@ (wrap _FileExtension (var "ext")) @@ var "specNs"] $
pair (var "filePath") (var "testModuleContent"))
(generateTestGroupHierarchy @@ var "g" @@ var "namespaces" @@ var "codec" @@ int32 1 @@ var "testGroup")
-- | Generate test hierarchy preserving the structure with H.describe blocks for subgroups
generateTestGroupHierarchy :: TBinding (Graph -> Namespaces H.ModuleName -> TestCodec -> Int -> TestGroup -> Either String String)
generateTestGroupHierarchy = define "generateTestGroupHierarchy" $
doc "Generate test hierarchy preserving the structure with H.describe blocks for subgroups" $
lambda "g" $ lambda "namespaces" $ lambda "codec" $ lambda "depth" $ lambda "testGroup" $ lets [
"cases_">: project _TestGroup _TestGroup_cases @@ var "testGroup",
"subgroups">: project _TestGroup _TestGroup_subgroups @@ var "testGroup",
"indent">: Strings.fromList (Lists.replicate (Math.mul (var "depth") (int32 2)) (int32 32))] $ -- space char
Eithers.bind
(Eithers.mapList
(lambda "tc" $ generateTestCaseWithCodec @@ var "g" @@ var "namespaces" @@ var "codec" @@ var "depth" @@ var "tc")
(var "cases_"))
(lambda "testCaseLinesRaw" $ lets [
"testCaseLines">: Lists.map
(lambda "lines_" $ Lists.map (lambda "line" $ Strings.cat2 (var "indent") (var "line")) (var "lines_"))
(var "testCaseLinesRaw"),
"testCasesStr">: Strings.intercalate (string "\n") (Lists.concat (var "testCaseLines"))] $
Eithers.map
(lambda "subgroupsStr" $
Strings.cat (list [
var "testCasesStr",
Logic.ifElse (Logic.or (Equality.equal (var "testCasesStr") (string "")) (Equality.equal (var "subgroupsStr") (string "")))
(string "")
(string "\n"),
var "subgroupsStr"]))
(Eithers.map
(lambda "blocks" $ Strings.intercalate (string "\n") (var "blocks"))
(Eithers.mapList
(lambda "subgroup" $ lets [
"groupName_">: project _TestGroup _TestGroup_name @@ var "subgroup"] $
Eithers.map
(lambda "content" $
Strings.cat (list [
var "indent",
string "H.describe ",
Literals.showString (var "groupName_"),
string " $ do\n",
var "content"]))
(generateTestGroupHierarchy @@ var "g" @@ var "namespaces" @@ var "codec" @@ Math.add (var "depth") (int32 1) @@ var "subgroup"))
(var "subgroups"))))
-- | Generate a type annotation for polymorphic output values
generateTypeAnnotationFor :: TBinding (Graph -> Namespaces H.ModuleName -> Term -> Term -> Either String (Maybe String))
generateTypeAnnotationFor = define "generateTypeAnnotationFor" $
doc "Generate a type annotation for polymorphic output values" $
lambda "g" $ lambda "namespaces" $ lambda "inputTerm" $ lambda "outputTerm" $
Logic.ifElse
(Logic.not (containsTriviallyPolymorphic @@ var "outputTerm"))
(right nothing)
(Maybes.maybe
(right nothing)
(lambda "result" $ lets [
"typeScheme">: Pairs.second (var "result"),
"typ">: project _TypeScheme _TypeScheme_type @@ var "typeScheme",
"schemaVars">: Sets.fromList (Maps.keys (Graph.graphSchemaTypes (var "g"))),
"freeVars">: Sets.toList (Sets.difference (Rewriting.freeVariablesInType @@ var "typ") (var "schemaVars")),
"isEither">: cases _Term (Rewriting.deannotateTerm @@ var "outputTerm") (Just false) [
_Term_either>>: lambda "_" $ true]] $
Logic.ifElse
(Logic.or (var "isEither") (Logic.not (Lists.null (var "freeVars"))))
(lets [
"int32Type">: inject _Type _Type_literal (inject _LiteralType _LiteralType_integer (inject _IntegerType _IntegerType_int32 unit)),
"subst">: Typing.typeSubst (Maps.fromList (Lists.map (lambda "v" $ pair (var "v") (var "int32Type")) (var "freeVars"))),
"groundedType">: Substitution.substInType @@ var "subst" @@ var "typ"] $
Eithers.map
(lambda "typeStr" $ just (Strings.cat2 (string " :: ") (var "typeStr")))
(typeToHaskell @@ var "namespaces" @@ var "groundedType" @@ var "g"))
(right nothing))
(tryInferTypeOf @@ var "g" @@ var "inputTerm"))
-- | Template for Haskell import statements
haskellImportTemplate :: TBinding String
haskellImportTemplate = define "haskellImportTemplate" $
doc "Template for Haskell import statements" $
string "import qualified {namespace} as {alias}"
-- | Template for Haskell test module structure
haskellModuleTemplate :: TBinding String
haskellModuleTemplate = define "haskellModuleTemplate" $
doc "Template for Haskell test module structure" $
Strings.intercalate (string "\n") (list [
Strings.cat2 (string "-- ") (asTerm Constants.warningAutoGeneratedFile),
string "",
string "module {moduleName} where",
string "",
string "{imports}",
string "",
string "spec :: H.Spec",
string "{testGroup}",
string "{testCases}",
string ""])
-- | Template for HSpec test case assertions
haskellTestCaseTemplate :: TBinding String
haskellTestCaseTemplate = define "haskellTestCaseTemplate" $
doc "Template for HSpec test case assertions" $
Strings.intercalate (string "\n") (list [
string " H.it {name} $ H.shouldBe",
string " ({input})",
string " ({output})",
string ""])
-- | Create a Haskell TestCodec that uses the real Haskell coder
haskellTestCodec :: TBinding (Namespaces H.ModuleName -> TestCodec)
haskellTestCodec = define "haskellTestCodec" $
doc "Create a Haskell TestCodec that uses the real Haskell coder" $
lambda "namespaces" $
record _TestCodec [
_TestCodec_language>>: Coders.languageName_ (string "haskell"),
_TestCodec_fileExtension>>: wrap _FileExtension (string "hs"),
_TestCodec_encodeTerm>>: termToHaskell @@ var "namespaces",
_TestCodec_encodeType>>: typeToHaskell @@ var "namespaces",
_TestCodec_formatTestName>>: lambda "n" $ var "n", -- identity
_TestCodec_formatModuleName>>: namespaceToModuleName,
_TestCodec_testCaseTemplate>>: haskellTestCaseTemplate,
_TestCodec_testGroupTemplate>>: haskellTestGroupTemplate,
_TestCodec_moduleTemplate>>: haskellModuleTemplate,
_TestCodec_importTemplate>>: haskellImportTemplate,
_TestCodec_findImports>>: findHaskellImports @@ var "namespaces"]
-- | Template for HSpec test group description
haskellTestGroupTemplate :: TBinding String
haskellTestGroupTemplate = define "haskellTestGroupTemplate" $
doc "Template for HSpec test group description" $
string "spec = H.describe {groupName} $ do"
-- | Indent continuation lines of a multi-line string
indentContinuationLines :: TBinding (Int -> String -> String)
indentContinuationLines = define "indentContinuationLines" $
doc "Indent continuation lines of a multi-line string" $
lambda "n" $ lambda "s" $
Strings.intercalate
(Strings.cat2 (string "\n") (Strings.fromList (Lists.replicate (var "n") (int32 32))))
(Strings.splitOn (string "\n") (var "s"))
-- | Convert namespace to Haskell module name
namespaceToModuleName :: TBinding (Namespace -> String)
namespaceToModuleName = define "namespaceToModuleName" $
doc "Convert namespace to Haskell module name" $
lambda "ns_" $
Strings.intercalate (string ".") (Lists.map Formatting.capitalize (Strings.splitOn (string ".") (unwrap _Namespace @@ var "ns_")))
-- | Convert a Hydra term to a Haskell expression string
termToHaskell :: TBinding (Namespaces H.ModuleName -> Term -> Graph -> Either String String)
termToHaskell = define "termToHaskell" $
doc "Convert a Hydra term to a Haskell expression string" $
lambda "namespaces" $ lambda "term" $ lambda "g" $
Eithers.bimap
("ic" ~> ShowError.error_ @@ Ctx.inContextObject (var "ic"))
(Serialization.printExpr <.> Serialization.parenthesize <.> HaskellSerdeSource.expressionToExpr)
(HaskellCoderSource.encodeTerm @@ int32 0 @@ var "namespaces" @@ var "term" @@ asTerm Lexical.emptyContext @@ var "g")
-- | Try to infer the type of a term, returning Nothing if inference fails
tryInferTypeOf :: TBinding (Graph -> Term -> Maybe (Term, TypeScheme))
tryInferTypeOf = define "tryInferTypeOf" $
doc "Try to infer the type of a term, returning Nothing if inference fails" $
lambda "g" $ lambda "term" $
Eithers.either_
(lambda "_" $ nothing)
(lambda "result" $ just (Pairs.first (var "result")))
(Inference.inferTypeOf @@ asTerm Lexical.emptyContext @@ var "g" @@ var "term")
-- | Convert a Hydra type to a Haskell type expression string
typeToHaskell :: TBinding (Namespaces H.ModuleName -> Type -> Graph -> Either String String)
typeToHaskell = define "typeToHaskell" $
doc "Convert a Hydra type to a Haskell type expression string" $
lambda "namespaces" $ lambda "typ" $ lambda "g" $
Eithers.bimap
("ic" ~> ShowError.error_ @@ Ctx.inContextObject (var "ic"))
(Serialization.printExpr <.> Serialization.parenthesize <.> HaskellSerdeSource.typeToExpr)
(HaskellCoderSource.encodeType @@ var "namespaces" @@ var "typ" @@ asTerm Lexical.emptyContext @@ var "g")