2Module : Idris.CaseSplit
3Description : Module to provide case split functionality.
6Maintainer : The Idris Community.
8Given a pattern clause and a variable 'n', elaborate the clause and find the
11Make new pattern clauses by replacing 'n' with all the possibly constructors
12applied to '_', and replacing all other variables with '_' in order to
13resolve other dependencies.
15Finally, merge the generated patterns with the original, by matching.
16Always take the "more specific" argument when there is a discrepancy, i.e.
17names over '_', patterns over names, etc.
20{-# LANGUAGE PatternGuards #-}
22module Idris.CaseSplit(
23 splitOnLine, replaceSplits
24 , getClause, getProofClause
31import Idris.AbsSyntaxTree (Idris, IState, PTerm)
33import Idris.Delaborate
35import Idris.Parser.Helpers
39import Idris.Elab.Value
43import Idris.Core.Typecheck
44import Idris.Core.Evaluate
48import Data.List (isPrefixOf, isSuffixOf)
50import Control.Monad.State.Strict
52import Text.Parser.Combinators
53import Text.Parser.Char(anyChar)
54import Text.Trifecta(Result(..), parseString)
55import Text.Trifecta.Delta
59-- | Given a variable to split, and a term application, return a list
60-- of variable updates, paired with a flag to say whether the given
61-- update typechecks (False = impossible) if the flag is 'False' the
62-- splits should be output with the 'impossible' flag, otherwise they
63-- should be output as normal
64split :: Name -> PTerm -> Idris (Bool, [[(Name, PTerm)]])
67 -- Make sure all the names in the term are accessible
68 mapM_ (\n -> setAccessibility n Public) (allNamesIn t')
69 -- ETyDecl rather then ELHS because there'll be explicit type
71 (tm, ty, pats) <- elabValBind (recinfo (fileFC "casesplit")) ETyDecl True (addImplPat ist t')
72 -- ASSUMPTION: tm is in normal form after elabValBind, so we don't
73 -- need to do anything special to find out what family each argument
75 logElab 4 ("Elaborated:\n" ++ show tm ++ " : " ++ show ty ++ "\n" ++ show pats)
76-- iputStrLn (show (delab ist tm) ++ " : " ++ show (delab ist ty))
77-- iputStrLn (show pats)
78 let t = mergeUserImpl (addImplPat ist t') (delabDirect ist tm)
79 let ctxt = tt_ctxt ist
81 Nothing -> ifail $ show n ++ " is not a pattern variable"
83 do let splits = findPats ist ty
84 logElab 1 ("New patterns " ++ showSep ", "
85 (map showTmImpls splits))
86 let newPats_in = zipWith (replaceVar ctxt n) splits (repeat t)
87 logElab 4 ("Working from " ++ showTmImpls t)
88 logElab 4 ("Trying " ++ showSep "\n"
89 (map (showTmImpls) newPats_in))
90 newPats_in <- mapM elabNewPat newPats_in
91 case anyValid [] [] newPats_in of
93 let fails' = mergeAllPats ist n t fails
94 return (False, (map snd fails'))
96 logElab 3 ("Original:\n" ++ show t)
97 logElab 3 ("Split:\n" ++
98 (showSep "\n" (map show newPats)))
100 let newPats' = mergeAllPats ist n t newPats
101 logElab 1 ("Name updates " ++ showSep "\n"
102 (map (\ (p, u) -> show u ++ " " ++ show p) newPats'))
103 return (True, (map snd newPats'))
105 anyValid ok bad [] = if null ok then Left (reverse bad)
106 else Right (reverse ok)
107 anyValid ok bad ((tc, p) : ps)
108 | tc = anyValid (p : ok) bad ps
109 | otherwise = anyValid ok (p : bad) ps
111data MergeState = MS { namemap :: [(Name, Name)],
112 invented :: [(Name, Name)],
114 updates :: [(Name, PTerm)] }
116addUpdate :: Name -> Idris.AbsSyntaxTree.PTerm -> State MergeState ()
117addUpdate n tm = do ms <- get
118 put (ms { updates = ((n, stripNS tm) : updates ms) } )
120inventName :: Idris.AbsSyntaxTree.IState -> Maybe Name -> Name -> State MergeState Name
123 let supp = case ty of
125 Just t -> getNameHints ist t
126 let nsupp = case n of
127 MN i n | not (tnull n) && thead n == '_'
128 -> mkSupply (supp ++ varlist)
129 MN i n -> mkSupply (UN n : supp ++ varlist)
130 UN n | thead n == '_'
131 -> mkSupply (supp ++ varlist)
132 x -> mkSupply (x : supp)
133 let badnames = map snd (namemap ms) ++ map snd (invented ms) ++
135 case lookup n (invented ms) of
138 do let n' = uniqueNameFrom nsupp badnames
139 put (ms { invented = (n, n') : invented ms })
142mkSupply :: [Name] -> [Name]
143mkSupply ns = mkSupply' ns (map nextName ns)
144 where mkSupply' xs ns' = xs ++ mkSupply ns'
147varlist = map (sUN . (:[])) "xyzwstuv" -- EB's personal preference :)
149stripNS :: Idris.AbsSyntaxTree.PTerm -> Idris.AbsSyntaxTree.PTerm
150stripNS tm = mapPT dens tm where
151 dens (PRef fc hls n) = PRef fc hls (nsroot n)
154mergeAllPats :: IState -> Name -> PTerm -> [PTerm] -> [(PTerm, [(Name, PTerm)])]
155mergeAllPats ist cv t [] = []
156mergeAllPats ist cv t (p : ps)
157 = let (p', MS _ _ _ u) = runState (mergePat ist t p Nothing)
158 (MS [] [] (filter (/=cv) (patvars t)) [])
159 ps' = mergeAllPats ist cv t ps in
161 where patvars (PRef _ _ n) = [n]
162 patvars (PApp _ _ as) = concatMap (patvars . getTm) as
163 patvars (PPatvar _ n) = [n]
166mergePat :: IState -> PTerm -> PTerm -> Maybe Name -> State MergeState PTerm
167mergePat ist orig new t =
168 do -- collect user names for name map, by matching user pattern against
169 -- the generated pattern
170 case matchClause ist orig new of
172 Right ns -> mapM_ addNameMap ns
173 mergePat' ist orig new t
175 addNameMap (n, PRef fc _ n') = do ms <- get
176 put (ms { namemap = ((n', n) : namemap ms) })
177 addNameMap _ = return ()
179-- | If any names are unified, make sure they stay unified. Always
180-- prefer user provided name (first pattern)
181mergePat' ist (PPatvar fc n) new t
182 = mergePat' ist (PRef fc [] n) new t
183mergePat' ist old (PPatvar fc n) t
184 = mergePat' ist old (PRef fc [] n) t
185mergePat' ist orig@(PRef fc _ n) new@(PRef _ _ n') t
186 | isDConName n' (tt_ctxt ist) = do addUpdate n new
190 case lookup n' (namemap ms) of
191 Just x -> do addUpdate n (PRef fc [] x)
192 return (PRef fc [] x)
193 Nothing -> do put (ms { namemap = ((n', n) : namemap ms) })
194 return (PRef fc [] n)
195mergePat' ist (PApp _ _ args) (PApp fc f args') t
196 = do newArgs <- zipWithM mergeArg args (zip args' (argTys ist f))
197 return (PApp fc f newArgs)
198 where mergeArg x (y, t)
199 = do tm' <- mergePat' ist (getTm x) (getTm y) t
202 return (y { machine_inf = machine_inf x,
204 _ -> return (y { getTm = tm' })
205mergePat' ist (PRef fc _ n) tm ty = do tm <- tidy ist tm ty
208mergePat' ist x y t = return y
210mergeUserImpl :: PTerm -> PTerm -> PTerm
213argTys :: IState -> PTerm -> [Maybe Name]
214argTys ist (PRef fc hls n)
215 = case lookupTy n (tt_ctxt ist) of
216 [ty] -> let ty' = normalise (tt_ctxt ist) [] ty in
217 map (tyName . snd) (getArgTys ty') ++ repeat Nothing
219 where tyName (Bind _ (Pi _ _ _) _) = Just (sUN "->")
220 tyName t | (P _ d _, [_, ty]) <- unApply t,
221 d == sUN "Delayed" = tyName ty
222 | (P _ n _, _) <- unApply t = Just n
223 | otherwise = Nothing
224argTys _ _ = repeat Nothing
226tidy :: IState -> PTerm -> Maybe Name -> State MergeState PTerm
227tidy ist orig@(PRef fc hls n) ty
229 case lookup n (namemap ms) of
230 Just x -> return (PRef fc [] x)
232 (UN _) -> return orig
233 _ -> do n' <- inventName ist ty n
234 return (PRef fc [] n')
235tidy ist (PApp fc f args) ty
236 = do args' <- zipWithM tidyArg args (argTys ist f)
237 return (PApp fc f args')
238 where tidyArg x ty' = do tm' <- tidy ist (getTm x) ty'
239 return (x { getTm = tm' })
240tidy ist tm ty = return tm
244-- where tidyVar (PRef _ _) = Placeholder
247elabNewPat :: PTerm -> Idris (Bool, PTerm)
248elabNewPat t = idrisCatch (do (tm, ty) <- elabVal (recinfo (fileFC "casesplit")) ELHS t
250 return (True, delabDirect i tm))
251 (\e -> do i <- getIState
252 logElab 5 $ "Not a valid split:\n" ++ showTmImpls t ++ "\n"
256findPats :: IState -> Type -> [PTerm]
257findPats ist t | (P _ n _, _) <- unApply t
258 = case lookupCtxt n (idris_datatypes ist) of
259 [ti] -> map genPat (con_names ti)
261 where genPat n = case lookupCtxt n (idris_implicits ist) of
262 [args] -> PApp emptyFC (PRef emptyFC [] n)
263 (map toPlaceholder args)
264 _ -> error $ "Can't happen (genPat) " ++ show n
265 toPlaceholder tm = tm { getTm = Placeholder }
266findPats ist t = [Placeholder]
268replaceVar :: Context -> Name -> PTerm -> PTerm -> PTerm
269replaceVar ctxt n t (PApp fc f pats) = PApp fc f (map substArg pats)
270 where subst :: PTerm -> PTerm
271 subst orig@(PPatvar _ v) | v == n = t
272 | otherwise = Placeholder
273 subst orig@(PRef _ _ v) | v == n = t
274 | isDConName v ctxt = orig
275 subst (PRef _ _ _) = Placeholder
276 subst (PApp fc (PRef _ _ t) pats)
277 | isTConName t ctxt = Placeholder -- infer types
278 subst (PApp fc f pats) = PApp fc f (map substArg pats)
281 substArg arg = arg { getTm = subst (getTm arg) }
283replaceVar ctxt n t pat = pat
285splitOnLine :: Int -- ^ line number
286 -> Name -- ^ variable
287 -> FilePath -- ^ name of file
288 -> Idris (Bool, [[(Name, PTerm)]])
289splitOnLine l n fn = do
290 cl <- getInternalApp fn l
291 logElab 3 ("Working with " ++ showTmImpls cl)
295replaceSplits :: String -> [[(Name, PTerm)]] -> Bool -> Idris [String]
296replaceSplits l ups impossible
297 = do ist <- getIState
298 updateRHSs 1 (map (rep ist (expandBraces l)) ups)
300 rep ist str [] = str ++ "\n"
301 rep ist str ((n, tm) : ups)
302 = rep ist (updatePat False (show n) (nshow (resugar ist tm)) str) ups
304 updateRHSs i [] = return []
305 updateRHSs i (x : xs)
306 | impossible = do xs' <- updateRHSs i xs
307 return (setImpossible False x : xs')
308 | otherwise = do (x', i') <- updateRHS (null xs) i x
309 xs' <- updateRHSs i' xs
312 updateRHS last i ('?':'=':xs) = do (xs', i') <- updateRHS last i xs
313 return ("?=" ++ xs', i')
314 updateRHS last i ('?':xs)
315 = do let (nm, rest_in) = span (not . (\x -> isSpace x || x == ')'
317 let rest = if last then rest_in else
318 case span (not . (=='\n')) rest_in of
319 (_, restnl) -> restnl
320 (nm', i') <- getUniq nm i
321 return ('?':nm' ++ rest, i')
322 updateRHS last i (x : xs) = do (xs', i') <- updateRHS last i xs
324 updateRHS last i [] = return ("", i)
326 setImpossible brace ('}':xs) = '}' : setImpossible False xs
327 setImpossible brace ('{':xs) = '{' : setImpossible True xs
328 setImpossible False ('=':xs) = "impossible\n"
329 setImpossible brace (x : xs) = x : setImpossible brace xs
330 setImpossible brace [] = ""
332 -- TMP HACK: If there are Nats, we don't want to show as numerals since
333 -- this isn't supported in a pattern, so special case here
334 nshow (PRef _ _ (UN z)) | z == txt "Z" = "Z"
335 nshow (PApp _ (PRef _ _ (UN s)) [x]) | s == txt "S" =
336 "(S " ++ addBrackets (nshow (getTm x)) ++ ")"
339 -- if there's any {n} replace with {n=n}
340 -- but don't replace it in comments
341 expandBraces ('{' : '-' : xs) = '{' : '-' : xs
342 expandBraces ('{' : xs)
343 = let (brace, (_:rest)) = span (/= '}') xs in
344 if (not ('=' `elem` brace))
345 then ('{' : brace ++ " = " ++ brace ++ "}") ++
347 else ('{' : brace ++ "}") ++ expandBraces rest
348 expandBraces (x : xs) = x : expandBraces xs
351 updatePat start n tm [] = []
352 updatePat start n tm ('{':rest) =
353 let (space, rest') = span isSpace rest in
354 '{' : space ++ updatePat False n tm rest'
355 updatePat start n tm done@('?':rest) = done
356 updatePat True n tm xs@(c:rest) | length xs > length n
357 = let (before, after@(next:_)) = splitAt (length n) xs in
358 if (before == n && not (isAlphaNum next))
359 then addBrackets tm ++ updatePat False n tm after
360 else c : updatePat (not (isAlphaNum c)) n tm rest
361 updatePat start n tm (c:rest) = c : updatePat (not ((isAlphaNum c) || c == '_')) n tm rest
363 addBrackets tm | ' ' `elem` tm
364 , not (isPrefixOf "(" tm && isSuffixOf ")" tm)
369getUniq :: (Show t, Num t) => [Char] -> t -> Idris ([Char], t)
371 = do ist <- getIState
372 let n = nameRoot [] nm ++ "_" ++ show i
373 case lookupTy (sUN n) (tt_ctxt ist) of
374 [] -> return (n, i+1)
375 _ -> getUniq nm (i+1)
377nameRoot acc nm | all isDigit nm = showSep "_" acc
379 case span (/='_') nm of
380 (before, ('_' : after)) -> nameRoot (acc ++ [before]) after
381 _ -> showSep "_" (acc ++ [nm])
383-- Show a name for use in pattern definitions on the lhs
384showLHSName :: Name -> String
385showLHSName n = let fn = show n in
386 if any (flip elem opChars) fn
387 then "(" ++ fn ++ ")"
390-- Show a name for the purpose of generating a metavariable name on the rhs
391showRHSName :: Name -> String
392showRHSName n = let fn = show n in
393 if any (flip elem opChars) fn
397getClause :: Int -- ^ line number that the type is declared on
398 -> Name -- ^ Function name
399 -> Name -- ^ User given name
400 -> FilePath -- ^ Source file name
404 case lookupCtxt un (idris_classes i) of
405 [c] -> return (mkClassBodies i (class_methods c))
406 _ -> do ty_in <- getInternalApp fp l
407 let ty = case ty_in of
411 let ap = mkApp ist ty []
412 return (showLHSName un ++ " " ++ ap ++ "= ?"
413 ++ showRHSName un ++ "_rhs")
414 where mkApp :: IState -> PTerm -> [Name] -> String
415 mkApp i (PPi (Exp _ _ False) (MN _ _) _ ty sc) used
416 = let n = getNameFrom i used ty in
417 show n ++ " " ++ mkApp i sc (n : used)
418 mkApp i (PPi (Exp _ _ False) (UN n) _ ty sc) used
420 = let n = getNameFrom i used ty in
421 show n ++ " " ++ mkApp i sc (n : used)
422 mkApp i (PPi (Exp _ _ False) n _ _ sc) used
423 = show n ++ " " ++ mkApp i sc (n : used)
424 mkApp i (PPi _ _ _ _ sc) used = mkApp i sc used
427 getNameFrom i used (PPi _ _ _ _ _)
428 = uniqueNameFrom (mkSupply [sUN "f", sUN "g"]) used
429 getNameFrom i used (PApp fc f as) = getNameFrom i used f
430 getNameFrom i used (PRef fc _ f)
431 = case getNameHints i f of
432 [] -> uniqueNameFrom (mkSupply [sUN "x", sUN "y",
434 ns -> uniqueNameFrom (mkSupply ns) used
435 getNameFrom i used _ = uniqueNameFrom (mkSupply [sUN "x", sUN "y",
438 -- write method declarations, indent with 4 spaces
439 mkClassBodies :: IState -> [(Name, (Bool, FnOpts, PTerm))] -> String
442 (zipWith (\(n, (_, _, ty)) m -> " " ++
443 def (show (nsroot n)) ++ " "
446 ++ showRHSName un ++ "_rhs_" ++ show m) ns [1..])
448 def n@(x:xs) | not (isAlphaNum x) = "(" ++ n ++ ")"
451getProofClause :: Int -- ^ line number that the type is declared
452 -> Name -- ^ Function name
453 -> FilePath -- ^ Source file name
455getProofClause l fn fp
456 = do ty_in <- getInternalApp fp l
457 let ty = case ty_in of
460 return (mkApp ty ++ " = ?" ++ showRHSName fn ++ "_rhs")
461 where mkApp (PPi _ _ _ _ sc) = mkApp sc
462 mkApp rt = "(" ++ show rt ++ ") <== " ++ show fn
464-- Purely syntactic - turn a pattern match clause into a with and a new
467mkWith :: String -> Name -> String
468mkWith str n = let str' = replaceRHS str "with (_)"
469 in str' ++ "\n" ++ newpat str
471 where replaceRHS [] str = str
472 replaceRHS ('?':'=': rest) str = str
473 replaceRHS ('=': rest) str
474 | not ('=' `elem` rest) = str
475 replaceRHS (x : rest) str = x : replaceRHS rest str
477 newpat ('>':patstr) = '>':newpat patstr
480 replaceRHS patstr "| with_pat = ?" ++ showRHSName n ++ "_rhs"
482-- Replace _ with names in missing clauses
484nameMissing :: [PTerm] -> Idris [PTerm]
485nameMissing ps = do ist <- get
486 newPats <- mapM nm ps
487 let newPats' = mergeAllPats ist (sUN "_") (base (head ps))
489 return (map fst newPats')
491 base (PApp fc f args) = PApp fc f (map (fmap (const (PRef fc [] (sUN "_")))) args)
494 nm ptm = do mptm <- elabNewPat ptm
496 (False, _) -> return ptm
497 (True, ptm') -> return ptm'
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