packages feed

gigaparsec-0.2.0.0: test/Text/Gigaparsec/CharTests.hs

{-# LANGUAGE OverloadedLists #-}
module Text.Gigaparsec.CharTests where

import Test.Tasty
import Test.Tasty.HUnit

import Control.Monad
import Data.Char
import Data.Map.Strict qualified as Map

import Text.Gigaparsec
import Text.Gigaparsec.Char
import Text.Gigaparsec.Internal.Require
import Text.Gigaparsec.Internal.Test

tests :: TestTree
tests = testGroup "Char"
  [ satisfyTests
  , charTests
  , stringTests
  , satisfyMapTests
  , oneOfTests
  , noneOfTests
  , stringsTests
  , trieTests
  ]

satisfyTests :: TestTree
satisfyTests = testGroup "satisfy should"
  [ testCase "be pure if it cannot read" do
      pureParse (satisfy (const False))
      pureParseWith (satisfy (== 'a')) ""
      pureParseWith (satisfy (== 'a')) "b"
  , testCase "be impure otherwise" do impureParseWith (satisfy (== 'a')) "a"
  , testCase "return the parsed character" do
      testParse item "a" @?= Success 'a'
      testParse item "ba" @?= Success 'b'
      testParse item "\NUL" @?= Success '\NUL'
  , testCase "fail otherwise" do
      ensureFails (satisfy (== 'b')) ""
      ensureFails item ""
      ensureFails (satisfy (== 'b')) "a"
  ]

charTests :: TestTree
charTests = testGroup "char should"
  [ testCase "be pure if it cannot read" do
      pureParseWith (char 'a') ""
      pureParseWith (char 'a') "b"
  , testCase "be impure otherwise" do impureParseWith (char 'a') "a"
  , testCase "return the parsed character" do testParse (char 'a') "a" @?= Success 'a'
  , testCase "fail otherwise" do
      ensureFails (char 'a') ""
      ensureFails (char 'a') "b"
  ]

stringTests :: TestTree
stringTests = testGroup "string should"
  [ testCase "reject the empty string" (throws @RequirementUnsatisfied (testParse (string "") "")) -- don't ask why `string ""` doesn't work all the time
  , testCase "be pure if it cannot read at all" do
      pureParseWith (string "abc") ""
      pureParseWith (string "abc") "123"
  , testCase "be impure if there is a partial read" do impureParseWith (string "abc") "abd"
  , testCase "be impure if full read" do impureParseWith (string "abc") "abc"
  , testCase "return the parsed string" do testParse (string "123") "123" @?= Success "123"
  , testCase "fail otherwise" do
      ensureFails (string "123") "124"
      ensureFails (string "123") "12"
  ]

satisfyMapTests :: TestTree
satisfyMapTests = testGroup "satisfyMap should"
  [ testCase "fail when invalid" do
      ensureFails p ""
      ensureFails p "a"
  , testCase "succeed performing the mapping otherwise" do
      testParse p "4" @?= Success 4
      testParse p "9" @?= Success 9
  ]
  where p = satisfyMap (\c -> digitToInt c <$ guard (isDigit c))

oneOfTests :: TestTree
oneOfTests = testGroup "oneOf should"
  [ testCase "fail when given no characters" do
      pureParse p
      ensureFails p ""
      ensureFails p "a"
  , testCase "act like character given one character" do
      pureParseWith q ""
      pureParseWith q "b"
      impureParseWith q "a"
      testParse q "a" @?= Success 'a'
      ensureFails q ""
      ensureFails q "b"
  , testCase "parse within a contiguous range" do
      pureParseWith r ""
      pureParseWith r "a"
      forM_ @[] ['0'..'9'] $ \c -> do
        impureParseWith r (pure c)
        testParse r (pure c) @?= Success c
      ensureFails r "a"
      ensureFails r "\NUL"
      ensureFails r ":"
      ensureFails r "/"
  , testCase "parse any other sets" do
      pureParseWith s ""
      pureParseWith s "a"
      forM_ @[] ['.', ';', ',', ':'] $ \c -> do
        impureParseWith s (pure c)
        testParse s (pure c) @?= Success c
      ensureFails s "a"
      ensureFails s "\NUL"
  ]
  where p = oneOf []
        q = oneOf ['a']
        r = oneOf ['0' .. '9']
        s = oneOf ['.', ';', ',', ':']

noneOfTests :: TestTree
noneOfTests = testGroup "oneOf should"
  [ testCase "act like item when given no characters" do
      pureParseWith p ""
      ensureFails p ""
      testParse p "a" @?= Success 'a'
      testParse p "\ACK" @?= Success '\ACK'
  , testCase "accept all but a specific character" do
      pureParseWith q ""
      pureParseWith q "a"
      impureParseWith q "b"
      testParse q "5" @?= Success '5'
      ensureFails q ""
      ensureFails q "a"
  , testCase "parse within a contiguous range" do
      pureParseWith r ""
      impureParseWith r "a"
      forM_ @[] ['0'..'9'] $ \c -> do
        pureParseWith r (pure c)
        ensureFails r (pure c)
      testParse r "a" @?= Success 'a'
      testParse r "\NUL" @?= Success '\NUL'
      testParse r ":" @?= Success ':'
      testParse r "/" @?= Success '/'
  , testCase "parse any other sets" do
      pureParseWith s ""
      impureParseWith s "a"
      forM_ @[] ['.', ';', ',', ':'] $ \c -> do
        pureParseWith s (pure c)
        ensureFails s (pure c)
      testParse s "a" @?= Success 'a'
      testParse s "\NUL" @?= Success '\NUL'
  ]
  where p = noneOf []
        q = noneOf ['a']
        r = noneOf ['0' .. '9']
        s = noneOf ['.', ';', ',', ':']

stringsTests :: TestTree
stringsTests = testGroup "strings should"
  [ testCase "reject any empty strings" do throws @RequirementUnsatisfied (strings ["abc", "323", ""])
  , testCase "have longest match behaviour" do
      testParse p "hello" @?= Success "hello"
      testParse p "hell" @?= Success "hell"
      testParse p "he" @?= Success "h"
      testParse p "123" @?= Success "123"
      testParse p "124" @?= Success "1"
  , testCase "reject anything outside of the set" do
      ensureFails p "543"
      ensureFails p "good"
  ]
  where p = strings ["hell", "hello", "h", "123", "1"]

trieTests :: TestTree
trieTests = testGroup "trie should"
  [ testCase "reject any empty strings" do throws @RequirementUnsatisfied (trie' ["" --> unit])
  , testCase "have longest match behaviour" do
      testParse p "hello" @?= Success "hello"
      testParse p "hell" @?= Success "hell"
      testParse p "h" @?= Success "h"
      testParse p "he" @?= Success "h"
      testParse p "hi" @?= Success "hi"
      testParse p "good" @?= Success "good"
      testParse p "goodby" @?= Success "good"
      testParse p "goodbye" @?= Success "goodbye"
  , testCase "reject anything outside of the set" do
      ensureFails p "543"
      ensureFails p "god"
  ]
  where p = trie'
              [ "h" --> atomic (trie' [ "ell" --> trie' ["o" --> pure "hello"]
                                              <|> pure "hell"
                                      , "i"   --> pure "hi"
                                      ])
                    <|> pure "h"
              , "good" --> atomic (trie' ["bye" --> pure "goodbye"])
                       <|> pure "good"
              ]
        infix 0 -->
        (-->) = (,)
        trie' = trie . Map.fromList