souffle-haskell 0.1.0 → 0.2.0
raw patch · 14 files changed
+1305/−613 lines, 14 filesdep +deepseqdep +directorydep +filepathPVP ok
version bump matches the API change (PVP)
Dependencies added: deepseq, directory, filepath, free, process, temporary
API changes (from Hackage documentation)
- Language.Souffle: -- used to check that only known facts are added to a program.
- Language.Souffle: -- | A type level list of facts that belong to this program. This list is
- Language.Souffle: addFact :: (MonadSouffle m, Fact a, ContainsFact prog a) => Handle prog -> a -> m ()
- Language.Souffle: addFacts :: (MonadSouffle m, Foldable t, Fact a, ContainsFact prog a) => Handle prog -> t a -> m ()
- Language.Souffle: class CollectFacts c
- Language.Souffle: class Marshal a => Fact a
- Language.Souffle: class Marshal a
- Language.Souffle: class Monad m => MonadSouffle m
- Language.Souffle: class Program a where {
- Language.Souffle: data Handle prog
- Language.Souffle: data SouffleM a
- Language.Souffle: factName :: Fact a => Proxy a -> String
- Language.Souffle: findFact :: (MonadSouffle m, Fact a, ContainsFact prog a) => Handle prog -> a -> m (Maybe a)
- Language.Souffle: getFacts :: (MonadSouffle m, Fact a, ContainsFact prog a, CollectFacts c) => Handle prog -> m (c a)
- Language.Souffle: getNumThreads :: MonadSouffle m => Handle prog -> m Word64
- Language.Souffle: init :: (MonadSouffle m, Program prog) => prog -> m (Maybe (Handle prog))
- Language.Souffle: instance (GHC.Base.Monoid w, Language.Souffle.MonadSouffle m) => Language.Souffle.MonadSouffle (Control.Monad.Trans.Writer.Lazy.WriterT w m)
- Language.Souffle: instance (Language.Souffle.MonadSouffle m, GHC.Base.Monoid w) => Language.Souffle.MonadSouffle (Control.Monad.Trans.RWS.Lazy.RWST r w s m)
- Language.Souffle: instance Control.Monad.IO.Class.MonadIO Language.Souffle.SouffleM
- Language.Souffle: instance GHC.Base.Applicative Language.Souffle.SouffleM
- Language.Souffle: instance GHC.Base.Functor Language.Souffle.SouffleM
- Language.Souffle: instance GHC.Base.Monad Language.Souffle.SouffleM
- Language.Souffle: instance Language.Souffle.CollectFacts Data.Vector.Vector
- Language.Souffle: instance Language.Souffle.CollectFacts []
- Language.Souffle: instance Language.Souffle.MonadSouffle Language.Souffle.SouffleM
- Language.Souffle: instance Language.Souffle.MonadSouffle m => Language.Souffle.MonadSouffle (Control.Monad.Trans.Except.ExceptT s m)
- Language.Souffle: instance Language.Souffle.MonadSouffle m => Language.Souffle.MonadSouffle (Control.Monad.Trans.Reader.ReaderT r m)
- Language.Souffle: instance Language.Souffle.MonadSouffle m => Language.Souffle.MonadSouffle (Control.Monad.Trans.State.Lazy.StateT s m)
- Language.Souffle: loadFiles :: MonadSouffle m => Handle prog -> FilePath -> m ()
- Language.Souffle: pop :: (Marshal a, Generic a, SimpleProduct a (Rep a), GMarshal (Rep a), MonadIO m) => MarshalT m a
- Language.Souffle: programName :: Program a => Proxy a -> String
- Language.Souffle: push :: (Marshal a, Generic a, SimpleProduct a (Rep a), GMarshal (Rep a), MonadIO m) => a -> MarshalT m ()
- Language.Souffle: run :: MonadSouffle m => Handle prog -> m ()
- Language.Souffle: runSouffle :: SouffleM a -> IO a
- Language.Souffle: setNumThreads :: MonadSouffle m => Handle prog -> Word64 -> m ()
- Language.Souffle: type family ProgramFacts a :: [Type];
- Language.Souffle: writeFiles :: MonadSouffle m => Handle prog -> m ()
- Language.Souffle: }
- Language.Souffle.Marshal: data MarshalT m a
- Language.Souffle.Marshal: instance (Control.Monad.Writer.Class.MonadWriter w m, Control.Monad.State.Class.MonadState s m) => Control.Monad.RWS.Class.MonadRWS Language.Souffle.Marshal.Tuple w s (Language.Souffle.Marshal.MarshalT m)
- Language.Souffle.Marshal: instance Control.Monad.Error.Class.MonadError e m => Control.Monad.Error.Class.MonadError e (Language.Souffle.Marshal.MarshalT m)
- Language.Souffle.Marshal: instance Control.Monad.IO.Class.MonadIO m => Control.Monad.IO.Class.MonadIO (Language.Souffle.Marshal.MarshalT m)
- Language.Souffle.Marshal: instance Control.Monad.State.Class.MonadState s m => Control.Monad.State.Class.MonadState s (Language.Souffle.Marshal.MarshalT m)
- Language.Souffle.Marshal: instance Control.Monad.Trans.Class.MonadTrans Language.Souffle.Marshal.MarshalT
- Language.Souffle.Marshal: instance Control.Monad.Writer.Class.MonadWriter w m => Control.Monad.Writer.Class.MonadWriter w (Language.Souffle.Marshal.MarshalT m)
- Language.Souffle.Marshal: instance GHC.Base.Applicative m => GHC.Base.Applicative (Language.Souffle.Marshal.MarshalT m)
- Language.Souffle.Marshal: instance GHC.Base.Functor m => GHC.Base.Functor (Language.Souffle.Marshal.MarshalT m)
- Language.Souffle.Marshal: instance GHC.Base.Monad m => Control.Monad.Reader.Class.MonadReader Language.Souffle.Marshal.Tuple (Language.Souffle.Marshal.MarshalT m)
- Language.Souffle.Marshal: instance GHC.Base.Monad m => GHC.Base.Monad (Language.Souffle.Marshal.MarshalT m)
- Language.Souffle.Marshal: runMarshalT :: MarshalT m a -> Tuple -> m a
+ Language.Souffle.Class: -- <a>init</a>, which returns a handle of this type.
+ Language.Souffle.Class: -- Souffle in a list or vector. Only used internally.
+ Language.Souffle.Class: -- used to check that only known facts are added to a program.
+ Language.Souffle.Class: -- | Helper associated type constraint that allows collecting facts from
+ Language.Souffle.Class: addFact :: (MonadSouffle m, Fact a, ContainsFact prog a) => Handler m prog -> a -> m ()
+ Language.Souffle.Class: addFacts :: (MonadSouffle m, Foldable t, Fact a, ContainsFact prog a) => Handler m prog -> t a -> m ()
+ Language.Souffle.Class: class Marshal a => Fact a
+ Language.Souffle.Class: class Monad m => MonadSouffle m where {
+ Language.Souffle.Class: class MonadSouffle m => MonadSouffleFileIO m
+ Language.Souffle.Class: class Program a where {
+ Language.Souffle.Class: factName :: Fact a => Proxy a -> String
+ Language.Souffle.Class: findFact :: (MonadSouffle m, Fact a, ContainsFact prog a, Eq a) => Handler m prog -> a -> m (Maybe a)
+ Language.Souffle.Class: getFacts :: (MonadSouffle m, Fact a, ContainsFact prog a, CollectFacts m c) => Handler m prog -> m (c a)
+ Language.Souffle.Class: getNumThreads :: MonadSouffle m => Handler m prog -> m Word64
+ Language.Souffle.Class: init :: (MonadSouffle m, Program prog) => prog -> m (Maybe (Handler m prog))
+ Language.Souffle.Class: instance (GHC.Base.Monoid w, Language.Souffle.Class.MonadSouffle m) => Language.Souffle.Class.MonadSouffle (Control.Monad.Trans.Writer.Lazy.WriterT w m)
+ Language.Souffle.Class: instance (GHC.Base.Monoid w, Language.Souffle.Class.MonadSouffleFileIO m) => Language.Souffle.Class.MonadSouffleFileIO (Control.Monad.Trans.Writer.Lazy.WriterT w m)
+ Language.Souffle.Class: instance (Language.Souffle.Class.MonadSouffle m, GHC.Base.Monoid w) => Language.Souffle.Class.MonadSouffle (Control.Monad.Trans.RWS.Strict.RWST r w s m)
+ Language.Souffle.Class: instance (Language.Souffle.Class.MonadSouffleFileIO m, GHC.Base.Monoid w) => Language.Souffle.Class.MonadSouffleFileIO (Control.Monad.Trans.RWS.Strict.RWST r w s m)
+ Language.Souffle.Class: instance Language.Souffle.Class.MonadSouffle m => Language.Souffle.Class.MonadSouffle (Control.Monad.Trans.Except.ExceptT e m)
+ Language.Souffle.Class: instance Language.Souffle.Class.MonadSouffle m => Language.Souffle.Class.MonadSouffle (Control.Monad.Trans.Reader.ReaderT r m)
+ Language.Souffle.Class: instance Language.Souffle.Class.MonadSouffle m => Language.Souffle.Class.MonadSouffle (Control.Monad.Trans.State.Lazy.StateT s m)
+ Language.Souffle.Class: instance Language.Souffle.Class.MonadSouffleFileIO m => Language.Souffle.Class.MonadSouffleFileIO (Control.Monad.Trans.Except.ExceptT s m)
+ Language.Souffle.Class: instance Language.Souffle.Class.MonadSouffleFileIO m => Language.Souffle.Class.MonadSouffleFileIO (Control.Monad.Trans.Reader.ReaderT r m)
+ Language.Souffle.Class: instance Language.Souffle.Class.MonadSouffleFileIO m => Language.Souffle.Class.MonadSouffleFileIO (Control.Monad.Trans.State.Lazy.StateT s m)
+ Language.Souffle.Class: loadFiles :: MonadSouffleFileIO m => Handler m prog -> FilePath -> m ()
+ Language.Souffle.Class: programName :: Program a => Proxy a -> String
+ Language.Souffle.Class: run :: MonadSouffle m => Handler m prog -> m ()
+ Language.Souffle.Class: setNumThreads :: MonadSouffle m => Handler m prog -> Word64 -> m ()
+ Language.Souffle.Class: type family CollectFacts m (c :: Type -> Type) :: Constraint;
+ Language.Souffle.Class: writeFiles :: MonadSouffleFileIO m => Handler m prog -> m ()
+ Language.Souffle.Class: }
+ Language.Souffle.Compiled: -- <a>init</a>, which returns a handle of this type.
+ Language.Souffle.Compiled: -- Souffle in a list or vector. Only used internally.
+ Language.Souffle.Compiled: -- used to check that only known facts are added to a program.
+ Language.Souffle.Compiled: -- | Helper associated type constraint that allows collecting facts from
+ Language.Souffle.Compiled: addFact :: (MonadSouffle m, Fact a, ContainsFact prog a) => Handler m prog -> a -> m ()
+ Language.Souffle.Compiled: addFacts :: (MonadSouffle m, Foldable t, Fact a, ContainsFact prog a) => Handler m prog -> t a -> m ()
+ Language.Souffle.Compiled: class Marshal a => Fact a
+ Language.Souffle.Compiled: class Marshal a
+ Language.Souffle.Compiled: class Monad m => MonadSouffle m where {
+ Language.Souffle.Compiled: class Program a where {
+ Language.Souffle.Compiled: data Handle prog
+ Language.Souffle.Compiled: data SouffleM a
+ Language.Souffle.Compiled: factName :: Fact a => Proxy a -> String
+ Language.Souffle.Compiled: findFact :: (MonadSouffle m, Fact a, ContainsFact prog a, Eq a) => Handler m prog -> a -> m (Maybe a)
+ Language.Souffle.Compiled: getFacts :: (MonadSouffle m, Fact a, ContainsFact prog a, CollectFacts m c) => Handler m prog -> m (c a)
+ Language.Souffle.Compiled: getNumThreads :: MonadSouffle m => Handler m prog -> m Word64
+ Language.Souffle.Compiled: init :: (MonadSouffle m, Program prog) => prog -> m (Maybe (Handler m prog))
+ Language.Souffle.Compiled: instance (Control.Monad.Writer.Class.MonadWriter w m, Control.Monad.State.Class.MonadState s m) => Control.Monad.RWS.Class.MonadRWS Language.Souffle.Compiled.Tuple w s (Language.Souffle.Compiled.MarshalT m)
+ Language.Souffle.Compiled: instance Control.Monad.Error.Class.MonadError e m => Control.Monad.Error.Class.MonadError e (Language.Souffle.Compiled.MarshalT m)
+ Language.Souffle.Compiled: instance Control.Monad.IO.Class.MonadIO Language.Souffle.Compiled.SouffleM
+ Language.Souffle.Compiled: instance Control.Monad.IO.Class.MonadIO m => Control.Monad.IO.Class.MonadIO (Language.Souffle.Compiled.MarshalT m)
+ Language.Souffle.Compiled: instance Control.Monad.State.Class.MonadState s m => Control.Monad.State.Class.MonadState s (Language.Souffle.Compiled.MarshalT m)
+ Language.Souffle.Compiled: instance Control.Monad.Trans.Class.MonadTrans Language.Souffle.Compiled.MarshalT
+ Language.Souffle.Compiled: instance Control.Monad.Writer.Class.MonadWriter w m => Control.Monad.Writer.Class.MonadWriter w (Language.Souffle.Compiled.MarshalT m)
+ Language.Souffle.Compiled: instance GHC.Base.Applicative Language.Souffle.Compiled.SouffleM
+ Language.Souffle.Compiled: instance GHC.Base.Applicative m => GHC.Base.Applicative (Language.Souffle.Compiled.MarshalT m)
+ Language.Souffle.Compiled: instance GHC.Base.Functor Language.Souffle.Compiled.SouffleM
+ Language.Souffle.Compiled: instance GHC.Base.Functor m => GHC.Base.Functor (Language.Souffle.Compiled.MarshalT m)
+ Language.Souffle.Compiled: instance GHC.Base.Monad Language.Souffle.Compiled.SouffleM
+ Language.Souffle.Compiled: instance GHC.Base.Monad m => Control.Monad.Reader.Class.MonadReader Language.Souffle.Compiled.Tuple (Language.Souffle.Compiled.MarshalT m)
+ Language.Souffle.Compiled: instance GHC.Base.Monad m => GHC.Base.Monad (Language.Souffle.Compiled.MarshalT m)
+ Language.Souffle.Compiled: instance Language.Souffle.Class.MonadSouffle Language.Souffle.Compiled.SouffleM
+ Language.Souffle.Compiled: instance Language.Souffle.Class.MonadSouffleFileIO Language.Souffle.Compiled.SouffleM
+ Language.Souffle.Compiled: instance Language.Souffle.Compiled.Collect Data.Vector.Vector
+ Language.Souffle.Compiled: instance Language.Souffle.Compiled.Collect []
+ Language.Souffle.Compiled: pop :: (Marshal a, Generic a, SimpleProduct a (Rep a), GMarshal (Rep a)) => MarshalM PopF a
+ Language.Souffle.Compiled: programName :: Program a => Proxy a -> String
+ Language.Souffle.Compiled: push :: (Marshal a, Generic a, SimpleProduct a (Rep a), GMarshal (Rep a)) => a -> MarshalM PushF ()
+ Language.Souffle.Compiled: run :: MonadSouffle m => Handler m prog -> m ()
+ Language.Souffle.Compiled: runSouffle :: SouffleM a -> IO a
+ Language.Souffle.Compiled: setNumThreads :: MonadSouffle m => Handler m prog -> Word64 -> m ()
+ Language.Souffle.Compiled: type family CollectFacts m (c :: Type -> Type) :: Constraint;
+ Language.Souffle.Compiled: }
+ Language.Souffle.Interpreted: -- <a>init</a>, which returns a handle of this type.
+ Language.Souffle.Interpreted: -- Souffle in a list or vector. Only used internally.
+ Language.Souffle.Interpreted: -- used to check that only known facts are added to a program.
+ Language.Souffle.Interpreted: -- | Helper associated type constraint that allows collecting facts from
+ Language.Souffle.Interpreted: Config :: FilePath -> Maybe FilePath -> Config
+ Language.Souffle.Interpreted: [cfgDatalogDir] :: Config -> FilePath
+ Language.Souffle.Interpreted: [cfgSouffleBin] :: Config -> Maybe FilePath
+ Language.Souffle.Interpreted: addFact :: (MonadSouffle m, Fact a, ContainsFact prog a) => Handler m prog -> a -> m ()
+ Language.Souffle.Interpreted: addFacts :: (MonadSouffle m, Foldable t, Fact a, ContainsFact prog a) => Handler m prog -> t a -> m ()
+ Language.Souffle.Interpreted: class Marshal a => Fact a
+ Language.Souffle.Interpreted: class Marshal a
+ Language.Souffle.Interpreted: class Monad m => MonadSouffle m where {
+ Language.Souffle.Interpreted: class Program a where {
+ Language.Souffle.Interpreted: cleanup :: forall prog. Program prog => Handle prog -> SouffleM ()
+ Language.Souffle.Interpreted: data Config
+ Language.Souffle.Interpreted: data Handle prog
+ Language.Souffle.Interpreted: data SouffleM a
+ Language.Souffle.Interpreted: defaultConfig :: MonadIO m => m Config
+ Language.Souffle.Interpreted: factName :: Fact a => Proxy a -> String
+ Language.Souffle.Interpreted: findFact :: (MonadSouffle m, Fact a, ContainsFact prog a, Eq a) => Handler m prog -> a -> m (Maybe a)
+ Language.Souffle.Interpreted: getFacts :: (MonadSouffle m, Fact a, ContainsFact prog a, CollectFacts m c) => Handler m prog -> m (c a)
+ Language.Souffle.Interpreted: getNumThreads :: MonadSouffle m => Handler m prog -> m Word64
+ Language.Souffle.Interpreted: init :: (MonadSouffle m, Program prog) => prog -> m (Maybe (Handler m prog))
+ Language.Souffle.Interpreted: instance Control.Monad.IO.Class.MonadIO Language.Souffle.Interpreted.SouffleM
+ Language.Souffle.Interpreted: instance Control.Monad.State.Class.MonadState [GHC.Base.String] Language.Souffle.Interpreted.IMarshal
+ Language.Souffle.Interpreted: instance GHC.Base.Applicative Language.Souffle.Interpreted.IMarshal
+ Language.Souffle.Interpreted: instance GHC.Base.Applicative Language.Souffle.Interpreted.SouffleM
+ Language.Souffle.Interpreted: instance GHC.Base.Functor Language.Souffle.Interpreted.IMarshal
+ Language.Souffle.Interpreted: instance GHC.Base.Functor Language.Souffle.Interpreted.SouffleM
+ Language.Souffle.Interpreted: instance GHC.Base.Monad Language.Souffle.Interpreted.IMarshal
+ Language.Souffle.Interpreted: instance GHC.Base.Monad Language.Souffle.Interpreted.SouffleM
+ Language.Souffle.Interpreted: instance GHC.Show.Show Language.Souffle.Interpreted.Config
+ Language.Souffle.Interpreted: instance Language.Souffle.Class.MonadSouffle Language.Souffle.Interpreted.SouffleM
+ Language.Souffle.Interpreted: instance Language.Souffle.Interpreted.Collect Data.Vector.Vector
+ Language.Souffle.Interpreted: instance Language.Souffle.Interpreted.Collect []
+ Language.Souffle.Interpreted: pop :: (Marshal a, Generic a, SimpleProduct a (Rep a), GMarshal (Rep a)) => MarshalM PopF a
+ Language.Souffle.Interpreted: programName :: Program a => Proxy a -> String
+ Language.Souffle.Interpreted: push :: (Marshal a, Generic a, SimpleProduct a (Rep a), GMarshal (Rep a)) => a -> MarshalM PushF ()
+ Language.Souffle.Interpreted: run :: MonadSouffle m => Handler m prog -> m ()
+ Language.Souffle.Interpreted: runSouffle :: SouffleM a -> IO a
+ Language.Souffle.Interpreted: runSouffleWith :: Config -> SouffleM a -> IO a
+ Language.Souffle.Interpreted: setNumThreads :: MonadSouffle m => Handler m prog -> Word64 -> m ()
+ Language.Souffle.Interpreted: type family CollectFacts m (c :: Type -> Type) :: Constraint;
+ Language.Souffle.Interpreted: }
+ Language.Souffle.Marshal: PopInt :: (Int32 -> a) -> PopF a
+ Language.Souffle.Marshal: PopStr :: (String -> a) -> PopF a
+ Language.Souffle.Marshal: PushInt :: Int32 -> a -> PushF a
+ Language.Souffle.Marshal: PushStr :: String -> a -> PushF a
+ Language.Souffle.Marshal: data PopF a
+ Language.Souffle.Marshal: data PushF a
+ Language.Souffle.Marshal: instance GHC.Base.Functor Language.Souffle.Marshal.PopF
+ Language.Souffle.Marshal: instance GHC.Base.Functor Language.Souffle.Marshal.PushF
+ Language.Souffle.Marshal: interpret :: Monad m => (forall x. f x -> m x) -> MarshalM f a -> m a
+ Language.Souffle.Marshal: type MarshalM = Free
- Language.Souffle.Marshal: pop :: (Marshal a, Generic a, SimpleProduct a (Rep a), GMarshal (Rep a), MonadIO m) => MarshalT m a
+ Language.Souffle.Marshal: pop :: (Marshal a, Generic a, SimpleProduct a (Rep a), GMarshal (Rep a)) => MarshalM PopF a
- Language.Souffle.Marshal: push :: (Marshal a, Generic a, SimpleProduct a (Rep a), GMarshal (Rep a), MonadIO m) => a -> MarshalT m ()
+ Language.Souffle.Marshal: push :: (Marshal a, Generic a, SimpleProduct a (Rep a), GMarshal (Rep a)) => a -> MarshalM PushF ()
Files
- CHANGELOG.md +14/−1
- README.md +69/−4
- lib/Language/Souffle.hs +4/−364
- lib/Language/Souffle/Class.hs +288/−0
- lib/Language/Souffle/Compiled.hs +192/−0
- lib/Language/Souffle/Internal.hs +0/−2
- lib/Language/Souffle/Internal/Constraints.hs +1/−2
- lib/Language/Souffle/Interpreted.hs +301/−0
- lib/Language/Souffle/Marshal.hs +48/−57
- lib/Language/Souffle/TH.hs +1/−1
- souffle-haskell.cabal +29/−9
- tests/Test/Language/Souffle/CompiledSpec.hs +175/−0
- tests/Test/Language/Souffle/InterpretedSpec.hs +183/−0
- tests/Test/TestSuiteSpec.hs +0/−173
CHANGELOG.md view
@@ -1,9 +1,22 @@ # Changelog -All notable changes to this project will be documented in this file.+All notable changes to this project (as seen by library users) will be documented in this file. The CHANGELOG is available on [Github](https://github.com/luc-tielen/souffle-haskell.git/CHANGELOG.md). +## [0.2.0] - 2020-04-22+### Added++- Added Language.Souffle.Interpreted module for running Souffle programs in interpreted mode.+ NOTE: For this mode the CSV fact files must use TAB (`'\t'`) characters as separators.+- In interpreted mode, you can configure where the library looks for datalog files or where+ the souffle executable is located. For more information, see the `runSouffle` and `runSouffleWith`+ functions in the Language.Souffle.Interpreted module.++### Changed++- Introduced Language.Souffle.Class module as separation of the typeclass and the+ Language.Souffle.Compiled module to offer a uniform API in both interpreted and compiled mode. ## [0.1.0] - 2019-12-21 ### Added
README.md view
@@ -2,12 +2,11 @@ # Souffle-haskell [](https://github.com/luc-tielen/souffle-haskell/blob/master/LICENSE)+[](https://circleci.com/gh/luc-tielen/souffle-haskell) [](https://hackage.haskell.org/package/souffle-haskell) This repo provides Haskell bindings for performing analyses with the [Souffle Datalog language](https://github.com/souffle-lang/souffle).-It does this by binding directly to an "embedded" Souffle program-(previously generated with `souffle -g`). Fun fact: this library combines both functional programming (Haskell), logic programming (Datalog / Souffle) and imperative / OO programming (C / C++).@@ -15,7 +14,7 @@ ## Motivating example -Let's first write a datalog program that can check if 1 point+Let's first write a datalog program that can check if one point is reachable from another: ```prolog@@ -55,7 +54,7 @@ import GHC.Generics import Data.Vector import qualified Language.Souffle.TH as Souffle-import qualified Language.Souffle as Souffle+import qualified Language.Souffle.Compiled as Souffle -- We only use template haskell for directly embedding the .cpp file into this file. -- If we do not do this, it will link incorrectly due to the way the@@ -144,6 +143,7 @@ ```yaml # ...+ cpp-options: - -D__EMBEDDED_SOUFFLE__ @@ -152,6 +152,71 @@ This will instruct the Souffle compiler to compile the C++ in such a way that it can be linked with other languages (including Haskell!).+++## Supported modes++Souffle programs can be run in 2 ways. They can either run in **interpreted** mode+(using the `souffle` CLI command), or they can be **compiled** to C++-code and+called from a host program for improved efficiency. This library supports both+modes (since version 0.2.0). The two variants have only a few minor differences+and can be swapped fairly easily.+++### Interpreted mode++This is probably the mode you want to start out with if you are developing a+program that uses Datalog for computing certain relations. Interpreted mode+offers quick development iterations (no compiling of C++ code each time you+change your Datalog code). However because the Souffle code is interpreted,+it can't offer the same speed as in compiled mode.++The main differences with compiled mode are the following:++1. You need to import `Language.Souffle.Interpreted`+2. You need to call `Souffle.cleanup` after you no longer need the Souffle+ functionality. This will clean up the generated CSV fact files located in+ a temporary directory.+3. You don't need to import `Language.Souffle.TH` to embed a Datalog program.+++#### Interpreter configuration++The interpreter uses CSV files to read or write facts. The configuration+allows specifiying where the fact directory is located. With the default+configuration, it will try to lookup `DATALOG_DIR` in the environment and+fall back to the current directory (or `.`).++You can also configure which souffle executable will be used. By default,+it will first look at the `SOUFFLE_BIN` environment variable. If this is+not set, it will try to find the executable using the `which` shell-command.+If it also can't find the executable this way, then it will fail to+initialize the interpreter.++For more information regarding configuration, take a look at the+`runSouffleWith` function.++The separators in the CSV fact files cannot be configured at the moment.+A tab character (`'\t'`) is used to separate the different columns.+++### Compiled mode++Once the prototyping phase of the Datalog algorithm is over, it is advised+to switch over to the compiled mode. It offers much improved performance+compared to the interpreted mode, at the cost of having to recompile your+Datalog algorithm each time it changes.++The main differences with interpreted mode are the following:++1. Compile the Datalog code with `souffle -g`.+2. You need to import `Language.Souffle.TH` to embed a Datalog program+ using `Language.Souffle.TH.embedProgram`, as shown in the+ [motivating example](#motivating-example).+3. Remove `Souffle.cleanup` if it is present in your code, compiled mode+ leaves no CSV artifacts.++The [motivating example](#motivating-example) is a complete example for the compiled mode. ## Contributing
lib/Language/Souffle.hs view
@@ -1,367 +1,7 @@--{-# OPTIONS_GHC -Wno-redundant-constraints #-}-{-# LANGUAGE RankNTypes, FlexibleInstances, FlexibleContexts, DataKinds #-}-{-# LANGUAGE ScopedTypeVariables, TypeFamilies, TypeOperators #-}-{-# LANGUAGE DerivingVia, InstanceSigs, UndecidableInstances, BangPatterns #-}---- | This module provides the top level API of this library.--- It makes use of Haskell's powerful typesystem to make certain invalid states--- impossible to represent. It does this with a small type level DSL for--- describing properties of the Datalog program (see the 'Program' and 'Fact'--- typeclasses for more information).--- This module also provides a MTL-style interface to Souffle related operations--- so it can be integrated with existing monad transformer stacks.+-- | This module functions as a re-export for the compiled, more performant+-- variant of the API available in this library (due to legacy reasons). module Language.Souffle- ( Program(..)- , Fact(..)- , Marshal.Marshal(..)- , Handle- , CollectFacts- , MonadSouffle(..)- , SouffleM- , runSouffle+ ( module Language.Souffle.Compiled ) where -import Prelude hiding ( init )-import Data.Foldable ( traverse_ )-import Control.Monad.Reader-import Control.Monad.Writer-import Control.Monad.State-import Control.Monad.RWS-import Control.Monad.Except-import Foreign.ForeignPtr-import Foreign.Ptr-import Type.Errors.Pretty-import Data.Proxy-import Data.Kind-import Data.Word-import qualified Data.Vector as V-import qualified Data.Vector.Mutable as MV-import qualified Language.Souffle.Internal as Internal-import qualified Language.Souffle.Marshal as Marshal----- | A datatype representing a handle to a datalog program.--- The type parameter is used for keeping track of which program--- type the handle belongs to for additional type safety.-newtype Handle prog = Handle (ForeignPtr Internal.Souffle)---- | A typeclass for describing a datalog program.------ Example usage (assuming the program was generated from path.dl--- and contains 2 facts: Edge and Reachable):------ @--- data Path = Path -- Handle for the datalog program------ instance Program Path where--- type ProgramFacts Path = '[Edge, Reachable]--- factName = const "path"--- @-class Program a where- -- | A type level list of facts that belong to this program.- -- This list is used to check that only known facts are added to a program.- type ProgramFacts a :: [Type]-- -- | Function for obtaining the name of a Datalog program.- -- This has to be the same as the name of the .dl file (minus the extension).- --- -- It uses a 'Proxy' to select the correct instance.- programName :: Proxy a -> String---- | A typeclass for data types representing a fact in datalog.-class Marshal.Marshal a => Fact a where- -- | Function for obtaining the name of a fact- -- (has to be the same as described in the Datalog program).- --- -- It uses a 'Proxy' to select the correct instance.- --- -- Example usage:- --- -- @- -- instance Fact Edge where- -- factName = const "edge"- -- @- factName :: Proxy a -> String--type family ContainsFact prog fact :: Constraint where- ContainsFact prog fact =- CheckContains prog (ProgramFacts prog) fact--type family CheckContains prog facts fact :: Constraint where- CheckContains prog '[] fact =- TypeError ("You tried to perform an action with a fact of type '" <> fact- <> "' for program '" <> prog <> "'."- % "The program contains the following facts: " <> ProgramFacts prog <> "."- % "It does not contain fact: " <> fact <> "."- % "You can fix this error by adding the type '" <> fact- <> "' to the ProgramFacts type in the Program instance for " <> prog <> ".")- CheckContains _ (a ': _) a = ()- CheckContains prog (_ ': as) b = CheckContains prog as b----- | A monad for executing Souffle-related actions in.-newtype SouffleM a- = SouffleM- { runSouffle :: IO a -- ^ Returns the underlying IO action.- } deriving ( Functor, Applicative, Monad, MonadIO ) via IO---- | Helper typeclass for collecting facts into a container-like structure.--- The order of returned facts is unspecified for performance reasons.--- Only used internally.-class CollectFacts c where- collectFacts :: Marshal.Marshal a- => Int- -> ForeignPtr Internal.RelationIterator- -> IO (c a)--instance CollectFacts V.Vector where- collectFacts factCount iterator = do- vec <- MV.unsafeNew factCount- go vec 0 factCount iterator- where- go vec idx count _ | idx == count = V.unsafeFreeze vec- go vec idx count it = do- tuple <- Internal.relationIteratorNext it- result <- Marshal.runMarshalT Marshal.pop tuple- MV.unsafeWrite vec idx result- go vec (idx + 1) count it- {-# INLINABLE collectFacts #-}--instance CollectFacts [] where- collectFacts factCount = go 0 factCount []- where- go idx count acc _ | idx == count = pure acc- go idx count !acc !it = do- tuple <- Internal.relationIteratorNext it- result <- Marshal.runMarshalT Marshal.pop tuple- go (idx + 1) count (result : acc) it- {-# INLINABLE collectFacts #-}---- | A mtl-style typeclass for Souffle-related actions.-class Monad m => MonadSouffle m where- {- | Initializes a Souffle program.-- The action will return 'Nothing' if it failed to load the Souffle program.- Otherwise it will return a 'Handle' that can be used in other functions- in this module.- -}- init :: Program prog => prog -> m (Maybe (Handle prog))-- -- | Runs the Souffle program.- run :: Handle prog -> m ()-- -- | Sets the number of CPU cores this Souffle program should use.- setNumThreads :: Handle prog -> Word64 -> m ()-- -- | Gets the number of CPU cores this Souffle program should use.- getNumThreads :: Handle prog -> m Word64-- -- | Load all facts from files in a certain directory.- loadFiles :: Handle prog -> FilePath -> m ()-- -- | Write out all facts of the program to CSV files- -- (as defined in the Souffle program).- writeFiles :: Handle prog -> m ()-- -- | Returns all facts of a program. This function makes use of type inference- -- to select the type of fact to return.- getFacts :: (Fact a, ContainsFact prog a, CollectFacts c)- => Handle prog -> m (c a)-- -- | Searches for a fact in a program.- -- Returns 'Nothing' if no matching fact was found; otherwise 'Just' the fact.- --- -- Conceptually equivalent to @List.find (== fact) \<$\> getFacts prog@, but this operation- -- can be implemented much faster.- findFact :: (Fact a, ContainsFact prog a)- => Handle prog -> a -> m (Maybe a)-- -- | Adds a fact to the program.- addFact :: (Fact a, ContainsFact prog a)- => Handle prog -> a -> m ()-- -- | Adds multiple facts to the program. This function could be implemented- -- in terms of 'addFact', but this is done as a minor optimization.- addFacts :: (Foldable t, Fact a, ContainsFact prog a)- => Handle prog -> t a -> m ()--instance MonadSouffle SouffleM where- init :: forall prog. Program prog- => prog -> SouffleM (Maybe (Handle prog))- init _ =- let progName = programName (Proxy :: Proxy prog)- in SouffleM $ fmap Handle <$> Internal.init progName- {-# INLINABLE init #-}-- run (Handle prog) = SouffleM $ Internal.run prog- {-# INLINABLE run #-}-- setNumThreads (Handle prog) numCores =- SouffleM $ Internal.setNumThreads prog numCores- {-# INLINABLE setNumThreads #-}-- getNumThreads (Handle prog) =- SouffleM $ Internal.getNumThreads prog- {-# INLINABLE getNumThreads #-}-- loadFiles (Handle prog) = SouffleM . Internal.loadAll prog- {-# INLINABLE loadFiles #-}-- writeFiles (Handle prog) = SouffleM $ Internal.printAll prog- {-# INLINABLE writeFiles #-}-- addFact :: forall a prog. (Fact a, ContainsFact prog a)- => Handle prog -> a -> SouffleM ()- addFact (Handle prog) fact = liftIO $ do- let relationName = factName (Proxy :: Proxy a)- relation <- Internal.getRelation prog relationName- addFact' relation fact- {-# INLINABLE addFact #-}-- addFacts :: forall t a prog. (Foldable t, Fact a, ContainsFact prog a)- => Handle prog -> t a -> SouffleM ()- addFacts (Handle prog) facts = liftIO $ do- let relationName = factName (Proxy :: Proxy a)- relation <- Internal.getRelation prog relationName- traverse_ (addFact' relation) facts- {-# INLINABLE addFacts #-}-- getFacts :: forall a prog c. (Fact a, ContainsFact prog a, CollectFacts c)- => Handle prog -> SouffleM (c a)- getFacts (Handle prog) = SouffleM $ do- let relationName = factName (Proxy :: Proxy a)- relation <- Internal.getRelation prog relationName- factCount <- Internal.countFacts relation- Internal.getRelationIterator relation >>= collectFacts factCount- {-# INLINABLE getFacts #-}-- findFact :: forall a prog. (Fact a, ContainsFact prog a)- => Handle prog -> a -> SouffleM (Maybe a)- findFact (Handle prog) a = SouffleM $ do- let relationName = factName (Proxy :: Proxy a)- relation <- Internal.getRelation prog relationName- tuple <- Internal.allocTuple relation- withForeignPtr tuple $ Marshal.runMarshalT (Marshal.push a)- found <- Internal.containsTuple relation tuple- pure $ if found then Just a else Nothing- {-# INLINABLE findFact #-}--addFact' :: Fact a => Ptr Internal.Relation -> a -> IO ()-addFact' relation fact = do- tuple <- Internal.allocTuple relation- withForeignPtr tuple $ Marshal.runMarshalT (Marshal.push fact)- Internal.addTuple relation tuple-{-# INLINABLE addFact' #-}---instance MonadSouffle m => MonadSouffle (ReaderT r m) where- init = lift . init- {-# INLINABLE init #-}- run = lift . run- {-# INLINABLE run #-}- setNumThreads prog = lift . setNumThreads prog- {-# INLINABLE setNumThreads #-}- getNumThreads = lift . getNumThreads- {-# INLINABLE getNumThreads #-}- loadFiles prog = lift . loadFiles prog- {-# INLINABLE loadFiles #-}- writeFiles = lift . writeFiles- {-# INLINABLE writeFiles #-}- getFacts = lift . getFacts- {-# INLINABLE getFacts #-}- findFact prog = lift . findFact prog- {-# INLINABLE findFact #-}- addFact fact = lift . addFact fact- {-# INLINABLE addFact #-}- addFacts facts = lift . addFacts facts- {-# INLINABLE addFacts #-}--instance (Monoid w, MonadSouffle m) => MonadSouffle (WriterT w m) where- init = lift . init- {-# INLINABLE init #-}- run = lift . run- {-# INLINABLE run #-}- setNumThreads prog = lift . setNumThreads prog- {-# INLINABLE setNumThreads #-}- getNumThreads = lift . getNumThreads- {-# INLINABLE getNumThreads #-}- loadFiles prog = lift . loadFiles prog- {-# INLINABLE loadFiles #-}- writeFiles = lift . writeFiles- {-# INLINABLE writeFiles #-}- getFacts = lift . getFacts- {-# INLINABLE getFacts #-}- findFact prog = lift . findFact prog- {-# INLINABLE findFact #-}- addFact fact = lift . addFact fact- {-# INLINABLE addFact #-}- addFacts facts = lift . addFacts facts- {-# INLINABLE addFacts #-}--instance MonadSouffle m => MonadSouffle (StateT s m) where- init = lift . init- {-# INLINABLE init #-}- run = lift . run- {-# INLINABLE run #-}- setNumThreads prog = lift . setNumThreads prog- {-# INLINABLE setNumThreads #-}- getNumThreads = lift . getNumThreads- {-# INLINABLE getNumThreads #-}- loadFiles prog = lift . loadFiles prog- {-# INLINABLE loadFiles #-}- writeFiles = lift . writeFiles- {-# INLINABLE writeFiles #-}- getFacts = lift . getFacts- {-# INLINABLE getFacts #-}- findFact prog = lift . findFact prog- {-# INLINABLE findFact #-}- addFact fact = lift . addFact fact- {-# INLINABLE addFact #-}- addFacts facts = lift . addFacts facts- {-# INLINABLE addFacts #-}--instance (MonadSouffle m, Monoid w) => MonadSouffle (RWST r w s m) where- init = lift . init- {-# INLINABLE init #-}- run = lift . run- {-# INLINABLE run #-}- setNumThreads prog = lift . setNumThreads prog- {-# INLINABLE setNumThreads #-}- getNumThreads = lift . getNumThreads- {-# INLINABLE getNumThreads #-}- loadFiles prog = lift . loadFiles prog- {-# INLINABLE loadFiles #-}- writeFiles = lift . writeFiles- {-# INLINABLE writeFiles #-}- getFacts = lift . getFacts- {-# INLINABLE getFacts #-}- findFact prog = lift . findFact prog- {-# INLINABLE findFact #-}- addFact fact = lift . addFact fact- {-# INLINABLE addFact #-}- addFacts facts = lift . addFacts facts- {-# INLINABLE addFacts #-}--instance MonadSouffle m => MonadSouffle (ExceptT s m) where- init = lift . init- {-# INLINABLE init #-}- run = lift . run- {-# INLINABLE run #-}- setNumThreads prog = lift . setNumThreads prog- {-# INLINABLE setNumThreads #-}- getNumThreads = lift . getNumThreads- {-# INLINABLE getNumThreads #-}- loadFiles prog = lift . loadFiles prog- {-# INLINABLE loadFiles #-}- writeFiles = lift . writeFiles- {-# INLINABLE writeFiles #-}- getFacts = lift . getFacts- {-# INLINABLE getFacts #-}- findFact prog = lift . findFact prog- {-# INLINABLE findFact #-}- addFact fact = lift . addFact fact- {-# INLINABLE addFact #-}- addFacts facts = lift . addFacts facts- {-# INLINABLE addFacts #-}-+import Language.Souffle.Compiled
+ lib/Language/Souffle/Class.hs view
@@ -0,0 +1,288 @@+{-# LANGUAGE DataKinds, UndecidableInstances, FlexibleContexts #-}+{-# LANGUAGE TypeFamilies, TypeOperators #-}++-- | This module provides the top level API for Souffle related operations.+-- It makes use of Haskell's powerful typesystem to make certain invalid states+-- impossible to represent. It does this with a small type level DSL for+-- describing properties of the Datalog program (see the 'Program' and 'Fact'+-- typeclasses for more information).+--+-- The Souffle operations are exposed via 2 mtl-style interfaces+-- (see `MonadSouffle` and `MonadSouffleFileIO`) that allows them to be+-- integrated with existing monad transformer stacks.+--+-- This module also contains some helper type families for additional+-- type safety and user-friendly error messages.+module Language.Souffle.Class+ ( ContainsFact+ , Program(..)+ , Fact(..)+ , MonadSouffle(..)+ , MonadSouffleFileIO(..)+ ) where++import Prelude hiding ( init )++import Control.Monad.Except+import Control.Monad.RWS.Strict+import Control.Monad.Reader+import Control.Monad.State+import Control.Monad.Writer+import Data.Proxy+import Data.Kind+import Data.Word+import qualified Language.Souffle.Marshal as Marshal+import Type.Errors.Pretty+++-- | A helper type family for checking if a specific Souffle `Program` contains a certain `Fact`.+-- This will generate a user-friendly type error if this is not the case.+type family ContainsFact prog fact :: Constraint where+ ContainsFact prog fact =+ CheckContains prog (ProgramFacts prog) fact++type family CheckContains prog facts fact :: Constraint where+ CheckContains prog '[] fact =+ TypeError ("You tried to perform an action with a fact of type '" <> fact+ <> "' for program '" <> prog <> "'."+ % "The program contains the following facts: " <> ProgramFacts prog <> "."+ % "It does not contain fact: " <> fact <> "."+ % "You can fix this error by adding the type '" <> fact+ <> "' to the ProgramFacts type in the Program instance for " <> prog <> ".")+ CheckContains _ (a ': _) a = ()+ CheckContains prog (_ ': as) b = CheckContains prog as b++-- | A typeclass for describing a datalog program.+--+-- Example usage (assuming the program was generated from path.dl+-- and contains 2 facts: Edge and Reachable):+--+-- @+-- data Path = Path -- Handle for the datalog program+--+-- instance Program Path where+-- type ProgramFacts Path = '[Edge, Reachable]+-- factName = const "path"+-- @+class Program a where+ -- | A type level list of facts that belong to this program.+ -- This list is used to check that only known facts are added to a program.+ type ProgramFacts a :: [Type]++ -- | Function for obtaining the name of a Datalog program.+ -- This has to be the same as the name of the .dl file (minus the extension).+ --+ -- It uses a 'Proxy' to select the correct instance.+ programName :: Proxy a -> String++-- | A typeclass for data types representing a fact in datalog.+class Marshal.Marshal a => Fact a where+ -- | Function for obtaining the name of a fact+ -- (has to be the same as described in the Datalog program).+ --+ -- It uses a 'Proxy' to select the correct instance.+ --+ -- Example usage:+ --+ -- @+ -- instance Fact Edge where+ -- factName = const "edge"+ -- @+ factName :: Proxy a -> String+++-- | A mtl-style typeclass for Souffle-related actions.+class Monad m => MonadSouffle m where+ -- | Represents a handle for interacting with a Souffle program.+ -- See also `init`, which returns a handle of this type.+ type Handler m :: Type -> Type++ -- | Helper associated type constraint that allows collecting facts from+ -- Souffle in a list or vector. Only used internally.+ type CollectFacts m (c :: Type -> Type) :: Constraint++ {- | Initializes a Souffle program.++ The action will return 'Nothing' if it failed to load the Souffle C+++ program or if it failed to find the Souffle interpreter (depending on+ compiled/interpreted variant).+ Otherwise it will return a handle that can be used in other functions+ in this module.+ -}+ init :: Program prog => prog -> m (Maybe (Handler m prog))++ -- | Runs the Souffle program.+ run :: Handler m prog -> m ()++ -- | Sets the number of CPU cores this Souffle program should use.+ setNumThreads :: Handler m prog -> Word64 -> m ()++ -- | Gets the number of CPU cores this Souffle program should use.+ getNumThreads :: Handler m prog -> m Word64++ -- | Returns all facts of a program. This function makes use of type inference+ -- to select the type of fact to return.+ getFacts :: (Fact a, ContainsFact prog a, CollectFacts m c)+ => Handler m prog -> m (c a)++ -- | Searches for a fact in a program.+ -- Returns 'Nothing' if no matching fact was found; otherwise 'Just' the fact.+ --+ -- Conceptually equivalent to @List.find (== fact) \<$\> getFacts prog@,+ -- but this operation can be implemented much faster.+ findFact :: (Fact a, ContainsFact prog a, Eq a)+ => Handler m prog -> a -> m (Maybe a)++ -- | Adds a fact to the program.+ addFact :: (Fact a, ContainsFact prog a)+ => Handler m prog -> a -> m ()++ -- | Adds multiple facts to the program. This function could be implemented+ -- in terms of 'addFact', but this is done as a minor optimization.+ addFacts :: (Foldable t, Fact a, ContainsFact prog a)+ => Handler m prog -> t a -> m ()++instance MonadSouffle m => MonadSouffle (ReaderT r m) where+ type Handler (ReaderT r m) = Handler m+ type CollectFacts (ReaderT r m) c = CollectFacts m c++ init = lift . init+ {-# INLINABLE init #-}+ run = lift . run+ {-# INLINABLE run #-}+ setNumThreads prog = lift . setNumThreads prog+ {-# INLINABLE setNumThreads #-}+ getNumThreads = lift . getNumThreads+ {-# INLINABLE getNumThreads #-}+ getFacts = lift . getFacts+ {-# INLINABLE getFacts #-}+ findFact prog = lift . findFact prog+ {-# INLINABLE findFact #-}+ addFact fact = lift . addFact fact+ {-# INLINABLE addFact #-}+ addFacts facts = lift . addFacts facts+ {-# INLINABLE addFacts #-}++instance (Monoid w, MonadSouffle m) => MonadSouffle (WriterT w m) where+ type Handler (WriterT w m) = Handler m+ type CollectFacts (WriterT w m) c = CollectFacts m c++ init = lift . init+ {-# INLINABLE init #-}+ run = lift . run+ {-# INLINABLE run #-}+ setNumThreads prog = lift . setNumThreads prog+ {-# INLINABLE setNumThreads #-}+ getNumThreads = lift . getNumThreads+ {-# INLINABLE getNumThreads #-}+ getFacts = lift . getFacts+ {-# INLINABLE getFacts #-}+ findFact prog = lift . findFact prog+ {-# INLINABLE findFact #-}+ addFact fact = lift . addFact fact+ {-# INLINABLE addFact #-}+ addFacts facts = lift . addFacts facts+ {-# INLINABLE addFacts #-}++instance MonadSouffle m => MonadSouffle (StateT s m) where+ type Handler (StateT s m) = Handler m+ type CollectFacts (StateT s m) c = CollectFacts m c++ init = lift . init+ {-# INLINABLE init #-}+ run = lift . run+ {-# INLINABLE run #-}+ setNumThreads prog = lift . setNumThreads prog+ {-# INLINABLE setNumThreads #-}+ getNumThreads = lift . getNumThreads+ {-# INLINABLE getNumThreads #-}+ getFacts = lift . getFacts+ {-# INLINABLE getFacts #-}+ findFact prog = lift . findFact prog+ {-# INLINABLE findFact #-}+ addFact fact = lift . addFact fact+ {-# INLINABLE addFact #-}+ addFacts facts = lift . addFacts facts+ {-# INLINABLE addFacts #-}++instance (MonadSouffle m, Monoid w) => MonadSouffle (RWST r w s m) where+ type Handler (RWST r w s m) = Handler m+ type CollectFacts (RWST r w s m) c = CollectFacts m c++ init = lift . init+ {-# INLINABLE init #-}+ run = lift . run+ {-# INLINABLE run #-}+ setNumThreads prog = lift . setNumThreads prog+ {-# INLINABLE setNumThreads #-}+ getNumThreads = lift . getNumThreads+ {-# INLINABLE getNumThreads #-}+ getFacts = lift . getFacts+ {-# INLINABLE getFacts #-}+ findFact prog = lift . findFact prog+ {-# INLINABLE findFact #-}+ addFact fact = lift . addFact fact+ {-# INLINABLE addFact #-}+ addFacts facts = lift . addFacts facts+ {-# INLINABLE addFacts #-}++instance MonadSouffle m => MonadSouffle (ExceptT e m) where+ type Handler (ExceptT e m) = Handler m+ type CollectFacts (ExceptT e m) c = CollectFacts m c++ init = lift . init+ {-# INLINABLE init #-}+ run = lift . run+ {-# INLINABLE run #-}+ setNumThreads prog = lift . setNumThreads prog+ {-# INLINABLE setNumThreads #-}+ getNumThreads = lift . getNumThreads+ {-# INLINABLE getNumThreads #-}+ getFacts = lift . getFacts+ {-# INLINABLE getFacts #-}+ findFact prog = lift . findFact prog+ {-# INLINABLE findFact #-}+ addFact fact = lift . addFact fact+ {-# INLINABLE addFact #-}+ addFacts facts = lift . addFacts facts+ {-# INLINABLE addFacts #-}+++-- | A mtl-style typeclass for Souffle-related actions that involve file IO.+class MonadSouffle m => MonadSouffleFileIO m where+ -- | Load all facts from files in a certain directory.+ loadFiles :: Handler m prog -> FilePath -> m ()++ -- | Write out all facts of the program to CSV files+ -- (as defined in the Souffle program).+ writeFiles :: Handler m prog -> m ()++instance MonadSouffleFileIO m => MonadSouffleFileIO (ReaderT r m) where+ loadFiles prog = lift . loadFiles prog+ {-# INLINABLE loadFiles #-}+ writeFiles = lift . writeFiles+ {-# INLINABLE writeFiles #-}++instance (Monoid w, MonadSouffleFileIO m) => MonadSouffleFileIO (WriterT w m) where+ loadFiles prog = lift . loadFiles prog+ {-# INLINABLE loadFiles #-}+ writeFiles = lift . writeFiles+ {-# INLINABLE writeFiles #-}++instance MonadSouffleFileIO m => MonadSouffleFileIO (StateT s m) where+ loadFiles prog = lift . loadFiles prog+ {-# INLINABLE loadFiles #-}+ writeFiles = lift . writeFiles+ {-# INLINABLE writeFiles #-}++instance (MonadSouffleFileIO m, Monoid w) => MonadSouffleFileIO (RWST r w s m) where+ loadFiles prog = lift . loadFiles prog+ {-# INLINABLE loadFiles #-}+ writeFiles = lift . writeFiles+ {-# INLINABLE writeFiles #-}++instance MonadSouffleFileIO m => MonadSouffleFileIO (ExceptT s m) where+ loadFiles prog = lift . loadFiles prog+ {-# INLINABLE loadFiles #-}+ writeFiles = lift . writeFiles+ {-# INLINABLE writeFiles #-}
+ lib/Language/Souffle/Compiled.hs view
@@ -0,0 +1,192 @@+{-# OPTIONS_GHC -Wno-redundant-constraints #-}+{-# LANGUAGE TypeFamilies, TypeOperators, DerivingVia, InstanceSigs, BangPatterns #-}+{-# LANGUAGE DataKinds, FlexibleContexts #-}++-- | This module provides an implementation for the typeclasses defined in+-- "Language.Souffle.Class".+-- It makes use of the low level Souffle C++ API to offer a much more+-- performant alternative implementation to the implementation in+-- "Language.Souffle.Interpreted".+--+-- This module is mainly intended to be used after the prototyping phase is+-- over since the iteration cycle is slower due to the additional+-- C++ compilation times.+module Language.Souffle.Compiled+ ( Program(..)+ , Fact(..)+ , Marshal(..)+ , Handle+ , SouffleM+ , MonadSouffle(..)+ , runSouffle+ ) where++import Prelude hiding ( init )++import Control.Monad.Except+import Control.Monad.RWS.Strict+import Control.Monad.Reader+import Data.Foldable ( traverse_ )+import Data.Proxy+import qualified Data.Vector as V+import qualified Data.Vector.Mutable as MV+import Foreign.ForeignPtr+import Foreign.Ptr+import Language.Souffle.Class+import qualified Language.Souffle.Internal as Internal+import Language.Souffle.Marshal+++-- | A datatype representing a handle to a datalog program.+-- The type parameter is used for keeping track of which program+-- type the handle belongs to for additional type safety.+newtype Handle prog = Handle (ForeignPtr Internal.Souffle)++-- | A monad for executing Souffle-related actions in.+newtype SouffleM a+ = SouffleM+ { runSouffle :: IO a -- ^ Returns the underlying IO action.+ } deriving ( Functor, Applicative, Monad, MonadIO ) via IO++type Tuple = Ptr Internal.Tuple++-- | A monad transformer, used solely for marshalling and unmarshalling+-- between Haskell and Souffle Datalog.+newtype MarshalT m a = MarshalT (ReaderT Tuple m a)+ deriving ( Functor, Applicative, Monad+ , MonadIO, MonadReader Tuple, MonadWriter w+ , MonadState s, MonadRWS Tuple w s, MonadError e )+ via ( ReaderT Tuple m )+ deriving MonadTrans via (ReaderT Tuple)++runM :: Monad m => MarshalT m a -> Tuple -> m a+runM (MarshalT m) = runReaderT m+{-# INLINABLE runM #-}++-- | Execute the monad transformer and return the result.+-- The tuple that is passed in will be used to marshal the data back and forth.+runPushT :: MonadIO m => MarshalM PushF a -> Tuple -> m a+runPushT = runM . interpret pushAlgM where+ pushAlgM (PushInt int v) = do+ tuple <- ask+ liftIO $ Internal.tuplePushInt tuple int+ pure v+ pushAlgM (PushStr str v) = do+ tuple <- ask+ liftIO $ Internal.tuplePushString tuple str+ pure v+{-# INLINABLE runPushT #-}++-- | Execute the monad transformer and return the result.+-- The tuple that is passed in will be used to marshal the data back and forth.+runPopT :: MonadIO m => MarshalM PopF a -> Tuple -> m a+runPopT = runM . interpret popAlgM where+ popAlgM (PopStr f) = MarshalT $ do+ tuple <- ask+ str <- liftIO $ Internal.tuplePopString tuple+ pure $ f str+ popAlgM (PopInt f) = MarshalT $ do+ tuple <- ask+ int <- liftIO $ Internal.tuplePopInt tuple+ pure $ f int+{-# INLINABLE runPopT #-}++class Collect c where+ collect :: Marshal a => Int -> ForeignPtr Internal.RelationIterator -> IO (c a)++instance Collect [] where+ collect factCount = go 0 factCount []+ where+ go idx count acc _ | idx == count = pure acc+ go idx count !acc !it = do+ tuple <- Internal.relationIteratorNext it+ result <- runPopT pop tuple+ go (idx + 1) count (result : acc) it+ {-# INLINABLE collect #-}++instance Collect V.Vector where+ collect factCount iterator = do+ vec <- MV.unsafeNew factCount+ go vec 0 factCount iterator+ where+ go vec idx count _ | idx == count = V.unsafeFreeze vec+ go vec idx count it = do+ tuple <- Internal.relationIteratorNext it+ result <- runPopT pop tuple+ MV.unsafeWrite vec idx result+ go vec (idx + 1) count it+ {-# INLINABLE collect #-}++instance MonadSouffle SouffleM where+ type Handler SouffleM = Handle+ type CollectFacts SouffleM c = Collect c++ init :: forall prog. Program prog+ => prog -> SouffleM (Maybe (Handle prog))+ init _ =+ let progName = programName (Proxy :: Proxy prog)+ in SouffleM $ fmap Handle <$> Internal.init progName+ {-# INLINABLE init #-}++ run (Handle prog) = SouffleM $ Internal.run prog+ {-# INLINABLE run #-}++ setNumThreads (Handle prog) numCores =+ SouffleM $ Internal.setNumThreads prog numCores+ {-# INLINABLE setNumThreads #-}++ getNumThreads (Handle prog) =+ SouffleM $ Internal.getNumThreads prog+ {-# INLINABLE getNumThreads #-}++ addFact :: forall a prog. (Fact a, ContainsFact prog a)+ => Handle prog -> a -> SouffleM ()+ addFact (Handle prog) fact = liftIO $ do+ let relationName = factName (Proxy :: Proxy a)+ relation <- Internal.getRelation prog relationName+ addFact' relation fact+ {-# INLINABLE addFact #-}++ addFacts :: forall t a prog . (Foldable t, Fact a, ContainsFact prog a)+ => Handle prog -> t a -> SouffleM ()+ addFacts (Handle prog) facts = liftIO $ do+ let relationName = factName (Proxy :: Proxy a)+ relation <- Internal.getRelation prog relationName+ traverse_ (addFact' relation) facts+ {-# INLINABLE addFacts #-}++ getFacts :: forall a c prog. (Fact a, ContainsFact prog a, Collect c)+ => Handle prog -> SouffleM (c a)+ getFacts (Handle prog) = SouffleM $ do+ let relationName = factName (Proxy :: Proxy a)+ relation <- Internal.getRelation prog relationName+ factCount <- Internal.countFacts relation+ Internal.getRelationIterator relation >>= collect factCount+ {-# INLINABLE getFacts #-}++ findFact :: forall a prog. (Fact a, ContainsFact prog a)+ => Handle prog -> a -> SouffleM (Maybe a)+ findFact (Handle prog) a = SouffleM $ do+ let relationName = factName (Proxy :: Proxy a)+ relation <- Internal.getRelation prog relationName+ tuple <- Internal.allocTuple relation+ withForeignPtr tuple $ runPushT (push a)+ found <- Internal.containsTuple relation tuple+ pure $ if found then Just a else Nothing+ {-# INLINABLE findFact #-}++addFact' :: Fact a => Ptr Internal.Relation -> a -> IO ()+addFact' relation fact = do+ tuple <- Internal.allocTuple relation+ withForeignPtr tuple $ runPushT (push fact)+ Internal.addTuple relation tuple+{-# INLINABLE addFact' #-}+++instance MonadSouffleFileIO SouffleM where+ loadFiles (Handle prog) = SouffleM . Internal.loadAll prog+ {-# INLINABLE loadFiles #-}++ writeFiles (Handle prog) = SouffleM $ Internal.printAll prog+ {-# INLINABLE writeFiles #-}+
lib/Language/Souffle/Internal.hs view
@@ -1,6 +1,4 @@ -{-# Language LambdaCase #-}- -- | An internal module, providing a slightly higher level interface than -- "Language.Souffle.Internal.Bindings". -- It uses more commonly found data types instead of the low level C types
lib/Language/Souffle/Internal/Constraints.hs view
@@ -1,4 +1,3 @@- {-# LANGUAGE TypeFamilies, DataKinds, TypeOperators, UndecidableInstances #-} -- | A helper module for generating more user friendly type errors in the form@@ -25,7 +24,7 @@ -- structure of the data type. -- -- A type error is returned if the passed in type is not a simple product type--- consisting of only simple types like Int32 and String.+-- consisting of only simple types like Int32, String and Text. type family SimpleProduct (a :: Type) (f :: Type -> Type) :: Constraint where SimpleProduct a f = (ProductLike a f, OnlySimpleFields a f)
+ lib/Language/Souffle/Interpreted.hs view
@@ -0,0 +1,301 @@+{-# OPTIONS_GHC -Wno-redundant-constraints #-}+{-# LANGUAGE DataKinds, FlexibleContexts, TypeFamilies, DerivingVia, InstanceSigs #-}++-- | This module provides an implementation for the `MonadSouffle` typeclass+-- defined in "Language.Souffle.Class".+-- It makes use of the Souffle interpreter and CSV files to offer an+-- implementation optimized for quick development speed compared to+-- "Language.Souffle.Compiled".+--+-- It is however __much__ slower so users are advised to switch over to+-- the compiled alternative once the prototyping phase is finished.+module Language.Souffle.Interpreted+ ( Program(..)+ , Fact(..)+ , Marshal(..)+ , Config(..)+ , Handle+ , SouffleM+ , MonadSouffle(..)+ , runSouffle+ , runSouffleWith+ , defaultConfig+ , cleanup+ ) where++import Prelude hiding (init)++import Control.DeepSeq (deepseq)+import Control.Monad.State.Strict+import Control.Monad.Reader+import Data.IORef+import Data.Foldable (traverse_)+import Data.List hiding (init)+import Data.Semigroup (Last(..))+import Data.Maybe (fromMaybe)+import Data.Proxy+import qualified Data.Vector as V+import Data.Word+import Language.Souffle.Class+import Language.Souffle.Marshal+import System.Directory+import System.Environment+import System.Exit+import System.FilePath+import System.IO (hGetContents)+import System.IO.Temp+import System.Process+import Text.Printf+++-- | A monad for executing Souffle-related actions in.+newtype SouffleM a+ = SouffleM (ReaderT Config IO a)+ deriving (Functor, Applicative, Monad, MonadIO)+ via (ReaderT Config IO)++-- | A helper data type for storing the configurable settings of the+-- interpreter.+--+-- - __cfgDatalogDir__: The directory where the datalog file(s) are located.+-- - __cfgSouffleBin__: The name of the souffle binary. Has to be available in+-- \$PATH or an absolute path needs to be provided. Note: Passing in `Nothing`+-- will fail to start up the interpreter in the `MonadSouffle.init` function.+data Config+ = Config+ { cfgDatalogDir :: FilePath+ , cfgSouffleBin :: Maybe FilePath+ } deriving Show++-- | Retrieves the default config for the interpreter. These settings can+-- be overridden using record update syntax if needed.+--+-- By default, the settings will be configured as follows:+--+-- - __cfgDatalogDir__: Looks at environment variable \$DATALOG_DIR,+-- falls back to the current directory if not set.+-- - __cfgSouffleBin__: Looks at environment variable \$SOUFFLE_BIN,+-- or tries to locate the souffle binary using the which shell command+-- if the variable is not set.+defaultConfig :: MonadIO m => m Config+defaultConfig = liftIO $ do+ dlDir <- lookupEnv "DATALOG_DIR"+ envSouffleBin <- fmap Last <$> lookupEnv "SOUFFLE_BIN"+ locatedBin <- fmap Last <$> locateSouffle+ let souffleBin = getLast <$> locatedBin <> envSouffleBin+ pure $ Config (fromMaybe "." dlDir) souffleBin+{-# INLINABLE defaultConfig #-}++-- | Returns an IO action that will run the Souffle interpreter with+-- default settings (see `defaultConfig`).+runSouffle :: SouffleM a -> IO a+runSouffle m = do+ cfg <- defaultConfig+ runSouffleWith cfg m+{-# INLINABLE runSouffle #-}++-- | Returns an IO action that will run the Souffle interpreter with+-- the given interpreter settings.+runSouffleWith :: Config -> SouffleM a -> IO a+runSouffleWith cfg (SouffleM m) = runReaderT m cfg+{-# INLINABLE runSouffleWith #-}++-- | A datatype representing a handle to a datalog program.+-- The type parameter is used for keeping track of which program+-- type the handle belongs to for additional type safety.+newtype Handle prog = Handle (IORef HandleData)++-- | The data needed for the interpreter is the path where the souffle+-- executable can be found, and a template directory where the program+-- is stored.+data HandleData = HandleData+ { soufflePath :: FilePath+ , basePath :: FilePath+ , factPath :: FilePath+ , outputPath :: FilePath+ , datalogExec :: FilePath+ , noOfThreads :: Word64+ }++newtype IMarshal a = IMarshal (State [String] a)+ deriving+ ( Functor+ , Applicative+ , Monad+ , MonadState [String]+ )+ via (State [String])++popMarshalT :: MarshalM PopF a -> [String] -> a+popMarshalT = runM . interpret popAlgM where+ runM (IMarshal m) = evalState m+ popAlgM (PopStr f) = do+ str <- state (\case+ [] -> error "Empty fact stack"+ (h:t) -> (h, t))+ pure $ f str+ popAlgM (PopInt f) = do+ int <- state (\case+ [] -> error "Empty fact stack"+ (h:t) -> (read h, t))+ pure $ f int+{-# INLINABLE popMarshalT #-}++pushMarshalT :: MarshalM PushF a -> [String]+pushMarshalT = runM . interpret pushAlgM where+ runM (IMarshal m) = reverse $ execState m []+ pushAlgM (PushInt i v) = do+ modify (show i:)+ pure v+ pushAlgM (PushStr s v) = do+ modify (s:)+ pure v+{-# INLINABLE pushMarshalT #-}+++class Collect c where+ collect :: Marshal a => FilePath -> IO (c a)++instance Collect [] where+ collect factFile = do+ factLines <- readCSVFile factFile+ let facts = map (popMarshalT pop) factLines+ pure $! facts+ {-# INLINABLE collect #-}++instance Collect V.Vector where+ collect factFile = V.fromList <$!> collect factFile+ {-# INLINABLE collect #-}++instance MonadSouffle SouffleM where+ type Handler SouffleM = Handle+ type CollectFacts SouffleM c = Collect c++ init :: forall prog. Program prog => prog -> SouffleM (Maybe (Handle prog))+ init prg = SouffleM $ datalogProgramFile prg >>= \case+ Nothing -> pure Nothing+ Just datalogExecutable -> do+ souffleTempDir <- liftIO $ do+ tmpDir <- getCanonicalTemporaryDirectory+ createTempDirectory tmpDir "souffle-haskell"+ let factDir = souffleTempDir </> "fact"+ outDir = souffleTempDir </> "out"+ liftIO $ do+ createDirectoryIfMissing True factDir+ createDirectoryIfMissing True outDir+ mSouffleBin <- asks cfgSouffleBin+ liftIO $ forM mSouffleBin $ \souffleBin ->+ fmap Handle $ newIORef $ HandleData+ { soufflePath = souffleBin+ , basePath = souffleTempDir+ , factPath = factDir+ , outputPath = outDir+ , datalogExec = datalogExecutable+ , noOfThreads = 1+ }+ {-# INLINABLE init #-}++ run (Handle ref) = liftIO $ do+ handle <- readIORef ref+ -- Invoke the souffle binary using parameters, supposing that the facts+ -- are placed in the factPath, rendering the output into the outputPath.+ callCommand $+ printf "%s -F%s -D%s -j%d %s"+ (soufflePath handle)+ (factPath handle)+ (outputPath handle)+ (noOfThreads handle)+ (datalogExec handle)+ {-# INLINABLE run #-}++ setNumThreads (Handle ref) n = liftIO $+ modifyIORef' ref (\h -> h { noOfThreads = n })+ {-# INLINABLE setNumThreads #-}++ getNumThreads (Handle ref) = liftIO $+ noOfThreads <$> readIORef ref+ {-# INLINABLE getNumThreads #-}++ getFacts :: forall a c prog. (Marshal a, Fact a, ContainsFact prog a, Collect c)+ => Handle prog -> SouffleM (c a)+ getFacts (Handle ref) = liftIO $ do+ handle <- readIORef ref+ let relationName = factName (Proxy :: Proxy a)+ let factFile = outputPath handle </> relationName <.> "csv"+ facts <- collect factFile+ pure $! facts -- force facts before running to avoid issues with lazy IO+ {-# INLINABLE getFacts #-}++ findFact :: (Fact a, ContainsFact prog a, Eq a)+ => Handle prog -> a -> SouffleM (Maybe a)+ findFact prog fact = do+ facts :: [a] <- getFacts prog+ pure $ find (== fact) facts+ {-# INLINABLE findFact #-}++ addFact :: forall a prog. (Fact a, ContainsFact prog a, Marshal a)+ => Handle prog -> a -> SouffleM ()+ addFact (Handle ref) fact = liftIO $ do+ handle <- readIORef ref+ let relationName = factName (Proxy :: Proxy a)+ let factFile = factPath handle </> relationName <.> "facts"+ let line = pushMarshalT (push fact)+ appendFile factFile $ intercalate "\t" line ++ "\n"+ {-# INLINABLE addFact #-}++ addFacts :: forall a prog f. (Fact a, ContainsFact prog a, Marshal a, Foldable f)+ => Handle prog -> f a -> SouffleM ()+ addFacts (Handle ref) facts = SouffleM $ liftIO $ do+ handle <- readIORef ref+ let relationName = factName (Proxy :: Proxy a)+ let factFile = factPath handle </> relationName <.> "facts"+ let factLines = map (pushMarshalT . push) (foldMap pure facts)+ traverse_ (\line -> appendFile factFile (intercalate "\t" line ++ "\n")) factLines+ {-# INLINABLE addFacts #-}++datalogProgramFile :: forall prog. Program prog => prog -> ReaderT Config IO (Maybe FilePath)+datalogProgramFile _ = do+ dir <- asks cfgDatalogDir+ let dlFile = dir </> programName (Proxy :: Proxy prog) <.> "dl"+ liftIO $ doesFileExist dlFile >>= \case+ False -> pure Nothing+ True -> pure $ Just dlFile+{-# INLINABLE datalogProgramFile #-}++locateSouffle :: IO (Maybe FilePath)+locateSouffle = do+ let locateCmd = (shell "which souffle") { std_out = CreatePipe }+ (_, Just hout, _, locateCmdHandle) <- createProcess locateCmd+ waitForProcess locateCmdHandle >>= \case+ ExitFailure _ -> pure Nothing+ ExitSuccess ->+ words <$> hGetContents hout >>= \case+ [souffleBin] -> pure $ Just souffleBin+ _ -> pure Nothing+{-# INLINABLE locateSouffle #-}++readCSVFile :: FilePath -> IO [[String]]+readCSVFile path = doesFileExist path >>= \case+ False -> pure []+ True -> do+ contents <- readFile path+ -- deepseq needed to avoid issues with lazy IO+ pure $ contents `deepseq` (map (splitOn '\t') . lines) contents+{-# INLINABLE readCSVFile #-}++-- | Cleans up the temporary directory that this library has written files to.+-- This functionality is only provided for the interpreted version since the+-- compiled version directly (de-)serializes data via the C++ API.+cleanup :: forall prog. Program prog => Handle prog -> SouffleM ()+cleanup (Handle ref) = liftIO $ do+ handle <- readIORef ref+ traverse_ removeDirectoryRecursive [factPath handle, outputPath handle, basePath handle]+{-# INLINABLE cleanup #-}++splitOn :: Char -> String -> [String]+splitOn c s =+ let (x, rest) = break (== c) s+ rest' = drop 1 rest+ in x : splitOn c rest'+{-# INLINABLE splitOn #-}+
lib/Language/Souffle/Marshal.hs view
@@ -1,61 +1,61 @@- {-# OPTIONS_GHC -Wno-redundant-constraints #-}-{-# LANGUAGE DerivingVia, TypeFamilies #-}-{-# LANGUAGE FlexibleInstances, FlexibleContexts, DataKinds #-}-{-# LANGUAGE UndecidableInstances, DefaultSignatures #-}-{-# LANGUAGE ScopedTypeVariables, TypeOperators #-}+{-# LANGUAGE FlexibleInstances, FlexibleContexts, DeriveFunctor #-}+{-# LANGUAGE DefaultSignatures, TypeOperators, RankNTypes #-} -- | This module exposes a uniform interface to marshal values -- to and from Souffle Datalog. This is done via the 'Marshal' typeclass--- and 'MarshalT' monad transformer.+-- and 'MarshalM' monad. -- Also, a mechanism is exposed for generically deriving marshalling -- and unmarshalling code for simple product types. module Language.Souffle.Marshal- ( MarshalT- , runMarshalT- , Marshal(..)+ ( Marshal(..)+ , PushF(..)+ , PopF(..)+ , MarshalM+ , interpret ) where -import Control.Monad.Reader-import Control.Monad.Writer-import Control.Monad.State-import Control.Monad.Except-import Control.Monad.RWS+import Control.Monad.Free import GHC.Generics-import Foreign.Ptr import Data.Int import qualified Data.Text as T import qualified Data.Text.Lazy as TL-import qualified Language.Souffle.Internal as Internal import qualified Language.Souffle.Internal.Constraints as C -type Tuple = Ptr Internal.Tuple+-- | A data type used for deserializing a `Marshal`-able value+-- from Souffle to Haskell, only used internally.+data PopF a+ = PopInt (Int32 -> a)+ | PopStr (String -> a)+ deriving Functor --- | A monad transformer, used solely for marshalling and unmarshalling--- between Haskell and Souffle Datalog.-newtype MarshalT m a = MarshalT (ReaderT Tuple m a)- deriving ( Functor, Applicative, Monad- , MonadIO, MonadReader Tuple, MonadWriter w- , MonadState s, MonadRWS Tuple w s, MonadError e )- via ( ReaderT Tuple m )- deriving MonadTrans via (ReaderT Tuple)+-- | A data type used for serializing a `Marshal`-able value+-- from Haskell to Souffle, only used internally.+data PushF a+ = PushInt Int32 a+ | PushStr String a+ deriving Functor --- | Execute the monad transformer and return the result.--- The tuple that is passed in will be used to marshal the data back and forth.-runMarshalT :: MarshalT m a -> Tuple -> m a-runMarshalT (MarshalT m) = runReaderT m-{-# INLINABLE runMarshalT #-}+-- | The monad used for serializing and deserializing of values that+-- implement the `Marshal` typeclass.+type MarshalM = Free +-- | Helper function for interpreting the actual (de-)serialization of values.+-- This allows both the compiled and interpreted variant to handle+-- (de-)serialization in their own way.+interpret :: Monad m => (forall x. f x -> m x) -> MarshalM f a -> m a+interpret = foldFree+{-# INLINABLE interpret #-} {- | A typeclass for providing a uniform API to marshal/unmarshal values between Haskell and Souffle datalog. The marshalling is done via a stack-based approach, where elements are-pushed/popped one by one. The programmer needs to make sure that the-marshalling values happens in the correct order or unexpected things-might happen (including crashes). Pushing and popping of fields should-happen in the same order (from left to right, as defined in Datalog).+pushed/popped one by one. You need to make sure that the marshalling+of values happens in the correct order or unexpected things might happen+(including crashes). Pushing and popping of fields should happen in the+same order (from left to right, as defined in Datalog). Generic implementations for 'push' and 'pop' that perform the previously described behavior are available. This makes it possible to@@ -69,54 +69,46 @@ -} class Marshal a where -- | Marshals a value to the datalog side.- push :: MonadIO m => a -> MarshalT m ()+ push :: a -> MarshalM PushF () -- | Unmarshals a value from the datalog side.- pop :: MonadIO m => MarshalT m a+ pop :: MarshalM PopF a - default push :: (Generic a, C.SimpleProduct a (Rep a), GMarshal (Rep a), MonadIO m)- => a -> MarshalT m ()- default pop :: (Generic a, C.SimpleProduct a (Rep a), GMarshal (Rep a), MonadIO m)- => MarshalT m a+ default push :: (Generic a, C.SimpleProduct a (Rep a), GMarshal (Rep a))+ => a -> MarshalM PushF ()+ default pop :: (Generic a, C.SimpleProduct a (Rep a), GMarshal (Rep a))+ => MarshalM PopF a push a = gpush (from a) {-# INLINABLE push #-} pop = to <$> gpop {-# INLINABLE pop #-} instance Marshal Int32 where- push int = do- tuple <- ask- liftIO $ Internal.tuplePushInt tuple int+ push int = liftF (PushInt int ()) {-# INLINABLE push #-}- pop = do- tuple <- ask- liftIO $ Internal.tuplePopInt tuple+ pop = liftF (PopInt id) {-# INLINABLE pop #-} instance Marshal String where- push str = do- tuple <- ask- liftIO $ Internal.tuplePushString tuple str+ push str = liftF (PushStr str ()) {-# INLINABLE push #-}- pop = do- tuple <- ask- liftIO $ Internal.tuplePopString tuple+ pop = liftF (PopStr id) {-# INLINABLE pop #-} instance Marshal T.Text where push = push . T.unpack {-# INLINABLE push #-}- pop = T.pack <$> pop+ pop = T.pack <$> pop {-# INLINABLE pop #-} instance Marshal TL.Text where push = push . TL.unpack {-# INLINABLE push #-}- pop = TL.pack <$> pop+ pop = TL.pack <$> pop {-# INLINABLE pop #-} class GMarshal f where- gpush :: MonadIO m => f a -> MarshalT m ()- gpop :: MonadIO m => MarshalT m (f a)+ gpush :: f a -> MarshalM PushF ()+ gpop :: MarshalM PopF (f a) instance Marshal a => GMarshal (K1 i a) where gpush (K1 x) = push x@@ -137,4 +129,3 @@ {-# INLINABLE gpush #-} gpop = M1 <$> gpop {-# INLINABLE gpop #-}-
lib/Language/Souffle/TH.hs view
@@ -19,7 +19,7 @@ -- @ -- module Main where -- import Language.Haskell.TH.Syntax as Souffle--- Souffle.embedProgram "path/to/file.cpp" -- NOTE: call directly on top level!+-- Souffle.embedProgram "path\/to\/file.cpp" -- NOTE: call directly on top level! -- @ embedProgram :: String -> Q [Dec] embedProgram path = [] <$ qAddForeignFilePath LangCxx path
souffle-haskell.cabal view
@@ -1,13 +1,13 @@ cabal-version: 2.2 --- This file has been generated from package.yaml by hpack version 0.31.2.+-- This file has been generated from package.yaml by hpack version 0.33.0. -- -- see: https://github.com/sol/hpack ----- hash: 16e962a64944c83557da55c667cc898e3e27687753e1f4439a658668e50b015b+-- hash: 3cc0bdbc7d2a4e5c9d8aa3151d7c875a61de85bddc1f6d1136c77e58cb874c3e name: souffle-haskell-version: 0.1.0+version: 0.2.0 synopsis: Souffle Datalog bindings for Haskell description: Souffle Datalog bindings for Haskell. category: Logic Programming, Foreign Binding, Bindings@@ -15,7 +15,7 @@ bug-reports: https://github.com/luc-tielen/souffle-haskell/issues author: Luc Tielen maintainer: luc.tielen@gmail.com-copyright: 2019 Luc Tielen+copyright: 2020 Luc Tielen license: MIT license-file: LICENSE build-type: Simple@@ -32,9 +32,12 @@ library exposed-modules: Language.Souffle+ Language.Souffle.Class+ Language.Souffle.Compiled Language.Souffle.Internal Language.Souffle.Internal.Bindings Language.Souffle.Internal.Constraints+ Language.Souffle.Interpreted Language.Souffle.Marshal Language.Souffle.TH other-modules:@@ -43,7 +46,7 @@ Paths_souffle_haskell hs-source-dirs: lib- default-extensions: OverloadedStrings+ default-extensions: OverloadedStrings LambdaCase ScopedTypeVariables ghc-options: -Wall -Weverything -Wno-safe -Wno-unsafe -Wno-implicit-prelude -Wno-missed-specializations -Wno-all-missed-specializations -Wno-missing-import-lists -Wno-type-defaults -Wno-missing-local-signatures -Wno-monomorphism-restriction -Wno-missing-deriving-strategies -optP-Wno-nonportable-include-path -fhide-source-paths -fno-show-valid-hole-fits -fno-sort-valid-hole-fits cpp-options: -std=c++17 cxx-options: -Wall@@ -51,11 +54,20 @@ cbits/souffle.cpp build-depends: base >=4.12 && <5+ , deepseq >=1.4.4 && <2+ , directory >=1.3.3 && <2+ , filepath >=1.4.2 && <2+ , free >=5.1 && <6 , mtl >=2.0 && <3+ , process >=1.6 && <2 , template-haskell >=2 && <3+ , temporary >=1.3 && <2 , text >=1.0 && <2 , type-errors-pretty >=0.0.1.0 && <1 , vector <=1.0+ if os(linux)+ extra-libraries:+ stdc++ default-language: Haskell2010 build-tools: souffle@@ -64,24 +76,32 @@ type: exitcode-stdio-1.0 main-is: test.hs other-modules:- Test.TestSuiteSpec+ Test.Language.Souffle.CompiledSpec+ Test.Language.Souffle.InterpretedSpec Paths_souffle_haskell hs-source-dirs: tests- default-extensions: OverloadedStrings+ default-extensions: OverloadedStrings LambdaCase ScopedTypeVariables ghc-options: -Wall -Weverything -Wno-safe -Wno-unsafe -Wno-implicit-prelude -Wno-missed-specializations -Wno-all-missed-specializations -Wno-missing-import-lists -Wno-type-defaults -Wno-missing-local-signatures -Wno-monomorphism-restriction -Wno-missing-deriving-strategies -optP-Wno-nonportable-include-path -fhide-source-paths -fno-show-valid-hole-fits -fno-sort-valid-hole-fits cpp-options: -std=c++17 -D__EMBEDDED_SOUFFLE__- extra-libraries:- c++ build-tools: souffle build-depends: base >=4.12 && <5+ , deepseq >=1.4.4 && <2+ , directory >=1.3.3 && <2+ , filepath >=1.4.2 && <2+ , free >=5.1 && <6 , hspec >=2.6.1 && <3.0.0 , mtl >=2.0 && <3+ , process >=1.6 && <2 , souffle-haskell , template-haskell >=2 && <3+ , temporary >=1.3 && <2 , text >=1.0 && <2 , type-errors-pretty >=0.0.1.0 && <1 , vector <=1.0+ if os(darwin)+ extra-libraries:+ c++ default-language: Haskell2010
+ tests/Test/Language/Souffle/CompiledSpec.hs view
@@ -0,0 +1,175 @@+{-# LANGUAGE TemplateHaskell, ScopedTypeVariables, DataKinds #-}+{-# LANGUAGE TypeFamilies, DeriveGeneric #-}++module Test.Language.Souffle.CompiledSpec+ ( module Test.Language.Souffle.CompiledSpec+ ) where++import Test.Hspec+import GHC.Generics+import Data.Maybe+import qualified Data.Vector as V+import qualified Language.Souffle.TH as Souffle+import qualified Language.Souffle as Souffle++Souffle.embedProgram "tests/fixtures/path.cpp"++data Path = Path++data Edge = Edge String String+ deriving (Eq, Show, Generic)++data Reachable = Reachable String String+ deriving (Eq, Show, Generic)++instance Souffle.Program Path where+ type ProgramFacts Path = [Edge, Reachable]+ programName = const "path"++instance Souffle.Fact Edge where+ factName = const "edge"++instance Souffle.Fact Reachable where+ factName = const "reachable"++instance Souffle.Marshal Edge+instance Souffle.Marshal Reachable+++data BadPath = BadPath++instance Souffle.Program BadPath where+ type ProgramFacts BadPath = [Edge, Reachable]+ programName = const "bad_path"+++spec :: Spec+spec = describe "Souffle API" $ parallel $ do+ describe "init" $ parallel $ do+ it "returns nothing if it cannot load a souffle program" $ do+ prog <- Souffle.runSouffle (Souffle.init BadPath)+ isJust prog `shouldBe` False++ it "returns just the program if it can load a souffle program" $ do+ prog <- Souffle.runSouffle (Souffle.init Path)+ isJust prog `shouldBe` True++ describe "getFacts" $ parallel $ do+ it "can retrieve facts as a list" $ do+ (edges, reachables) <- Souffle.runSouffle $ do+ prog <- fromJust <$> Souffle.init Path+ Souffle.run prog+ es <- Souffle.getFacts prog+ rs <- Souffle.getFacts prog+ pure (es , rs)+ edges `shouldBe` [Edge "b" "c", Edge "a" "b"]+ reachables `shouldBe` [Reachable "b" "c", Reachable "a" "c", Reachable "a" "b"]++ it "can retrieve facts as a vector" $ do+ (edges, reachables) <- Souffle.runSouffle $ do+ prog <- fromJust <$> Souffle.init Path+ Souffle.run prog+ es <- Souffle.getFacts prog+ rs <- Souffle.getFacts prog+ pure (es , rs)+ edges `shouldBe` V.fromList [Edge "a" "b", Edge "b" "c"]+ reachables `shouldBe` V.fromList [Reachable "a" "b", Reachable "a" "c", Reachable "b" "c"]++ it "returns no facts if program hasn't run yet" $ do+ edges <- Souffle.runSouffle $ do+ prog <- fromJust <$> Souffle.init Path+ Souffle.getFacts prog+ edges `shouldBe` ([] :: [Edge])++ describe "addFact" $ parallel $ do+ it "adds a fact" $ do+ (edgesBefore, edgesAfter) <- Souffle.runSouffle $ do+ prog <- fromJust <$> Souffle.init Path+ Souffle.run prog+ es1 <- Souffle.getFacts prog+ Souffle.addFact prog $ Edge "e" "f"+ Souffle.run prog+ es2 <- Souffle.getFacts prog+ pure (es1, es2)+ edgesBefore `shouldBe` [Edge "b" "c", Edge "a" "b"]+ edgesAfter `shouldBe` [Edge "e" "f", Edge "b" "c", Edge "a" "b"]++ -- NOTE: this is different compared to interpreted version (bug in Souffle?)+ it "can add a fact even if it is marked as output" $ do+ reachables <- Souffle.runSouffle $ do+ prog <- fromJust <$> Souffle.init Path+ Souffle.addFact prog $ Reachable "e" "f"+ Souffle.run prog+ Souffle.getFacts prog+ reachables `shouldBe` [ Reachable "e" "f", Reachable "b" "c"+ , Reachable "a" "c", Reachable "a" "b" ]++ describe "addFacts" $ parallel $+ it "can add multiple facts at once" $ do+ (edgesBefore, edgesAfter) <- Souffle.runSouffle $ do+ prog <- fromJust <$> Souffle.init Path+ Souffle.run prog+ es1 <- Souffle.getFacts prog+ Souffle.addFacts prog [Edge "e" "f", Edge "f" "g"]+ Souffle.run prog+ es2 <- Souffle.getFacts prog+ pure (es1, es2)+ edgesBefore `shouldBe` [Edge "b" "c", Edge "a" "b"]+ edgesAfter `shouldBe` [Edge "f" "g", Edge "e" "f", Edge "b" "c", Edge "a" "b"]++ describe "run" $ parallel $ do+ it "is OK to run a program multiple times" $ do+ edges <- Souffle.runSouffle $ do+ prog <- fromJust <$> Souffle.init Path+ Souffle.run prog+ Souffle.run prog+ Souffle.getFacts prog+ edges `shouldBe` [Edge "b" "c", Edge "a" "b"]++ it "discovers new facts after running with new facts" $ do+ (reachablesBefore, reachablesAfter) <- Souffle.runSouffle $ do+ prog <- fromJust <$> Souffle.init Path+ Souffle.run prog+ rs1 <- Souffle.getFacts prog+ Souffle.addFacts prog [Edge "e" "f", Edge "f" "g"]+ Souffle.run prog+ rs2 <- Souffle.getFacts prog+ pure (rs1, rs2)+ reachablesBefore `shouldBe` [Reachable "b" "c", Reachable "a" "c", Reachable "a" "b"]+ reachablesAfter `shouldBe` [ Reachable "f" "g", Reachable "e" "g", Reachable "e" "f"+ , Reachable "b" "c",Reachable "a" "c", Reachable "a" "b" ]++ describe "configuring number of cores" $ parallel $+ it "is possible to configure number of cores" $ do+ results <- Souffle.runSouffle $ do+ prog <- fromJust <$> Souffle.init Path+ numCpus1 <- Souffle.getNumThreads prog+ Souffle.setNumThreads prog 4+ numCpus2 <- Souffle.getNumThreads prog+ Souffle.setNumThreads prog 2+ numCpus3 <- Souffle.getNumThreads prog+ pure (numCpus1, numCpus2, numCpus3)+ results `shouldBe` (1, 4, 2)++ describe "findFact" $ parallel $ do+ it "returns Nothing if no matching fact was found" $ do+ (edge, reachable) <- Souffle.runSouffle $ do+ prog <- fromJust <$> Souffle.init Path+ Souffle.run prog+ e <- Souffle.findFact prog $ Edge "c" "d"+ r <- Souffle.findFact prog $ Reachable "d" "e"+ pure (e, r)+ edge `shouldBe` Nothing+ reachable `shouldBe` Nothing++ it "returns Just the fact if matching fact was found" $ do+ (edge, reachable) <- Souffle.runSouffle $ do+ prog <- fromJust <$> Souffle.init Path+ Souffle.run prog+ e <- Souffle.findFact prog $ Edge "a" "b"+ r <- Souffle.findFact prog $ Reachable "a" "c"+ pure (e, r)+ edge `shouldBe` Just (Edge "a" "b")+ reachable `shouldBe` Just (Reachable "a" "c")++ -- TODO writeFiles / loadFiles
+ tests/Test/Language/Souffle/InterpretedSpec.hs view
@@ -0,0 +1,183 @@++{-# LANGUAGE ScopedTypeVariables, DataKinds, TypeFamilies, DeriveGeneric #-}++module Test.Language.Souffle.InterpretedSpec+ ( module Test.Language.Souffle.InterpretedSpec+ ) where++import Test.Hspec+import GHC.Generics+import Data.Maybe+import qualified Data.Vector as V+import qualified Language.Souffle.Interpreted as Souffle+++data Path = Path++data PathNoInput = PathNoInput -- doesn't mark edge as an input++data BadPath = BadPath++data Edge = Edge String String+ deriving (Eq, Show, Generic)++data Reachable = Reachable String String+ deriving (Eq, Show, Generic)++instance Souffle.Fact Edge where+ factName = const "edge"++instance Souffle.Fact Reachable where+ factName = const "reachable"++instance Souffle.Marshal Edge+instance Souffle.Marshal Reachable++instance Souffle.Program Path where+ type ProgramFacts Path = [Edge, Reachable]+ programName = const "path"++instance Souffle.Program PathNoInput where+ type ProgramFacts PathNoInput = [Edge, Reachable]+ programName = const "path_no_input"++instance Souffle.Program BadPath where+ type ProgramFacts BadPath = [Edge, Reachable]+ programName = const "bad_path"+++spec :: Spec+spec = describe "Souffle API" $ parallel $ do+ describe "init" $ parallel $ do+ it "returns nothing if it cannot load a souffle program" $ do+ prog <- Souffle.runSouffle (Souffle.init BadPath)+ isJust prog `shouldBe` False++ it "returns just the program if it can load a souffle program" $ do+ prog <- Souffle.runSouffle $ do+ handle <- fromJust <$> Souffle.init Path+ Souffle.cleanup handle+ pure $ Just handle+ isJust prog `shouldBe` True++ describe "getFacts" $ parallel $ do+ it "can retrieve facts as a list" $ do+ (edges, reachables) <- Souffle.runSouffle $ do+ prog <- fromJust <$> Souffle.init PathNoInput+ Souffle.run prog+ es <- Souffle.getFacts prog+ rs <- Souffle.getFacts prog+ Souffle.cleanup prog+ pure (es , rs)+ edges `shouldBe` [Edge "a" "b", Edge "b" "c"]+ reachables `shouldBe` [Reachable "a" "b", Reachable "a" "c", Reachable "b" "c"]++ it "can retrieve facts as a vector" $ do+ (edges, reachables) <- Souffle.runSouffle $ do+ prog <- fromJust <$> Souffle.init PathNoInput+ Souffle.run prog+ es <- Souffle.getFacts prog+ rs <- Souffle.getFacts prog+ Souffle.cleanup prog+ pure (es , rs)+ edges `shouldBe` V.fromList [Edge "a" "b", Edge "b" "c"]+ reachables `shouldBe` V.fromList [Reachable "a" "b", Reachable "a" "c", Reachable "b" "c"]++ it "returns no facts if program hasn't run yet" $ do+ edges <- Souffle.runSouffle $ do+ prog <- fromJust <$> Souffle.init Path+ results <- Souffle.getFacts prog+ Souffle.cleanup prog+ pure results+ edges `shouldBe` ([] :: [Edge])++ describe "addFact" $ parallel $ do+ it "adds a fact" $ do+ edges <- Souffle.runSouffle $ do+ prog <- fromJust <$> Souffle.init Path+ Souffle.addFact prog $ Edge "e" "f"+ Souffle.run prog+ Souffle.getFacts prog+ edges `shouldBe` [Edge "a" "b", Edge "b" "c", Edge "e" "f"]++ -- NOTE: this is different compared to compiled version (bug in Souffle?)+ it "can not add a fact if it is not marked as input" $ do+ reachables <- Souffle.runSouffle $ do+ prog <- fromJust <$> Souffle.init PathNoInput+ Souffle.addFact prog $ Reachable "e" "f"+ Souffle.run prog+ Souffle.getFacts prog+ reachables `shouldBe`+ [ Reachable "a" "b", Reachable "a" "c", Reachable "b" "c" ]++ describe "addFacts" $ parallel $+ it "can add multiple facts at once" $ do+ edges <- Souffle.runSouffle $ do+ prog <- fromJust <$> Souffle.init Path+ Souffle.addFacts prog [Edge "e" "f", Edge "f" "g"]+ Souffle.run prog+ Souffle.getFacts prog+ edges `shouldBe` [Edge "a" "b", Edge "b" "c", Edge "e" "f", Edge "f" "g"]++ describe "run" $ parallel $ do+ it "is OK to run a program multiple times" $ do+ edges <- Souffle.runSouffle $ do+ prog <- fromJust <$> Souffle.init PathNoInput+ Souffle.run prog+ Souffle.run prog+ facts <- Souffle.getFacts prog+ Souffle.cleanup prog+ pure facts+ edges `shouldBe` [Reachable "a" "b", Reachable "a" "c", Reachable "b" "c"]++ it "discovers new facts after running with new facts" $ do+ (reachablesBefore, reachablesAfter) <- Souffle.runSouffle $ do+ prog <- fromJust <$> Souffle.init Path+ Souffle.addFacts prog [Edge "c" "d"]+ Souffle.run prog+ rs1 <- Souffle.getFacts prog+ Souffle.addFacts prog [Edge "b" "e"]+ Souffle.run prog+ rs2 <- Souffle.getFacts prog+ Souffle.cleanup prog+ pure (rs1, rs2)+ reachablesBefore `shouldBe`+ [ Reachable "a" "b", Reachable "a" "c", Reachable "a" "d"+ , Reachable "b" "c", Reachable "b" "d", Reachable "c" "d" ]+ reachablesAfter `shouldBe`+ [ Reachable "a" "b", Reachable "a" "c", Reachable "a" "d"+ , Reachable "a" "e", Reachable "b" "c", Reachable "b" "d"+ , Reachable "b" "e", Reachable "c" "d" ]++ describe "configuring number of cores" $ parallel $+ it "is possible to configure number of cores" $ do+ results <- Souffle.runSouffle $ do+ prog <- fromJust <$> Souffle.init Path+ numCpus1 <- Souffle.getNumThreads prog+ Souffle.setNumThreads prog 4+ numCpus2 <- Souffle.getNumThreads prog+ Souffle.setNumThreads prog 2+ numCpus3 <- Souffle.getNumThreads prog+ pure (numCpus1, numCpus2, numCpus3)+ results `shouldBe` (1, 4, 2)++ describe "findFact" $ parallel $ do+ it "returns Nothing if no matching fact was found" $ do+ (edge, reachable) <- Souffle.runSouffle $ do+ prog <- fromJust <$> Souffle.init PathNoInput+ Souffle.run prog+ e <- Souffle.findFact prog $ Edge "c" "d"+ r <- Souffle.findFact prog $ Reachable "d" "e"+ pure (e, r)+ edge `shouldBe` Nothing+ reachable `shouldBe` Nothing++ it "returns Just the fact if matching fact was found" $ do+ (edge, reachable) <- Souffle.runSouffle $ do+ prog <- fromJust <$> Souffle.init PathNoInput+ Souffle.run prog+ e <- Souffle.findFact prog $ Edge "a" "b"+ r <- Souffle.findFact prog $ Reachable "a" "c"+ pure (e, r)+ edge `shouldBe` Just (Edge "a" "b")+ reachable `shouldBe` Just (Reachable "a" "c")
− tests/Test/TestSuiteSpec.hs
@@ -1,173 +0,0 @@--{-# LANGUAGE TemplateHaskell, ScopedTypeVariables, DataKinds #-}-{-# LANGUAGE TypeFamilies, DeriveGeneric #-}--module Test.TestSuiteSpec ( module Test.TestSuiteSpec ) where--import Test.Hspec-import GHC.Generics-import Data.Maybe-import qualified Language.Souffle.TH as Souffle-import qualified Language.Souffle as Souffle-import qualified Data.Vector as V--Souffle.embedProgram "tests/fixtures/path.cpp"--data Path = Path--data Edge = Edge String String- deriving (Eq, Show, Generic)--data Reachable = Reachable String String- deriving (Eq, Show, Generic)--instance Souffle.Program Path where- type ProgramFacts Path = [Edge, Reachable]- programName = const "path"--instance Souffle.Fact Edge where- factName = const "edge"--instance Souffle.Fact Reachable where- factName = const "reachable"--instance Souffle.Marshal Edge-instance Souffle.Marshal Reachable---data BadPath = BadPath--instance Souffle.Program BadPath where- type ProgramFacts BadPath = [Edge, Reachable]- programName = const "bad_path"---spec :: Spec-spec = describe "Souffle API" $ parallel $ do- describe "init" $ parallel $ do- it "returns nothing if it cannot load a souffle program" $ do- prog <- Souffle.runSouffle (Souffle.init BadPath)- isJust prog `shouldBe` False-- it "returns just the program if it can load a souffle program" $ do- prog <- Souffle.runSouffle (Souffle.init Path)- isJust prog `shouldBe` True-- describe "getFacts" $ parallel $ do- it "can retrieve facts as a list" $ do- (edges, reachables) <- Souffle.runSouffle $ do- prog <- fromJust <$> Souffle.init Path- Souffle.run prog- es <- Souffle.getFacts prog- rs <- Souffle.getFacts prog- pure (es , rs)- edges `shouldBe` [Edge "b" "c", Edge "a" "b"]- reachables `shouldBe` [Reachable "b" "c", Reachable "a" "c", Reachable "a" "b"]-- it "can retrieve facts as a vector" $ do- (edges, reachables) <- Souffle.runSouffle $ do- prog <- fromJust <$> Souffle.init Path- Souffle.run prog- es <- Souffle.getFacts prog- rs <- Souffle.getFacts prog- pure (es , rs)- edges `shouldBe` V.fromList [Edge "a" "b", Edge "b" "c"]- reachables `shouldBe` V.fromList [Reachable "a" "b", Reachable "a" "c", Reachable "b" "c"]-- it "returns no facts if program hasn't run yet" $ do- edges <- Souffle.runSouffle $ do- prog <- fromJust <$> Souffle.init Path- Souffle.getFacts prog- edges `shouldBe` ([] :: [Edge])-- describe "addFact" $ parallel $ do- it "adds a fact" $ do- (edgesBefore, edgesAfter) <- Souffle.runSouffle $ do- prog <- fromJust <$> Souffle.init Path- Souffle.run prog- es1 <- Souffle.getFacts prog- Souffle.addFact prog $ Edge "e" "f"- Souffle.run prog- es2 <- Souffle.getFacts prog- pure (es1, es2)- edgesBefore `shouldBe` [Edge "b" "c", Edge "a" "b"]- edgesAfter `shouldBe` [Edge "e" "f", Edge "b" "c", Edge "a" "b"]-- it "can add a fact even if it is marked as output" $ do- reachables <- Souffle.runSouffle $ do- prog <- fromJust <$> Souffle.init Path- Souffle.addFact prog $ Reachable "e" "f"- Souffle.run prog- Souffle.getFacts prog- reachables `shouldBe` [ Reachable "e" "f", Reachable "b" "c"- , Reachable "a" "c", Reachable "a" "b" ]-- describe "addFacts" $ parallel $- it "can add multiple facts at once" $ do- (edgesBefore, edgesAfter) <- Souffle.runSouffle $ do- prog <- fromJust <$> Souffle.init Path- Souffle.run prog- es1 <- Souffle.getFacts prog- Souffle.addFacts prog [Edge "e" "f", Edge "f" "g"]- Souffle.run prog- es2 <- Souffle.getFacts prog- pure (es1, es2)- edgesBefore `shouldBe` [Edge "b" "c", Edge "a" "b"]- edgesAfter `shouldBe` [Edge "f" "g", Edge "e" "f", Edge "b" "c", Edge "a" "b"]-- describe "run" $ parallel $ do- it "is OK to run a program multiple times" $ do- edges <- Souffle.runSouffle $ do- prog <- fromJust <$> Souffle.init Path- Souffle.run prog- Souffle.run prog- Souffle.getFacts prog- edges `shouldBe` [Edge "b" "c", Edge "a" "b"]-- it "discovers new facts after running with new facts" $ do- (reachablesBefore, reachablesAfter) <- Souffle.runSouffle $ do- prog <- fromJust <$> Souffle.init Path- Souffle.run prog- rs1 <- Souffle.getFacts prog- Souffle.addFacts prog [Edge "e" "f", Edge "f" "g"]- Souffle.run prog- rs2 <- Souffle.getFacts prog- pure (rs1, rs2)- reachablesBefore `shouldBe` [Reachable "b" "c", Reachable "a" "c", Reachable "a" "b"]- reachablesAfter `shouldBe` [ Reachable "f" "g", Reachable "e" "g", Reachable "e" "f"- , Reachable "b" "c",Reachable "a" "c", Reachable "a" "b" ]-- describe "configuring number of cores" $ parallel $- it "is possible to configure number of cores" $ do- results <- Souffle.runSouffle $ do- prog <- fromJust <$> Souffle.init Path- numCpus1 <- Souffle.getNumThreads prog- Souffle.setNumThreads prog 4- numCpus2 <- Souffle.getNumThreads prog- Souffle.setNumThreads prog 2- numCpus3 <- Souffle.getNumThreads prog- pure (numCpus1, numCpus2, numCpus3)- results `shouldBe` (1, 4, 2)-- describe "findFact" $ parallel $ do- it "returns Nothing if no matching fact was found" $ do- (edge, reachable) <- Souffle.runSouffle $ do- prog <- fromJust <$> Souffle.init Path- Souffle.run prog- e <- Souffle.findFact prog $ Edge "c" "d"- r <- Souffle.findFact prog $ Reachable "d" "e"- pure (e, r)- edge `shouldBe` Nothing- reachable `shouldBe` Nothing-- it "returns Just the fact if matching fact was found" $ do- (edge, reachable) <- Souffle.runSouffle $ do- prog <- fromJust <$> Souffle.init Path- Souffle.run prog- e <- Souffle.findFact prog $ Edge "a" "b"- r <- Souffle.findFact prog $ Reachable "a" "c"- pure (e, r)- edge `shouldBe` Just (Edge "a" "b")- reachable `shouldBe` Just (Reachable "a" "c")-- -- TODO writeFiles / loadFiles