Parsing int or float using FParsec

I am trying to parse a file using FParsec, which consists of float or int values. I ran into two problems for which I cannot find a good solution.

1

Both pint32 and pfloat will successfully parse the same string, but give different answers, for example pint32 will return 3 when parsing the string "3.0" and pfloat will return 3.0 when parsing the same string. Is it possible to try to parse a floating point value using pint32 , and will it fail if the string is "3.0" ?

In other words, is there a way to do the following code:

 let parseFloatOrInt lines = let rec loop intvalues floatvalues lines = match lines with | [] -> floatvalues, intvalues | line::rest -> match run floatWs line with | Success (r, _, _) -> loop intvalues (r::floatvalues) rest | Failure _ -> match run intWs line with | Success (r, _, _) -> loop (r::intvalues) floatvalues rest | Failure _ -> loop intvalues floatvalues rest loop [] [] lines 

This piece of code will correctly place all floating point values ​​in the floatvalues list, but since pfloat returns "3.0" when parsing the string "3" , all integer values ​​will also be placed in the floatvalues list.

2

The above code example seems a bit awkward to me, so I guess there should be a better way to do this. I considered combining them with choice , however, both parsers must return the same type to work. I think I could make a discriminated union with one parameter for float and one for int and convert the output from pint32 and pfloat using the |>> operator. However, I wonder if there is a better solution?

+5
source share
1 answer

You are on the right track, thinking about defining domain data and separating the definition of parsers and their use from the source data. This seems to be a good approach, because as your real project grows, you probably need more data types.

Here is how I wrote it:

 /// The resulting type, or DSL type MyData = | IntValue of int | FloatValue of float | Error // special case for all parse failures // Then, let define individual parsers: let pMyInt = pint32 |>> IntValue // this is an alternative version of float parser. // it ensures that the value has non-zero fractional part. // caveat: the naive approach would treat values like 42.0 as integer let pMyFloat = pfloat >>= (fun x -> if x % 1 = 0 then fail "Not a float" else preturn (FloatValue x)) let pError = // this parser must consume some input, // otherwise combined with `many` it would hang in a dead loop skipAnyChar >>. preturn Error // Now, the combined parser: let pCombined = [ pMyFloat; pMyInt; pError ] // note, future parsers will be added here; // mind the order as float supersedes the int, // and Error must be the last |> List.map (fun p -> p .>> ws) // I'm too lazy to add whitespase skipping // into each individual parser |> List.map attempt // each parser is optional |> choice // on each iteration, one of the parsers must succeed |> many // a loop 

Please note that the above code is able to work with any sources: strings, streams, etc. Your real application may need to work with files, but unit testing can be simplified using only the string list .

 // Now, applying the parser somewhere in the code: let maybeParseResult = match run pCombined myStringData with | Success(result, _, _) -> Some result | Failure(_, _, _) -> None // or anything that indicates general parse failure 

UPD I edited the code according to the comments. pMyFloat been updated to ensure that the parsed value has a nonzero fractional part.

+3
source

Source: https://habr.com/ru/post/1242182/


All Articles