packages feed

symparsec-1.0.0: README.md

# Symparsec
Type level string (`Symbol`) parser combinators. Reify to runtime parsers with
guaranteed identical behaviour.

It's a Parsec-like for `Symbol`s; thus, Symparsec.

Previously named symbol-parser.

Requires GHC 9.6 for singling parsers.

## Features
* Define parsers compositionally, largely as you would on the term level.
* Pretty parse errors.
* Hopefully decent performance.
* Reify parsers to term level with guaranteed identical behaviour via a
  healthy dose of singletons.

## Examples
```haskell
ghci> import Symparsec
ghci> :k! Run (Drop 3 :*>: Isolate 2 NatDec :<*>: (Drop 3 :*>: NatHex)) "___10___FF"
...
= Right '( '(10, 255), "")
```

## 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.

## Design
### The parser
A parser is a 4-tuple of:

* a consuming character parser; given a character and a state, returns
  * `Cont s`: keep going, here's the next state `s`
  * `Done r`: parse successful with value `r`
  * `Err  E`: parse error, details in the `E` (a structured error)
* an end handler, which takes only a state, and can only return `Done` or `Err`
* an initial "raw" state
* a state initializer, which turns the initial "raw" state into the first state
  value (the indirection here assists singling)

Running a parser is simple:

* initialize state
* parse character by character until end of input, or `Done`/`Err`

Parsers may not communicate with the runner any other way. This means no
backtracking, chunking etc. This is a conscious decision, made for simplicity.

Note that due to character parsers being consuming, we often need to do a bit of
"internal lookahead", where we check if we expect to consume any more
characters, and if not then emit a `Done`. It also means that non-consuming
parsers such as `Take 0` are invalid for non-empty strings. The state
initializer should be used to catch such cases.

This is a rough overview of parser design. See the code and/or Haddock
documentation for precise details.

### Pitfall: Character parsers always consume
There is no backtracking or lookahead, that you do not implement yourself. This
keeps the parser execution extremely simple, but breaks null parsers such as
`Drop 0`, so these must be handled specially (unless you don't getting mind
stuck type families on misuse).

For concrete examples, see the implementation of `Drop` and `Literal`.

### Pitfall: Not all parsers are definable
* No changing parser state. Thus, parsers such as `Try :: Parser s r -> Parser
  (s, [Char]) r` are not definable. _Parsers may not backtrack._
  * Combinators such as `<|>` can emulate backtracking, but they are complex and
    hard to reason about (they may have bugs!).

### Feature: Parsers may be reified to use at runtime, with guaranteed same behaviour
TODO

## 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.