Hi all,
I'm writing some code to interact with an ACL2 [0] process. I'd like
to be able to write a function
test :: String -> IO Bool
that will attempt to prove something by forking an ACL2 process and
screen scraping its output, to see whether the conjecture was proved.
The code below [1] is close: it launches the process, sends the
conjecture to ACL2, and reads ACL2's output line-by-line until it
finds "Proof succeeded" or "attempt has failed". This works well
enough for the simple case, but there is a problem.
In readACL2Answer, the code shouldn't have to check for success *and*
failure, since the conditions are complementary. But if I change that
code to check only for success, then it is possible (and indeed it
happens every time) that if I send ACL2 a conjecture it can't prove,
readACL2Answer blocks indefinitely -- and the print statements show
that hWaitForInput has said there is more data on the Handle but
hGetLine blocks trying to read it.
The reason for this, as far as I can tell, is that there *is* more
data -- a character, for example -- but no newline, so hGetLine blocks
waiting for a newline. However, if I'm reading the BufferMode
documentation [3] correctly, the fact that I've set my streams to
LineBuffering should mitigate that scenario.
Concretely, I know that ACL2 runs in a REPL and the last thing it
prints is in fact an incomplete line -- "ACL2 !>". I'd rather not
have to read every line character by character just to detect the
uncommon case.
Should I just do it character by character? Am I reading the
BufferMode docs wrong, and if so, what is the correct interpretation?
Is there better approach for the original problem I'm trying to solve?
Thanks for reading this far. =]
--
Denis
[0] http://www.cs.utexas.edu/users/moore/acl2/
[1]
import System.Process
import System.IO
type Proc = (Handle, Handle, Handle, ProcessHandle)
testThm = test "(equal x x)" -- something acl2 can prove
testBad = test "(equal x (not x))" -- something it can't
-- | Pass a conjecture form to ACL2, attempt to prove it, and return a boolean
-- indicating whether ACL2 was able to prove it.
test :: String -> IO Bool
test thm =
do i@(stdin, _, _, pid) <- setUpACL2
success <- testProc i thm
hPutStrLn stdin "(good-bye)"
putStrLn "saying goodbye"
exitCode <- waitForProcess pid
case exitCode of
ExitSuccess -> return success
ExitFailure i -> error ("ACL2 bad exit code: " ++ show i)
-- | Pass a conjecture vio the given process' input stream, and return whether
-- it is proved.
testProc :: Proc -> String -> IO Bool
testProc i@(stdin, stdout, _, pid) thm =
do hSetBuffering stdout LineBuffering
hSetBuffering stdin LineBuffering
success <- testInACL2 ("(thm " ++ thm ++ ")") i
putStrLn "found success value"
return success
succeedRx = mkRegex "Proof succeeded[.]"
failedRx = mkRegex "attempt has failed[.]"
-- create acl2 process and return handles
setUpACL2 :: IO Proc
setUpACL2 = runInteractiveCommand "acl2"
testInACL2 :: String -> Proc -> IO Bool
testInACL2 str (pin, pout, perr, pid) =
do putStrLn "printing to acl2..."
hPutStrLn pin str
putStrLn "getting acl2 result..."
readACL2Answer pout
-- | How long we should wait for ACL2 to start saying something. Five
-- seconds, currently.
readTimeout = 100
readACL2Answer :: Handle -> IO Bool
-- I *should* be required *only* to check for *success* in the matchRegex
-- statement below, instead of checking for success and failure. But if I do
-- that, and the theorem to be proved fails, this function blocks indefinitely
-- (at least I observe a much greater than 5 second wait period), even though
-- it should only block for `readTimeout' milliseconds. I'm not sure why this
-- is, but at least there is an acceptable workaround.
readACL2Answer pout = hWaitForInput pout readTimeout >>= (\isReady ->
if isReady
then do putStrLn "getting line"
line <- hGetLine pout
putStrLn $ "read line: " ++ line
-- Check for success or failure.
case (matchRegex succeedRx line, matchRegex failedRx line) of
(Nothing, Nothing) -> readACL2Answer pout
(Nothing, Just _) -> return False
(Just _, _) -> return True
else do putStrLn "not ready"
return False)
[3] http://haskell.org/ghc/docs/latest/html/libraries/base-3.0.0.0/System-IO.html#12