packages feed

aihc-cabal-syntax-1.0.0.1: src/Aihc/Cabal/Internal/Types.hs

{-# LANGUAGE OverloadedStrings #-}
-- | The data types of the library. "Aihc.Cabal" exports all of them.
--
-- The types keep the data of a Cabal file as the file gives it. Names,
-- modules, and options are 'Text'. Paths are 'FilePath', because the caller
-- joins them with directories. A scalar field that is absent is 'Nothing'.
-- A list field that is absent is @[]@. Defaults are applied by
-- 'Aihc.Cabal.resolvePackage', not by the parser.
module Aihc.Cabal.Internal.Types where

import Data.List (nub)
import Data.List.NonEmpty (NonEmpty)
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Data.Text (Text)
import qualified Data.Text as T
import Aihc.Cabal.Internal.Version (Version, VersionRange)

-- * Positions and diagnostics

-- | A source position. Rows start at 1. Columns start at 1 and count UTF-8
-- bytes, as in Cabal-syntax.
data Position = Position
  { positionRow :: !Int
  , positionColumn :: !Int
  } deriving (Eq, Ord, Show)

-- | An error or a warning from the parser. A check of the complete package
-- has no position.
data Diagnostic = Diagnostic
  { diagnosticPosition :: Maybe Position
  , diagnosticMessage :: Text
  } deriving (Eq, Show)

-- | The result of a parse. The parser stops at the first error, so the
-- error side holds one diagnostic. Warnings are present with an error and
-- with a value. The parser gives one warning: @Legacy cabal file@ for a
-- file that gets a Cabal-syntax patch.
data ParseResult a = ParseResult
  { parseWarnings :: [Diagnostic]
  , parseValue :: Either Diagnostic a
  } deriving (Eq, Show)

-- * Field values

-- | One line of a field value. The text has no leading spaces or line break.
data FieldLine = FieldLine
  { fieldLinePosition :: !Position
  , fieldLineText :: !Text
  } deriving (Eq, Show)

-- | A field value as the source file gives it. The position is the position
-- of the field name. The lines are in source order. Comment lines and blank
-- lines are not included.
--
-- Field values occur in 'packageFields', 'extraFields', and
-- 'sourceRepositoryFields'. The parser does not check these values.
data FieldValue = FieldValue
  { fieldPosition :: !Position
  , fieldLines :: [FieldLine]
  } deriving (Eq, Show)

-- | The lines of a field value, joined with line breaks. For a free text
-- field, such as @description@, use 'Aihc.Cabal.packageFieldText'.
fieldText :: FieldValue -> Text
fieldText = T.intercalate "\n" . map fieldLineText . fieldLines

-- * Dependencies

-- | A library of a package. 'MainLibrary' is the library without a name.
data LibraryTarget = MainLibrary | NamedLibrary Text deriving (Eq, Ord, Show)

-- | One entry of a @build-depends@ or @setup-depends@ field.
--
-- Before @cabal-version@ 3.4, a dependency on the name of an internal
-- library of the same package refers to that library. The parser changes
-- such a dependency to the package name and the 'NamedLibrary' target.
data Dependency = Dependency
  { dependencyPackage :: Text
  , dependencyRange :: VersionRange
  -- ^ 'Aihc.Cabal.anyVersion' when the field gives no range.
  , dependencyLibraries :: NonEmpty LibraryTarget
  -- ^ The libraries in @pkg:{a, b}@ syntax. Without that syntax, the main
  -- library.
  } deriving (Eq, Show)

-- | Module visibility for a mixin or a package dependency.
data ModuleRenaming
  = DefaultRenaming
  -- ^ All exposed modules with their names.
  | ModuleRenaming [(Text, Text)]
  -- ^ Only the given modules, each with a new name.
  | HidingRenaming [Text]
  -- ^ All exposed modules except the given modules.
  deriving (Eq, Show)

-- | A Backpack mixin from a @mixins@ field.
data Mixin = Mixin
  { mixinPackage :: Text
  , mixinLibrary :: LibraryTarget
  , mixinProvides :: ModuleRenaming
  , mixinRequires :: ModuleRenaming
  } deriving (Eq, Show)

-- | One entry of a @build-tool-depends@ or a legacy @build-tools@ field.
data ToolDependency = ToolDependency
  { toolPackage :: Maybe Text
  -- ^ The package in @pkg:exe@ syntax. 'Nothing' for a @build-tools@ entry,
  -- which names only the tool.
  , toolName :: Text
  , toolRange :: VersionRange
  -- ^ 'Aihc.Cabal.anyVersion' when the field gives no range.
  } deriving (Eq, Show)

-- * Flags and conditions

-- | A @flag@ section. Flag names are lower case.
data Flag = Flag
  { flagName :: Text
  , flagDefault :: Bool
  -- ^ The @default@ field. 'True' when the field is absent.
  , flagManual :: Bool
  -- ^ The @manual@ field. 'False' when the field is absent.
  , flagDescription :: Text
  -- ^ The @description@ field after the Cabal free text rules. @\"\"@ when
  -- the field is absent.
  } deriving (Eq, Show)

-- | Flag values by flag name.
type FlagAssignment = Map Text Bool

-- | The condition of an @if@ or @elif@ section.
data Condition
  = Literal Bool
  | OS Text
  -- ^ @os(name)@. The comparison ignores case.
  | Arch Text
  -- ^ @arch(name)@. The comparison ignores case.
  | Impl Text VersionRange
  -- ^ @impl(compiler range)@. Without a range, 'Aihc.Cabal.anyVersion'.
  -- The comparison of the compiler name ignores case.
  | FlagValue Text
  -- ^ @flag(name)@. The name is lower case.
  | Not Condition
  | And Condition Condition
  | Or Condition Condition
  deriving (Eq, Show)

-- | Data with conditional parts, as one section of a Cabal file gives it.
-- 'unconditional' holds the fields outside @if@ sections. 'branches' holds
-- the @if@ sections in source order.
data Conditional a = Conditional
  { unconditional :: a
  , branches :: [Branch a]
  } deriving (Eq, Show)

-- | An @if@ section with its optional @else@ or @elif@ part. An @elif@ part
-- becomes an @else@ part with one branch.
data Branch a = Branch
  { condition :: Condition
  , whenTrue :: Conditional a
  , whenFalse :: Maybe (Conditional a)
  } deriving (Eq, Show)

-- * Components

-- | The kind and name of a component section.
data ComponentKind
  = Library LibraryTarget
  | Executable Text
  | TestSuite Text
  | Benchmark Text
  | ForeignLibrary Text
  deriving (Eq, Ord, Show)

-- | A component of a package. In a t'Package', the data is a
-- @t'Conditional' t'BuildInfo'@. In a t'ResolvedPackage', the data is a
-- t'BuildInfo'.
data Component a = Component
  { componentKind :: ComponentKind
  , componentData :: a
  } deriving (Eq, Show)

-- | The build fields of one section level, before defaults. Lists keep
-- their source order. A field that the Cabal format version of the file
-- does not support is absent.
--
-- The 'Semigroup' instance merges two parts as Cabal-syntax merges build
-- information. Values in the second part come after values in the first
-- part. Some lists do not keep duplicate values. A scalar value in the
-- second part replaces the scalar value in the first part. The parser uses
-- the merge for common stanza imports. 'Aihc.Cabal.resolvePackage' uses it
-- for active conditional branches.
data BuildInfo = BuildInfo
  { buildable :: Maybe Bool
  -- ^ @buildable@. Two values merge with 'Bool' and.
  , sourceDirs :: [FilePath]
  -- ^ @hs-source-dirs@, then the older @hs-source-dir@ values.
  , exposedModules :: [Text]
  -- ^ @exposed-modules@. Only a library has this field.
  , otherModules :: [Text]
  -- ^ @other-modules@.
  , autogenModules :: [Text]
  -- ^ @autogen-modules@, from @cabal-version@ 2.0.
  , virtualModules :: [Text]
  -- ^ @virtual-modules@, from @cabal-version@ 2.2.
  , mainIs :: Maybe FilePath
  -- ^ @main-is@. Only an executable, a test suite, or a benchmark has this
  -- field.
  , defaultLanguage :: Maybe Text
  -- ^ @default-language@, from @cabal-version@ 1.10. 'Nothing' means the
  -- Haskell98 default of Cabal.
  , otherLanguages :: [Text]
  -- ^ @other-languages@, from @cabal-version@ 1.10.
  , extensions :: [Text]
  -- ^ @default-extensions@, from @cabal-version@ 1.10.
  , otherExtensions :: [Text]
  -- ^ @other-extensions@.
  , legacyExtensions :: [Text]
  -- ^ The older @extensions@ field. It is an error from @cabal-version@ 3.0.
  -- Use it with 'extensions' when you select compiler extensions.
  , dependencies :: [Dependency]
  -- ^ @build-depends@.
  , mixins :: [Mixin]
  -- ^ @mixins@, from @cabal-version@ 2.0.
  , buildTools :: [ToolDependency]
  -- ^ The legacy @build-tools@ values, then the @build-tool-depends@ values.
  , cSources :: [FilePath]
  -- ^ @c-sources@.
  , cxxSources :: [FilePath]
  -- ^ @cxx-sources@, from @cabal-version@ 2.2.
  , asmSources :: [FilePath]
  -- ^ @asm-sources@, from @cabal-version@ 3.0.
  , cmmSources :: [FilePath]
  -- ^ @cmm-sources@, from @cabal-version@ 3.0.
  , jsSources :: [FilePath]
  -- ^ @js-sources@.
  , includeDirs :: [FilePath]
  -- ^ @include-dirs@.
  , includes :: [FilePath]
  -- ^ @includes@.
  , installIncludes :: [FilePath]
  -- ^ @install-includes@.
  , autogenIncludes :: [FilePath]
  -- ^ @autogen-includes@, from @cabal-version@ 3.0.
  , extraLibDirs :: [FilePath]
  -- ^ @extra-lib-dirs@.
  , extraLibDirsStatic :: [FilePath]
  -- ^ @extra-lib-dirs-static@, from @cabal-version@ 3.8.
  , frameworks :: [Text]
  -- ^ @frameworks@.
  , extraFrameworkDirs :: [FilePath]
  -- ^ @extra-framework-dirs@.
  , cppOptions :: [Text]
  -- ^ @cpp-options@. Options keep all values, also duplicates.
  , ccOptions :: [Text]
  -- ^ @cc-options@.
  , cxxOptions :: [Text]
  -- ^ @cxx-options@, from @cabal-version@ 2.2.
  , ghcOptions :: [Text]
  -- ^ @ghc-options@.
  , extraFields :: Map Text [FieldValue]
  -- ^ All other fields of the section, by lower case name, with their values
  -- in source order. This includes @x-@ fields, for example
  -- @x-aihc-lir-sources@. Use 'fieldText' to read a value.
  } deriving (Eq, Show)

-- | A t'BuildInfo' without fields. Use it with record syntax to make a value.
emptyBuildInfo :: BuildInfo
emptyBuildInfo = BuildInfo
  { buildable = Nothing, sourceDirs = [], exposedModules = [], otherModules = []
  , autogenModules = [], virtualModules = [], mainIs = Nothing, defaultLanguage = Nothing
  , otherLanguages = [], extensions = [], otherExtensions = [], legacyExtensions = []
  , dependencies = [], mixins = [], buildTools = [], cSources = [], cxxSources = [], asmSources = []
  , cmmSources = [], jsSources = [], includeDirs = [], includes = [], installIncludes = []
  , autogenIncludes = [], extraLibDirs = [], extraLibDirsStatic = [], frameworks = []
  , extraFrameworkDirs = [], cppOptions = [], ccOptions = [], cxxOptions = []
  , ghcOptions = [], extraFields = Map.empty
  }

-- | Merge two parts. See the 'Semigroup' instance of t'BuildInfo'.
mergeBuildInfo :: BuildInfo -> BuildInfo -> BuildInfo
mergeBuildInfo a b = BuildInfo
  { buildable = case (buildable a, buildable b) of
      (Nothing, y) -> y
      (x, Nothing) -> x
      (Just x, Just y) -> Just (x && y)
  , sourceDirs = unique sourceDirs
  , exposedModules = both exposedModules
  , otherModules = unique otherModules
  , autogenModules = unique autogenModules
  , virtualModules = unique virtualModules
  , mainIs = prefer (mainIs a) (mainIs b)
  , defaultLanguage = prefer (defaultLanguage a) (defaultLanguage b)
  , otherLanguages = unique otherLanguages
  , extensions = unique extensions
  , otherExtensions = unique otherExtensions
  , legacyExtensions = unique legacyExtensions
  , dependencies = unique dependencies
  , mixins = both mixins
  , buildTools = both buildTools
  , cSources = unique cSources
  , cxxSources = unique cxxSources
  , asmSources = unique asmSources
  , cmmSources = unique cmmSources
  , jsSources = unique jsSources
  , includeDirs = unique includeDirs
  , includes = unique includes
  , installIncludes = unique installIncludes
  , autogenIncludes = unique autogenIncludes
  , extraLibDirs = unique extraLibDirs
  , extraLibDirsStatic = unique extraLibDirsStatic
  , frameworks = unique frameworks
  , extraFrameworkDirs = unique extraFrameworkDirs
  , cppOptions = both cppOptions
  , ccOptions = both ccOptions
  , cxxOptions = both cxxOptions
  , ghcOptions = both ghcOptions
  , extraFields = Map.unionWith (++) (extraFields a) (extraFields b)
  }
  where
    both f = f a ++ f b
    unique f = nub (both f)
    prefer x Nothing = x
    prefer _ y = y

instance Semigroup BuildInfo where
  (<>) = mergeBuildInfo

instance Monoid BuildInfo where
  mempty = emptyBuildInfo

-- * Packages

-- | A @source-repository@ section. Repeated fields keep their values in
-- source order. The values keep quotation marks.
data SourceRepository = SourceRepository
  { sourceRepositoryKind :: Text
  -- ^ The section argument, for example @head@ or @this@.
  , sourceRepositoryFields :: Map Text [FieldValue]
  } deriving (Eq, Show)

-- | A parsed package description. Conditions are not evaluated. Use
-- 'Aihc.Cabal.resolvePackage' to get a t'ResolvedPackage'.
data Package = Package
  { packageName :: Text
  , packageVersion :: Version
  , cabalVersion :: Version
  -- ^ The Cabal format version that Cabal-syntax uses for the file. A range
  -- such as @>=1.9@ gives the version that Cabal-syntax selects, here 1.10.
  -- A file without a @cabal-version@ field has version 1.0.
  , buildType :: Text
  -- ^ The build type that Cabal-syntax uses: @Simple@, @Configure@,
  -- @Custom@, @Make@, or @Hooks@. Without a @build-type@ field, the value is
  -- @Simple@ from @cabal-version@ 2.2 and @Custom@ before it. A
  -- @custom-setup@ section gives @Custom@.
  , packageFlags :: [Flag]
  -- ^ The @flag@ sections in source order.
  , packageComponents :: [Component (Conditional BuildInfo)]
  -- ^ The component sections in source order.
  , packageFields :: Map Text [FieldValue]
  -- ^ All fields before the first section, by lower case name, with their
  -- values in source order. This includes @name@, @version@,
  -- @cabal-version@, and @build-type@. Use 'Aihc.Cabal.packageFieldText'
  -- to read a value.
  , packageSourceRepositories :: [SourceRepository]
  -- ^ The @source-repository@ sections in source order.
  , packageSetupDependencies :: Maybe [Dependency]
  -- ^ The @setup-depends@ values of a @custom-setup@ section. 'Nothing'
  -- without that section.
  } deriving (Eq, Show)

-- | The target that 'Aihc.Cabal.evaluateCondition' compares with. The
-- library does not read the host platform or an installed compiler.
data Environment = Environment
  { targetOS :: Text
  -- ^ For @os(...)@, for example @linux@. Case does not matter.
  , targetArch :: Text
  -- ^ For @arch(...)@, for example @x86_64@. Case does not matter.
  , compiler :: Text
  -- ^ For @impl(...)@, for example @ghc@. Case does not matter.
  , compilerVersion :: Version
  -- ^ The version that the compiler reports.
  } deriving (Eq, Show)

-- | A package after condition evaluation and defaults. See
-- 'Aihc.Cabal.resolvePackage'.
data ResolvedPackage = ResolvedPackage
  { resolvedFlags :: FlagAssignment
  -- ^ The value of each declared flag.
  , resolvedComponents :: [Component BuildInfo]
  -- ^ All components, in source order. This includes components with
  -- @buildable: False@. The caller selects the components to build.
  } deriving (Eq, Show)

-- | The contents of a @.buildinfo@ file, as a configure script writes it.
data HookedBuildInfo = HookedBuildInfo
  { hookedLibrary :: Maybe BuildInfo
  -- ^ The fields before the first @executable@ field.
  , hookedExecutables :: Map Text BuildInfo
  -- ^ The fields after each @executable: name@ field.
  } deriving (Eq, Show)