Как превратить это регулярное выражение в парсер Megaparse c, не создавая путаницы? - PullRequest
4 голосов
/ 09 января 2020

Рассмотрим это регулярное выражение:

^foo/[^=]+/baz=(.*),[^,]*$

Если я запускаю его на foo/bar/baz=one,two, оно совпадает, и подгруппа захватывает one. Если я запускаю его на foo/bar/baz/bar/baz=three,four,five, он совпадает, и подгруппа захватывает three,four.

Я знаю, как превратить это в парсер regex-applicative или парсер ReadP:

import Text.Regex.Applicative
match (string "foo/" *> some (psym (/= '=')) *> string "/baz=" *> many anySym <* sym ',' <* many (psym (/= ','))) <$> ["foo/bar/baz=one,two", "foo/bar/baz/bar/baz=three,four,five"]
-- [Just "one",Just "three,four"]
import Text.ParserCombinators.ReadP
readP_to_S (string "foo/" *> many1 (satisfy (/= '=')) *> string "/baz=" *> many get <* char ',' <* many (satisfy (/= ',')) <* eof) <$> ["foo/bar/baz=one,two", "foo/bar/baz/bar/baz=three,four,five"]
-- [[("one","")],[("three,four","")]]

И оба они работают так, как я хочу. Но когда я пытаюсь транслитерировать это прямо в Megaparse c, это плохо:

import Text.Megaparsec
parse (chunk "foo/" *> some (anySingleBut '=') *> chunk "/baz=" *> many anySingle <* single ',' <* many (anySingleBut ',') <* eof) "" <$> ["foo/bar/baz=one,two", "foo/bar/baz/bar/baz=three,four,five"]
-- [Left (ParseErrorBundle {bundleErrors = TrivialError 11 (Just (Tokens ('=' :| "one,"))) (fromList [Tokens ('/' :| "baz=")]) :| [], bundlePosState = PosState {pstateInput = "foo/bar/baz=one,two", pstateOffset = 0, pstateSourcePos = SourcePos {sourceName = "", sourceLine = Pos 1, sourceColumn = Pos 1}, pstateTabWidth = Pos 8, pstateLinePrefix = ""}}),Left (ParseErrorBundle {bundleErrors = TrivialError 19 (Just (Tokens ('=' :| "thre"))) (fromList [Tokens ('/' :| "baz=")]) :| [], bundlePosState = PosState {pstateInput = "foo/bar/baz/bar/baz=three,four,five", pstateOffset = 0, pstateSourcePos = SourcePos {sourceName = "", sourceLine = Pos 1, sourceColumn = Pos 1}, pstateTabWidth = Pos 8, pstateLinePrefix = ""}})]

Я знаю, что это происходит из Megaparse c, а не с возвратом по умолчанию. Я пытался исправить это, просто вставив try в кучу разных мест, но я не мог заставить это работать. В конце концов я получил это чудовище с notFollowedBy на работу:

import Text.Megaparsec
parse (chunk "foo/" *> some (noneOf "=/" <|> try (single '/' <* notFollowedBy (chunk "baz="))) *> chunk "/baz=" *> many (try (anySingle <* notFollowedBy (many (anySingleBut ',') <* eof))) <* single ',' <* many (anySingleBut ',') <* eof) "" <$> ["foo/bar/baz=one,two", "foo/bar/baz/bar/baz=three,four,five"]
-- [Right "one",Right "three,four"]

Но это похоже на беспорядок! В частности, мне не нравится, что мне фактически пришлось указывать большую часть шаблона дважды. И технически разве это не было бы эквивалентно регулярному выражению ^foo/(?:[^=/]|/(?!baz=))+/baz=((?:.(?![^,]*$))*),[^,]*$, а не моему начальному регулярному выражению? Должен быть лучший способ написать этот парсер. Как мне это сделать?


Редактировать: я тоже пробовал это так, , который также работает (нет, он неправильно принимает foo//baz=,):

import Text.Megaparsec
parse (chunk "foo/" *> (some . try $ many (noneOf "=/") *> single '/') *> chunk "baz=" *> ((++) <$> many (anySingleBut ',') <*> (concat <$> manyTill ((:) <$> single ',' <*> many (anySingleBut ',')) (try $ single ',' *> many (anySingleBut ',') *> eof)))) "" <$> ["foo/bar/baz=one,two", "foo/bar/baz/bar/baz=three,four,five"]
-- [Right "one",Right "three,four"]

Хотя это выглядит так же грязно, а manyTill означает, что больше не отображается ни на какое регулярное выражение.

1 Ответ

2 голосов
/ 09 января 2020

Не читая внимательно, я полагаю, что эта проблема доставляет вам неприятности:

(.*),[^,]*

Если так, рассмотрите возможность использования

sepBy (many (noneOf ",")) (string ",")

, которая будет анализировать список запятых -отделенные вещи. Затем снова вставьте запятые между всеми, кроме последнего элемента этого списка, в чистом коде (например, с хорошо расположенным fmap).

Из комментариев, кажется, у вас также есть некоторые проблемы с этим часть:

/[^=]+/baz=

Вы могли бы рассмотреть что-то вроде этого как перевод для этого:

slashPath = string "/" <++> path
path = string "baz=" <|> (many (noneOf "=/") <++> slashPath)
(<++>) = liftA2 (++)
...