Search code examples
f#parsecparser-combinatorsfparsec

fparsec key-value parser fails to parse


I have to write a parser which parses key-value pairs in a file that looks like this:

as235 242kj25klj Pairs:A=a1|B=b1|C=c1

kjlkjlkjlkj Pairs:A=a2|B=b2|C=c2

Note that the lines contain some garbage, the label and then the key-value pairs.

The F# code that I wrote is the following:

#r"FParsec.dll"

open FParsec

let parse keys label =
    let pkey = keys |> Seq.map pstring |> choice

    let pvalue = manyCharsTill anyChar (anyOf "|\n")

    let ppair = pkey .>> (skipChar '=') .>>. pvalue

    let ppairSeq = many ppair

    let pline = skipManyTill anyChar (pstring label) 
                >>. ppairSeq .>> newline

    let pfile = many (opt pline) |>> Seq.choose id

    run pfile 
    >> function
    | Success (result, _, _) -> result
    | Failure (errorMsg, _, _) -> failwith errorMsg

"""
as235 242kj25klj Pairs:A=a1|B=b1|C=c1

lkjlkjlkjlkj Pairs:A=a2|B=b2|C=c2



"""
|> parse ["A";"B";"C"] "Pairs:"
|> List.ofSeq
|> printfn "%A"

The expected result is:

[[("A","a1"); "B","b1"; "C","c1"]
 [("A","a2"); "B","b2"; "C","c2"]]

...but instead I get the following error:

System.Exception: Error: Error in Ln: 8 Col: 1
Note: The error occurred at the end of the input stream.
Expecting: any char or 'Pairs:'

Any ideas about how I can fix this parser?

Thanks!

UPDATE: after Stephan's comment I tried to fix it but without success. This is one of my last attempts which I was expecting to work but it doesn't.

let pkey = keys |> Seq.map pstring |> choice

let pvalue = manyCharsTill anyChar (anyOf "|\n")

let ppair = pkey .>> (skipChar '=') .>>. pvalue

let ppairSeq = manyTill ppair newline

let pnonEmptyLine =
    skipManyTill anyChar (pstring label) 
    >>. ppairSeq
    |>> Some

let pemptyLine = spaces >>. newline >>% None

let pline = pemptyLine <|> pnonEmptyLine

let pfile = manyTill pline eof |>> Seq.choose id

Now the error message is:

Error in Ln: 2 Col: 5

    as235 242kj25klj Pairs:A=a1|B=b1|C=c1

    ^

Expecting: newline

Solution

  • A colleague of mine found the solution and I'm posting here for others who have similar issues. Also the parser is even better because it doesn't need the key set. I uses the left side of '=' as key and the right side as value:

    let parse label str =
        let poperand = manyChars (noneOf "=|\n") 
    
        let ppair = poperand .>> skipChar '=' .>>. poperand
    
        let ppairSeq = sepBy ppair (pchar '|')
    
        let pLineWithPairs = skipManyTill anyChar (pstring label) >>. ppairSeq |>> Some
    
        let pLineWithoutPairs = (restOfLine false) >>% None
    
        let pLogLine = (attempt pLineWithPairs) <|> pLineWithoutPairs
    
        let pfile = sepBy pLogLine newline |>> Seq.choose id
    
        match run pfile str with
        | Success (result, _, _) -> result
        | Failure (errorMsg, _, _) -> sprintf "Error: %s" errorMsg |> failwith