/src/Editing/EditsPreparation.hs

http://github.com/Eelis/geordi · Haskell · 473 lines · 362 code · 76 blank · 35 comment · 53 complexity · 62de35b998b2b073d28986819524a870 MD5 · raw file

  1. {-# LANGUAGE UnicodeSyntax, MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances, TypeSynonymInstances, FlexibleContexts, UndecidableInstances, OverlappingInstances, PatternGuards, ViewPatterns #-}
  2. module Editing.EditsPreparation (use_tests, findInStr, FindResult(..), FoundIn(..)) where
  3. import qualified Cxx.Basics
  4. import qualified Cxx.Show
  5. import qualified Cxx.Operations
  6. import qualified Editing.Diff
  7. import qualified Editing.Show
  8. import qualified Data.List as List
  9. import qualified Data.Char as Char
  10. import qualified Data.List.NonEmpty as NeList
  11. import Data.Foldable (toList)
  12. import Data.Traversable (forM, mapM, sequence)
  13. import Data.List.NonEmpty (NonEmpty((:|)), nonEmpty)
  14. import Control.Monad (liftM2, join)
  15. import Control.Monad.Except (throwError)
  16. import Data.SetOps
  17. import Util ((.), (), Convert(..), Op(..), ops_cost, erase_indexed, levenshtein, replaceAllInfix, approx_match, Cost, Invertible(..), Ordinal(..), test_cmp, multiplicative_numeral, E, or_fail, pairs, NeList, neElim, neHomogenize, safeNth)
  18. -- One property that might be suitable for formal verification is that finders only return anchor/ranges/edits contained in the range they received, and that no anchor/range/edit ever goes out of bounds.
  19. import Prelude hiding (last, (.), all, sequence, mapM)
  20. import Prelude.Unicode hiding (())
  21. import Editing.Basics (TextEdit(..), Range(..), Pos(..), Anchor(..), frontAnchor, selectRange, fullRange, replaceRange, Side(..), end, rangeFromTo, unanchor_range, wideRange, StickyRange, find_occs, offset, contained_in, makeMoveEdit, tightRange, stickyRange, merge_contiguous)
  22. import Editing.Commands
  23. import Request (RequestEdit(..))
  24. import Control.Monad.Reader (ReaderT(..), local, ask)
  25. {- The present module concerns the translation of commands into Edits. The translation mostly follows the grammatical structure of commands, and so we get translators for various kinds of clauses, most of which will actually yield (lists of) positions and/or ranges rather than Edits, with only the topmost translators yielding actual Edits.
  26. Since most edit clauses refer to parts of a subject snippet, the translation from commands to edits is always performed in the context of such a subject. In addition, a context consists of several more things: -}
  27. data ResolutionContext = ResolutionContext
  28. { context_suffix :: String
  29. , _given :: String
  30. , fixIt :: Maybe (TextEdit Char)
  31. , search_range :: Range Char -- Todo: Should this not be an StickyRange?
  32. , well_formed :: E (Cxx.Basics.GeordiRequest, Anchor Char E (Anchor Char))
  33. }
  34. -- We will explain each of these fields in more detail, but first introduce a type class for translators, which are just functions run in a reader monad providing the above context, returning types in a certain class.
  35. type Resolver = ReaderT ResolutionContext E
  36. class InGiven_to_InWf b Find a b | a b where find :: a Resolver b
  37. -- We will describe the InGiven_to_InWf class in a moment. Some fairly obvious Find instances are:
  38. instance (Find x a, Find y a) Find (Either x y) a where find = either find find
  39. instance Find a b Find (AndList a) (NeList b) where find = sequence . (find .) . andList
  40. -- The _given and search_range fields in ResolutionContext simply specify a string and subrange of that string for the finder to search in. The context_suffix field describes the context (e.g. "after third statement in body of f"). Its only purpose is to make for nicer error messages: when Find instances fail, context_suffix lets us produce error messages like "Could not find `beh` after third statement in body of f."
  41. fail_with_context :: String Resolver a
  42. fail_with_context s = (s ++) . context_suffix . ask >>= throwError
  43. -- Find instances for things like Relative typically invoke Find instances for constituent clauses on subranges of the range they received themselves. For this we define |narrow|, which simultaneously modifies the search_range and extends the context_suffix:
  44. narrow :: String Range Char Resolver a Resolver a
  45. narrow x y = local $ \(ResolutionContext z v f _ w) ResolutionContext (" " ++ x ++ z) v f y w
  46. {- To motivate the well_formed field in ResolutionContext and the InGiven_to_InWf class, we must first describe some general edit command properties we desire.
  47. Consider the perfectly reasonable composite command "erase first x and move second x to end" executed on the snippet "xyxy". Clearly, we want the result to be "yyx". This means we cannot just execute the two commands in sequence in isolation, because once the first 'x' has been erased, there no longer /is/ a second 'x' to move. We conclude that whenever possible, all commands in a composite command should be translated to edits in the context of the same original snippet. The edits from the different commands should then be merged intelligently (this is done by the Execute module).
  48. Now consider the composite command "add ) after ( and erase declaration of i" executed on the snippet "void f(; int i;". Following the principle above to the letter, we should look for a declaration of i in the original snippet. However, since the original snippet does not parse, this would result in an error. This is really unfortunate, because the whole point of the "add ) after (" edit was to make the snippet well-formed. Hence, what we really want is for non-semantic things to be resolved in the original snippet as usual, but for semantic things to be looked up in the original snippet with the fewest number of preceding edits needed to make it well formed applied to it, so that in the above example, the declaration of i is looked for in the snippet as it appears after the "add ) after (" command has been applied to it.
  49. Following this idea, a Find instance that needs to do a semantic look-up should not just try to parse _given, but should have access to this notion of "the original snippet with the fewest number of preceding edits needed to make it well formed applied to it" (let us call this "the well-formed snippet"), and that is exactly what the well_formed field is. The 'E' monad is there because there simply may not be a sequence of preceding edits that make the snippet well-formed. The Anchor transformer translates an Anchor in _given to an Anchor in well-formed snippet, encapsulating the actual edits that turn the former into the latter. In particular, the Anchor transformer may be applied to search_range to give the range to search in the well-formed snippet.
  50. The anchor/ranges/edits a Find instance might find in well_formed are obviously relative to the well-formed snippet, not to _given (unless they are the same--more about that later). To inform the caller of what a returned anchor/range/edit is relative to, these are wrapped in FindResults: -}
  51. data FoundIn = InGiven | InWf deriving Eq
  52. data FindResult a = Found FoundIn a
  53. instance Functor FindResult where fmap f (Found x y) = Found x (f y)
  54. -- Before we explain the InGiven_to_InWf constraint and some of the finer _given vs well_formed points, let us look at some actual Find instances, starting with the one for verbatim strings.
  55. instance Find String (NeList (FindResult DualStickyRange)) where
  56. find x = do
  57. ResolutionContext _ s _ r _ ask
  58. case nonEmpty $ find_occs x $ selectRange r s of
  59. Nothing fail_with_context $ "String `" ++ x ++ "` does not occur"
  60. Just l return $ (Found InGiven . convert . (\o tightRange $ rangeFromTo (offset (pos (start r)) o :: Pos Char) (offset (pos (start r) + length x) o))) . l
  61. {- Since no semantic lookup is needed, Find String only looks in _given, of which it informs its caller by returning values marked Found InGiven. DualStickyRange and Anchor sidedness is described in Editing.Basics.
  62. For our next example, we consider the Find instance for "in"-clauses: -}
  63. instance (Find a (NeList b)) Find (In a) (NeList b) where
  64. find (In o Nothing) = find o
  65. find (In o (Just incl)) = ((full_range .) .) . find incl >>= (join .) . mapM (\(Found a x)
  66. (case a of InGiven id; InWf inwf) $ narrow (Editing.Show.show incl) (convert x) $ find o)
  67. -- For the nontrivial case, we first simply search for incl, which yields a number of DualStickyRanges, which we map to their full_range components. Then, for each StickyRange x that was found, we distinguish between two cases. If x is relative to the current _given, we just use |narrow| to focus our attention on x, and try to find |o| there. If x is relative to the well-formed snippet, then we should find |o| in there, too. So in this case, we want to force the Find instance for |o| to search in the well-formed snippet. We do this by first changing _given to the well-formed snippet and setting the Anchor transformer in well_formed to |return|, and then proceeding with |narrow| as before. We realize this with the following utility function:
  68. inwf :: InGiven_to_InWf a Resolver a Resolver a
  69. inwf re = ReaderT $ \(ResolutionContext w _ f r wf) do
  70. (tree, anchor_trans) or_fail wf
  71. Anchor _ a anchor_trans $ Anchor Before $ start r
  72. Anchor _ b anchor_trans $ Anchor Before $ end r
  73. (inGiven_to_inWf .) $ runReaderT re $ ResolutionContext w
  74. (Cxx.Show.show_simple tree) f (rangeFromTo a b) (Right (tree, return))
  75. -- Results returned by the re-contexted resolver may be marked as Found InGiven, but since we changed _given to the well-formed snippet, these are really Found InWf, so inwf should adjust them, and that's where the InGiven_to_InWf class comes in.
  76. class InGiven_to_InWf a where inGiven_to_inWf :: a a
  77. instance InGiven_to_InWf (FindResult a) where inGiven_to_inWf (Found _ x) = Found InWf x
  78. instance InGiven_to_InWf (Range Char) where inGiven_to_inWf = id
  79. instance InGiven_to_InWf a InGiven_to_InWf (NeList a) where inGiven_to_inWf = fmap inGiven_to_inWf
  80. instance InGiven_to_InWf a InGiven_to_InWf [a] where inGiven_to_inWf = fmap inGiven_to_inWf
  81. -- Next, we look at a Find instance for a typically semantic thing:
  82. instance Find Cxx.Basics.Findable (NeList (FindResult DualStickyRange)) where
  83. find d = inwf $ do
  84. (tree, _) well_formed . ask >>= or_fail
  85. r search_range . ask
  86. case nonEmpty $ filter ((`contained_in` r) . fst) $ Cxx.Operations.find d tree of
  87. Nothing fail_with_context $ "Could not find " ++ show d
  88. Just l return $ fmap (\(q, r'@(Range u h)) →
  89. let m = length $ takeWhile (==' ') $ reverse $ selectRange r' (Cxx.Show.show_simple tree) in
  90. Found InWf $ DualStickyRange (tightRange q) (tightRange $ (Range u (h-m) :: Range Char))) l
  91. {- Here, we immediately go into wf and do all the work there.
  92. In several other places we can see given-vs.-wf considerations: -}
  93. instance (Invertible a, Find a b, Convert (FindResult (StickyRange Char)) b) Find (Relative a) (NeList b) where
  94. find (Absolute x) = return . find x
  95. find (Relative o (AndList bas) w) = do
  96. Found c r (unanchor_range . full_range) find w
  97. (case c of InGiven id; InWf inwf) $ do
  98. u search_range . ask
  99. forM bas $ \ba do
  100. let h = Editing.Show.show ba ++ " " ++ Editing.Show.show w
  101. case ba of
  102. Before narrow h (rangeFromTo (start u) (start r)) $ find (invert o)
  103. After narrow h (rangeFromTo (end r) (end u)) $ find o
  104. find (FromTill b e) = do
  105. Found c p'@(Anchor _ p) ← (either ($ Before) id .) . find b
  106. (case c of InGiven id; InWf inwf) $ do
  107. sr search_range . ask
  108. narrow ("after " ++ Editing.Show.show b) (rangeFromTo p (end sr)) $ do
  109. Found d y either ($ After) id find e
  110. return . convert . Found d . flip stickyRange y . (case d of InGiven return; InWf toWf) p'
  111. find (Between o be@(Betw b e)) = do
  112. Found c x find b
  113. Found d y find e
  114. x' ← (if (c, d) == (InGiven, InGiven) ∨ c == InWf then return else toWf) x
  115. y' ← (if (c, d) == (InGiven, InGiven) ∨ d == InWf then return else toWf) y
  116. (if (c, d) == (InGiven, InGiven) then id else inwf) $ do
  117. let (p, q) = if either ($ Before) id x' ≤ either ($ Before) id y' then (x', y') else (y', x')
  118. narrow (Editing.Show.show be) (convert $ stickyRange (either ($ After) id p) (either ($ Before) id q)) $ return . find o
  119. -- More documentation some other time!
  120. findInStr :: Find a b String Maybe (TextEdit Char) -> (E (Cxx.Basics.GeordiRequest, Anchor Char E (Anchor Char))) a E b
  121. findInStr s f e x = runReaderT (find x) $ ResolutionContext "." s f (fullRange s) e
  122. instance Find (Around Substrs) (NeList (FindResult DualStickyRange)) where find (Around x) = find x
  123. instance Convert (FindResult (StickyRange Char)) (NeList (FindResult DualStickyRange)) where
  124. convert (Found c x) = return $ Found c $ convert x
  125. instance Find Substrs (NeList (FindResult DualStickyRange)) where
  126. find (Substrs l) = join . join . find l
  127. instance Find MakeSubject (NeList (FindResult DualStickyRange)) where
  128. find (MakeSubject l) = join . join . find l
  129. class OccurrenceError a where
  130. doesNotOccur_n_times :: a Int String
  131. multipleOccur :: a String
  132. instance OccurrenceError String where
  133. doesNotOccur_n_times s n = "String `" ++ s ++ "` does not occur " ++ multiplicative_numeral (if n < 0 then -n else n+1)
  134. multipleOccur s = "String `" ++ s ++ "` occurs multiple times"
  135. instance OccurrenceError Cxx.Basics.Findable where
  136. doesNotOccur_n_times s n = "Could not find a " ++ show (Ordinal n) ++ " " ++ Editing.Show.show s
  137. multipleOccur s = "Multiple " ++ Cxx.Show.show_plural s ++ " occur"
  138. instance (OccurrenceError a, OccurrenceError b) OccurrenceError (Either a b) where
  139. doesNotOccur_n_times = either doesNotOccur_n_times doesNotOccur_n_times
  140. multipleOccur = either multipleOccur multipleOccur
  141. instance Editing.Show.Show a OccurrenceError a where
  142. doesNotOccur_n_times s n = Editing.Show.show s ++ " does not occur " ++ multiplicative_numeral (if n < 0 then -n else n+1)
  143. multipleOccur s = Editing.Show.show s ++ " occurs multiple times"
  144. instance (OccurrenceError a, Find a (NeList (FindResult DualStickyRange))) Find (Ranked a) (FindResult DualStickyRange) where
  145. find (Sole x) = find x >>= \l if null (NeList.tail l) then return $ NeList.head l else fail_with_context $ multipleOccur x
  146. find (Ranked (Ordinal n) s) = safeNth n . toList . find s >>= maybe (fail_with_context $ doesNotOccur_n_times s n) return
  147. instance (OccurrenceError a, Find a (NeList (FindResult DualStickyRange))) Find (Rankeds a) (NeList (FindResult DualStickyRange)) where
  148. find (All x) = find x
  149. find (Sole' x) =
  150. find x >>= \l if null (NeList.tail l) then return l else fail_with_context $ multipleOccur x
  151. find (Rankeds rs s) = sequence ((\r find (Ranked r s)) . flatten_occ_clauses rs)
  152. find (AllBut rs s) =
  153. erase_indexed (ordinal_carrier . toList (flatten_occ_clauses rs)) . toList . find s >>= \z case z of
  154. [] throwError "All occurrences excluded." -- Todo: Better error.
  155. x:y return $ x :| y
  156. flatten_occ_clauses :: AndList OccurrencesClause NeList Ordinal
  157. flatten_occ_clauses (AndList rs) = join $ (\(OccurrencesClause l) l) . rs
  158. findResult_as_either :: FindResult a Either a a
  159. findResult_as_either (Found c a) = (case c of InGiven Left; InWf Right) a
  160. merge_contiguous_FindResult_StickyRanges :: NeList (FindResult (StickyRange Char)) Resolver (FindResult (NeList (StickyRange Char)))
  161. merge_contiguous_FindResult_StickyRanges l =
  162. neHomogenize toWf (findResult_as_either . l) >>= \a case a of
  163. Left xs return $ Found InGiven $ merge_contiguous xs
  164. Right xs return $ Found InWf $ merge_contiguous xs
  165. -- This is not optimal, because wf-ness of one contiguous range should not imply wf-ness of all ranges.
  166. instance Find Substr (FindResult DualStickyRange) where
  167. find Everything = Found InGiven . convert . wideRange . search_range . ask
  168. find (NotEverything x) = find x
  169. instance Find (EverythingOr (Rankeds (Either Cxx.Basics.Findable String))) (NeList (FindResult DualStickyRange)) where
  170. find Everything = return . Found InGiven . convert . wideRange . search_range . ask
  171. find (NotEverything x) = find x
  172. instance Find (EverythingOr (Rankeds (Either Cxx.Basics.Findable ImplicitDeclarationOf))) (NeList (FindResult DualStickyRange)) where
  173. find Everything = return . Found InGiven . convert . wideRange . search_range . ask
  174. find (NotEverything x) = find x
  175. instance Find ImplicitBodyOf (NeList (FindResult DualStickyRange)) where
  176. find (ImplicitBodyOf x) = find $ Cxx.Basics.BodyOf x
  177. instance Find ImplicitDeclarationOf (NeList (FindResult DualStickyRange)) where
  178. find (ImplicitDeclarationOf x) = find $ Cxx.Basics.DeclarationOf x
  179. instance Find InClause (NeList (FindResult DualStickyRange)) where find (InClause x) = join . join . find x
  180. instance Find AppendPositionsClause (NeList (FindResult (Anchor Char))) where
  181. find (NonAppendPositionsClause pc) = find pc
  182. find (AppendIn incl) = (((($ After) . full_range) .) .) . find incl
  183. instance Find PrependPositionsClause (NeList (FindResult (Anchor Char))) where
  184. find (NonPrependPositionsClause pc) = find pc
  185. find (PrependIn incl) = (((($ Before) . full_range) .) .) . find incl
  186. instance Find PositionsClause (NeList (FindResult (Anchor Char))) where
  187. find (PositionsClause (AndList bas) x) = do
  188. Found w l ((replace_range .) .) . find x >>= merge_contiguous_FindResult_StickyRanges
  189. return $ l >>= (\e (\ba Found w $ e ba) . bas)
  190. instance Find Replacer (NeList (FindResult RequestEdit)) where
  191. find (Replacer p r) = do
  192. Found c v ((replace_range .) .) . find p >>= merge_contiguous_FindResult_StickyRanges
  193. return $ (TextEdit . flip RangeReplaceEdit r . convert) Found c . v
  194. find (ReplaceOptions o o') = return $ fmap (Found InGiven) $ RemoveOptions o :| [AddOptions o']
  195. instance Find Changer (NeList (FindResult RequestEdit)) where
  196. find (Changer p r) = find (Replacer p r)
  197. find (ChangeOptions o o') = find (ReplaceOptions o o')
  198. instance Find Eraser [FindResult RequestEdit] where
  199. find (EraseText x) = ((TextEdit . flip RangeReplaceEdit "" . convert . full_range) .) toList . find x
  200. find (EraseOptions o) = return [Found InGiven $ RemoveOptions o]
  201. find (EraseAround (Wrapping x y) (Around z)) = do
  202. l (((unanchor_range . full_range) .) .) . toList . find z
  203. (concat .) $ forM l $ \(Found v u)
  204. (case v of InGiven id; InWf inwf) $ do
  205. sr search_range . ask
  206. (concat .) $ forM [(Before, x, -1, rangeFromTo (start sr) (start u)), (After, y, 0, rangeFromTo (end u) (end sr))] $ \(ba, xy, i, r)
  207. narrow (Editing.Show.show ba ++ " " ++ Editing.Show.show z) r $
  208. find $ EraseText $ Substrs $ and_one $ flip In Nothing $ Absolute $ NotEverything $ Rankeds (and_one $ OccurrencesClause $ return $ Ordinal i) (Right xy)
  209. instance Find Bound (FindResult (Either (StickyRange Char) (Anchor Char))) where
  210. find (Bound Nothing Everything) = Found InGiven . Left . stickyRange frontAnchor . Anchor After . Pos . size . search_range . ask
  211. find (Bound (Just Before) Everything) = return $ Found InGiven $ Right $ frontAnchor
  212. find (Bound (Just After) Everything) = Found InGiven . Right . Anchor After . Pos . size . search_range . ask
  213. find (Bound mba p) = ((maybe Left (\ba Right . ($ ba)) mba . full_range) .) . find p
  214. instance Find RelativeBound (FindResult (Either (StickyRange Char) (Anchor Char))) where
  215. find Front = Found InGiven . Right . Anchor Before . start . search_range . ask
  216. find Back = Found InGiven . Right . Anchor After . end . search_range . ask
  217. find (RelativeBound mba p) = find p >>= \l if null (NeList.tail l)
  218. then return $ maybe Left (\ba Right . ($ ba)) mba . full_range . NeList.head l
  219. else throwError "Relative bound must be singular."
  220. class ToWf a where toWf :: a Resolver a
  221. instance ToWf (Anchor Char) where toWf a = (((($ a) . snd) .) . well_formed . ask >>= or_fail) >>= or_fail
  222. instance ToWf (StickyRange Char) where
  223. toWf a = do
  224. f (snd .) . well_formed . ask >>= or_fail
  225. liftM2 stickyRange (or_fail (f $ a Before)) (or_fail (f $ a After))
  226. instance (ToWf a, ToWf b) ToWf (Either a b) where
  227. toWf (Left x) = Left . toWf x
  228. toWf (Right x) = Right . toWf x
  229. makeMoveEdit' :: FindResult (Anchor Char) → FindResult (StickyRange Char) → Resolver (FindResult (TextEdit Char))
  230. makeMoveEdit' (Found InGiven a) (Found InGiven r) = Found InGiven . makeMoveEdit a (convert r)
  231. makeMoveEdit' (Found InWf a) (Found c x) = do
  232. r (case c of InGiven toWf; InWf return) x
  233. Found InWf . makeMoveEdit a (convert r)
  234. makeMoveEdit' (Found c x) (Found InWf r) = do
  235. a' ← (case c of InGiven → toWf; InWf → return) x
  236. Found InWf . makeMoveEdit a' (convert r)
  237. makeSwapEdit :: FindResult (StickyRange Char) FindResult (StickyRange Char) Resolver [FindResult (TextEdit Char)]
  238. makeSwapEdit a b = do
  239. some makeMoveEdit' (($ Before) . b) a
  240. more makeMoveEdit' (($ Before) . a) b
  241. return [some, more]
  242. instance Find Mover [FindResult (TextEdit Char)] where
  243. find (Mover o p) = do
  244. a find p
  245. toList . find o >>= mapM (makeMoveEdit' a . (full_range .)) . reverse
  246. instance Find Position (FindResult (Anchor Char)) where
  247. find (Position ba x) = find x >>= \l if null (NeList.tail l)
  248. then return $ flip full_range ba . (NeList.head l)
  249. else throwError "Anchor position must be singular."
  250. instance Find UsePattern (FindResult (Range Char)) where
  251. find (UsePattern z) = do
  252. ResolutionContext _ s _ r _ ask
  253. let
  254. text_tokens = edit_tokens Char.isAlphaNum $ selectRange r s
  255. pattern_tokens = edit_tokens Char.isAlphaNum z
  256. (x, y) = (sum $ length . take stt text_tokens, sum $ length . take siz (drop stt text_tokens))
  257. (owc, stt, siz) = head $ approx_match token_edit_cost pattern_tokens (replaceAllInfix pattern_tokens (replicate (length pattern_tokens) (replicate 100 'X')) text_tokens)
  258. if y == 0 ops_cost owc > fromIntegral (length z) / 1.5 then fail_with_context $ "No non-exact match for " ++ z else return $ Found InGiven $ offset (pos (start r)) $ Range (Pos x) y
  259. instance Invertible UsePattern where invert = id
  260. instance Convert (FindResult (Range a)) (FindResult (Range a)) where convert = id
  261. instance Find UseClause (NeList (FindResult RequestEdit)) where
  262. find (UseOptions o) = return $ return $ Found InGiven $ AddOptions o
  263. find (UseString ru@(In b _)) = case unrelative b of
  264. Nothing throwError "Nonsensical use-command."
  265. Just (UsePattern v) (((TextEdit . flip RangeReplaceEdit v) .) .) . find ru
  266. token_edit_cost :: Op String Cost
  267. token_edit_cost (SkipOp (' ':_)) = 0
  268. token_edit_cost (SkipOp x) | x Cxx.Basics.keywords = -2.4
  269. token_edit_cost (SkipOp (h:t)) | Char.isAlphaNum h = -2.2 - fromIntegral (length t) * 0.2
  270. token_edit_cost (SkipOp _) = -2
  271. token_edit_cost (EraseOp (' ':_)) = 0.02
  272. token_edit_cost (EraseOp x) = token_edit_cost (InsertOp x)
  273. token_edit_cost (InsertOp t) | t Cxx.Basics.keywords = 2
  274. token_edit_cost (InsertOp (' ':_)) = -0.02
  275. token_edit_cost (InsertOp x@(y:_)) | Char.isAlpha y = fromIntegral (length x) * 0.7
  276. token_edit_cost (InsertOp (x:y)) | Char.isDigit x = 1 + fromIntegral (length y) * 0.3
  277. token_edit_cost (InsertOp _) = 1
  278. token_edit_cost (ReplaceOp x y)
  279. | or $ (\c List.all ( c) [x, y]) . [Cxx.Basics.classKeys, Cxx.Basics.accessSpecifiers, Cxx.Basics.relational_ops] = 0.4
  280. token_edit_cost (ReplaceOp (c:_) (d:_)) | not $ Char.isAlphaNum c Char.isAlphaNum d = 1.1
  281. token_edit_cost (ReplaceOp x@(c:_) y@(d:_)) | Char.isAlpha c, Char.isAlpha d =
  282. if null (x y) then 10 else levenshtein x y * 0.4
  283. token_edit_cost (ReplaceOp x@(c:_) y@(d:_)) | Char.isAlphaNum c, Char.isAlphaNum d = levenshtein x y * 0.8
  284. token_edit_cost (ReplaceOp _ _) = 10
  285. -- The precise values of these costs are fine-tuned to make the tests pass, and that is their only justification. We're trying to approximate the human intuition for what substring should be replaced, as codified in the tests.
  286. instance Find Command [FindResult RequestEdit] where
  287. find (Use l) = toList . join . find l
  288. find (Append x Nothing) = do
  289. r search_range . ask
  290. return [Found InGiven $ TextEdit $ InsertEdit (Anchor After (Pos (size r))) x]
  291. find (Prepend x Nothing) = return [Found InGiven $ TextEdit $ InsertEdit frontAnchor x]
  292. find (Append r (Just p)) = toList . (((TextEdit . flip InsertEdit r) .) .) . join . find p
  293. find (Prepend r (Just p)) = toList . (((TextEdit . flip InsertEdit r) .) .) . join . find p
  294. find (Erase (AndList l)) = concat . sequence (find . toList l)
  295. find (Replace (AndList l)) = concat . sequence ((toList .) . find . toList l)
  296. find (Change (AndList l)) = concat . sequence ((toList .) . find . toList l)
  297. find (Insert (SimpleInsert r) p) = toList . (((TextEdit . flip InsertEdit r) .) .) . join . find p
  298. find (Insert (WrapInsert (Wrapping x y)) (AndList z)) =
  299. concatMap (\(Found v a, Found w b) [Found v $ TextEdit $ InsertEdit a x, Found w $ TextEdit $ InsertEdit b y]) . pairs . concat . map toList . sequence (map find $ toList z)
  300. find (Move (AndList movers)) = ((TextEdit .) .) . concat . sequence (find . toList movers)
  301. find (Swap substrs Nothing) = toList . ((replace_range .) .) . find substrs >>= f
  302. where
  303. f [] = return []
  304. f (a:b:c) = liftM2 (++) (((TextEdit .) .) . makeSwapEdit a b) (f c)
  305. f _ = throwError "Cannot swap uneven number of operands."
  306. find (Swap substrs (Just substrs')) = do
  307. Found v x ((full_range .) .) . find substrs >>= merge_contiguous_FindResult_StickyRanges
  308. Found w y ((full_range .) .) . find substrs' >>= merge_contiguous_FindResult_StickyRanges
  309. let a = Found v . x; b = Found w . y
  310. if null (NeList.tail a) && null (NeList.tail b) then ((TextEdit .) .) . makeSwapEdit (NeList.head a) (NeList.head b)
  311. else throwError "Swap operands must be contiguous ranges."
  312. find (Make s b) = inwf $ do
  313. (tree, _) well_formed . ask >>= or_fail
  314. l (fmap (\(Found _ x) replace_range x)) . find s
  315. (Found InGiven .) . concat . toList . forM l (\x
  316. (TextEdit .) . Cxx.Operations.make_edits (convert x) b 0 tree)
  317. find Fix = do
  318. mf fixIt . ask
  319. case mf of
  320. Nothing throwError "No fix available."
  321. Just f return [Found InGiven $ TextEdit f]
  322. use_tests :: IO ()
  323. use_tests = do
  324. t "ETYPE_DESC" "ETPYE" "Replaced `<< ETPYE` with `<< ETYPE_DESC`." "Replaced `<< ETYPE_DESC` with `<< ETPYE`."
  325. t "kip(a.~T)" "a.~T" "Replaced a.~T with kip(a.~T)." "Replaced kip(a.~T) with a.~T."
  326. -- t "cos(a.~T)" "a.~T" -- Fails, but can probably be made to work by rewarding successive skips.
  327. t "size_type" "size_t" "Replaced `string::size_t- siz` with `string::size_type- siz`." "Replaced `string::size_type- siz` with `string::size_t- siz`."
  328. t "size = 9" "siz = 2" "Replaced `string::size_t- siz = 2` with `string::size_t- size = 9`." "Replaced `string::size_t- size = 9` with `string::size_t- siz = 2`."
  329. t "ETYPE" "ETPYE" "Replaced `<< ETPYE` with `<< ETYPE`." "Replaced `<< ETYPE` with `<< ETPYE`."
  330. t "std::string" "string" "Replaced `string::size_t- siz` with `std::string::size_t- siz`." "Replaced `std::string::size_t- siz` with `string::size_t- siz`."
  331. t "; float x" "; int x" "Replaced `int x` with `float x`." "Replaced `float x` with `int x`."
  332. t "x-" "x -" "Replaced `x - size` with `x- size`." "Replaced `x- size` with `x - size`."
  333. t ") cin <<" ") cout <<" "Replaced cout with cin." "Replaced cin with cout."
  334. t "x = 4" "x = 3" "Replaced 3 with 4." "Replaced 4 with 3."
  335. t "x - 8);" "x - size);" "Replaced `x - size` with `x - 8`." "Replaced `x - 8` with `x - size`."
  336. t "(!i)" "(i == 0)" "Replaced `i == 0` with !i." "Replaced !i with `i == 0`."
  337. t "seekp" "seek" "Replaced a.seek with a.seekp." "Replaced a.seekp with a.seek."
  338. t "<char>" "<unsigned char>" "Replaced `vector<unsigned char> & r` with `vector<char> & r`." "Replaced `vector<char> & r` with `vector<unsigned char> & r`."
  339. t "<const fish>" "<fish>" "Replaced `reinterpret_cat<fish>` with `reinterpret_cat<const fish>`." "Replaced `reinterpret_cat<const fish>` with `reinterpret_cat<fish>`."
  340. t "&); };" "&) };" "Inserted semicolon after `C const &)`." "Erased semicolon after `C const &)`."
  341. t "> * r = v" "> & r = v" "Replaced `& r` with `* r`." "Replaced `* r` with `& r`."
  342. t "v.cbegin()" "v.begin()" "Replaced v.begin with v.cbegin." "Replaced v.cbegin with v.begin."
  343. -- Todo: "void foo" should match "voidfoo".
  344. -- t "x - sizeof(y))" "x - size)" "Replaced `x - size` with `x - sizeof(y)`." "Replaced `x - sizeof(y))` with `x - size)`."
  345. t "int a(2);" "int a;" "Inserted (2) after `{ int a`." "Erased (2) after `{ int a`."
  346. t "int const * w" "int * w" "Replaced `int * w` with `int const * w`." "Replaced `int const * w` with `int * w`."
  347. t "main(int argc) {" "main() {" "Inserted `int argc` after `void main(`." "Erased `int argc`."
  348. t "_cast" "_cat" "Replaced `reinterpret_cat<fish>` with `reinterpret_cast<fish>`." "Replaced `reinterpret_cast<fish>` with `reinterpret_cat<fish>`."
  349. t "(++a)" "(a++)" "Replaced a++ with ++a." "Replaced ++a with a++."
  350. t "list<int>" "vector<int>" "Replaced `vector<int> v` with `list<int> v`." "Replaced `list<int> v` with `vector<int> v`."
  351. t "a->seekp" "a.seek" "Replaced a.seek with a->seekp." "Replaced a->seekp with a.seek."
  352. t "vector<int>::iterator i" "vector<int> i" "Replaced `vector<int> i` with `vector<int>::iterator i`." "Replaced `vector<int>::iterator i` with `vector<int> i`."
  353. t "runtime_error(" "runtime_exception(" "Replaced `throw runtime_exception` with `throw runtime_error`." "Replaced `throw runtime_error` with `throw runtime_exception`."
  354. t "~T();" "~T;" "Inserted () after `) { a.~T`." "Erased () after `) { a.~T`." -- Todo: ugly.
  355. t "int const * w" "int * w" "Replaced `int * w` with `int const * w`." "Replaced `int const * w` with `int * w`."
  356. t "(T & a)" "(T a)" "Replaced `T a` with `T & a`." "Replaced `T & a` with `T a`."
  357. t "& r(v);" "& r = v;" "Replaced `= v` after `vector<unsigned char> & r` with (v)." "Replaced (v) after `vector<unsigned char> & r` with `= v`."
  358. t "ios_base::end_t" "ios::end" "Replaced ios::end with `ios_base::end_t`.""Replaced `ios_base::end_t` with ios::end."
  359. t "95" "94" "Replaced 94 with 95." "Replaced 95 with 94."
  360. t "vector<int> const v { 3, 2 };" "vector<int> v; v = { 3, 2 };" "Replaced `vector<int> v; v =` with `vector<int> const v`." "Replaced `vector<int> const v` with `vector<int> v; v =`."
  361. t "class C" "struct C" "Replaced `struct C` with `class C`." "Replaced `class C` with `struct C`."
  362. t "B z{p};" "B z = B{p};" "Erased `= B` after `B z`." "Inserted `= B` after `B z`."
  363. t "friend C & operator+" "C & operator+" "Inserted friend before `C & operator+`." "Erased friend before `C & operator+`."
  364. t "char const(&here)[N]" "char(const&here)[N]" "Replaced `char(const&here` with `char const(&here`." "Replaced `char const(&here` with `char(const&here`."
  365. t "z = shared_ptr<B>{new p}" "z = B{p}" "Replaced B{p with `shared_ptr<B>{new p`." "Replaced `shared_ptr<B>{new p` with B{p." -- Todo: ugly.
  366. t "(X(y));" "X(y);" "Inserted ( before X(y) and inserted ) after `} X(y)`." "Erased ( before X(y)) and ) after `} (X(y)`." -- Todo: ugly.
  367. t "2000" "1800" "Replaced 1800 with 2000." "Replaced 2000 with 1800."
  368. t "8000100808" "10000000000" "Replaced 10000000000 with 8000100808." "Replaced 8000100808 with 10000000000."
  369. t "> 7" ">= 7" "Replaced `x >= 7` with `x > 7`." "Replaced `x > 7` with `x >= 7`."
  370. t "private: fstream" "public: fstream" "Replaced `public: fstream p` with `private: fstream p`." "Replaced `private: fstream p` with `public: fstream p`." -- Todo: "replaced public: with private: before fstream p".
  371. t "int main" "void main" "Replaced `void main` with `int main`." "Replaced `int main` with `void main`." -- Todo: One day this should say: "Made main return int."
  372. t "<char>" "<unsigned char>" "Replaced `vector<unsigned char> & r` with `vector<char> & r`." "Replaced `vector<char> & r` with `vector<unsigned char> & r`."
  373. t "int const u =" "int x =" "Replaced `int x` with `int const u`." "Replaced `int const u` with `int x`."
  374. t "u - -j" "u--j" "Replaced &u--j with `&u - -j`." "Replaced `&u - -j` with &u--j."
  375. t "struct C{" "struct C(){" "Erased () after `struct C`." "Inserted () after `struct C`."
  376. --t "&ETPYE" "ETPYE" "Replaced ETPYE with &ETPYE." "Replaced &ETPYE with ETPYE."
  377. putStrLn "All use tests passed."
  378. where
  379. u :: String String String String String IO ()
  380. u txt pattern match d rd =
  381. case runReaderT (find (UseString $ flip In Nothing $ Absolute $ UsePattern pattern)) (ResolutionContext "." txt Nothing (fullRange txt) (Left "-")) of
  382. Left e fail e
  383. Right (neElim (Found _ (TextEdit (RangeReplaceEdit rng _)), [])) do
  384. test_cmp pattern match (selectRange rng txt)
  385. let r = replaceRange rng pattern txt
  386. test_cmp pattern d $ show $ Editing.Diff.diff txt r
  387. test_cmp (pattern ++ " (reverse)") rd $ show $ Editing.Diff.diff r txt
  388. _ error "should not happen"
  389. t :: String String String String IO ()
  390. t = u "{ string::size_t- siz = 2; int x = 3; if(i == 0) cout << ETPYE(x - size); vector<int> v; v = { 3, 2 }; vector<int> i = reinterpret_cat<fish>(10000000000, v.begin()); } X(y); using tracked::B; B z = B{p}; int const u = 94; int * w = &u--j; !B && !D; vector<unsigned char> & r = v; struct C(){ C & operator+(ostream &, char(const&here)[N], C const &) }; template<typename T> voidfoo(T a) { a.~T; } void main() { int a; a.seek(1800, ios::end); foo(a++); if(x >= 7) throw runtime_exception(y); } class Qbla { public: fstream p; };"