Вставить символ в поток символов комбинатора парсера в Haskell
Этот вопрос связан с обоими Parsec
а также uu-parsinglib
, Когда мы пишем синтаксические анализаторы, они обрабатывают потоки символов из компилятора. Можно ли как-то проанализировать символ и вернуть его (или вернуть другой символ обратно) во входной поток?
Я хочу, например, для разбора ввода "тест + 5", разобрать t
, e
, s
, t
и после признания test
шаблон, например v
символ обратно в поток символов, поэтому при продолжении процесса анализа мы сопоставляем v + 5
Я не хочу сейчас использовать это в каком-либо конкретном случае - я хочу глубоко изучить возможности.
3 ответа
Я не уверен, возможно ли это с этими синтаксическими анализаторами напрямую, но в целом вы можете сделать это, комбинируя парсеры с некоторой потоковой передачей, которая позволяет вводить остатки.
Например, используя attoparsec-канал, вы можете превратить парсер в канал, используя
sinkParser :: (AttoparsecInput a, MonadThrow m)
=> Parser a b -> Consumer a m b
где Consumer
это специальный вид канала, который не производит никаких выходных данных, только получает входные данные и возвращает окончательное значение.
Поскольку каналы поддерживают остатки, вы можете создать вспомогательный метод, который преобразует синтаксический анализатор, который при необходимости возвращает значение, которое должно быть передано в поток, в канал:
import Data.Attoparsec.Types
import Data.Conduit
import Data.Conduit.Attoparsec
import Data.Functor
reinject :: (AttoparsecInput a, MonadThrow m)
=> Parser a (Maybe a, b) -> Consumer a m b
reinject p = do
(lo, r) <- sinkParser p
maybe (return ()) leftover lo
return r
Затем вы конвертируете стандартные парсеры в каналы, используя sinkParser
и эти специальные парсеры, использующие reinject
, а затем объединить каналы вместо парсеров.
Это легко сделать в uu-parsinglib с помощью функции pSwitch. Но вопрос в том, почему вы хотите это сделать? Потому что v отсутствует на входе? В этом случае uu-parsinglib выполнит исправление ошибок автоматически, поэтому вам не нужно что-то подобное. В противном случае вы можете написать
pSwitch :: (st1 -> (st2, st2 -> st1)) -> P st2 a -> P st1 a
pInsert_v = pSwitch (\st1 -> (prepend v st2, id) (pSucceed ())
От того, как на самом деле добавляется v, зависит ваш тип состояния, поэтому вам нужно будет определить функцию
перед именемсам. Я не знаю, например, как такая вставка повлияет на текущую позицию в файле и т. Д.
Дойце Свирстра
Я думаю, что самый простой способ заархивировать это - создать многоуровневый парсер. Подумайте о комбинации лексер + парсер. Это чистый подход к этой проблеме.
Вы должны разделить два вида разбора. Синтаксический анализ поиска и замены переходит к первому анализатору, а синтаксический анализ build-the-AST - второму. Или вы можете создать промежуточное представление токена.
import Text.Parsec
import Text.Parsec.String
parserLvl1 :: Parser String
parserLvl1 = many (try (string "test" >> return 'v') <|> anyChar)
parserLvl2 :: Parser Plus
parserLvl2 = do text1 <- many (noneOf "+")
char '+'
text2 <- many (noneOf "+")
return $ Plus text1 text2
data Plus = Plus String String
deriving Show
wholeParse :: String -> Either ParseError Plus
wholeParse source = do res1 <- parse parserLvl1 "lvl1" source
res2 <- parse parserLvl2 "lvl2" res1
return res2
Теперь вы можете разобрать свой пример. wholeParse "test+5"
результаты в Right (Plus "v" "5")
,
Возможные варианты:
- Создайте класс и экземпляр для объединения обернутых этапов парсера. (Возможно, переносит состояние парсера.)
- Создать промежуточное представление, поток токенов