Как устранить ошибку FParsec "Комбинатор 'many' был применен к анализатору, который успешно работает без использования ..." - PullRequest
5 голосов
/ 17 декабря 2011

У меня есть парсер, который кажется достаточно простым. Я добавил этот подпарсер до конца, чтобы дать информацию об общих ошибках синтаксического анализа, так как все остальные подпарсеры потерпели неудачу -

/// 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 неверен. Правильный подход - использовать синтаксический анализатор «до конца», например, так:

/// 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

Теперь я просто запускаю readExprsTillEnd, когда мне нужно получить все выражения во входном потоке.

Еще раз спасибо, Густаво!

1 Ответ

1 голос
/ 17 декабря 2011

Спасибо за добавленный вами дополнительный код, к сожалению, я не смог воспроизвести ошибку. Но почему бы вам не попытаться удалить последний attempt? Я думаю, что это не имеет смысла и, возможно, вызывает проблемы.

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

Я не эксперт по FParsec, но я думаю, что последний анализатор выбора не должен быть попыткой.

UPDATE:

Синтаксический анализатор readError преуспевает даже при отсутствии ввода, если в какой-то момент у вас есть вызов readExpr в качестве параметра many, он никогда не закончится. Я имею в виду, если вы позвоните

run (many readError) "" ;;

Вы получите это сообщение об ошибке, потому что many будет продолжать применять этот синтаксический анализатор до тех пор, пока он не выйдет из строя, но никогда не выйдет из строя.

Посмотрите на спецификацию функции restOfLine на http://www.quanttec.com/fparsec/reference/charparsers.html#members.restOfLine, она предупреждает вас об этом.

Теперь есть много способов ее решить, но я бы сказал, что вам придется пересмотреть способы обработки ошибок синтаксического анализатора.

Одна вещь, которую вы можете сделать, это отключить функцию readError, а затем, когда вы вызываете синтаксический анализатор readExpr , вы вызываете ее следующим образом

let readExprs = many readExpr .>> eof

тем самым вы применяете eof, и если парсеры в выборе до eof что-то не обработали, FParsec автоматически сгенерирует для вас приятное сообщение об ошибке.

И если вы хотите устранить эту ошибку, взгляните на http://www.quanttec.com/fparsec/users-guide/customizing-error-messages.html

...