次の正規表現を検討してください。
^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","")]]
そして、どちらも私が望むように機能します。しかし、それを Megaparsec に直接音訳しようとすると、うまくいきません。
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 = ""}})]
これは、Megaparsec がデフォルトでバックトラックしないことに起因することを知っています。さまざまな場所に貼り付けてこれを修正しようとしまし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"]
しかし、それは混乱のように見えます!特に、パターンの大部分を実質的に 2 回指定しなければならなかったことは好ましくありません。^foo/(?:[^=/]|/(?!baz=))+/baz=((?:.(?![^,]*$))*),[^,]*$
そして技術的には、それは私の最初の正規表現ではなく、 regex と同等ではないでしょうか? そのパーサーを書くためのより良い方法が必要です。どうすればいいのですか?
編集:私もこの方法で試しましたが、これも機能します(いいえ、間違って受け入れます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
。つまり、正規表現に実際にマップされていないことを意味します。