1{-# LANGUAGE PatternGuards #-}
3module Idris.CaseSplit(splitOnLine, replaceSplits,
4 getClause, getProofClause,
7 getUniq, nameRoot) where
8-- splitting a variable in a pattern clause
11import Idris.AbsSyntaxTree (Idris, IState, PTerm)
13import Idris.Delaborate
18import Idris.Elab.Value
22import Idris.Core.Typecheck
23import Idris.Core.Evaluate
27import Data.List (isPrefixOf, isSuffixOf)
29import Control.Monad.State.Strict
31import Text.Parser.Combinators
32import Text.Parser.Char(anyChar)
33import Text.Trifecta(Result(..), parseString)
34import Text.Trifecta.Delta
40Given a pattern clause and a variable 'n', elaborate the clause and find the
43Make new pattern clauses by replacing 'n' with all the possibly constructors
44applied to '_', and replacing all other variables with '_' in order to
45resolve other dependencies.
47Finally, merge the generated patterns with the original, by matching.
48Always take the "more specific" argument when there is a discrepancy, i.e.
49names over '_', patterns over names, etc.
52-- Given a variable to split, and a term application, return a list of
53-- variable updates, paired with a flag to say whether the given update
54-- typechecks (False = impossible)
55-- if the flag is 'False' the splits should be output with the 'impossible'
56-- flag, otherwise they should be output as normal
57split :: Name -> PTerm -> Idris (Bool, [[(Name, PTerm)]])
60 -- Make sure all the names in the term are accessible
61 mapM_ (\n -> setAccessibility n Public) (allNamesIn t')
62 -- ETyDecl rather then ELHS because there'll be explicit type
64 (tm, ty, pats) <- elabValBind recinfo ETyDecl True (addImplPat ist t')
65 -- ASSUMPTION: tm is in normal form after elabValBind, so we don't
66 -- need to do anything special to find out what family each argument
68 logElab 4 ("Elaborated:\n" ++ show tm ++ " : " ++ show ty ++ "\n" ++ show pats)
69-- iputStrLn (show (delab ist tm) ++ " : " ++ show (delab ist ty))
70-- iputStrLn (show pats)
71 let t = mergeUserImpl (addImplPat ist t') (delab ist tm)
72 let ctxt = tt_ctxt ist
74 Nothing -> ifail $ show n ++ " is not a pattern variable"
76 do let splits = findPats ist ty
77 logElab 1 ("New patterns " ++ showSep ", "
78 (map showTmImpls splits))
79 let newPats_in = zipWith (replaceVar ctxt n) splits (repeat t)
80 logElab 4 ("Working from " ++ showTmImpls t)
81 logElab 4 ("Trying " ++ showSep "\n"
82 (map (showTmImpls) newPats_in))
83 newPats_in <- mapM elabNewPat newPats_in
84 case anyValid [] [] newPats_in of
86 let fails' = mergeAllPats ist n t fails
87 return (False, (map snd fails'))
89 logElab 3 ("Original:\n" ++ show t)
90 logElab 3 ("Split:\n" ++
91 (showSep "\n" (map show newPats)))
93 let newPats' = mergeAllPats ist n t newPats
94 logElab 1 ("Name updates " ++ showSep "\n"
95 (map (\ (p, u) -> show u ++ " " ++ show p) newPats'))
96 return (True, (map snd newPats'))
98 anyValid ok bad [] = if null ok then Left (reverse bad)
99 else Right (reverse ok)
100 anyValid ok bad ((tc, p) : ps)
101 | tc = anyValid (p : ok) bad ps
102 | otherwise = anyValid ok (p : bad) ps
104data MergeState = MS { namemap :: [(Name, Name)],
105 invented :: [(Name, Name)],
107 updates :: [(Name, PTerm)] }
109addUpdate :: Name -> Idris.AbsSyntaxTree.PTerm -> State MergeState ()
110addUpdate n tm = do ms <- get
111 put (ms { updates = ((n, stripNS tm) : updates ms) } )
113inventName :: Idris.AbsSyntaxTree.IState -> Maybe Name -> Name -> State MergeState Name
116 let supp = case ty of
118 Just t -> getNameHints ist t
119 let nsupp = case n of
120 MN i n | not (tnull n) && thead n == '_'
121 -> mkSupply (supp ++ varlist)
122 MN i n -> mkSupply (UN n : supp ++ varlist)
123 UN n | thead n == '_'
124 -> mkSupply (supp ++ varlist)
125 x -> mkSupply (x : supp)
126 let badnames = map snd (namemap ms) ++ map snd (invented ms) ++
128 case lookup n (invented ms) of
131 do let n' = uniqueNameFrom nsupp badnames
132 put (ms { invented = (n, n') : invented ms })
135mkSupply :: [Name] -> [Name]
136mkSupply ns = mkSupply' ns (map nextName ns)
137 where mkSupply' xs ns' = xs ++ mkSupply ns'
140varlist = map (sUN . (:[])) "xyzwstuv" -- EB's personal preference :)
142stripNS :: Idris.AbsSyntaxTree.PTerm -> Idris.AbsSyntaxTree.PTerm
143stripNS tm = mapPT dens tm where
144 dens (PRef fc hls n) = PRef fc hls (nsroot n)
147mergeAllPats :: IState -> Name -> PTerm -> [PTerm] -> [(PTerm, [(Name, PTerm)])]
148mergeAllPats ist cv t [] = []
149mergeAllPats ist cv t (p : ps)
150 = let (p', MS _ _ _ u) = runState (mergePat ist t p Nothing)
151 (MS [] [] (filter (/=cv) (patvars t)) [])
152 ps' = mergeAllPats ist cv t ps in
154 where patvars (PRef _ _ n) = [n]
155 patvars (PApp _ _ as) = concatMap (patvars . getTm) as
156 patvars (PPatvar _ n) = [n]
159mergePat :: IState -> PTerm -> PTerm -> Maybe Name -> State MergeState PTerm
160-- If any names are unified, make sure they stay unified. Always prefer
161-- user provided name (first pattern)
162mergePat ist (PPatvar fc n) new t
163 = mergePat ist (PRef fc [] n) new t
164mergePat ist old (PPatvar fc n) t
165 = mergePat ist old (PRef fc [] n) t
166mergePat ist orig@(PRef fc _ n) new@(PRef _ _ n') t
167 | isDConName n' (tt_ctxt ist) = do addUpdate n new
171 case lookup n' (namemap ms) of
172 Just x -> do addUpdate n (PRef fc [] x)
173 return (PRef fc [] x)
174 Nothing -> do put (ms { namemap = ((n', n) : namemap ms) })
175 return (PRef fc [] n)
176mergePat ist (PApp _ _ args) (PApp fc f args') t
177 = do newArgs <- zipWithM mergeArg args (zip args' (argTys ist f))
178 return (PApp fc f newArgs)
179 where mergeArg x (y, t)
180 = do tm' <- mergePat ist (getTm x) (getTm y) t
183 return (y { machine_inf = machine_inf x,
185 _ -> return (y { getTm = tm' })
186mergePat ist (PRef fc _ n) tm ty = do tm <- tidy ist tm ty
189mergePat ist x y t = return y
191mergeUserImpl :: PTerm -> PTerm -> PTerm
194argTys :: IState -> PTerm -> [Maybe Name]
195argTys ist (PRef fc hls n)
196 = case lookupTy n (tt_ctxt ist) of
197 [ty] -> map (tyName . snd) (getArgTys ty) ++ repeat Nothing
199 where tyName (Bind _ (Pi _ _ _) _) = Just (sUN "->")
200 tyName t | (P _ n _, _) <- unApply t = Just n
201 | otherwise = Nothing
202argTys _ _ = repeat Nothing
204tidy :: IState -> PTerm -> Maybe Name -> State MergeState PTerm
205tidy ist orig@(PRef fc hls n) ty
207 case lookup n (namemap ms) of
208 Just x -> return (PRef fc [] x)
210 (UN _) -> return orig
211 _ -> do n' <- inventName ist ty n
212 return (PRef fc [] n')
213tidy ist (PApp fc f args) ty
214 = do args' <- zipWithM tidyArg args (argTys ist f)
215 return (PApp fc f args')
216 where tidyArg x ty' = do tm' <- tidy ist (getTm x) ty'
217 return (x { getTm = tm' })
218tidy ist tm ty = return tm
222-- where tidyVar (PRef _ _) = Placeholder
225elabNewPat :: PTerm -> Idris (Bool, PTerm)
226elabNewPat t = idrisCatch (do (tm, ty) <- elabVal recinfo ELHS t
228 return (True, delab i tm))
229 (\e -> do i <- getIState
230 logElab 5 $ "Not a valid split:\n" ++ showTmImpls t ++ "\n"
234findPats :: IState -> Type -> [PTerm]
235findPats ist t | (P _ n _, _) <- unApply t
236 = case lookupCtxt n (idris_datatypes ist) of
237 [ti] -> map genPat (con_names ti)
239 where genPat n = case lookupCtxt n (idris_implicits ist) of
240 [args] -> PApp emptyFC (PRef emptyFC [] n)
241 (map toPlaceholder args)
242 _ -> error $ "Can't happen (genPat) " ++ show n
243 toPlaceholder tm = tm { getTm = Placeholder }
244findPats ist t = [Placeholder]
246replaceVar :: Context -> Name -> PTerm -> PTerm -> PTerm
247replaceVar ctxt n t (PApp fc f pats) = PApp fc f (map substArg pats)
248 where subst :: PTerm -> PTerm
249 subst orig@(PPatvar _ v) | v == n = t
250 | otherwise = Placeholder
251 subst orig@(PRef _ _ v) | v == n = t
252 | isDConName v ctxt = orig
253 subst (PRef _ _ _) = Placeholder
254 subst (PApp fc (PRef _ _ t) pats)
255 | isTConName t ctxt = Placeholder -- infer types
256 subst (PApp fc f pats) = PApp fc f (map substArg pats)
259 substArg arg = arg { getTm = subst (getTm arg) }
261replaceVar ctxt n t pat = pat
263splitOnLine :: Int -- ^ line number
264 -> Name -- ^ variable
265 -> FilePath -- ^ name of file
266 -> Idris (Bool, [[(Name, PTerm)]])
267splitOnLine l n fn = do
268 cl <- getInternalApp fn l
269 logElab 3 ("Working with " ++ showTmImpls cl)
273replaceSplits :: String -> [[(Name, PTerm)]] -> Bool -> Idris [String]
274replaceSplits l ups impossible
275 = updateRHSs 1 (map (rep (expandBraces l)) ups)
277 rep str [] = str ++ "\n"
278 rep str ((n, tm) : ups) = rep (updatePat False (show n) (nshow tm) str) ups
280 updateRHSs i [] = return []
281 updateRHSs i (x : xs)
282 | impossible = do xs' <- updateRHSs i xs
283 return (setImpossible False x : xs')
284 | otherwise = do (x', i') <- updateRHS (null xs) i x
285 xs' <- updateRHSs i' xs
288 updateRHS last i ('?':'=':xs) = do (xs', i') <- updateRHS last i xs
289 return ("?=" ++ xs', i')
290 updateRHS last i ('?':xs)
291 = do let (nm, rest_in) = span (not . (\x -> isSpace x || x == ')'
293 let rest = if last then rest_in else
294 case span (not . (=='\n')) rest_in of
295 (_, restnl) -> restnl
296 (nm', i') <- getUniq nm i
297 return ('?':nm' ++ rest, i')
298 updateRHS last i (x : xs) = do (xs', i') <- updateRHS last i xs
300 updateRHS last i [] = return ("", i)
302 setImpossible brace ('}':xs) = '}' : setImpossible False xs
303 setImpossible brace ('{':xs) = '{' : setImpossible True xs
304 setImpossible False ('=':xs) = "impossible\n"
305 setImpossible brace (x : xs) = x : setImpossible brace xs
306 setImpossible brace [] = ""
308 -- TMP HACK: If there are Nats, we don't want to show as numerals since
309 -- this isn't supported in a pattern, so special case here
310 nshow (PRef _ _ (UN z)) | z == txt "Z" = "Z"
311 nshow (PApp _ (PRef _ _ (UN s)) [x]) | s == txt "S" =
312 "(S " ++ addBrackets (nshow (getTm x)) ++ ")"
315 -- if there's any {n} replace with {n=n}
316 -- but don't replace it in comments
317 expandBraces ('{' : '-' : xs) = '{' : '-' : xs
318 expandBraces ('{' : xs)
319 = let (brace, (_:rest)) = span (/= '}') xs in
320 if (not ('=' `elem` brace))
321 then ('{' : brace ++ " = " ++ brace ++ "}") ++
323 else ('{' : brace ++ "}") ++ expandBraces rest
324 expandBraces (x : xs) = x : expandBraces xs
327 updatePat start n tm [] = []
328 updatePat start n tm ('{':rest) =
329 let (space, rest') = span isSpace rest in
330 '{' : space ++ updatePat False n tm rest'
331 updatePat start n tm done@('?':rest) = done
332 updatePat True n tm xs@(c:rest) | length xs > length n
333 = let (before, after@(next:_)) = splitAt (length n) xs in
334 if (before == n && not (isAlphaNum next))
335 then addBrackets tm ++ updatePat False n tm after
336 else c : updatePat (not (isAlphaNum c)) n tm rest
337 updatePat start n tm (c:rest) = c : updatePat (not ((isAlphaNum c) || c == '_')) n tm rest
339 addBrackets tm | ' ' `elem` tm
340 , not (isPrefixOf "(" tm)
341 , not (isSuffixOf ")" tm) = "(" ++ tm ++ ")"
345getUniq :: (Show t, Num t) => [Char] -> t -> Idris ([Char], t)
347 = do ist <- getIState
348 let n = nameRoot [] nm ++ "_" ++ show i
349 case lookupTy (sUN n) (tt_ctxt ist) of
350 [] -> return (n, i+1)
351 _ -> getUniq nm (i+1)
353nameRoot acc nm | all isDigit nm = showSep "_" acc
355 case span (/='_') nm of
356 (before, ('_' : after)) -> nameRoot (acc ++ [before]) after
357 _ -> showSep "_" (acc ++ [nm])
359getClause :: Int -- ^ line number that the type is declared on
360 -> Name -- ^ Function name
361 -> Name -- ^ User given name
362 -> FilePath -- ^ Source file name
366 case lookupCtxt un (idris_classes i) of
367 [c] -> return (mkClassBodies i (class_methods c))
368 _ -> do ty_in <- getInternalApp fp l
369 let ty = case ty_in of
373 let ap = mkApp ist ty []
374 return (show un ++ " " ++ ap ++ "= ?"
375 ++ show un ++ "_rhs")
376 where mkApp :: IState -> PTerm -> [Name] -> String
377 mkApp i (PPi (Exp _ _ False) (MN _ _) _ ty sc) used
378 = let n = getNameFrom i used ty in
379 show n ++ " " ++ mkApp i sc (n : used)
380 mkApp i (PPi (Exp _ _ False) (UN n) _ ty sc) used
382 = let n = getNameFrom i used ty in
383 show n ++ " " ++ mkApp i sc (n : used)
384 mkApp i (PPi (Exp _ _ False) n _ _ sc) used
385 = show n ++ " " ++ mkApp i sc (n : used)
386 mkApp i (PPi _ _ _ _ sc) used = mkApp i sc used
389 getNameFrom i used (PPi _ _ _ _ _)
390 = uniqueNameFrom (mkSupply [sUN "f", sUN "g"]) used
391 getNameFrom i used (PApp fc f as) = getNameFrom i used f
392 getNameFrom i used (PRef fc _ f)
393 = case getNameHints i f of
394 [] -> uniqueNameFrom (mkSupply [sUN "x", sUN "y",
396 ns -> uniqueNameFrom (mkSupply ns) used
397 getNameFrom i used _ = uniqueNameFrom (mkSupply [sUN "x", sUN "y",
400 -- write method declarations, indent with 4 spaces
401 mkClassBodies :: IState -> [(Name, (FnOpts, PTerm))] -> String
404 (zipWith (\(n, (_, ty)) m -> " " ++
405 def (show (nsroot n)) ++ " "
408 ++ show un ++ "_rhs_" ++ show m) ns [1..])
410 def n@(x:xs) | not (isAlphaNum x) = "(" ++ n ++ ")"
413getProofClause :: Int -- ^ line number that the type is declared
414 -> Name -- ^ Function name
415 -> FilePath -- ^ Source file name
417getProofClause l fn fp
418 = do ty_in <- getInternalApp fp l
419 let ty = case ty_in of
422 return (mkApp ty ++ " = ?" ++ show fn ++ "_rhs")
423 where mkApp (PPi _ _ _ _ sc) = mkApp sc
424 mkApp rt = "(" ++ show rt ++ ") <== " ++ show fn
426-- Purely syntactic - turn a pattern match clause into a with and a new
429mkWith :: String -> Name -> String
430mkWith str n = let str' = replaceRHS str "with (_)"
431 in str' ++ "\n" ++ newpat str
433 where replaceRHS [] str = str
434 replaceRHS ('?':'=': rest) str = str
435 replaceRHS ('=': rest) str
436 | not ('=' `elem` rest) = str
437 replaceRHS (x : rest) str = x : replaceRHS rest str
439 newpat ('>':patstr) = '>':newpat patstr
442 replaceRHS patstr "| with_pat = ?" ++ show n ++ "_rhs"
444-- Replace _ with names in missing clauses
446nameMissing :: [PTerm] -> Idris [PTerm]
447nameMissing ps = do ist <- get
448 newPats <- mapM nm ps
449 let newPats' = mergeAllPats ist (sUN "_") (base (head ps))
451 return (map fst newPats')
453 base (PApp fc f args) = PApp fc f (map (fmap (const (PRef fc [] (sUN "_")))) args)
456 nm ptm = do mptm <- elabNewPat ptm
458 (False, _) -> return ptm
459 (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