Skip to content

Commit 7b12bec

Browse files
committed
Add many-try test
1 parent 647c570 commit 7b12bec

File tree

3 files changed

+84
-0
lines changed

3 files changed

+84
-0
lines changed

parsec.cabal

+1
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ test-suite parsec-tests
114114
Bugs.Bug6
115115
Bugs.Bug9
116116
Bugs.Bug35
117+
Bugs.Bug179
117118
Features
118119
Features.Feature80
119120
Features.Feature150

test/Bugs.hs

+2
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ import qualified Bugs.Bug2
99
import qualified Bugs.Bug6
1010
import qualified Bugs.Bug9
1111
import qualified Bugs.Bug35
12+
import qualified Bugs.Bug179
1213

1314
bugs :: [TestTree]
1415
bugs = [ Bugs.Bug2.main
1516
, Bugs.Bug6.main
1617
, Bugs.Bug9.main
1718
, Bugs.Bug35.main
19+
, Bugs.Bug179.tests
1820
]

test/Bugs/Bug179.hs

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
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

Comments
 (0)