Diana Vrabie
Diana Vrabie

Reputation: 45

Parsing bencode string with Parsec

I'm trying to use Parsec to parse bencode strings. The format is 3:abc (number of characters, :, actual string).

I am given the following Parsec functions:

char :: Char -> Parser Char
char c = satisfies (== c) ("character " ++ show c)

-- | Chain two parses, discarding the value of the first parser
pThen :: Parser a -> Parser b -> Parser b
pThen pa pb = parser inner
  where
    inner input =
      case runParser pa input of
        Success (_, rest) -> runParser pb rest
        Error err -> Error err

-- | Chain two parsers, feeding both the result and the remaining input from the first parser to the second parser.
with :: Parser a -> (a -> Parser b) -> Parser b
with pa f = parser inner
  where
    inner input =
      case runParser pa input of
        Success (a, rest) ->
          case runParser (f a) rest of
            Success (b, remaining) -> success b remaining
            Error err -> Error err
        Error err -> Error err

-- | Parse a number
number :: Parser Int
number = pMap read (some digit) `expecting` "number"

-- | Parser that consumes a fixed number of characters.
take :: Int -> Parser String
take nr = parser $ \input ->
  uncurry success (L.splitAt nr input)

The function signature is string :: Parser String

I tried doing it like this: string = P.with P.number (P.pThen P.char ':' P.take), with the idea being thet I take the integer returned by P.number, process and discard the :, then feed the integer into P.take to consume the required number of characters. What am I doing wrong?

Upvotes: 1

Views: 138

Answers (1)

K. A. Buhr
K. A. Buhr

Reputation: 50819

Note that this isn't parsec. This is some weird homebrew parsing library. I gather this is homework and your professor has provided a set of parsing primitives for you to use? A bunch of definitions seem to be missing (e.g., the definition of Parser itself, plus satisfies, pMap, etc.), but I think I can guess what they do.

Your attempt:

string = P.with P.number (P.pThen P.char ':' P.take)

has all the right components, but the expression P.pThen P.char ':' P.take is pretty broken. It's passing three arguments to pThen, which only takes two. You want some parentheses in there:

P.pThen (P.char ':') P.take

This still isn't going to work because P.take needs an integer argument. You can't just hope that p.with will magically know where to supply it, so you'll want something explicit like:

P.pThen (P.char ':') (P.take n)

You want to use this expression with P.with, something like:

P.with P.number (... P.pThen (P.char ':') (P.take n) ...)

but you want the part in parenthesis to be a function that takes the argument supplied by P.with from the P.number parser and binds that to variable n so it can be passed to P.take. You can do this with a lambda expression, so the following should work:

string = P.with P.number (\n -> P.pThen (P.char ':') (P.take n))

As an alternative syntax that does exactly the same thing, P.pThen can be surrounded by backticks and used as binary "operator", like so:

string = P.with P.number (\n -> P.char ':' `P.pThen` P.take n)

The name of the pThen function has been chosen to make this syntax look nice -- "first parse this, pThen parse that".

Upvotes: 0

Related Questions