Search code examples
parsinghaskellparsec

Haskell Parsec parsing chunks of string to map values


I'm trying to parse a string, like

AA{A}{END}}

with given map: fromList [("{",43),("}",44),("{END}",255),("A",65)],

so that desired output is: [65,65,43,65,44,255,44]

It looks like searching for longest prefix in map in straight Haskell, but how do I parse it with Parsec? It is similar to this question, but here I should return Word8 value instead of string parsed by 'choice'.


Solution

  • You first need to write a parser that takes as input a (Word8, Int) tuple and returns a Parser Word8 value.

    keyValParser :: (String, Word8) -> Parser Word8
    keyValParser (s,v) = try (string s) >> return v
    

    The above parser uses try against the string parser because Parsec has the nasty habit of consuming matching characters even on fail. If the try (string s) portion is successful, it returns the Word8 value from the tuple.

    Now you can map your input list against that keyValParser to build the parser you're looking for:

    parser :: [(String, Word8)] -> Parser [Word8]
    parser = many . choice . map keyValParser
    

    Running this parser using parseTest in GHCi yields:

    > let lookup = [("{",43),("}",44),("{END}",255),("A",65)]
    > parseTest (parser lookup) "AA{A}{END}}"
    [65,65,43,65,44,43]
    

    But wait! That's not quite right. The problem now is that choice stops at the first matching parser, and the string {END} first matches { and thus returns 43. You can fix this by ordering the lookup values by longest text first using sortBy (flip $ comparing (length . fst)):

    parser :: [(String, Word8)] -> Parser [Word8]
    parser = many . choice . map keyValParser . sortBy (flip $ comparing (length . fst))
    

    Now you get the right results:

    > let lookup = [("{",43),("}",44),("{END}",255),("A",65)]
    > parseTest (parser lookup) "AA{A}{END}}"
    [65,65,43,65,44,255,44]