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 " ++ show 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" ++ pshow i e
233findPats :: IState -> Type -> [PTerm]
234findPats ist t | (P _ n _, _) <- unApply t
235 = case lookupCtxt n (idris_datatypes ist) of
236 [ti] -> map genPat (con_names ti)
238 where genPat n = case lookupCtxt n (idris_implicits ist) of
239 [args] -> PApp emptyFC (PRef emptyFC [] n)
240 (map toPlaceholder args)
241 _ -> error $ "Can't happen (genPat) " ++ show n
242 toPlaceholder tm = tm { getTm = Placeholder }
243findPats ist t = [Placeholder]
245replaceVar :: Context -> Name -> PTerm -> PTerm -> PTerm
246replaceVar ctxt n t (PApp fc f pats) = PApp fc f (map substArg pats)
247 where subst :: PTerm -> PTerm
248 subst orig@(PPatvar _ v) | v == n = t
249 | otherwise = Placeholder
250 subst orig@(PRef _ _ v) | v == n = t
251 | isDConName v ctxt = orig
252 subst (PRef _ _ _) = Placeholder
253 subst (PApp fc (PRef _ _ t) pats)
254 | isTConName t ctxt = Placeholder -- infer types
255 subst (PApp fc f pats) = PApp fc f (map substArg pats)
258 substArg arg = arg { getTm = subst (getTm arg) }
260replaceVar ctxt n t pat = pat
262splitOnLine :: Int -- ^ line number
263 -> Name -- ^ variable
264 -> FilePath -- ^ name of file
265 -> Idris (Bool, [[(Name, PTerm)]])
266splitOnLine l n fn = do
267 cl <- getInternalApp fn l
268 logElab 3 ("Working with " ++ showTmImpls cl)
272replaceSplits :: String -> [[(Name, PTerm)]] -> Bool -> Idris [String]
273replaceSplits l ups impossible
274 = updateRHSs 1 (map (rep (expandBraces l)) ups)
276 rep str [] = str ++ "\n"
277 rep str ((n, tm) : ups) = rep (updatePat False (show n) (nshow tm) str) ups
279 updateRHSs i [] = return []
280 updateRHSs i (x : xs)
281 | impossible = do xs' <- updateRHSs i xs
282 return (setImpossible False x : xs')
283 | otherwise = do (x', i') <- updateRHS (null xs) i x
284 xs' <- updateRHSs i' xs
287 updateRHS last i ('?':'=':xs) = do (xs', i') <- updateRHS last i xs
288 return ("?=" ++ xs', i')
289 updateRHS last i ('?':xs)
290 = do let (nm, rest_in) = span (not . (\x -> isSpace x || x == ')'
292 let rest = if last then rest_in else
293 case span (not . (=='\n')) rest_in of
294 (_, restnl) -> restnl
295 (nm', i') <- getUniq nm i
296 return ('?':nm' ++ rest, i')
297 updateRHS last i (x : xs) = do (xs', i') <- updateRHS last i xs
299 updateRHS last i [] = return ("", i)
301 setImpossible brace ('}':xs) = '}' : setImpossible False xs
302 setImpossible brace ('{':xs) = '{' : setImpossible True xs
303 setImpossible False ('=':xs) = "impossible\n"
304 setImpossible brace (x : xs) = x : setImpossible brace xs
305 setImpossible brace [] = ""
307 -- TMP HACK: If there are Nats, we don't want to show as numerals since
308 -- this isn't supported in a pattern, so special case here
309 nshow (PRef _ _ (UN z)) | z == txt "Z" = "Z"
310 nshow (PApp _ (PRef _ _ (UN s)) [x]) | s == txt "S" =
311 "(S " ++ addBrackets (nshow (getTm x)) ++ ")"
314 -- if there's any {n} replace with {n=n}
315 -- but don't replace it in comments
316 expandBraces ('{' : '-' : xs) = '{' : '-' : xs
317 expandBraces ('{' : xs)
318 = let (brace, (_:rest)) = span (/= '}') xs in
319 if (not ('=' `elem` brace))
320 then ('{' : brace ++ " = " ++ brace ++ "}") ++
322 else ('{' : brace ++ "}") ++ expandBraces rest
323 expandBraces (x : xs) = x : expandBraces xs
326 updatePat start n tm [] = []
327 updatePat start n tm ('{':rest) =
328 let (space, rest') = span isSpace rest in
329 '{' : space ++ updatePat False n tm rest'
330 updatePat start n tm done@('?':rest) = done
331 updatePat True n tm xs@(c:rest) | length xs > length n
332 = let (before, after@(next:_)) = splitAt (length n) xs in
333 if (before == n && not (isAlphaNum next))
334 then addBrackets tm ++ updatePat False n tm after
335 else c : updatePat (not (isAlphaNum c)) n tm rest
336 updatePat start n tm (c:rest) = c : updatePat (not ((isAlphaNum c) || c == '_')) n tm rest
338 addBrackets tm | ' ' `elem` tm
339 , not (isPrefixOf "(" tm)
340 , not (isSuffixOf ")" tm) = "(" ++ tm ++ ")"
344getUniq :: (Show t, Num t) => [Char] -> t -> Idris ([Char], t)
346 = do ist <- getIState
347 let n = nameRoot [] nm ++ "_" ++ show i
348 case lookupTy (sUN n) (tt_ctxt ist) of
349 [] -> return (n, i+1)
350 _ -> getUniq nm (i+1)
352nameRoot acc nm | all isDigit nm = showSep "_" acc
354 case span (/='_') nm of
355 (before, ('_' : after)) -> nameRoot (acc ++ [before]) after
356 _ -> showSep "_" (acc ++ [nm])
358getClause :: Int -- ^ line number that the type is declared on
359 -> Name -- ^ Function name
360 -> Name -- ^ User given name
361 -> FilePath -- ^ Source file name
365 case lookupCtxt un (idris_classes i) of
366 [c] -> return (mkClassBodies i (class_methods c))
367 _ -> do ty_in <- getInternalApp fp l
368 let ty = case ty_in of
372 let ap = mkApp ist ty []
373 return (show un ++ " " ++ ap ++ "= ?"
374 ++ show un ++ "_rhs")
375 where mkApp :: IState -> PTerm -> [Name] -> String
376 mkApp i (PPi (Exp _ _ False) (MN _ _) _ ty sc) used
377 = let n = getNameFrom i used ty in
378 show n ++ " " ++ mkApp i sc (n : used)
379 mkApp i (PPi (Exp _ _ False) (UN n) _ ty sc) used
381 = let n = getNameFrom i used ty in
382 show n ++ " " ++ mkApp i sc (n : used)
383 mkApp i (PPi (Exp _ _ False) n _ _ sc) used
384 = show n ++ " " ++ mkApp i sc (n : used)
385 mkApp i (PPi _ _ _ _ sc) used = mkApp i sc used
388 getNameFrom i used (PPi _ _ _ _ _)
389 = uniqueNameFrom (mkSupply [sUN "f", sUN "g"]) used
390 getNameFrom i used (PApp fc f as) = getNameFrom i used f
391 getNameFrom i used (PRef fc _ f)
392 = case getNameHints i f of
393 [] -> uniqueNameFrom (mkSupply [sUN "x", sUN "y",
395 ns -> uniqueNameFrom (mkSupply ns) used
396 getNameFrom i used _ = uniqueNameFrom (mkSupply [sUN "x", sUN "y",
399 -- write method declarations, indent with 4 spaces
400 mkClassBodies :: IState -> [(Name, (FnOpts, PTerm))] -> String
403 (zipWith (\(n, (_, ty)) m -> " " ++
404 def (show (nsroot n)) ++ " "
407 ++ show un ++ "_rhs_" ++ show m) ns [1..])
409 def n@(x:xs) | not (isAlphaNum x) = "(" ++ n ++ ")"
412getProofClause :: Int -- ^ line number that the type is declared
413 -> Name -- ^ Function name
414 -> FilePath -- ^ Source file name
416getProofClause l fn fp
417 = do ty_in <- getInternalApp fp l
418 let ty = case ty_in of
421 return (mkApp ty ++ " = ?" ++ show fn ++ "_rhs")
422 where mkApp (PPi _ _ _ _ sc) = mkApp sc
423 mkApp rt = "(" ++ show rt ++ ") <== " ++ show fn
425-- Purely syntactic - turn a pattern match clause into a with and a new
428mkWith :: String -> Name -> String
429mkWith str n = let str' = replaceRHS str "with (_)"
430 in str' ++ "\n" ++ newpat str
432 where replaceRHS [] str = str
433 replaceRHS ('?':'=': rest) str = str
434 replaceRHS ('=': rest) str
435 | not ('=' `elem` rest) = str
436 replaceRHS (x : rest) str = x : replaceRHS rest str
438 newpat ('>':patstr) = '>':newpat patstr
441 replaceRHS patstr "| with_pat = ?" ++ show n ++ "_rhs"
443-- Replace _ with names in missing clauses
445nameMissing :: [PTerm] -> Idris [PTerm]
446nameMissing ps = do ist <- get
447 newPats <- mapM nm ps
448 let newPats' = mergeAllPats ist (sUN "_") (base (head ps))
450 return (map fst newPats')
452 base (PApp fc f args) = PApp fc f (map (fmap (const (PRef fc [] (sUN "_")))) args)
455 nm ptm = do mptm <- elabNewPat ptm
457 (False, _) -> return ptm
458 (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