【问题标题】:FParsec failing on manyFParsec 在许多方面都失败了
【发布时间】:2013-05-19 19:58:03
【问题描述】:

我有这个测试程序:

open FParsec

let test p str =
    match run p str with
    | Success(result, _, _)   -> printfn "Success: %A" result
    | Failure(errorMsg, _, _) -> printfn "Failure: %s" errorMsg

let str s = pstring s

let sepPart = skipNewline >>. pstring "-"

let part = manyChars (notFollowedBy sepPart >>. anyChar)

[<EntryPoint>]
let main argv = 
    let s = "AA 12345\nBB 6789\n-----\nCC 9876\nDD 54321\n-----"
    test part s
    test (many part) s

    0 // return an integer exit code

{test part s} 行按预期工作,但下一行 {test (many part) s} 失败,我不明白我做错了什么。

编辑:

澄清一下,我要做的是让 {test (many part) s} return ["AA 12345\nBB 6789"; “CC 9876\nDD 54321”]。换句话说,我所拥有的是一个由“pars”或“chunks”组成的输入字符串,这些字符串由带有所有破折号的行分隔。对于输出,我想要一个数组,其中每个元素都是部分之一,并且带有破折号的行被简单地丢弃。

【问题讨论】:

    标签: f# fparsec


    【解决方案1】:

    当您执行您的示例时,FParsec 会引发异常并显示以下消息:

    附加信息:(Ln:2,Col:8):组合子“many”是 应用于在不消耗输入且不消耗输入的情况下成功的解析器 以任何其他方式更改解析器状态。 (如果没有例外 引发,组合器可能会进入无限循环。)

    问题是你的part 解析器总是成功的,即使它只能解析一个空字符串。您可以通过将part 定义中的manyChars 替换为many1Chars 来解决该问题。

    如果您搜索例如“应用于无需消耗输入即可成功的解析器”您会在 Internet 上找到几个关于类似错误的讨论,包括 FParse 的用户指南中的一个:http://www.quanttec.com/fparsec/users-guide/parsing-sequences.html#the-many-parser

    更新: 这是一个有效的简单解析器定义:

    let sepPart = skipNewline 
                  >>? (skipMany1SatisfyL ((=) '-') "'-'" 
                        >>. (skipNewline <|> eof))
    
    let part = many1CharsTill anyChar sepPart    
    let parser = many part
    

    请注意,我在sepPart 的定义中使用了&gt;&gt;?,如果换行符后面没有破折号,则允许此解析器回溯到开头。或者,您也可以使用attempt (skipNewline &gt;&gt;. ...),它也会在初始破折号后回溯错误。 many[Chars]Till p endp 的文档声明了与 many (notFollowedBy endp &gt;&gt;. p) .&gt;&gt; endp 的等价性,这并不完全正确,因为 many[Chars]Till 不像 notFollowedBy 那样回溯。我会澄清文档。

    如果您尽可能避免使用many[Chars]TillnotFollowedBy 回溯,则性能会更好。例如,您还可以按如下方式解析行块:

    let id = manyMinMaxSatisfyL 2 2 isUpper "id (two capital letters)"
    
    let line = id .>>. (pchar ' ' >>. restOfLine true)
    
    let separator = many1SatisfyL ((=) '-') "dash separator"
                    >>. (skipNewline <|> eof)
    
    let chunk = many1 line     
    let parser = sepEndBy1 chunk separator
    

    请注意,此实现不需要最后一个块以分隔符结束。如果你愿意,你可以改用:

    let chunk = many line .>> separator
    let parser = many chunk
    

    如果您想使用 sepEndBy 定义允许空块,您可以使用:

    let chunk = many1 line <|> (notFollowedByEof >>% [])
    let parser = sepEndBy1 chunk separator
    

    【讨论】:

    • 这确实抑制了错误,但是它不会返回正确的结果。如果您查看第一次解析 {test part s} 返回的内容,您会看到当部件解析器中的 manyChars 更改为 many1Chars 时,结果会发生变化。
    • 我看不出有什么区别,除了part 返回一个字符串和many part 返回一个列表中的字符串。您能否更具体一点并解释您期望的输出?你的解析器永远不会跳过sepPart,所以也许你想要many (part .&gt;&gt; sepPart)之类的东西。如果要匹配一个或多个破折号作为分隔符,还必须在 sepPart 的定义中使用 skipMany1 (pstring "-")skipMany1SatisfyL ((=) '-') "'-'"
    • 我编辑了问题以更好地描述预期的输出。我很抱歉没有一开始就说得更清楚。
    • 我已经更新了答案。如果您有任何不清楚的地方,请询问。
    • 对不起,我不得不再次更新答案,因为建议的更简单的解决方案有一个错误:many 太多了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多