Понимание многих в Мегапарсеке

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

В частности, меня озадачивает следующий минимальный код:

import Text.Megaparsec (Parsec, many, parseTest)
import Text.Megaparsec.Char (string)
import Data.Void

test :: Parsec Void String [String] -> String -> IO ()
test = parseTest

main = do
    test (many (string "a" >> string "b")) "ac" -- error
    test (many (string "a" <> string "b")) "ac" -- error
    test (many (string "ab")) "ac" -- []

Его вывод следующий:

1:2:
  |
1 | ac
  |  ^
unexpected 'c'
expecting 'b'
1:2:
  |
1 | ac
  |  ^
unexpected 'c'
expecting 'b'
[]

Я не понимаю, почему первые два теста терпят неудачу, а третий нет.

Парсер сначала обрабатывает "a", а это означает, что теперь он пытается проанализировать string "a" >> string "b" или string "a" <> string "b" и поэтому застревает. Вам следует добавить try, чтобы этого не произошло.

willeM_ Van Onsem 19.04.2024 11:46

Разве он также не зависает, когда пытается проанализировать string "ab" на "ac"?

Wirius 19.04.2024 11:51

Разница в том, что string "a" потребляет входные данные при успехе, и невозможно отменить их использование, если вы не обернете их в try.

n. m. could be an AI 19.04.2024 13:07

Что мне кажется очень странным, так это то, что Парсек и Мегапарсек ведут себя в этом отношении по-разному. Например, «тот же» код с использованием Parsec main = (parseTest (many (string "a" >> string "b")) "ac") >> (parseTest (many (string "a" <> string "b")) "ac") >> (parseTest (many (string "ab")) "ac") выдает три ошибки.

Wirius 19.04.2024 13:14

где код предыдущего комментария импортирует Parsec, many и parseTest из Text.Parsec и string из Text.Parsec.Char.

Wirius 19.04.2024 13:17

В Parsec string использует соответствующий префикс, вам следует использовать string'.

n. m. could be an AI 19.04.2024 13:58
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
6
73
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Нет никакой разницы в том, что будут анализировать парсеры string "a" >> string "b" и string "a" <> string "b", есть только разница в том, что они возвращают в случае успешного анализа (то есть "b" для первого и "ab" для второго), поэтому неудивительно, что первые два случая ведут себя одинаково ( то есть оба терпят неудачу).

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

Когда парсер string "a" >> string "b" работает с входными данными "ac", парсер string "a" успешно анализирует "a", а затем парсер string "b" не может проанализировать "c", не обрабатывая входные данные. Это приводит к сбою всего синтаксического анализатора string "a" >> string "b" из-за использования входных данных (вводных данных, потребляемых string "a", даже несмотря на то, что синтаксический анализ, вызвавший сбой string "b", сам не потреблял никаких входных данных).

Соберите все это вместе и many (string "a" >> string "b") запускает парсер string "a" >> string "b" до тех пор, пока он не выйдет из строя, что происходит при самом первом приложении по той причине, что парсер string "b" ожидал "b", но получил "c". Поскольку этот сбой потребляет входные данные ( "a" потребляется string "a"), весь синтаксический анализатор many (string "a" >> string "b") выходит из строя по той же «причине».

Парсер string "ab" немного отличается, и его поведение различается в Parsec и версиях Megaparsec начиная с 4.4.0 (см. документацию для токенов ).

В Parsec синтаксический анализатор string "ab", примененный к входным данным "ac", обрабатывает "a", а затем дает сбой на "c", поэтому он завершается с ошибкой после обработки входных данных. Вероятно, это плохая идея, поэтому Parsec представил string' в версии 3.1.16.0. Парсер string' "ab" поступает более разумно: он либо потребляет все "ab", либо терпит неудачу, не потребляя ничего. В частности, когда он применяется к входным данным "ac" и не может использовать "ab", он терпит неудачу, не потребляя входные данные.

В Мегапарсеке string изначально вел себя как string Парсека, но когда они исправили этот недостаток конструкции, вместо введения нового комбинатора string' они просто внесли несовместимое изменение в string, поэтому string "ab" в Мегапарсеке, как string' "ab" в Парсеке, либо потребляет весь "ab", либо терпит неудачу, ничего не потребляя.

(Чтобы еще больше запутать, у Megaparsec также есть комбинатор string', но это версия string без учета регистра и поэтому не имеет ничего общего с версией string' Parsec.)

Большое спасибо за ваш ответ. Объяснение string и string' особенно поучительно. Я использую Haskell в основном для небольших проектов и не особо забочусь об эффективности (в некоторой степени, конечно). Я хотел бы иметь комбинатор синтаксического анализатора звезды Клини, который никогда не подвел бы и не делал бы различия между eerr и cerr. Мне кажется, можно было бы определить kleene p = many (try p), но интересно: есть ли уже подобный оператор в mega.parsec?

Wirius 19.04.2024 19:54

Я взглянул на библиотеку ReadP, очень маленькую по сравнению с Parsec, которая не делает различия между пустыми ошибками и потребляемыми ошибками. Однако его many не жрёт и параллельно сохраняет все возможные префиксы. У них есть термин munch, который выглядит так, как я хочу, но в качестве входных данных он не принимает ReadP a, а только Char -> Bool. Есть ли способ заполучить пылкую звезду Клини, которая никогда не подведет?

Wirius 19.04.2024 19:59

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

K. A. Buhr 19.04.2024 21:22

Другие вопросы по теме