packages feed

paripari-0.3.0.0: test/test.hs

{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE FlexibleContexts #-}
module Main (main) where

import Control.Monad (replicateM, replicateM_)
import Data.ByteString (ByteString)
import Data.Either (isLeft)
import Data.Text (Text)
import GHC.Stack (HasCallStack)
import Prelude hiding (getLine)
import System.Random
import Test.Tasty
import Test.Tasty.HUnit
import Text.PariPari
import Text.PariPari.Internal.Chunk (stringToChunk, asc_a, asc_0, asc_9)
import qualified Data.Char as C
import qualified Data.List.NonEmpty as NE

main :: IO ()
main = defaultMain tests

randomTries :: Int
randomTries = 1000

randomStringLen :: Int
randomStringLen = 1000

-- Only generate valid Unicode characters
randomChar :: IO Char
randomChar = do
  c <- randomIO
  let n = C.ord c
  if n == 0 || (n >= 0xD800 && n <= 0xDFFF) || n > 0x10FFFF then
    randomChar
  else
    pure c

randomString :: IO String
randomString = do
  n <- randomRIO (1, randomStringLen)
  replicateM n randomChar

randomAsciiString :: IO String
randomAsciiString = do
  n <- randomRIO (1, randomStringLen)
  replicateM n (randomRIO (C.chr 1, C.chr 127))

tests :: TestTree
tests = testGroup "Tests"
  [ testGroup "Chunk"
    [ testGroup "Acceptor" $ chunkTests runAcceptor
    , testGroup "Reporter" $ chunkTests runAcceptor
    ]

  , testGroup "Char"
    [ testGroup "Acceptor"
      [ testGroup "Text"       $ charTests @Text       runAcceptor
      , testGroup "ByteString" $ charTests @ByteString runAcceptor
      ]

    , testGroup "Reporter"
      [ testGroup "Text"       $ charTests @Text       runReporter
      , testGroup "ByteString" $ charTests @ByteString runReporter
      ]
    ]
  ]

charTests :: forall k p e. (CharParser k p, CharChunk k, Eq e, Show e, Show k)
          => (forall a. p a -> FilePath -> k -> Either e a) -> [TestTree]
charTests run =
  [ testGroup "CharParser" $
    [ testCase "char" $ do
        ok (char 'a') "abc" 'a'
        ok (char 'a' <* eof) "a" 'a'
        err (char 'b') "abc"
        err (char 'a') ""

    , testCase "char-random" $ replicateM_ randomTries $ do
        s <- randomString
        ok (traverse char s *> eof) s ()

    , testCase "asciiSatisfy" $ do
        ok (asciiSatisfy (== asc_a)) "abc" asc_a
        ok (asciiSatisfy (== asc_a) <* eof) "a" asc_a
        err (asciiSatisfy (== asc_0)) "abc"
        err (asciiSatisfy (== asc_0)) ""

        -- because of sentinel
        err (asciiSatisfy (== 0)) "\0"
        err (asciiSatisfy (== 0)) ""

    , testCase "asciiSatisfy-random" $ replicateM_ randomTries $ do
        s <- randomAsciiString
        ok (traverse (asciiSatisfy . (==) . fromIntegral . C.ord) s *> eof) s ()

    , testCase "asciiByte" $ do
        ok (asciiByte asc_a) "abc" asc_a
        ok (asciiByte asc_a <* eof) "a" asc_a
        ok (asciiByte 127 <* eof) "\x7F" 127
        err (asciiByte asc_0) "abc"
        err (asciiByte asc_0) ""

    , testCase "asciiByte-random" $ replicateM_ randomTries $ do
        s <- randomAsciiString
        ok (traverse (asciiByte . fromIntegral . C.ord) s *> eof) s ()
    ]

  , testGroup "Char Combinators"
    [ testCase "satisfy" $ do
        ok (satisfy (== 'a')) "abc" 'a'
        ok (satisfy (== 'a') <* eof) "a" 'a'
        err (satisfy (== 'b')) "abc"
        err (satisfy (== 'a')) ""

        -- because of sentinel
        err (satisfy (== '\0')) "\0"
        err (satisfy (== '\0')) ""

    , testCase "satisfy-random" $ replicateM_ randomTries $ do
        s <- randomString
        ok (traverse (satisfy . (==)) s *> eof) s ()

    , testCase "string" $ do
        ok (string "ab") "abc" (stringToChunk "ab")
        err (string "bc") "abc"
        err (string "ab") ""

    , testCase "string-random" $ replicateM_ randomTries $ do
        s <- randomString
        ok (string s <* eof) s (stringToChunk s)

    , testCase "anyChar" $ do
        ok anyChar "abc" 'a'
        ok (anyChar <* eof) "a" 'a'
        err anyChar ""

    , testCase "anyChar-random" $ replicateM_ randomTries $ do
        s <- randomString
        ok (traverse (const anyChar) s *> eof) s ()

    , testCase "anyAsciiByte" $ do
        ok anyAsciiByte "abc" asc_a
        ok (anyAsciiByte <* eof) "a" asc_a
        err anyAsciiByte ""
        err anyAsciiByte "\x80"

    , testCase "anyAsciiByte-random" $ replicateM_ randomTries $ do
        s <- randomAsciiString
        ok (traverse (const anyAsciiByte) s *> eof) s ()

    , testCase "notChar" $ do
        ok (notChar 'b') "abc" 'a'
        ok (notChar 'b' <* eof) "a" 'a'
        err (notChar 'a') "a"
        err (notChar 'a') ""

    , testCase "char'" $ do
        ok (char' 'a') "a" 'a'
        ok (char' 'a' <* eof) "a" 'a'
        ok (char' 'a') "A" 'A'
        ok (char' 'A') "a" 'a'
        ok (char' 'A') "A" 'A'
        ok (char' '9') "9" '9'
        err (char' 'a') "b"
        err (char' 'a') ""

    , testCase "alphaNumChar" $ do
        ok (alphaNumChar <* eof) "a" 'a'
        ok alphaNumChar "9" '9'
        err alphaNumChar "_"
        err alphaNumChar ""

    , testCase "digitChar" $ do
        ok (digitChar 10 <* eof) "9" '9'
        ok (digitChar 36) "z" 'z'
        err (digitChar 2) "2"
        err (digitChar 2) ""

    , testCase "letterChar" $ do
        ok (letterChar <* eof) "a" 'a'
        err letterChar "9"
        err letterChar "_"
        err letterChar ""

    , testCase "lowerChar" $ do
        ok (lowerChar <* eof) "a" 'a'
        err lowerChar "A"
        err lowerChar "9"
        err lowerChar "_"
        err lowerChar ""

    , testCase "upperChar" $ do
        ok (upperChar <* eof) "A" 'A'
        err upperChar "a"
        err upperChar "9"
        err upperChar "_"
        err upperChar ""

    , testCase "symbolChar" $ do
        ok (symbolChar <* eof) "+" '+'
        err symbolChar "a"
        err symbolChar "."
        err symbolChar ""

    , testCase "punctuationChar" $ do
        ok (punctuationChar <* eof) "." '.'
        err punctuationChar "+"
        err punctuationChar "a"
        err punctuationChar ""

    , testCase "spaceChar" $ do
        ok (spaceChar <* eof) " " ' '
        ok (spaceChar <* eof) "\n" '\n'
        ok (spaceChar <* eof) "\r" '\r'
        ok (spaceChar <* eof) "\t" '\t'
        err spaceChar "a"
        err spaceChar ""

    , testCase "asciiChar" $ do
        ok (asciiChar <* eof) "a" 'a'
        ok (asciiChar <* eof) "\x7F" '\x7F'
        err asciiChar "\x80"
        err asciiChar ""

    , testCase "categoryChar" $ do
        ok (categoryChar C.UppercaseLetter <* eof) "A" 'A'
        err (categoryChar C.UppercaseLetter) "a"
        err (categoryChar C.UppercaseLetter) ""

    , testCase "digitByte" $ do
        ok (digitByte 2 <* eof) "0" asc_0
        ok (digitByte 10 <* eof) "9" asc_9
        err (digitByte 10) "\0"
        err (digitByte 10) ""

    , testCase "skipChars" $ do
        ok (skipChars 0) "" ()
        ok (skipChars 3 <* eof) "abc" ()
        ok (skipChars 3 <* char 'b') "aaab" ()
        err (skipChars 1) ""
        err (skipChars 2) "a"

    , testCase "takeChars" $ do
        ok (takeChars 0) "" (stringToChunk "")
        ok (takeChars 3 <* eof) "abc" (stringToChunk "abc")
        ok (takeChars 3 <* char 'b') "aaab" (stringToChunk "aaa")
        err (takeChars 1) ""
        err (takeChars 2) "a"

    , testCase "skipCharsWhile" $ do
        ok (skipCharsWhile (== 'a')) "" ()
        ok (skipCharsWhile (== 'a')) "b" ()
        ok (skipCharsWhile (== 'a') *> eof) "aaa" ()
        ok (skipCharsWhile (== 'a') *> char 'b') "aaab" 'b'

    , testCase "takeCharsWhile" $ do
        ok (takeCharsWhile (== 'a')) "" (stringToChunk "")
        ok (takeCharsWhile (== 'a')) "b" (stringToChunk "")
        ok (takeCharsWhile (== 'a') <* eof) "aaa" (stringToChunk "aaa")
        ok (takeCharsWhile (== 'a') <* char 'b') "aaab" (stringToChunk "aaa")

    , testCase "skipCharsWhile1" $ do
        err (skipCharsWhile1 (== 'a')) ""
        err (skipCharsWhile1 (== 'a')) "b"
        ok (skipCharsWhile1 (== 'a') *> eof) "aaa" ()
        ok (skipCharsWhile1 (== 'a') *> char 'b') "aaab" 'b'

    , testCase "takeCharsWhile1" $ do
        err (takeCharsWhile1 (== 'a')) ""
        err (takeCharsWhile1 (== 'a')) "b"
        ok (takeCharsWhile1 (== 'a') <* eof) "aaa" (stringToChunk "aaa")
        ok (takeCharsWhile1 (== 'a') <* char 'b') "aaab" (stringToChunk "aaa")
    ]

  , testGroup "Fraction Combinators"
    [ testCase "fractionDec" $ do
        okFraction (fractionDec (pure ()) <* eof) "1.23" (Right (123, 10, -2))
        okFraction (fractionDec (pure ()) <* eof) "99e0" (Right (99, 10, 0))
        okFraction (fractionDec (pure ()) <* eof) "123.45" (Right (12345, 10, -2))
        okFraction (fractionDec (pure ()) <* eof) "00123." (Right (123, 10, 0))
        okFraction (fractionDec (pure ()) <* eof) "456.000" (Right (456000, 10, -3))

        okFraction (fractionDec (pure ()) <* eof) "987e-5" (Right (987, 10, -5))
        okFraction (fractionDec (pure ()) <* eof) "987.e-123" (Right (987, 10, -123))
        okFraction (fractionDec (pure ()) <* eof) "987.654e-67" (Right (987654, 10, -70))
        okFraction (fractionDec (pure ()) <* eof) "987.654000e-7" (Right (987654000, 10, -13))
        okFraction (fractionDec (pure ()) <* eof) "000987.654000e-7" (Right (987654000, 10, -13))

        okFraction (fractionDec (pure ()) <* eof) "987e+5" (Right (987, 10, 5))
        okFraction (fractionDec (pure ()) <* eof) "987.e+123" (Right (987, 10, 123))
        okFraction (fractionDec (pure ()) <* eof) "987.654e+67" (Right (987654, 10, 64))
        okFraction (fractionDec (pure ()) <* eof) "987.654000e+7" (Right (987654000, 10, 1))
        okFraction (fractionDec (pure ()) <* eof) "000987.654000e+7" (Right (987654000, 10, 1))

        okFraction (fractionDec (pure ()) <* eof) "987e5" (Right (987, 10, 5))
        okFraction (fractionDec (pure ()) <* eof) "987.e123" (Right (987, 10, 123))
        okFraction (fractionDec (pure ()) <* eof) "987.654e67" (Right (987654, 10, 64))
        okFraction (fractionDec (pure ()) <* eof) "987.654000e7" (Right (987654000, 10, 1))
        okFraction (fractionDec (pure ()) <* eof) "000987.654000e7" (Right (987654000, 10, 1))

        okFraction (fractionDec (pure ())) "123" (Left 123)

        errFraction (fractionDec (pure ())) "123e"
        errFraction (fractionDec (pure ())) ""
        errFraction (fractionDec (pure ())) "abc"
    ]

  , testGroup "Integer Combinators"
    [ testCase "decimal" $ do
        ok @Integer (decimal <* eof) "0123" 123
        ok @Integer (decimal <* eof) "1234567890" 1234567890
        ok @Integer (decimal <* string "abc" <* eof) "123abc" 123
        err @Integer decimal "abc"
        err @Integer decimal "-1"
        err @Integer decimal ""

    , testCase "octal" $ do
        ok @Integer (octal <* eof) "0123" 0o123
        ok @Integer (octal <* eof) "12345670" 0o12345670
        ok @Integer (octal <* string "abc" <* eof) "123abc" 0o123
        err @Integer octal "8abc"
        err @Integer octal "-1"
        err @Integer octal ""

    , testCase "hexadecimal" $ do
        ok @Integer (hexadecimal <* eof) "0123" 0x123
        ok @Integer (hexadecimal <* eof) "123456789aBcDeF0" 0x123456789ABCDEF0
        ok @Integer (hexadecimal <* string "xyz" <* eof) "123xyz" 0x123
        err @Integer hexadecimal "gabc"
        err @Integer hexadecimal "-1"
        err @Integer hexadecimal ""

    , testCase "integer" $ do
        ok @Integer (integer (char '_') 10) "1_2_3" 123
        ok @Integer (integer (char '_') 10 <* char '_') "1_2_3_" 123
        ok @Integer (integer (optional $ char '_') 10) "123_456_789" 123456789
        err @Integer (integer (pure ()) 10) "abc"
        err @Integer (integer (char '_') 10) "_1_2_3"
        err @Integer (integer (pure ()) 10) "-1"
        err @Integer (integer (pure ()) 10) ""

        ok @Integer (integer (pure ()) 2) "101" 5
        ok @Integer (integer (pure ()) 7) "321" 162
        ok @Integer (integer (pure ()) 36) "XyZ" 44027

    , testCase "integer'" $ do
        ok @(Integer, Int) (integer' (char '_') 10) "1_2_3" (123, 3)
        ok @(Integer, Int) (integer' (char '_') 10 <* char '_') "1_2_3_" (123, 3)
        ok @(Integer, Int) (integer' (optional $ char '_') 10) "123_456_789" (123456789, 9)
        err @(Integer, Int) (integer' (pure ()) 10) "abc"
        err @(Integer, Int) (integer' (char '_') 10) "_1_2_3"
        err @(Integer, Int) (integer' (pure ()) 10) "-1"
        err @(Integer, Int) (integer' (pure ()) 10) ""

        ok @(Integer, Int) (integer' (pure ()) 10) "0123" (123, 4)
        ok @(Integer, Int) (integer' (pure ()) 10) "01230" (1230, 5)
        ok @(Integer, Int) (integer' (pure ()) 10) "000" (0, 3)

        ok @(Integer, Int) (integer' (pure ()) 2) "101" (5, 3)
        ok @(Integer, Int) (integer' (pure ()) 7) "321" (162, 3)
        ok @(Integer, Int) (integer' (pure ()) 36) "XyZ" (44027, 3)

    , testCase "signed" $ do
        ok @Integer (signed decimal) "-123" (-123)
        ok @Integer (signed decimal) "+123" 123
        err @Integer (signed decimal) "- 123"
        err @Integer (signed decimal) "+ 123"
        err @Integer (signed decimal) ""

    , testCase "digit" $ do
        ok (digit 2) "1" 1
        ok (digit 10) "7" 7
        ok (digit 36) "Z" 35
        err (digit 10) "a"
        err (digit 10) ""
    ]
  ]
  where
  ok :: (Eq a, Show a, HasCallStack) => p a -> String -> a -> Assertion
  ok p i o = run p "filename" (stringToChunk @k i) @?= Right o
  err :: (Eq a, Show a, HasCallStack) => p a -> String -> Assertion
  err p i = assertBool "err" $ isLeft $ run p "filename" (stringToChunk @k i)
  errFraction :: HasCallStack => p (Either Integer (Integer, Int, Integer)) -> String -> Assertion
  errFraction = err
  okFraction :: HasCallStack => p (Either Integer (Integer, Int, Integer)) -> String -> Either Integer (Integer, Int, Integer) -> Assertion
  okFraction = ok

chunkTests :: forall p e. (ChunkParser Text p, Eq e, Show e)
          => (forall a. p a -> FilePath -> Text -> Either e a) -> [TestTree]
chunkTests run =
  [ testGroup "ChunkParser"
    [ testCase "getFile" $ do
        ok getFile "" "filename"

    , testCase "getPos" $ do
        ok getPos "" (Pos 1 1)
        ok (element 'a' *> getPos) "abc" (Pos 1 2)
        ok (element 'a' *> element '\n' *> getPos) "a\nb" (Pos 2 1)
        ok (chunk "a\n" *> getPos) "a\nb" (Pos 1 3) -- chunk must not contain newlines!
        ok (element 'a' *> element '\n' *> element 'b' *> getPos) "a\nb" (Pos 2 2)

    , testCase "getRefPos" $ do
        ok getRefPos "" (Pos 1 1)
        ok (element 'a' *> getRefPos) "abc" (Pos 1 1)
        ok (element 'a' *> element '\n' *> withRefPos getRefPos) "a\nb" (Pos 2 1)
        ok (element 'a' *> element '\n' *> element 'b' *> withRefPos getRefPos) "a\nb" (Pos 2 2)
        ok (element 'a' *> element '\n' *> withRefPos (element 'b' *> getRefPos)) "a\nb" (Pos 2 1)

    , testCase "notFollowedBy" $ do
        ok (element 'a' <* notFollowedBy (element 'c')) "abc" 'a'
        err (element 'a' <* notFollowedBy (element 'b')) "abc"
        ok (element 'a' *> notFollowedBy (chunk "bd") *> element 'b') "abc" 'b'
        err (element 'a' *> notFollowedBy (chunk "bc")) "abc"
        ok (element 'a' *> notFollowedBy (element 'c') *> getPos) "abc" (Pos 1 2)

    , testCase "lookAhead" $ do
        ok (lookAhead (element 'a')) "abc" 'a'
        ok (lookAhead (element 'a') *> getPos) "abc" (Pos 1 1)
        ok (lookAhead (chunk "ab") *> getPos) "abc" (Pos 1 1)
        err (lookAhead (element 'b')) "abc"
        err (lookAhead (chunk "bd")) "abc"
        err (lookAhead (element 'a')) ""

    , testCase "failWith" $
        err (failWith (ECombinator "empty") :: p ()) "abc"

    , testCase "eof" $ do
        ok eof "" ()
        ok (chunk "abc" *> eof) "abc" ()
        err eof "abc"
        err eof "\0"
        err (chunk "ab" *> eof) "abc"

    , testCase "label" $ do
        ok (label "blub" $ element 'a') "abc" 'a'
        err (label "blub" $ element 'b') "abc"
        ok (element 'a' <?> "blub") "abc" 'a'

    , testCase "hidden" $ do
        ok (hidden $ element 'a') "abc" 'a'
        err (hidden $ element 'b') "abc"

    , testCase "commit" $ do
        ok (commit $ element 'a') "abc" 'a'
        err (commit $ element 'b') "abc"

    , testCase "element" $ do
        ok (element 'a') "abc" 'a'
        ok (element 'a' <* eof) "a" 'a'
        err (element 'b') "abc"
        err (element 'a') ""

        -- sentinel
        ok (element '\0') "\0" '\0'
        ok (element '\0' <* eof) "\0" '\0'
        err (element '\0') ""

    , testCase "chunk" $ do
        ok (chunk "ab") "abc" "ab"
        err (chunk "bc") "abc"
        err (chunk "ab") ""

    , testCase "asChunk" $ do
        ok (asChunk (void $ chunk "ab")) "abc" "ab"
        ok (asChunk (void $ anyElement *> anyElement)) "abc" "ab"
        ok (asChunk (skipCount 2 anyElement)) "abc" "ab"
        err (asChunk (void $ chunk "bc")) "abc"
        err (asChunk (void $ chunk "ab")) ""
    ]

  , testGroup "Position Combinators"
    [ testCase "getLine" $ do
        ok getLine "" 1
        ok (element 'a' *> getLine) "abc" 1
        ok (element 'a' *> element '\n' *> getLine) "a\nb" 2
        ok (element 'a' *> element '\n' *> element 'b' *> getLine) "a\nb" 2

    , testCase "getRefLine" $ do
        ok getRefLine "" 1
        ok (element 'a' *> getRefLine) "abc" 1
        ok (element 'a' *> element '\n' *> withRefPos getRefLine) "a\nb" 2
        ok (element 'a' *> element '\n' *> element 'b' *> withRefPos getRefLine) "a\nb" 2
        ok (element 'a' *> element '\n' *> withRefPos (element 'b' *> getRefLine)) "a\nb" 2

    , testCase "getColumn" $ do
        ok getColumn "" 1
        ok (element 'a' *> getColumn) "abc" 2
        ok (element 'a' *> element '\n' *> getColumn) "a\nb" 1
        ok (element 'a' *> element '\n' *> element 'b' *> getColumn) "a\nb" 2

    , testCase "getRefColumn" $ do
        ok getRefColumn "" 1
        ok (element 'a' *> getRefColumn) "abc" 1
        ok (element 'a' *> element '\n' *> withRefPos getRefColumn) "a\nb" 1
        ok (element 'a' *> element '\n' *> element 'b' *> withRefPos getRefColumn) "a\nb" 2
        ok (element 'a' *> element '\n' *> withRefPos (element 'b' *> getRefColumn)) "a\nb" 1

    , testCase "withPos" $ do
        ok (withPos $ element 'a') "abc" (Pos 1 1, 'a')
        ok (element 'a' *> withPos (element 'b')) "abc" (Pos 1 2, 'b')
        ok (element 'a' *> element '\n' *> withPos (element 'b')) "a\nb" (Pos 2 1, 'b')

    , testCase "withSpan" $ do
        ok (withSpan $ chunk "ab") "abc" ((Pos 1 1, Pos 1 3), "ab")
        ok (element 'a' *> withSpan (chunk "bcd")) "abcde" ((Pos 1 2, Pos 1 5), "bcd")
        ok (element 'a' *> element '\n' *> withSpan (chunk "bcd")) "a\nbcde" ((Pos 2 1, Pos 2 4), "bcd")
    ]

  , testGroup "Element Combinators"
    [ testCase "elementSatisfy" $ do
        ok (elementSatisfy (== 'a')) "abc" 'a'
        ok (elementSatisfy (== 'a') <* eof) "a" 'a'
        err (elementSatisfy (== 'b')) "abc"
        err (elementSatisfy (== 'a')) ""

        -- sentinel
        ok (elementSatisfy (== '\0')) "\0" '\0'
        ok (elementSatisfy (== '\0') <* eof) "\0" '\0'
        err (elementSatisfy (== '\0')) ""

    , testCase "anyElement" $ do
        ok anyElement "abc" 'a'
        err anyElement ""

    , testCase "notElement" $ do
        ok (notElement 'b') "abc" 'a'
        err (notElement 'a') "a"
        err (notElement 'a') ""

    , testCase "takeElementsWhile" $ do
        ok (takeElementsWhile (== 'a')) "" ""
        ok (takeElementsWhile (== 'a')) "b" ""
        ok (takeElementsWhile (== 'a') <* eof) "aaa" "aaa"
        ok (takeElementsWhile (== 'a') <* element 'b') "aaab" "aaa"

    , testCase "skipElements" $ do
        ok (skipElements 0) "" ()
        ok (skipElements 3 <* eof) "abc" ()
        ok (skipElements 3 <* element 'b') "aaab" ()
        err (skipElements 1) ""
        err (skipElements 2) "a"

    , testCase "takeElements" $ do
        ok (takeElements 0) "" ""
        ok (takeElements 3 <* eof) "abc" "abc"
        ok (takeElements 3 <* element 'b') "aaab" "aaa"
        err (takeElements 1) ""
        err (takeElements 2) "a"

    , testCase "skipElementsWhile" $ do
        ok (skipElementsWhile (== 'a')) "" ()
        ok (skipElementsWhile (== 'a')) "b" ()
        ok (skipElementsWhile (== 'a') *> eof) "aaa" ()
        ok (skipElementsWhile (== 'a') *> element 'b') "aaab" 'b'

    , testCase "skipElementsWhile1" $ do
        err (skipElementsWhile1 (== 'a')) ""
        err (skipElementsWhile1 (== 'a')) "b"
        ok (skipElementsWhile1 (== 'a') *> eof) "aaa" ()
        ok (skipElementsWhile1 (== 'a') *> element 'b') "aaab" 'b'

    , testCase "takeElementsWhile1" $ do
        err (takeElementsWhile1 (== 'a')) ""
        err (takeElementsWhile1 (== 'a')) "b"
        ok (takeElementsWhile1 (== 'a') <* eof) "aaa" "aaa"
        ok (takeElementsWhile1 (== 'a') <* element 'b') "aaab" "aaa"
    ]

  , testGroup "MonadFail"
    [ testCase "fail" $ do
        err (failWith (ECombinator "empty") :: p ()) "abc"
    ]

  , testGroup "Alternative"
    [ testCase "(<|>)" $ do
        ok (element 'b' <|> element 'a' <|> element 'c') "abc" 'a'
        ok (chunk "abd" <|> chunk "abc" <|> chunk "abe") "abcdef" "abc"

    , testCase "empty" $ do
        err (empty :: p ()) "abc"
        err (empty :: p ()) ""
    ]

  , testGroup "Basic Combinators"
    [ testCase "optional" $ do
        ok (optional (element 'a')) "abc" (Just 'a')
        ok (optional (element 'b')) "abc" Nothing
        ok (optional (element 'a')) "" Nothing

    , testCase "some" $ do
        ok (some (element 'a')) "abc" (NE.fromList "a")
        ok (some (element 'a')) "aabc" (NE.fromList "aa")
        err (some (element 'b')) "abc"
        err (some (element 'a')) ""

    , testCase "many" $ do
        ok (many (element 'a')) "abc" "a"
        ok (many (element 'a')) "aabc" "aa"
        ok (many (element 'b')) "abc" ""
        ok (many (element 'a')) "" ""
    ]
  ]
  where
  ok :: (Eq a, Show a, HasCallStack) => p a -> Text -> a -> Assertion
  ok p i o = run p "filename" i @?= Right o
  err :: (Eq a, Show a, HasCallStack) => p a -> Text -> Assertion
  err p i = assertBool "err" $ isLeft $ run p "filename" i

{-
TODO:

Instances:
Semigroup
Monoid
Functor
Applicative
Monad
MonadPlus
Alternative

Additional combinators provided by parser-combinators.
They are also tested by megaparsec:
endBy1
someTill
sepBy1
sepEndBy1
between
choice
count
count'
eitherP
endBy
manyTill
option
sepBy
sepEndBy
skipMany
skipSome
skipCount
skipManyTill
skipSomeTill

Identation:
align
indented
line
linefold

Fraction:
fraction
fractionHex

CharParser:
scan

ChunkParser:
elementScan

Element Combinators:
scanElementsWhile
scanElementsWhile1

Char Combinators:
scanCharsWhile
scanCharsWhile1
-}