|
| 1 | +{-# LANGUAGE BangPatterns #-} |
| 2 | +module Bugs.Bug179 |
| 3 | + ( tests |
| 4 | + ) where |
| 5 | + |
| 6 | +import Control.Applicative |
| 7 | +import Control.Monad |
| 8 | +import Data.Char |
| 9 | +import Data.Functor |
| 10 | +import Test.Tasty ( testGroup, TestTree ) |
| 11 | +import Test.Tasty.HUnit |
| 12 | + |
| 13 | +import qualified Control.Applicative |
| 14 | +import qualified Text.Parsec as P |
| 15 | +import qualified Text.Parsec.String as P |
| 16 | +import qualified Text.Parsec.Pos as P |
| 17 | + |
| 18 | +tests :: TestTree |
| 19 | +tests = testGroup "many try (#179)" |
| 20 | + [ testCase "Parsec" $ examples parser3 |
| 21 | + , testCase "manyDefault" $ examples parser1 |
| 22 | + , testCase "C.Applicative" $ examples parser2 |
| 23 | + ] |
| 24 | + where |
| 25 | + examples p = do |
| 26 | + res1 <- parseString p $ "foo # bar" |
| 27 | + res1 @?= ["foo"] |
| 28 | + |
| 29 | + |
| 30 | + parseString :: P.Parser [String] -> String -> IO [String] |
| 31 | + parseString p input = |
| 32 | + case P.parse p "" input of |
| 33 | + Left err -> assertFailure $ show err |
| 34 | + Right xs -> return xs |
| 35 | + |
| 36 | +skipSpaces :: P.Parser () |
| 37 | +skipSpaces = P.skipMany (P.satisfy isSpace) |
| 38 | + |
| 39 | +lexeme :: P.Parser a -> P.Parser a |
| 40 | +lexeme p = p <* skipSpaces |
| 41 | + |
| 42 | +identifier :: P.Parser String |
| 43 | +identifier = mfilter (not . null) (scan (\s c -> if isAlphaNum c then Just s else Nothing) ()) |
| 44 | + |
| 45 | +parser1 :: P.Parser [String] |
| 46 | +parser1 = skipSpaces *> manyDefault (lexeme identifier) |
| 47 | + |
| 48 | +parser2 :: P.Parser [String] |
| 49 | +parser2 = skipSpaces *> Control.Applicative.many (lexeme identifier) |
| 50 | + |
| 51 | +parser3 :: P.Parser [String] |
| 52 | +parser3 = skipSpaces *> P.many (P.try (lexeme identifier) <|> lexeme hash) |
| 53 | + |
| 54 | +hash :: P.Parser String |
| 55 | +hash = "#" <$ P.char '#' |
| 56 | + |
| 57 | +-- many's default definition |
| 58 | +manyDefault :: Alternative f => f a -> f [a] |
| 59 | +manyDefault v = many_v |
| 60 | + where |
| 61 | + many_v = some_v <|> pure [] |
| 62 | + some_v = liftA2 (:) v many_v |
| 63 | + |
| 64 | +-- | Scan the input text, accumulating characters as long as the scanning |
| 65 | +-- function returns true. |
| 66 | +scan :: (s -> Char -> Maybe s) -- ^ scan function |
| 67 | + -> s -- ^ initial state |
| 68 | + -> P.Parser String |
| 69 | +scan f st = do |
| 70 | + s@P.State{ P.stateInput = inp, P.statePos = pos } <- P.getParserState |
| 71 | + go inp st pos 0 $ \inp' pos' n -> do |
| 72 | + let s' = s{ P.stateInput = inp', P.statePos = pos' } |
| 73 | + P.setParserState s' $> take n inp |
| 74 | + where |
| 75 | + go inp s !pos !n cont |
| 76 | + = case inp of |
| 77 | + [] -> cont inp pos n -- ran out of input |
| 78 | + c : inp' -> |
| 79 | + case f s c of |
| 80 | + Nothing -> cont inp pos n -- scan function failed |
| 81 | + Just s' -> go inp' s' (P.updatePosChar pos c) (n+1) cont |
0 commit comments