【问题标题】:How to generalize repetitive parsing with Parsec如何使用 Parsec 泛化重复解析
【发布时间】:2016-05-08 15:17:03
【问题描述】:

我正在通过解析文本文件中的行来学习使用 Parsec。我有以下内容:

import Text.Parsec (ParseError, parse)
import Text.Parsec.String (Parser)
import Text.Parsec.Char (anyChar, digit, char, oneOf)
import Control.Monad (void)
import qualified Text.Parsec.Combinator as C

data Action =
  ActionA Int Int
  | ActionB Int Int Int Int Int
  | ActionC Int Int Int
  deriving (Show)

parseWithEof :: Parser a -> String -> Either ParseError a
parseWithEof p = parse (p <* C.eof) ""

parseActionA :: Parser Action
parseActionA = do
  char 'A'
  void $ oneOf " "
  a <- C.many1 digit
  void $ oneOf " "
  b <- C.many1 digit
  return $ ActionA (read a) (read b)

parseActionB :: Parser Action
parseActionB = do
  char 'B'
  void $ oneOf " "
  a <- C.many1 digit
  void $ oneOf " "
  b <- C.many1 digit
  void $ oneOf " "
  c <- C.many1 digit
  void $ oneOf " "
  d <- C.many1 digit
  void $ oneOf " "
  e <- C.many1 digit
  return $ ActionB (read a) (read b) (read c) (read d) (read e)

parseActionC :: Parser Action
parseActionC = do
  char 'C'
  void $ oneOf " "
  a <- C.many1 digit
  void $ oneOf " "
  b <- C.many1 digit
  void $ oneOf " "
  c <- C.many1 digit
  return $ ActionC (read a) (read b) (read c)

我希望能够概括这些解析函数,因为我觉得它们是重复的。我不知道这是否可能,或者怎么可能。

我也想知道能不能有这样的功能:

parseAction :: String -> Either ParseError Action
parseAction input = 
    parseWithEof parseActionA input
    <some operator|combinator> parseWithEof parseActionB input
    <some operator|combinator> parseWithEof parseActionC input

所以当parseAction 接收到一个字符串作为参数时,它会尝试使用不同的解析器来解析它。如果没有解析器可以解析输入,我希望它返回 (Left ParseError),如果解析器成功解析输入,我希望它返回 (Right Action)。

有可能吗?

【问题讨论】:

    标签: haskell parsec


    【解决方案1】:

    使用 Applicative 组合器,您可以编写:

    num = do oneOf " "; fmap read (C.many1 digit)
    
    parseActionA = ActionA <$> (char 'A' >> num) <*> num
    
    parseActionB = ActionB <$> (char 'B' >> num) <*> num <*> num <*> num <*> num
    

    对于第二个问题,只需使用 &lt;|&gt;try

    parseAction = try parseActionA <|> try parseActionB <|> try parseActionC
    

    注意 - 最后一个解析器不需要try,但拥有它并没有什么坏处。此外,如果您对解析器的工作方式有足够的了解,您也许可以取消一些 trys。

    【讨论】:

    • 这在一定程度上简化了它,但我仍然需要写 n 次 &lt;*&gt; num 部分。有没有办法自动重复?
    • @OneEyeQuestion 看看能不能用replicateM
    • @ErikR 你正在使用try parseActionA &lt;|&gt; try parseActionB,但是根据这篇文章(blog.ezyang.com/2014/05/parsec-try-a-or-b-considered-harmful)我们应该尽量缩小try的范围。这是相同的情况还是不同的情况?
    • count :: Stream s m t =&gt; Int -&gt; ParsecT s u m a -&gt; ParsecT s u m [a] 基本上你可以得到parseActionB = do char 'B'; ([a,b,c,d,e]) &lt;- count 5 num; return $ ActionB a b c d e的效果
    • @OneEyeQuestion 将try 放在每个解析器安全之前。当您更多地了解 Parsec 的工作原理时,您可以删除不需要的那些以提高效率。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-06
    • 2013-01-18
    相关资源
    最近更新 更多