1
votes

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
1
The pline parser seems to fail after consuming input, as anyChar also consumes newlines, which is probably not what you wanted. Note that many (opt pline) will eventually lead to an exception, as opt x can succeed without consuming input. To fix this you could skip empty lines as (trailing) whitespace or your empty line parser needs to actually consume a newline. - Stephan Tolksdorf
I think I understand what you mean but I don't know how to fix the parser. I posted my attempt as an update - vidi

1 Answers

4
votes

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