3

I'm just getting started with FParsec and can't wrap my head around a simple list parser. Given the input

"{ a;b;c d; }"

I want to get the result ['a';'b';'c';'d']

If I do

let baseChars = ['0'..'9'] @ ['A'..'Z'] @ ['a'..'z'] @ ['_'; '-']
let chars : Parser<_> = anyOf baseChars
let nameChars : Parser<_> = anyOf (baseChars @ ['.'])                

let semiColonList p : Parser<_> = sepBy p (pstring ";")
let pList p : Parser<_> = between (pstring "{") (pstring "}") (semiColonList p)

do  """{
    a;b;c;
    d;
}"""
    |> run (parse {
        let! data = pList (spaces >>. many1Chars nameChars)
        return data
    })
    |> printfn "%A"

I get a failure on the last } as it's trying to match that on the nameChars parser before closing the between parser. This feels like there is a simple solution that I'm missing, especially since if I delete the last semi-colon after d all works as expected. Any help appreciated.

[edit] Thanks to Fyodor Soikin the following works:

    let semiColonList p = many (p .>> (pstring ";" >>. spaces))
    let pList p : Parser<_> = between (pstring "{") (pstring "}") (semiColonList p)
    """{
    a;b;c;
    d;
}"""
    |> run (parse {
        let! data = pList (spaces >>. many1Chars nameChars)
        return data
    })
    |> printfn "%A" 
Dylan
  • 1,306
  • 1
  • 11
  • 29
  • `sepBy` doesn't admit a trailing separator. Try `many` instead. – Fyodor Soikin Jul 08 '19 at 19:58
  • @FyodorSoikin not sure what you mean there. I can't do `many notSemiColon` as that will backtrack and then expect the closing } after the first match (a) – Dylan Jul 08 '19 at 20:07
  • I mean make a parser that parses both the value and the semicolon together, and then run `many` of that parser. – Fyodor Soikin Jul 08 '19 at 20:11
  • `semiColonList p = many (p .>> pstring ";")` – Fyodor Soikin Jul 08 '19 at 20:14
  • ah, thanks for the clarification, If you pop it in an answer I'll mark it and give you the cred ;) – Dylan Jul 08 '19 at 20:42
  • 2
    Fyodor Soikin is correct that `sepBy` doesn't allow a trailing separator. BUT there's also `sepEndBy` which allows an optional trailing separator. That might be better for your needs. See https://www.quanttec.com/fparsec/reference/primitives.html#members.sepEndBy for details. – rmunn Jul 08 '19 at 21:28

1 Answers1

4

sepBy does not admit a trailing separator. A parser like sepBy a b is meant to parse input like a b a b a, but your input is like a b a b a b - there's an extra separator b at the end.

What you want to do instead is to parse multiple expressions that are like a b - that will give you your desired shape of the input.

In order to parse one such expression, use the sequencing operator .>>, and in order to parse multiple such pairs, use many:

semiColonList p = many (p .>> pstring ";")
Fyodor Soikin
  • 78,590
  • 9
  • 125
  • 172