Я пытаюсь разобрать строку, разделенную запятыми, которая может содержать или не содержать строку с размерами изображения. Например "hello world, 300x300, good bye world".

Я написал следующую небольшую программу:

import Text.Parsec
import qualified Text.Parsec.Text as PS

parseTestString :: Text -> [Maybe (Int, Int)]
parseTestString s = case parse dimensStringParser "" s of
                      Left _ -> [Nothing]
                      Right dimens -> dimens

dimensStringParser :: PS.Parser [Maybe (Int, Int)]
dimensStringParser = (optionMaybe dimensParser) `sepBy` (char ',')

dimensParser :: PS.Parser (Int, Int)
dimensParser = do
  w <- many1 digit
  char 'x'
  h <- many1 digit
  return (read w, read h)

main :: IO ()
main = do
  print $ parseTestString "300x300,40x40,5x5"
  print $ parseTestString "300x300,hello,5x5,6x6"

Согласно документации optionMaybe, он возвращает Nothing, если не может выполнить синтаксический анализ, поэтому я ожидал получить такой вывод:

[Just (300,300),Just (40,40),Just (5,5)]
[Just (300,300),Nothing, Just (5,5), Just (6,6)]

Но вместо этого я получаю:

[Just (300,300),Just (40,40),Just (5,5)]
[Just (300,300),Nothing]

Т.е. анализ останавливается после первого сбоя. Итак, у меня есть два вопроса:

  1. Почему он так себя ведет?
  2. Как мне написать правильный парсер для этого случая?
2
dimsuz 1 Янв 2018 в 13:30

2 ответа

Лучший ответ

Я предполагаю, что optionMaybe dimensParser при вводе ввода "hello,..." пытается dimensParser. Это не удается, поэтому optionMaybe возвращает успех с Nothing и не потребляет никакой части ввода.

Последняя часть является решающей: после того, как будет возвращено Nothing, входная строка, которую нужно проанализировать, все еще будет "hello,...".

В этот момент sepBy пытается проанализировать char ',', но безуспешно. Таким образом, он делает вывод, что список закончился, и завершает список вывода, не потребляя больше ввода.

Если вы хотите пропустить другие объекты, вам нужен "потребляющий" синтаксический анализатор, который возвращает Nothing вместо optionMaybe. Однако этому парсеру необходимо знать, сколько потреблять: в вашем случае до запятой.

Возможно вам понадобится что-то вроде (непроверено)

(   try (Just <$> dimensParser) 
<|> (noneOf "," >> return Nothing))
    `sepBy` char ','
1
chi 1 Янв 2018 в 10:49

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

Мы начинаем с «300x300, привет, 5x5,6x6», наш текущий синтаксический анализатор - optionMaybe .... Правильно ли наш dimensParser разбирает измерение? Давай проверим:

  w <- many1 digit   -- yes, "300"
  char 'x'           -- yes, "x"
  h <- many1 digit   -- yes, "300"
  return (read w, read h) -- never fails

Мы успешно проанализировали первое измерение. Следующий токен - это ,, поэтому sepBy успешно его анализирует. Далее мы пытаемся разобрать «привет» и терпим неудачу:

 w <- many1 digit -- no. 'h' is not a digit. Stop

Затем sepBy пытается проанализировать ,, но это невозможно, так как следующий токен - это 'h', а не ,. Следовательно, sepBy останавливается.

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

parse (dimensStringParser <* eof)

В любом случае, если вы хотите отбросить что-либо в списке, не являющееся измерением, вы можете использовать

dimensStringParser1 :: Parser (Maybe (Int, Int))
dimensStringParser1 = (Just <$> dimensParser) <|> (skipMany (noneOf ",") >> Nothing)

dimensStringParser = dimensStringParser1  `sepBy` char ','
3
Zeta 1 Янв 2018 в 10:50