hydra-haskell-0.17.0: src/main/haskell/Hydra/Haskell/Utils.hs
-- Note: this is an automatically generated file. Do not edit.
-- | Utilities for working with Haskell syntax trees
module Hydra.Haskell.Utils where
import qualified Hydra.Analysis as Analysis
import qualified Hydra.Ast as Ast
import qualified Hydra.Coders as Coders
import qualified Hydra.Core as Core
import qualified Hydra.Docs as Docs
import qualified Hydra.Error.Checking as Checking
import qualified Hydra.Error.Core as ErrorCore
import qualified Hydra.Error.File as ErrorFile
import qualified Hydra.Error.Packaging as ErrorPackaging
import qualified Hydra.Error.System as ErrorSystem
import qualified Hydra.Errors as Errors
import qualified Hydra.File as File
import qualified Hydra.Formatting as Formatting
import qualified Hydra.Graph as Graph
import qualified Hydra.Haskell.Language as Language
import qualified Hydra.Haskell.Syntax as Syntax
import qualified Hydra.Json.Model as Model
import qualified Hydra.Overlay.Haskell.Lib.Eithers as Eithers
import qualified Hydra.Overlay.Haskell.Lib.Equality as Equality
import qualified Hydra.Overlay.Haskell.Lib.Lists as Lists
import qualified Hydra.Overlay.Haskell.Lib.Logic as Logic
import qualified Hydra.Overlay.Haskell.Lib.Maps as Maps
import qualified Hydra.Overlay.Haskell.Lib.Math as Math
import qualified Hydra.Overlay.Haskell.Lib.Optionals as Optionals
import qualified Hydra.Overlay.Haskell.Lib.Pairs as Pairs
import qualified Hydra.Overlay.Haskell.Lib.Sets as Sets
import qualified Hydra.Overlay.Haskell.Lib.Strings as Strings
import qualified Hydra.Names as Names
import qualified Hydra.Packaging as Packaging
import qualified Hydra.Parsing as Parsing
import qualified Hydra.Paths as Paths
import qualified Hydra.Query as Query
import qualified Hydra.Relational as Relational
import qualified Hydra.Strip as Strip
import qualified Hydra.System as System
import qualified Hydra.Tabular as Tabular
import qualified Hydra.Testing as Testing
import qualified Hydra.Time as Time
import qualified Hydra.Topology as Topology
import qualified Hydra.Typed as Typed
import qualified Hydra.Typing as Typing
import qualified Hydra.Util as Util
import qualified Hydra.Validation as Validation
import qualified Hydra.Variants as Variants
import Prelude hiding (Enum, Ordering, decodeFloat, encodeFloat, fail, map, pure, sum)
import qualified Data.Scientific as Sci
import qualified Data.Set as S
-- | Create an application pattern from a name and argument patterns
applicationPattern :: Syntax.Name -> [Syntax.Pattern] -> Syntax.Pattern
applicationPattern name args =
Syntax.PatternApplication (Syntax.ApplicationPattern {
Syntax.applicationPatternName = name,
Syntax.applicationPatternArgs = args})
-- | Generate a Haskell name reference for a Hydra element
elementReference :: Util.ModuleNames Syntax.ModuleName -> Core.Name -> Syntax.Name
elementReference namespaces name =
let namespacePair = Util.moduleNamesFocus namespaces
gname = Pairs.first namespacePair
gmod = Syntax.unModuleName (Pairs.second namespacePair)
namespacesMap = Util.moduleNamesMapping namespaces
qname = Names.qualifyName name
local = Util.qualifiedNameLocal qname
escLocal = sanitizeHaskellName local
mns = Util.qualifiedNameModuleName qname
in (Optionals.cases (Util.qualifiedNameModuleName qname) (simpleName local) (\ns -> Optionals.cases (Maps.lookup ns namespacesMap) (simpleName local) (\mn ->
let aliasStr = Syntax.unModuleName mn
in (Logic.ifElse (Equality.equal ns gname) (simpleName escLocal) (rawName (Strings.cat [
aliasStr,
".",
(sanitizeHaskellName local)]))))))
-- | Create a Haskell function application expression
hsapp :: Syntax.Expression -> Syntax.Expression -> Syntax.Expression
hsapp l r =
Syntax.ExpressionApplication (Syntax.ApplicationExpression {
Syntax.applicationExpressionFunction = l,
Syntax.applicationExpressionArgument = r})
-- | Create a Haskell lambda expression
hslambda :: Syntax.Name -> Syntax.Expression -> Syntax.Expression
hslambda name rhs =
Syntax.ExpressionLambda (Syntax.LambdaExpression {
Syntax.lambdaExpressionBindings = [
Syntax.PatternName name],
Syntax.lambdaExpressionInner = rhs})
-- | Create a Haskell literal expression
hslit :: Syntax.Literal -> Syntax.Expression
hslit lit = Syntax.ExpressionLiteral lit
-- | Create a Haskell variable expression from a string
hsvar :: String -> Syntax.Expression
hsvar s = Syntax.ExpressionVariable (rawName s)
-- | Compute the Haskell module namespaces for a Hydra module
namespacesForModule :: Packaging.Module -> t0 -> Graph.Graph -> Either Errors.Error (Util.ModuleNames Syntax.ModuleName)
namespacesForModule mod cx g =
Eithers.bind (Analysis.moduleDependencyModuleNames cx g True True True True mod) (\termNss ->
let knownNss =
Sets.fromList (Optionals.cat (Lists.map Names.moduleNameOf (Lists.concat2 (Maps.keys (Graph.graphSchemaTypes g)) (Maps.keys (Graph.graphBoundTerms g)))))
rawDeclaredNss = Sets.fromList (Lists.map (\dep -> Packaging.moduleDependencyModule dep) (Packaging.moduleDependencies mod))
declaredNss = Sets.fromList (Lists.filter (\ns -> Sets.member ns knownNss) (Sets.toList rawDeclaredNss))
ownNs = Packaging.moduleName mod
nss = Sets.delete ownNs (Sets.union termNss declaredNss)
ns = ownNs
segmentsOf = \namespace -> Strings.splitOn "." (Packaging.unModuleName namespace)
aliasFromSuffix =
\segs -> \n ->
let dropCount = Math.sub (Lists.length segs) n
suffix = Lists.drop dropCount segs
capitalizedSuffix = Lists.map Formatting.capitalize suffix
in (Syntax.ModuleName (Strings.cat capitalizedSuffix))
toModuleName = \namespace -> aliasFromSuffix (segmentsOf namespace) 1
focusPair = (ns, (toModuleName ns))
nssAsList = Sets.toList nss
segsMap = Maps.fromList (Lists.map (\nm -> (nm, (segmentsOf nm))) nssAsList)
maxSegs =
Lists.foldl (\a -> \b -> Logic.ifElse (Equality.gt a b) a b) 1 (Lists.map (\nm -> Lists.length (segmentsOf nm)) nssAsList)
initialState = Maps.fromList (Lists.map (\nm -> (nm, 1)) nssAsList)
segsFor = \nm -> Optionals.fromOptional [] (Maps.lookup nm segsMap)
takenFor = \state -> \nm -> Optionals.fromOptional 1 (Maps.lookup nm state)
growStep =
\state -> \_ign ->
let aliasEntries =
Lists.map (\nm ->
let segs = segsFor nm
n = takenFor state nm
segCount = Lists.length segs
aliasStr = Syntax.unModuleName (aliasFromSuffix segs n)
in (nm, (n, (segCount, aliasStr)))) nssAsList
aliasCounts =
Lists.foldl (\m -> \e ->
let k = Pairs.second (Pairs.second (Pairs.second e))
in (Maps.insert k (Math.add 1 (Optionals.fromOptional 0 (Maps.lookup k m))) m)) Maps.empty aliasEntries
aliasMinSegs =
Lists.foldl (\m -> \e ->
let segCount = Pairs.first (Pairs.second (Pairs.second e))
k = Pairs.second (Pairs.second (Pairs.second e))
existing = Maps.lookup k m
in (Maps.insert k (Optionals.cases existing segCount (\prev -> Logic.ifElse (Equality.lt segCount prev) segCount prev)) m)) Maps.empty aliasEntries
aliasMinSegsCount =
Lists.foldl (\m -> \e ->
let segCount = Pairs.first (Pairs.second (Pairs.second e))
k = Pairs.second (Pairs.second (Pairs.second e))
minSegs = Optionals.fromOptional segCount (Maps.lookup k aliasMinSegs)
in (Logic.ifElse (Equality.equal segCount minSegs) (Maps.insert k (Math.add 1 (Optionals.fromOptional 0 (Maps.lookup k m))) m) m)) Maps.empty aliasEntries
in (Maps.fromList (Lists.map (\e ->
let nm = Pairs.first e
n = Pairs.first (Pairs.second e)
segCount = Pairs.first (Pairs.second (Pairs.second e))
aliasStr = Pairs.second (Pairs.second (Pairs.second e))
count = Optionals.fromOptional 0 (Maps.lookup aliasStr aliasCounts)
minSegs = Optionals.fromOptional segCount (Maps.lookup aliasStr aliasMinSegs)
minSegsCount = Optionals.fromOptional 0 (Maps.lookup aliasStr aliasMinSegsCount)
canGrow =
Logic.and (Equality.gt count 1) (Logic.and (Equality.gt segCount n) (Logic.or (Equality.gt segCount minSegs) (Equality.gt minSegsCount 1)))
newN = Logic.ifElse canGrow (Math.add n 1) n
in (nm, newN)) aliasEntries))
finalState = Lists.foldl growStep initialState (Lists.replicate maxSegs ())
resultMap = Maps.fromList (Lists.map (\nm -> (nm, (aliasFromSuffix (segsFor nm) (takenFor finalState nm)))) nssAsList)
in (Right (Util.ModuleNames {
Util.moduleNamesFocus = focusPair,
Util.moduleNamesMapping = resultMap})))
-- | Generate an accessor name for a newtype wrapper (e.g., 'unFoo' for Foo)
newtypeAccessorName :: Core.Name -> String
newtypeAccessorName name = Strings.cat2 "un" (Names.localNameOf name)
-- | Create a raw Haskell name from a string without sanitization
rawName :: String -> Syntax.Name
rawName n =
Syntax.NameNormal (Syntax.QualifiedName {
Syntax.qualifiedNameQualifiers = [],
Syntax.qualifiedNameUnqualified = (Syntax.NamePart n)})
-- | Generate a Haskell name for a record field accessor
recordFieldReference :: Util.ModuleNames Syntax.ModuleName -> Core.Name -> Core.Name -> Syntax.Name
recordFieldReference namespaces sname fname =
let fnameStr = Core.unName fname
qname = Names.qualifyName sname
ns = Util.qualifiedNameModuleName qname
typeNameStr = typeNameForRecord sname
decapitalized = Formatting.decapitalize typeNameStr
capitalized = Formatting.capitalize fnameStr
nm = Strings.cat2 decapitalized capitalized
qualName =
Util.QualifiedName {
Util.qualifiedNameModuleName = ns,
Util.qualifiedNameLocal = nm}
unqualName = Names.unqualifyName qualName
in (elementReference namespaces unqualName)
-- | Sanitize a string to be a valid Haskell identifier, escaping reserved words
sanitizeHaskellName :: String -> String
sanitizeHaskellName = Formatting.sanitizeWithUnderscores Language.reservedWords
-- | Create a sanitized Haskell name from a string
simpleName :: String -> Syntax.Name
simpleName arg_ = rawName (sanitizeHaskellName arg_)
-- | Create a simple value binding (e.g., 'foo = expr' or 'foo = expr where ...')
simpleValueBinding :: Syntax.Name -> Syntax.Expression -> Maybe Syntax.LocalBindings -> Syntax.ValueBinding
simpleValueBinding hname rhs bindings =
let pat =
Syntax.PatternApplication (Syntax.ApplicationPattern {
Syntax.applicationPatternName = hname,
Syntax.applicationPatternArgs = []})
rightHandSide = Syntax.RightHandSide rhs
in (Syntax.ValueBindingSimple (Syntax.SimpleValueBinding {
Syntax.simpleValueBindingPattern = pat,
Syntax.simpleValueBindingRhs = rightHandSide,
Syntax.simpleValueBindingLocalBindings = bindings,
Syntax.simpleValueBindingComments = Nothing}))
-- | Convert a list of types into a nested type application
toTypeApplication :: [Syntax.Type] -> Syntax.Type
toTypeApplication types =
let dummyType =
Syntax.TypeVariable (Syntax.NameNormal (Syntax.QualifiedName {
Syntax.qualifiedNameQualifiers = [],
Syntax.qualifiedNameUnqualified = (Syntax.NamePart "")}))
app =
\l -> Optionals.fromOptional dummyType (Optionals.map (\p -> Logic.ifElse (Lists.null (Pairs.second p)) (Pairs.first p) (Syntax.TypeApplication (Syntax.ApplicationType {
Syntax.applicationTypeContext = (app (Pairs.second p)),
Syntax.applicationTypeArgument = (Pairs.first p)}))) (Lists.uncons l))
in (app (Lists.reverse types))
-- | Extract the local type name from a fully qualified record type name
typeNameForRecord :: Core.Name -> String
typeNameForRecord sname =
let snameStr = Core.unName sname
parts = Strings.splitOn "." snameStr
in (Optionals.fromOptional snameStr (Lists.maybeLast parts))
-- | Generate a Haskell name for a union variant constructor, with disambiguation
unionFieldReference :: S.Set Core.Name -> Util.ModuleNames Syntax.ModuleName -> Core.Name -> Core.Name -> Syntax.Name
unionFieldReference boundNames namespaces sname fname =
let fnameStr = Core.unName fname
qname = Names.qualifyName sname
ns = Util.qualifiedNameModuleName qname
typeNameStr = typeNameForRecord sname
capitalizedTypeName = Formatting.capitalize typeNameStr
capitalizedFieldName = Formatting.capitalize fnameStr
deconflict =
\name ->
let tname =
Names.unqualifyName (Util.QualifiedName {
Util.qualifiedNameModuleName = ns,
Util.qualifiedNameLocal = name})
in (Logic.ifElse (Sets.member tname boundNames) (deconflict (Strings.cat2 name "_")) name)
nm = deconflict (Strings.cat2 capitalizedTypeName capitalizedFieldName)
qualName =
Util.QualifiedName {
Util.qualifiedNameModuleName = ns,
Util.qualifiedNameLocal = nm}
unqualName = Names.unqualifyName qualName
in (elementReference namespaces unqualName)
-- | Unpack nested forall types into a list of type variables and the inner type
unpackForallType :: Core.Type -> ([Core.Name], Core.Type)
unpackForallType t =
case (Strip.deannotateType t) of
Core.TypeForall v0 ->
let v = Core.forallTypeParameter v0
tbody = Core.forallTypeBody v0
recursiveResult = unpackForallType tbody
vars = Pairs.first recursiveResult
finalType = Pairs.second recursiveResult
in (Lists.cons v vars, finalType)
_ -> ([], t)