简体   繁体   English

如何解决FParsec错误“组合器'很多'被应用于成功的解析器而不消耗......”

[英]How to resolve FParsec error “The combinator 'many' was applied to a parser that succeeds without consuming…”

I have a parser that seems straight-forward enough. 我有一个看起来很简单的解析器。 I added this sub-parser to the end to give info about general parsing errors since all the other sub-parsers failed - 我将这个子解析器添加到最后,以提供有关一般解析错误的信息,因为所有其他子解析器都失败了 -

/// Read the rest of a line as an error.
let readError =
    parse {
        let! restOfLineStr = restOfLine true
        return makeViolation ("Read error on: " + restOfLineStr + ".") }

/// Read an expression.
do readExprRef :=
    choice
        [attempt readBoolean
         attempt readCharacter
         attempt readString
         attempt readInt
         attempt readError] // just now added this sub-parser, and get the issue

However, once I add readError as a choice, I get the dreaded FParsec error about stream consumption at run-time - The combinator 'many' was applied to a parser that succeeds without consuming input and without changing the parser state in any other way. 但是,一旦我添加readError作为选择,我就会在运行时得到关于流消耗的可怕的FParsec错误 - The combinator 'many' was applied to a parser that succeeds without consuming input and without changing the parser state in any other way. I don't understand why I get this since I do use the parsed rest of the line to create a used error (here 'violation') structure. 我不明白为什么我得到这个,因为我使用解析后的其余部分来创建一个使用错误(这里是'违规')结构。

Can someone help me understand this? 有人能帮助我理解这个吗? Am I going about signaling parser errors to the user in the wrong way? 我是否会以错误的方式向用户发送解析器错误信息? If not, how could I fix this? 如果没有,我该如何解决这个问题?

Thank you kindly for your help! 谢谢你的帮助!

* More detail * * 更多详情 *

Here's some more code that may be relevant - 这里有一些可能相关的代码 -

/// The expression structure.
type Expr =
| Violation of Expr
| Boolean of bool
| Character of char
| String of string
| Int of int

/// Make a violation from a string.
let makeViolation str = Violation (String str)

/// Read whitespace character as a string.
let spaceAsStr = anyOf whitespaceChars |>> fun chr -> string chr

/// Read a line comment.
let lineComment = pchar lineCommentChar >>. restOfLine true

/// Read a multiline comment.
/// TODO: make multiline comments nest.
let multilineComment =
    between
        (pstring openMultilineCommentStr)
        (pstring closeMultilineCommentStr)
        (charsTillString closeMultilineCommentStr false System.Int32.MaxValue)

/// Read whitespace text.
let whitespace = lineComment <|> multilineComment <|> spaceAsStr

/// Skip any white space characters.
let skipWhitespace = skipMany whitespace

/// Skip at least one white space character.
let skipWhitespace1 = skipMany1 whitespace

/// Read a boolean.
let readBoolean = 
    parse {
        do! skipWhitespace
        let! booleanValue = readStr trueStr <|> readStr falseStr
        return Boolean (booleanValue = trueStr) }

/// Read a character.
let readCharacter =
    parse {
        // TODO: enable reading of escaped chars
        do! skipWhitespace
        let! chr = between skipSingleQuote skipSingleQuote (manyChars (noneOf "\'"))
        return Character chr.[0] }

/// Read a string.
let readString =
    parse {
        // TODO: enable reading of escaped chars
        do! skipWhitespace
        let! str = between skipDoubleQuote skipDoubleQuote (manyChars (noneOf "\""))
        return String str }

/// Read an int.
let readInt =
    parse {
        do! skipWhitespace
        let! value = pint32
        let! _ = opt (skipString intSuffixStr)
        do! notFollowedByLetterOrNameChar
        do! notFollowedByDot
        return Int value }

I dunno. 我不知道。 Maybe the issue is that it's already at the end of the stream once it tries to run the readError parser. 也许问题是它一旦尝试运行readError解析器就已经在流的末尾。 Would that make restOfLine consume no input, not even whitespace? 这会使restOfLine不消耗输入,甚至不消耗空格吗?

* Conclusion * *结论*

It turns out that the approach to error reporting with a readError parser is wrong. 事实证明,使用readError解析器进行错误报告的方法是错误的。 The correct approach is to use a 'till end' parser like so - 正确的方法是使用像这样的'till end'解析器 -

/// Read the end of input.
let readEndOfInput = skipWhitespace >>. eof

// Read multiple exprs.
let readExprs = many readExpr

// Read exprs until the end of the input.
let readExprsTillEnd = readExprs .>> readEndOfInput

Now I just run readExprsTillEnd when I need to get all the exprs in an input stream. 现在我只需要在输入流中获取所有exprs时运行readExprsTillEnd。

Thanks again, Gustavo! 再次感谢,古斯塔沃!

Thanks for the additional code you posted, unfortunately I was unable to reproduce the error. 感谢您发布的其他代码,遗憾的是我无法重现错误。 But why don't you try to remove the last attempt ? 但是你为什么不尝试删除最后一次attempt I think it makes no sense and maybe is causing a problem. 我认为这没有任何意义,可能导致问题。

do readExprRef :=
    choice
        [attempt readBoolean
         attempt readCharacter
         attempt readString
         attempt readInt
         readError]

I'm not an FParsec expert but I think the last parser of a choice should not be an attempt. 我不是FParsec专家,但我认为选择的最后一个解析器不应该是一个尝试。

UPDATE: 更新:

The readError parser succeeds even consuming no input, if at some point you have a call to readExpr as parameter of a many it would never ends. readError解析器成功甚至没有输入,如果在某些时候你调用readExpr作为many它永远不会结束的参数。 I mean if you call 我的意思是如果你打电话

run (many readError) "" ;;

You'll get that error message because many will continue applying that parser until it fails, but it will never fail. 您将收到该错误消息,因为many将继续应用该解析器,直到它失败,但它永远不会失败。

Have a look at the restOfLine function specification at http://www.quanttec.com/fparsec/reference/charparsers.html#members.restOfLine it warns you about this. 请查看http://www.quanttec.com/fparsec/reference/charparsers.html#members.restOfLine上的restOfLine函数规范,它会向您发出警告。

Now there are many ways you can solve it, but I would say you will have to reconsider the way you handle the parser errors. 现在有很多方法可以解决它,但我想你必须重新考虑处理解析器错误的方式。

One thing you can do is take out the readError function and then when you call the readExpr parser you call it this way 您可以做的一件事是取出readError函数,然后当您调用readExpr解析器时,您可以这样调用它

let readExprs = many readExpr .>> eof

doing so you enforce the eof and if there is something not handled by the parsers in the choice before the eof, FParsec will automatically generate a nice error message for you. 这样你就强制执行eof,如果在eof之前选择的解析器没有处理某些东西,FParsec会自动为你生成一个很好的错误信息。

And if you want to handle that error, have a look at http://www.quanttec.com/fparsec/users-guide/customizing-error-messages.html 如果您想处理该错误,请查看http://www.quanttec.com/fparsec/users-guide/customizing-error-messages.html

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM