使用 FParsec 解析 int 或 float
Parsing int or float with FParsec
我正在尝试使用 FParsec 解析文件,该文件由 float 或 int 值组成。我面临两个找不到好的解决方案的问题。
1
pint32
和pfloat
都会成功解析同一个字符串,但是会给出不同的答案,例如pint32
在解析字符串时会return3
"3.0"
和 pfloat
在解析同一个字符串时会 return 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
returns "3.0"
在解析字符串"3"
,所有整数值也将放在 floatvalues
列表中。
2
上面的代码示例对我来说似乎有点笨拙,所以我猜一定有更好的方法来实现它。我考虑过使用 choice
组合它们,但是两个解析器必须 return 相同的类型才能工作。我想我可以创建一个有区别的联合,其中一个选项用于 float,一个选项用于 int,并使用 |>>
运算符转换 pint32
和 pfloat
的输出。但是,我想知道是否有更好的解决方案?
考虑定义域数据并将解析器的 定义 与其在源数据上的 用法 分开,您走在正确的道路上。这似乎是一个很好的方法,因为随着您的实际项目进一步发展,您可能需要更多的数据类型。
我会这样写:
/// 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
已更新以确保解析的值具有非零小数部分。
FParsec 有 numberLiteral
解析器可以用来解决问题。
首先,您可以使用上面 link 中提供的示例:
open FParsec
open FParsec.Primitives
open FParsec.CharParsers
type Number = Int of int64
| Float of float
// -?[0-9]+(\.[0-9]*)?([eE][+-]?[0-9]+)?
let numberFormat = NumberLiteralOptions.AllowMinusSign
||| NumberLiteralOptions.AllowFraction
||| NumberLiteralOptions.AllowExponent
let pnumber : Parser<Number, unit> =
numberLiteral numberFormat "number"
|>> fun nl ->
if nl.IsInteger then Int (int64 nl.String)
else Float (float nl.String)```
我正在尝试使用 FParsec 解析文件,该文件由 float 或 int 值组成。我面临两个找不到好的解决方案的问题。
1
pint32
和pfloat
都会成功解析同一个字符串,但是会给出不同的答案,例如pint32
在解析字符串时会return3
"3.0"
和 pfloat
在解析同一个字符串时会 return 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
returns "3.0"
在解析字符串"3"
,所有整数值也将放在 floatvalues
列表中。
2
上面的代码示例对我来说似乎有点笨拙,所以我猜一定有更好的方法来实现它。我考虑过使用 choice
组合它们,但是两个解析器必须 return 相同的类型才能工作。我想我可以创建一个有区别的联合,其中一个选项用于 float,一个选项用于 int,并使用 |>>
运算符转换 pint32
和 pfloat
的输出。但是,我想知道是否有更好的解决方案?
考虑定义域数据并将解析器的 定义 与其在源数据上的 用法 分开,您走在正确的道路上。这似乎是一个很好的方法,因为随着您的实际项目进一步发展,您可能需要更多的数据类型。
我会这样写:
/// 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
已更新以确保解析的值具有非零小数部分。
FParsec 有 numberLiteral
解析器可以用来解决问题。
首先,您可以使用上面 link 中提供的示例:
open FParsec
open FParsec.Primitives
open FParsec.CharParsers
type Number = Int of int64
| Float of float
// -?[0-9]+(\.[0-9]*)?([eE][+-]?[0-9]+)?
let numberFormat = NumberLiteralOptions.AllowMinusSign
||| NumberLiteralOptions.AllowFraction
||| NumberLiteralOptions.AllowExponent
let pnumber : Parser<Number, unit> =
numberLiteral numberFormat "number"
|>> fun nl ->
if nl.IsInteger then Int (int64 nl.String)
else Float (float nl.String)```