Skip to content

Commit

Permalink
Basic libclang bindings
Browse files Browse the repository at this point in the history
This adds all bindings required to translate the `libclang` tutorial to
Haskell.
  • Loading branch information
edsko committed Aug 7, 2024
1 parent 83e323f commit a1805ba
Show file tree
Hide file tree
Showing 23 changed files with 2,029 additions and 16 deletions.
7 changes: 4 additions & 3 deletions .github/workflows/haskell-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -168,11 +168,12 @@ jobs:
echo " ghc-options: -Werror=missing-methods" >> cabal.project
cat >> cabal.project <<EOF
package hs-bindgen
extra-lib-dirs: /usr/lib/llvm-14/lib
extra-include-dirs: /usr/lib/llvm-14/include
flags: +build-clang-tutorial
ghc-options: -Werror
package hs-bindgen
ghc-options: -Werror
extra-lib-dirs: /usr/lib/llvm-14/lib
extra-include-dirs: /usr/lib/llvm-14/include
EOF
$HCPKG list --simple-output --names-only | perl -ne 'for (split /\s+/) { print "constraints: any.$_ installed\n" unless /^(hs-bindgen)$/; }' >> cabal.project.local
cat cabal.project
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
dist-newstyle/
unversioned
cabal.project.local
.vscode/
5 changes: 4 additions & 1 deletion cabal.project
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
packages: hs-bindgen
packages: hs-bindgen-patterns, hs-bindgen

package hs-bindgen
flags: +build-clang-tutorial
6 changes: 4 additions & 2 deletions cabal.project.ci
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
packages: hs-bindgen

package hs-bindgen
flags: +build-clang-tutorial
ghc-options: -Werror

-- TODO: <https://github.com/well-typed/hs-bindgen/issues/78>
-- We should instead /discover/ where @libclang@ is.
package hs-bindgen
Expand All @@ -8,5 +12,3 @@ package hs-bindgen
extra-include-dirs:
/usr/lib/llvm-14/include

package hs-bindgen
ghc-options: -Werror
5 changes: 5 additions & 0 deletions hs-bindgen-patterns/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Revision history for hs-bindgen-patterns

## 0.1.0 -- YYYY-mm-dd

* First version. Released on an unsuspecting world.
29 changes: 29 additions & 0 deletions hs-bindgen-patterns/LICENSE
Original file line number Diff line number Diff line change
@@ -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.
37 changes: 37 additions & 0 deletions hs-bindgen-patterns/hs-bindgen-patterns.cabal
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
cabal-version: 3.0
name: hs-bindgen-patterns
version: 0.1.0
synopsis: Design patterns for writing high-level FFI bindings
license: BSD-3-Clause
license-file: LICENSE
author: Edsko de Vries
maintainer: [email protected]
category: Development
build-type: Simple
extra-doc-files: CHANGELOG.md

common lang
ghc-options:
-Wall
build-depends:
base >= 4.16 && < 4.21
default-language:
GHC2021
default-extensions:
DeriveAnyClass
DerivingStrategies
other-extensions:
CPP

library
import:
lang
exposed-modules:
HsBindgen.Patterns
other-modules:
HsBindgen.Patterns.Enum.Bitfield
HsBindgen.Patterns.Enum.Simple
HsBindgen.Patterns.SafeForeignPtr
HsBindgen.Patterns.Stack
hs-source-dirs:
src
37 changes: 37 additions & 0 deletions hs-bindgen-patterns/src/HsBindgen/Patterns.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
-- | Design patterns for writing high-level FFI bindings
--
-- This is the only exported module in this library. It is intended to be
-- imported unqualified.
--
-- __NOTE__: This library is little more than an experiment in its current form,
-- with some patterns to support the FFI bindings that @hs-bindgen@ itself needs
-- (for the @libclang@ bindings).
module HsBindgen.Patterns (
-- * Enums
-- ** Simple
SimpleEnum(..)
, IsSimpleEnum(..)
, simpleEnum
, fromSimpleEnum
, unsafeFromSimpleEnum
-- ** Bitfield
, BitfieldEnum(..)
, IsSingleFlag(..)
-- *** API
, bitfieldEnum
, fromBitfieldEnum
, flagIsSet
-- * Foreign pointers
, SafeForeignPtr
, AccessedFinalizedForeignPtrException
-- ** API
, newSafeForeignPtr
, withSafeForeignPtr
, finalizeSafeForeignPtr
) where

import HsBindgen.Patterns.Enum.Bitfield
import HsBindgen.Patterns.Enum.Simple
import HsBindgen.Patterns.SafeForeignPtr


76 changes: 76 additions & 0 deletions hs-bindgen-patterns/src/HsBindgen/Patterns/Enum/Bitfield.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
module HsBindgen.Patterns.Enum.Bitfield (
BitfieldEnum(..)
, IsSingleFlag(..)
-- * API
, bitfieldEnum
, fromBitfieldEnum
, flagIsSet
) where

import Foreign.C
import Data.Foldable (foldl')
import Data.Bits

{-------------------------------------------------------------------------------
Definition
-------------------------------------------------------------------------------}

-- | Single flags
--
-- See 'BitfieldEnum' for discussion.
class IsSingleFlag flag where
flagToC :: flag -> CUInt

-- | Enum that corresponds to a bitfield
--
-- Some C enumerations are defined like this:
--
-- > enum Flags {
-- > Flag1 = 0x00,
-- > Flag2 = 0x01,
-- > Flag3 = 0x02,
-- > Flag4 = 0x04,
-- > Flag5 = 0x08,
-- > ..
-- > };
--
-- The intention then is that these flags are ORed together to select multiple
-- flags. We term this a "bitfield enum": the @flag@ type is intended to be an
-- ADT with a 'IsSingleFlag' instance, mapping ADT constructors to the values from
-- the enum. Using @hsc2hs@, such an instance might look like
--
-- > data Flags = Flag1 | Flag2 | Flag3 | Flag 4 | Flag5
-- >
-- > instance IsSingleFlag Flags where
-- > flagToC Flag1 = #const Flag1
-- > flagToC Flag2 = #const Flag2
-- > flagToC Flag3 = #const Flag3
-- > flagToC Flag4 = #const Flag4
-- > flagToC Flag5 = #const Flag5
newtype BitfieldEnum flag = BitfieldEnum CUInt

{-------------------------------------------------------------------------------
API
-------------------------------------------------------------------------------}

-- | Construct 'BitfieldEnum'
bitfieldEnum :: IsSingleFlag flag => [flag] -> BitfieldEnum flag
bitfieldEnum = BitfieldEnum . foldl' (.|.) 0 . map flagToC

-- | Check if the given flag is set
flagIsSet :: IsSingleFlag flag => BitfieldEnum flag -> flag -> Bool
flagIsSet (BitfieldEnum i) flag = (i .&. flagToC flag) /= 0

-- | All set flags
--
-- This is @O(n)@ in the number of constructs of the @flag@ ADT; while that is
-- technically speaking a constant, making this function @O(1)@, this is still
-- a relatively expensive function. Consider using 'flagIsSet' instead.
--
-- NOTE: The @Enum@ and @Bounded@ instances are simply used to enumerate all
-- flags. Their definition has no bearing on the generated C code, and can
-- simply be derived.
fromBitfieldEnum ::
(IsSingleFlag flag, Enum flag, Bounded flag)
=> BitfieldEnum flag -> [flag]
fromBitfieldEnum i = [flag | flag <- [minBound .. maxBound], flagIsSet i flag]
85 changes: 85 additions & 0 deletions hs-bindgen-patterns/src/HsBindgen/Patterns/Enum/Simple.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
module HsBindgen.Patterns.Enum.Simple (
SimpleEnum(..)
, IsSimpleEnum(..)
-- * API
, simpleEnum
, fromSimpleEnum
, unsafeFromSimpleEnum
) where

import Foreign.C
import GHC.Stack

{-------------------------------------------------------------------------------
Definition
-------------------------------------------------------------------------------}

-- | ADTs corresponding to simple enums
--
-- See 'SimpleEnum' for discussion
class IsSimpleEnum a where
-- | Translate Haskell constructor to C value
simpleToC :: a -> CInt

-- | Translate C value to haskell constructor
--
-- This returns a 'Maybe' value, because C enums do not restrict the range.
-- From Wikipedia (<https://en.wikipedia.org/wiki/C_syntax#Enumerated_type>):
--
-- > Some compilers warn if an object with enumerated type is assigned a value
-- > that is not one of its constants. However, such an object can be assigned
-- > any values in the range of their compatible type, and enum constants can
-- > be used anywhere an integer is expected. For this reason, enum values are
-- > often used in place of preprocessor #define directives to create named
-- > constants. Such constants are generally safer to use than macros, since
-- > they reside within a specific identifier namespace.
--
-- This means that a 'Nothing' value is not necessary an error.
simpleFromC :: CInt -> Maybe a

-- | Simple C enums
--
-- Suppose we have a simple C enum defined like this:
--
-- > enum SomeEnum {
-- > Value1,
-- > Value2,
-- > Value3
-- > };
--
-- Then 'SimpleEnum' can link the underlying 'CInt' to a Haskell ADT. Using
-- @hsc2hs@, this might look like
--
-- > data SomeEnum = Value1 | Value2 | Value3
-- >
-- > instance IsSimpleEnum SomeEnum where
-- > simpleToC Value1 = #const Value1
-- > simpleToC Value2 = #const Value2
-- > simpleToC Value3 = #const Value3
-- >
-- > simpleFromC (#const Value1) = Just Value1
-- > simpleFromC (#const Value2) = Just Value2
-- > simpleFromC (#const Value3) = Just Value3
-- >
-- > simpleFromC _otherwise = Nothing
newtype SimpleEnum a = SimpleEnum CInt

{-------------------------------------------------------------------------------
API
-------------------------------------------------------------------------------}

simpleEnum :: IsSimpleEnum a => a -> SimpleEnum a
simpleEnum = SimpleEnum . simpleToC

-- | Underlying C value
--
-- Returns the raw 'CInt' if is out of the range of @a@
fromSimpleEnum :: IsSimpleEnum a => SimpleEnum a -> Either CInt a
fromSimpleEnum (SimpleEnum i) = maybe (Left i) Right $ simpleFromC i

-- | Like 'fromSimpleEnum', but throw an exception if the value is out of range
unsafeFromSimpleEnum :: (HasCallStack, IsSimpleEnum a) => SimpleEnum a -> a
unsafeFromSimpleEnum = either (error . err) id . fromSimpleEnum
where
err :: CInt -> String
err i = "SimpleEnum out of range: " ++ show i
Loading

0 comments on commit a1805ba

Please sign in to comment.