attoparsec 中的条件先行
Conditional lookahead in attoparsec
假设有一个数据结构表示一个文本,里面有注释。
data TWC
= T Text TWC -- text
| C Text TWC -- comment
| E -- end
deriving Show
因此字符串像
"Text, {-comment-}, and something else"
可以编码为
T "Text, " (C "comment" (T ", and something else" E))
注释块和 E
的解析器非常简单:
twcP :: Parser TWC
twcP = eP <|> cP <|> tP
cP :: Parser TWC
cP = do
_ <- string "{-"
c <- manyTill anyChar (string "-}")
rest <- cP <|> tP <|> eP
return (C (pack c) rest)
eP :: Parser TWC
eP = do
endOfInput
return E
以如此简单的方式实现文本块的解析器
tP :: Parser TWC
tP = do
t <- many1 anyChar
rest <- cP <|> eP
return (T (pack t) rest)
由于其贪婪的本性,使其将评论部分作为文本使用
> parseOnly twcP "text{-comment-}"
Right (T "text{-comment-}" E)
it ∷ Either String TWC
所以,问题是如何表达直到输入结束或直到注释部分的解析逻辑?换句话说,如何实现条件先行解析器?
你是对的,有问题的代码是tP
的第一行,它贪婪地解析文本而不在注释处停止:
tP = do
t <- many1 anyChar
在解决这个问题之前,我首先想稍微重构一下您的代码以引入助手并使用应用样式,并将有问题的代码隔离到 text
助手中:
-- Like manyTill, but pack the result to Text.
textTill :: Alternative f => f Char -> f b -> f Text
textTill p end = pack <$> manyTill p end
-- Parse one comment string
comment :: Parser Text
comment = string "{-" *> textTill anyChar (string "-}")
-- Parse one non-comment text string (problematic implementation)
text :: Parser Text
text = pack <$> many1 anyChar
-- TWC parsers:
twcP :: Parser TWC
twcP = eP <|> cP <|> tP
cP :: Parser TWC
cP = C <$> comment <*> twcP
eP :: Parser TWC
eP = E <$ endOfInput
tP :: Parser TWC
tP = T <$> text <*> twcP
要实现前瞻,我们可以使用 lookAhead
组合器,它应用解析器而不消耗输入。这允许我们进行 text
解析,直到它达到 comment
(不消耗它)或 endOfInput
:
-- Parse one non-comment text string (working implementation)
text :: Parser Text
text = textTill anyChar (void (lookAhead comment) <|> endOfInput)
通过该实施,twcP
的行为符合预期:
ghci> parseOnly twcP "text{-comment-} post"
Right (T "text" (C "comment" (T " post" E)))
假设有一个数据结构表示一个文本,里面有注释。
data TWC
= T Text TWC -- text
| C Text TWC -- comment
| E -- end
deriving Show
因此字符串像
"Text, {-comment-}, and something else"
可以编码为
T "Text, " (C "comment" (T ", and something else" E))
注释块和 E
的解析器非常简单:
twcP :: Parser TWC
twcP = eP <|> cP <|> tP
cP :: Parser TWC
cP = do
_ <- string "{-"
c <- manyTill anyChar (string "-}")
rest <- cP <|> tP <|> eP
return (C (pack c) rest)
eP :: Parser TWC
eP = do
endOfInput
return E
以如此简单的方式实现文本块的解析器
tP :: Parser TWC
tP = do
t <- many1 anyChar
rest <- cP <|> eP
return (T (pack t) rest)
由于其贪婪的本性,使其将评论部分作为文本使用
> parseOnly twcP "text{-comment-}"
Right (T "text{-comment-}" E)
it ∷ Either String TWC
所以,问题是如何表达直到输入结束或直到注释部分的解析逻辑?换句话说,如何实现条件先行解析器?
你是对的,有问题的代码是tP
的第一行,它贪婪地解析文本而不在注释处停止:
tP = do
t <- many1 anyChar
在解决这个问题之前,我首先想稍微重构一下您的代码以引入助手并使用应用样式,并将有问题的代码隔离到 text
助手中:
-- Like manyTill, but pack the result to Text.
textTill :: Alternative f => f Char -> f b -> f Text
textTill p end = pack <$> manyTill p end
-- Parse one comment string
comment :: Parser Text
comment = string "{-" *> textTill anyChar (string "-}")
-- Parse one non-comment text string (problematic implementation)
text :: Parser Text
text = pack <$> many1 anyChar
-- TWC parsers:
twcP :: Parser TWC
twcP = eP <|> cP <|> tP
cP :: Parser TWC
cP = C <$> comment <*> twcP
eP :: Parser TWC
eP = E <$ endOfInput
tP :: Parser TWC
tP = T <$> text <*> twcP
要实现前瞻,我们可以使用 lookAhead
组合器,它应用解析器而不消耗输入。这允许我们进行 text
解析,直到它达到 comment
(不消耗它)或 endOfInput
:
-- Parse one non-comment text string (working implementation)
text :: Parser Text
text = textTill anyChar (void (lookAhead comment) <|> endOfInput)
通过该实施,twcP
的行为符合预期:
ghci> parseOnly twcP "text{-comment-} post"
Right (T "text" (C "comment" (T " post" E)))