diff --git a/.github/workflows/haskell-ci.yml b/.github/workflows/haskell-ci.yml new file mode 100644 index 00000000..48551f0a --- /dev/null +++ b/.github/workflows/haskell-ci.yml @@ -0,0 +1,208 @@ +# This GitHub workflow config has been generated by a script via +# +# haskell-ci 'github' 'cabal.project.ci' +# +# To regenerate the script (for example after adjusting tested-with) run +# +# haskell-ci regenerate +# +# For more information, see https://github.com/haskell-CI/haskell-ci +# +# version: 0.19.20240708 +# +# REGENDATA ("0.19.20240708",["github","cabal.project.ci"]) +# +name: Haskell-CI +on: + - push + - pull_request +jobs: + linux: + name: Haskell-CI - Linux - ${{ matrix.compiler }} + runs-on: ubuntu-20.04 + timeout-minutes: + 60 + container: + image: buildpack-deps:jammy + continue-on-error: ${{ matrix.allow-failure }} + strategy: + matrix: + include: + - compiler: ghc-9.10.1 + compilerKind: ghc + compilerVersion: 9.10.1 + setup-method: ghcup + allow-failure: false + - compiler: ghc-9.8.2 + compilerKind: ghc + compilerVersion: 9.8.2 + setup-method: ghcup + allow-failure: false + - compiler: ghc-9.6.6 + compilerKind: ghc + compilerVersion: 9.6.6 + setup-method: ghcup + allow-failure: false + - compiler: ghc-9.4.8 + compilerKind: ghc + compilerVersion: 9.4.8 + setup-method: ghcup + allow-failure: false + - compiler: ghc-9.2.8 + compilerKind: ghc + compilerVersion: 9.2.8 + setup-method: ghcup + allow-failure: false + fail-fast: false + steps: + - name: apt + run: | + apt-get update + apt-get install -y --no-install-recommends gnupg ca-certificates dirmngr curl git software-properties-common libtinfo5 + mkdir -p "$HOME/.ghcup/bin" + curl -sL https://downloads.haskell.org/ghcup/0.1.30.0/x86_64-linux-ghcup-0.1.30.0 > "$HOME/.ghcup/bin/ghcup" + chmod a+x "$HOME/.ghcup/bin/ghcup" + "$HOME/.ghcup/bin/ghcup" install ghc "$HCVER" || (cat "$HOME"/.ghcup/logs/*.* && false) + "$HOME/.ghcup/bin/ghcup" install cabal 3.12.1.0 || (cat "$HOME"/.ghcup/logs/*.* && false) + env: + HCKIND: ${{ matrix.compilerKind }} + HCNAME: ${{ matrix.compiler }} + HCVER: ${{ matrix.compilerVersion }} + - name: Set PATH and environment variables + run: | + echo "$HOME/.cabal/bin" >> $GITHUB_PATH + echo "LANG=C.UTF-8" >> "$GITHUB_ENV" + echo "CABAL_DIR=$HOME/.cabal" >> "$GITHUB_ENV" + echo "CABAL_CONFIG=$HOME/.cabal/config" >> "$GITHUB_ENV" + HCDIR=/opt/$HCKIND/$HCVER + HC=$("$HOME/.ghcup/bin/ghcup" whereis ghc "$HCVER") + HCPKG=$(echo "$HC" | sed 's#ghc$#ghc-pkg#') + HADDOCK=$(echo "$HC" | sed 's#ghc$#haddock#') + echo "HC=$HC" >> "$GITHUB_ENV" + echo "HCPKG=$HCPKG" >> "$GITHUB_ENV" + echo "HADDOCK=$HADDOCK" >> "$GITHUB_ENV" + echo "CABAL=$HOME/.ghcup/bin/cabal-3.12.1.0 -vnormal+nowrap" >> "$GITHUB_ENV" + HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\d+)\.(\d+)\.(\d+)(\.(\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))') + echo "HCNUMVER=$HCNUMVER" >> "$GITHUB_ENV" + echo "ARG_TESTS=--enable-tests" >> "$GITHUB_ENV" + echo "ARG_BENCH=--enable-benchmarks" >> "$GITHUB_ENV" + echo "HEADHACKAGE=false" >> "$GITHUB_ENV" + echo "ARG_COMPILER=--$HCKIND --with-compiler=$HC" >> "$GITHUB_ENV" + echo "GHCJSARITH=0" >> "$GITHUB_ENV" + env: + HCKIND: ${{ matrix.compilerKind }} + HCNAME: ${{ matrix.compiler }} + HCVER: ${{ matrix.compilerVersion }} + - name: env + run: | + env + - name: write cabal config + run: | + mkdir -p $CABAL_DIR + cat >> $CABAL_CONFIG <> $CABAL_CONFIG < cabal-plan.xz + echo 'f62ccb2971567a5f638f2005ad3173dba14693a45154c1508645c52289714cb2 cabal-plan.xz' | sha256sum -c - + xz -d < cabal-plan.xz > $HOME/.cabal/bin/cabal-plan + rm -f cabal-plan.xz + chmod a+x $HOME/.cabal/bin/cabal-plan + cabal-plan --version + - name: checkout + uses: actions/checkout@v4 + with: + path: source + - name: initial cabal.project for sdist + run: | + touch cabal.project + echo "packages: $GITHUB_WORKSPACE/source/hs-bindgen" >> cabal.project + cat cabal.project + - name: sdist + run: | + mkdir -p sdist + $CABAL sdist all --output-dir $GITHUB_WORKSPACE/sdist + - name: unpack + run: | + mkdir -p unpacked + find sdist -maxdepth 1 -type f -name '*.tar.gz' -exec tar -C $GITHUB_WORKSPACE/unpacked -xzvf {} \; + - name: generate cabal.project + run: | + PKGDIR_hs_bindgen="$(find "$GITHUB_WORKSPACE/unpacked" -maxdepth 1 -type d -regex '.*/hs-bindgen-[0-9.]*')" + echo "PKGDIR_hs_bindgen=${PKGDIR_hs_bindgen}" >> "$GITHUB_ENV" + rm -f cabal.project cabal.project.local + touch cabal.project + touch cabal.project.local + echo "packages: ${PKGDIR_hs_bindgen}" >> cabal.project + echo "package hs-bindgen" >> cabal.project + echo " ghc-options: -Werror=missing-methods" >> cabal.project + cat >> cabal.project <> cabal.project.local + cat cabal.project + cat cabal.project.local + - name: dump install plan + run: | + $CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH --dry-run all + cabal-plan + - name: restore cache + uses: actions/cache/restore@v4 + with: + key: ${{ runner.os }}-${{ matrix.compiler }}-${{ github.sha }} + path: ~/.cabal/store + restore-keys: ${{ runner.os }}-${{ matrix.compiler }}- + - name: install dependencies + run: | + $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks --dependencies-only -j2 all + $CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH --dependencies-only -j2 all + - name: build w/o tests + run: | + $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks all + - name: build + run: | + $CABAL v2-build $ARG_COMPILER $ARG_TESTS $ARG_BENCH all --write-ghc-environment-files=always + - name: cabal check + run: | + cd ${PKGDIR_hs_bindgen} || false + ${CABAL} -vnormal check + - name: haddock + run: | + $CABAL v2-haddock --disable-documentation --haddock-all $ARG_COMPILER --with-haddock $HADDOCK $ARG_TESTS $ARG_BENCH all + - name: unconstrained build + run: | + rm -f cabal.project.local + $CABAL v2-build $ARG_COMPILER --disable-tests --disable-benchmarks all + - name: save cache + uses: actions/cache/save@v4 + if: always() + with: + key: ${{ runner.os }}-${{ matrix.compiler }}-${{ github.sha }} + path: ~/.cabal/store diff --git a/.gitignore b/.gitignore index 1eabfb23..9739e331 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .envrc dist-newstyle/ unversioned +cabal.project.local diff --git a/cabal.project b/cabal.project new file mode 100644 index 00000000..e8522a6f --- /dev/null +++ b/cabal.project @@ -0,0 +1 @@ +packages: hs-bindgen diff --git a/cabal.project.ci b/cabal.project.ci new file mode 100644 index 00000000..dfca401c --- /dev/null +++ b/cabal.project.ci @@ -0,0 +1,4 @@ +packages: hs-bindgen + +package hs-bindgen + ghc-options: -Werror diff --git a/hs-bindgen/CHANGELOG.md b/hs-bindgen/CHANGELOG.md new file mode 100644 index 00000000..2a6ab9d1 --- /dev/null +++ b/hs-bindgen/CHANGELOG.md @@ -0,0 +1,5 @@ +# Revision history for hs-bindgen + +## 0.1.0 -- YYYY-mm-dd + +* First version. Released on an unsuspecting world. diff --git a/hs-bindgen/LICENSE b/hs-bindgen/LICENSE new file mode 100644 index 00000000..0cb6e3c8 --- /dev/null +++ b/hs-bindgen/LICENSE @@ -0,0 +1,29 @@ +Copyright (c) 2024, Well-Typed LLP and Anduril Industries Inc. + + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/hs-bindgen/app/HsBindgen/Cmdline.hs b/hs-bindgen/app/HsBindgen/Cmdline.hs new file mode 100644 index 00000000..45bf6efc --- /dev/null +++ b/hs-bindgen/app/HsBindgen/Cmdline.hs @@ -0,0 +1,87 @@ +module HsBindgen.Cmdline ( + Cmdline(..) + , getCmdline + ) where + +import Options.Applicative + +import HsBindgen.Preprocessor.Render +import HsBindgen.Spec qualified as Unresolved + +{------------------------------------------------------------------------------- + Definition +-------------------------------------------------------------------------------} + +-- | Command line arguments +-- +-- TODO: +-- We might want to support multiple Haskell modules; in this case, we'll +-- probably want to bundle an 'Unresolved.Spec' with a 'FilePath' (output); we +-- might also want to split 'Unresolved.Spec' into a section that would be +-- common to /all/ modules we want to generate, and then specify that only once +-- on the command line. +data Cmdline = Cmdline { + cmdInput :: Unresolved.Spec + , cmdOutput :: FilePath + , cmdRenderOptions :: RenderOptions + } + deriving stock (Show) + +{------------------------------------------------------------------------------- + Top-level +-------------------------------------------------------------------------------} + +getCmdline :: IO Cmdline +getCmdline = execParser opts + where + opts :: ParserInfo Cmdline + opts = info (parseCmdline <**> helper) $ mconcat [ + header "hs-bindgen - generate Haskell bindings from C headers" + ] + +{------------------------------------------------------------------------------- + Parser +-------------------------------------------------------------------------------} + +parseCmdline :: Parser Cmdline +parseCmdline = + Cmdline + <$> parseInput + <*> parseOutput + <*> parseRenderOptions + +parseInput :: Parser Unresolved.Spec +parseInput = + Unresolved.Spec + <$> strOption (mconcat [ + help "Input path to the C header" + , metavar "PATH" + , long "input" + , short 'i' + ]) + <*> strOption (mconcat [ + help "Name of the generated Haskell module" + , metavar "NAME" + , long "module" + , showDefault + , value "Generated" + ]) + +parseOutput :: Parser FilePath +parseOutput = + strOption $ mconcat [ + help "Output path for the Haskell module" + , metavar "PATH" + , long "output" + , short 'o' + ] + +parseRenderOptions :: Parser RenderOptions +parseRenderOptions = + RenderOptions + <$> option auto (mconcat [ + help "Maximum length line" + , long "render-line-length" + , showDefault + , value $ renderLineLength defaultRenderOptions + ]) diff --git a/hs-bindgen/app/Main.hs b/hs-bindgen/app/Main.hs new file mode 100644 index 00000000..ca7a38d7 --- /dev/null +++ b/hs-bindgen/app/Main.hs @@ -0,0 +1,14 @@ +module Main where + +import HsBindgen.Cmdline +import HsBindgen.Preprocessor (generateModule) +import HsBindgen.Preprocessor.Render (render) +import HsBindgen.Spec qualified as Spec + +main :: IO () +main = do + cmdline <- getCmdline + spec' <- Spec.resolve (cmdInput cmdline) + writeFile (cmdOutput cmdline) $ + render (cmdRenderOptions cmdline) $ + generateModule spec' diff --git a/hs-bindgen/hs-bindgen.cabal b/hs-bindgen/hs-bindgen.cabal new file mode 100644 index 00000000..03f19c6d --- /dev/null +++ b/hs-bindgen/hs-bindgen.cabal @@ -0,0 +1,69 @@ +cabal-version: 3.0 +name: hs-bindgen +version: 0.1.0 +license: BSD-3-Clause +license-file: LICENSE +author: Edsko de Vries +maintainer: edsko@well-typed.com +category: Development +build-type: Simple +synopsis: Generate Haskell bindings from C headers +extra-doc-files: CHANGELOG.md +tested-with: , GHC==9.2.8 + , GHC==9.4.8 + , GHC==9.6.6 + , GHC==9.8.2 + , GHC==9.10.1 + +common lang + ghc-options: + -Wall + -Widentities + -Wprepositive-qualified-module + -Wredundant-constraints + -Wunused-packages + build-depends: + base >= 4.16 && < 4.21 + default-language: + GHC2021 + default-extensions: + DerivingStrategies + DisambiguateRecordFields + +library + import: + lang + exposed-modules: + HsBindgen + HsBindgen.Preprocessor + HsBindgen.Preprocessor.Render + HsBindgen.Spec + other-modules: + HsBindgen.Annotation + HsBindgen.Spec.Resolved + hs-source-dirs: + src + build-depends: + , haskell-src-exts >= 1.23 && < 1.24 + , haskell-src-meta >= 0.8 && < 0.9 + , template-haskell >= 2.18 && < 2.23 + +executable hs-bindgen + import: + lang + main-is: + Main.hs + other-modules: + HsBindgen.Cmdline + build-depends: + -- Internal dependencies + , hs-bindgen + build-depends: + -- External dependencies + -- + -- NOTE: Ideally this should not depend on haskell-src-exts or + -- haskell-src-meta; functionality that requires these libraries should + -- live in the library instead. + , optparse-applicative >= 0.18 && < 0.19 + hs-source-dirs: + app diff --git a/hs-bindgen/src/HsBindgen.hs b/hs-bindgen/src/HsBindgen.hs new file mode 100644 index 00000000..b816ecf7 --- /dev/null +++ b/hs-bindgen/src/HsBindgen.hs @@ -0,0 +1,17 @@ +-- | Generate Haskell bindings from C headers +-- +-- This module is intended to be used when using the tool in TH mode. +module HsBindgen (generateBindingsFor) where + +import Control.Monad.IO.Class (liftIO) +import Language.Haskell.Meta (toDec) +import Language.Haskell.TH + +import HsBindgen.Preprocessor qualified as Preprocessor +import HsBindgen.Spec qualified as Unresolved + +-- TODO: +generateBindingsFor :: Unresolved.Spec -> Q [Dec] +generateBindingsFor spec = do + spec' <- liftIO $ Unresolved.resolve spec + return $ map toDec $ Preprocessor.generateDeclarations spec' diff --git a/hs-bindgen/src/HsBindgen/Annotation.hs b/hs-bindgen/src/HsBindgen/Annotation.hs new file mode 100644 index 00000000..9ecae56d --- /dev/null +++ b/hs-bindgen/src/HsBindgen/Annotation.hs @@ -0,0 +1,31 @@ +-- | Syntax tree annotations +-- +-- The type argument @l@ pervasive in @haskell-src-exts@ allows for arbitrary +-- annotations; we will instantiate it to 'Ann', defined in this module. This +-- type is not considered part of the public API of @hs-bindgen@. +-- +-- TODO: +-- If we want to include LINE pragmas, we will need to include line information +-- (referring to the C header) in these annotations. +-- +-- Intended for qualified import. +-- +-- > import HsBindgen.Annotation (Ann) +-- > import HsBindgen.Annotation qualified as Ann +module HsBindgen.Annotation ( + Ann(..) + ) where + +{------------------------------------------------------------------------------- + Definition +-------------------------------------------------------------------------------} + +-- | Syntax tree annotation +data Ann = Ann { + } + +instance Semigroup Ann where + _ <> _ = mempty + +instance Monoid Ann where + mempty = Ann \ No newline at end of file diff --git a/hs-bindgen/src/HsBindgen/Preprocessor.hs b/hs-bindgen/src/HsBindgen/Preprocessor.hs new file mode 100644 index 00000000..90380341 --- /dev/null +++ b/hs-bindgen/src/HsBindgen/Preprocessor.hs @@ -0,0 +1,53 @@ +-- | Generate Haskell bindings from C headers +-- +-- This module is intended to be used when using the tool in preprocessor mode. +module HsBindgen.Preprocessor ( + generateModule + -- * Support for TH mode + , generateDeclarations + ) where + +import Language.Haskell.Exts + +import HsBindgen.Annotation (Ann) +import HsBindgen.Spec.Resolved (Spec(..)) + +{------------------------------------------------------------------------------- + Top-level +-------------------------------------------------------------------------------} + +-- TODO: +generateModule :: Spec -> Module Ann +generateModule spec = + Module + mempty + (Just $ moduleHead spec) + [] -- No module pragmas + (importDecls spec) + (generateDeclarations spec) + +generateDeclarations :: Spec -> [Decl Ann] +generateDeclarations _spec = [] + +{------------------------------------------------------------------------------- + Module components +-------------------------------------------------------------------------------} + +moduleHead :: Spec -> ModuleHead Ann +moduleHead spec = + ModuleHead + mempty + (moduleName spec) + Nothing -- No warning text (the module is not deprecated) + (exportList spec) + +moduleName :: Spec -> ModuleName Ann +moduleName spec = ModuleName mempty $ specHsModuleName spec + +-- TODO: +-- Generate export list. For now we just export everything. +exportList :: Spec -> Maybe (ExportSpecList Ann) +exportList _ = Nothing + +importDecls :: Spec -> [ImportDecl Ann] +importDecls _ = [] diff --git a/hs-bindgen/src/HsBindgen/Preprocessor/Render.hs b/hs-bindgen/src/HsBindgen/Preprocessor/Render.hs new file mode 100644 index 00000000..768ce90b --- /dev/null +++ b/hs-bindgen/src/HsBindgen/Preprocessor/Render.hs @@ -0,0 +1,75 @@ +module HsBindgen.Preprocessor.Render ( + RenderOptions(..) + , defaultRenderOptions + , render + ) where + +import Language.Haskell.Exts (Module) +import Language.Haskell.Exts.Pretty qualified as Pretty + +import HsBindgen.Annotation (Ann) + +{------------------------------------------------------------------------------- + Options + + We abstract away from the options offered by @language-src-exts@. +-------------------------------------------------------------------------------} + +-- | Rendering options +data RenderOptions = RenderOptions { + renderLineLength :: Int + } + deriving stock (Show) + +defaultRenderOptions :: RenderOptions +defaultRenderOptions = RenderOptions { + renderLineLength = 80 + } + +{------------------------------------------------------------------------------- + Rendering +-------------------------------------------------------------------------------} + +-- | Pretty-print generated bindings +render :: RenderOptions -> Module Ann -> String +render opts = Pretty.prettyPrintStyleMode (toStyle opts) (toMode opts) + +{------------------------------------------------------------------------------- + Internal: translate our options to @haskell-src-exts@ + + We construct the records explicitly (rather than using a record update) so + that we get compilation errors when the record changes, forcing us to make + an explicit decision about the new situation. +-------------------------------------------------------------------------------} + +toStyle :: RenderOptions -> Pretty.Style +toStyle opts = Pretty.Style { + mode = Pretty.PageMode + , lineLength = renderLineLength opts + , ribbonsPerLine = Pretty.ribbonsPerLine def + } + where + def :: Pretty.Style + def = Pretty.style + +toMode :: RenderOptions -> Pretty.PPHsMode +toMode _opts = Pretty.PPHsMode { + classIndent = Pretty.classIndent def + , doIndent = Pretty.doIndent def + , multiIfIndent = Pretty.multiIfIndent def + , caseIndent = Pretty.caseIndent def + , letIndent = Pretty.letIndent def + , whereIndent = Pretty.whereIndent def + , onsideIndent = Pretty.onsideIndent def + , spacing = True + , layout = Pretty.PPOffsideRule + , linePragmas = noLinePragmas + } + where + def :: Pretty.PPHsMode + def = Pretty.defaultMode + + -- TODO: + -- For now we don't include @LINE@ pragmas. + noLinePragmas :: Bool + noLinePragmas = False diff --git a/hs-bindgen/src/HsBindgen/Spec.hs b/hs-bindgen/src/HsBindgen/Spec.hs new file mode 100644 index 00000000..cae73126 --- /dev/null +++ b/hs-bindgen/src/HsBindgen/Spec.hs @@ -0,0 +1,59 @@ +-- | Specification of what @hs-bindgen@ should do +-- +-- Intended for qualified import. +-- +-- > import HsBindgen.Spec (Spec(..)) +-- > import HsBindgen.Spec qualified as Unresolved +-- +-- The @Unresolved@ qualifier distinguishes this module from +-- "HsBindgen.Spec.Resolved". +module HsBindgen.Spec ( + Spec(..) + -- * Resolution + , resolve + ) where + +import HsBindgen.Spec.Resolved qualified as Resolved + +{------------------------------------------------------------------------------- + Definition +-------------------------------------------------------------------------------} + +-- | Specification of what @hs-bindgen@ should do +-- +-- TODO: +-- This needs to include parameters for cross compilation. +-- +-- TODO: +-- This needs to have fields with paths, preprocessor defines, etc. +-- +-- TODO: +-- Support multiple C headers. +data Spec = Spec { + -- | Path to the C header + specCHeader :: FilePath + + -- | Name of the generated Haskell module + , specHsModuleName :: String + } + deriving stock (Show) + +{------------------------------------------------------------------------------- + Resolution +-------------------------------------------------------------------------------} + +-- | Resolve the spec +-- +-- The resulting 'Resolved.Spec' contains the same information as the input +-- 'Spec', but the resolution will involve reading files from disk, interacting +-- with the C toolchain, etc. The goal is that after resolution the translation +-- from the resolved 'Resolved.Spec' to a Haskell module is a pure function. +-- +-- The 'Resolved.Spec' is an opaque type in the public API. +resolve :: Spec -> IO Resolved.Spec +resolve spec = do + cHeader <- readFile $ specCHeader spec + return Resolved.Spec{ + specCHeader = cHeader + , specHsModuleName = specHsModuleName spec + } \ No newline at end of file diff --git a/hs-bindgen/src/HsBindgen/Spec/Resolved.hs b/hs-bindgen/src/HsBindgen/Spec/Resolved.hs new file mode 100644 index 00000000..c66e147c --- /dev/null +++ b/hs-bindgen/src/HsBindgen/Spec/Resolved.hs @@ -0,0 +1,33 @@ +-- | Resolved spec +-- +-- "HsBindgen.Spec" 'HsBindgen.Spec.Spec' defines a specification, but executing +-- it requires @IO@: reading header files, interacting with @libclang@, etc. +-- The generation of the Haskell module(s) however is morally pure, and we'd +-- like to keep it that way: the /resolved/ spec enables this. +-- +-- Unlike "HsBindgen.Spec", this is /not/ part of the library's public API. +-- +-- Intended for qualified import. +-- +-- > import HsBindgen.Spec.Resolved (Spec(..)) +-- > import HsBindgen.Spec.Resolved qualified as Spec +-- +-- Where there is ambiguity, the module should be qualified as @Resolved@. +module HsBindgen.Spec.Resolved ( + Spec(..) + ) where + +{------------------------------------------------------------------------------- + Definition +-------------------------------------------------------------------------------} + +data Spec = Spec { + -- | Input C header + -- TODO: + -- + -- This should be the output of parsing the C header using @libclang@. + specCHeader :: String + + -- | Name of the generated Haskell module + , specHsModuleName :: String + }