replace-megaparsec (empty) → 1.0.0.0
raw patch · 9 files changed
+773/−0 lines, 9 filesdep +Cabaldep +basedep +bytestringsetup-changed
Dependencies added: Cabal, base, bytestring, megaparsec, replace-megaparsec, text
Files
- CHANGELOG.md +5/−0
- LICENSE +30/−0
- README.md +216/−0
- Setup.hs +2/−0
- replace-megaparsec.cabal +63/−0
- src/Replace/Megaparsec.hs +239/−0
- tests/TestByteString.hs +77/−0
- tests/TestString.hs +69/−0
- tests/TestText.hs +72/−0
+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# Revision history for replace-megaparsec++## 1.1.0.0 -- 2019-08-24++* First version. Megaparsec.
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2019, James Brock++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of James Brock nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,216 @@+# replace-megaparsec++__replace-megaparsec__ is for finding text patterns, and also editing and+replacing the found patterns.+This activity is traditionally done with regular expressions,+but __replace-megaparsec__ uses+[__Megaparsec__](http://hackage.haskell.org/package/megaparsec)+parsers instead for the pattern matching.++__replace-megaparsec__ can be used in the same sort of “pattern capture”+or “find all” situations in which one would use Python+[`re.findall`](https://docs.python.org/3/library/re.html#re.findall)+or+Perl [`m//`](https://perldoc.perl.org/functions/m.html),+or+Unix [`grep`](https://www.gnu.org/software/grep/).++__replace-megaparsec__ can be used in the same sort of “stream editing”+or “search-and-replace” situations in which one would use Python+[`re.sub`](https://docs.python.org/3/library/re.html#re.sub),+or+Perl [`s///`](https://perldoc.perl.org/functions/s.html),+or Unix+[`sed`](https://www.gnu.org/software/sed/manual/html_node/The-_0022s_0022-Command.html),+or+[`awk`](https://www.gnu.org/software/gawk/manual/gawk.html).++## Why would we want to do pattern matching and substitution with parsers instead of regular expressions?++* Parsers have a nicer syntax than+ [regular expressions](https://en.wikipedia.org/wiki/Regular_expression),+ which are notoriously+ [difficult to read](https://en.wikipedia.org/wiki/Write-only_language).++* Regular expressions can do “group capture” on sections of the matched+ pattern, but they can only return stringy lists of the capture groups. Parsers+ can construct typed data structures based on the capture groups, guaranteeing+ no disagreement between the pattern rules and the rules that we're using+ to build data structures based on the pattern matches.+ + For example, consider+ scanning a string for numbers. A lot of different things can look like a number,+ and can have leading plus or minus signs, or be in scientific notation, or+ have commas, or whatever. If we try to parse all of the numbers out of a string+ using regular expressions, then we have to make sure that the regular expression+ and the string-to-number conversion function agree about exactly what is+ and what isn't a numeric string. We can get into an awkward situation in which+ the regular expression says it has found a numeric string but the+ string-to-number conversion function fails. A typed parser will perform both+ the pattern match and the conversion, so it will never be in that situation.++* Regular expressions are only able to pattern-match+ [regular](https://en.wikipedia.org/wiki/Chomsky_hierarchy#The_hierarchy)+ grammers.+ Parsers are able pattern-match with context-free grammers, and+ even context-sensitive or Turing-complete grammers, if needed. See below for+ an example of lifting a `Parser` into a `State` monad for context-sensitive+ pattern-matching.++## Examples++Try the examples in `ghci` by+running `cabal v2-repl` in the `replace-megaparsec/`+root directory.++The examples depend on these imports.++```haskell+import Replace.Megaparsec+import Text.Megaparsec+import Text.Megaparsec.Char+import Text.Megaparsec.Char.Lexer+```++### Parsing with `sepCap` family of parser combinators++The following examples show how to match a pattern to a string of text+and deconstruct the string of text by separating it into sections+which match the pattern, and sections which don't match.++#### Pattern-match, capture only the parsed result++Separate the input string into sections which can be parsed as a hexadecimal+number with a prefix `"0x"`, and sections which can't.++```haskell+let hexparser = string "0x" >> hexadecimal :: Parsec Void String Integer+parseTest (sepCap hexparser) "0xA 000 0xFFFF"+```+```haskell+[Right 10,Left " 000 ",Right 65535]+```++#### Pattern match, capture only the matched text++Just get the strings sections which match the hexadecimal parser, throw away+the parsed number.++```haskell+let hexparser = string "0x" >> hexadecimal :: Parsec Void String Integer+parseTest (findAll hexparser) "0xA 000 0xFFFF"+```+```haskell+[Right "0xA",Left " 000 ",Right "0xFFFF"]+```++#### Pattern match, capture the matched text and the parsed result++Capture the parsed hexadecimal number, as well as the string section which+parses as a hexadecimal number.++```haskell+let hexparser = string "0x" >> hexadecimal :: Parsec Void String Integer+parseTest (findAllCap hexparser) "0xA 000 0xFFFF"+```+```haskell+[Right ("0xA",10),Left " 000 ",Right ("0xFFFF",65535)]+```++#### Pattern match, capture only the locations of the matched patterns++Find all of the sections of the stream which match+the `Text.Megaparsec.Char.space1` parser (a string of whitespace).+Print a list of the offsets of the beginning of every pattern match.++```haskell+import Data.Either+let spaceoffset = getOffset <* space1 :: Parsec Void String Int+parseTest (return . rights =<< sepCap spaceoffset) " a b "+```+```haskell+[0,2,5]+```++### Edit text strings by running parsers with `streamEdit`++The following examples show how to search for a pattern in a string of text+and then edit the string of text to substitute in some replacement text+for the matched patterns.++#### Pattern match and replace with a constant++Replace all carriage-return-newline instances with newline.++```haskell+streamEdit crlf (const "\n") "1\r\n\r\n2"+```+```haskell+"1\n\n2"+```++#### Pattern match and edit the matches++Replace alphabetic characters with the next character in the alphabet.++```haskell+streamEdit (some letterChar) (fmap succ) "HAL 9000"+```+```haskell+"IBM 9000"+```++#### Pattern match and edit the matches++Find all of the string sections *`s`* which can be parsed as a+hexadecimal number *`r`*,+and if *`r≤16`*, then replace *`s`* with a decimal number.++```haskell+let hexparser = string "0x" >> hexadecimal :: Parsec Void String Integer+streamEdit (match hexparser) (\(s,r) -> if r <= 16 then show r else s) "0xA 000 0xFFFF"+```+```haskell+"10 000 0xFFFF"+```++#### Context-sensitive pattern match and edit the matches++Capitalize the third letter in a string. The `capthird` parser searches for+individual letters, and it needs to remember how many times it has run so+that it can match successfully only on the third time that it finds a letter.+To enable the parser to remember how many times it has run, we'll+compose the parser with a `State` monad from+the `mtl` package. (Run in `ghci` with `cabal v2-repl -b mtl`).++```haskell+import qualified Control.Monad.State.Strict as MTL+import Control.Monad.State.Strict (get, put, evalState)+import Data.Char (toUpper)++let capthird :: ParsecT Void String (MTL.State Int) String+ capthird = do+ x <- letterChar+ i <- get+ put (i+1)+ if i==3 then return [x] else empty++flip evalState 1 $ streamEditT capthird (return . fmap toUpper) "a a a a a"+```+```haskell+"a a A a a"+```++## Alternatives++<http://hackage.haskell.org/package/regex>++<http://hackage.haskell.org/package/pipes-parse>++<http://hackage.haskell.org/package/stringsearch>++<http://hackage.haskell.org/package/substring-parser>++<http://hackage.haskell.org/package/pcre-utils>++<http://hackage.haskell.org/package/template>
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ replace-megaparsec.cabal view
@@ -0,0 +1,63 @@+name: replace-megaparsec+version: 1.0.0.0+cabal-version: 1.18+synopsis: Stream editing with parsers+homepage: https://github.com/jamesdbrock/replace-megaparsec+bug-reports: https://github.com/jamesdbrock/replace-megaparsec/issues+license: BSD3+license-file: LICENSE+author: James Brock+maintainer: jamesbrock@gmail.com+build-type: Simple+category: Parsing+description:++ Stream editing and find-and-replace with Megaparsec monadic parser+ combinators.++extra-doc-files: README.md+ , CHANGELOG.md++source-repository head+ type: git+ location: https://github.com/jamesdbrock/replace-megaparsec.git++library+ hs-source-dirs: src+ -- rely on megaparsec for version bounds+ build-depends: base+ , megaparsec+ default-language: Haskell2010+ exposed-modules: Replace.Megaparsec++test-suite test-string+ type: detailed-0.9+ test-module: TestString+ hs-source-dirs: tests+ default-language: Haskell2010+ build-depends: base >=4.12 && < 5.0+ , replace-megaparsec+ , megaparsec+ , Cabal++test-suite test-text+ type: detailed-0.9+ test-module: TestText+ hs-source-dirs: tests+ default-language: Haskell2010+ build-depends: base >=4.12 && < 5.0+ , replace-megaparsec+ , megaparsec+ , Cabal+ , text++test-suite test-bytestring+ type: detailed-0.9+ test-module: TestByteString+ hs-source-dirs: tests+ default-language: Haskell2010+ build-depends: base >=4.12 && < 5.0+ , replace-megaparsec+ , megaparsec+ , Cabal+ , bytestring
+ src/Replace/Megaparsec.hs view
@@ -0,0 +1,239 @@+-- |+-- Module : Replace.Megaparsec+--+-- __Replace.Megaparsec__ is for finding text patterns, and also editing and+-- replacing the found patterns.+-- This activity is traditionally done with regular expressions,+-- but __Replace.Megaparsec__ uses "Text.Megaparsec" parsers instead for+-- the pattern matching.+--+-- __Replace.Megaparsec__ can be used in the same sort of “pattern capture”+-- or “find all” situations in which one would use Python+-- <https://docs.python.org/3/library/re.html#re.findall re.findall>,+-- or Perl+-- <https://perldoc.perl.org/functions/m.html m//>,+-- or Unix+-- <https://www.gnu.org/software/grep/ grep>.+--+-- __Replace.Megaparsec__ can be used in the same sort of “stream editing”+-- or “search-and-replace” situations in which one would use Python+-- <https://docs.python.org/3/library/re.html#re.sub re.sub>,+-- or Perl+-- <https://perldoc.perl.org/functions/s.html s///>,+-- or Unix+-- <https://www.gnu.org/software/sed/manual/html_node/The-_0022s_0022-Command.html sed>,+-- or+-- <https://www.gnu.org/software/gawk/manual/gawk.html awk>.+--+-- See the __replace-megaparsec__ package README for usage examples.++{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Replace.Megaparsec+ (+ -- * Parser combinator+ sepCap+ , findAll+ , findAllCap++ -- * Running parser+ , streamEditT+ , streamEdit+ )+where+++import Data.Void+import Data.Maybe+import Data.Bifunctor+import Data.Functor.Identity+import Data.Proxy+import Data.Foldable+import Control.Exception (throw)+import Data.Typeable+import Control.Monad++import Text.Megaparsec++-- |+-- == Separate and capture+--+-- Parser combinator to find all of the non-overlapping ocurrences+-- of the pattern @sep@ in a text stream. Separate the stream into sections:+--+-- * sections which can parsed by the pattern @sep@ will be captured as+-- matching sections in 'Right'+-- * non-matching sections of the stream will be captured in 'Left'.+--+-- This parser will always consume its entire input and can never fail.+-- If there are no pattern matches, then the entire input stream will be+-- returned as a non-matching 'Left' section.+--+-- The pattern matching parser @sep@ will not be allowed to succeed without+-- consuming any input. If we allow the parser to match a zero-width pattern,+-- then it can match the same zero-width pattern again at the same position+-- on the next iteration, which would result in an infinite number of+-- overlapping pattern matches. So, for example, the+-- pattern @many digitChar@, which can match zero occurences of a digit,+-- will be treated by @sepCap@ as @some digitChar@, and required to match+-- at least one digit.+--+-- This @sepCap@ parser combinator is the basis for all of the other+-- features of this module. It is similar to the @sep*@ family of functions+-- found in+-- <http://hackage.haskell.org/package/parser-combinators/docs/Control-Monad-Combinators.html parser-combinators>+-- and+-- <http://hackage.haskell.org/package/parsers/docs/Text-Parser-Combinators.html parsers>+-- but, importantly, it returns the parsed result of the @sep@ parser instead+-- of throwing it away.+--+sepCap+ :: forall e s m a. (MonadParsec e s m)+ => m a -- ^ The pattern matching parser @sep@+ -> m [Either (Tokens s) a]+sepCap sep = (fmap.fmap) (first $ tokensToChunk (Proxy::Proxy s))+ $ fmap sequenceLeft+ $ many $ fmap Right (try $ consumeSome sep) <|> fmap Left anySingle+ where+ sequenceLeft :: [Either l r] -> [Either [l] r]+ sequenceLeft = foldr consLeft []+ where+ consLeft :: Either l r -> [Either [l] r] -> [Either [l] r]+ consLeft (Left l) ((Left ls):xs) = (Left (l:ls)):xs+ consLeft (Left l) xs = (Left [l]):xs+ consLeft (Right r) xs = (Right r):xs+ -- If sep succeeds and consumes 0 input tokens, we must force it to fail,+ -- otherwise infinite loop+ consumeSome p = do+ offset1 <- getOffset+ x <- p+ offset2 <- getOffset+ when (offset1 == offset2) empty+ return x++-- |+-- == Find all occurences, parse and capture pattern matches+--+-- Parser combinator for finding all occurences of a pattern in a stream.+--+-- Will call 'sepCap' with the 'Text.Megaparsec.match' combinator so that+-- the text which matched the pattern parser @sep@ will be returned in+-- the 'Right' sections, along with the result of the parse of @sep@.+--+-- @+-- findAllCap sep = 'sepCap' ('Text.Megaparsec.match' sep)+-- @+findAllCap+ :: MonadParsec e s m+ => m a -- ^ The pattern matching parser @sep@+ -> m [Either (Tokens s) (Tokens s, a)]+findAllCap sep = sepCap (match sep)++-- |+-- == Find all occurences+--+-- Parser combinator for finding all occurences of a pattern in a stream.+--+-- Will call 'sepCap' with the 'Text.Megaparsec.match' combinator and+-- return the text which matched the pattern parser @sep@ in+-- the 'Right' sections.+--+-- @+-- findAll sep = (fmap.fmap) ('Data.Bifunctor.second' fst) $ 'sepCap' ('Text.Megaparsec.match' sep)+-- @+findAll+ :: MonadParsec e s m+ => m a -- ^ The pattern matching parser @sep@+ -> m [Either (Tokens s) (Tokens s)]+findAll sep = (fmap.fmap) (second fst) $ sepCap (match sep)+++-- |+-- == Stream editor+--+-- Also can be considered “find-and-replace”. Finds all+-- of the sections of the stream which match the pattern @sep@, and replaces+-- them with the result of the @editor@ function.+--+-- This function is not a “parser combinator,” it is+-- a “way to run a parser”, like 'Text.Megaparsec.parse'+-- or 'Text.Megaparsec.runParserT'.+--+-- === Access the matched section of text in the editor+--+-- If you want access to the matched string in the @editor@ function,+-- then combine the pattern parser @sep@ with 'Text.Megaparsec.match', like+--+-- @+-- let editor (matchString,parseResult) = return matchString+-- in streamEditT ('Text.Megaparsec.match' sep) editor inputstring+-- @+--+-- === Type constraints+--+-- The type of the stream of text that is input must+-- be @Stream s@ such that @Tokens s ~ s@, because we want+-- to output the same type of stream that was input. That requirement is+-- satisfied for all the 'Text.Megaparsec.Stream' instances included+-- with "Text.Megaparsec":+-- "Data.Text",+-- "Data.Text.Lazy",+-- "Data.Bytestring",+-- "Data.Bytestring.Lazy",+-- and "Data.String".+--+-- We need the @Monoid s@ instance so that we can @mappend@ the output+-- stream.+--+-- We need @Typeable s@ and @Show s@ for 'Control.Exception.throw'. In theory+-- this function should never throw an exception, because it only throws+-- when the 'sepCap' parser fails, and the 'sepCap' parser+-- can never fail. If this function ever throws, please report that as a bug.+--+-- === Underlying monad context+--+-- Both the parser @sep@ and the @editor@ function are run in the underlying+-- monad context.+--+-- If you want to do 'IO' operations in the @editor@ function or the+-- parser @sep@, then run this in 'IO'.+--+-- If you want the @editor@ function or the parser @sep@ to remember some state,+-- then run this in a stateful monad.+streamEditT+ :: forall s m a. (Stream s, Monad m, Monoid s, Tokens s ~ s, Show s, Show (Token s), Typeable s)+ => ParsecT Void s m a+ -- ^ The parser @sep@ for the pattern of interest.+ -> (a -> m s)+ -- ^ The @editor@ function. Takes a parsed result of @sep@+ -- and returns a new stream section for the replacement.+ -> s+ -- ^ The input stream of text to be edited.+ -> m s+streamEditT sep editor input = do+ runParserT (sepCap sep) "" input >>= \case+ (Left err) -> throw err -- sepCap can never fail, but if it does, throw+ (Right r) -> fmap fold $ traverse (either return editor) r++++-- |+-- == Pure stream editor+--+-- Pure version of 'streamEditT'.+streamEdit+ :: forall s a. (Stream s, Monoid s, Tokens s ~ s, Show s, Show (Token s), Typeable s)+ => Parsec Void s a+ -- ^ The parser @sep@ for the pattern of interest.+ -> (a -> s)+ -- ^ The @editor@ function. Takes a parsed result of @sep@+ -- and returns a new stream section for the replacement.+ -> s+ -- ^ The input stream of text to be edited.+ -> s+streamEdit sep editor = runIdentity . streamEditT sep (Identity . editor)+
+ tests/TestByteString.hs view
@@ -0,0 +1,77 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-}++module TestByteString ( tests ) where++import Distribution.TestSuite+import Replace.Megaparsec+import Text.Megaparsec+import Text.Megaparsec.Byte+import Text.Megaparsec.Byte.Lexer+import Data.Void+import qualified Data.ByteString as BS+import Data.ByteString (ByteString)+import Data.ByteString.Internal (c2w)+import GHC.Word++type Parser = Parsec Void ByteString++tests :: IO [Test]+tests = return+ [ Test $ runParserTest "findAll upperChar"+ (findAllCap (upperChar :: Parser Word8))+ ("aBcD" :: ByteString)+ [Left "a", Right ("B", c2w 'B'), Left "c", Right ("D", c2w 'D')]+ -- check that sepCap can progress even when parser consumes nothing+ -- and succeeds.+ , Test $ runParserTest "zero-consumption parser"+ (sepCap (many (upperChar :: Parser Word8)))+ ("aBcD" :: ByteString)+ [Left "a", Right [c2w 'B'], Left "c", Right [c2w 'D']]+ , Test $ runParserTest "scinum"+ (sepCap scinum)+ ("1E3")+ ([Right (1,3)])+ , Test $ runParserTest "getOffset"+ (sepCap offsetA)+ ("xxAxx")+ ([Left "xx", Right 2, Left "xx"])+ , Test $ runParserTest "monad fail"+ (sepCap (fail "" :: Parser ()))+ ("xxx")+ ([Left "xxx"])+ , Test $ runParserTest "read fail"+ (sepCap (return (read "a" :: Int) :: Parser Int))+ ("a")+ ([Left "a"])+ ]++runParserTest name p input expected = TestInstance+ { run = do+ case runParser p "" input of+ Left e -> return (Finished $ Fail $ show e)+ Right output ->+ if (output == expected)+ then return (Finished Pass)+ else return (Finished $ Fail+ $ show output ++ " ≠ " ++ show expected)+ , name = name+ , tags = []+ , options = []+ , setOption = \_ _ -> Left "no options supported"+ }++scinum :: Parser (Double, Integer)+scinum = do+ -- This won't parse mantissas that contain a decimal point,+ -- but if we use the Text.Megaparsec.Byte.Lexer.float, then it consumes+ -- the "E" and the exponent. Whatever, doesn't really matter for this test.+ m <- fromIntegral <$> decimal+ string "E"+ e <- decimal+ return (m, e)+++offsetA :: Parser Int+offsetA = getOffset <* string "A"+
+ tests/TestString.hs view
@@ -0,0 +1,69 @@+{-# LANGUAGE FlexibleContexts #-}++module TestString ( tests ) where++import Distribution.TestSuite+import Replace.Megaparsec+import Text.Megaparsec+import Text.Megaparsec.Char+import Text.Megaparsec.Char.Lexer+import Data.Void++type Parser = Parsec Void String++tests :: IO [Test]+tests = return+ [ Test $ runParserTest "findAll upperChar"+ (findAllCap (upperChar :: Parser Char))+ ("aBcD" :: String)+ [Left "a", Right ("B", 'B'), Left "c", Right ("D", 'D')]+ -- check that sepCap can progress even when parser consumes nothing+ -- and succeeds.+ , Test $ runParserTest "zero-consumption parser"+ (sepCap (many (upperChar :: Parser Char)))+ ("aBcD" :: String)+ [Left "a", Right "B", Left "c", Right "D"]+ , Test $ runParserTest "scinum"+ (sepCap scinum)+ ("1E3")+ ([Right (1,3)])+ , Test $ runParserTest "getOffset"+ (sepCap offsetA)+ ("xxAxx")+ ([Left "xx", Right 2, Left "xx"])+ , Test $ runParserTest "monad fail"+ (sepCap (fail "" :: Parser ()))+ ("xxx")+ ([Left "xxx"])+ , Test $ runParserTest "read fail"+ (sepCap (return (read "a" :: Int) :: Parser Int))+ ("a")+ ([Left "a"])+ ]++runParserTest name p input expected = TestInstance+ { run = do+ case runParser p "" input of+ Left e -> return (Finished $ Fail $ show e)+ Right output ->+ if (output == expected)+ then return (Finished Pass)+ else return (Finished $ Fail+ $ show output ++ " ≠ " ++ show expected)+ , name = name+ , tags = []+ , options = []+ , setOption = \_ _ -> Left "no options supported"+ }++scinum :: Parser (Double, Integer)+scinum = do+ m <- some digitChar+ string "E"+ e <- some digitChar+ return (read m, read e)+++offsetA :: Parser Int+offsetA = getOffset <* string "A"+
+ tests/TestText.hs view
@@ -0,0 +1,72 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-}++module TestText ( tests ) where++import Distribution.TestSuite+import Replace.Megaparsec+import Text.Megaparsec+import Text.Megaparsec.Char+import Text.Megaparsec.Char.Lexer+import Data.Void+import qualified Data.Text as T+import Data.Text (Text)++type Parser = Parsec Void Text++tests :: IO [Test]+tests = return+ [ Test $ runParserTest "findAll upperChar"+ (findAllCap (upperChar :: Parser Char))+ ("aBcD" :: Text)+ [Left "a", Right ("B", 'B'), Left "c", Right ("D", 'D')]+ -- check that sepCap can progress even when parser consumes nothing+ -- and succeeds.+ , Test $ runParserTest "zero-consumption parser"+ (sepCap (many (upperChar :: Parser Char)))+ ("aBcD" :: Text)+ [Left "a", Right "B", Left "c", Right "D"]+ , Test $ runParserTest "scinum"+ (sepCap scinum)+ ("1E3")+ ([Right (1,3)])+ , Test $ runParserTest "getOffset"+ (sepCap offsetA)+ ("xxAxx")+ ([Left "xx", Right 2, Left "xx"])+ , Test $ runParserTest "monad fail"+ (sepCap (fail "" :: Parser ()))+ ("xxx")+ ([Left "xxx"])+ , Test $ runParserTest "read fail"+ (sepCap (return (read "a" :: Int) :: Parser Int))+ ("a")+ ([Left "a"])+ ]++runParserTest name p input expected = TestInstance+ { run = do+ case runParser p "" input of+ Left e -> return (Finished $ Fail $ show e)+ Right output ->+ if (output == expected)+ then return (Finished Pass)+ else return (Finished $ Fail+ $ show output ++ " ≠ " ++ show expected)+ , name = name+ , tags = []+ , options = []+ , setOption = \_ _ -> Left "no options supported"+ }++scinum :: Parser (Double, Integer)+scinum = do+ m <- some digitChar+ string "E"+ e <- some digitChar+ return (read m, read e)+++offsetA :: Parser Int+offsetA = getOffset <* string "A"+