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 (IState, Idris, PTerm)
32import Idris.Core.Evaluate
34import Idris.Core.Typecheck
35import Idris.Delaborate
37import Idris.Elab.Value
42import Idris.Parser.Helpers
45import Control.Monad.State.Strict
47import Data.List (isPrefixOf, isSuffixOf)
50import Text.Parser.Char (anyChar)
51import Text.Parser.Combinators
52import Text.Trifecta (Result(..), parseString)
53import Text.Trifecta.Delta
55-- | Given a variable to split, and a term application, return a list
56-- of variable updates, paired with a flag to say whether the given
57-- update typechecks (False = impossible) if the flag is 'False' the
58-- splits should be output with the 'impossible' flag, otherwise they
59-- should be output as normal
60split :: Name -> PTerm -> Idris (Bool, [[(Name, PTerm)]])
63 -- Make sure all the names in the term are accessible
64 mapM_ (\n -> setAccessibility n Public) (allNamesIn t')
65 -- ETyDecl rather then ELHS because there'll be explicit type
67 (tm, ty, pats) <- elabValBind (recinfo (fileFC "casesplit")) ETyDecl True (addImplPat ist t')
68 -- ASSUMPTION: tm is in normal form after elabValBind, so we don't
69 -- need to do anything special to find out what family each argument
71 logElab 4 ("Elaborated:\n" ++ show tm ++ " : " ++ show ty ++ "\n" ++ show pats)
72-- iputStrLn (show (delab ist tm) ++ " : " ++ show (delab ist ty))
73-- iputStrLn (show pats)
74 let t = mergeUserImpl (addImplPat ist t') (delabDirect ist tm)
75 let ctxt = tt_ctxt ist
77 Nothing -> ifail $ show n ++ " is not a pattern variable"
79 do let splits = findPats ist ty
80 logElab 1 ("New patterns " ++ showSep ", "
81 (map showTmImpls splits))
82 let newPats_in = zipWith (replaceVar ctxt n) splits (repeat t)
83 logElab 4 ("Working from " ++ showTmImpls t)
84 logElab 4 ("Trying " ++ showSep "\n"
85 (map (showTmImpls) newPats_in))
86 newPats_in <- mapM elabNewPat newPats_in
87 case anyValid [] [] newPats_in of
89 let fails' = mergeAllPats ist n t fails
90 return (False, (map snd fails'))
92 logElab 3 ("Original:\n" ++ show t)
93 logElab 3 ("Split:\n" ++
94 (showSep "\n" (map show newPats)))
96 let newPats' = mergeAllPats ist n t newPats
97 logElab 1 ("Name updates " ++ showSep "\n"
98 (map (\ (p, u) -> show u ++ " " ++ show p) newPats'))
99 return (True, (map snd newPats'))
101 anyValid ok bad [] = if null ok then Left (reverse bad)
102 else Right (reverse ok)
103 anyValid ok bad ((tc, p) : ps)
104 | tc = anyValid (p : ok) bad ps
105 | otherwise = anyValid ok (p : bad) ps
107data MergeState = MS { namemap :: [(Name, Name)],
108 invented :: [(Name, Name)],
110 updates :: [(Name, PTerm)] }
112addUpdate :: Name -> Idris.AbsSyntaxTree.PTerm -> State MergeState ()
113addUpdate n tm = do ms <- get
114 put (ms { updates = ((n, stripNS tm) : updates ms) } )
116inventName :: Idris.AbsSyntaxTree.IState -> Maybe Name -> Name -> State MergeState Name
119 let supp = case ty of
121 Just t -> getNameHints ist t
122 let nsupp = case n of
123 MN i n | not (tnull n) && thead n == '_'
124 -> mkSupply (supp ++ varlist)
125 MN i n -> mkSupply (UN n : supp ++ varlist)
126 UN n | thead n == '_'
127 -> mkSupply (supp ++ varlist)
128 x -> mkSupply (x : supp)
129 let badnames = map snd (namemap ms) ++ map snd (invented ms) ++
131 case lookup n (invented ms) of
134 do let n' = uniqueNameFrom nsupp badnames
135 put (ms { invented = (n, n') : invented ms })
138mkSupply :: [Name] -> [Name]
139mkSupply ns = mkSupply' ns (map nextName ns)
140 where mkSupply' xs ns' = xs ++ mkSupply ns'
143varlist = map (sUN . (:[])) "xyzwstuv" -- EB's personal preference :)
145stripNS :: Idris.AbsSyntaxTree.PTerm -> Idris.AbsSyntaxTree.PTerm
146stripNS tm = mapPT dens tm where
147 dens (PRef fc hls n) = PRef fc hls (nsroot n)
150mergeAllPats :: IState -> Name -> PTerm -> [PTerm] -> [(PTerm, [(Name, PTerm)])]
151mergeAllPats ist cv t [] = []
152mergeAllPats ist cv t (p : ps)
153 = let (p', MS _ _ _ u) = runState (mergePat ist t p Nothing)
154 (MS [] [] (filter (/=cv) (patvars t)) [])
155 ps' = mergeAllPats ist cv t ps in
157 where patvars (PRef _ _ n) = [n]
158 patvars (PApp _ _ as) = concatMap (patvars . getTm) as
159 patvars (PPatvar _ n) = [n]
162mergePat :: IState -> PTerm -> PTerm -> Maybe Name -> State MergeState PTerm
163mergePat ist orig new t =
164 do -- collect user names for name map, by matching user pattern against
165 -- the generated pattern
166 case matchClause ist orig new of
168 Right ns -> mapM_ addNameMap ns
169 mergePat' ist orig new t
171 addNameMap (n, PRef fc _ n') = do ms <- get
172 put (ms { namemap = ((n', n) : namemap ms) })
173 addNameMap _ = return ()
175-- | If any names are unified, make sure they stay unified. Always
176-- prefer user provided name (first pattern)
177mergePat' ist (PPatvar fc n) new t
178 = mergePat' ist (PRef fc [] n) new t
179mergePat' ist old (PPatvar fc n) t
180 = mergePat' ist old (PRef fc [] n) t
181mergePat' ist orig@(PRef fc _ n) new@(PRef _ _ n') t
182 | isDConName n' (tt_ctxt ist) = do addUpdate n new
186 case lookup n' (namemap ms) of
187 Just x -> do addUpdate n (PRef fc [] x)
188 return (PRef fc [] x)
189 Nothing -> do put (ms { namemap = ((n', n) : namemap ms) })
190 return (PRef fc [] n)
191mergePat' ist (PApp _ _ args) (PApp fc f args') t
192 = do newArgs <- zipWithM mergeArg args (zip args' (argTys ist f))
193 return (PApp fc f newArgs)
194 where mergeArg x (y, t)
195 = do tm' <- mergePat' ist (getTm x) (getTm y) t
198 return (y { machine_inf = machine_inf x,
200 _ -> return (y { getTm = tm' })
201mergePat' ist (PRef fc _ n) tm ty = do tm <- tidy ist tm ty
204mergePat' ist x y t = return y
206mergeUserImpl :: PTerm -> PTerm -> PTerm
209argTys :: IState -> PTerm -> [Maybe Name]
210argTys ist (PRef fc hls n)
211 = case lookupTy n (tt_ctxt ist) of
212 [ty] -> let ty' = normalise (tt_ctxt ist) [] ty in
213 map (tyName . snd) (getArgTys ty') ++ repeat Nothing
215 where tyName (Bind _ (Pi _ _ _) _) = Just (sUN "->")
216 tyName t | (P _ d _, [_, ty]) <- unApply t,
217 d == sUN "Delayed" = tyName ty
218 | (P _ n _, _) <- unApply t = Just n
219 | otherwise = Nothing
220argTys _ _ = repeat Nothing
222tidy :: IState -> PTerm -> Maybe Name -> State MergeState PTerm
223tidy ist orig@(PRef fc hls n) ty
225 case lookup n (namemap ms) of
226 Just x -> return (PRef fc [] x)
228 (UN _) -> return orig
229 _ -> do n' <- inventName ist ty n
230 return (PRef fc [] n')
231tidy ist (PApp fc f args) ty
232 = do args' <- zipWithM tidyArg args (argTys ist f)
233 return (PApp fc f args')
234 where tidyArg x ty' = do tm' <- tidy ist (getTm x) ty'
235 return (x { getTm = tm' })
236tidy ist tm ty = return tm
240-- where tidyVar (PRef _ _) = Placeholder
243elabNewPat :: PTerm -> Idris (Bool, PTerm)
244elabNewPat t = idrisCatch (do (tm, ty) <- elabVal (recinfo (fileFC "casesplit")) ELHS t
246 return (True, delabDirect i tm))
247 (\e -> do i <- getIState
248 logElab 5 $ "Not a valid split:\n" ++ showTmImpls t ++ "\n"
252findPats :: IState -> Type -> [PTerm]
253findPats ist t | (P _ n _, _) <- unApply t
254 = case lookupCtxt n (idris_datatypes ist) of
255 [ti] -> map genPat (con_names ti)
257 where genPat n = case lookupCtxt n (idris_implicits ist) of
258 [args] -> PApp emptyFC (PRef emptyFC [] n)
259 (map toPlaceholder args)
260 _ -> error $ "Can't happen (genPat) " ++ show n
261 toPlaceholder tm = tm { getTm = Placeholder }
262findPats ist t = [Placeholder]
264replaceVar :: Context -> Name -> PTerm -> PTerm -> PTerm
265replaceVar ctxt n t (PApp fc f pats) = PApp fc f (map substArg pats)
266 where subst :: PTerm -> PTerm
267 subst orig@(PPatvar _ v) | v == n = t
268 | otherwise = Placeholder
269 subst orig@(PRef _ _ v) | v == n = t
270 | isDConName v ctxt = orig
271 subst (PRef _ _ _) = Placeholder
272 subst (PApp fc (PRef _ _ t) pats)
273 | isTConName t ctxt = Placeholder -- infer types
274 subst (PApp fc f pats) = PApp fc f (map substArg pats)
277 substArg arg = arg { getTm = subst (getTm arg) }
279replaceVar ctxt n t pat = pat
281splitOnLine :: Int -- ^ line number
282 -> Name -- ^ variable
283 -> FilePath -- ^ name of file
284 -> Idris (Bool, [[(Name, PTerm)]])
285splitOnLine l n fn = do
286 cl <- getInternalApp fn l
287 logElab 3 ("Working with " ++ showTmImpls cl)
291replaceSplits :: String -> [[(Name, PTerm)]] -> Bool -> Idris [String]
292replaceSplits l ups impossible
293 = do ist <- getIState
294 updateRHSs 1 (map (rep ist (expandBraces l)) ups)
296 rep ist str [] = str ++ "\n"
297 rep ist str ((n, tm) : ups)
298 = rep ist (updatePat False (show n) (nshow (resugar ist tm)) str) ups
300 updateRHSs i [] = return []
301 updateRHSs i (x : xs)
302 | impossible = do xs' <- updateRHSs i xs
303 return (setImpossible False x : xs')
304 | otherwise = do (x', i') <- updateRHS (null xs) i x
305 xs' <- updateRHSs i' xs
308 updateRHS last i ('?':'=':xs) = do (xs', i') <- updateRHS last i xs
309 return ("?=" ++ xs', i')
310 updateRHS last i ('?':xs)
311 = do let (nm, rest_in) = span (not . (\x -> isSpace x || x == ')'
313 let rest = if last then rest_in else
314 case span (not . (=='\n')) rest_in of
315 (_, restnl) -> restnl
316 (nm', i') <- getUniq nm i
317 return ('?':nm' ++ rest, i')
318 updateRHS last i (x : xs) = do (xs', i') <- updateRHS last i xs
320 updateRHS last i [] = return ("", i)
322 setImpossible brace ('}':xs) = '}' : setImpossible False xs
323 setImpossible brace ('{':xs) = '{' : setImpossible True xs
324 setImpossible False ('=':xs) = "impossible\n"
325 setImpossible brace (x : xs) = x : setImpossible brace xs
326 setImpossible brace [] = ""
328 -- TMP HACK: If there are Nats, we don't want to show as numerals since
329 -- this isn't supported in a pattern, so special case here
330 nshow (PRef _ _ (UN z)) | z == txt "Z" = "Z"
331 nshow (PApp _ (PRef _ _ (UN s)) [x]) | s == txt "S" =
332 "(S " ++ addBrackets (nshow (getTm x)) ++ ")"
335 -- if there's any {n} replace with {n=n}
336 -- but don't replace it in comments
337 expandBraces ('{' : '-' : xs) = '{' : '-' : xs
338 expandBraces ('{' : xs)
339 = let (brace, (_:rest)) = span (/= '}') xs in
340 if (not ('=' `elem` brace))
341 then ('{' : brace ++ " = " ++ brace ++ "}") ++
343 else ('{' : brace ++ "}") ++ expandBraces rest
344 expandBraces (x : xs) = x : expandBraces xs
347 updatePat start n tm [] = []
348 updatePat start n tm ('{':rest) =
349 let (space, rest') = span isSpace rest in
350 '{' : space ++ updatePat False n tm rest'
351 updatePat start n tm done@('?':rest) = done
352 updatePat True n tm xs@(c:rest) | length xs > length n
353 = let (before, after@(next:_)) = splitAt (length n) xs in
354 if (before == n && not (isAlphaNum next))
355 then addBrackets tm ++ updatePat False n tm after
356 else c : updatePat (not (isAlphaNum c)) n tm rest
357 updatePat start n tm (c:rest) = c : updatePat (not ((isAlphaNum c) || c == '_')) n tm rest
359 addBrackets tm | ' ' `elem` tm
360 , not (isPrefixOf "(" tm && isSuffixOf ")" tm)
365getUniq :: (Show t, Num t) => [Char] -> t -> Idris ([Char], t)
367 = do ist <- getIState
368 let n = nameRoot [] nm ++ "_" ++ show i
369 case lookupTy (sUN n) (tt_ctxt ist) of
370 [] -> return (n, i+1)
371 _ -> getUniq nm (i+1)
373nameRoot acc nm | all isDigit nm = showSep "_" acc
375 case span (/='_') nm of
376 (before, ('_' : after)) -> nameRoot (acc ++ [before]) after
377 _ -> showSep "_" (acc ++ [nm])
379-- Show a name for use in pattern definitions on the lhs
380showLHSName :: Name -> String
381showLHSName n = let fn = show n in
382 if any (flip elem opChars) fn
383 then "(" ++ fn ++ ")"
386-- Show a name for the purpose of generating a metavariable name on the rhs
387showRHSName :: Name -> String
388showRHSName n = let fn = show n in
389 if any (flip elem opChars) fn
393getClause :: Int -- ^ line number that the type is declared on
394 -> Name -- ^ Function name
395 -> Name -- ^ User given name
396 -> FilePath -- ^ Source file name
400 case lookupCtxt un (idris_interfaces i) of
401 [c] -> return (mkInterfaceBodies i (interface_methods c))
402 _ -> do ty_in <- getInternalApp fp l
403 let ty = case ty_in of
407 let ap = mkApp ist ty []
408 return (showLHSName un ++ " " ++ ap ++ "= ?"
409 ++ showRHSName un ++ "_rhs")
410 where mkApp :: IState -> PTerm -> [Name] -> String
411 mkApp i (PPi (Exp _ _ False) (MN _ _) _ ty sc) used
412 = let n = getNameFrom i used ty in
413 show n ++ " " ++ mkApp i sc (n : used)
414 mkApp i (PPi (Exp _ _ False) (UN n) _ 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) n _ _ sc) used
419 = show n ++ " " ++ mkApp i sc (n : used)
420 mkApp i (PPi _ _ _ _ sc) used = mkApp i sc used
423 getNameFrom i used (PPi _ _ _ _ _)
424 = uniqueNameFrom (mkSupply [sUN "f", sUN "g"]) used
425 getNameFrom i used (PApp fc f as) = getNameFrom i used f
426 getNameFrom i used (PRef fc _ f)
427 = case getNameHints i f of
428 [] -> uniqueNameFrom (mkSupply [sUN "x", sUN "y",
430 ns -> uniqueNameFrom (mkSupply ns) used
431 getNameFrom i used _ = uniqueNameFrom (mkSupply [sUN "x", sUN "y",
434 -- write method declarations, indent with 4 spaces
435 mkInterfaceBodies :: IState -> [(Name, (Bool, FnOpts, PTerm))] -> String
436 mkInterfaceBodies i ns
438 (zipWith (\(n, (_, _, ty)) m -> " " ++
439 def (show (nsroot n)) ++ " "
442 ++ showRHSName un ++ "_rhs_" ++ show m) ns [1..])
444 def n@(x:xs) | not (isAlphaNum x) = "(" ++ n ++ ")"
447getProofClause :: Int -- ^ line number that the type is declared
448 -> Name -- ^ Function name
449 -> FilePath -- ^ Source file name
451getProofClause l fn fp
452 = do ty_in <- getInternalApp fp l
453 let ty = case ty_in of
456 return (mkApp ty ++ " = ?" ++ showRHSName fn ++ "_rhs")
457 where mkApp (PPi _ _ _ _ sc) = mkApp sc
458 mkApp rt = "(" ++ show rt ++ ") <== " ++ show fn
460-- Purely syntactic - turn a pattern match clause into a with and a new
463mkWith :: String -> Name -> String
464mkWith str n = let str' = replaceRHS str "with (_)"
465 in str' ++ "\n" ++ newpat str
467 where replaceRHS [] str = str
468 replaceRHS ('?':'=': rest) str = str
469 replaceRHS ('=': rest) str
470 | not ('=' `elem` rest) = str
471 replaceRHS (x : rest) str = x : replaceRHS rest str
473 newpat ('>':patstr) = '>':newpat patstr
476 replaceRHS patstr "| with_pat = ?" ++ showRHSName n ++ "_rhs"
478-- Replace _ with names in missing clauses
480nameMissing :: [PTerm] -> Idris [PTerm]
481nameMissing ps = do ist <- get
482 newPats <- mapM nm ps
483 let newPats' = mergeAllPats ist (sUN "_") (base (head ps))
485 return (map fst newPats')
487 base (PApp fc f args) = PApp fc f (map (fmap (const (PRef fc [] (sUN "_")))) args)
490 nm ptm = do mptm <- elabNewPat ptm
492 (False, _) -> return ptm
493 (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