| 1 | {- | |
|---|
| 2 | Module : $Header$ |
|---|
| 3 | Description : Manages the history of proof commands |
|---|
| 4 | Copyright : (c) Markus Gross 2008 |
|---|
| 5 | License : similar to LGPL, see HetCATS/LICENSE.txt or LIZENZ.txt |
|---|
| 6 | |
|---|
| 7 | Maintainer : Markus.Gross@dfki.de |
|---|
| 8 | Stability : provisional |
|---|
| 9 | Portability : portable |
|---|
| 10 | |
|---|
| 11 | Manages the history of proof commands. |
|---|
| 12 | -} |
|---|
| 13 | module GUI.History (CommandHistory, |
|---|
| 14 | emptyCommandHistory, |
|---|
| 15 | initCommandHistory, |
|---|
| 16 | addMenuItemToHist, |
|---|
| 17 | addProveToHist, |
|---|
| 18 | askSaveProofScript) where |
|---|
| 19 | |
|---|
| 20 | import Common.OrderedMap (keys) |
|---|
| 21 | import Common.Utils (joinWith, splitOn) |
|---|
| 22 | import Driver.Options (rmSuffix) |
|---|
| 23 | import GUI.GraphAbstraction (GraphInfo, showTemporaryMessage) |
|---|
| 24 | import GUI.Utils (infoDialog, fileSaveDialog) |
|---|
| 25 | import Logic.Comorphism (AnyComorphism(..)) |
|---|
| 26 | import Logic.Grothendieck (SigId(..)) |
|---|
| 27 | import Logic.Logic (language_name) |
|---|
| 28 | import Logic.Prover |
|---|
| 29 | import Proofs.AbstractState |
|---|
| 30 | import Static.GTheory (G_theory(..)) |
|---|
| 31 | |
|---|
| 32 | import Control.Monad (when) |
|---|
| 33 | import Data.IORef |
|---|
| 34 | import Data.List (isPrefixOf, stripPrefix) |
|---|
| 35 | import Data.Maybe (fromMaybe) |
|---|
| 36 | import System.Directory (getCurrentDirectory) |
|---|
| 37 | |
|---|
| 38 | -- This datastructure holds all information for the history. |
|---|
| 39 | data CommandHistory = CommandHistory {file :: String, |
|---|
| 40 | hist :: IORef [Command]} |
|---|
| 41 | |
|---|
| 42 | -- Represents a command in the history. |
|---|
| 43 | data Command = DGComm DGCommand | NodeChange Node | ProveCommand Prove |
|---|
| 44 | deriving Eq |
|---|
| 45 | |
|---|
| 46 | instance Show Command where |
|---|
| 47 | show (DGComm c) = show c |
|---|
| 48 | show (NodeChange n) = show n |
|---|
| 49 | show (ProveCommand p) = show p |
|---|
| 50 | |
|---|
| 51 | -- This is used to recognize a change of node between the proofs. |
|---|
| 52 | data Node = Node Int String deriving Eq |
|---|
| 53 | |
|---|
| 54 | instance Show Node where |
|---|
| 55 | show (Node _ nName) = |
|---|
| 56 | "\n# " ++ (replicate 78 '-') ++ "\n\ndg basic " ++ nName ++ "\n" |
|---|
| 57 | |
|---|
| 58 | -- This represents a dg menu-item. |
|---|
| 59 | data DGCommand = AllAuto | AllGlobSubsume | AllGlobDecomp | AllLocInfer | |
|---|
| 60 | AllLocDecomp | AllComp | AllCompNew | AllHideThm | |
|---|
| 61 | AllThmHide deriving Eq |
|---|
| 62 | |
|---|
| 63 | instance Show DGCommand where |
|---|
| 64 | show AllAuto = "dg-all auto" |
|---|
| 65 | show AllGlobSubsume = "dg-all glob-subsume" |
|---|
| 66 | show AllGlobDecomp = "dg-all glob-decomp" |
|---|
| 67 | show AllLocInfer = "dg-all loc-infer" |
|---|
| 68 | show AllLocDecomp = "dg-all loc-decomp" |
|---|
| 69 | show AllComp = "dg-all comp" |
|---|
| 70 | show AllCompNew = "dg-all comp-new" |
|---|
| 71 | show AllHideThm = "dg-all hide-thm" |
|---|
| 72 | show AllThmHide = "dg-all thm-hide" |
|---|
| 73 | |
|---|
| 74 | -- This represents a prove with all properties needed to add it to the history. |
|---|
| 75 | data Prove = Prove {prover :: Maybe String, -- name of the prover |
|---|
| 76 | translation :: Maybe AnyComorphism, -- used translation |
|---|
| 77 | provenGoals :: [ProvenGoal], -- proven goals |
|---|
| 78 | allAxioms :: [String] -- all available axioms |
|---|
| 79 | } deriving Eq |
|---|
| 80 | |
|---|
| 81 | instance Show Prove where |
|---|
| 82 | show p = |
|---|
| 83 | "drop-translations\n" |
|---|
| 84 | ++ |
|---|
| 85 | -- selected translation |
|---|
| 86 | (maybe "" (unlines . splitComorphism) $ translation p) |
|---|
| 87 | ++ |
|---|
| 88 | -- selected prover |
|---|
| 89 | (maybe "# no prover chosen" ("prover " ++) $ prover p) ++ "\n\n" |
|---|
| 90 | ++ |
|---|
| 91 | -- proven goals |
|---|
| 92 | (joinWith '\n' $ map (goalToString p) $ provenGoals p) |
|---|
| 93 | |
|---|
| 94 | -- This represents the information about a proved goal |
|---|
| 95 | data ProvenGoal = ProvenGoal {name :: String, -- name of the goal |
|---|
| 96 | axioms :: [String], -- used axioms in the prove |
|---|
| 97 | timeLimit :: Int -- chosen time-limit |
|---|
| 98 | } deriving Eq |
|---|
| 99 | |
|---|
| 100 | -- Converts a proven goal to a string. |
|---|
| 101 | goalToString :: Prove -> ProvenGoal -> String |
|---|
| 102 | goalToString p g = |
|---|
| 103 | "set goals " ++ (name g) |
|---|
| 104 | ++ |
|---|
| 105 | -- axioms to include in prove |
|---|
| 106 | '\n':(if (allAxioms p == axioms g) || (null $ axioms g) |
|---|
| 107 | then "set-all axioms" |
|---|
| 108 | else "set axioms " ++ (joinWith ' ' $ axioms g)) |
|---|
| 109 | ++ |
|---|
| 110 | -- selected time-limit |
|---|
| 111 | "\nset time-limit " ++ (show $ timeLimit g) ++ "\nprove\n" |
|---|
| 112 | |
|---|
| 113 | -- Creates an empty command history. |
|---|
| 114 | emptyCommandHistory :: IO CommandHistory |
|---|
| 115 | emptyCommandHistory = initCommandHistory "" |
|---|
| 116 | |
|---|
| 117 | -- Initializes the command history with a filename. |
|---|
| 118 | initCommandHistory :: String -> IO CommandHistory |
|---|
| 119 | initCommandHistory f = |
|---|
| 120 | do |
|---|
| 121 | ff <- tryRemoveAbsolutePathComponent f |
|---|
| 122 | ch <- newIORef [] |
|---|
| 123 | return $ CommandHistory {file = rmSuffix ff, hist = ch} |
|---|
| 124 | |
|---|
| 125 | -- Adds a single command to the history. |
|---|
| 126 | addToHist :: CommandHistory -> Command -> IO () |
|---|
| 127 | addToHist (CommandHistory {hist = c}) cm = |
|---|
| 128 | readIORef c >>= (\h -> writeIORef c $ h ++ [cm]) |
|---|
| 129 | |
|---|
| 130 | -- Adds a menu item to the history. |
|---|
| 131 | addMenuItemToHist :: CommandHistory -> String -> IO () |
|---|
| 132 | addMenuItemToHist ch item = |
|---|
| 133 | maybe (return ()) (addToHist ch . DGComm) (lookup item menuItems) |
|---|
| 134 | |
|---|
| 135 | -- A List of menu items and their corresponding proof-script command. |
|---|
| 136 | menuItems :: [(String, DGCommand)] |
|---|
| 137 | menuItems = [ |
|---|
| 138 | ("Automatic", AllAuto), |
|---|
| 139 | ("Global Subsumption", AllGlobSubsume), |
|---|
| 140 | ("Global Decomposition", AllGlobDecomp), |
|---|
| 141 | ("Local Inference", AllLocInfer), |
|---|
| 142 | ("Local Decomposition (merge of rules)", AllLocDecomp), |
|---|
| 143 | ("Composition (merge of rules)", AllComp), |
|---|
| 144 | ("Composition - creating new links", AllCompNew), |
|---|
| 145 | ("Hide Theorem Shift", AllHideThm), |
|---|
| 146 | ("Theorem Hide Shift", AllThmHide) |
|---|
| 147 | ] |
|---|
| 148 | |
|---|
| 149 | -- If at least one goal was proved and the prove is not the same as last time |
|---|
| 150 | -- the prove gets added, otherwise not. |
|---|
| 151 | addProveToHist :: CommandHistory |
|---|
| 152 | -> ProofState lid sentence -- current proofstate |
|---|
| 153 | -> Maybe (G_prover, AnyComorphism) -- possible used translation |
|---|
| 154 | -> [Proof_status proof_tree] -- goals included in prove |
|---|
| 155 | -> IO () |
|---|
| 156 | addProveToHist ch st pcm pt |
|---|
| 157 | | null $ filter (wasProved) pt = return () |
|---|
| 158 | | otherwise = do |
|---|
| 159 | p' <- proofTreeToProve st pcm pt |
|---|
| 160 | mp <- getLastProve ch |
|---|
| 161 | case mp of |
|---|
| 162 | Just p -> when (p /= p') (addProve p') |
|---|
| 163 | Nothing -> addProve p' |
|---|
| 164 | where |
|---|
| 165 | -- Drops the filename as prefix from a node-name. |
|---|
| 166 | dropName :: String -> String |
|---|
| 167 | dropName s = maybe s (tail) (stripPrefix (file ch) s) |
|---|
| 168 | -- Adds a prove to the history. |
|---|
| 169 | addProve :: Prove -> IO () |
|---|
| 170 | addProve p = |
|---|
| 171 | do |
|---|
| 172 | -- Create new NodeChange and compare it with last found NodeChange. |
|---|
| 173 | let (SigId nId) = gTheorySignIdx $ theory st |
|---|
| 174 | let nc' = NodeChange $ Node nId (dropName $ theoryName st) |
|---|
| 175 | mn <- getLastNode ch |
|---|
| 176 | -- Add new NodeChange if it doesn't match. |
|---|
| 177 | case mn of |
|---|
| 178 | Just n -> when (NodeChange n /= nc') (addToHist ch nc') |
|---|
| 179 | Nothing -> addToHist ch nc' |
|---|
| 180 | -- Finally add the prove. |
|---|
| 181 | addToHist ch $ ProveCommand p |
|---|
| 182 | |
|---|
| 183 | -- Converts a list of proof-trees to a prove. |
|---|
| 184 | proofTreeToProve :: ProofState lid sentence -- current proofstate |
|---|
| 185 | -> Maybe (G_prover, AnyComorphism) -- possible used translation |
|---|
| 186 | -> [Proof_status proof_tree] -- goals included in prove |
|---|
| 187 | -> IO Prove |
|---|
| 188 | proofTreeToProve st pcm pt = |
|---|
| 189 | do |
|---|
| 190 | -- selected prover |
|---|
| 191 | let prvr = maybe (selectedProver st) (Just . getProverName . fst) pcm |
|---|
| 192 | -- selected translation |
|---|
| 193 | let trans = maybe Nothing (Just . snd) pcm |
|---|
| 194 | -- 1. filter out the not proven goals |
|---|
| 195 | -- 2. reverse the list, because the last proven goals are on top |
|---|
| 196 | -- 3. convert all proof-trees to goals |
|---|
| 197 | -- 4. merge goals with same used axioms |
|---|
| 198 | let goals = mergeGoals $ reverse $ map (convertGoal) $ filter (wasProved) pt |
|---|
| 199 | -- axioms to include in prove |
|---|
| 200 | let allax = case theory st of |
|---|
| 201 | G_theory _ _ _ axs _ -> keys axs |
|---|
| 202 | return $ Prove {prover = prvr, |
|---|
| 203 | translation = trans, |
|---|
| 204 | provenGoals = goals, |
|---|
| 205 | allAxioms = allax} |
|---|
| 206 | |
|---|
| 207 | -- This checks wether a goal was proved or not. |
|---|
| 208 | wasProved :: Proof_status proof_tree -> Bool |
|---|
| 209 | wasProved g = case goalStatus g of |
|---|
| 210 | Proved _ -> True |
|---|
| 211 | _ -> False |
|---|
| 212 | |
|---|
| 213 | -- Converts a proof-tree into a goal. |
|---|
| 214 | convertGoal :: Proof_status proof_tree -> ProvenGoal |
|---|
| 215 | convertGoal pt = |
|---|
| 216 | ProvenGoal {name = goalName pt, axioms = usedAxioms pt, timeLimit = tlimit} |
|---|
| 217 | where |
|---|
| 218 | (Tactic_script script) = tacticScript pt |
|---|
| 219 | tlimit = maybe 20 (read) (parseTimeLimit $ splitOn '\n' script) |
|---|
| 220 | |
|---|
| 221 | -- Parses time-limit from the tactic-script of a goal. |
|---|
| 222 | parseTimeLimit :: [String] -> Maybe String |
|---|
| 223 | parseTimeLimit l = |
|---|
| 224 | if null l' then Nothing else Just $ drop (length tlStr) $ last l' |
|---|
| 225 | where |
|---|
| 226 | l' = filter (isPrefixOf tlStr) l |
|---|
| 227 | tlStr = "Time limit: " |
|---|
| 228 | |
|---|
| 229 | -- Merges goals with the same used axioms into one goal. |
|---|
| 230 | mergeGoals :: [ProvenGoal] -> [ProvenGoal] |
|---|
| 231 | mergeGoals [] = [] |
|---|
| 232 | mergeGoals (h:[]) = [h] |
|---|
| 233 | mergeGoals (h:t) | mergeAble h h' = mergeGoals $ (merge h h'):(tail t) |
|---|
| 234 | | otherwise = h:(mergeGoals t) |
|---|
| 235 | where |
|---|
| 236 | h' = head t |
|---|
| 237 | mergeAble :: ProvenGoal -> ProvenGoal -> Bool |
|---|
| 238 | mergeAble a b = axioms a == axioms b && |
|---|
| 239 | timeLimit a == timeLimit b |
|---|
| 240 | merge :: ProvenGoal -> ProvenGoal -> ProvenGoal |
|---|
| 241 | merge a b = a{name = name a ++ ' ':(name b)} |
|---|
| 242 | |
|---|
| 243 | -- Returns the last nodechange in the history. |
|---|
| 244 | getLastNode :: CommandHistory -> IO (Maybe Node) |
|---|
| 245 | getLastNode (CommandHistory{hist = c}) = |
|---|
| 246 | do |
|---|
| 247 | h <- readIORef c |
|---|
| 248 | let h' = [n | NodeChange n <- h] |
|---|
| 249 | return $ if null h' then Nothing else Just $ last h' |
|---|
| 250 | |
|---|
| 251 | -- If the last command in history is a prove it gets returned. |
|---|
| 252 | getLastProve :: CommandHistory -> IO (Maybe Prove) |
|---|
| 253 | getLastProve (CommandHistory{hist = c}) = |
|---|
| 254 | do |
|---|
| 255 | h <- readIORef c |
|---|
| 256 | return $ if null h |
|---|
| 257 | then Nothing |
|---|
| 258 | else case last h of |
|---|
| 259 | ProveCommand p -> Just p |
|---|
| 260 | _ -> Nothing |
|---|
| 261 | |
|---|
| 262 | -- | Displays a Save-As dialog and writes the proof-script. |
|---|
| 263 | askSaveProofScript :: GraphInfo -> CommandHistory -> IO () |
|---|
| 264 | askSaveProofScript graphInfo ch = |
|---|
| 265 | do |
|---|
| 266 | h <- readIORef $ hist ch |
|---|
| 267 | if null h |
|---|
| 268 | then infoDialog "Information" "The history is empty. No file written." |
|---|
| 269 | else do |
|---|
| 270 | f <- getProofScriptFileName ch |
|---|
| 271 | maybeFilePath <- fileSaveDialog f [ ("Proof-Script",["*.hpf"]) |
|---|
| 272 | , ("All Files", ["*"])] Nothing |
|---|
| 273 | case maybeFilePath of |
|---|
| 274 | Just filePath -> do |
|---|
| 275 | showTemporaryMessage graphInfo "Saving proof-script..." |
|---|
| 276 | saveCommandHistory ch filePath |
|---|
| 277 | showTemporaryMessage graphInfo $ "Proof-script saved to " ++ |
|---|
| 278 | filePath ++ "!" |
|---|
| 279 | Nothing -> showTemporaryMessage graphInfo $ "Aborted!" |
|---|
| 280 | |
|---|
| 281 | -- Saves the history of commands in a file. |
|---|
| 282 | saveCommandHistory :: CommandHistory -> String -> IO () |
|---|
| 283 | saveCommandHistory (CommandHistory {file = ff, hist = c}) f = |
|---|
| 284 | do |
|---|
| 285 | h <- readIORef c |
|---|
| 286 | let history = ["# automatically generated hets proof-script", "", |
|---|
| 287 | "use " ++ ff, ""] ++ map (show) h |
|---|
| 288 | writeFile f $ joinWith '\n' history |
|---|
| 289 | |
|---|
| 290 | -- Suggests a proof-script filename. |
|---|
| 291 | getProofScriptFileName :: CommandHistory -> IO FilePath |
|---|
| 292 | getProofScriptFileName (CommandHistory {file = f}) |
|---|
| 293 | | "/" `isPrefixOf` f = return $ f ++ ".hpf" |
|---|
| 294 | | otherwise = do |
|---|
| 295 | dir <- getCurrentDirectory |
|---|
| 296 | return $ dir ++ '/':f ++ ".hpf" |
|---|
| 297 | |
|---|
| 298 | -- Splits the comorphism string into its translations. |
|---|
| 299 | splitComorphism :: AnyComorphism -> [String] |
|---|
| 300 | splitComorphism (Comorphism cid) = |
|---|
| 301 | map ("translate " ++) $ tail $ splitOn ';' $ language_name cid |
|---|
| 302 | |
|---|
| 303 | -- If an absolute path is given, |
|---|
| 304 | -- it tries to remove the current working directory as prefix of it. |
|---|
| 305 | tryRemoveAbsolutePathComponent :: String -> IO String |
|---|
| 306 | tryRemoveAbsolutePathComponent f |
|---|
| 307 | | "/" `isPrefixOf` f = do |
|---|
| 308 | dir <- getCurrentDirectory |
|---|
| 309 | return $ fromMaybe f (stripPrefix (dir ++ "/") f) |
|---|
| 310 | | otherwise = return f |
|---|