问题描述
我遇到了以下解析问题:
从有限的字符集中解析一些可能包含零个或多个元素的文本字符串,直到但不包括一组终止字符中的一个。应通过 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
,而我希望会出现错误消息。
两个问题:
解决方法
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" 应该再次报错,因为 '&' 将结束第一个组件但不会被接受为分隔符。
,解析器对象通常所做的是从输入流中提取它应该接受的任何子集。这是通常的规则。
在这里,您似乎希望解析器接受跟随特定内容的字符串。从您的示例中,它是文件结尾 (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")
λ>
,
fail
通常不会结束解析。它只是继续下一个选择。在这种情况下,它选择由 many
组合器引入的空列表替代项,因此它会停止解析而不会显示错误消息。
我认为解决您的问题的最佳方法是指定输入必须以终止字符结尾,这意味着它不能像这样中途“成功”。您可以使用 notFollowedBy
或 lookAhead
组合符来做到这一点。 Here is the relevant part of the megaparsec tutorial。