使用FParsec解析int或float [英] Parsing int or float with FParsec

查看:74
本文介绍了使用FParsec解析int或float的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm trying to parse a file, using FParsec, which consists of either float or int values. I'm facing two problems that I can't find a good solution for.

1

pint32pfloat都将成功解析相同的字符串,但给出不同的答案,例如,解析字符串"3.0"pint32将返回3,而解析时pfloat将返回3.0相同的字符串.

Both pint32 and pfloat will successfully parse the same string, but give different answers, e.g 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 parsing a floating point value using pint32 and have it fail if the string is "3.0"?

换句话说,有一种方法可以使以下代码起作用:

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

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"时返回"3.0",因此所有整数值也将放置在floatvalues中列表.

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

2

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

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

推荐答案

您在正确的道路上思考如何定义域数据以及分离解析器的定义和它们的用法 >关于源数据.这似乎是一种好方法,因为随着您的现实生活项目的进一步发展,您可能需要更多的数据类型.

You're on the right path thinking about defining domain data and separating definition of parsers and their usage on source data. This seems to be a good approach, because as your real-life project grows further, you would probably need more data types.

这是我的写法:

/// 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即可简化单元测试.

Note, the code above is capable working with any sources: strings, streams, or whatever. Your real app may need to work with files, but unit testing can be simplified by using just 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已更新,以确保解析的值具有非零的小数部分.

UPD. I have edited the code according to comments. pMyFloat was updated to ensure that the parsed value has non-zero fractional part.

这篇关于使用FParsec解析int或float的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆