import LowLevel (FileNode(..),nil,hatVersionNumber,openHatFile ,getSrcRef,getDefnRef) import qualified SrcRef (SrcRef(..), readSrcRef) import SExp (prettyEquation) import TExp (TExp,linearise) import HighlightStyle (highlight,Highlight(..),Colour(..),getTerminalSize) import Foreign.C.String (withCString) import Numeric (showHex) import System.IO.Unsafe (unsafePerformIO) import Detect (findMain,edtNextChild,ParentSet,newParentSet,anySuspect) import CmdLine (initialize,cmdline) import CommonUI (hatObserve,hatTrail,hatAnim,hatView,shortHelpText ,Options(..),initialOptions,Keep(..) ,OptionCmd(..),optionCmd,onOff,number,safeReadInt ,optionsUpdate,showOption,showOnOff) import Data.Maybe import System.Environment (getArgs,getProgName) import System.Exit (exitWith,ExitCode(..)) import System.Cmd (system) import Control.Monad (when) import Data.List (isPrefixOf,isSuffixOf,intersperse) import Data.Char (isDigit,digitToInt,toLower,isSpace) import System.IO (hFlush,stdout,hSetBuffering,BufferMode(..)) import System.Directory (doesFileExist) {- Issues to resolve: The 'memoise' option is really the same as Filter Unique ? Should the :observe command take the current equation, a numbered eqn, or a string query? Should the :detect command exist at all (only for orthogonality)? Should the :trail command take an equation number, or the current eqn? -} ----------------------------------------------------------------------- -- type for state of session data State = State { filename :: FilePath -- .hat file name , treePath :: [ParentSet] -- path to here through EDT (head=here) , currentNode :: FileNode -- location of current equation -- , children :: [FileNode] -- current EDT children -- , recentNodes :: [(FileNode, LinExpr, Bool)] -- already considered nodes , trusted :: [FileNode] -- trusted fun-identifiers , postponed :: [(Int,FileNode)]-- postponed questions , questnumber :: Int -- current question number , screenWidth :: Int -- for pretty-printing , options :: Options -- user-configurable display options , memoMode :: Bool -- memoizeMode , reconsider :: Bool -- True when reconsidering a } -- postponed question initialState :: FilePath -> ParentSet -> State initialState file start = State { filename = file , treePath = [start] , currentNode = LowLevel.nil -- , children = [start] -- , recentNodes = [] , trusted = [] , postponed = [] , questnumber = 1 , screenWidth = 80 , options = initialOptions , memoMode = True , reconsider = False } setState :: Mode -> State -> State setState (O o) state = state {options=optionsUpdate o (options state)} setState (Memoise b) state = state {memoMode=b} showState :: Mode -> State -> String showState (O o) state = showOption o (options state) showState (Memoise _) state = " "++ highlight [Underscore] "memoise" ++ showOnOff (memoMode state) ++ "remember previous answers" ----------------------------------------------------------------------- main = do hSetBuffering stdout NoBuffering arguments <- System.Environment.getArgs let numArgs = length arguments when (numArgs < 1) (do putStrLn cmdlineHelp exitWith (ExitFailure 1)) putStrLn ("\n hat-detect "++hatVersionNumber ++" ("++shortHelpText++")\n") (width,_) <- getTerminalSize CmdLine.initialize -- for readline functionality let hatFile = rectify (arguments!!0) prog <- System.Environment.getProgName withCString prog (\p-> withCString hatFile (openHatFile p)) case numArgs of 1 -> do mainNode <- findMain if mainNode == LowLevel.nil then error "Bad file format! \"Main.main\" could not be found!" else do mainPS <- newParentSet mainNode askQuestion mainNode mainPS (initialState hatFile mainPS) { screenWidth=width } 2 -> case safeReadInt 0 (arguments!!1) of 0 -> do putStrLn cmdlineHelp exitWith (ExitFailure 1) a -> do startPS <- newParentSet (FileNode a) askQuestion (FileNode a) startPS (initialState hatFile startPS) { screenWidth=width } putStrLn (highlight [Foreground Blue] "Press return.") _ <- getLine return () _ -> do putStrLn cmdlineHelp exitWith (ExitFailure 1) putStrLn (highlight [Foreground Blue] "Done.") where rectify :: FilePath -> FilePath rectify f | ".hat" `isSuffixOf` f = f | otherwise = f ++ ".hat" ----------------------------------------------------------------------- -- Add new node to the list of already-seen nodes. List holds node, linear -- representation of it, and the value of the users answer (Yes=True, No=False) addToRecentNodes :: [(FileNode,[TExp],Bool)] -> FileNode -> Bool -> [(FileNode,[TExp],Bool)] addToRecentNodes recentNodes node answerYes = {- (node, linearise (toHatExpressionTree 100 node), answerYes) : -} recentNodes -- check whether node is less general than an earlier given answer memoizeCheck :: [(FileNode,[TExp],Bool)] -> FileNode -> Maybe Bool memoizeCheck recentNodes node = memoizeCheck' recentNodes [] {-(linearise (toHatExpressionTree 100 node))-} where memoizeCheck' [] _ = Nothing -- memoizeCheck' ((_,expr2,answer):recentNodes) expr1 = -- if compareExpr expr1 expr2 then Just answer -- else memoizeCheck' recentNodes expr1 -- The main interactive loop interactive :: FileNode -> ParentSet -> State -> IO () interactive node ps state = do -- node = parent of current question, ps = complete trail of parents cmd <- getCommand "hat-detect> " doCommand cmd node ps state askQuestion :: FileNode -> ParentSet -> State -> IO () askQuestion node ps state = do c <- edtNextChild ps -- (head (treePath state)) -- t <- anySuspect c if c==LowLevel.nil then do putStrLn ("bug found at "++showHex (int node) "") putStrLn (prettyEquation " " "" (screenWidth state) (options state) node) return () else do putStrLn (prettyEquation (show (questnumber state)++" ") "" -- (showHex (int c) (" "++show t)) (screenWidth state) (options state) c) interactive node ps state { currentNode = c , questnumber = questnumber state +1 } {- interactive :: State -> IO (Bool,Int,State) interactive state | null (children state) && null (postponed state) = -- nothing else to do return (True,0,state) interactive state | null (children state) && not (null (postponed state)) = -- take one postponed question as new current question interactive state {children=[node] ,questnumber=qn ,postponed=tail (postponed state) ,reconsider=False } where (qn,node) = head (postponed state) interactive state | not (null (children state)) = let node = head (children state) -- ask about the next remaining EDT child answer = memoizeCheck (recentNodes state) node in if node `elem` map (\(a,_,_)->a) (recentNodes state) || (memoMode state && isJust answer) then if fromJust answer -- don't ask about identical(!) node in memoizemode then doCommand (Answer Yes) state -- pretend user answered "YES" else doCommand (Answer No) state -- pretend user answered "NO" else if hatLeftmost node `elem` trusted state then doCommand (Answer Yes) state -- pretend user answered "YES" else do when (reconsider state) (putStrLn "reconsider: ") putStr (prettyEquation (show (questnumber state)++" ") ("? ") (screenWidth state) (options state) node) hFlush stdout cmd <- getCommand "hat-detect> " doCommand cmd state -} doCommand :: Cmd -> FileNode -> ParentSet -> State -> IO () doCommand Quit n ps state = return () doCommand (Help s) n ps state = do interactiveHelp (dropWhile isSpace s) interactive n ps state doCommand Status n ps state = do mapM_ (\m-> putStrLn (showState m state)) [ O (Uneval True), O (Strings True), O (Lists True) , O (Qualify True), O (Equations True), Memoise True , O (CutOff 0), O (Filter All) ] interactive n ps state doCommand (Set mode) n ps state = do let state' = setState mode state putStrLn (showState mode state') interactive n ps state' ---- {- doCommand Children n ps state = do putStrLn ("children = "++show (children state)) interactive n ps state doCommand Trust n ps state = -- user defined function trusting do let child = head (children state) trustFun = hatLeftmost child putStrLn (showExpression trustFun " Ok, \"" ++ "\" is trusted from now on.") (b,q,state') <- interactive n ps state {recentNodes = addToRecentNodes (recentNodes state) child True ,trusted = trustFun:trusted state ,questnumber = questnumber state + 1 ,reconsider = False } if q/=questnumber state then return (b,q,state') else interactive n ps state { options = options state' , memoMode = memoMode state' , reconsider = False } -} doCommand Untrust n ps state = do putStrLn "Ok, all functions are untrusted now." interactive n ps state {trusted=[]} ---- answering the question: yes, no, ?yes, or ?no doCommand (Answer Yes) n ps state = askQuestion n ps state -- look for next sibling doCommand (Answer No) n ps state = do let c = currentNode state p <- newParentSet c -- descend into this subtree askQuestion c p state {treePath=(p:treePath state)} {- doCommand (Answer Yes) n ps state = do let child = head (children state) (b,q,state') <- interactive n ps state { children = tail (children state) , recentNodes = addToRecentNodes (recentNodes state) child True , questnumber = questnumber state + 1 , reconsider = False } if q/=questnumber state then return (b,q,state') else interactive n ps state { options = state' , memoMode = memoMode state' , reconsider = False } doCommand (Answer QueryYes) n ps state = do let child = head (children state) (b,q,state') <- interactive n ps state { children = tail (children state) , postponed = postponed state ++ [(questnumber state,child)] , questnumber = questnumber state + 1 , reconsider = False } if q/=questnumber state then return (b,q,state') else interactive n state ps { options = options state' , memoMode = memoMode state' , reconsider = False } doCommand (Answer No) n ps state = do let child = head (children state) newchildren = edtChildren child (b,q,state') <- interactive n ps state { children = newchildren , recentNodes = addToRecentNodes (recentNodes state) child False , postponed = [] , questnumber = questnumber state + 1 , reconsider = False } if q==questnumber state then interactive n ps state { options = options state' , memoMode = memoMode state' , reconsider = False } else if b && q==0 then do let lmo = hatLeftmost child src = if isInvalidNode lmo then HatNoSourceRef else hatSourceRef lmo putStrLn ("\nErroneous reduction:\n" ++ highlight [Foreground Blue] (prettyEquation "" "" (screenWidth state) (options state) child) ++ showExpression lmo "\nBug found within the body of function: \"" ++ "\"\n" ++ "line "++show (row src)++", column " ++ show (column src) ++ " in module \"" ++ moduleName src ++ "\", file: \"" ++ moduleFile src ++"\"") putStr ("\n:q to quit, any other key to go back to question " ++ show (questnumber state) ++": ") cmd <- getCommand "hat-detect> " case cmd of Quit -> return (False,-1,state') _ -> interactive state else return (b,q,state') doCommand (Answer QueryNo) n ps state | not (reconsider state) = do let child = head (children state) newchildren = edtChildren child (b,q,state') <- interactive n ps state { children = newchildren , recentNodes = addToRecentNodes (recentNodes state) child False , postponed = [] , questnumber = questnumber state + 1 , reconsider = False } if q==questnumber state then interactive n ps state { options = options state' , memoMode = memoMode state' , reconsider = False } else if b && q==0 then interactive n ps state { options = options state' , trusted = trusted state' , memoMode = memoMode state' , reconsider = True } else return (b,q,state') doCommand (Answer QueryNo) n ps state | reconsider state = do putStrLn ("The question has already been deferred once.\n\ \You must answer it now with y/y?/n ") interactive n ps state doCommand (AskQuestion q) n ps state = if q>0 && q IO Cmd getCommand prompt = do s <- cmdline prompt if null s then return Unknown else if all isDigit s then return (number AskQuestion [s] 0) else if head s `elem` "yYnN?mM" then return (answer (map toLower s)) else if head s /= ':' then return Unknown else case words (tail s) of [] -> return Unknown (cmd:ss) | cmd `isPrefixOf` "quit" -> return Quit | cmd `isPrefixOf` "help" -> return (Help (unwords ss)) | cmd `isPrefixOf` "observe" -> return (StartTool (Observe (unwords ss))) | cmd `isPrefixOf` "trail" -> return (StartTool Trail) | cmd `isPrefixOf` "animate" -> return (StartTool Anim) | cmd `isPrefixOf` "detect" -> return (number (StartTool . Detect) ss 0) | cmd `isPrefixOf` "trust" -> return Trust | cmd `isPrefixOf` "untrust" -> return Untrust | cmd `isPrefixOf` "children" -> return Children | cmd `isPrefixOf` "source" -> return (number Source ss 0) | cmd `isPrefixOf` "Source" -> return (number Definition ss 0) | cmd `isPrefixOf` "resize" -> return Resize | cmd `isPrefixOf` "set" -> case ss of [] -> return Status (m:sss) -> case optionCmd ss of Just o -> return (Set (O o)) Nothing -> if m `isPrefixOf` "memoise" then return (maybe Unknown Set (onOff Memoise sss)) else return Unknown | head cmd == '?' -> return (Help (unwords (tail cmd:ss))) | head cmd == '+' -> return (number (Set . O . Deeper) (tail cmd:ss) 1) | head cmd == '-' -> return (number (Set . O . Shallower) (tail cmd:ss) 1) | head cmd == '!' -> return (Shell (unwords (tail cmd:ss))) | otherwise -> return Unknown -- answer expects some partial checking of the string to have been done already answer :: String -> Cmd answer "y" = Answer Yes answer "n" = Answer No answer "yes" = Answer Yes answer "no" = Answer No answer "y?" = Answer QueryYes answer "n?" = Answer QueryNo answer "?y" = Answer QueryYes answer "?n" = Answer QueryNo answer "?" = Answer QueryNo answer "maybe" = Answer QueryNo answer _ = Unknown