Parsley is a very fast parser combinator library that outperforms the other libraries in both the parsec family, as well as Happy. To make this possible, it makes use of Typed Template Haskell to generate the code for the parsers.
Parsley is distributed on Hackage, and can be added by depending on the package parsley
.
The version policy for parsley-core
adheres to the regular Haskell PVP, but the two major versions are distinguished: the first is the Public API major version, which represents backwards incompatible changes
in the regular PVP sense that could affect parsley
itself (note parsley
only imports from Parsley.Internal
itself); the second version is the
Internal API major version, which would only effect users who use part of the internal parsley
modules. As such, for people that are not explicitly importing anything from Parsley.Internal.*
, the second major version does not matter: 0.2.0.0
and 0.3.0.0
would be compatible, for instance.
To use it, you'll need to write you parsers in another file from where they will be used: this is due to Template Haskell.
By being a Selective Parser Combinator library, Parsley does not support monadic operations such
as (>>=)
or return
. Instead, the most powerful operations are select
or branch
. Most monadic
power can be recovered using the functionality provided by Parsley.Register
, as well as the
selectives.
The reason monads are not supported is because of the Staging: Parsley parsers are compiled ahead
of time to produce fast code, but this means the entirety of the parser must be known before any
input is provided, ruling out dynamic monadic operations. The use of staging (in this instance provided
by Typed Template Haskell) means that the signatures of the combinators do not correspond to their
counterparts in other libraries: they don't use raw values, they use code of values. A consequence
of this is that Parsley does not implement instances of Functor
, Applicative
, Alternative
,
or indeed Selective
; do
-notation also cannot be used even with ApplicativeDo
, except perhaps
if RebindableSyntax
is used.
Code is provided to the combinators by way of the datatype WQ
(or Defunc
if you're feeling fancy),
which pairs a normal value with its Haskell code representation:
data WQ a
makeQ :: a -> Code a -> WQ a -- or Defunc a
This gives us combinators like:
pure :: WQ a -> Parser a
satisfy :: WQ a -> Parser a
char :: Char -> Parser a
char c = satisfy (makeQ (== c) [||(== c)||])
Using WQ
explicitly like this can get annoying, which is what the parsley-garnish
package is for!
Currently, the garnish provides one plugin called OverloadedQuotes
, which replaces the behaviour of
the default Untyped Template Haskell quotes in a file so that they produce one of WQ
or Defunc
.
See the Parsley.OverloadedQuotesPlugin
module in the parsley-garnish
package for more information.
Currently, parsley-garnish
is not ready for GHC 9.2. Instead of using [|
and |]
as the builder
for Defunc
values, you can make use of the following CPP
macro instead:
{-# LANGUAGE CPP #-}
#define QQ(x) (makeQ (x) [||(x)||])
char :: Char -> Parser a
char c = satisfy QQ(== c)
This may require the use of the cpphs
buildtool.
In short, Parsley represents all parsers as Abstract Syntax Trees (ASTs). The representation of the parsers the users write is called the Combinator Tree, which is analysed and optimised by Parsley. This representation is then transformed into an Abstract Machine in CPS form, this is analysed further before being partially evaluated at compile-time to generate high quality Haskell code. For the long version, I'd recommend checking out the paper!
If you encounter a bug when using Parsley, try and minimise the example of the parser (and the input)
that triggers the bug. If possible, make a self contained example: this will help me to identify the
issue without too much issue. It might be helpful to import parsley-core:Parsley.Internal.Verbose
to provide a
debug dump that I can check out.
- This work spawned a paper at ICFP 2020: Staged Selective Parser Combinators
- Supports patterns and combinators from Haskell Symposium 2021: Design Patterns of Parser Combinators
For talks on how writing parsers changes when using Parsley see either of these:
- Garnishing Parsec with Parsley - Berlin Functional Programming Group, January 2021
- Exploring Parsley: Working with Staged Selective Parsers - MuniHac 2020
For the technical overview of how Parsley works:
- Staged Selective Parser Combinators - ICFP 2020
For information about how to write parsers cleanly:
- Design Patterns for Parser Combinators - Haskell 2021