6

float または int 値で構成される FParsec を使用して、ファイルを解析しようとしています。適切な解決策が見つからない 2 つの問題に直面しています。

1

pint32とはどちらもpfloat同じ文字列を正常に解析しますが、異なる答えpint32を返します。たとえば3、文字列"3.0"を解析するpfloatと戻り3.0、同じ文字列を解析すると戻ります。を使用して浮動小数点値の解析を試みpint32、文字列が の場合に失敗することは可能"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が、両方のパーサーが同じ型を返す必要があります。pint32float 用と int 用の 1 つのオプションを使用して判別共用体を作成し、演算子からの出力と演算子をpfloat使用して変換できると思います|>>。しかし、より良い解決策があるかどうか疑問に思っていますか?

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

アップデート。コメントに従ってコードを編集しました。pMyFloat解析された値にゼロ以外の小数部があることを確認するために更新されました。

于 2016-02-03T11:55:06.910 に答える