2Module : Idris.Interactive
3Description : Bits and pieces for editing source files interactively, called from the REPL
6Maintainer : The Idris Community.
9{-# LANGUAGE PatternGuards #-}
11module Idris.Interactive(
12 caseSplitAt, addClauseFrom, addProofClauseFrom
13 , addMissing, makeWith, makeCase, doProofSearch
18import Idris.Core.Evaluate
23import Idris.ErrReverse
24import Idris.Delaborate
26import Idris.IdeMode hiding (IdeModeCommand(..))
27import Idris.Elab.Value
34import System.Directory
37import Data.Maybe (fromMaybe)
38import Data.List (isSuffixOf)
42caseSplitAt :: FilePath -> Bool -> Int -> Name -> Idris ()
43caseSplitAt fn updatefile l n
44 = do src <- runIO $ readSource fn
45 (ok, res) <- splitOnLine l n fn
46 logLvl 1 (showSep "\n" (map show res))
47 let (before, (ap : later)) = splitAt (l-1) (lines src)
48 res' <- replaceSplits ap res (not ok)
51 then do let fb = fn ++ "~" -- make a backup!
52 runIO $ writeSource fb (unlines before ++ new ++ unlines later)
53 runIO $ copyFile fb fn
54 else -- do iputStrLn (show res)
57addClauseFrom :: FilePath -> Bool -> Int -> Name -> Idris ()
58addClauseFrom fn updatefile l n = do
59 -- if a definition already exists, add missing cases rather than
60 -- adding a new definition.
62 cl <- getInternalApp fn l
63 let fulln = getAppName cl
65 case lookup fulln (idris_metavars ist) of
66 Nothing -> addMissing fn updatefile l n
68 src <- runIO $ readSource fn
69 let (before, tyline : later) = splitAt (l-1) (lines src)
70 let indent = getIndent 0 (show n) tyline
71 cl <- getClause l fulln n fn
72 -- add clause before first blank line in 'later'
73 let (nonblank, rest) = span (not . all isSpace) (tyline:later)
75 then do let fb = fn ++ "~"
76 runIO $ writeSource fb (unlines (before ++ nonblank) ++
77 replicate indent ' ' ++
80 runIO $ copyFile fb fn
84 getIndent i n xs | take 9 xs == "instance " = i
85 getIndent i n xs | take (length n) xs == n = i
86 getIndent i n (x : xs) = getIndent (i + 1) n xs
88 getAppName (PApp _ r _) = getAppName r
89 getAppName (PRef _ _ r) = r
90 getAppName (PTyped n _) = getAppName n
93addProofClauseFrom :: FilePath -> Bool -> Int -> Name -> Idris ()
94addProofClauseFrom fn updatefile l n
95 = do src <- runIO $ readSource fn
96 let (before, tyline : later) = splitAt (l-1) (lines src)
97 let indent = getIndent 0 (show n) tyline
98 cl <- getProofClause l n fn
99 -- add clause before first blank line in 'later'
100 let (nonblank, rest) = span (not . all isSpace) (tyline:later)
102 then do let fb = fn ++ "~"
103 runIO $ writeSource fb (unlines (before ++ nonblank) ++
104 replicate indent ' ' ++
107 runIO $ copyFile fb fn
111 getIndent i n xs | take (length n) xs == n = i
112 getIndent i n (x : xs) = getIndent (i + 1) n xs
114addMissing :: FilePath -> Bool -> Int -> Name -> Idris ()
115addMissing fn updatefile l n
116 = do src <- runIO $ readSource fn
117 let (before, tyline : later) = splitAt (l-1) (lines src)
118 let indent = getIndent 0 (show n) tyline
120 cl <- getInternalApp fn l
121 let n' = getAppName cl
123 extras <- case lookupCtxtExact n' (idris_patdefs i) of
125 Just (_, tms) -> do tms' <- nameMissing tms
126 showNew (show n ++ "_rhs") 1 indent tms'
127 let (nonblank, rest) = span (not . all isSpace) (tyline:later)
129 then do let fb = fn ++ "~"
130 runIO $ writeSource fb (unlines (before ++ nonblank)
132 (if null extras then ""
133 else "\n" ++ unlines rest))
134 runIO $ copyFile fb fn
135 else iPrintResult extras
136 where showPat = show . stripNS
137 stripNS tm = mapPT dens tm where
138 dens (PRef fc hls n) = PRef fc hls (nsroot n)
141 nsroot (NS n _) = nsroot n
142 nsroot (SN (WhereN _ _ n)) = nsroot n
145 getAppName (PApp _ r _) = getAppName r
146 getAppName (PRef _ _ r) = r
147 getAppName (PTyped n _) = getAppName n
150 makeIndent ind | ".lidr" `isSuffixOf` fn = '>' : ' ' : replicate (ind-2) ' '
151 | otherwise = replicate ind ' '
153 showNew nm i ind (tm : tms)
154 = do (nm', i') <- getUniq nm i
155 rest <- showNew nm i' ind tms
156 return (makeIndent ind ++
157 showPat tm ++ " = ?" ++ nm' ++
158 (if null rest then "" else
160 showNew nm i _ [] = return ""
163 getIndent i n xs | take (length n) xs == n = i
164 getIndent i n (x : xs) = getIndent (i + 1) n xs
167makeWith :: FilePath -> Bool -> Int -> Name -> Idris ()
168makeWith fn updatefile l n
169 = do src <- runIO $ readSource fn
170 let (before, tyline : later) = splitAt (l-1) (lines src)
171 let ind = getIndent tyline
172 let with = mkWith tyline n
173 -- add clause before first blank line in 'later',
174 -- or (TODO) before first line with same indentation as tyline
175 let (nonblank, rest) = span (\x -> not (all isSpace x) &&
176 not (ind == getIndent x)) later
178 do let fb = fn ++ "~"
179 runIO $ writeSource fb (unlines (before ++ nonblank)
182 runIO $ copyFile fb fn
183 else iPrintResult (with ++ "\n")
184 where getIndent s = length (takeWhile isSpace s)
186-- Replace the given metavariable on the given line with a 'case'
187-- block, using a _ for the scrutinee
188makeCase :: FilePath -> Bool -> Int -> Name -> Idris ()
189makeCase fn updatefile l n
190 = do src <- runIO $ readSource fn
191 let (before, tyline : later) = splitAt (l-1) (lines src)
192 let newcase = addCaseSkel (show n) tyline
195 do let fb = fn ++ "~"
196 runIO $ writeSource fb (unlines (before ++ newcase ++ later))
197 runIO $ copyFile fb fn
198 else iPrintResult (showSep "\n" newcase ++"\n")
199 where addCaseSkel n line =
200 let b = brackets False line in
201 case findSubstr ('?':n) line of
202 Just (before, pos, after) ->
203 [before ++ (if b then "(" else "") ++ "case _ of",
204 take (pos + (if b then 6 else 5)) (repeat ' ') ++
205 "case_val => ?" ++ n ++ (if b then ")" else "")
207 Nothing -> fail "No such metavariable"
209 -- Assume case needs to be bracketed unless the metavariable is
210 -- on its own after an =
211 brackets eq line | line == '?' : show n = not eq
212 brackets eq ('=':ls) = brackets True ls
213 brackets eq (' ':ls) = brackets eq ls
214 brackets eq (l : ls) = brackets False ls
215 brackets eq [] = True
217 findSubstr n xs = findSubstr' [] 0 n xs
219 findSubstr' acc i n xs | take (length n) xs == n
220 = Just (reverse acc, i, drop (length n) xs)
221 findSubstr' acc i n [] = Nothing
222 findSubstr' acc i n (x : xs) = findSubstr' (x : acc) (i + 1) n xs
228doProofSearch :: FilePath -> Bool -> Bool ->
229 Int -> Name -> [Name] -> Maybe Int -> Idris ()
230doProofSearch fn updatefile rec l n hints Nothing
231 = doProofSearch fn updatefile rec l n hints (Just 20)
232doProofSearch fn updatefile rec l n hints (Just depth)
233 = do src <- runIO $ readSource fn
234 let (before, tyline : later) = splitAt (l-1) (lines src)
236 mn <- case lookupNames n ctxt of
239 ns -> ierror (CantResolveAlts ns)
241 let (top, envlen, psnames)
242 = case lookup mn (idris_metavars i) of
243 Just (t, e, ns, False, d) -> (t, e, ns)
244 _ -> (Nothing, 0, [])
246 let body t = PProof [Try (TSeq Intros (ProofSearch rec False depth t psnames hints))
247 (ProofSearch rec False depth t psnames hints)]
248 let def = PClause fc mn (PRef fc [] mn) [] (body top) []
249 newmv_pre <- idrisCatch
250 (do elabDecl' EAll (recinfo (fileFC "proofsearch")) (PClauses fc [] mn [def])
251 (tm, ty) <- elabVal (recinfo (fileFC "proofsearch")) ERHS (PRef fc [] mn)
254 return . flip displayS "" . renderPretty 1.0 80 $
255 pprintPTerm defaultPPOption [] [] (idris_infixes i)
258 (delab i (fst (specialise ctxt [] [(mn, 1)] tm))))))
259 (\e -> return ("?" ++ show n))
260 let newmv = guessBrackets False tyline (show n) newmv_pre
262 do let fb = fn ++ "~"
263 runIO $ writeSource fb (unlines before ++
264 updateMeta tyline (show n) newmv ++ "\n"
266 runIO $ copyFile fb fn
267 else iPrintResult newmv
268 where dropCtxt 0 sc = sc
269 dropCtxt i (PPi _ _ _ _ sc) = dropCtxt (i - 1) sc
270 dropCtxt i (PLet _ _ _ _ _ sc) = dropCtxt (i - 1) sc
271 dropCtxt i (PLam _ _ _ _ sc) = dropCtxt (i - 1) sc
274 stripNS tm = mapPT dens tm where
275 dens (PRef fc hls n) = PRef fc hls (nsroot n)
278 nsroot (NS n _) = nsroot n
279 nsroot (SN (WhereN _ _ n)) = nsroot n
282updateMeta ('?':cs) n new
283 | length cs >= length n
284 = case splitAt (length n) cs of
286 if ((isSpace c || c == ')' || c == '}') && mv == n)
288 else '?' : mv ++ c : updateMeta cs n new
289 (mv, []) -> if (mv == n) then new else '?' : mv
290updateMeta ('=':'>':cs) n new = '=':'>':updateMeta cs n new
291updateMeta ('=':cs) n new = '=':updateMeta cs n new
292updateMeta (c:cs) n new
293 = c : updateMeta cs n new
294updateMeta [] n new = ""
296guessBrackets brack ('?':cs) n new
297 | length cs >= length n
298 = case splitAt (length n) cs of
300 if ((isSpace c || c == ')' || c == '}') && mv == n)
301 then addBracket brack new
302 else guessBrackets True cs n new
303 (mv, []) -> if (mv == n) then addBracket brack new else '?' : mv
304guessBrackets brack ('=':'>':cs) n new = guessBrackets False cs n new
305guessBrackets brack ('-':'>':cs) n new = guessBrackets False cs n new
306guessBrackets brack ('=':cs) n new = guessBrackets False cs n new
307guessBrackets brack (c:cs) n new
308 = guessBrackets (brack || not (isSpace c)) cs n new
309guessBrackets brack [] n new = ""
311checkProv line n = isProv' False line n
313 isProv' p cs n | take (length n) cs == n = p
314 isProv' _ ('?':cs) n = isProv' False cs n
315 isProv' _ ('{':cs) n = isProv' True cs n
316 isProv' _ ('}':cs) n = isProv' True cs n
317 isProv' p (_:cs) n = isProv' p cs n
318 isProv' _ [] n = False
320addBracket False new = new
321addBracket True new@('(':xs) | last xs == ')' = new
322addBracket True new | any isSpace new = '(' : new ++ ")"
326makeLemma :: FilePath -> Bool -> Int -> Name -> Idris ()
327makeLemma fn updatefile l n
328 = do src <- runIO $ readSource fn
329 let (before, tyline : later) = splitAt (l-1) (lines src)
331 -- if the name is in braces, rather than preceded by a ?, treat it
332 -- as a lemma in a provisional definition
334 let isProv = checkProv tyline (show n)
337 (fname, mty_full) <- case lookupTyName n ctxt of
339 [] -> ierror (NoSuchVariable n)
340 ns -> ierror (CantResolveAlts (map fst ns))
343 let mty = errReverse i mty_full
345 margs <- case lookup fname (idris_metavars i) of
346 Just (_, arity, _, _, _) -> return arity
349 if (not isProv) then do
350 let skip = guessImps i (tt_ctxt i) mty
351 let impty = stripMNBind skip margs (delab i mty)
352 let classes = guessClasses i (tt_ctxt i) [] (allNamesIn impty) mty
354 let lem = show n ++ " : " ++
355 constraints i classes mty ++
356 showTmOpts (defaultPPOption { ppopt_pinames = True })
358 let lem_app = guessBrackets False tyline (show n) (show n ++ appArgs skip margs mty)
361 do let fb = fn ++ "~"
362 runIO $ writeSource fb (addLem before tyline lem lem_app later)
363 runIO $ copyFile fb fn
364 else case idris_outputmode i of
365 RawOutput _ -> iPrintResult $ lem ++ "\n" ++ lem_app
367 let good = SexpList [SymbolAtom "ok",
368 SexpList [SymbolAtom "metavariable-lemma",
369 SexpList [SymbolAtom "replace-metavariable",
371 SexpList [SymbolAtom "definition-type",
373 in runIO . hPutStrLn h $ convSExp "return" good n
375 else do -- provisional definition
376 let lem_app = show n ++ appArgs [] (-1) mty ++
377 " = ?" ++ show n ++ "_rhs"
379 do let fb = fn ++ "~"
380 runIO $ writeSource fb (addProv before tyline lem_app later)
381 runIO $ copyFile fb fn
382 else case idris_outputmode i of
383 RawOutput _ -> iPrintResult lem_app
385 let good = SexpList [SymbolAtom "ok",
386 SexpList [SymbolAtom "provisional-definition-lemma",
387 SexpList [SymbolAtom "definition-clause",
388 StringAtom lem_app]]]
389 in runIO . hPutStrLn h $ convSExp "return" good n
391 where getIndent s = length (takeWhile isSpace s)
393 appArgs skip 0 _ = ""
394 appArgs skip i (Bind n@(UN c) (Pi _ _ _) sc)
395 | (thead c /= '_' && n `notElem` skip)
396 = " " ++ show n ++ appArgs skip (i - 1) sc
397 appArgs skip i (Bind _ (Pi _ _ _) sc) = appArgs skip (i - 1) sc
398 appArgs skip i _ = ""
400 stripMNBind _ args t | args <= 0 = stripImp t
401 stripMNBind skip args (PPi b n@(UN c) _ ty sc)
402 | n `notElem` skip ||
403 take 4 (str c) == "__pi" -- keep in type, but not in app
404 = PPi b n NoFC (stripImp ty) (stripMNBind skip (args - 1) sc)
405 stripMNBind skip args (PPi b _ _ ty sc) = stripMNBind skip (args - 1) sc
406 stripMNBind skip args t = stripImp t
408 stripImp (PApp fc f as) = PApp fc (stripImp f) (map placeholderImp as)
410 placeholderImp tm@(PImp _ _ _ _ _) = tm { getTm = Placeholder }
411 placeholderImp tm = tm { getTm = stripImp (getTm tm) }
412 stripImp (PPi b n f ty sc) = PPi b n f (stripImp ty) (stripImp sc)
415 constraints :: IState -> [Name] -> Type -> String
416 constraints i [] ty = ""
417 constraints i [n] ty = showSep ", " (showConstraints i [n] ty) ++ " => "
418 constraints i ns ty = "(" ++ showSep ", " (showConstraints i ns ty) ++ ") => "
420 showConstraints i ns (Bind n (Pi _ ty _) sc)
421 | n `elem` ns = show (delab i ty) :
422 showConstraints i ns (substV (P Bound n Erased) sc)
423 | otherwise = showConstraints i ns (substV (P Bound n Erased) sc)
424 showConstraints _ _ _ = []
426 -- Guess which binders should be implicits in the generated lemma.
427 -- Make them implicit if they appear guarded by a top level constructor,
428 -- or at the top level themselves.
429 -- Also, make type class instances implicit
430 guessImps :: IState -> Context -> Term -> [Name]
431 -- machine names aren't lifted
432 guessImps ist ctxt (Bind n@(MN _ _) (Pi _ ty _) sc)
433 = n : guessImps ist ctxt sc
434 guessImps ist ctxt (Bind n (Pi _ ty _) sc)
435 | guarded ctxt n (substV (P Bound n Erased) sc)
436 = n : guessImps ist ctxt sc
438 = n : guessImps ist ctxt sc
439 | paramty ty = n : guessImps ist ctxt sc
440 | ignoreName n = n : guessImps ist ctxt sc
441 | otherwise = guessImps ist ctxt sc
442 guessImps ist ctxt _ = []
444 paramty (TType _) = True
445 paramty (Bind _ (Pi _ (TType _) _) sc) = paramty sc
448 -- TMP HACK unusable name so don't lift
449 ignoreName n = case show n of
453 guessClasses :: IState -> Context -> [Name] -> [Name] -> Term -> [Name]
454 guessClasses ist ctxt binders usednames (Bind n (Pi _ ty _) sc)
455 | isParamClass ist ty && any (\x -> elem x usednames)
456 (paramNames binders ty)
457 = n : guessClasses ist ctxt (n : binders) usednames sc
458 | otherwise = guessClasses ist ctxt (n : binders) usednames sc
459 guessClasses ist ctxt _ _ _ = []
461 paramNames bs ty | (P _ _ _, args) <- unApply ty
464 vnames (V i : xs) | i < length bs = bs !! i : vnames xs
465 vnames (_ : xs) = vnames xs
468 | (P _ n _, args) <- unApply t
469 = case lookupCtxtExact n (idris_classes ist) of
475 | (P _ n _, args) <- unApply t
476 = case lookupCtxtExact n (idris_classes ist) of
477 Just _ -> any isV args
480 where isV (V _) = True
483 guarded ctxt n (P _ n' _) | n == n' = True
484 guarded ctxt n ap@(App _ _ _)
485 | (P _ f _, args) <- unApply ap,
486 isConName f ctxt = any (guarded ctxt n) args
487-- guarded ctxt n (Bind (UN cn) (Pi t) sc) -- ignore shadows
488-- | thead cn /= '_' = guarded ctxt n t || guarded ctxt n sc
489 guarded ctxt n (Bind _ (Pi _ t _) sc)
490 = guarded ctxt n t || guarded ctxt n sc
491 guarded ctxt n _ = False
495 addLem before tyline lem lem_app later
496 = let (bef_end, blankline : bef_start)
497 = case span (not . blank) (reverse before) of
498 (bef, []) -> (bef, [""])
500 mvline = updateMeta tyline (show n) lem_app in
501 unlines $ reverse bef_start ++
502 [blankline, lem, blankline] ++
503 reverse bef_end ++ mvline : later
505 addProv before tyline lem_app later
506 = let (later_bef, blankline : later_end)
507 = case span (not . blank) later of
508 (bef, []) -> (bef, [""])
510 unlines $ before ++ tyline :
511 (later_bef ++ [blankline, lem_app, blankline] ++
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list