Skip to main content

A Monadic Parser Combinator for Python.

Reason this release was yanked:

Deprecated due to fatal errors

Project description

Parsec Ask DeepWiki

A Monadic Parser Combinator for Python.

License Typed Tests

Documents · Example · Issue


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 << f equivalent to f(p), enables successive application of combinators to a parser, supporting a piping style of composition.
  • p >> f equivalent to p.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 @ f equivalent to p.map(f), mapping the f over the result of the parser p. This corresponds to the Functor's map (or fmap) 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.

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

parsec_python-0.1.0.tar.gz (45.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

parsec_python-0.1.0-py3-none-any.whl (13.9 kB view details)

Uploaded Python 3

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

Hashes for parsec_python-0.1.0.tar.gz
Algorithm Hash digest
SHA256 14d9cb2239035a3a01a8c3279476f0f336c35d7a800be1d52d27886327dd3442
MD5 7abef0835f140366cc87e2280d633bce
BLAKE2b-256 5aba257ff3e8bd19c9c648f90ed3ae4db604f2e7fa136a2d3b05cf45d567b9f4

See more details on using hashes here.

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

Hashes for parsec_python-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 376a10d22df7394c9b0a4cc402b636e83fa3667f823cd06d6e8ff314336ffede
MD5 4bf70fbf8fe383ecb3c8a964c5bd959b
BLAKE2b-256 dd445a95a0453a53efb931e527ecd616b638d6cbf5f40c567ccf6705e08ff1e9

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page