Using ParserResult
Asked Answered
H

2

6

The example code below appears to work nicely:

open FParsec
let capitalized : Parser<unit,unit> =(asciiUpper >>. many asciiLower >>. eof)
let inverted : Parser<unit,unit> =(asciiLower >>. many asciiUpper >>. eof)
let capsOrInvert =choice [capitalized;inverted]

You can then do:

run capsOrInvert "Dog";;
run capsOrInvert "dOG";;

and get a success or:

run capsOrInvert "dog";;

and get a failure.

Now that I have a ParserResult, how do I do things with it? For example, print the string backwards?

Hartill answered 15/11, 2016 at 20:57 Comment(0)
S
8

There are several notable issues with your code.

First off, as noticed in @scrwtp's answer, your parser returns unit. Here's why: operator (>>.) returns only the result returned by the right inner parser. On the other hand, (.>>) would return the result of a left parser, while (.>>.) would return a tuple of both left and right ones.

So, parser1 >>. parser2 >>. eof is essentially (parser1 >>. parser2) >>. eof.
The code in parens completely ignores the result of parser1, and the second (>>.) then ignores the entire result of the parser in parens. Finally, eof returns unit, and this value is being returned.

You may need some meaningful data returned instead, e.g. the parsed string. The easiest way is:

let capitalized = (asciiUpper .>>. many asciiLower .>> eof)

Mind the operators.
The code for inverted can be done in a similar manner.


This parser would be of type Parser<(char * char list), unit>, a tuple of first character and all the remaining ones, so you may need to merge them back. There are several ways to do that, here's one:

let mymerge (c1: char, cs: char list) = c1 :: cs // a simple cons
let pCapitalized = capitalized >>= mymerge

The beauty of this code is that your mymerge is a normal function, working with normal char's, it knows nothing about parsers or so. It just works with the data, and (>>=) operator does the rest.

Note, pCapitalized is also a parser, but it returns a single char list.


Nothing stops you from applying further transitions. As you mentioned printing the string backwards:

let pCapitalizedAndReversed =
    capitalized
    >>= mymerge
    >>= List.rev

I have written the code in this way for purpose. In different lines you see a gradual transition of your domain data, still within the paradigm of Parser. This is an important consideration, because any subsequent transition may "decide" that the data is bad for some reason and raise a parsing exception, for example. Or, alternatively, it may be merged with other parser.

As soon as your domain data (a parsed-out word) is complete, you extract the result as mentioned in another answer.


A minor note. choice is superfluous for only two parsers. Use (<|>) instead. From experience, careful choosing parser combinators is important because a wrong choice deep inside your core parser logic can easily make your parsers dramatically slow.
See FParsec Primitives for further details.

Sommerville answered 16/11, 2016 at 0:18 Comment(0)
B
8

ParserResult is a discriminated union. You simply match the Success and Failure cases.

let r = run capsOrInvert "Dog"

match r with
| Success(result, _, _)   -> printfn "Success: %A" result
| Failure(errorMsg, _, _) -> printfn "Failure: %s" errorMsg

But this is probably not what you find tricky about your situation.

The thing about your Parser<unit, unit> type is that the parsed value is of type unit (the first type argument to Parser). What this means is that this parser doesn't really produce any sensible output for you to use - it can only tell you whether it can parse a string (in which case you get back a Success ((), _, _) - carrying the single value of type unit) or not.

What do you expect to get out of this parser?

Edit: This sounds close to what you want, or at least you should be able to pick up some pointers from it. capitalized accepts capitalized strings, inverted accepts capitalized strings that have been reversed and reverses them as part of the parser logic.

let reverse (s: string) = 
    System.String(Array.rev (Array.ofSeq s))

let capitalized : Parser<string,unit> = 
    (asciiUpper .>>. manyChars asciiLower)  
    |>> fun (upper, lower) -> string upper + lower

let inverted : Parser<string,unit> = 
    (manyChars asciiLower .>>. asciiUpper)
    |>> fun (lower, upper) -> reverse (lower + string upper)

let capsOrInvert = choice [capitalized;inverted]

run capsOrInvert "Dog"
run capsOrInvert "doG"
run capsOrInvert "dog"
Basque answered 15/11, 2016 at 22:2 Comment(2)
I was hoping to get the parsed string back. How would I modify it to do that? Really, I'm just getting started with this lib. Thanks for the reply.Hartill
You'd usually want some more structured data representation coming out of a parser rather than going from a string to a string, but fair enough. I've added something that sounds close to what you want, the other answer also has some informative examples.Basque
S
8

There are several notable issues with your code.

First off, as noticed in @scrwtp's answer, your parser returns unit. Here's why: operator (>>.) returns only the result returned by the right inner parser. On the other hand, (.>>) would return the result of a left parser, while (.>>.) would return a tuple of both left and right ones.

So, parser1 >>. parser2 >>. eof is essentially (parser1 >>. parser2) >>. eof.
The code in parens completely ignores the result of parser1, and the second (>>.) then ignores the entire result of the parser in parens. Finally, eof returns unit, and this value is being returned.

You may need some meaningful data returned instead, e.g. the parsed string. The easiest way is:

let capitalized = (asciiUpper .>>. many asciiLower .>> eof)

Mind the operators.
The code for inverted can be done in a similar manner.


This parser would be of type Parser<(char * char list), unit>, a tuple of first character and all the remaining ones, so you may need to merge them back. There are several ways to do that, here's one:

let mymerge (c1: char, cs: char list) = c1 :: cs // a simple cons
let pCapitalized = capitalized >>= mymerge

The beauty of this code is that your mymerge is a normal function, working with normal char's, it knows nothing about parsers or so. It just works with the data, and (>>=) operator does the rest.

Note, pCapitalized is also a parser, but it returns a single char list.


Nothing stops you from applying further transitions. As you mentioned printing the string backwards:

let pCapitalizedAndReversed =
    capitalized
    >>= mymerge
    >>= List.rev

I have written the code in this way for purpose. In different lines you see a gradual transition of your domain data, still within the paradigm of Parser. This is an important consideration, because any subsequent transition may "decide" that the data is bad for some reason and raise a parsing exception, for example. Or, alternatively, it may be merged with other parser.

As soon as your domain data (a parsed-out word) is complete, you extract the result as mentioned in another answer.


A minor note. choice is superfluous for only two parsers. Use (<|>) instead. From experience, careful choosing parser combinators is important because a wrong choice deep inside your core parser logic can easily make your parsers dramatically slow.
See FParsec Primitives for further details.

Sommerville answered 16/11, 2016 at 0:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.