ftshell-0.2: src/Commands.hs
-- Defines the commands which are not connected to a particular type signature.
module Commands where
import Control.Monad (liftM, when)
import Control.Monad.Trans (liftIO)
import Data.List (isPrefixOf, intersperse, sort, transpose)
import Data.Map as Map (fromList, keys, lookup, elems)
import Data.Maybe (fromJust)
import Language.Haskell.FreeTheorems
import Language.Haskell.FreeTheorems.Syntax
import Language.Haskell.FreeTheorems.Theorems
import System.Console.Shell
import System.Console.Shell.ShellMonad
import Text.PrettyPrint
import ShellState
------- File handling ---------------------------------------------------------
-- Loads a file of declarations and shows a message of statistics afterwards.
cmdLoad :: File -> Sh ShellState ()
cmdLoad (File fileName) = do
state <- getShellSt
state' <- liftIO (loadDeclarations fileName state)
modifyShellSt (const state')
-- Unloads an already loaded file.
cmdUnload :: Completable LoadedFile -> Sh ShellState ()
cmdUnload (Completable file) = do
files <- fromShellSt getDeclarationsFiles
let removeFile st = st { getDeclarationsFiles = filter (/= file) files }
when (file `elem` files)
(do modifyShellSt removeFile
shellPutStrLn $ "Unloaded `" ++ file ++ "'."
cmdReload)
-- A tag to identify names of loaded files.
data LoadedFile = LoadedFile
instance Completion LoadedFile ShellState where
complete _ st input = return $ sort $ filter (isPrefixOf input)
(getDeclarationsFiles st)
completableLabel _ = "<file>"
-- Resets the shell state and reloads all files.
-- Resetting deletes all previous user input.
cmdReload :: Sh ShellState ()
cmdReload = do
files <- fromShellSt getDeclarationsFiles
modifyShellSt reset
mapM_ (cmdLoad . File) files
-- Shows the names of all loaded files.
cmdShowLoadedFiles :: Sh ShellState ()
cmdShowLoadedFiles = do
files <- fromShellSt getDeclarationsFiles
if null files
then shellPutStrLn "There are no loaded files."
else mapM_ shellPutStrLn files
------- Declarations ----------------------------------------------------------
-- Displays the names of all valid declarations per input file.
-- Names are shown in groups for algebraic data types, type renamings, type
-- synonyms, type classes and type signatures.
-- Names are shown columnwise for each group.
cmdShowAllDeclarations :: Sh ShellState ()
cmdShowAllDeclarations = do
files <- fromShellSt getDeclarationsFiles
strings <- mapM fileToString files
sigs <- fromShellSt getUserSignatureNames
let s = prepend "Type signatures entered by the user:" (inColumns 76 3 2 sigs)
let allDecls = strings ++ [s]
if and (map null allDecls)
then shellPutStrLn "There are no declarations."
else mapM_ shellPutStr allDecls
where
fileToString f = do
ds <- mapM (declToString f) declTypes
return $ prepend ("In `" ++ f ++ "':") (concat ds)
declTypes =
[ (" Algebraic data types:", onlyDataDecl)
, (" Type renamings:", onlyNewtypeDecl)
, (" Type synonyms:", onlyTypeDecl)
, (" Type classes:", onlyClassDecl)
, (" Type signatures:", onlySigDecl) ]
declToString f (label, select) = do
ds <- fromShellSt (getDeclarationNamesIn select f)
return $ prepend label (inColumns 74 5 2 $ sort ds)
prepend _ "" = ""
prepend s t = s ++ "\n" ++ t
-- Formats a list of strings into a column view.
-- The first argument is the length of a line, the second one is the indent to
-- prepend every line, and the third argument describes the gap between items.
inColumns :: Int -> Int -> Int -> [String] -> String
inColumns _ _ _ [] = []
inColumns lineLength indent gap strings =
let max = gap + maximum (map length strings)
nCols = (lineLength + gap) `div` max
guess = (length strings) `div` nCols
len = if (length strings) `mod` nCols == 0
then guess
else guess + 1
blocks :: Int -> [a] -> [[a]]
blocks n xs = if n == 0
then [xs]
else case xs of
[] -> []
otherwise -> let (s,t) = splitAt n xs
in s : (blocks n t)
spaces i = replicate i ' '
resize s = s ++ spaces (max - length s)
toLine xs = concatMap resize xs
shift i s = spaces i ++ s
in (unlines . map (shift indent . toLine) . transpose . blocks len) strings
-- Shows the declaration for a given name, if it can be found in the list
-- of all declarations.
cmdShowDeclaration :: Completable DeclarationName -> Sh ShellState ()
cmdShowDeclaration (Completable name) = do
maybeDecl <- fromShellSt (getDeclarationOf name)
case maybeDecl of
Nothing -> shellPutStrLn $ "There is no declaration with name `"
++ name ++ "'."
Just d -> (shellPutStrLn . doRender 0) d
-- A tag to identify declaration names.
data DeclarationName = DeclarationName
instance Completion DeclarationName ShellState where
complete _ st input = return $ sort $ filter (isPrefixOf input)
(getAllDeclarationNames st)
completableLabel _ = "<name>"
-- Shows the signatures loaded from input files and previously entered by the
-- user.
cmdShowSignatures :: Sh ShellState ()
cmdShowSignatures = do
files <- fromShellSt getDeclarationsFiles
let fs = map (\f -> "In `" ++ f ++ "':") files
sigs <- liftM (zip fs) (mapM (fromShellSt . getSignaturesIn) files)
sig <- liftM (\s -> ("Type signatures entered by the user:", s))
(fromShellSt getUserSignatures)
let s = filter (\(_,l) -> not (null l)) (sigs ++ [sig])
if null s
then shellPutStrLn "There are no type signatures."
else mapM_ showSignature s
where
showSignature (label, sigs) = do
shellPutStrLn label
let toString = doRender 3 . prettySignature . rawSignature
(mapM_ shellPutStrLn . sort . map toString) sigs
------- Language subsets ------------------------------------------------------
-- Shows the current language subset.
cmdShowLanguageSubset :: Sh ShellState ()
cmdShowLanguageSubset = do
l <- fromShellSt getCurrentLanguageSubset
let name = languageSubsetToString l
shellPutStrLn $ "The current language subset is `" ++ name ++ "'."
-- Maps language subsets to human-readable strings.
languageSubsetToString :: LanguageSubset -> String
languageSubsetToString l = case l of
BasicSubset -> "basic"
SubsetWithFix EquationalTheorem -> "fix-equational"
SubsetWithFix InequationalTheorem -> "fix-inequational"
SubsetWithSeq EquationalTheorem -> "seq-equational"
SubsetWithSeq InequationalTheorem -> "seq-inequational"
-- Changes the current language subset and displays the new one afterwards.
-- Additionally, if there is a context, an updated theorem is shown.
cmdSetLanguageSubset :: LanguageSubset -> Sh ShellState ()
cmdSetLanguageSubset l = do
modifyShellSt (setCurrentLanguageSubset l)
context <- fromShellSt getCurrentContext
maybe cmdShowLanguageSubset (\_ -> cmdShowTheorem) context
------- Display current theorem and associates --------------------------------
-- Executes a command using the current signature.
withCurrentSignature :: (ValidSignature -> Sh ShellState ()) -> Sh ShellState ()
withCurrentSignature command = do
context <- fromShellSt getCurrentContext
maybe errorNoSignature (command . fst) context
errorNoSignature =
shellPutStrLn $
"Please enter a signature or a signature name before using this command."
errorNoTheorem =
shellPutStrLn $
"There is no theorem possible for the current type in the current language"
++ "\nsubset. Please select a language subset with `seq'."
-- Execute a command using the current context.
-- The command is applied to the current type signature and the latest
-- intermediate generated for that type signature.
withCurrentContext ::
(ValidSignature -> Intermediate -> Sh ShellState ()) -> Sh ShellState ()
withCurrentContext command = do
context <- fromShellSt getCurrentContext
maybe errorNoSignature
(\(s,i) -> maybe errorNoTheorem (command s) i) context
-- Shows the current type signature.
cmdShowCurrentSignature :: Sh ShellState ()
cmdShowCurrentSignature = withCurrentSignature $ \sig -> do
shellPutStrLn . doRender 0 . prettySignature . rawSignature $ sig
-- Shows the current theorem.
cmdShowTheorem :: Sh ShellState ()
cmdShowTheorem = withCurrentContext $ \sig intermediate -> do
opts <- fromShellSt getCurrentTheoremOptions
let pt = prettyTheorem opts (asTheorem intermediate)
shellPutStrLn "The free theorem for the type signature\n"
(shellPutStrLn . doRender 2 . prettySignature . rawSignature) sig
l <- fromShellSt getCurrentLanguageSubset
let n = languageSubsetToString l
shellPutStrLn $ "\nin the language subset `" ++ n ++ "' is:\n"
shellPutStrLn $ (doRender 2 pt) ++ "\n"
-- Shows the definitions of lifted relations occurring in the current theorem.
cmdShowLiftedRelations :: Sh ShellState ()
cmdShowLiftedRelations = withCurrentContext $ \_ intermediate -> do
vds <- fromShellSt (concat . Map.elems . getAllDeclarations)
opts <- fromShellSt getCurrentTheoremOptions
let lifts = unfoldLifts vds intermediate
if null lifts
then shellPutStrLn "There are no lifted relations in the current theorem."
else do shellPutStrLn ""
mapM_ (\s -> shellPutStrLn (s ++ "\n"))
(map (doRender 2 . prettyUnfoldedLift opts) lifts)
-- Shows the definitions of the class constraints occurring in the current
-- theorem.
cmdShowClasses :: Sh ShellState ()
cmdShowClasses = withCurrentContext $ \_ intermediate -> do
vds <- fromShellSt (concat . Map.elems . getAllDeclarations)
opts <- fromShellSt getCurrentTheoremOptions
let classes = unfoldClasses vds intermediate
if null classes
then shellPutStrLn "There are no class constraints in the current theorem."
else do shellPutStrLn ""
mapM_ (\s -> shellPutStrLn (s ++ "\n"))
(map (doRender 2 . prettyUnfoldedClass opts) classes)
------- Specialising relation variables ---------------------------------------
-- Shows all relation variables which can be specialised to functions.
cmdShowRelationVariables :: Sh ShellState ()
cmdShowRelationVariables = withCurrentContext $ \_ intermediate -> do
let rvs = relationVariables intermediate
in if null rvs
then shellPutStrLn $
"There are no relations variables which could be specialised."
else shellPutStrLn
. doRender 0
. fcat . punctuate (text " ")
. map prettyRelationVariable
$ rvs
-- Specialises a relation variable to a function.
cmdSpecialise :: Completable RelationVariable -> Sh ShellState ()
cmdSpecialise (Completable rv) = withCurrentContext $ \_ intermediate -> do
if rv `notElem` map (\(RVar r) -> r) (relationVariables intermediate)
then shellPutStrLn $ "Relation variable `" ++ rv ++ "' cannot be specialised."
else do modifyShellSt (addSpecialisation (Left (RVar rv)))
cmdShowTheorem
-- Specialises a relation variable to a function.
cmdSpecialiseInverse :: Completable RelationVariable -> Sh ShellState ()
cmdSpecialiseInverse (Completable rv) = withCurrentContext $ \_ intermediate -> do
l <- fromShellSt getCurrentLanguageSubset
case l of
BasicSubset -> shellPutStrLn error
SubsetWithFix EquationalTheorem -> shellPutStrLn error
SubsetWithSeq EquationalTheorem -> shellPutStrLn error
otherwise -> do
if rv `notElem` map (\(RVar r) -> r) (relationVariables intermediate)
then shellPutStrLn $ "There is no relation variable `" ++ rv ++ "'."
else do modifyShellSt (addSpecialisation (Right (RVar rv)))
cmdShowTheorem
where error =
"This command cannot be used with the current language subset.\n"
++ "Please use an inequational language subset."
instance Completion RelationVariable ShellState where
complete _ st input =
case getCurrentContext st of
Nothing -> return []
Just (_,i) -> return . sort
. filter (isPrefixOf input)
. map (\(RVar r) -> r)
$ maybe [] relationVariables i
completableLabel _ = "<relation-variable>"
-- Reverts a specialisation of a relation variable, if at least one
-- relation variable has been specialised.
cmdUndo :: Sh ShellState ()
cmdUndo = do
h <- fromShellSt getSpecialisationHistory
if null h
then shellPutStrLn "There is nothing to undo."
else do modifyShellSt undoSpecialisation
cmdShowTheorem
------- Theorem options -------------------------------------------------------
-- Describes a mapping of theorem options to their human-readable name.
allTheoremOptions = Map.fromList
[ ("language-subsets", OmitLanguageSubsets)
, ("type-instantiations", OmitTypeInstantiations)
]
-- Show the currently set theorem options.
cmdShowCurrentTheoremOptions :: Sh ShellState ()
cmdShowCurrentTheoremOptions = do
opts <- fromShellSt getCurrentTheoremOptions
shellPutStrLn "The current options for displaying theorems are:"
mapM_ (showOpt opts) (Map.keys allTheoremOptions)
where
showOpt opts o =
if fromJust (Map.lookup o allTheoremOptions) `elem` opts
then shellPutStrLn $ " omit " ++ o
else shellPutStrLn $ " show " ++ o
-- Enables a theorem option.
cmdTheoremOptionShow :: Completable TheoremOptions -> Sh ShellState ()
cmdTheoremOptionShow =
let del opt os = filter (/= opt) os
in updateTheoremOptions del
-- Disables a theorem option.
cmdTheoremOptionOmit :: Completable TheoremOptions -> Sh ShellState ()
cmdTheoremOptionOmit =
let add opt os = if opt `notElem` os then opt : os else os
in updateTheoremOptions add
-- Modifies the current theorem options with the help of a modification
-- function.
updateTheoremOptions ::
(PrettyTheoremOption -> [PrettyTheoremOption] -> [PrettyTheoremOption])
-> Completable TheoremOptions
-> Sh ShellState ()
updateTheoremOptions f (Completable opt) =
case Map.lookup opt allTheoremOptions of
Nothing -> shellPutErrLn $ "Unknown theorem option `" ++ opt ++ "'."
Just o -> do modifyShellSt (updateOptions (f o))
cmdShowCurrentTheoremOptions
ctx <- fromShellSt getCurrentContext
maybe (shellSpecial ShellNothing)
(\_ -> shellPutStrLn "" >> cmdShowTheorem) ctx
where
updateOptions f state =
state { getCurrentTheoremOptions = f (getCurrentTheoremOptions state) }
-- A tag to identify theorem options.
data TheoremOptions = TheoremOptions
instance Completion TheoremOptions ShellState where
complete _ st input = return $ sort $ filter (isPrefixOf input)
(Map.keys allTheoremOptions)
completableLabel _ = "<option>"