Потоковые процессоры представляют собой реагирующую систему а-ля Hoare CSP (Communicating Sequential Processes). Они получают сообщения из какого-то множества входных и выдают на выход результат - значения из множества выходных значений.
В принципе, разборщик методом "в ширину" и представляет из себя такой потоковый процессор - на входе он получает очередной символ входного потока а на выход выдает возможный результат успешного разбора (если он существует для уже просмотренной строки). Вот его-то мы и попытаемся построить.
data SP i o = Null | Get (i -> SP i o) | Put 0 (SP i o)Что означает, что процессоры создаются из пустого процессора - Null, - который может быть свободно отброшен, как ненужный, из процессора, который ожидает прием сообщения - Get, - и передает управление созданному на основе принятых данных процессору и из процессора, который имеет готовый результат, готов его отдать и передать управление своему второму аргументу.
mapSP f Null = Null mapSP f (Put o sp) = Put (f o) (mapSP f sp) mapSP f (Get spf) = Get (\i -> mapSP f (spf i))Вроде, все просто: в пустом процессоре отображать нечего, для процессора с готовым выходом надо отобразить выход и результаты работы процессора-продолжения и для ожидающего сообщения процессора надо отобразить результаты процессора, полученного после разбора сообщения.
pempty x = Put x NullПарсер, отвечающий за разбор лямбда (или эпсилон) правил (правил с пустым входом) может только вернуть константное значение.
ptoken t = Get (\i -> if i == t then Put i Null else Null)Для того, чтобы разобрать входной токен, нам надо его получить (отсюда Get), и, если он эквивалентен требуемому (i == t), то вернуть именно входной токен (Put i ..), поскольку нам интересно то, что прийдет на вход, а не наш известный токен.
Эквивалентность токенов лучше всего проверять только по их тэгу, примерно так:
(TokenVariableName _) == (TokenVariableName _) == TrueПоэтому, если мы получим на входе (TokenVariableName "x") то мы успешно сравним его с (TokenVariableName $ error "constant value"), но на выход попадет именно входной токен (TokenVariableName "x").
Простые парсеры закончились. ;)
ppar (Put o sp) spb = Put o (sp `ppar` spb) ppar sp (Put o spb) = Put o (sp `ppar` spb)Особого значения порядок выдачи готовых данных не имеет. Поэтому "левая альтернатива вперед" - вполне достойный выбор.
ppar Null sp = sp ppar sp Null = spЕсли нам нечего выбирать в одной из альтернатив, то мы перейдем к другой.
Остается только вариант, когда обе альтернативы ждут входной токен:
ppar (Get fa) (Get fb) = Get (\i -> (fa i) `ppar` (fb i))
pseq Null _ = NullЕсли мы не смогли разобрать начало строки, то мы гарантировано не сможем разобрать всю строку.
pseq (Get fsp) spb = Get (\i -> (fsp i) `pseq` spb)Если мы ждем токен, необходимый для разбора префикса строки, то мы должны подождать этот токен (Get), получить вытекающий из него разборщик начала строки за входным токеном (fsp i) и продолжить разбор строки (`pseq` spb).
А если у нас есть готовый выход, то мы считаем его функцией, которую мы применим к результату разбора остатка входной строки. И, также, нам надо разобрать новый префикс (sp из первого Put) и еще один суффикс (spb). Возможно, один из этих вариантов сработает, поэтому мы используем ppar.
pseq (Put o sp) spb =(mapSP o spb) `ppar` (sp `pseq` spb)Тип pseq на данный момент будет:
pseq :: SP i (a->b) -> SP i a -> SP i bЕго можно реализовать и так, что он будет иметь тип
pseq :: SP i a -> (a -> SP i b) -> SP i bтогда можно будет реализовать класс Monad, и использовать do-нотацию.
pfail = NullВсе понятно? ;)
prun parser tokens = runp parser tokens where runp (Put o sp) tokens = (o,tokens):runp sp tokens runp Null tokens = [] runp (Get f) [] = [] runp (Get f) (t:ts) = runp (f t) tsМы запустим выполнение потокового процессора, комбинируя выдаваемые им результаты с оставшимся списком входных токенов. Если потоковый процессор пуст, то мы завершим работу. Если для продолжения разбора требуется токен, но его нет - мы завершим работу. Если же токен есть, то мы применим его (f t), получим новый потоковый процессор и продолжим разбор с оставшимся входом.
pmany p = pmany1 p `ppar` pempty []Она основана на разборе списка из одного и более элементов:
pmany1 p = (pempty (:) `pseq` p) `pseq` pmany pкоторая основана на разборе списка элементов.
Вот, как этим пользоваться:
test = pmany (ptoken '1' `ppar` ptoken '2')И результат использования:
Parser> prun (pmany $ (ptoken '1' `ppar` ptoken '2')) "1213" [("","1213"),("1","213"),("12","13"),("121","3")] (557 reductions, 871 cells) Parser>Вот. Мы сконструировали разборщик, который умеет распознавать список из единичек и/или двоечек, и применили его ко входу, который содержит неверный символ - '3'.
В результате мы получили список промежуточных результатов, последний из которых не содержал пустой список в качестве остатка входа, что означает ошибку.
Haskell never ceases to amuze me. ;)
В чем дело - пока не знаю, просто не было времени разобраться.
Может, вы попробуете? ;)