{-# LANGUAGE CPP #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Language.Alloy.Internal.Call (
CallAlloyConfig (..),
SatSolver (..),
defaultCallAlloyConfig,
getRawInstances,
getRawInstancesWith,
) where
import qualified Data.ByteString as BS (
intercalate,
stripPrefix,
)
import qualified Data.ByteString.Char8 as BS (
hGetLine,
putStrLn,
unlines,
)
import Control.Concurrent (
threadDelay,
)
import Control.Concurrent.Async (
concurrently,
mapConcurrently_,
wait,
withAsync
)
import Control.Concurrent.Extra (Lock, newLock, withLock)
import Control.Exception (IOException, bracket, catch)
import Control.Monad (unless, when)
import Control.Monad.Extra (whenJust)
import Data.ByteString (ByteString)
import Data.ByteString.Char8 (unpack)
import Data.IORef (
IORef,
atomicWriteIORef,
#ifdef mingw32_HOST_OS
newIORef,
#endif
readIORef,
)
import Data.List (intercalate)
import Data.List.Split (splitOn)
import Data.Maybe (fromMaybe)
import System.Exit (ExitCode (..))
import System.FilePath
(searchPathSeparator)
import System.IO (
#ifndef mingw32_HOST_OS
BufferMode (..),
hSetBuffering,
#endif
Handle,
hClose,
hFlush,
hIsEOF,
hPutStr,
hPutStrLn,
stderr,
)
import System.IO.Unsafe (unsafePerformIO)
import System.Process (
CreateProcess (..), StdStream (..), ProcessHandle,
cleanupProcess,
createProcess, proc, terminateProcess, waitForProcess,
)
import Language.Alloy.RessourceNames (
className,
classPackage,
)
import Language.Alloy.Ressources (
alloyJar,
commonsCliJar,
slf4jJar,
)
import Paths_call_alloy (getDataDir)
data SatSolver
= BerkMin
| Glucose
| Glucose41
| Lingeling
| MiniSat
| MiniSatProver
| PLingeling
| SAT4J
| SAT4JLight
| SAT4JPMax
| Spear
deriving (SatSolver
SatSolver -> SatSolver -> Bounded SatSolver
forall a. a -> a -> Bounded a
$cminBound :: SatSolver
minBound :: SatSolver
$cmaxBound :: SatSolver
maxBound :: SatSolver
Bounded, Int -> SatSolver
SatSolver -> Int
SatSolver -> [SatSolver]
SatSolver -> SatSolver
SatSolver -> SatSolver -> [SatSolver]
SatSolver -> SatSolver -> SatSolver -> [SatSolver]
(SatSolver -> SatSolver)
-> (SatSolver -> SatSolver)
-> (Int -> SatSolver)
-> (SatSolver -> Int)
-> (SatSolver -> [SatSolver])
-> (SatSolver -> SatSolver -> [SatSolver])
-> (SatSolver -> SatSolver -> [SatSolver])
-> (SatSolver -> SatSolver -> SatSolver -> [SatSolver])
-> Enum SatSolver
forall a.
(a -> a)
-> (a -> a)
-> (Int -> a)
-> (a -> Int)
-> (a -> [a])
-> (a -> a -> [a])
-> (a -> a -> [a])
-> (a -> a -> a -> [a])
-> Enum a
$csucc :: SatSolver -> SatSolver
succ :: SatSolver -> SatSolver
$cpred :: SatSolver -> SatSolver
pred :: SatSolver -> SatSolver
$ctoEnum :: Int -> SatSolver
toEnum :: Int -> SatSolver
$cfromEnum :: SatSolver -> Int
fromEnum :: SatSolver -> Int
$cenumFrom :: SatSolver -> [SatSolver]
enumFrom :: SatSolver -> [SatSolver]
$cenumFromThen :: SatSolver -> SatSolver -> [SatSolver]
enumFromThen :: SatSolver -> SatSolver -> [SatSolver]
$cenumFromTo :: SatSolver -> SatSolver -> [SatSolver]
enumFromTo :: SatSolver -> SatSolver -> [SatSolver]
$cenumFromThenTo :: SatSolver -> SatSolver -> SatSolver -> [SatSolver]
enumFromThenTo :: SatSolver -> SatSolver -> SatSolver -> [SatSolver]
Enum, SatSolver -> SatSolver -> Bool
(SatSolver -> SatSolver -> Bool)
-> (SatSolver -> SatSolver -> Bool) -> Eq SatSolver
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: SatSolver -> SatSolver -> Bool
== :: SatSolver -> SatSolver -> Bool
$c/= :: SatSolver -> SatSolver -> Bool
/= :: SatSolver -> SatSolver -> Bool
Eq, ReadPrec [SatSolver]
ReadPrec SatSolver
Int -> ReadS SatSolver
ReadS [SatSolver]
(Int -> ReadS SatSolver)
-> ReadS [SatSolver]
-> ReadPrec SatSolver
-> ReadPrec [SatSolver]
-> Read SatSolver
forall a.
(Int -> ReadS a)
-> ReadS [a] -> ReadPrec a -> ReadPrec [a] -> Read a
$creadsPrec :: Int -> ReadS SatSolver
readsPrec :: Int -> ReadS SatSolver
$creadList :: ReadS [SatSolver]
readList :: ReadS [SatSolver]
$creadPrec :: ReadPrec SatSolver
readPrec :: ReadPrec SatSolver
$creadListPrec :: ReadPrec [SatSolver]
readListPrec :: ReadPrec [SatSolver]
Read, Int -> SatSolver -> [Char] -> [Char]
[SatSolver] -> [Char] -> [Char]
SatSolver -> [Char]
(Int -> SatSolver -> [Char] -> [Char])
-> (SatSolver -> [Char])
-> ([SatSolver] -> [Char] -> [Char])
-> Show SatSolver
forall a.
(Int -> a -> [Char] -> [Char])
-> (a -> [Char]) -> ([a] -> [Char] -> [Char]) -> Show a
$cshowsPrec :: Int -> SatSolver -> [Char] -> [Char]
showsPrec :: Int -> SatSolver -> [Char] -> [Char]
$cshow :: SatSolver -> [Char]
show :: SatSolver -> [Char]
$cshowList :: [SatSolver] -> [Char] -> [Char]
showList :: [SatSolver] -> [Char] -> [Char]
Show)
toParameter :: SatSolver -> String
toParameter :: SatSolver -> [Char]
toParameter = \case
SatSolver
BerkMin -> [Char]
"berkmin"
SatSolver
Glucose -> [Char]
"glucose"
SatSolver
Glucose41 -> [Char]
"glucose41"
SatSolver
Lingeling -> [Char]
"lingeling.parallel"
SatSolver
MiniSat -> [Char]
"minisat"
SatSolver
MiniSatProver -> [Char]
"minisat.prover"
SatSolver
PLingeling -> [Char]
"plingeling"
SatSolver
SAT4J -> [Char]
"sat4j"
SatSolver
SAT4JLight -> [Char]
"sat4j.light"
SatSolver
SAT4JPMax -> [Char]
"sat4j.pmax"
SatSolver
Spear -> [Char]
"spear"
data CallAlloyConfig =
CallAlloyConfig {
CallAlloyConfig -> Maybe Integer
maxInstances :: !(Maybe Integer),
CallAlloyConfig -> Bool
noOverflow :: !Bool,
CallAlloyConfig -> SatSolver
satSolver :: !SatSolver,
CallAlloyConfig -> Maybe Int
timeout :: !(Maybe Int)
}
defaultCallAlloyConfig :: CallAlloyConfig
defaultCallAlloyConfig :: CallAlloyConfig
defaultCallAlloyConfig = CallAlloyConfig {
maxInstances :: Maybe Integer
maxInstances = Maybe Integer
forall a. Maybe a
Nothing,
noOverflow :: Bool
noOverflow = Bool
True,
satSolver :: SatSolver
satSolver = SatSolver
SAT4J,
timeout :: Maybe Int
timeout = Maybe Int
forall a. Maybe a
Nothing
}
{-# NOINLINE outLock #-}
outLock :: Lock
outLock :: Lock
outLock = IO Lock -> Lock
forall a. IO a -> a
unsafePerformIO IO Lock
newLock
putOutLn :: String -> IO ()
putOutLn :: [Char] -> IO ()
putOutLn = Lock -> IO () -> IO ()
forall a. Lock -> IO a -> IO a
withLock Lock
outLock (IO () -> IO ()) -> ([Char] -> IO ()) -> [Char] -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Char] -> IO ()
putStrLn
putErrLn :: String -> IO ()
putErrLn :: [Char] -> IO ()
putErrLn = Lock -> IO () -> IO ()
forall a. Lock -> IO a -> IO a
withLock Lock
outLock (IO () -> IO ()) -> ([Char] -> IO ()) -> [Char] -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Handle -> [Char] -> IO ()
hPutStrLn Handle
stderr
getRawInstances
:: Maybe Integer
-> String
-> IO [ByteString]
getRawInstances :: Maybe Integer -> [Char] -> IO [ByteString]
getRawInstances Maybe Integer
maxIs = CallAlloyConfig -> [Char] -> IO [ByteString]
getRawInstancesWith CallAlloyConfig
defaultCallAlloyConfig {
maxInstances = maxIs
}
callAlloyWith
:: CallAlloyConfig
-> IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
callAlloyWith :: CallAlloyConfig
-> IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
callAlloyWith CallAlloyConfig
config = do
classPath <- IO [Char]
getClassPath
let callAlloy = [Char] -> [[Char]] -> CreateProcess
proc [Char]
"java"
([[Char]] -> CreateProcess) -> [[Char]] -> CreateProcess
forall a b. (a -> b) -> a -> b
$ [[Char]
"-cp", [Char]
classPath, [Char]
classPackage [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ Char
'.' Char -> [Char] -> [Char]
forall a. a -> [a] -> [a]
: [Char]
className,
[Char]
"-i", Integer -> [Char]
forall a. Show a => a -> [Char]
show (Integer -> [Char]) -> Integer -> [Char]
forall a b. (a -> b) -> a -> b
$ Integer -> Maybe Integer -> Integer
forall a. a -> Maybe a -> a
fromMaybe (-Integer
1) (Maybe Integer -> Integer) -> Maybe Integer -> Integer
forall a b. (a -> b) -> a -> b
$ CallAlloyConfig -> Maybe Integer
maxInstances CallAlloyConfig
config]
[[Char]] -> [[Char]] -> [[Char]]
forall a. [a] -> [a] -> [a]
++ [[Char]
"-o" | Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ CallAlloyConfig -> Bool
noOverflow CallAlloyConfig
config]
[[Char]] -> [[Char]] -> [[Char]]
forall a. [a] -> [a] -> [a]
++ [[Char]
"-s", SatSolver -> [Char]
toParameter (CallAlloyConfig -> SatSolver
satSolver CallAlloyConfig
config)]
createProcess callAlloy {
std_out = CreatePipe,
std_in = CreatePipe,
std_err = CreatePipe
}
getRawInstancesWith
:: CallAlloyConfig
-> String
-> IO [ByteString]
getRawInstancesWith :: CallAlloyConfig -> [Char] -> IO [ByteString]
getRawInstancesWith CallAlloyConfig
config [Char]
content
= IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
-> ((Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
-> IO ())
-> ((Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
-> IO [ByteString])
-> IO [ByteString]
forall a b c. IO a -> (a -> IO b) -> (a -> IO c) -> IO c
bracket (CallAlloyConfig
-> IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
callAlloyWith CallAlloyConfig
config) (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle) -> IO ()
cleanupProcess (((Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
-> IO [ByteString])
-> IO [ByteString])
-> ((Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
-> IO [ByteString])
-> IO [ByteString]
forall a b. (a -> b) -> a -> b
$ \(Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
p -> do
(Just hin, Just hout, Just herr, ph) <- (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
-> IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)
p
#ifndef mingw32_HOST_OS
hSetBuffering hin NoBuffering
let abort = Maybe a
forall a. Maybe a
Nothing
#else
abort <- Just <$> newIORef False
#endif
let evaluateAlloy' = do
Handle -> [Char] -> IO ()
hPutStr Handle
hin [Char]
content
Handle -> IO ()
hFlush Handle
hin
Handle -> IO ()
hClose Handle
hin
evaluateAlloy = IO () -> (IOException -> IO ()) -> IO ()
forall e a. Exception e => IO a -> (e -> IO a) -> IO a
catch IO ()
evaluateAlloy' ((IOException -> IO ()) -> IO ())
-> (IOException -> IO ()) -> IO ()
forall a b. (a -> b) -> a -> b
$ \IOException
e -> do
let err :: [Char]
err = IOException -> [Char]
forall a. Show a => a -> [Char]
show (IOException
e :: IOException)
warn :: [Char]
warn = [Char]
"Maybe not complete instance was sent to Alloy "
explain :: [Char]
explain = [Char]
"(Are timeouts set? Make sure they are not too small!): "
[Char] -> IO ()
putErrLn ([Char]
"Warning: " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
warn [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
explain [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
err)
withTimeout hin hout herr ph abort (timeout config) $ do
(out, err) <- fst <$> concurrently
(concurrently (getOutput hout) (getOutput herr))
evaluateAlloy
printContentOnError out abort ph
let err' = [ByteString] -> [ByteString]
removeInfoLines [ByteString]
err
unless (null err') $ fail $ unpack $ BS.unlines err'
return $ fmap (BS.intercalate "\n")
$ filterLast ((/= partialInstance) . last)
$ drop 1 $ splitOn [begin] out
where
begin :: ByteString
begin :: ByteString
begin = ByteString
"---Trace---"
filterLast :: (a -> Bool) -> [a] -> [a]
filterLast a -> Bool
_ [] = []
filterLast a -> Bool
p x :: [a]
x@[a
_] = (a -> Bool) -> [a] -> [a]
forall {a}. (a -> Bool) -> [a] -> [a]
filter a -> Bool
p [a]
x
filterLast a -> Bool
p (a
x:[a]
xs) = a
xa -> [a] -> [a]
forall a. a -> [a] -> [a]
:(a -> Bool) -> [a] -> [a]
filterLast a -> Bool
p [a]
xs
getOutput' :: Handle -> IO [ByteString]
getOutput' Handle
h = do
eof <- Handle -> IO Bool
hIsEOF Handle
h
if eof
then return []
else (:) <$> BS.hGetLine h <*> getOutput h
getOutput :: Handle -> IO [ByteString]
getOutput Handle
h = IO [ByteString]
-> (IOException -> IO [ByteString]) -> IO [ByteString]
forall e a. Exception e => IO a -> (e -> IO a) -> IO a
catch
(Handle -> IO [ByteString]
getOutput' Handle
h)
(\(IOException
_ :: IOException) -> [ByteString] -> IO [ByteString]
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return [ByteString
partialInstance])
printContentOnError :: [ByteString] -> Maybe (IORef Bool) -> ProcessHandle -> IO ()
printContentOnError [ByteString]
out Maybe (IORef Bool)
abort ProcessHandle
ph = do
code <- ProcessHandle -> IO ExitCode
waitForProcess ProcessHandle
ph
aborted <- maybe (return False) readIORef abort
when (code == ExitFailure 1 && not aborted)
$ putOutLn $ "Failed parsing the Alloy code?:\n" <> content
when (code == ExitFailure 2 && not aborted)
$ withLock outLock $ BS.putStrLn $ BS.unlines out
partialInstance :: ByteString
partialInstance :: ByteString
partialInstance = ByteString
"---PARTIAL_INSTANCE---"
removeInfoLines :: [ByteString] -> [ByteString]
removeInfoLines :: [ByteString] -> [ByteString]
removeInfoLines (ByteString
x:[ByteString]
xs)
| Just ByteString
_ <- ByteString -> ByteString -> Maybe ByteString
BS.stripPrefix ByteString
"[main] INFO" ByteString
x
= [ByteString] -> [ByteString]
removeInfoLines [ByteString]
xs
| Just ByteString
_ <- ByteString -> ByteString -> Maybe ByteString
BS.stripPrefix ByteString
"[main] WARN" ByteString
x
= [ByteString] -> [ByteString]
removeInfoLines [ByteString]
xs
| Just ByteString
_ <- ByteString -> ByteString -> Maybe ByteString
BS.stripPrefix ByteString
"[Finalizer] WARN" ByteString
x
= [ByteString] -> [ByteString]
removeInfoLines [ByteString]
xs
| ByteString
x ByteString -> ByteString -> Bool
forall a. Eq a => a -> a -> Bool
== ByteString
partialInstance
= [ByteString] -> [ByteString]
removeInfoLines [ByteString]
xs
removeInfoLines [ByteString]
xs = [ByteString]
xs
withTimeout
:: Handle
-> Handle
-> Handle
-> ProcessHandle
-> Maybe (IORef Bool)
-> Maybe Int
-> IO a
-> IO a
withTimeout :: forall a.
Handle
-> Handle
-> Handle
-> ProcessHandle
-> Maybe (IORef Bool)
-> Maybe Int
-> IO a
-> IO a
withTimeout Handle
_ Handle
_ Handle
_ ProcessHandle
_ Maybe (IORef Bool)
_ Maybe Int
Nothing IO a
p = IO a
p
withTimeout Handle
i Handle
o Handle
e ProcessHandle
ph Maybe (IORef Bool)
abort (Just Int
t) IO a
p = IO a -> (Async a -> IO a) -> IO a
forall a b. IO a -> (Async a -> IO b) -> IO b
withAsync IO a
p ((Async a -> IO a) -> IO a) -> (Async a -> IO a) -> IO a
forall a b. (a -> b) -> a -> b
$ \Async a
a -> do
Int -> IO ()
threadDelay Int
t
Maybe (IORef Bool) -> (IORef Bool -> IO ()) -> IO ()
forall (m :: * -> *) a.
Applicative m =>
Maybe a -> (a -> m ()) -> m ()
whenJust Maybe (IORef Bool)
abort (IORef Bool -> Bool -> IO ()
forall a. IORef a -> a -> IO ()
`atomicWriteIORef` Bool
True)
(IO () -> IO ()) -> [IO ()] -> IO ()
forall (f :: * -> *) a b. Foldable f => (a -> IO b) -> f a -> IO ()
mapConcurrently_ IO () -> IO ()
forall a. a -> a
id [
Handle -> IO ()
hClose Handle
e,
Handle -> IO ()
hClose Handle
o,
ProcessHandle -> IO ()
terminateProcess ProcessHandle
ph
]
Handle -> IO ()
hClose Handle
i
Async a -> IO a
forall a. Async a -> IO a
wait Async a
a
getClassPath :: IO FilePath
getClassPath :: IO [Char]
getClassPath =
[Char] -> [Char] -> [Char] -> [Char] -> [Char]
concatPaths ([Char] -> [Char] -> [Char] -> [Char] -> [Char])
-> IO [Char] -> IO ([Char] -> [Char] -> [Char] -> [Char])
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> IO [Char]
getDataDir IO ([Char] -> [Char] -> [Char] -> [Char])
-> IO [Char] -> IO ([Char] -> [Char] -> [Char])
forall a b. IO (a -> b) -> IO a -> IO b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> IO [Char]
alloyJar IO ([Char] -> [Char] -> [Char])
-> IO [Char] -> IO ([Char] -> [Char])
forall a b. IO (a -> b) -> IO a -> IO b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> IO [Char]
commonsCliJar IO ([Char] -> [Char]) -> IO [Char] -> IO [Char]
forall a b. IO (a -> b) -> IO a -> IO b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> IO [Char]
slf4jJar
where
concatPaths :: [Char] -> [Char] -> [Char] -> [Char] -> [Char]
concatPaths [Char]
w [Char]
x [Char]
y [Char]
z = [Char] -> [[Char]] -> [Char]
forall a. [a] -> [[a]] -> [a]
intercalate
[Char
searchPathSeparator]
[[Char]
w, [Char]
x, [Char]
y, [Char]
z]