allen-0.1.0.0: app/Main.hs
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
module Main (main) where
import Control.Monad
import Control.Monad.State
import Data.Aeson
import Data.Allen
import Data.List (sortBy)
import Data.Ord (comparing)
import Data.Text (Text, pack, unpack)
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import qualified Data.ByteString as BS
import GHC.Generics (Generic)
import System.IO
import System.Environment
data RelationJSON = RelationJSON { relationFrom :: Int
, relationTo :: Int
, relChars :: Text
} deriving Generic
data NetworkJSON = NetworkJSON { ivCount :: Int
, relations :: [RelationJSON]
}
data IntervalJSON = IntervalJSON { ivID :: Int
, ivRels :: [RelationJSON]
} deriving Generic
newtype ResultJSON = ResultJSON { result :: [IntervalJSON] } deriving Generic
instance ToJSON RelationJSON where
toJSON (RelationJSON a b r) = object [ "from" .= a, "to" .= b, "relation" .= r ]
instance ToJSON IntervalJSON where
toJSON (IntervalJSON i r) = object [ "id" .= i, "relations" .= r ]
instance ToJSON ResultJSON where
toJSON (ResultJSON r) = object [ "intervals" .= r ]
instance FromJSON RelationJSON where
parseJSON = withObject "RelationJSON" $ \v -> RelationJSON
<$> v .: "from"
<*> v .: "to"
<*> v .: "relation"
instance FromJSON NetworkJSON where
parseJSON = withObject "NetworkJSON" $ \v -> NetworkJSON
<$> v .: "intervals"
<*> v .: "relations"
data REPLState = REPLState { graph :: Allen (), intervalNames :: Map String IntervalID }
type REPL = StateT REPLState IO
newREPLState :: REPLState
newREPLState = REPLState (return ()) Map.empty
withIntervals :: String -> String -> (IntervalID -> IntervalID -> REPL ()) -> REPL ()
withIntervals a b func = do
names <- gets intervalNames
case (Map.lookup a names, Map.lookup b names) of
(Just aID, Just bID) -> func aID bID
(Nothing, _) -> liftIO $ putStrLn $ "Interval " <> a <> " does not exist"
(_, Nothing) -> liftIO $ putStrLn $ "Interval " <> b <> " does not exist"
createInterval :: String -> REPL ()
createInterval name = do
REPLState calc names <- get
case Map.lookup name names of
Just _ -> error $ "Interval " ++ name ++ " already exists"
Nothing -> do
let newID = Map.size names
put $ REPLState (void $ calc >> interval) (Map.insert name newID names)
assumeRelation :: String -> RelationBits -> String -> REPL ()
assumeRelation a r b = do
REPLState calc names <- get
withIntervals a b $ \aID bID -> do
put $ REPLState (calc >> assumeBits aID r bID) names
showConstraints :: String -> String -> REPL ()
showConstraints a b = do
calc <- gets graph
withIntervals a b $ \aID bID -> do
let constraints = fromBits $ evalAllen $ calc >> getConstraints aID bID
liftIO $ putStrLn $ a <> " --(" <> map relationToChar constraints <> ")--> " <> b
showGraph :: REPL ()
showGraph = do
REPLState calc names <- get
let sorted = sortBy (comparing fst) $ Map.toList names
liftIO $ mapM_ printPair sorted
liftIO $ putStrLn "---------------------------------------------"
liftIO $ mapM_ (print . snd) $ Map.toList $ execAllen calc
where printPair (name, iD) = putStrLn $ show iD <> ": " <> name
resetGraph :: REPL ()
resetGraph = put newREPLState
data Command = CreateInterval String
| AssumeRelation String RelationBits String
| GetConstraints String String
| ShowGraph
| ResetGraph
| Help
| Quit
| InvalidCommand
parseCommand :: String -> Command
parseCommand str = case words str of
[ "create", name ] -> CreateInterval name
[ "assume", a, rel, b ] -> AssumeRelation a (bitsFromString rel) b
[ "constraints", a, b ] -> GetConstraints a b
[ "graph" ] -> ShowGraph
[ "clear" ] -> ResetGraph
[ "help" ] -> Help
[ "exit" ] -> Quit
_ -> InvalidCommand
readCommand :: IO Command
readCommand = do
putStr "Allen > "
command <- parseCommand <$> getLine
case command of
InvalidCommand -> do
putStrLn "Invalid command"
readCommand
_ -> return command
printHelp :: IO ()
printHelp = do
putStrLn "Commands:"
putStrLn " create <name> Create a new interval with the given name"
putStrLn " assume <a> <rel> <b> Assume that a and b have the given relation"
putStrLn " (Use: pmoFDseSdfoMP to represent relations)"
putStrLn " constraints <a> <b> Show the constraints between a and b"
putStrLn " graph Show the current graph"
putStrLn " clear Clear the current graph"
putStrLn " help Show this help message"
putStrLn " exit Exit the program"
repl :: REPL ()
repl = do
command <- liftIO readCommand
case command of
CreateInterval name -> createInterval name >> repl
AssumeRelation a r b -> assumeRelation a r b >> repl
GetConstraints a b -> showConstraints a b >> repl
ShowGraph -> showGraph >> repl
ResetGraph -> resetGraph >> repl
Help -> liftIO printHelp >> repl
Quit -> return ()
InvalidCommand -> do
liftIO $ putStrLn "Error: Invalid Command"
repl
addRelationJSON :: RelationJSON -> Allen ()
addRelationJSON rel = assumeBits a r b
where a = relationFrom rel
b = relationTo rel
r = bitsFromString $ unpack $ relChars rel
jsonToGraph :: NetworkJSON -> IntervalGraph
jsonToGraph network = execAllen $ do
replicateM_ (ivCount network) interval
mapM_ addRelationJSON (relations network)
toResultJSON :: IntervalGraph -> ResultJSON
toResultJSON = ResultJSON . map toIntervalJSON . Map.toList
where toIntervalJSON (iD, iv ) = IntervalJSON iD (map toRelationJSON $ Map.toList $ intervalRelations iv)
toRelationJSON (iD, bits) = RelationJSON iD (iD + 1) (pack $ map relationToChar $ fromBits bits)
calcJSON :: NetworkJSON -> ResultJSON
calcJSON = toResultJSON . jsonToGraph
startRepl :: IO ()
startRepl = do
putStrLn "Interactive Allen's Interval Algebra Solver"
putStrLn "Author: Archaversine"
putStrLn "Type 'help' for a list of commands"
putStrLn "------------------------------------------"
evalStateT repl newREPLState
main :: IO ()
main = do
-- Fix buffering so that we can see the prompt
hSetBuffering stdout NoBuffering
args <- getArgs
case args of
[ ] -> startRepl
[from, to] -> do
contents <- BS.readFile from
let decoded = eitherDecode (BS.fromStrict contents) :: Either String NetworkJSON
case decoded of
Left m -> errorWithoutStackTrace m
Right j -> BS.writeFile to (BS.toStrict $ encode $ calcJSON j)
_ -> do
putStrLn "Usage: allen [input] [output]"
putStrLn " allen"
errorWithoutStackTrace "Invalid Arguments."