3

我陷入以下解析问题:

从有限的字符集中解析一些可能包含零个或多个元素的文本字符串,最多但不包括一组终止字符中的一个。内容/无内容应通过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到 yield ABC:DABC&D但是,不可接受的 parsing&确实会产生ABC,而我会期望出现错误消息。

两个问题:

  • 为什么fail结束解析而不是解析器失败?
  • 上述方法对解决问题是否明智,或者是否有“正确”的规范方法来解析我不知道的终止字符串?
4

3 回答 3

2

many必须允许其子解析器在整个解析失败的情况下失败一次 - 例如many (char 'A') *> char 'B',在解析“AAAB”时,必须无法解析 B 才能知道它到达了 As 的末尾。

您可能想要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" 应该再次给出错误,因为 '&' 将结束第一个组件但不会被接受为分隔符。

于 2021-06-02T19:33:55.417 回答
1

解析器对象通常做的是从输入流中提取它应该接受的任何子集。这是通常的规则。

在这里,您似乎希望解析器接受后跟特定内容的字符串。从您的示例中,它是文件结尾(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")
 λ> 

于 2021-06-02T19:53:37.353 回答
0

fail一般不会结束解析。它只是继续下一个替代方案。在这种情况下,它会选择组合器引入的空列表替代方案many,因此它会停止解析而不会出现错误消息。

我认为解决您的问题的最佳方法是指定输入必须以终止字符结尾,这意味着它不能像这样中途“成功”。您可以使用notFollowedByorlookAhead组合符来做到这一点。这是 megaparsec 教程的相关部分

于 2021-06-02T19:27:17.533 回答