Matching More than One ParserResult and Extracting Values

166 Views Asked by At

This is a question about working with FParsec's ParserResult.

Is there a cleaner implementation of match_result to pull out the XKEYVALUE or XATTRIBUTES contained inside the ParserResult without two nested matches?

The following code works as a F# console app...

// Learn more about F# at http://fsharp.org

open System
open FParsec

// Types
type XKEYVALUE = string * string
type XATTRIBUTES = Map< string, string>

type PARSER_RESULT_XML = 
    | PR_XKEYVALUE of ParserResult<XKEYVALUE, unit>
    | PR_XATTRIBUTES of ParserResult<XATTRIBUTES, unit>

// Parser Trace
let (<!>) (p: Parser<_,_>) label : Parser<_,_> =
    fun stream ->
        printfn "%A: Entering %s" stream.Position label
        let reply = p stream
        do
            match (reply.Status) with
                | Ok  -> printfn "%A: Leaving %s (%A) - %A" stream.Position label reply.Status reply.Result
                | Error -> printfn "%A: Leaving %s (%A) - %A" stream.Position label reply.Status reply.Error
                | FatalError -> printfn "%A: Leaving %s with FatalError (%A)" stream.Position label reply.Status
                | _ -> printfn "%A: Leaving %s with unknown status" stream.Position label
        reply

// Parsers
let ws = spaces
let str = pstring
let xKey : Parser<string,unit> = (ws >>. regex "[a-zA-Z][a-zA-Z0-9:]*") <!> "xKey"
let xStringLiteral = regex "[^\"]+" <!> "xStringLiteral"
let xKeyValue : Parser<XKEYVALUE, unit> = 
    ws >>. xKey .>>. (ws >>. str "=" >>. ws >>. str "\"" >>. xStringLiteral .>> str "\"" .>> ws) |>> XKEYVALUE <!> "xKeyValue"
let xAttributes : Parser<XATTRIBUTES, unit> = sepEndBy xKeyValue ws |>> XATTRIBUTES <!> "xAttributes"

// Test Data
let xKeyValue_text = """    key =  "value"    aa"""
let xAttributes_text = "key1=\"value1\" key2=\"value2\""

let match_result ( result : PARSER_RESULT_XML) : string =
    match result with 
        | PR_XKEYVALUE(a) ->    match a with  
                                    | Success( ((key : string), (value : string)), x2, x3) -> sprintf "%s=\"%s\"" key value
                                    | Failure( _, _, _) -> sprintf "FAILURE"
        | PR_XATTRIBUTES( a) -> match a with
                                    | Success( (x1 : Map<string, string>), x2, x3) -> sprintf "x1=%A, x2=%A, x3=%A" x1 x2 x3
                                    | Failure( _, _, _) -> sprintf "FAILURE"

[<EntryPoint>]
let main argv =
    run xKeyValue xKeyValue_text |> PR_XKEYVALUE |> match_result |> printfn "%A"
    run xAttributes xAttributes_text |> PR_XATTRIBUTES |> match_result |> printfn "%A"
    0 // return an integer exit code

But match_result just seems clumsy with its nested matches.

A failed experiment was to use an AND pattern match to put the match of PARSER_RESULT_XML & Success( …) in the same match expression but I couldn't get the types of the two match expressions to agree.

How would you modify match_result to do this better or cleaner?

2

There are 2 best solutions below

1
nilekirk On BEST ANSWER

You can pattern match how deep as you need into a structure:

let match_result result =
    match result with
    | PR_XKEYVALUE (Success ((key, value), _, _)) -> sprintf "%s=\"%s\"" key value 
    | PR_XATTRIBUTES (Success (x1, x2, x3)) -> sprintf "x1=%A, x2=%A, x3=%A" x1 x2 x3
    | _ -> "FAILURE"
1
Jim Foye On

This is subjective on my part, but if you get rid of unneeded type annotations and change the indentation on match_result, I think it improves the readability a little bit.

let match_result result =
    match result with 
    | PR_XKEYVALUE a ->
        match a with  
        | Success ((key, value), _, _) -> sprintf "%s=\"%s\"" key value
        | Failure _ -> sprintf "FAILURE"
    | PR_XATTRIBUTES a -> 
        match a with
        | Success (x1, x2, x3) -> sprintf "x1=%A, x2=%A, x3=%A" x1 x2 x3
        | Failure _ -> sprintf "FAILURE"

If you're still not happy with that, you might find using an active pattern here helpful:

let (|KeyValue|Attributes|Failed|) result =
    match result with
    | PR_XKEYVALUE a ->
        match a with  
        | Success ((key, value), _, _) -> KeyValue (key, value)
        | Failure _ -> Failed
    | PR_XATTRIBUTES a -> 
        match a with
        | Success (x1, x2, x3) -> Attributes (x1, x2, x3)
        | Failure _ -> Failed

let match_result result =
    match result with 
    | KeyValue (key, value) -> sprintf "%s=\"%s\"" key value
    | Attributes (x1, x2, x3) -> sprintf "x1=%A, x2=%A, x3=%A" x1 x2 x3
    | Failed -> sprintf "FAILURE"

The latter is better in that you're separating out interpreting the result in domain terms, and what to do with it (print messages, in this case).

Related Questions in F#