Tarmil posted the straightforward solution.
Here's another variant, which doesn't need a newline at the beginning and which checks for a following identifier only at the end of lines:
let id = manyMinMaxSatisfyL 2 2 isUpper "ID" .>> pchar ' '
let text =
stringsSepBy (restOfLine true)
((notFollowedBy ((id >>% ()) <|> skipNewline <|> eof)) >>% "\n")
let parser = many (id .>>. text)
If you wanted to optimize the second parser used with the stringsSepBy
combinator, you could replace it with the following version:
let notFollowedByIdOrEmptyLineOrEof : Parser<string,_> =
fun stream ->
let cs = stream.Peek2()
let c0, c1 = cs.Char0, cs.Char1
if c0 = '\r' || c0 = '\n' || c0 = EOS
|| (isUpper c0 && isUpper c1 && stream.Peek(2) = ' ')
then Reply(Error, NoErrorMessages)
else Reply("\n")
let text2 = stringsSepBy (restOfLine true)
notFollowedByIdOrEmptyLineOrEof