Symparsec

Type level string parser combinators. A Parsec-like for Symbols; thus, Symparsec! With many of the features you’d expect:

  • define parsers compositionally, largely as you would on the term level
  • define complex parsers, including mutually recursive ones (e.g. expression parsers!)
  • pretty, detailed parse errors
  • good performance (probably? please help me benchmark!)

Requires GHC >= 9.6.

Examples

Define a type-level parser:

import Symparsec
import DeFun.Core
type PExample = Skip 1 *> Tuple (Isolate 2 NatHex) (Literal "_" *> TakeRest)

Use it to parse a type-level string (in a GHCi session):

ghci> :k! Run PExample "xFF_etc"
Run ...
= Right '( '(255, "etc"), "")

See the Symparsec.Example namespace for further examples.

Why?

Via GHC.Generics, we may inspect Haskell data types on the type level. Constructor names are Symbols. Ever reify these, then perform some sort of checking or parsing on the term level? Symparsec does the parsing on the type level instead. Catch bugs earlier, get faster runtime.

Also type-level Haskell authors deserve fun libraries too!!

Limitations

Symparsec defines a lot of low-level parsers you would expect on the term level, even monadic (bind) and applicative (ap) parser combinators. The key limitation Symparsec grapples with is Haskell having no type-level binders. This means:

  • no lets, no wheres
  • no do notation

The workaround is writing extra functions, and writing uglier functions. Otherwise, I believe an average term-level Parsec-like parser should look comparable to a type-level Symparsec one.

Writing complex type-level Haskell programs in 2025 is unintuitive. I intend to provide guides on writing Symparsec parsers that walk through type-level programming design patterns and solutions. Please ping me on the issues tab if you read this, and can’t find any such guides!

Contributing

I would gladly accept further combinators or other suggestions. Please add an issue or pull request, or contact me via email or whatever (I’m raehik everywhere).

License

Provided under the MIT license. See LICENSE for license text.

Changes

2.0.0 (2025-10-11)

Full rewrite.

  • parsers are now much more general: mutually-recursive parsers are game
    • added an example parser for a simple expression AST
  • added parsers matching Functor, Applicative, Monad type class methods
  • temporarily removed singling (will be lots of work)

Simple parsers written with the provided combinators should still function the same, or with minimal changes.

1.1.1 (2024-06-15)

  • add Apply combinator (effectively fmap)
  • add some more runners and utils (handy for generic-data-functions)

1.1.0 (2024-06-01)

  • add While combinator
  • add Count combinator
  • re-add :<|>: re-export in Symparsec.Parsers

1.0.1 (2024-05-27)

  • add TakeRest combinator
  • re-add :<|>: combinator with more accurate behaviour clarification

1.0.0 (2024-05-25)

  • small rewrite, changing how Done works (now non-consuming)
  • single all parsers
    • …except :<|>:, which is disabled for now due to complexity

0.4.0 (2024-05-12)

  • rebrand from symbol-parser to Symparsec
  • rename Drop -> Skip (more commonly used for monadic parsers)
  • document parsers
  • provide fixity declarations for infix binary combinators

0.3.0 (2024-04-20)

  • add new parsers: Take, :<|>:
  • tons of cleanup, renaming (RunParser -> Run)
  • add handful of tests (via type-spec)

0.2.0 (2024-04-19)

  • add two more combinators: End, Literal
  • remove some old code (Data.Type.Symbol, Data.Type.Symbol.Natural)
  • fix base lower bound (at least base-4.16, == GHC 9.2)
  • style: don’t tick promoted constructors unless necessary for disambiguation

0.1.0 (2024-04-17)

Initial release.

  • basic combinators: Drop, Isolate, NatHex (etc.), sequencing
  • acceptable error messages