简体   繁体   中英

How to implement parsec's try method?

I want to implement the same method as parsec's try method. But instead of using a parse transformer, I am using a Parser object that holds state:

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

I'm pretty sure my effort is way off the mark here. So any help would be greatly appreciated.

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

EDIT:

Example of what I would like to parse:

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

By running:

doParse simpleComment excomment

simpleComment is taken from the Parsec website, along with 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) }

You don't need try for your kind of parser. Or, if you really want one, you can define it trivially as:

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

Parsec makes a distinction between failing after consuming some input and failing without consuming any input. For example, if you look at the documentation for Parsec's (<|>) , you'll find an important emphasized piece of text:

The parser p <|> q first applies p . If it succeeds, the value of p is returned. If p fails without consuming any input, parser q is tried.

Unstated is the fact that, if p fails after consuming partial input, the whole thing fails and q is never tried. That means that in Parsec, the parser:

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

doesn't work. It can parse "hello" , but it can't parse "hi" , because the first parser string "hello" consumes the "h" before discovering that the rest doesn't match, so it never tries the string "hi" parser:

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

To fix this, you have to use try , which allows you to override this rule:

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

giving:

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

Your parser is different. Even though you haven't given a definition of choose , I can tell from your Parser type that it has no sensible way of signalling "failure after consuming input" versus "failure without consuming input", so your implementation of p <|> q undoubtedly tries q whenever p fails, even if it fails after processing a bit of input.

As a result, your parser acts as if every single term is surrounded by try , so a try function would be redundant.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM