maude-0.1.2: Foreign/Maude.hs
-----------------------------------------------------------------------------
-- |
-- Module : Foreign.Maude
-- Copyright : (c) David Lazar, 2011
-- License : MIT
--
-- Maintainer : lazar6@illinois.edu
-- Stability : experimental
-- Portability : non-portable
--
-- This package provides a simple interface (via the 'rewrite' function) for
-- doing Maude rewrites from within Haskell. The following steps are
-- performed every time the 'rewrite' function is executed:
--
-- 1. A temporary file is created that contains the necessary commands.
--
-- 2. The temporary file (with any other input files) is executed by Maude.
--
-- 3. The temporary file is removed.
--
-- 4. The output from step 2 is parsed and returned.
--
-- This is a simple way to perform a single rewrite command, but it is
-- inefficient for performing many rewrite commands. See /Future Work/
-- below.
-----------------------------------------------------------------------------
module Foreign.Maude
(
-- * Invoking Maude
MaudeResult(..)
, rewrite
-- * Examples
-- $examples
-- * Future Work
-- $future
) where
import Data.List (break, stripPrefix)
import System.IO (hPutStrLn, hClose, openTempFile)
import System.Directory (getCurrentDirectory, removeFile)
import System.Process (readProcess)
-- | The result of a Maude rewrite.
data MaudeResult = MaudeResult
{ resultSort :: String -- ^ The sort of the rewritten term.
, resultTerm :: String -- ^ The rewritten term.
, statistics :: String -- ^ Statistics printed by Maude.
} deriving (Show)
-- | The name of the Maude executable. It should be in $PATH.
maudeCmd :: FilePath
maudeCmd = "maude"
-- | Maude option flags which force Maude's output to be as relevant as
-- possible.
maudeArgs :: [String]
maudeArgs =
[ "-no-banner"
, "-no-advise"
, "-no-wrap"
, "-no-ansi-color"
]
-- | The 'rewrite' function takes a list of Maude file names and a term, and
-- attempts to rewrite the term in the context of those files. It assumes
-- the command to run Maude is \"maude\" and that it located somewhere in
-- @$PATH@.
rewrite :: [FilePath] -> String -> IO (Maybe MaudeResult)
rewrite files term = do
runner <- newRunnerFile term
let args = maudeArgs ++ files ++ [runner]
out <- readProcess maudeCmd args []
removeFile runner
return $ parseMaudeResult out
-- | Parse Maude's output into a MaudeResult. The current implementation
-- does very little sanity checking and can not parse Maude failures.
parseMaudeResult :: String -> Maybe MaudeResult
parseMaudeResult str = case lines str of
(stats : res : _) -> do
r <- stripPrefix "result " res
let (sort, term) = break (==':') r
return $ MaudeResult
{ resultSort = sort
, resultTerm = drop 2 term
, statistics = stats
}
_ -> Nothing
-- | Create a temporary file which contains the commands Maude should run at
-- startup, namely some formatting commands, the rewrite command, and quit.
newRunnerFile :: String -> IO FilePath
newRunnerFile term = do
currDir <- getCurrentDirectory
(tmpf, tmph) <- openTempFile currDir "runner.maude"
hPutStrLn tmph "set print with parentheses on ."
hPutStrLn tmph "set show command off ."
hPutStrLn tmph $ "rewrite " ++ term ++ " ."
hPutStrLn tmph "quit"
hClose tmph
return tmpf
{- $examples
Maude's standard prelude is loaded by default, even when no input files are
specified:
>>> rewrite [] "not (A:Bool or B:Bool) implies (not A:Bool) and (not B:Bool)"
Just (MaudeResult
{ resultSort = "Bool"
, resultTerm = "true"
, statistics = "rewrites: 13 in 0ms cpu (0ms real) (~ rewrites/second)"
})
The name of the module in which to reduce a term can be given explicitly:
>>> rewrite [] "in NAT-LIST : reverse(1 2 3 4)"
Just (MaudeResult
{ resultSort = "NeNatList"
, resultTerm = "(4 (3 (2 1)))"
, statistics = "rewrites: 6 in 0ms cpu (0ms real) (~ rewrites/second)"
})
Notice that the resulting list is given in parenthesized form. This is
because
> set print with parentheses on .
is executed before every rewrite.
Using a naive implementation of primes in Maude:
>>> rewrite ["primes.maude"] "2 .. 20"
Just (MaudeResult
{ resultSort = "PrimeSet"
, resultTerm = "(2 (3 (5 (7 (11 (13 (17 19)))))))"
, statistics = "rewrites: 185 in 0ms cpu (0ms real) (~ rewrites/second)"
})
If we are only interested in the statistics:
>>> liftM statistics <$> rewrite ["primes.maude"] "2 .. 1000"
Just "rewrites: 409905 in 839ms cpu (856ms real) (488014 rewrites/second)"
-}
{- $future
This Maude interface is very minimal first step. It could be extended in the
following ways:
* Better handling of Maude failures. Failure messages should be parsed and
returned to the user.
* The rewriting environment should be configurable. This would allow
parenthesized mode to be turned off and for the location of the Maude
binary to be customized.
* Support for other Maude commands besides @rewrite@.
* A Maude monad that handles failure and multiple Maude commands efficiently
is a long-term goal for this package.
-}