Reputation: 113
I've been trying to learn functional parsing in Haskell, and as an exercise I want to write a simple tuple parser, using the functions between
and sepBy1
in Text.ParserCombinators.ReadP
. Essentially I want tupleAsIntPair :: ReadP (Int, Int)
, which when parsed using ReadP_to_S
takes a string such as "(3,4)"
and returns the pair of integers (3,4)
boxed in a ReadS
. Currently I have:
import Text.ParserCombinators.ReadP
isNumericOrSep :: Char -> Bool
isNumericOrSep = flip elem $ "0123456789-, "
tuplify2 :: [Int] -> (Int, Int)
tuplify2 [x,y] = (x,y)
tupleAsIntPair :: ReadP (Int, Int)
tupleAsIntPair = fmap tuplify2 parsedList
where parsedList = fmap (map read) $ sepBy1 noparens sep
noparens = between open close $ many1 (satisfy isNumericOrSep)
open = char '('
close = char ')'
sep = char ','
However, when I try to run (readP_to_S tupleAsIntPair) "(3,4)"
, I get a no parse error. On the other hand, if I define noparens
globally and run (readP_to_S noparens) "(3,4)"
, I get [("3,4","")]
and if I run (readP_to_S $ sepBy1 (many1 $ satisfy isNumericOrSep) sep) "3,4"
, I get a list [(["3"],",4"),(["3,"],"4"),(["3","4"],""),(["3,4"],"")]
, so at least the parser sepBy1
is doing something, even though I only want the third parsing.
I think I'm composing the two parsers between
and sepBy1
incorrectly, or maybe sepBy1
is not doing what I think it should. How do I actually implement this tuple parser? I would also appreciate any stylistic advice (e.g., tuplify2
bugs me a little).
Upvotes: 1
Views: 1483
Reputation: 1500
The first problem is your definition of isNumericOrSep
. The list you are defining contains ,
as an element. This means that isNumericOrSep
will parse the ,
that you're using as separator, therefor parsedList
will fail, due to the missing sep
.
isNumericOrSep :: Char -> Bool
isNumericOrSep = flip elem $ "0123456789- "
So, wouldn't you want to define isNumeric
? Why do you need the separator in this definition?
The second problem is the order of your combinators. Your description parses sep
between two noparens
, where noparens
is defined as the combination of an opening parenthesis, many (but at least one) numeric values and a closing parenthesis. So, I guess what you really want is to parse a tuple between the opening and closing parenthesis.
tupleAsIntPair :: ReadP (Int,Int)
tupleAsIntPair = fmap tuplify2 parsedList
where
parsedList = fmap (map read) $ between open close $ sepBy1 noparens sep
noparens = many1 (satisfy isNumericOrSep)
open = char '('
close = char ')'
sep = char ','
This yields the following result:
*Main> (readP_to_S tupleAsIntPair) "(3,4)"
[((3,4),"")]
It's possible, that I misinterpret what you're aiming for. But in your introduction, I read that you want to parse one tuple - but maybe you want to parse many tuples?
EDIT:
The parentheses aren't parsed first. Take a look at the definition of between
:
between :: ReadP open -> ReadP close -> ReadP a -> ReadP a
-- ^ @between open close p@ parses @open@, followed by @p@ and finally
-- @close@. Only the value of @p@ is returned.
between open close p = do _ <- open
x <- p
_ <- close
return x
The order is left to right, again. First open
, then the parser p
and at the end close
is parsed.
Upvotes: 3