Вставить символ в поток символов комбинатора парсера в 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"),

Возможные варианты:

  • Создайте класс и экземпляр для объединения обернутых этапов парсера. (Возможно, переносит состояние парсера.)
  • Создать промежуточное представление, поток токенов
Другие вопросы по тегам