Skip to main content

A Python parser combinator library inspired by Haskell's Parsec.

Project description

PyParsec

License: MIT Python Version Tests

PyParsec is an industrial-strength Parser Combinator library for Python.

It is a faithful port of Haskell's legendary Parsec library, adapted for Python's idioms while maintaining mathematical correctness. Unlike many Python parser libraries, PyParsec is stack-safe, type-safe, and supports generic inputs (strings, bytes, or token lists).

Key Features

  • 🛡️ Robust Error Handling: Distinguishes between "Empty" and "Consumed" failures, allowing precise control over backtracking using try_parse.
  • 🚀 Stack-Safe Combinators: many, choice, and expression chains are implemented iteratively. You won't hit RecursionError on large inputs.
  • 🧩 Generic Input: Parse str, bytes, or List[T] (e.g., tokens from a lexer). The SourcePos logic is decoupled from the input type.
  • 🔋 Batteries Included: Includes the full suite of Parsec modules:
    • Token: Automatically generate lexers (whitespace, comments, identifiers) from a language definition.
    • Expr: Built-in operator precedence parsing (Infix, Prefix, Postfix).
  • 🐍 Pythonic Operators:
    • >> is polymorphic: behaves like Bind (>>=) when passed a function, and Sequence (*>) when passed a parser.
    • | is Choice (<|>).

Installation

This project is managed with uv and hatchling. You can install it directly from the repository:

pip install pyarsec

import pyparsec, import parsecpy, import parsec, and import pyarsec all work.

For development:

git clone https://github.com/khengari77/PyParsec.git
cd PyParsec
uv sync

Quick Start

1. Basic Parsing

Parsing a sequence of digits into an integer.

from pyparsec import many1, digit, run_parser, pure

# Logic: Parse one or more digits -> join them -> convert to int
integer = many1(digit()) >> (lambda ds: pure(int("".join(ds))))

result, err = run_parser(integer, "12345")
print(result) # 12345

2. Using the Token Module (Easy Lexing)

Don't write manual regexes. Use TokenParser to handle whitespace, comments, and data types automatically.

from pyparsec import TokenParser, python_style, run_parser

# Create a lexer based on Python syntax rules (handling # comments, etc.)
lexer = TokenParser(python_style)

# These parsers automatically skip trailing whitespace/comments!
integer = lexer.integer
parens  = lexer.parens
identifier = lexer.identifier

# Parse: ( count )
parser = parens(identifier)

print(run_parser(parser, "(  my_variable  ) # comments are ignored"))
# Output: ('my_variable', None)

3. Operator Precedence (Expr Module)

Parsing mathematical expressions with correct order of operations (PEMDAS) is often difficult. PyParsec makes it declarative.

from pyparsec import build_expression_parser, Operator, Infix, Assoc, run_parser
from pyparsec import TokenParser, empty_def

# Setup a simple lexer
lexer = TokenParser(empty_def)
integer = lexer.integer

# Define functions
def add(x, y): return x + y
def mul(x, y): return x * y

# Define Operator Table
# Higher precedence comes first!
table = [
    [Infix(lexer.reserved_op("*") >> (lambda _: mul), Assoc.LEFT)],
    [Infix(lexer.reserved_op("+") >> (lambda _: add), Assoc.LEFT)]
]

expr = build_expression_parser(table, integer)

print(run_parser(expr, "2 + 3 * 4")) 
# Output: 14 (not 20!)

Advanced: Generic Input (Parsing Tokens)

PyParsec isn't limited to strings. You can parse a list of objects produced by a separate lexing stage.

from dataclasses import dataclass
from pyparsec import token, run_parser, SourcePos

@dataclass
class Tok:
    kind: str
    value: str

# Define a primitive that consumes a Token object
def match_kind(kind):
    return token(
        show_tok=lambda t: f"Token({kind})",
        test_tok=lambda t: t.value if t.kind == kind else None,
        # Update position based on token logic (optional)
        next_pos=lambda pos, t: SourcePos(pos.line, pos.column + 1, pos.name)
    )

stream = [Tok("ID", "x"), Tok("EQ", "="), Tok("NUM", "10")]

# Grammar: ID = NUM
parser = match_kind("ID") >> match_kind("EQ") >> match_kind("NUM")

print(run_parser(parser, stream))
# Output: '10'

Module Overview

  • pyparsec.Prim: Core primitives (pure, bind, fail, try_parse, many).
  • pyparsec.Char: String-specific parsers (char, string, alpha_num, one_of).
  • pyparsec.Combinators: Logic flow (choice, optional, sep_by, between).
  • pyparsec.Token: Automated lexer generation (TokenParser, LanguageDef).
  • pyparsec.Expr: Operator precedence builder (build_expression_parser).
  • pyparsec.Language: Pre-defined language styles (java_style, python_style, haskell_style).

Contributing

  1. Clone the repo.
  2. Install uv.
  3. Run tests: uv run pytest tests/ (We use Hypothesis for property-based testing).

Star History

Star History Chart

License

MIT License.

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

pyarsec-0.1.0.tar.gz (72.1 kB view details)

Uploaded Source

Built Distribution

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

pyarsec-0.1.0-py3-none-any.whl (31.7 kB view details)

Uploaded Python 3

File details

Details for the file pyarsec-0.1.0.tar.gz.

File metadata

  • Download URL: pyarsec-0.1.0.tar.gz
  • Upload date:
  • Size: 72.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for pyarsec-0.1.0.tar.gz
Algorithm Hash digest
SHA256 28797562b836ee3e48928bacdc3d9bc245868d1862ec0b22f2e852772a5cdf00
MD5 3d500c4062eacc5ffc62ef21629e56b3
BLAKE2b-256 4a7f74260444f9a1712a1c6dac02ba9d9a8ac92f0a1aec670dd7b1fb1c8f0356

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyarsec-0.1.0.tar.gz:

Publisher: publish.yml on khengari77/PyParsec

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pyarsec-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: pyarsec-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 31.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for pyarsec-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8eb84c5e3f0215415a7cad1a7fcc3a9bdc95cccd05e3feda5cbec93c64cf15aa
MD5 2d56be10d7037fac0184fef4758cc29f
BLAKE2b-256 22a20f5c230855ae5af1fdc454aef1bd5c83d7a87895d45778264fc52e73a3cc

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyarsec-0.1.0-py3-none-any.whl:

Publisher: publish.yml on khengari77/PyParsec

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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