如何使嵌套的 megaparsec 解析器失败?
How to fail a nested megaparsec parser?
我遇到了以下解析问题:
解析一些可能包含来自有限字符集的零个或多个元素的文本字符串,最多但不包括一组终止字符中的一个。 Content/no内容应通过Maybe
表示。终止字符可能以转义形式出现在字符串中。任何不允许的字符解析都应该失败。
这是我想出的(简化):
import qualified Text.Megaparsec as MP
-- Predicate for admissible characters, not including the control characters.
isAdmissibleChar :: Char -> Bool
...
-- Predicate for control characters that need to be escaped.
isControlChar :: Char -> Bool
...
-- The escape character.
escChar :: Char
...
pComponent :: Parser (Maybe Text)
pComponent = do
t <- MP.many (escaped <|> regular)
if null t then return Nothing else return $ Just (T.pack t)
where
regular = MP.satisfy isAdmissibleChar <|> fail "Inadmissible character"
escaped = do
_ <- MC.char escChar
MP.satisfy isControlChar -- only control characters may be escaped
比如说,允许的字符是大写的ASCII,转义是'\',控制是':'。
然后,以下正确解析:ABC\:D:EF
产生 ABC:D
。
但是,解析 ABC&D
,其中 &
是不可接受的,确实会产生 ABC
而我希望得到一条错误消息。
两个问题:
- 为什么
fail
结束解析而不是使解析器失败?
- 上述方法是否可以解决问题,或者是否有一种“正确的”、规范的方法来解析我不知道的此类终止字符串?
fail
一般不会结束解析。它只是继续下一个选择。在这种情况下,它选择了由 many
组合器引入的空列表替代项,因此它会停止解析而不会出现错误消息。
我认为解决你的问题最好的方法是指定输入必须以终止符结尾,也就是说不能这样“中途”“成功”。您可以使用 notFollowedBy
or lookAhead
combinators. Here is the relevant part of the megaparsec tutorial.
many
必须允许其子解析器在没有整个解析的情况下失败一次
失败 - 例如 many (char 'A') *> char 'B'
,解析时
“AAAB”,必须无法解析 B 才能知道它到达了结尾
作为.
您可能需要 manyTill
来识别终结符
明确地。像这样:
MP.manyTill (escaped <|> regular) (MP.satisfy isControlChar)
如果 isControlChar 不接受“&”,“ABC&D”会在此处给出错误。
或者,如果您想解析多个组件,您可以保留
pComponent 的现有定义并将其与 sepBy
或类似的一起使用,例如:
MP.sepBy pComponent (MP.satisfy isControlChar)
如果您在此之后还检查文件结尾,例如:
MP.sepBy pComponent (MP.satisfy isControlChar) <* MP.eof
那么 "ABC&D" 应该会再次报错,因为 '&' 将结束第一个组件,但不会被接受为分隔符。
解析器对象通常所做的是从输入流中提取它应该接受的任何子集。这是通常的规则。
在这里,您似乎希望解析器接受后跟[=42=] 特定内容的字符串。根据您的示例,它是文件结尾 (eof) 或字符“:”。所以你可能要考虑 向前看.
环境及辅助功能:
import Data.Void (Void)
import qualified Data.Text as T
import qualified Text.Megaparsec as MP
import qualified Text.Megaparsec.Char as MC
type Parser = MP.Parsec Void T.Text
-- Predicate for admissible characters, not including the control characters.
isAdmissibleChar :: Char -> Bool
isAdmissibleChar ch = elem ch ['A' .. 'Z']
-- Predicate for control characters that need to be escaped.
isControlChar :: Char -> Bool
isControlChar ch = elem ch ":"
-- The escape character:
escChar :: Char
escChar = '\'
终止解析器,用于前瞻:
termination :: Parser ()
termination = MP.eof MP.<|> do
_ <- MP.satisfy isControlChar
return ()
修改后的 pComponent 解析器:
pComponent :: Parser (Maybe T.Text)
pComponent = do
txt <- MP.many (escaped MP.<|> regular)
MP.lookAhead termination -- **CHANGE HERE**
if (null txt) then (return Nothing) else (return $ Just (T.pack txt))
where
regular = (MP.satisfy isAdmissibleChar) MP.<|> (fail "Inadmissible character")
escaped = do
_ <- MC.char escChar
MP.satisfy isControlChar -- only control characters may be escaped
测试实用程序:
tryParse :: String -> IO ()
tryParse str = do
let res = MP.parse pComponent "(noname)" (T.pack str)
putStrLn $ (show res)
让我们尝试重新运行您的示例:
$ ghci
λ>
λ> :load q67809465.hs
λ>
λ> str1 = "ABC\:D:EF"
λ> putStrLn str1
ABC\:D:EF
λ>
λ> tryParse str1
Right (Just "ABC:D")
λ>
这样就成功了,如愿。
λ>
λ> tryParse "ABC&D"
Left (ParseErrorBundle {bundleErrors = TrivialError 3 (Just (Tokens ('&' :| ""))) (fromList [EndOfInput]) :| [], bundlePosState = PosState {pstateInput = "ABC&D", pstateOffset = 0, pstateSourcePos = SourcePos {sourceName = "(noname)", sourceLine = Pos 1, sourceColumn = Pos 1}, pstateTabWidth = Pos 8, pstateLinePrefix = ""}})
λ>
所以失败了,如愿。
尝试我们的 2 个可接受的终止上下文:
λ> tryParse "ABC:&D"
Right (Just "ABC")
λ>
λ>
λ> tryParse "ABCDEF"
Right (Just "ABCDEF")
λ>
我遇到了以下解析问题:
解析一些可能包含来自有限字符集的零个或多个元素的文本字符串,最多但不包括一组终止字符中的一个。 Content/no内容应通过Maybe
表示。终止字符可能以转义形式出现在字符串中。任何不允许的字符解析都应该失败。
这是我想出的(简化):
import qualified Text.Megaparsec as MP
-- Predicate for admissible characters, not including the control characters.
isAdmissibleChar :: Char -> Bool
...
-- Predicate for control characters that need to be escaped.
isControlChar :: Char -> Bool
...
-- The escape character.
escChar :: Char
...
pComponent :: Parser (Maybe Text)
pComponent = do
t <- MP.many (escaped <|> regular)
if null t then return Nothing else return $ Just (T.pack t)
where
regular = MP.satisfy isAdmissibleChar <|> fail "Inadmissible character"
escaped = do
_ <- MC.char escChar
MP.satisfy isControlChar -- only control characters may be escaped
比如说,允许的字符是大写的ASCII,转义是'\',控制是':'。
然后,以下正确解析:ABC\:D:EF
产生 ABC:D
。
但是,解析 ABC&D
,其中 &
是不可接受的,确实会产生 ABC
而我希望得到一条错误消息。
两个问题:
- 为什么
fail
结束解析而不是使解析器失败? - 上述方法是否可以解决问题,或者是否有一种“正确的”、规范的方法来解析我不知道的此类终止字符串?
fail
一般不会结束解析。它只是继续下一个选择。在这种情况下,它选择了由 many
组合器引入的空列表替代项,因此它会停止解析而不会出现错误消息。
我认为解决你的问题最好的方法是指定输入必须以终止符结尾,也就是说不能这样“中途”“成功”。您可以使用 notFollowedBy
or lookAhead
combinators. Here is the relevant part of the megaparsec tutorial.
many
必须允许其子解析器在没有整个解析的情况下失败一次
失败 - 例如 many (char 'A') *> char 'B'
,解析时
“AAAB”,必须无法解析 B 才能知道它到达了结尾
作为.
您可能需要 manyTill
来识别终结符
明确地。像这样:
MP.manyTill (escaped <|> regular) (MP.satisfy isControlChar)
如果 isControlChar 不接受“&”,“ABC&D”会在此处给出错误。
或者,如果您想解析多个组件,您可以保留
pComponent 的现有定义并将其与 sepBy
或类似的一起使用,例如:
MP.sepBy pComponent (MP.satisfy isControlChar)
如果您在此之后还检查文件结尾,例如:
MP.sepBy pComponent (MP.satisfy isControlChar) <* MP.eof
那么 "ABC&D" 应该会再次报错,因为 '&' 将结束第一个组件,但不会被接受为分隔符。
解析器对象通常所做的是从输入流中提取它应该接受的任何子集。这是通常的规则。
在这里,您似乎希望解析器接受后跟[=42=] 特定内容的字符串。根据您的示例,它是文件结尾 (eof) 或字符“:”。所以你可能要考虑 向前看.
环境及辅助功能:
import Data.Void (Void)
import qualified Data.Text as T
import qualified Text.Megaparsec as MP
import qualified Text.Megaparsec.Char as MC
type Parser = MP.Parsec Void T.Text
-- Predicate for admissible characters, not including the control characters.
isAdmissibleChar :: Char -> Bool
isAdmissibleChar ch = elem ch ['A' .. 'Z']
-- Predicate for control characters that need to be escaped.
isControlChar :: Char -> Bool
isControlChar ch = elem ch ":"
-- The escape character:
escChar :: Char
escChar = '\'
终止解析器,用于前瞻:
termination :: Parser ()
termination = MP.eof MP.<|> do
_ <- MP.satisfy isControlChar
return ()
修改后的 pComponent 解析器:
pComponent :: Parser (Maybe T.Text)
pComponent = do
txt <- MP.many (escaped MP.<|> regular)
MP.lookAhead termination -- **CHANGE HERE**
if (null txt) then (return Nothing) else (return $ Just (T.pack txt))
where
regular = (MP.satisfy isAdmissibleChar) MP.<|> (fail "Inadmissible character")
escaped = do
_ <- MC.char escChar
MP.satisfy isControlChar -- only control characters may be escaped
测试实用程序:
tryParse :: String -> IO ()
tryParse str = do
let res = MP.parse pComponent "(noname)" (T.pack str)
putStrLn $ (show res)
让我们尝试重新运行您的示例:
$ ghci
λ>
λ> :load q67809465.hs
λ>
λ> str1 = "ABC\:D:EF"
λ> putStrLn str1
ABC\:D:EF
λ>
λ> tryParse str1
Right (Just "ABC:D")
λ>
这样就成功了,如愿。
λ>
λ> tryParse "ABC&D"
Left (ParseErrorBundle {bundleErrors = TrivialError 3 (Just (Tokens ('&' :| ""))) (fromList [EndOfInput]) :| [], bundlePosState = PosState {pstateInput = "ABC&D", pstateOffset = 0, pstateSourcePos = SourcePos {sourceName = "(noname)", sourceLine = Pos 1, sourceColumn = Pos 1}, pstateTabWidth = Pos 8, pstateLinePrefix = ""}})
λ>
所以失败了,如愿。
尝试我们的 2 个可接受的终止上下文:
λ> tryParse "ABC:&D"
Right (Just "ABC")
λ>
λ>
λ> tryParse "ABCDEF"
Right (Just "ABCDEF")
λ>