Ответ 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
для управления этим внутренним состоянием и установить "ссылочный столбец" , как вам нравится.
Ура!