如何在 FParsec 中解析同类列表?
How can I parse homogeneous lists in FParsec?
我在尝试在 FParsec 中解析类似 json 的同类数组时遇到问题。我已将问题分解为一个重现它的简短示例。
#r @"..\packages\FParsec.1.0.2\lib\net40-client\FParsecCS.dll"
#r @"..\packages\FParsec.1.0.2\lib\net40-client\FParsec.dll"
open System
open FParsec
let test p str =
match run p str with
| Success(result, _, _) -> printfn "Success: %A" result
| Failure(errormsg, _, _) -> printfn "Failure: %s" errormsg
type CValue = CInt of int64
| CBool of bool
| CList of CValue list
let P_WHITESPACE = spaces
let P_COMMA = pstring ","
let P_L_SBRACE = pstring "[" .>> P_WHITESPACE
let P_R_SBRACE = P_WHITESPACE >>. pstring "]"
let P_INT_VALUE = pint64 |>> CInt
let P_TRUE = stringReturn "true" (CBool true)
let P_FALSE = stringReturn "false" (CBool false)
let P_BOOL_VALUE = P_TRUE <|> P_FALSE
let P_LIST_VALUE =
let commaDelimitedList ptype = sepBy (ptype .>> P_WHITESPACE) (P_COMMA .>> P_WHITESPACE)
let delimitedList = (commaDelimitedList P_INT_VALUE) <|> (commaDelimitedList P_BOOL_VALUE)
let enclosedList = between P_L_SBRACE P_R_SBRACE delimitedList
enclosedList |>> CList
当我使用 test
函数进行尝试时,我得到以下结果:
test P_LIST_VALUE "[1,2,3]"
Success: CList [CInt 1L; CInt 2L; CInt 3L]
test P_LIST_VALUE "[true,false]"
Failure: Error in Ln: 1 Col: 2
[true,false]
^
Expecting: integer number (64-bit, signed) or ']'
如果我在使用 <|>
运算符时交换 P_INT_VALUE
和 P_BOOL_VALUE
的顺序,则 [true,false]
解析成功但 [1,2,3]
失败并出现类似错误。所以基本上,我首先使用的解析器就是它尝试使用的解析器。
我知道如果 LHS 改变了用户状态,<|>
运算符将不会尝试 RHS 解析器 - 但我看不出这是怎么发生的。 P_BOOL_VALUE 和 P_INT_VALUE 没有任何共同的起始字符,因此在尝试解析错误的数据类型时两者都应该立即失败。整数从不以 'false' 或 'true' 开头,布尔从不以数字开头。
我做错了什么?
啊,我想通了。错误消息中的提示是 or ']'
。问题是 sepBy
在空输入时成功,所以当它遇到 t
时,它 returns 成功地使用一个空列表,然后控制权传回 between
尝试并且找不到终止 ]
.
解决方案是将空列表案例移出 int/bool-specific 解析器,如下所示:
let P_LIST_VALUE =
let commaDelimitedList ptype = sepBy1 (ptype .>> P_WHITESPACE) (P_COMMA .>> P_WHITESPACE)
let delimitedList = (commaDelimitedList P_INT_VALUE) <|> (commaDelimitedList P_BOOL_VALUE) <|> preturn []
let enclosedList = between P_L_SBRACE P_R_SBRACE delimitedList
enclosedList |>> CList
注意使用 sepBy1
而不是 sepBy
,并添加 <|> preturn []
以在 delimitedList
.
中仅处理一次空大小写
附带说明一下,我不知道您的具体应用,但在解析器中强制输入通常不是一个好主意;一种更常见的实现方法是仅解析 commaDelimitedList (P_INT_VALUE <|> P_BOOL_VALUE)
(使用原始 commaDelimitedList
),然后在后续分析阶段检查输入。
我在尝试在 FParsec 中解析类似 json 的同类数组时遇到问题。我已将问题分解为一个重现它的简短示例。
#r @"..\packages\FParsec.1.0.2\lib\net40-client\FParsecCS.dll"
#r @"..\packages\FParsec.1.0.2\lib\net40-client\FParsec.dll"
open System
open FParsec
let test p str =
match run p str with
| Success(result, _, _) -> printfn "Success: %A" result
| Failure(errormsg, _, _) -> printfn "Failure: %s" errormsg
type CValue = CInt of int64
| CBool of bool
| CList of CValue list
let P_WHITESPACE = spaces
let P_COMMA = pstring ","
let P_L_SBRACE = pstring "[" .>> P_WHITESPACE
let P_R_SBRACE = P_WHITESPACE >>. pstring "]"
let P_INT_VALUE = pint64 |>> CInt
let P_TRUE = stringReturn "true" (CBool true)
let P_FALSE = stringReturn "false" (CBool false)
let P_BOOL_VALUE = P_TRUE <|> P_FALSE
let P_LIST_VALUE =
let commaDelimitedList ptype = sepBy (ptype .>> P_WHITESPACE) (P_COMMA .>> P_WHITESPACE)
let delimitedList = (commaDelimitedList P_INT_VALUE) <|> (commaDelimitedList P_BOOL_VALUE)
let enclosedList = between P_L_SBRACE P_R_SBRACE delimitedList
enclosedList |>> CList
当我使用 test
函数进行尝试时,我得到以下结果:
test P_LIST_VALUE "[1,2,3]"
Success: CList [CInt 1L; CInt 2L; CInt 3L]
test P_LIST_VALUE "[true,false]"
Failure: Error in Ln: 1 Col: 2
[true,false]
^
Expecting: integer number (64-bit, signed) or ']'
如果我在使用 <|>
运算符时交换 P_INT_VALUE
和 P_BOOL_VALUE
的顺序,则 [true,false]
解析成功但 [1,2,3]
失败并出现类似错误。所以基本上,我首先使用的解析器就是它尝试使用的解析器。
我知道如果 LHS 改变了用户状态,<|>
运算符将不会尝试 RHS 解析器 - 但我看不出这是怎么发生的。 P_BOOL_VALUE 和 P_INT_VALUE 没有任何共同的起始字符,因此在尝试解析错误的数据类型时两者都应该立即失败。整数从不以 'false' 或 'true' 开头,布尔从不以数字开头。
我做错了什么?
啊,我想通了。错误消息中的提示是 or ']'
。问题是 sepBy
在空输入时成功,所以当它遇到 t
时,它 returns 成功地使用一个空列表,然后控制权传回 between
尝试并且找不到终止 ]
.
解决方案是将空列表案例移出 int/bool-specific 解析器,如下所示:
let P_LIST_VALUE =
let commaDelimitedList ptype = sepBy1 (ptype .>> P_WHITESPACE) (P_COMMA .>> P_WHITESPACE)
let delimitedList = (commaDelimitedList P_INT_VALUE) <|> (commaDelimitedList P_BOOL_VALUE) <|> preturn []
let enclosedList = between P_L_SBRACE P_R_SBRACE delimitedList
enclosedList |>> CList
注意使用 sepBy1
而不是 sepBy
,并添加 <|> preturn []
以在 delimitedList
.
附带说明一下,我不知道您的具体应用,但在解析器中强制输入通常不是一个好主意;一种更常见的实现方法是仅解析 commaDelimitedList (P_INT_VALUE <|> P_BOOL_VALUE)
(使用原始 commaDelimitedList
),然后在后续分析阶段检查输入。