reprinter 0.2.0.0 → 0.3.0.0
raw patch · 9 files changed
+639/−53 lines, 9 filesdep +bytestringdep +hspecdep +reprinterdep −uniplatedep ~mtldep ~sybdep ~syzsetup-changednew-uploader
Dependencies added: bytestring, hspec, reprinter
Dependencies removed: uniplate
Dependency ranges changed: mtl, syb, syz, text
Files
- CHANGELOG.md +8/−0
- LICENSE +13/−0
- Setup.hs +0/−2
- reprinter.cabal +37/−11
- src/Text/Reprinter.hs +46/−40
- src/Text/Reprinter/Example.lhs +416/−0
- src/Text/Reprinter/StringLike.hs +65/−0
- tests/hspec/Hspec.hs +1/−0
- tests/hspec/ReprinterSpec.hs +53/−0
+ CHANGELOG.md view
@@ -0,0 +1,8 @@+## 0.3.0.0 (2021-03-03)+* `Reprinting m` is now `Reprinting i m`, where `i` is the input type, which+ must be "`String`-like" (containers holding some "list" of `Char`s).+ Previously, `i` was limited to `Text`. By default, `ByteString`, `Text` and+ `String` are supported.+* Add an example module taking prompts from the 2017 paper, and rewrite the+ tests to use the definitions in there.+* Support at least GHC 8.6, 8.8, 8.10, 9.0
+ LICENSE view
@@ -0,0 +1,13 @@+Copyright (c) 2017: Dominic Orchard, Vilem-Benjamin Liepelt, Harry Clarke++Licensed under the Apache License, Version 2.0 (the "License");+you may not use this file except in compliance with the License.+You may obtain a copy of the License at++ http://www.apache.org/licenses/LICENSE-2.0++Unless required by applicable law or agreed to in writing, software+distributed under the License is distributed on an "AS IS" BASIS,+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+See the License for the specific language governing permissions and+limitations under the License.
− Setup.hs
@@ -1,2 +0,0 @@-import Distribution.Simple-main = defaultMain
reprinter.cabal view
@@ -1,19 +1,25 @@--- This file has been generated from package.yaml by hpack version 0.17.1.+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.33.0. -- -- see: https://github.com/sol/hpack+--+-- hash: b19671665da6cf09b8aa157c387db536e02e3f8518551ed0fcfd6c5c43b40134 name: reprinter-version: 0.2.0.0+version: 0.3.0.0 synopsis: Scrap Your Reprinter description: A datatype generic algorithm for layout-preserving refactoring-license: Apache-2.0-author: Dominic Orchard, Harry Clarke-maintainer: d.a.orchard@kent.ac.uk category: Text homepage: https://github.com/camfort/reprinter#readme bug-reports: https://github.com/camfort/reprinter/issues+author: Dominic Orchard, Vilem-Benjamin Liepelt, Harry Clarke+maintainer: d.a.orchard@kent.ac.uk+license: Apache-2.0+license-file: LICENSE build-type: Simple-cabal-version: >= 1.10+extra-source-files:+ CHANGELOG.md source-repository head type: git@@ -22,16 +28,36 @@ library exposed-modules: Text.Reprinter+ Text.Reprinter.StringLike+ Text.Reprinter.Example other-modules: Paths_reprinter+ hs-source-dirs:+ src build-depends: base >=4.9 && <5+ , bytestring >=0.10.8.0 && <0.12.0.0+ , mtl >=2.2 && <2.3+ , syb >=0.6 && <1.0+ , syz >=0.2 && <0.3 , text >=1.2.2 && <2 , transformers >=0.5 && <0.6- , syb >=0.6 && <0.7- , uniplate >=1.6 && <1.7- , mtl >=2.2 && <2.3- , syz ==0.2.0.0+ default-language: Haskell2010++test-suite spec+ type: exitcode-stdio-1.0+ main-is: Hspec.hs+ other-modules:+ ReprinterSpec+ Paths_reprinter hs-source-dirs:- src+ tests/hspec+ build-tool-depends:+ hspec-discover:hspec-discover+ build-depends:+ base >=4.9 && <5+ , hspec+ , mtl+ , reprinter+ , text default-language: Haskell2010
src/Text/Reprinter.hs view
@@ -2,37 +2,40 @@ {-# LANGUAGE DeriveDataTypeable #-} module Text.Reprinter- (- reprintSort- , reprint- , Source+ ( module Data.Functor.Identity+ , module Data.Generics+ , module Data.Generics.Zipper+ , Span , Position , initPosition- , initLine , initCol- , mkLine+ , initLine , mkCol- , advanceLine+ , mkLine , advanceCol- , Span+ , advanceLine+ , RefactorType(..)+ , Refactorable(..) , Reprinting , catchAll , genReprinting- , Refactorable(..)- , RefactorType(..)+ , reprint+ , reprintSort ) where +-- Import solely for re-exporting for library clients+import Data.Functor.Identity+import Data.Generics+++import Text.Reprinter.StringLike import Control.Monad (forM) import Control.Monad.Trans.Class (lift) import Control.Monad.Trans.State.Lazy-import qualified Data.Text.Lazy as Text import Data.Data import Data.Generics.Zipper-import Data.Monoid ((<>), mempty) import Data.List (sortOn)---- | Text from source file-type Source = Text.Text+import Data.Monoid ((<>), mempty) -- | A line within the source text newtype Line = Line Int deriving (Data, Eq, Ord, Show)@@ -79,7 +82,9 @@ type Span = (Position, Position) -- | Type of a reprinting function-type Reprinting m = forall node . Typeable node => node -> m (Maybe (RefactorType, Source, Span))+--+-- @i@ is the input type (something with a '[Char]'-like interface)+type Reprinting i m = forall node . (Typeable node) => node -> m (Maybe (RefactorType, i, Span)) -- | Specify a refactoring type data RefactorType = Before | After | Replace@@ -87,11 +92,11 @@ -- | The reprint algorithm takes a refactoring (parameteric in -- | some monad m) and turns an arbitrary pretty-printable type 'ast'--- | into a monadic Source transformer.-reprint :: (Monad m, Data ast) => Reprinting m -> ast -> Source -> m Source+-- | into a monadic 'StringLike i' transformer.+reprint :: (Monad m, Data ast, StringLike i) => Reprinting i m -> ast -> i -> m i reprint reprinting ast input -- If the input is empty return empty- | Text.null input = return mempty+ | slNull input = return mempty -- Otherwise proceed with the algorithm | otherwise = do@@ -103,8 +108,9 @@ -- Add to the output source the remaining input source return (out <> remaining) --- | Take a refactoring and a zipper producing a stateful Source transformer with Position state.-enter :: Monad m => Reprinting m -> Zipper ast -> StateT (Position, Source) m Source+-- | Take a refactoring and a zipper producing a stateful 'StringLike i'+-- | transformer with Position state.+enter :: (Monad m, StringLike i) => Reprinting i m -> Zipper ast -> StateT (Position, i) m i enter reprinting zipper = do -- Step 1: Apply a refactoring refactoringInfo <- lift (query reprinting zipper)@@ -133,11 +139,11 @@ -- | The reprint algorithm takes a refactoring (parameteric in -- | some monad m) and turns an arbitrary pretty-printable type 'ast'--- | into a monadic Source transformer.-reprintSort :: (Monad m, Data ast) => Reprinting m -> ast -> Source -> m Source+-- | into a monadic 'StringLike i' transformer.+reprintSort :: (Monad m, Data ast, StringLike i) => Reprinting i m -> ast -> i -> m i reprintSort reprinting ast input -- If the input is empty return empty- | Text.null input = return mempty+ | slNull input = return mempty -- Otherwise proceed with the algorithm | otherwise = do@@ -151,19 +157,19 @@ -- | Take a refactoring and a zipper to produce a list of refactorings-enter' :: Monad m => Reprinting m -> Zipper ast- -> StateT (Position, Source) m Source+enter' :: (Monad m, StringLike i) => Reprinting i m -> Zipper ast+ -> StateT (Position, i) m i enter' reprinting zipper = do -- Step 1: Get refactorings via AST zipper traversal rs <- lift $ getRefactorings reprinting zipper [] -- Step 2: Do the splicing on the sorted refactorings srcs <- mapM splice (sortBySpan . reverse $ rs)- return $ Text.concat srcs+ return $ mconcat srcs where sortBySpan = sortOn (\(_,_,sp) -> sp) -getRefactorings :: Monad m => Reprinting m -> Zipper ast -> [(RefactorType, Source, Span)]- -> m [(RefactorType, Source, Span)]+getRefactorings :: (Monad m, StringLike i) => Reprinting i m -> Zipper ast -> [(RefactorType, i, Span)]+ -> m [(RefactorType, i, Span)] getRefactorings reprinting zipper acc = do -- Step 1: Apply a refactoring refactoringInfo <- query reprinting zipper@@ -186,7 +192,7 @@ -- Otherwise return the empty string Nothing -> return acc -splice :: Monad m => (RefactorType, Source, Span) -> StateT (Position, Source) m Source+splice :: (Monad m, StringLike i) => (RefactorType, i, Span) -> StateT (Position, i) m i splice (typ, output, (lb, ub)) = do (cursor, inp) <- get case typ of@@ -210,25 +216,25 @@ put (ub, inp'') return (pre <> output <> post) --- Given a lower-bound and upper-bound pair of Positions, split the--- incoming Source based on the distance between the Position pairs-splitBySpan :: Span -> Source -> (Source, Source)+-- | Given a lower-bound and upper-bound pair of Positions, split the+-- | incoming 'StringLike i' based on the distance between the Position pairs.+splitBySpan :: StringLike i => Span -> i -> (i, i) splitBySpan (lower, upper) = subtext mempty lower where subtext acc cursor input | cursor < lower =- case Text.uncons input of+ case slUncons input of Nothing -> done Just ('\n', input') -> subtext acc (advanceLine cursor) input' Just (_, input') -> subtext acc (advanceCol cursor) input' | cursor < upper =- case Text.uncons input of+ case slUncons input of Nothing -> done- Just ('\n', input') -> subtext (Text.cons '\n' acc) (advanceLine cursor) input'- Just (x, input') -> subtext (Text.cons x acc) (advanceCol cursor) input'+ Just ('\n', input') -> subtext (slCons '\n' acc) (advanceLine cursor) input'+ Just (x, input') -> subtext (slCons x acc) (advanceCol cursor) input' | otherwise = done- where done = (Text.reverse acc, input)+ where done = (slReverse acc, input) @@ -238,8 +244,8 @@ getSpan :: t -> Span -- | Essentially wraps the refactorable interface-genReprinting :: (Monad m, Refactorable t, Typeable t)- => (t -> m Source) -> t -> m (Maybe (RefactorType, Source, Span))+genReprinting :: (Monad m, Refactorable t, Typeable t, StringLike i)+ => (t -> m i) -> t -> m (Maybe (RefactorType, i, Span)) genReprinting f z = case isRefactored z of Nothing -> return Nothing Just refactorType -> do
+ src/Text/Reprinter/Example.lhs view
@@ -0,0 +1,416 @@+Scrap Your Reprinter: Example+=============================++Reprinting takes a source file and its (possible transformed) AST and+"stitches" them together into a new source file. This library provides+a generic reprinting algorithm that works on any AST with some modest+requirements. Where any changes to the AST have been made the+reprinting algorithm can be parameterised to hook into+application-specific functionality for handling nodes in the AST that+have been marked as transformed (e.g., applying a pretty printer to+these parts).++This module gives an introduction to library usage. For a better view+of the library itself, [the 2017+paper](https://www.cs.kent.ac.uk/people/staff/dao7/publ/reprinter2017.pdf)+goes over implementation in depth. (This module is adapted from+Section 3.4.)++We demonstrate the library on a limited integer expression language (reused for+the library tests). This is a literate Haskell/Markdown file, so feel free to+follow along in GHCi or your favourite text viewer.++\begin{code}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE FlexibleInstances #-}++module Text.Reprinter.Example where++import Text.Reprinter+import Control.Monad.State -- for later example+import Data.Char -- for parsing+\end{code}++Introduction+------------+*(Section 1 of the 2017 paper covers this in better detail.)*++A compiler translates source code to a target language. Sometimes when writing+language tools, you may find yourself writing a compiler where the source and+target languages are the same; automated code refactoring tools in IDEs+provide a common set of examples. Such tools must be careful not to remove+*secondary notation* like whitespace and comments. This, in short, can be a+pain to do well.++The reprinter library allows you to write a reprinter for any algebraic data+type supporting a minimal interface the algorithm needs to track changes.++This module designs a whitespace-flexible language with comments, and uses the+library to allow reprinting that preserves such secondary notation.++Language definition+-------------------+Let's take a language targeting integer addition, plus variable assignments. Our+top-level type will be an SSA-like list of *variable declaration-assignments*:++\begin{code}+type AST a = [Decl a]+data Decl a = Decl a Span String (Expr a)+ deriving (Eq, Data, Typeable, Show)+\end{code}++A `Decl a span var expr` represents the assignment of the value of an+expression `expr` to a variable `var`. The AST is composed of a sequence+(list) of these `Decl`s.++Expressions are formed of variables, literals, and additions over expressions:++\begin{code}+data Expr a+ = Plus a Span (Expr a) (Expr a)+ | Var a Span String+ | Const a Span Int+ deriving (Eq, Data, Typeable, Show)+\end{code}++For our reprinting algorithm, every refactorable node in the AST must+store position information (`Span`, i.e., the start and end point of+this piece of syntax in the source code text) and whether it's been+refactored (and thus needs reprinting). In this case, we've+parameterised our AST over an arbitrary type `a`, which we specialise+in the rest of this file to `Bool` to represent whether this node has+been refactored or not. In a more complex AST, you could add this as a+field to an existing node annotation record type.++Note that the algorithm requires ASTs to have `Data` and `Typeable` instances.+Deriving these automatically requires the `DeriveDataTypeable` language pragma.++*(Section 1.1 in the 2017 paper gives an illustrated step-by-step example of a+transformation and reprint.)*++Concrete syntax and goals+-------------------------+Let's digress for a while to discuss our language's concrete syntax, since+reprinting uses abstract and concrete syntax simultaneously. Our language is+going to look something like this:++\begin{code}+exBasic :: String+exBasic = "x = +(0,1)\n"+\end{code}++We permit arbitrary spacing for prettier code, like so:++\begin{code}+exPrettier :: String+exPrettier = unlines+ [ "var = +(0 , 1)"+ , "x = +(var, 2)"+ ]+\end{code}++And lines can be empty, or comments:++\begin{code}+exComment :: String+exComment = unlines+ [ "// slightly superfluous variable"+ , "zero = 0"+ , ""+ , "// somewhat useful variable"+ , "x = +(zero, 1)"+ ]+\end{code}++Knowing all this, our aim is to take a formatted program source, parse it, apply+a transformation to the AST, then reprint the program while keeping the original+formatting. Starting with the given source (taken from the 2017 paper)++\begin{code}+exPaper :: String+exPaper = unlines+ [ "x = +(1,2)"+ , "y = +(x,0)"+ , "// Calculate z"+ , "z = +( 1, +(+(0,x) ,y) )"+ ]+\end{code}++We'll produce the following refactored and reprinted output:++ > putStr exPaper+ x = +(1,2)+ y = +(x,0)+ // Calculate z+ z = +( 1, +(+(0,x) ,y) )+ > (putStr . refactor) exPaper+ x = +(1,2)+ y = x+ // Calculate z+ z = +( 1, +(x ,y) )++Writing a transformation+------------------------+Putting concrete syntax aside, let's write a transformation for our AST - a+refactoring. A nice obvious one is replacing `x+0` (and `0+x`) expressions with+just `x`.++\begin{code}+refactorZero :: AST Bool -> AST Bool+refactorZero = map $ \(Decl a s n e) -> Decl a s n (go e)+ where+ go (Plus _ s e (Const _ _ 0)) = markRefactored (go e) s+ go (Plus _ s (Const _ _ 0) e) = markRefactored (go e) s+ go (Plus a s e1 e2) = Plus a s (go e1) (go e2)+ go e = e++ markRefactored (Plus _ _ e1 e2) s = Plus True s e1 e2+ markRefactored (Var _ _ n) s = Var True s n+ markRefactored (Const _ _ i) s = Const True s i+\end{code}++Note that when marking nodes as refactored (`markRefactored`), we+replace the `Span` of the refactored node with the span of the+original `x+0` node- this allows the reprinting algorithm to replace+the original part of the source code with the new refactored node.++In concrete syntax, we're making changes like:++ + ( x , 0 ) becomes+ x++See how `x` is pulled out. The `+(x,y)` expression is directly replaced with+`x`, so we make sure to use the original span. Any comments following the+expression will be `shifted' - *not* removed, because the reprinter only makes+changes when a node in the AST indicates it has been refactored. Parts of a+source file that aren't captured in the AST will be printed with no changes.++Reprinter plumbing+------------------+We have an AST and a transformation on it. Next, we need to tell the reprinter+how to use our AST.++\begin{code}+-- FlexibleInstances used to define this without a wrapper+instance Refactorable (Expr Bool) where+ isRefactored (Plus True _ _ _) = Just Replace+ isRefactored (Var True _ _) = Just Replace+ isRefactored (Const True _ _) = Just Replace+ isRefactored _ = Nothing++ getSpan (Plus _ s _ _) = s+ getSpan (Var _ s _) = s+ getSpan (Const _ s _) = s+\end{code}++Your AST's `Refactorable` instances will depend on how you store annotations in+your tree. Likely you store refactoring information inside a larger record type.+Perhaps you disallow refactoring at the type level for certain nodes. In this+case, we're only writing an instance for `Expr`s, because we don't reprint+`Decl`s directly. (If we wrote a variable renaming transformation, then it would+be needed.)++We're almost there. Next we define a generic query over the AST that determines+what we do for each node in the AST. This reprinting is straightforward:++ * If an `Expr` is marked as refactored, replace it with the updated `Expr`+ pretty-printed (AST -> concrete syntax)+ * Else skip (if the node was a `Decl`, or an unrefactored `Expr`)++Reprintings of this type can be generated with `genReprinting`. A later example+writes the reprinting directly to annotate all nodes of a certain type. For now,+let's code that reprinting and the required pretty printer:++\begin{code}+-- See the 2017 paper and SYB documentation for more info on 'extQ' queries.+exprReprinter :: Reprinting String Identity+exprReprinter = catchAll `extQ` reprintExpr+ where+ reprintExpr x = genReprinting (return . prettyExpr) (x :: Expr Bool)++-- | Print an expression in canonical string form.+prettyExpr :: Expr a -> String+prettyExpr (Plus _ _ e1 e2) = "+(" <> prettyExpr e1 <> ", " <> prettyExpr e2 <> ")"+prettyExpr (Var _ _ n) = n+prettyExpr (Const _ _ n) = show n++-- Note that we don't define a pretty printer for declarations, as we're not+-- refactoring on that level, so won't ever reprint them.+\end{code}++`catchAll \`extQ\` reprintExpr` essentially says "try casting my argument to+use in `reprintExpr`, else default to `catchAll`" where `catchAll` always+returns `Nothing` (meaning no refactoring/don't reprint). See the 2017 paper and+Scrap Your Boilerplate (SYB) materials for more details.++Finally, we put together a function that parses, runs our refactoring, then+reprints.++\begin{code}+-- | Parse and refactor, then run the reprinter with the original source and+-- updated AST.+refactor :: String -> String+refactor s =+ runIdentity+ . flip (reprint exprReprinter) s+ . refactorZero+ . parse $ s++\end{code}++Further example: reprinting `After`+-----------------------------------+Using a monadic reprinter, we can write more complex reprintings. This example+from the 2017 paper annotates every variable declaration with its value.+Declarations are evaluated in order, building up a variable-value association+list. The list is stored in the `State` monad, which is passed along through the+reprinting.++\begin{code}+commentPrinter :: Reprinting String (State [(String, Int)])+commentPrinter = catchAll `extQ` decl+ where+ decl (Decl _ s v e) = do+ val <- eval (e :: Expr Bool)+ case val of+ Nothing -> return $ Nothing -- declaration expression referenced a+ -- variable before assignment: no annotation+ Just val -> do+ modify ((v,val) :) -- add mapping to environment+ let msg = " // " <> v <> " = " <> show val+ return $ Just (After, msg, s)++eval :: Expr a -> State [(String, Int)] (Maybe Int)+eval (Plus _ _ e1 e2) = do+ e1' <- eval e1+ e2' <- eval e2+ return $ (+) <$> e1' <*> e2'+eval (Const _ _ i) = return $ Just i+eval (Var _ _ s) = get >>= return . lookup s++refactorComment :: String -> String+refactorComment input =+ flip evalState []+ . flip (reprint commentPrinter) input+ . parse $ input+\end{code}++Unscrapped boilerplate: parser for example language+---------------------------------------------------+The remainder of this module defines a simple monadic parser for the language.+It attempts to generate a position-tagged AST from a `String`.++\begin{code}+parse :: String -> AST Bool+parse s = evalState parseDecl (s, initPosition)++type Parser = State (String, Position)++parseDecl :: Parser (AST Bool)+parseDecl = do+ (xs, p1) <- get+ case xs of+ [] -> return []+ ('\n':xs) -> do+ put (xs, advanceLine p1)+ parseDecl+ _ -> do+ case commentPrefix xs of+ Just (comment, rest) -> do+ put (rest, p1)+ parseDecl+ Nothing -> do+ name <- many isAlpha+ spaces+ char '='+ spaces+ expr <- parseExpr+ p2 <- getPos+ char '\n'+ (xs, p') <- get+ put (xs, advanceLine p')+ rest <- parseDecl+ return $ Decl False (p1, p2) name expr : rest++commentPrefix :: String -> Maybe (String, String)+commentPrefix [] = Nothing+commentPrefix (' ':xs) = commentPrefix xs+commentPrefix ('/':'/':xs) = Just $ break (== '\n') xs+commentPrefix _ = Nothing++parseExpr :: Parser (Expr Bool)+parseExpr = do+ p1 <- getPos+ isPlus <- charP '+'+ if isPlus then do+ char '('+ spaces+ n <- parseExpr+ spaces+ charP ','+ spaces+ m <- parseExpr+ spaces+ char ')'+ p2 <- getPos+ return $ Plus False (p1, p2) n m+ else do+ isVar <- peekChar isAlpha+ if isVar then do+ name <- many isAlpha+ p2 <- getPos+ return $ Var False (p1, p2) name+ else do+ num <- many isDigit+ p2 <- getPos+ return $ Const False (p1, p2) $ read num++-- Some monadic parser helpers (standard)++getPos :: Parser Position+getPos = do+ (_, p) <- get+ return p++many :: (Char -> Bool) -> Parser String+many p = do+ (xs, pos) <- get+ case xs of+ (x:xs) | p x -> do+ put (xs, advanceCol pos)+ ys <- many p+ return $ x : ys+ _ -> return ""++spaces = many (==' ')++char :: Char -> Parser ()+char c = do+ (xs, pos) <- get+ case xs of+ (x:xs') -> if x == c+ then do+ put (xs', advanceCol pos)+ return ()+ else error $ "Expecting " ++ [c] ++ " but got " ++ [x]+ [] -> error $ "Expecting " ++ [c] ++ " but got empty"++charP :: Char -> Parser Bool+charP c = do+ (xs, pos) <- get+ case xs of+ (x:xs') -> if x == c+ then do+ put (xs', advanceCol pos)+ return True+ else return False+ [] -> error $ "Expecting " ++ (c : " but got empty")++peekChar :: (Char -> Bool) -> Parser Bool+peekChar p = do+ (xs, pos) <- get+ case xs of+ (x:_) -> if p x+ then return True+ else return False+\end{code}
+ src/Text/Reprinter/StringLike.hs view
@@ -0,0 +1,65 @@+{-# LANGUAGE TypeFamilies #-}++module Text.Reprinter.StringLike+ ( StringLike(..)+ , IsString(..)+ ) where++import Data.List (uncons)+import Data.String (IsString(..))++import qualified Data.Text as TextStrict+import qualified Data.Text.Lazy as TextLazy+import qualified Data.ByteString.Char8 as BSCStrict+import qualified Data.ByteString.Lazy.Char8 as BSCLazy++-- | Data types that can be used as a list-like structure of 'Char's.+--+-- Clumsy solution to allow parameterising over the input type (Text,+-- ByteString, String), rather than converting to and from an internal concrete+-- type. Only operations required by the reprinting algorithm are included.+-- Where possible, operations are prefilled using presumed-existing instances+-- (any @[Char]@-like should be a monoid and have a @String -> a@).+class (Monoid a, IsString a) => StringLike a where+ slCons :: Char -> a -> a+ slUncons :: a -> Maybe (Char, a)+ slNull :: a -> Bool+ slReverse :: a -> a+ -- | like @unpack@+ slToString :: a -> String++-- same trick as used in IsString, to avoid possible ambiguity issues+instance (a ~ Char) => StringLike [a] where+ slCons = (:)+ slUncons = uncons+ slNull = null+ slReverse = reverse+ slToString = id++instance StringLike TextStrict.Text where+ slCons = TextStrict.cons+ slUncons = TextStrict.uncons+ slNull = TextStrict.null+ slReverse = TextStrict.reverse+ slToString = TextStrict.unpack++instance StringLike TextLazy.Text where+ slCons = TextLazy.cons+ slUncons = TextLazy.uncons+ slNull = TextLazy.null+ slReverse = TextLazy.reverse+ slToString = TextLazy.unpack++instance StringLike BSCStrict.ByteString where+ slCons = BSCStrict.cons+ slUncons = BSCStrict.uncons+ slNull = BSCStrict.null+ slReverse = BSCStrict.reverse+ slToString = BSCStrict.unpack++instance StringLike BSCLazy.ByteString where+ slCons = BSCLazy.cons+ slUncons = BSCLazy.uncons+ slNull = BSCLazy.null+ slReverse = BSCLazy.reverse+ slToString = BSCLazy.unpack
+ tests/hspec/Hspec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
+ tests/hspec/ReprinterSpec.hs view
@@ -0,0 +1,53 @@+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-}++module ReprinterSpec where++import Text.Reprinter+import Text.Reprinter.Example++import Test.Hspec++-- These tests use definitions from the example module 'Text.Reprinter.Example'.++-- Note that 'unlines' appends a newline on to _every_ string, including the+-- last one.+spec :: Spec+spec = do+ describe "refactor" $ do+ it "removes additions of zeroes" $ do+ refactor exPaper `shouldBe` unlines+ [ "x = +(1,2)"+ , "y = x"+ , "// Calculate z"+ , "z = +( 1, +(x ,y) )"+ ]+ it "removes additions of zeroes" $ do+ refactor input_simple `shouldBe` "x = 1\n"++ describe "refactorComment" $ do+ it "appends evaluated variables in comments" $ do+ refactorComment exPaper `shouldBe` unlines+ [ "x = +(1,2) // x = 3"+ , "y = +(x,0) // y = 3"+ , "// Calculate z"+ , "z = +( 1, +(+(0,x) ,y) ) // z = 7"+ ]+ it "appends evaluated variables in comments" $ do+ refactorComment input_simple `shouldBe` "x = +(1,0) // x = 1\n"++input_simple :: String+input_simple = "x = +(1,0)\n"++type AST' = AST Bool++-- Apply zero-refactoring in a loop: deals with +(0, 0) subexpressions+refactorZeroLoop :: AST' -> AST'+refactorZeroLoop = refactorLoop refactorZero++-- Apply the refactoring in a loop until a pass makes no changes.+refactorLoop :: (AST' -> AST') -> AST' -> AST'+refactorLoop refactoring ast+ | refactoring ast == ast = ast+ | otherwise = refactorLoop refactoring (refactoring ast)