如何实现parsec的try方法?

时间:2019-12-06 05:34:17

标签: parsing haskell monads

我想实现与parsec的try方法相同的方法。但是,我没有使用解析转换器,而是使用了一个保存状态的Parser对象:

try :: Parser String -> Parser String
try p = P $ \s -> case doParse p s of
            [] -> [("", s)]
            [(a, s')] -> [(a, s')]

我很确定我的努力在这里没有完成。因此,任何帮助将不胜感激。

newtype Parser a = P (String -> [(a, String)])

instance Functor Parser where
   fmap = liftA

instance Applicative Parser where
   pure x    = P (\cs -> [ (x,cs) ])
   p1 <*> p2 = P (\cs -> do (f, cs')  <- doParse p1 cs
                            (x, cs'') <- doParse p2 cs'
                            return (f x, cs''))

instance Monad Parser where
    return = pure
    p1 >>= f = P (\cs -> let [(a, s)] = doParse p1 cs
                         in doParse (f a) s)

instance Alternative Parser where
  -- the parser that always fails
  empty     = P $ const []
  -- | Combine two parsers together in parallel, but only use the
  -- first result. This means that the second parser is used only
  -- if the first parser completely fails.
  p1 <|> p2 = P $ \cs -> case doParse (p1 `choose` p2) cs of
                          []   -> []
                          x:_ -> [x]

doParse :: Parser a -> String -> [(a, String)]
doParse (P p) = p

编辑:

我想解析的示例:

<!-- This is a random 
     HTML
   Comment -->

通过运行:

doParse simpleComment excomment

simpleComment取自Parsec网站以及manyTill:

simpleComment   = do{ string "<!--"
                     ; manyTill anyChar (try (string "-->"))
                     }

manyTill p end      = scan
                    where
                      scan  = do{ _ <- end; return [] }
                            <|>
                              do{ x <- p; xs <- scan; return (x:xs) }

1 个答案:

答案 0 :(得分:2)

您不需要try这样的解析器。或者,如果您真的想要一个,可以将其定义为:

try :: Parser a -> Parser a
try = id

Parsec在使用某些输入后失败与不使用任何输入时失败之间进行了区分。例如,如果您查看Parsec的(<|>)的文档,则会发现一条重要的强调文字:

  

解析器p <|> q首先应用p。如果成功,则返回p的值。如果p在不消耗任何输入的情况下失败 ,则尝试解析器q

未声明的事实是,如果p在消耗了部分输入后失败,则整个操作都会失败,并且q不会尝试。这意味着在Parsec中,解析器:

broken :: Parser String
broken = string "hello" <|> string "hi"

不起作用。它可以解析"hello",但不能解析"hi",因为第一个解析器string "hello"在发现其余不匹配之前消耗了"h",因此它永远不会尝试string "hi"解析器:

> parseTest broken "hello"
"hello"
> parseTest broken "hi"
parse error at (line 1, column 1):
unexpected "i"
expecting "hello"

要解决此问题,您必须使用try,它允许您覆盖此规则:

okay :: Parser String
okay = try (string "hello") <|> string "hi"

给予:

> parseTest okay "hello"
"hello"
> parseTest okay "hi"
"hi"

您的解析器是不同的。即使您没有给出choose的定义,我也可以从您的Parser类型看出,它没有明智的方式来表示“消耗输入后失败”与“不消耗输入后失败”的信号,因此您的p <|> q的实现无疑会在q失败时尝试p,即使在处理了一点输入后也失败了。

因此,您的解析器的作用就像每个单独的项都被try包围一样,因此try函数将是多余的。