3
votes

I'm trying to parse an input integer string in haskell using parsec. The string might either be in decimal, octal or hexadecimal. The base is specified by a prefix of #d, #o or #x for decimal, octal and hexadecimal respectively, which is then followed by the integer. If no prefix is specified, the base is assumed to be 10. Here's what I've done so far:

parseNumber = do x <- noPrefix <|> withPrefix
             return x
          where noPrefix = many1 digit
                withPrefix = do char '#'
                                prefix <- oneOf "dox"
                                return $ case prefix of
                                    'd' -> many1 digit
                                    'o' -> fmap (show . fst . head . readOct) (many1 octDigit)
                                    'x' -> fmap (show . fst . head . readHex) (many1 hexDigit)

However, this isn't compiling and is failing with type errors. I don't quite really understand the type error and would just like help in general with this problem. Any alternative ways to solve it will also be appreciated.

Thank you for your time and help. :)

EDIT: Here's the error I've been getting.

2
Can you please post the errors you’re getting? - bdesham

2 Answers

2
votes

In Megaparsec—a modern fork of Parsec, this problem is non-existent (from documentation of hexadecimal):

Parse an integer in hexadecimal representation. Representation of hexadecimal number is expected to be according to Haskell report except for the fact that this parser doesn't parse “0x” or “0X” prefix. It is responsibility of the programmer to parse correct prefix before parsing the number itself.

For example you can make it conform to Haskell report like this:

hexadecimal = char '0' >> char' 'x' >> L.hexadecimal

So in your case you can just define (note how it's more readable):

import Data.Void
import Text.Megaparsec
import Text.Megaparsec.Char
import qualified Text.Megaparsec.Char.Lexer as L

type Parser = Parsec Void String

parseNumber :: Parser Integer
parseNumber = choice
  [ L.decimal
  , (string  "o#" *> L.octal)       <?> "octal integer"
  , (string  "d#" *> L.decimal)     <?> "decimal integer"
  , (string  "h#" *> L.hexadecimal) <?> "hexadecimal integer" ]

Let's try the parser (note quality of error messages):

λ> parseTest' (parseNumber <* eof) ""
1:1:
  |
1 | <empty line>
  | ^
unexpected end of input
expecting decimal integer, hexadecimal integer, integer, or octal integer
λ> parseTest' (parseNumber <* eof) "d#3"
3
λ> parseTest' (parseNumber <* eof) "h#ff"
255
λ> parseTest' (parseNumber <* eof) "o#8"
1:3:
  |
1 | o#8
  |   ^
unexpected '8'
expecting octal integer
λ> parseTest' (parseNumber <* eof) "o#77"
63
λ> parseTest' (parseNumber <* eof) "190"
190

Full-disclosure: I'm the author/maintainer of Megaparsec.

1
votes

You have two slight errors:

One indention error (return x must be indented compared to do) and the parsers in withPrefix must not be returned, since they will return their results anyway.

parseNumber = do x <- noPrefix <|> withPrefix
                 return x
      where noPrefix = many1 digit
            withPrefix = do char '#'
                            prefix <- oneOf "dox"
                            case prefix of
                                'd' -> many1 digit
                                'o' -> fmap (show . fst . head . readOct) (many1 octDigit)
                                'x' -> fmap (show . fst . head . readHex) (many1 hexDigit)

This should work