Search code examples
parsinghaskellparsec

Parsec parsing and separating different structures


Let's say I have different parsers p1, ..., pk. I want to define a function pk :: Parser ([t1], ..., [tk]) where pi :: Parser ti.

That will parse a collection of strings (one after the other) that match any of p1...pk and separate them in the corresponding lists. Assume for simplicity that none of the strings matches two parsers.

I managed to do it, but I am really struggling to find an elegant way (preferably using many or any other builtin parsec parser).


Solution

  • The first step is to turn each of your parsers into parser of the big type:

    p1 :: Parser t1
    p2 :: Parser t2
    p3 :: Parser t3
    p1 = undefined
    p2 = undefined
    p3 = undefined
    
    p1', p2', p3' :: Parser ([t1], [t2], [t3])
    p1' = fmap (\x -> ([x], [], [])) p1
    p2' = fmap (\x -> ([], [x], [])) p2
    p3' = fmap (\x -> ([], [], [x])) p3
    

    Now, we repeatedly choose from these last parsers, and concatenate the results at the end:

    parser :: Parser ([t1], [t2], [t3])
    parser = fmap mconcat . many . choice $ [p1', p2', p3']
    

    There are Monoid instances for tuples up to size five; beyond that, you can either use nested tuples or a more appropriate data structure.