我有一个看起来很直截了当的解析器。我将这个子解析器添加到最后,以提供有关一般解析错误的信息,因为所有其他子解析器都失败了 -
/// 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
但是,一旦我添加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.
我不明白为什么我得到这个,因为我使用解析的其余部分用于创建使用错误(此处为“违规”)结构的行。
有人可以帮助我理解这个吗?我是否会以错误的方式向用户发送解析器错误信息?如果没有,我该如何解决这个问题?
谢谢你的帮助!
*更详细*
这里有一些可能相关的代码 -
/// 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 }
我不知道。也许问题是它一旦尝试运行readError解析器就已经在流的末尾。这会使restOfLine不消耗输入,甚至不消耗空格吗?
*结论*
事实证明,使用readError解析器进行错误报告的方法是错误的。正确的方法是使用像这样的'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
现在我只需要在输入流中获取所有exprs时运行readExprsTillEnd。
再次感谢Gustavo!
答案 0 :(得分:1)
感谢您发布的其他代码,遗憾的是我无法重现错误。
但是你为什么不尝试删除最后一个attempt
?我认为这没有任何意义,可能导致问题。
do readExprRef :=
choice
[attempt readBoolean
attempt readCharacter
attempt readString
attempt readInt
readError]
我不是FParsec专家,但我认为选择的最后一个解析器不应该是尝试。
更新:
即使没有输入,readError
解析器也会成功,如果在某些时候您调用readExpr
作为many
的参数,它将永远不会结束。
我的意思是,如果你打电话
run (many readError) "" ;;
您将收到该错误消息,因为many
将继续应用该解析器,直到失败,但它永远不会失败。
查看http://www.quanttec.com/fparsec/reference/charparsers.html#members.restOfLine处的restOfLine函数规范,它会对此发出警告。
现在有很多方法可以解决它,但我会说你必须重新考虑处理解析器错误的方式。
您可以做的一件事就是取出readError
函数,然后当您调用 readExpr
解析器时,您可以这样调用它
let readExprs = many readExpr .>> eof
这样做可以强制执行eof,如果在eof之前选择的解析器没有处理某些内容,FParsec会自动为您生成一个很好的错误消息。
如果您想处理该错误,请查看http://www.quanttec.com/fparsec/users-guide/customizing-error-messages.html