on master help sign in
Merge pull request #180 from david-christiansen/completion-work

Basic completion now works at REPL and in prover

commitd53fccf8aaa3e2a4c65a39cbfebfc52f8acf1e72 authorEdwin Brady <edwin.brady@gmail.com> dateTue 12 Feb 2013, 07:27 (14 years ago) parent053708abdf07be
7 files changed+199−40
src/Core/ProofShell.hs+18−16 src/Idris/AbsSyntax.hs+2−2 src/Idris/AbsSyntaxTree.hs+1−1 src/Idris/Completion.hs+143 src/Idris/Prover.hs+24−7 src/Idris/REPL.hs+10−13 src/Main.hs+1−1
src/Core/ProofShell.hs+18−16
@@ -58,20 +58,22 @@ processCommand (Tac e) state
5858 | otherwise = (state, "No proof in progress")
5959
6060runShell :: ShellState -> Idris ShellState
61−runShell st = do (prompt, parser) <-
62− maybe (return ("TT# ", parseCommand))
63− (\st -> do outputStrLn . render . pretty $ st
64− return (show (thname st) ++ "# ", parseTactic))
65− (prf st)
66− x <- getInputLine prompt
67− cmd <- case x of
68− Nothing -> return $ Right Quit
69− Just input -> return (parser input)
70− case cmd of
71− Left err -> do outputStrLn (show err)
72− runShell st
73− Right cmd -> do let (st', r) = processCommand cmd st
74− outputStrLn r
75− if (not (exitNow st')) then runShell st'
76− else return st'
61+runShell st = runInputT defaultSettings $ runShell' st
62+ where
63+ runShell' st = do (prompt, parser) <-
64+ maybe (return ("TT# ", parseCommand))
65+ (\st -> do outputStrLn . render . pretty $ st
66+ return (show (thname st) ++ "# ", parseTactic))
67+ (prf st)
68+ x <- getInputLine prompt
69+ cmd <- case x of
70+ Nothing -> return $ Right Quit
71+ Just input -> return (parser input)
72+ case cmd of
73+ Left err -> do outputStrLn (show err)
74+ runShell' st
75+ Right cmd -> do let (st', r) = processCommand cmd st
76+ outputStrLn r
77+ if (not (exitNow st')) then runShell' st'
78+ else return st'
7779
src/Idris/AbsSyntax.hs+2−2
@@ -148,10 +148,10 @@ setSO s = do i <- getIState
148148 putIState $ (i { compiled_so = s })
149149
150150getIState :: Idris IState
151−getIState = lift get
151+getIState = get
152152
153153putIState :: IState -> Idris ()
154−putIState = lift . put
154+putIState = put
155155
156156getName :: Idris Int
157157getName = do i <- getIState;
src/Idris/AbsSyntaxTree.hs+1−1
@@ -136,7 +136,7 @@ idrisInit = IState initContext [] [] emptyContext emptyContext emptyContext
136136
137137-- The monad for the main REPL - reading and processing files and updating
138138-- global state (hence the IO inner monad).
139−type Idris = InputT (StateT IState IO)
139+type Idris = StateT IState IO
140140
141141-- Commands in the REPL
142142
src/Idris/Completion.hsnew file+143
@@ -0,0 +1,143 @@
1+module Idris.Completion (replCompletion, proverCompletion) where
2+
3+import Core.Evaluate (ctxtAlist)
4+import Core.TT
5+
6+import Idris.AbsSyntaxTree
7+
8+import Control.Monad.State.Strict
9+
10+import Data.List
11+import Data.Maybe
12+
13+import System.Console.Haskeline
14+
15+-- TODO: add specific classes of identifiers to complete, fx metavariables
16+-- | A specification of the arguments that REPL commands can take
17+data CmdArg = ExprArg -- ^ The command takes an expression
18+ | NameArg -- ^ The command takes a name
19+ | FileArg -- ^ The command takes a file
20+ | ModuleArg -- ^ The command takes a module name
21+ | OptionArg -- ^ The command takes an option
22+
23+-- | Information about how to complete the various commands
24+cmdArgs :: [(String, CmdArg)]
25+cmdArgs = [ (":t", ExprArg)
26+ , (":miss", NameArg)
27+ , (":missing", NameArg)
28+ , (":i", NameArg)
29+ , (":info", NameArg)
30+ , (":total", NameArg)
31+ , (":l", FileArg)
32+ , (":load", FileArg)
33+ , (":m", ModuleArg) -- NOTE: Argumentless form is a different command
34+ , (":p", NameArg)
35+ , (":prove", NameArg)
36+ , (":a", NameArg)
37+ , (":addproof", NameArg)
38+ , (":rmproof", NameArg)
39+ , (":showproof", NameArg)
40+ , (":c", FileArg)
41+ , (":compile", FileArg)
42+ , (":js", FileArg)
43+ , (":javascript", FileArg)
44+ , (":set", OptionArg)
45+ , (":unset", OptionArg)
46+ ]
47+commands = map fst cmdArgs
48+
49+-- | A specification of the arguments that tactics can take
50+data TacticArg = NameTArg -- ^ Names: n1, n2, n3, ... n
51+ | ExprTArg
52+ | AltsTArg
53+
54+-- | A list of available tactics and their argument requirements
55+tacticArgs :: [(String, Maybe TacticArg)]
56+tacticArgs = [ ("intro", Nothing)
57+ , ("intros", Nothing)
58+ , ("refine", Just ExprTArg)
59+ , ("rewrite", Just ExprTArg)
60+ , ("let", Nothing) -- FIXME syntax for let
61+ , ("focus", Just ExprTArg)
62+ , ("exact", Just ExprTArg)
63+ , ("reflect", Just ExprTArg)
64+ , ("try", Just AltsTArg)
65+ ] ++ map (\x -> (x, Nothing)) [
66+ "intro", "intros", "compute", "trivial", "solve", "attack",
67+ "state", "term", "undo", "qed", "abandon", ":q"
68+ ]
69+tactics = map fst tacticArgs
70+
71+-- | Convert a name into a string usable for completion. Filters out names
72+-- that users probably don't want to see.
73+nameString :: Name -> Maybe String
74+nameString (UN ('@':_)) = Nothing
75+nameString (UN ('#':_)) = Nothing
76+nameString (UN n) = Just n
77+nameString (NS n _) = nameString n
78+nameString _ = Nothing
79+
80+-- FIXME: Respect module imports
81+-- | Get the user-visible names from the current interpreter state.
82+names :: Idris[String]
83+names = do i <- get
84+ let ctxt = tt_ctxt i
85+ return $ nub $ mapMaybe (nameString . fst) $ ctxtAlist ctxt
86+
87+
88+modules :: Idris [String]
89+modules = do i <- get
90+ return $ map show $ imported i
91+
92+
93+
94+completeWith :: [String] -> String -> [Completion]
95+completeWith ns n = if uniqueExists
96+ then [simpleCompletion n]
97+ else map simpleCompletion prefixMatches
98+ where prefixMatches = filter (isPrefixOf n) ns
99+ uniqueExists = n `elem` prefixMatches
100+
101+completeName :: [String] -> String -> Idris [Completion]
102+completeName extra n = do ns <- names
103+ return $ completeWith (extra ++ ns) n
104+
105+completeExpr :: [String] -> CompletionFunc Idris
106+completeExpr extra = completeWord Nothing " \t" (completeName extra)
107+
108+completeOption :: CompletionFunc Idris
109+completeOption = completeWord Nothing " \t" completeOpt
110+ where completeOpt = return . (completeWith ["errorcontext", "showimplicits"])
111+
112+isWhitespace :: Char -> Bool
113+isWhitespace = (flip elem) " \t\n"
114+
115+-- | Get the completion function for a particular command
116+completeCmd :: String -> CompletionFunc Idris
117+completeCmd cmd (prev, next) = fromMaybe completeCmdName $ fmap completeArg $ lookup cmd cmdArgs
118+ where completeArg FileArg = completeFilename (prev, next)
119+ completeArg NameArg = completeExpr [] (prev, next) -- FIXME only complete one name
120+ completeArg OptionArg = completeOption (prev, next)
121+ completeArg ModuleArg = noCompletion (prev, next) -- FIXME do later
122+ completeArg ExprArg = completeExpr [] (prev, next)
123+ completeCmdName = return $ ("", completeWith commands cmd)
124+
125+replCompletion :: CompletionFunc Idris
126+replCompletion (prev, next) = case firstWord of
127+ ':':cmdName -> completeCmd (':':cmdName) (prev, next)
128+ _ -> completeExpr [] (prev, next)
129+ where firstWord = fst $ break isWhitespace $ dropWhile isWhitespace $ reverse prev
130+
131+
132+completeTactic :: [String] -> String -> CompletionFunc Idris
133+completeTactic as tac (prev, next) = fromMaybe completeTacName $ fmap completeArg $ lookup tac tacticArgs
134+ where completeTacName = return $ ("", completeWith tactics tac)
135+ completeArg Nothing = noCompletion (prev, next)
136+ completeArg (Just NameTArg) = noCompletion (prev, next) -- this is for binding new names!
137+ completeArg (Just ExprTArg) = completeExpr as (prev, next)
138+ completeArg (Just AltsTArg) = noCompletion (prev, next) -- TODO
139+
140+
141+proverCompletion :: [String] -> CompletionFunc Idris
142+proverCompletion assumptions (prev, next) = completeTactic assumptions firstWord (prev, next)
143+ where firstWord = fst $ break isWhitespace $ dropWhile isWhitespace $ reverse prev
src/Idris/Prover.hs+24−7
@@ -13,8 +13,10 @@ import Idris.ElabTerm
1313import Idris.Parser
1414import Idris.Error
1515import Idris.DataOpts
16+import Idris.Completion
1617
1718import System.Console.Haskeline
19+import System.Console.Haskeline.History
1820import Control.Monad.State
1921
2022import Util.Pretty
@@ -37,10 +39,18 @@ showProof lit n ps
3739 where bird = if lit then "> " else ""
3840 break = "\n" ++ bird
3941
42+proverSettings :: ElabState [PDecl] -> Settings Idris
43+proverSettings e = setComplete (proverCompletion assumptionNames) defaultSettings
44+ where assumptionNames = case envAtFocus (proof e) of
45+ OK env -> names env
46+ names [] = []
47+ names ((MN _ _, _) : bs) = names bs
48+ names ((n, _) : bs) = show n : names bs
49+
4050prove :: Context -> Bool -> Name -> Type -> Idris ()
4151prove ctxt lit n ty
4252 = do let ps = initElaborator n ctxt ty
43− (tm, prf) <- ploop True ("-" ++ show n) [] (ES (ps, []) "" Nothing)
53+ (tm, prf) <- ploop True ("-" ++ show n) [] (ES (ps, []) "" Nothing) Nothing
4454 iLOG $ "Adding " ++ show tm
4555 iputStrLn $ showProof lit n prf
4656 i <- getIState
@@ -108,18 +118,25 @@ lifte :: ElabState [PDecl] -> ElabD a -> Idris a
108118lifte st e = do (v, _) <- elabStep st e
109119 return v
110120
111−ploop :: Bool -> String -> [String] -> ElabState [PDecl] -> Idris (Term, [String])
112−ploop d prompt prf e
121+ploop :: Bool -> String -> [String] -> ElabState [PDecl] -> Maybe History -> Idris (Term, [String])
122+ploop d prompt prf e h
113123 = do i <- getIState
114124 when d $ liftIO $ dumpState i (proof e)
115− x <- getInputLine (prompt ++ "> ")
125+ (x, h') <- runInputT (proverSettings e) $
126+ -- Manually track the history so that we can use the proof state
127+ do _ <- case h of
128+ Just history -> putHistory history
129+ Nothing -> return ()
130+ l <- getInputLine (prompt ++ "> ")
131+ h' <- getHistory
132+ return (l, Just h')
116133 (cmd, step) <- case x of
117134 Nothing -> fail "Abandoned"
118135 Just input -> do return (parseTac i input, input)
119136 case cmd of
120137 Right Abandon -> fail "Abandoned"
121138 _ -> return ()
122− (d, st, done, prf') <- idrisCatch
139+ (d, st, done, prf') <- idrisCatch
123140 (case cmd of
124141 Left err -> do iputStrLn (show err)
125142 return (False, e, False, prf)
@@ -140,7 +157,7 @@ ploop d prompt prf e
140157 return (True, st, False, prf ++ [step]))
141158 (\err -> do iputStrLn (show err)
142159 return (False, e, False, prf))
143− if done then do (tm, _) <- elabStep st get_term
160+ if done then do (tm, _) <- elabStep st get_term
144161 return (tm, prf')
145− else ploop d prompt prf' st
162+ else ploop d prompt prf' st h'
146163
src/Idris/REPL.hs+10−13
@@ -16,6 +16,7 @@ import Idris.Primitives
1616import Idris.Coverage
1717import Idris.UnusedArgs
1818import Idris.Docs
19+import Idris.Completion
1920
2021import Paths_idris
2122import Util.System
@@ -49,23 +50,23 @@ import Data.List
4950import Data.Char
5051import Data.Version
5152
52−repl :: IState -> [FilePath] -> Idris ()
53+repl :: IState -> [FilePath] -> InputT Idris ()
5354repl orig mods
5455 = H.catch
5556 (do let prompt = mkPrompt mods
5657 x <- getInputLine (prompt ++ "> ")
5758 case x of
58− Nothing -> do iputStrLn "Bye bye"
59+ Nothing -> do lift $ iputStrLn "Bye bye"
5960 return ()
6061 Just input -> H.catch
61− (do ms <- processInput input orig mods
62+ (do ms <- lift $ processInput input orig mods
6263 case ms of
6364 Just mods -> repl orig mods
6465 Nothing -> return ())
6566 ctrlC)
6667 ctrlC
67− where ctrlC :: SomeException -> Idris ()
68− ctrlC e = do iputStrLn (show e)
68+ where ctrlC :: SomeException -> InputT Idris ()
69+ ctrlC e = do lift $ iputStrLn (show e)
6970 repl orig mods
7071
7172mkPrompt [] = "Idris"
@@ -489,17 +490,13 @@ help =
489490 ([":q",":quit"], "", "Exit the Idris system")
490491 ]
491492
492−idrisCompletion :: CompletionFunc (StateT IState IO)
493−idrisCompletion (before, after) = return (before, [])
494493
495−haskelineSettings :: Settings (StateT IState IO)
496−haskelineSettings = setComplete idrisCompletion defaultSettings
494+replSettings :: Settings Idris
495+replSettings = setComplete replCompletion defaultSettings
497496
498497-- invoke as if from command line
499498idris :: [Opt] -> IO IState
500−idris opts = execStateT (runInputT haskelineSettings $ idrisMain opts) idrisInit
501−
502−--runInputT haskelineSettings $ execStateT (idrisMain opts) idrisInit
499+idris opts = execStateT (idrisMain opts) idrisInit
503500
504501idrisMain :: [Opt] -> Idris ()
505502idrisMain opts =
@@ -554,7 +551,7 @@ idrisMain opts =
554551 when ok $ case newoutput of
555552 [] -> return ()
556553 (o:_) -> process "" (NewCompile o)
557− when runrepl $ repl ist inputs
554+ when runrepl $ runInputT replSettings $ repl ist inputs
558555 ok <- noErrors
559556 when (not ok) $ liftIO (exitWith (ExitFailure 1))
560557 where
src/Main.hs+1−1
@@ -39,7 +39,7 @@ import Paths_idris
3939
4040main = do xs <- getArgs
4141 let opts = parseArgs xs
42− execStateT (runInputT haskelineSettings $ runIdris opts) idrisInit
42+ execStateT (runIdris opts) idrisInit
4343
4444runIdris :: [Opt] -> Idris ()
4545runIdris opts = do