Haskell/Parsec: Как вы используете функции в Text.Parsec.Indent?

У меня возникли проблемы с тем, как использовать любую из функций модуля Text.Parsec.Indent, предоставленного indents пакет для Haskell, который является своего рода дополнением для Parsec.

Что делают все эти функции? Как их использовать?

Я могу понять краткое описание Haddock withBlock, и я нашел примеры использования withBlock, runIndent и IndentParser типа здесь, здесь и здесь. Я также понимаю документацию для четырех парсеров indentBrackets и друзей. Но многие вещи все еще меня путают.

В частности:

  • В чем разница между withBlock f a p и

    do aa <- a
       pp <- block p
       return f aa pp
    

    Аналогично, какая разница между withBlock' a p и do {a; block p}

  • В семействе функций indented и друзья, каков уровень ссылки? То есть, что такое 'ссылка?

  • Опять же, с функциями indented и друзьями, как их использовать? За исключением withPos, похоже, что они не принимают аргументов и все типа IParser () (IParser определяется как this или this), поэтому я предполагаю, что все, что они могут сделать, это создать ошибку или нет и что они должны появиться в блоке do, но я не могу понять детали.

    Я, по крайней мере, нашел несколько примеров использования withPos в исходный код, поэтому я, вероятно, могу понять это, если Я смотрю на него достаточно долго.

  • <+/> содержит полезное описание "<+/> - это чувствительные к отступу парсы, что ap принадлежит монадам" Это здорово, если вы хотите провести несколько сеансов, пытаясь обернуть голову вокруг ap, а затем выясните, как это аналогично парсеру. Остальные три комбинатора определяются со ссылкой на <+/>, что делает всю группу неприступной для новичков.

    Нужно ли использовать их? Могу ли я просто игнорировать их и вместо этого использовать do?

  • Обычный lexeme combator и < Парсер t224 > от Parsec будет счастливо потреблять новые строки в середине конструкции с несколькими токенами без жалоб. Но в языке с отступом иногда вы хотите прекратить разбор лексической конструкции или выбросить ошибку, если строка сломана, а следующая строка отступом меньше, чем должно быть. Как мне сделать это в Parsec?

  • В language Я пытаюсь разобрать, в идеале, правила, когда лексической структуре разрешено продолжать следующая строка должна зависеть от того, какие маркеры появляются в конце первой строки или в начале следующей строки. Есть ли простой способ достичь этого в Parsec? (Если это сложно, то это не то, что мне нужно в это время заботиться.)

Ответ 1

Итак, первый намек - взглянуть на IndentParser

type IndentParser s u a = ParsecT s u (State SourcePos) a

т.е. это a ParsecT, поддерживая дополнительную тщательную проверку SourcePos, абстрактного контейнера, который может быть использован для доступа, помимо прочего, к текущему номер столбца. Таким образом, он, вероятно, сохраняет текущий "уровень отступов" в SourcePos. Это было бы моим первоначальным предположением относительно того, что означает "уровень ссылки".

Короче говоря, indents дает вам новый вид Parsec, который чувствителен к контексту, в частности, чувствителен к текущему отступу. Я отвечу на ваши вопросы не по порядку.


(2) "Уровень ссылки" - это "вера", указанная в текущем состоянии контекста парсера, где начинается этот уровень отступов. Чтобы быть более ясным, позвольте мне привести несколько тестовых примеров (3).

(3) Чтобы начать экспериментировать с этими функциями, мы построим небольшой тестовый бегун. Он запустит анализатор со строкой, которую мы даем, а затем разворачиваем внутреннюю часть State с помощью initialPos, которую мы модифицируем. В коде

import Text.Parsec
import Text.Parsec.Pos
import Text.Parsec.Indent
import Control.Monad.State

testParse :: (SourcePos -> SourcePos) 
          -> IndentParser String () a 
          -> String -> Either ParseError a
testParse f p src = fst $ flip runState (f $ initialPos "") $ runParserT p () "" src

(Обратите внимание, что это почти runIndent, за исключением того, что я дал backdoor для изменения initialPos.)

Теперь мы можем взглянуть на indented. Изучив источник, я могу сказать, что он делает две вещи. Во-первых, это будет fail, если текущий номер столбца SourcePos меньше или равно - к "уровню ссылки", хранящемуся в SourcePos, хранящемся в State. Во-вторых, он несколько таинственным образом обновляет счетчик строк State SourcePos (не счетчик столбцов) как текущий.

Для моего понимания важно только первое поведение. Мы можем видеть здесь разницу.

>>> testParse id indented ""
Left (line 1, column 1): not indented

>>> testParse id (spaces >> indented) "   "
Right ()

>>> testParse id (many (char 'x') >> indented) "xxxx"
Right ()

Итак, чтобы преуспеть indented, нам нужно было потратить достаточно пробелов (или что-нибудь еще!), чтобы вытеснить позицию столбца за столбец "reference". В противном случае он не будет говорить "не с отступом". Подобное поведение существует в течение следующих трех функций: same терпит неудачу, если текущее положение и референтное положение не находятся на одной линии, sameOrIndented не выполняется, если текущий столбец строго меньше опорной колонны, если они не находятся на одной и той же линии, и checkIndent завершается сбой, если не совпадают текущие и ссылочные столбцы.

withPos немного отличается. Это не просто IndentParser, а IndentParser -combinator-он преобразует входной файл IndentParser в тот, который считает "ссылочный столбец" (SourcePos в State) именно там, где это было, когда мы называется withPos.

Это дает нам еще один намек, кстати. Это позволяет нам знать, что мы можем изменить ссылочный столбец.

(1) Теперь давайте посмотрим, как работают block и withBlock, используя наши новые операторы опорных столбцов нижнего уровня. withBlock реализуется в терминах block, поэтому мы начнем с block.

-- simplified from the actual source
block p = withPos $ many1 (checkIndent >> p)

Итак, block сбрасывает "столбец ссылок" как любой текущий столбец, а затем потребляет по крайней мере 1 разбора из p, если каждый из них имеет одинаковый отступ, как этот вновь заданный "столбец ссылок" . Теперь мы можем взглянуть на withBlock

withBlock f a p = withPos $ do
  r1 <- a
  r2 <- option [] (indented >> block p)
  return (f r1 r2)

Таким образом, он сбрасывает "ссылочный столбец" в текущий столбец, анализирует один синтаксический анализ a, пытается проанализировать indented block of p s, а затем объединяет результаты с помощью f. Ваша реализация почти правильна, за исключением того, что вам нужно использовать withPos, чтобы выбрать правильный "столбец ссылок" .

Затем, как только у вас есть withBlock, withBlock' = withBlock (\_ bs -> bs).

(5) Итак, indented и друзья - это в точности инструменты для этого: они заставят разбор немедленно сбой, если он неправильно отступил в отношении "контрольной позиции", выбранной withPos.

(4) Да, не беспокойтесь об этих парнях, пока вы не научитесь использовать Applicative style разбор в базе Parsec, Это часто гораздо более чистый, быстрый и простой способ указания разбора. Иногда они еще более мощные, но если вы понимаете Monad, то они почти всегда полностью эквивалентны.

(6) И в этом суть. Упомянутые до сих пор инструменты могут выполнять только отступ, если вы можете описать свой намеченный отступ с помощью withPos. Быстро, я не думаю, что можно указать withPos на основе успеха или неудачи других анализов... так что вам придется идти на другой уровень глубже. К счастью, механизм, который делает работу IndentParser, очевиден - это просто внутренняя монада State, содержащая SourcePos. Вы можете использовать lift :: MonadTrans t => m a -> t m a для управления этим внутренним состоянием и установить "ссылочный столбец" , как вам нравится.

Ура!