1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082
|
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Data.GI.CodeGen.Code
( Code
, ModuleInfo(moduleCode, sectionDocs)
, ModuleFlag(..)
, CodeGen
, ExcCodeGen
, CGError
, genCode
, evalCodeGen
, writeModuleTree
, listModuleTree
, codeToText
, transitiveModuleDeps
, minBaseVersion
, BaseVersion(..)
, showBaseVersion
, registerNSDependency
, qualified
, getDeps
, recurseWithAPIs
, handleCGExc
, printCGError
, notImplementedError
, badIntroError
, missingInfoError
, indent
, increaseIndent
, bline
, line
, blank
, group
, comment
, cppIf
, CPPGuard(..)
, hsBoot
, submodule
, setLanguagePragmas
, addLanguagePragma
, setGHCOptions
, setModuleFlags
, setModuleMinBase
, getFreshTypeVariable
, resetTypeVariableScope
, exportModule
, exportDecl
, export
, HaddockSection(..)
, NamedSection(..)
, addSectionFormattedDocs
, prependSectionFormattedDocs
, findAPI
, getAPI
, findAPIByName
, getAPIs
, getC2HMap
, config
, currentModule
) where
#if !MIN_VERSION_base(4,8,0)
import Control.Applicative ((<$>))
import Data.Monoid (Monoid(..))
#endif
#if MIN_VERSION_base(4,18,0)
import Control.Monad (forM, unless, when)
#endif
import Control.Monad.Reader
import Control.Monad.State.Strict
import Control.Monad.Except
import qualified Data.Foldable as F
import Data.Maybe (fromMaybe, catMaybes, mapMaybe)
#if !MIN_VERSION_base(4,13,0)
import Data.Monoid ((<>), mempty)
#endif
import qualified Data.Map.Strict as M
import Data.Sequence (ViewL ((:<)), viewl, (|>))
import qualified Data.Sequence as Seq
import qualified Data.Semigroup as Sem
import qualified Data.Set as Set
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Lazy.Builder as B
import qualified Data.Text.Lazy as LT
import GHC.Stack (HasCallStack)
import System.Directory (createDirectoryIfMissing)
import System.FilePath (joinPath, takeDirectory)
import Data.GI.CodeGen.API (API, Name(..))
import Data.GI.CodeGen.Config (Config(..))
import {-# SOURCE #-} Data.GI.CodeGen.CtoHaskellMap (cToHaskellMap,
Hyperlink)
import Data.GI.CodeGen.GtkDoc (CRef)
import Data.GI.CodeGen.ModulePath (ModulePath(..), dotModulePath, (/.))
import Data.GI.CodeGen.Type (Type(..))
import Data.GI.CodeGen.Util (tshow, terror, padTo, utf8WriteFile)
import Data.GI.CodeGen.ProjectInfo (authors, license, maintainers)
-- | Set of CPP conditionals understood by the code generator.
data CPPConditional = CPPIf Text -- ^ #if Foo
deriving (Eq, Show, Ord)
-- | The generated `Code` is a sequence of `CodeToken`s.
newtype Code = Code (Seq.Seq CodeToken)
deriving (Sem.Semigroup, Monoid, Eq, Show, Ord)
-- | Initializes a code block to the empty sequence.
emptyCode :: Code
emptyCode = Code Seq.empty
-- | Checks whether the given code block is empty.
isCodeEmpty :: Code -> Bool
isCodeEmpty (Code seq) = Seq.null seq
-- | A block of code consisting of a single token.
codeSingleton :: CodeToken -> Code
codeSingleton t = Code (Seq.singleton t)
-- | Possible code tokens.
data CodeToken
= Line Text -- ^ A single line, indented to current indentation.
| Indent Code -- ^ Indented region.
| Group Code -- ^ A grouped set of lines
| Comment [Text] -- ^ A (possibly multi line) comment
| IncreaseIndent -- ^ Increase the indentation for the rest
-- of the lines in the group.
| CPPBlock CPPConditional Code -- ^ A block of code guarded by the
-- given CPP conditional
deriving (Eq, Ord, Show)
type Deps = Set.Set Text
-- | Subsection of the haddock documentation where the export should
-- be located, or alternatively the toplevel section.
data HaddockSection = ToplevelSection
| Section NamedSection
| NamedSubsection NamedSection Text
deriving (Show, Eq, Ord)
-- | Known subsections. The ordering here is the ordering in which
-- they will appear in the haddocks.
data NamedSection = MethodSection
| PropertySection
| SignalSection
| EnumSection
| FlagSection
deriving (Show, Eq, Ord)
-- | Symbol to export.
type SymbolName = Text
-- | Possible exports for a given module. Every export type
-- constructor has two parameters: the section of the haddocks where
-- it should appear, and the symbol name to export in the export list
-- of the module.
data Export = Export {
exportType :: ExportType -- ^ Which kind of export.
, exportSymbol :: SymbolName -- ^ Actual symbol to export.
, exportGuards :: [CPPConditional] -- ^ Protect the export by the
-- given CPP export guards.
} deriving (Show, Eq, Ord)
-- | Possible types of exports.
data ExportType = ExportSymbol HaddockSection -- ^ An export in the
-- given haddock section.
| ExportTypeDecl -- ^ A type declaration.
| ExportModule -- ^ Reexport of a whole module.
deriving (Show, Eq, Ord)
-- | Information on a generated module.
data ModuleInfo = ModuleInfo {
modulePath :: ModulePath -- ^ Full module name: ["Gtk", "Label"].
, moduleCode :: Code -- ^ Generated code for the module.
, bootCode :: Code -- ^ Interfaces going into the .hs-boot file.
, submodules :: M.Map Text ModuleInfo -- ^ Indexed by the relative
-- module name.
, moduleDeps :: Deps -- ^ Set of dependencies for this module.
, moduleExports :: Seq.Seq Export -- ^ Exports for the module.
, qualifiedImports :: Set.Set ModulePath -- ^ Qualified (source) imports.
, modulePragmas :: Set.Set Text -- ^ Set of language pragmas for the module.
, moduleGHCOpts :: Set.Set Text -- ^ GHC options for compiling the module.
, moduleFlags :: Set.Set ModuleFlag -- ^ Flags for the module.
, sectionDocs :: M.Map HaddockSection Text -- ^ Documentation
-- for the different sections in
-- the module.
, moduleMinBase :: BaseVersion -- ^ Minimal version of base the
-- module will work on.
}
-- | Flags for module code generation.
data ModuleFlag = ImplicitPrelude -- ^ Use the standard prelude,
-- instead of the haskell-gi-base short one.
deriving (Show, Eq, Ord)
-- | Minimal version of base supported by a given module.
data BaseVersion = Base47 -- ^ 4.7.0
| Base48 -- ^ 4.8.0
deriving (Show, Eq, Ord)
-- | A `Text` representation of the given base version bound.
showBaseVersion :: BaseVersion -> Text
showBaseVersion Base47 = "4.7"
showBaseVersion Base48 = "4.8"
-- | Generate the empty module.
emptyModule :: ModulePath -> ModuleInfo
emptyModule m = ModuleInfo { modulePath = m
, moduleCode = emptyCode
, bootCode = emptyCode
, submodules = M.empty
, moduleDeps = Set.empty
, moduleExports = Seq.empty
, qualifiedImports = Set.empty
, modulePragmas = Set.empty
, moduleGHCOpts = Set.empty
, moduleFlags = Set.empty
, sectionDocs = M.empty
, moduleMinBase = Base47
}
-- | Information for the code generator.
data CodeGenConfig = CodeGenConfig {
hConfig :: Config -- ^ Ambient config.
, loadedAPIs :: M.Map Name API -- ^ APIs available to the generator.
, c2hMap :: M.Map CRef Hyperlink -- ^ Map from C references
-- to Haskell symbols.
}
-- | Set of errors for the code generator.
data CGError = CGErrorNotImplemented Text
| CGErrorBadIntrospectionInfo Text
| CGErrorMissingInfo Text
deriving (Show)
-- | Temporaty state for the code generator.
data CGState = CGState {
cgsCPPConditionals :: [CPPConditional] -- ^ Active CPP conditionals,
-- outermost condition first.
, cgsNextAvailableTyvar :: NamedTyvar -- ^ Next unused type
-- variable.
}
-- | The name for a type variable.
data NamedTyvar = SingleCharTyvar Char
-- ^ A single variable type variable: 'a', 'b', etc...
| IndexedTyvar Text Integer
-- ^ An indexed type variable: 'a17', 'key1', ...
-- | Clean slate for `CGState`.
emptyCGState :: CGState
emptyCGState = CGState { cgsCPPConditionals = []
, cgsNextAvailableTyvar = SingleCharTyvar 'a'
}
-- | The base type for the code generator monad. Generators that
-- cannot throw errors are parametric in the exception type 'excType'.
type CodeGen excType a =
ReaderT CodeGenConfig (StateT (CGState, ModuleInfo) (Except excType)) a
-- | Code generators that can throw errors.
type ExcCodeGen a = CodeGen CGError a
-- | Run a `CodeGen` with given `Config` and initial state, returning
-- either the resulting exception, or the result and final module info.
runCodeGen :: CodeGen e a -> CodeGenConfig -> (CGState, ModuleInfo) ->
(Either e (a, ModuleInfo))
runCodeGen cg cfg state =
dropCGState <$> runExcept (runStateT (runReaderT cg cfg) state)
where dropCGState :: (a, (CGState, ModuleInfo)) -> (a, ModuleInfo)
dropCGState (x, (_, m)) = (x, m)
-- | This is useful when we plan run a subgenerator, and `mconcat` the
-- result to the original structure later.
cleanInfo :: ModuleInfo -> ModuleInfo
cleanInfo info = info { moduleCode = emptyCode, submodules = M.empty,
bootCode = emptyCode, moduleExports = Seq.empty,
qualifiedImports = Set.empty,
sectionDocs = M.empty, moduleMinBase = Base47 }
-- | Run the given code generator using the state and config of an
-- ambient CodeGen, but without adding the generated code to
-- `moduleCode`, instead returning it explicitly.
recurseCG :: CodeGen e a -> CodeGen e (a, Code)
recurseCG = recurseWithState id
-- | Like `recurseCG`, but we allow for explicitly setting the state
-- of the inner code generator.
recurseWithState :: (CGState -> CGState) -> CodeGen e a
-> CodeGen e (a, Code)
recurseWithState cgsSet cg = do
cfg <- ask
(cgs, oldInfo) <- get
-- Start the subgenerator with no code and no submodules.
let info = cleanInfo oldInfo
case runCodeGen cg cfg (cgsSet cgs, info) of
Left e -> throwError e
Right (r, new) -> put (cgs, mergeInfoState oldInfo new) >>
return (r, moduleCode new)
-- | Like `recurseCG`, giving explicitly the set of loaded APIs and C to
-- Haskell map for the subgenerator.
recurseWithAPIs :: M.Map Name API -> CodeGen e () -> CodeGen e ()
recurseWithAPIs apis cg = do
cfg <- ask
(cgs, oldInfo) <- get
-- Start the subgenerator with no code and no submodules.
let info = cleanInfo oldInfo
cfg' = cfg {loadedAPIs = apis,
c2hMap = cToHaskellMap (M.toList apis)}
case runCodeGen cg cfg' (cgs, info) of
Left e -> throwError e
Right (_, new) -> put (cgs, mergeInfo oldInfo new)
-- | Merge everything but the generated code for the two given `ModuleInfo`.
mergeInfoState :: ModuleInfo -> ModuleInfo -> ModuleInfo
mergeInfoState oldState newState =
let newDeps = Set.union (moduleDeps oldState) (moduleDeps newState)
newSubmodules = M.unionWith mergeInfo (submodules oldState) (submodules newState)
newExports = moduleExports oldState <> moduleExports newState
newImports = qualifiedImports oldState <> qualifiedImports newState
newPragmas = Set.union (modulePragmas oldState) (modulePragmas newState)
newGHCOpts = Set.union (moduleGHCOpts oldState) (moduleGHCOpts newState)
newFlags = Set.union (moduleFlags oldState) (moduleFlags newState)
newBoot = bootCode oldState <> bootCode newState
newDocs = sectionDocs oldState <> sectionDocs newState
newMinBase = max (moduleMinBase oldState) (moduleMinBase newState)
in oldState {moduleDeps = newDeps, submodules = newSubmodules,
moduleExports = newExports, qualifiedImports = newImports,
modulePragmas = newPragmas,
moduleGHCOpts = newGHCOpts, moduleFlags = newFlags,
bootCode = newBoot, sectionDocs = newDocs,
moduleMinBase = newMinBase }
-- | Merge the infos, including code too.
mergeInfo :: ModuleInfo -> ModuleInfo -> ModuleInfo
mergeInfo oldInfo newInfo =
let info = mergeInfoState oldInfo newInfo
in info { moduleCode = moduleCode oldInfo <> moduleCode newInfo }
-- | Add the given submodule to the list of submodules of the current
-- module.
addSubmodule :: Text -> ModuleInfo -> (CGState, ModuleInfo)
-> (CGState, ModuleInfo)
addSubmodule modName submodule (cgs, current) =
(cgs, current { submodules = M.insertWith mergeInfo modName submodule (submodules current)})
-- | Run the given CodeGen in order to generate a single submodule of the
-- current module. Note that we do not generate the submodule if the
-- code generator generated no code and the module does not have
-- submodules.
submodule' :: Text -> CodeGen e () -> CodeGen e ()
submodule' modName cg = do
cfg <- ask
(_, oldInfo) <- get
let info = emptyModule (modulePath oldInfo /. modName)
case runCodeGen cg cfg (emptyCGState, info) of
Left e -> throwError e
Right (_, smInfo) -> if isCodeEmpty (moduleCode smInfo) &&
M.null (submodules smInfo)
then return ()
else modify' (addSubmodule modName smInfo)
-- | Run the given CodeGen in order to generate a submodule (specified
-- an an ordered list) of the current module.
submodule :: ModulePath -> CodeGen e () -> CodeGen e ()
submodule (ModulePath []) cg = cg
submodule (ModulePath (m:ms)) cg = submodule' m (submodule (ModulePath ms) cg)
-- | Try running the given `action`, and if it fails run `fallback`
-- instead.
handleCGExc :: (CGError -> CodeGen e a) -> ExcCodeGen a -> CodeGen e a
handleCGExc fallback
action = do
cfg <- ask
(cgs, oldInfo) <- get
let info = cleanInfo oldInfo
case runCodeGen action cfg (cgs, info) of
Left e -> fallback e
Right (r, newInfo) -> do
put (cgs, mergeInfo oldInfo newInfo)
return r
-- | Return the currently loaded set of dependencies.
getDeps :: CodeGen e Deps
getDeps = moduleDeps . snd <$> get
-- | Return the ambient configuration for the code generator.
config :: CodeGen e Config
config = hConfig <$> ask
-- | Return the name of the current module.
currentModule :: CodeGen e Text
currentModule = do
(_, s) <- get
return (dotWithPrefix (modulePath s))
-- | Return the list of APIs available to the generator.
getAPIs :: CodeGen e (M.Map Name API)
getAPIs = loadedAPIs <$> ask
-- | Return the C -> Haskell available to the generator.
getC2HMap :: CodeGen e (M.Map CRef Hyperlink)
getC2HMap = c2hMap <$> ask
-- | Due to the `forall` in the definition of `CodeGen`, if we want to
-- run the monad transformer stack until we get a result, our only
-- option is ignoring the possible error code from `runExcept`. This
-- is perfectly safe, since there is no way to construct a computation
-- in the `CodeGen` monad that throws an exception, due to the higher
-- rank type.
unwrapCodeGen :: CodeGen e a -> CodeGenConfig -> (CGState, ModuleInfo)
-> (a, ModuleInfo)
unwrapCodeGen cg cfg info =
case runCodeGen cg cfg info of
Left _ -> error "unwrapCodeGen:: The impossible happened!"
Right (r, newInfo) -> (r, newInfo)
-- | Run a code generator, and return the information for the
-- generated module together with the return value of the generator.
evalCodeGen :: Config -> M.Map Name API ->
ModulePath -> CodeGen e a -> (a, ModuleInfo)
evalCodeGen cfg apis mPath cg =
let initialInfo = emptyModule mPath
cfg' = CodeGenConfig {hConfig = cfg, loadedAPIs = apis,
c2hMap = cToHaskellMap (M.toList apis)}
in unwrapCodeGen cg cfg' (emptyCGState, initialInfo)
-- | Like `evalCodeGen`, but discard the resulting output value.
genCode :: Config -> M.Map Name API ->
ModulePath -> CodeGen e () -> ModuleInfo
genCode cfg apis mPath cg = snd $ evalCodeGen cfg apis mPath cg
-- | Mark the given dependency as used by the module.
registerNSDependency :: Text -> CodeGen e ()
registerNSDependency name = do
deps <- getDeps
unless (Set.member name deps) $ do
let newDeps = Set.insert name deps
modify' $ \(cgs, s) -> (cgs, s {moduleDeps = newDeps})
-- | Return the transitive set of dependencies, i.e. the union of
-- those of the module and (transitively) its submodules.
transitiveModuleDeps :: ModuleInfo -> Deps
transitiveModuleDeps minfo =
Set.unions (moduleDeps minfo
: map transitiveModuleDeps (M.elems $ submodules minfo))
-- | Given a module name and a symbol in the module (including a
-- proper namespace), return a qualified name for the symbol.
qualified :: ModulePath -> Name -> CodeGen e Text
qualified mp (Name ns s) = do
cfg <- config
-- Make sure the module is listed as a dependency.
when (modName cfg /= ns) $
registerNSDependency ns
(_, minfo) <- get
if mp == modulePath minfo
then return s
else do
qm <- qualifiedImport mp
return (qm <> "." <> s)
-- | Import the given module name qualified (as a source import if the
-- namespace is the same as the current one), and return the name
-- under which the module was imported.
qualifiedImport :: ModulePath -> CodeGen e Text
qualifiedImport mp = do
modify' $ \(cgs, s) -> (cgs, s {qualifiedImports = Set.insert mp (qualifiedImports s)})
return (qualifiedModuleName mp)
-- | Construct a simplified version of the module name, suitable for a
-- qualified import.
qualifiedModuleName :: ModulePath -> Text
qualifiedModuleName (ModulePath [ns, "Objects", o]) = ns <> "." <> o
qualifiedModuleName (ModulePath [ns, "Interfaces", i]) = ns <> "." <> i
qualifiedModuleName (ModulePath [ns, "Structs", s]) = ns <> "." <> s
qualifiedModuleName (ModulePath [ns, "Unions", u]) = ns <> "." <> u
qualifiedModuleName mp = dotModulePath mp
-- | Return the minimal base version supported by the module and all
-- its submodules.
minBaseVersion :: ModuleInfo -> BaseVersion
minBaseVersion minfo =
maximum (moduleMinBase minfo
: map minBaseVersion (M.elems $ submodules minfo))
-- | Print, as a comment, a friendly textual description of the error.
printCGError :: CGError -> CodeGen e ()
printCGError (CGErrorNotImplemented e) = do
comment $ "Not implemented: " <> e
printCGError (CGErrorBadIntrospectionInfo e) =
comment $ "Bad introspection data: " <> e
printCGError (CGErrorMissingInfo e) =
comment $ "Missing info: " <> e
notImplementedError :: Text -> ExcCodeGen a
notImplementedError s = throwError $ CGErrorNotImplemented s
badIntroError :: Text -> ExcCodeGen a
badIntroError s = throwError $ CGErrorBadIntrospectionInfo s
missingInfoError :: Text -> ExcCodeGen a
missingInfoError s = throwError $ CGErrorMissingInfo s
-- | Get a type variable unused in the current scope.
getFreshTypeVariable :: CodeGen e Text
getFreshTypeVariable = do
(cgs@(CGState{cgsNextAvailableTyvar = available}), s) <- get
let (tyvar, next) =
case available of
SingleCharTyvar char -> case char of
'z' -> ("z", IndexedTyvar "a" 0)
-- 'm' is reserved for the MonadIO constraint in signatures
'm' -> ("n", SingleCharTyvar 'o')
c -> (T.singleton c, SingleCharTyvar (toEnum $ fromEnum c + 1))
IndexedTyvar root index -> (root <> tshow index,
IndexedTyvar root (index+1))
put (cgs {cgsNextAvailableTyvar = next}, s)
return tyvar
-- | Introduce a new scope for type variable naming: the next fresh
-- variable will be called 'a'.
resetTypeVariableScope :: CodeGen e ()
resetTypeVariableScope =
modify' (\(cgs, s) -> (cgs {cgsNextAvailableTyvar = SingleCharTyvar 'a'}, s))
-- | Try to find the API associated with a given type, if known.
findAPI :: HasCallStack => Type -> CodeGen e (Maybe API)
findAPI (TInterface n) = Just <$> findAPIByName n
findAPI _ = return Nothing
-- | Find the API associated with a given type. If the API cannot be
-- found this raises an `error`.
getAPI :: HasCallStack => Type -> CodeGen e API
getAPI t = findAPI t >>= \case
Just a -> return a
Nothing -> terror ("Could not resolve type \"" <> tshow t <> "\".")
findAPIByName :: HasCallStack => Name -> CodeGen e API
findAPIByName n@(Name ns _) = do
apis <- getAPIs
case M.lookup n apis of
Just api -> return api
Nothing ->
terror $ "couldn't find API description for " <> ns <> "." <> name n
-- | Add some code to the current generator.
tellCode :: CodeToken -> CodeGen e ()
tellCode c = modify' (\(cgs, s) -> (cgs, s {moduleCode = moduleCode s <>
codeSingleton c}))
-- | Print out a (newline-terminated) line.
line :: Text -> CodeGen e ()
line = tellCode . Line
-- | Print out the given line both to the normal module, and to the
-- HsBoot file.
bline :: Text -> CodeGen e ()
bline l = hsBoot (line l) >> line l
-- | A blank line
blank :: CodeGen e ()
blank = line ""
-- | A (possibly multi line) comment, separated by newlines
comment :: Text -> CodeGen e ()
comment = tellCode . Comment . T.lines
-- | Increase the indent level for code generation.
indent :: CodeGen e a -> CodeGen e a
indent cg = do
(x, code) <- recurseCG cg
tellCode (Indent code)
return x
-- | Increase the indentation level for the rest of the lines in the
-- current group.
increaseIndent :: CodeGen e ()
increaseIndent = tellCode IncreaseIndent
-- | Group a set of related code.
group :: CodeGen e a -> CodeGen e a
group cg = do
(x, code) <- recurseCG cg
tellCode (Group code)
blank
return x
-- | Guard a block of code with @#if@.
cppIfBlock :: Text -> CodeGen e a -> CodeGen e a
cppIfBlock cond cg = do
(x, code) <- recurseWithState addConditional cg
tellCode (CPPBlock (CPPIf cond) code)
blank
return x
where addConditional :: CGState -> CGState
addConditional cgs = cgs {cgsCPPConditionals = CPPIf cond :
cgsCPPConditionals cgs}
-- | Possible features to test via CPP.
data CPPGuard = CPPOverloading -- ^ Enable overloading
| CPPMinVersion Text (Integer, Integer, Integer)
-- ^ Require a specific version of the given package.
-- | Guard a code block with CPP code, such that it is included only
-- if the specified feature is enabled.
cppIf :: CPPGuard -> CodeGen e a -> CodeGen e a
cppIf CPPOverloading = cppIfBlock "defined(ENABLE_OVERLOADING)"
cppIf (CPPMinVersion pkg (a,b,c)) = cppIfBlock $ "MIN_VERSION_" <> pkg <>
"(" <> tshow a <> "," <> tshow b <> "," <> tshow c <> ")"
-- | Write the given code into the .hs-boot file for the current module.
hsBoot :: CodeGen e a -> CodeGen e a
hsBoot cg = do
(x, code) <- recurseCG cg
modify' (\(cgs, s) -> (cgs, s{bootCode = bootCode s <>
addGuards (cgsCPPConditionals cgs) code}))
return x
where addGuards :: [CPPConditional] -> Code -> Code
addGuards [] c = c
addGuards (cond : conds) c = codeSingleton $ CPPBlock cond (addGuards conds c)
-- | Add a export to the current module.
exportPartial :: ([CPPConditional] -> Export) -> CodeGen e ()
exportPartial partial =
modify' $ \(cgs, s) -> (cgs,
let e = partial $ cgsCPPConditionals cgs
in s{moduleExports = moduleExports s |> e})
-- | Reexport a whole module.
exportModule :: SymbolName -> CodeGen e ()
exportModule m = exportPartial (Export ExportModule m)
-- | Add a type declaration-related export.
exportDecl :: SymbolName -> CodeGen e ()
exportDecl d = exportPartial (Export ExportTypeDecl d)
-- | Export a symbol in the given haddock subsection.
export :: HaddockSection -> SymbolName -> CodeGen e ()
export s n = exportPartial (Export (ExportSymbol s) n)
-- | Set the language pragmas for the current module.
setLanguagePragmas :: [Text] -> CodeGen e ()
setLanguagePragmas ps =
modify' $ \(cgs, s) -> (cgs, s{modulePragmas = Set.fromList ps})
-- | Add a language pragma for the current module.
addLanguagePragma :: Text -> CodeGen e ()
addLanguagePragma p =
modify' $ \(cgs, s) -> (cgs, s{modulePragmas =
Set.insert p (modulePragmas s)})
-- | Set the GHC options for compiling this module (in a OPTIONS_GHC pragma).
setGHCOptions :: [Text] -> CodeGen e ()
setGHCOptions opts =
modify' $ \(cgs, s) -> (cgs, s{moduleGHCOpts = Set.fromList opts})
-- | Set the given flags for the module.
setModuleFlags :: [ModuleFlag] -> CodeGen e ()
setModuleFlags flags =
modify' $ \(cgs, s) -> (cgs, s{moduleFlags = Set.fromList flags})
-- | Set the minimum base version supported by the current module.
setModuleMinBase :: BaseVersion -> CodeGen e ()
setModuleMinBase v =
modify' $ \(cgs, s) -> (cgs, s{moduleMinBase = max v (moduleMinBase s)})
-- | Add documentation for a given section.
addSectionFormattedDocs :: HaddockSection -> Text -> CodeGen e ()
addSectionFormattedDocs section docs =
modify' $ \(cgs, s) -> (cgs, s{sectionDocs = M.insertWith (flip (<>))
section docs (sectionDocs s)})
-- | Prepend documentation at the beginning of a given section.
prependSectionFormattedDocs :: HaddockSection -> Text -> CodeGen e ()
prependSectionFormattedDocs section docs =
modify' $ \(cgs, s) -> (cgs, s{sectionDocs = M.insertWith (<>)
section docs (sectionDocs s)})
-- | Format a CPP conditional.
cppCondFormat :: CPPConditional -> (Text, Text)
cppCondFormat (CPPIf c) = ("#if " <> c <> "\n", "#endif\n")
-- | Return a text representation of the `Code`.
codeToText :: Code -> Text
codeToText (Code seq) = LT.toStrict . B.toLazyText $ genCode 0 (viewl seq)
where genCode :: Int -> ViewL CodeToken -> B.Builder
genCode _ Seq.EmptyL = mempty
genCode n (Line s :< rest) = B.fromText (paddedLine n s) <>
genCode n (viewl rest)
genCode n (Indent (Code seq) :< rest) = genCode (n+1) (viewl seq) <>
genCode n (viewl rest)
genCode n (Group (Code seq) :< rest) = genCode n (viewl seq) <>
genCode n (viewl rest)
genCode n (Comment [] :< rest) = genCode n (viewl rest)
genCode n (Comment [s] :< rest) =
B.fromText (paddedLine n ("-- " <> s)) <> genCode n (viewl rest)
genCode n (Comment (l:ls):< rest) =
B.fromText ("{- " <> l <> "\n" <>
paddedLines (n+1) ls <> "-}\n") <> genCode n (viewl rest)
genCode n (CPPBlock cond (Code seq) :< rest) =
let (condBegin, condEnd) = cppCondFormat cond
in B.fromText condBegin <> genCode n (viewl seq) <>
B.fromText condEnd <> genCode n (viewl rest)
genCode n (IncreaseIndent :< rest) = genCode (n+1) (viewl rest)
-- | Pad a line to the given number of leading tabs (with one tab
-- equal to four spaces), and add a newline at the end.
paddedLine :: Int -> Text -> Text
paddedLine n s = T.replicate (n * 4) " " <> s <> "\n"
-- | Pad a set of lines to the given number of leading tabs (with one
-- tab equal to four spaces), and add a newline at the end of each
-- line.
paddedLines :: Int -> [Text] -> Text
paddedLines n ls = mconcat $ map (paddedLine n) ls
-- | Put a (padded) comma at the end of the text.
comma :: Text -> Text
comma s = padTo 40 s <> ","
-- | Format the given export symbol.
formatExport :: (Export -> Text) -> Export -> Text
formatExport formatName export = go (exportGuards export)
where go :: [CPPConditional] -> Text
go [] = (paddedLine 1 . comma . formatName) export
go (c:cs) = let (begin, end) = cppCondFormat c
in begin <> go cs <> end
-- | Format the list of exported modules.
formatExportedModules :: [Export] -> Maybe Text
formatExportedModules [] = Nothing
formatExportedModules exports =
Just . T.concat . map (formatExport (("module " <>) . exportSymbol))
. filter ((== ExportModule) . exportType) $ exports
-- | Format the toplevel exported symbols.
formatToplevel :: [Export] -> Maybe Text
formatToplevel [] = Nothing
formatToplevel exports =
Just . T.concat . map (formatExport exportSymbol)
. filter ((== ExportSymbol ToplevelSection) . exportType) $ exports
-- | Format the type declarations section.
formatTypeDecls :: [Export] -> Maybe Text
formatTypeDecls exports =
let exportedTypes = filter ((== ExportTypeDecl) . exportType) exports
in if exportedTypes == []
then Nothing
else Just . T.unlines $ [ "-- * Exported types"
, T.concat . map ( formatExport exportSymbol )
$ exportedTypes ]
-- | A subsection name, with an optional anchor name.
data Subsection = Subsection { subsectionTitle :: Text
, subsectionAnchor :: Maybe Text
, subsectionDoc :: Maybe Text
} deriving (Eq, Show, Ord)
-- | A subsection with an anchor given by the title and @prefix:title@
-- anchor, and the given documentation.
subsecWithPrefix :: NamedSection -> Text -> Maybe Text -> Subsection
subsecWithPrefix mainSection title doc =
Subsection { subsectionTitle = title
, subsectionAnchor = Just (prefix <> ":" <> title)
, subsectionDoc = doc }
where prefix = case mainSection of
MethodSection -> "method"
PropertySection -> "attr"
SignalSection -> "signal"
EnumSection -> "enum"
FlagSection -> "flag"
-- | User-facing name in the Haddocks for the given main section.
mainSectionName :: NamedSection -> Text
mainSectionName MethodSection = "Methods"
mainSectionName PropertySection = "Properties"
mainSectionName SignalSection = "Signals"
mainSectionName EnumSection = "Enumerations"
mainSectionName FlagSection = "Flags"
-- | Format a given section made of subsections.
formatSection :: M.Map HaddockSection Text -> NamedSection ->
(Set.Set Export, [(Subsection, Export)]) -> Maybe Text
formatSection docs section (sectionExports, subsectionExports) =
if null subsectionExports && Set.null sectionExports
then Nothing
else let docstring = case M.lookup (Section section) docs of
Nothing -> ""
Just s -> formatHaddockComment s
in Just . T.unlines $ [" -- * " <> mainSectionName section
, docstring
, ( T.concat
. map (formatExport exportSymbol)
. Set.toList ) sectionExports
, ( T.unlines
. map formatSubsection
. M.toList ) exportedSubsections]
where
exportedSubsections :: M.Map Subsection (Set.Set Export)
exportedSubsections = foldr extract M.empty subsectionExports
extract :: (Subsection, Export) -> M.Map Subsection (Set.Set Export)
-> M.Map Subsection (Set.Set Export)
extract (subsec, m) secs =
M.insertWith Set.union subsec (Set.singleton m) secs
formatSubsection :: (Subsection, Set.Set Export) -> Text
formatSubsection (subsec, symbols) =
T.unlines [ "-- ** " <> case subsectionAnchor subsec of
Just anchor -> subsectionTitle subsec <>
" #" <> anchor <> "#"
Nothing -> subsectionTitle subsec
, case subsectionDoc subsec of
Just text -> formatHaddockComment text
Nothing -> ""
, ( T.concat
. map (formatExport exportSymbol)
. Set.toList ) symbols]
-- | Format the list of exports into grouped sections.
formatSubsectionExports :: M.Map HaddockSection Text -> [Export] -> [Maybe Text]
formatSubsectionExports docs exports = map (uncurry (formatSection docs))
(M.toAscList collectedExports)
where collectedExports :: M.Map NamedSection (Set.Set Export, [(Subsection, Export)])
collectedExports = foldl classifyExport M.empty exports
classifyExport :: M.Map NamedSection (Set.Set Export, [(Subsection, Export)]) ->
Export ->
M.Map NamedSection (Set.Set Export, [(Subsection, Export)])
classifyExport m export =
let join (snew, exnew) (sold, exold) = (Set.union snew sold,
exnew ++ exold)
in case exportType export of
ExportSymbol hs@(NamedSubsection ms n) ->
let subsec = subsecWithPrefix ms n (M.lookup hs docs)
in M.insertWith join ms (Set.empty, [(subsec, export)]) m
ExportSymbol (Section s) ->
M.insertWith join s (Set.singleton export, []) m
_ -> m
-- | Format the given export list. This is just the inside of the
-- parenthesis.
formatExportList :: M.Map HaddockSection Text -> [Export] -> Text
formatExportList docs exports =
T.unlines . catMaybes $ formatExportedModules exports
: formatToplevel exports
: formatTypeDecls exports
: formatSubsectionExports docs exports
-- | Write down the list of language pragmas.
languagePragmas :: [Text] -> Text
languagePragmas [] = ""
languagePragmas ps = "{-# LANGUAGE " <> T.intercalate ", " ps <> " #-}\n"
-- | Write down the list of GHC options.
ghcOptions :: [Text] -> Text
ghcOptions [] = ""
ghcOptions opts = "{-# OPTIONS_GHC " <> T.intercalate ", " opts <> " #-}\n"
-- | Generate some convenience CPP macros.
cppMacros :: Text
cppMacros = T.unlines
["#if !defined(__HADDOCK_VERSION__)"
, "#define ENABLE_OVERLOADING"
, "#endif"]
-- | Standard fields for every module.
standardFields :: Text
standardFields = T.unlines [ "Copyright : " <> authors
, "License : " <> license
, "Maintainer : " <> maintainers ]
-- | The haddock header for the module, including optionally a description.
moduleHaddock :: Maybe Text -> Text
moduleHaddock Nothing = formatHaddockComment $ standardFields
moduleHaddock (Just description) =
formatHaddockComment $ T.unlines [standardFields, description]
-- | Format the comment with the module documentation.
formatHaddockComment :: Text -> Text
formatHaddockComment doc = let lines = case T.lines doc of
[] -> []
(first:rest) -> ("-- | " <> first) :
map ("-- " <>) rest
in T.unlines lines
-- | Generic module prelude. We reexport all of the submodules.
modulePrelude :: M.Map HaddockSection Text -> Text -> [Export] -> [Text] -> Text
modulePrelude _ name [] [] = "module " <> name <> " () where\n"
modulePrelude docs name exports [] =
"module " <> name <> "\n ( "
<> formatExportList docs exports
<> " ) where\n"
modulePrelude docs name [] reexportedModules =
"module " <> name <> "\n ( "
<> formatExportList docs (map (\m -> Export ExportModule m []) reexportedModules)
<> " ) where\n\n"
<> T.unlines (map ("import " <>) reexportedModules)
modulePrelude docs name exports reexportedModules =
"module " <> name <> "\n ( "
<> formatExportList docs (map (\m -> Export ExportModule m []) reexportedModules)
<> "\n"
<> formatExportList docs exports
<> " ) where\n\n"
<> T.unlines (map ("import " <>) reexportedModules)
-- | Code for loading the needed dependencies. One needs to give the
-- prefix for the namespace being currently generated, modules with
-- this prefix will be imported as {-# SOURCE #-}, and otherwise will
-- be imported normally.
importDeps :: ModulePath -> [ModulePath] -> Text
importDeps (ModulePath prefix) deps = T.unlines . map toImport $ deps
where toImport :: ModulePath -> Text
toImport dep = let impSt = if importSource dep
then "import {-# SOURCE #-} qualified "
else "import qualified "
in impSt <> dotWithPrefix dep <>
" as " <> qualifiedModuleName dep
importSource :: ModulePath -> Bool
importSource (ModulePath [_, "Callbacks"]) = False
importSource (ModulePath mp) = take (length prefix) mp == prefix
-- | Standard imports.
moduleImports :: Text
moduleImports = T.unlines [
"import Data.GI.Base.ShortPrelude"
, "import qualified Data.GI.Base.ShortPrelude as SP"
, "import qualified Data.GI.Base.Overloading as O"
, "import qualified Prelude as P"
, ""
, "import qualified Data.GI.Base.Attributes as GI.Attributes"
, "import qualified Data.GI.Base.BasicTypes as B.Types"
, "import qualified Data.GI.Base.ManagedPtr as B.ManagedPtr"
, "import qualified Data.GI.Base.GArray as B.GArray"
, "import qualified Data.GI.Base.GClosure as B.GClosure"
, "import qualified Data.GI.Base.GError as B.GError"
, "import qualified Data.GI.Base.GHashTable as B.GHT"
, "import qualified Data.GI.Base.GVariant as B.GVariant"
, "import qualified Data.GI.Base.GValue as B.GValue"
, "import qualified Data.GI.Base.GParamSpec as B.GParamSpec"
, "import qualified Data.GI.Base.CallStack as B.CallStack"
, "import qualified Data.GI.Base.Properties as B.Properties"
, "import qualified Data.GI.Base.Signals as B.Signals"
, "import qualified Control.Monad.IO.Class as MIO"
, "import qualified Data.Coerce as Coerce"
, "import qualified Data.Text as T"
, "import qualified Data.Kind as DK"
, "import qualified Data.ByteString.Char8 as B"
, "import qualified Data.Map as Map"
, "import qualified Foreign.Ptr as FP"
, "import qualified GHC.OverloadedLabels as OL"
, "import qualified GHC.Records as R"
, "import qualified Data.Word as DW"
, "import qualified Data.Int as DI"
, "import qualified System.Posix.Types as SPT"
, "import qualified Foreign.C.Types as FCT"]
-- | Like `dotModulePath`, but add a "GI." prefix.
dotWithPrefix :: ModulePath -> Text
dotWithPrefix mp = dotModulePath ("GI" <> mp)
-- | Write to disk the code for a module, under the given base
-- directory. Does not write submodules recursively, for that use
-- `writeModuleTree`.
writeModuleInfo :: Bool -> Maybe FilePath -> ModuleInfo ->
M.Map ModulePath ModuleInfo -> IO ()
writeModuleInfo verbose dirPrefix minfo treeMap = do
let submodulePaths = map (modulePath) (M.elems (submodules minfo))
-- We reexport any submodules.
submoduleExports = map dotWithPrefix submodulePaths
fname = modulePathToFilePath dirPrefix (modulePath minfo) ".hs"
dirname = takeDirectory fname
code = codeToText (moduleCode minfo)
pragmas = languagePragmas (Set.toList $ modulePragmas minfo)
optionsGHC = ghcOptions (Set.toList $ moduleGHCOpts minfo)
prelude = modulePrelude (sectionDocs minfo)
(dotWithPrefix $ modulePath minfo)
(F.toList (moduleExports minfo))
submoduleExports
imports = if ImplicitPrelude `Set.member` moduleFlags minfo
then ""
else moduleImports
pkgRoot = ModulePath (take 1 (modulePathToList $ modulePath minfo))
allImports = transitiveImports minfo treeMap
minimalImports = qualifiedImports minfo
allDeps = importDeps pkgRoot (Set.toList allImports)
minimalDeps = importDeps pkgRoot (Set.toList minimalImports)
deps = T.unlines [
"-- Workaround for https://gitlab.haskell.org/ghc/ghc/-/issues/23392",
"#if MIN_VERSION_base(4,18,0)",
allDeps,
"#else",
minimalDeps,
"#endif"
]
haddock = moduleHaddock (M.lookup ToplevelSection (sectionDocs minfo))
when verbose $ putStrLn ((T.unpack . dotWithPrefix . modulePath) minfo
++ " -> " ++ fname)
createDirectoryIfMissing True dirname
utf8WriteFile fname (T.unlines [pragmas, optionsGHC, haddock, cppMacros,
prelude, imports, deps, code])
when (not . isCodeEmpty $ bootCode minfo) $ do
let bootFName = modulePathToFilePath dirPrefix (modulePath minfo) ".hs-boot"
utf8WriteFile bootFName (genHsBoot minfo)
-- | Collect the transitive set of imports for this module. In
-- principle just importing the set of strictly necessary imports (via
-- qualifiedImports) should be sufficient; the following is a
-- workaround for a GHC bug:
-- https://gitlab.haskell.org/ghc/ghc/-/issues/23392
transitiveImports :: ModuleInfo -> M.Map ModulePath ModuleInfo
-> Set.Set ModulePath
transitiveImports root treeMap = collectImports root Set.empty
where
collectImports :: ModuleInfo -> Set.Set ModulePath -> Set.Set ModulePath
collectImports minfo deps = let
isCallbacks (ModulePath [_, "Callbacks"]) = True
isCallbacks _ = False
-- Deps that we haven't analysed yet.
unseenDeps = Set.filter (\e -> Set.notMember e deps) (qualifiedImports minfo)
-- Make sure we don't try to import ourselves
unrooted = Set.filter (\mp -> mp /= modulePath root) unseenDeps
unseenModules = mapMaybe (\d -> M.lookup d treeMap) (Set.toList unrooted)
-- We don't collect implicit deps from the callbacks module,
-- which is always imported normally (not just the hs-boot)
notCallbacks = filter (not . isCallbacks . modulePath) unseenModules
-- Imports in unseenDeps
depImports = map (\m -> collectImports m (Set.union deps unseenDeps)) notCallbacks
in Set.unions (unrooted : depImports)
-- | Generate the .hs-boot file for the given module.
genHsBoot :: ModuleInfo -> Text
genHsBoot minfo =
cppMacros <>
"module " <> (dotWithPrefix . modulePath) minfo <> " where\n\n" <>
moduleImports <> "\n" <>
codeToText (bootCode minfo)
-- | Construct the filename corresponding to the given module.
modulePathToFilePath :: Maybe FilePath -> ModulePath -> FilePath -> FilePath
modulePathToFilePath dirPrefix (ModulePath mp) ext =
joinPath (fromMaybe "" dirPrefix : "GI" : map T.unpack mp) ++ ext
-- | Write down the code for a module and its submodules to disk under
-- the given base directory. It returns the list of written modules.
writeModuleTree :: Bool -> Maybe FilePath -> ModuleInfo -> IO [Text]
writeModuleTree verbose dirPrefix root = doWriteModuleTree root
where
doWriteModuleTree :: ModuleInfo -> IO [Text]
doWriteModuleTree minfo = do
submodulePaths <- concat <$> forM (M.elems (submodules minfo)) doWriteModuleTree
writeModuleInfo verbose dirPrefix minfo treeMap
return $ (dotWithPrefix (modulePath minfo) : submodulePaths)
treeMap = M.fromList (gatherSubmodules root)
gatherSubmodules :: ModuleInfo -> [(ModulePath, ModuleInfo)]
gatherSubmodules minfo = (modulePath minfo, minfo) :
concatMap gatherSubmodules (M.elems $ submodules minfo)
-- | Return the list of modules `writeModuleTree` would write, without
-- actually writing anything to disk.
listModuleTree :: ModuleInfo -> [Text]
listModuleTree minfo =
let submodulePaths = concatMap listModuleTree (M.elems (submodules minfo))
in dotWithPrefix (modulePath minfo) : submodulePaths
|