JHF
JHF

Reputation: 113

A tuple parser in Haskell

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

Answers (1)

ichistmeinname
ichistmeinname

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

Related Questions