Как я могу заставить Parsec вернуть ошибку? - PullRequest
0 голосов
/ 08 января 2019

Я делаю парсер с Parsec и пытаюсь вернуть конкретную ошибку при разборе.

Это минимальный пример синтаксического анализатора для выявления моей проблемы:

parseA = try seq1
      <|>  seq2

seq1 = do
          manyTill anyChar (try $ string "\n* ")
          many1 anyChar
          fail "My error message" 

seq2 = do
          manyTill anyChar (try $ string "\n- ")
          many1 anyChar

Я хотел бы выполнить некоторые тесты в первой последовательности try $ do, остановить анализ и вернуть конкретное сообщение об ошибке. Когда я не использую fail, я получаю:

ghci>  parse parseA  "" "aaaaaa\nbbbb\n* ccccc\n- ddd"
Right "ccccc\n- ddd"

Когда я использую fail или unexpected, мой анализатор не останавливается (из-за функции try) и выполняет следующую последовательность do:

ghci>  parse parseA  "" "aaaaaa\nbbbb\n* ccccc\n- ddd"
Right "ddd"

И это не то, что я хочу!

Я подумал об использовании базовой функции error, чтобы остановить выполнение моего синтаксического анализатора, но я хотел бы получить «чистую» ошибку, возвращаемую функцией синтаксического анализа, такую ​​как:

ghci>  parse parseA  "" "aaaaaa\nbbbb\n* ccccc\n- ddd"
Left "My error message"

Знаете ли вы, как правильно остановить анализатор и вернуть пользовательскую ошибку?

1 Ответ

0 голосов
/ 08 января 2019

Если вы хотите, чтобы монада вел себя по-другому, возможно, вам следует создать другую монаду. (Н.Б. Я не совсем понимаю, чего вы хотите, но все равно буду двигаться вперед).

Решение: используйте монадный стек трансформаторов

Например, чтобы получить fail -подобную функцию, которая не перехватывается и не игнорируется парсекской try, вы можете использовать Кроме монады . Except позволяет генерировать ошибки, похожие на исключения, но они монтируются монадически вместо использования фактического механизма исключений, который требует IO для его обнаружения.

Во-первых, давайте определим нашу монаду:

import Text.Parsec
import Text.Parsec.Combinator
import Text.Parsec.Char
import Control.Monad.Trans.Except
import Control.Monad.Trans

type EscParse a = ParsecT String () (Except String) a

Итак, монада EscParse и сочетает в себе функции Parsec (через преобразователь ParsecT) и Except.

Во-вторых, давайте определимся с некоторыми помощниками:

run :: EscParse a -> SourceName -> String -> Either String (Either ParseError a)
run op sn input = runExcept (runPT op () sn input)

escFail :: String -> EscParse a
escFail = lift. throwE

Наш run похож на runParse, но также запускает монаду кроме. Возможно, вы захотите сделать что-нибудь, чтобы избежать вложенного Either, но это легкое косметическое изменение. escFail - это то, что вы используете, если не хотите, чтобы ошибка игнорировалась.

В-третьих, нам нужно реализовать ваш парсер с помощью этой новой монады:

parseA :: EscParse String
parseA = try seq1 <|>  seq2

seq1 :: EscParse String
seq1 = do manyTill anyChar (try $ string "\n* ")
          many1 anyChar
          escFail "My error message"

seq2 :: EscParse String
seq2 = do manyTill anyChar (try $ string "\n- ")
          many1 anyChar

Кроме пробелов и сигнатур типа, вышеприведенное соответствует тому, что вы имели, но с использованием escFail вместо fail.

...