A Monadic Parser Combinator for Python.
Reason this release was yanked:
Deprecated due to fatal errors
Project description
Parsec 
A Monadic Parser Combinator for Python.
Parsec provides a declarative, modular way to build complex text parsers. By leveraging the powerful expressiveness of Monads, you can compose simple parsers like building blocks to handle complex grammar structures, while elegantly managing state and errors during the parsing process.
Unlike traditional parser generation tools (like Lark, PLY), Parsec allows you to define and combine your parsing logic directly within Python code, without the need for separate grammar files, making parser construction more flexible and dynamic.
Features
- Monadic A Parser is a monad.
- Declarative Declarative grammar definition
- Operator Operator-based combinators(
<<,>>,&,|,/,@) - Lazy Evalution Lazy evaluation for recursion
- Curried Curried functional interfaces
- Typed Support type inference
Intallation
Requirements
For full type support, Python 3.13+ is required.
Install from source code
git clone https://github.com/lunexnocty/parsec-python.git
cd parsec-python
uv install .
Quick Start
The parsec.text module provides a set of fundamental text parsers, such as number, char, blank, etc. The parsec.combinator module offers a rich, curried functional interface for combinators, enabling you to compose these basic parsers into powerful and expressive parsing logic.
To make parser composition even more intuitive, several operators have been overloaded:
p << fequivalent tof(p), enables successive application of combinators to a parser, supporting a piping style of composition.p >> fequivalent top.bind(f), represents the monadic bind operation, allowing the result of parser p to determine and sequence the next parser generated by function f. This enables context-sensitive and dependent parsing, as found in the Monad interface in functional programming.p @ fequivalent top.map(f), mapping thefover the result of the parserp. This corresponds to the Functor'smap(orfmap) operation, allowing you to transform the output of a parser in a declarative and compositional way.- The
&operator combines multiple parsers in sequence and collects their results into a tuple. - The
|operator tries each parser in sequence and returns the first successful result. - The
/operator is similar to|, but never backtracking.
Parsers can also be defined lazily to support recursive grammar definitions—essential for constructs like nested expressions or parentheses.
Below is an arithmetic expression grammar supporting operator precedence, parentheses, and left-associative chaining:
expr := <add_or_sub_exp>
add_or_sub_exp := <mul_or_div_exp> (('+' | '-') <mul_or_div_exp>)*
mul_or_div_exp := <factor> (('*' | '/') <factor>)*
factor := '(' <expr> ')' | <num>
num := { number }
Leveraging these features, you can build a fully functional arithmetic calculator with ease:
from parsec import combinator as C
from parsec import text as T
from parsec.utils import curry
def calc(op: str):
@curry
def _(x: int | float, y: int | float):
return {"+": x + y, "-": x - y, "*": x * y, "/": x / y}[op]
return _
expr = T.Parser[str, int | float]()
num = T.number << C.trim(T.blank)
factor = expr << C.between(T.open_round)(T.close_round) | num
mul_or_div = T.char("*") | T.char("/") # operator `|`
mul_or_div_op = (mul_or_div << C.trim(T.blank)) @ calc # priority of `@` is higher than `<<`
mul_or_div_expr = factor << C.chainl1(mul_or_div_op)
add_or_sub = T.item << C.range("+-") # use `range` combinator
add_or_sub_op = (add_or_sub << C.trim(T.blank)) @ calc
add_or_sub_expr = mul_or_div_expr << C.chainl1(add_or_sub_op)
expr.define(add_or_sub_expr) # Lazy definition
src = "(1. + .2e-1) * 100 - 1 / 2.5 "
assert T.parse(expr, src) == eval(src) # True
The parsec.combinator.chainl1 combinator handles left-associative chaining of operations, parsing one or more occurrences of a parser p separated by an operator parser, and combining results in a left-associative manner.
This approach is highly extensible: you can add additional operators, functions, or syntax features by composing and reusing combinators.
- For more basic text parsers, see
parsec.text - For more parser combinators, see
parsec.combinator
Architecture
A parser is a function that takes a Context[I] as input and returns a Result[I, R], where I and R are generic type parameters. Here, I represents the type of each element in the input stream, and R denotes the type of the value produced by the parser.
parser[I, R]: Context[I] -> Result[I, R]
The parsing function is wrapped in the Parser[I, R] class, endowing it with a monadic interface for functional composition and declarative parsing.
class Parser[I, R]:
def bind[S](self, fn: R -> Parser[I, S]) -> Parser[I, S]: ...
def okay(self, value: R) -> Parser[I, R]: ...
def fail(self, errpr: ParseErr) -> Parser[I, R]: ...
A Context[I] consists of two primary components: a stream[I], which provides access to the underlying input sequence, and a State[I], which manages auxiliary parsing state. If you need to parse data types other than text, you can extend IStream and IState to implement custom stream and state management logic, enabling the parsing of arbitrary data sequences.
class Context[I]:
stream: IStream[I]
state: IState[I]
The Result[I, R] type represents the outcome of a parsing operation, containing the updated context, the parsing result (either a successfully parsed value or an error), and the number of input elements consumed during parsing.
class Result[I, R]:
context: Context[I]
outcome: Okay[R] | Fail
consumed: int
The combinator module provides a curried functional interface for composing parsers, and also supports method chaining. Both styles are equivalent in expressive power.
Parsec do not support left-recursive grammars.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file parsec_python-0.1.0.tar.gz.
File metadata
- Download URL: parsec_python-0.1.0.tar.gz
- Upload date:
- Size: 45.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
14d9cb2239035a3a01a8c3279476f0f336c35d7a800be1d52d27886327dd3442
|
|
| MD5 |
7abef0835f140366cc87e2280d633bce
|
|
| BLAKE2b-256 |
5aba257ff3e8bd19c9c648f90ed3ae4db604f2e7fa136a2d3b05cf45d567b9f4
|
File details
Details for the file parsec_python-0.1.0-py3-none-any.whl.
File metadata
- Download URL: parsec_python-0.1.0-py3-none-any.whl
- Upload date:
- Size: 13.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
376a10d22df7394c9b0a4cc402b636e83fa3667f823cd06d6e8ff314336ffede
|
|
| MD5 |
4bf70fbf8fe383ecb3c8a964c5bd959b
|
|
| BLAKE2b-256 |
dd445a95a0453a53efb931e527ecd616b638d6cbf5f40c567ccf6705e08ff1e9
|