6

我正在尝试使用 FParsec 解析一个文件,该文件由 float 或 int 值组成。我面临着两个我找不到好的解决方案的问题。

1

两者pint32pfloat都会成功解析同一个字符串,但给出不同的答案,例如解析字符串时pint32返回,解析同一个字符串时返回。是否可以尝试使用解析浮点值并在字符串为时失败?3"3.0"pfloat3.0pint32"3.0"

换句话说,有没有办法使以下代码工作:

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

这段代码会正确地将所有浮点值放入floatvalues列表中,但由于解析字符串时pfloat返回,所有整数值也会放入列表中。"3.0""3"floatvalues

2

上面的代码示例对我来说似乎有点笨拙,所以我猜一定有更好的方法来做到这一点。我考虑使用组合它们choice,但是两个解析器必须返回相同的类型才能工作。我想我可以使用一个选项用于浮点数和一个选项用于 int 来创建一个有区别的联合,pint32pfloat使用|>>运算符转换输出。但是,我想知道是否有更好的解决方案?

4

1 回答 1

3

您在考虑定义域数据和分离解析器的定义及其在源数据上的使用方面走在正确的道路上。这似乎是一个好方法,因为随着您的实际项目进一步发展,您可能需要更多的数据类型。

我会这样写:

/// The resulting type, or DSL
type MyData =
    | IntValue of int
    | FloatValue of float
    | Error  // special case for all parse failures

// Then, let's 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

请注意,上面的代码能够处理任何源:字符串、流或其他。您的真实应用程序可能需要处理文件,但单元测试可以通过仅使用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。我根据评论编辑了代码。pMyFloat已更新以确保解析的值具有非零小数部分。

于 2016-02-03T11:55:06.910 回答