4 ms·
> Can anyone give ballpark effort costs for writing a parser with a context-free grammar parser generator vs. parser combinators vs. by hand? The few times I’v
by bradrn 6y ago
> Can anyone give ballpark effort costs for writing a parser with a context-free grammar parser generator vs. parser combinators vs. by hand?
The few times I’ve attempted to use a parser generator, I could never figure them out. By contrast, when I started using parser combinators (in Haskell), I was able to start writing parsers almost immediately.
> I know good error messages are the main benefit of writing parsers by hand; can you also get those with parser combinators?
It depends on the library, of course, but they can get pretty good if you annotate your parsers correctly. For comparison, here’s the CSV parser from the article translated into Haskell (using ‘megaparsec’, https://hackage.haskell.org/package/megaparsec https://hackage.haskell.org/package/megaparsec):
-- stack --resolver lts-17.9 script --package megaparsec
module Main where
import Data.Bifunctor
import Data.Void
import Text.Megaparsec
import Text.Megaparsec.Char
type Parser = Parsec Void String
quotedChar :: Parser String
quotedChar = label "string character" $ string "\"\"" <|> (pure <$> anySingleBut '\"')
regularVal :: Parser String
regularVal = takeWhileP (Just "a non-escaped character") $ not . (`elem` ['\r', '\n', ','])
escapedVal :: Parser String
escapedVal = char '\"' *> (concat <$> many quotedChar) <* char '\"'
value :: Parser String
value = escapedVal <|> regularVal
line :: Parser [String]
line = sepBy1 value (char ',') <* newline
parseCSVFile :: String -> Either String [[String]]
parseCSVFile input = first errorBundlePretty $ parse (many line) "" input
main :: IO ()
main = do
putStrLn "INPUT:\n-----"
putStrLn input
putStrLn "\nOUTPUT:\n------"
case parseCSVFile input of
Left err -> putStrLn err
Right r -> print r
where
input =
"this,is,a,test\n\
\1,\"an \"\"escaped,string\",2,3\n\
\whoops,\"there is,an unpaired,quote"
And the output:
INPUT:
-----
this,is,a,test
1,"an ""escaped,string",2,3
whoops,"there is,an unpaired,quote
OUTPUT:
------
3:35:
|
3 | whoops,"there is,an unpaired,quote
| ^
unexpected end of input
expecting '"' or string character
> Do parser combinators handle error recovery and stuff like that, or are you responsible for it?
It’s dependent on the implementation, but with ‘megaparsec’ it’s a combination of both. It’s often OK by default — e.g. my example above has no explicit error recovery — but in more complex parsers, you’ll need to wrap some components with ‘try’, to indicate that it needs to backtrack if an error was found. (There’s other error-recovery combinators as well, such as ‘withRecovery’ and ‘observing’, but in practise you mostly end up using ‘try’.)