generated from tweag/project
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathProcess.hs
182 lines (165 loc) · 5.95 KB
/
Process.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE ViewPatterns #-}
-- | A module providing a backend that launches solvers as external processes.
module SMTLIB.Backends.Process
( Config (..),
Handle (..),
defaultConfig,
new,
close,
with,
toBackend,
P.StdStream (..),
)
where
import qualified Control.Exception as X
import Data.ByteString.Builder
( Builder,
byteString,
hPutBuilder,
toLazyByteString,
)
import qualified Data.ByteString.Char8 as BS
import GHC.IO.Exception (IOException (ioe_description))
import SMTLIB.Backends (Backend (..))
import qualified System.IO as IO
import qualified System.Process as P
data Config = Config
{ -- | The command to call to run the solver.
exe :: String,
-- | Arguments to pass to the solver's command.
args :: [String],
-- | How to handle std_err of the solver.
std_err :: P.StdStream
}
-- | By default, use Z3 as an external process and ignores log messages.
defaultConfig :: Config
-- if you change this, make sure to also update the comment two lines above
-- as well as the one in @smtlib-backends-process/tests/Examples.hs@
defaultConfig = Config "z3" ["-in"] P.CreatePipe
data Handle = Handle
{ -- | The process running the solver.
process :: P.ProcessHandle,
-- | The input channel of the process.
hIn :: IO.Handle,
-- | The output channel of the process.
hOut :: IO.Handle,
-- | The error channel of the process.
hMaybeErr :: Maybe IO.Handle
}
-- | Run a solver as a process.
new ::
-- | The solver process' configuration.
Config ->
IO Handle
new Config {..} = decorateIOError "creating the solver process" $ do
(Just hIn, Just hOut, hMaybeErr, process) <-
P.createProcess
(P.proc exe args)
{ P.std_in = P.CreatePipe,
P.std_out = P.CreatePipe,
P.std_err = std_err
}
mapM_ setupHandle [hIn, hOut]
-- log error messages created by the backend
return $ Handle process hIn hOut hMaybeErr
where
setupHandle h = do
IO.hSetBinaryMode h True
IO.hSetBuffering h $ IO.BlockBuffering Nothing
-- | Send a command to the process without reading its response.
write :: Handle -> Builder -> IO ()
write Handle {..} cmd =
decorateIOError msg $ do
hPutBuilder hIn $ cmd <> "\n"
IO.hFlush hIn
where
msg = "sending command " ++ show (toLazyByteString cmd) ++ " to the solver"
-- | Cleanup the process' resources, terminate it and wait for it to actually exit.
close :: Handle -> IO ()
close Handle {..} = decorateIOError "closing the solver process" $ do
P.cleanupProcess (Just hIn, Just hOut, hMaybeErr, process)
-- | Create a solver process, use it to make a computation and close it.
with ::
-- | The solver process' configuration.
Config ->
-- | The computation to run with the solver process
(Handle -> IO a) ->
IO a
with config = X.bracket (new config) close
infixr 5 :<
pattern (:<) :: Char -> BS.ByteString -> BS.ByteString
pattern c :< rest <- (BS.uncons -> Just (c, rest))
-- | Make the solver process into an SMT-LIB backend.
toBackend :: Handle -> Backend
toBackend handle = Backend backendSend backendSend_
where
backendSend_ = write handle
backendSend cmd = do
-- exceptions are decorated inside the body of 'write'
write handle cmd
decorateIOError "reading solver's response" $
toLazyByteString
<$> continueNextLine (scanParen 0) mempty
-- scanParen read lines from the handle's output channel until it has detected
-- a complete s-expression, i.e. a well-parenthesized word that may contain
-- strings, quoted symbols, and comments
-- if we detect a ')' at depth 0 that is not enclosed in a string, a quoted
-- symbol or a comment, we give up and return immediately
-- see also the SMT-LIB standard v2.6
-- https://smtlib.cs.uiowa.edu/papers/smt-lib-reference-v2.6-r2021-05-12.pdf#part.2
scanParen :: Int -> Builder -> BS.ByteString -> IO Builder
scanParen depth acc ('(' :< more) = scanParen (depth + 1) acc more
scanParen depth acc ('"' :< more) = do
(acc', more') <- string acc more
scanParen depth acc' more'
scanParen depth acc ('|' :< more) = do
(acc', more') <- quotedSymbol acc more
scanParen depth acc' more'
scanParen depth acc (';' :< _) = continueNextLine (scanParen depth) acc
scanParen depth acc (')' :< more)
| depth <= 1 = return acc
| otherwise = scanParen (depth - 1) acc more
scanParen depth acc (_ :< more) = scanParen depth acc more
-- mempty case
scanParen 0 acc _ = return acc
scanParen depth acc _ = continueNextLine (scanParen depth) acc
string :: Builder -> BS.ByteString -> IO (Builder, BS.ByteString)
string acc ('"' :< '"' :< more) = string acc more
string acc ('"' :< more) = return (acc, more)
string acc (_ :< more) = string acc more
-- mempty case
string acc _ = continueNextLine string acc
quotedSymbol :: Builder -> BS.ByteString -> IO (Builder, BS.ByteString)
quotedSymbol acc ('|' :< more) = return (acc, more)
quotedSymbol acc (_ :< more) = string acc more
-- mempty case
quotedSymbol acc _ = continueNextLine quotedSymbol acc
continueNextLine :: (Builder -> BS.ByteString -> IO a) -> Builder -> IO a
continueNextLine f acc = do
next <-
BS.hGetLine (hOut handle) `X.catch` \ex ->
X.throwIO
( ex
{ ioe_description =
ioe_description ex
++ ": "
++ show (toLazyByteString acc)
}
)
f (acc <> byteString next) next
decorateIOError :: String -> IO a -> IO a
decorateIOError contextDescription =
X.handle $ \ex ->
X.throwIO
( ex
{ ioe_description =
"[smtlib-backends-process] while "
++ contextDescription
++ ": "
++ ioe_description ex
}
)