Bidirectional parser/generator combinators for Python with recursive regex-style grammar fragments.
Project description
Syncraft
Syncraft is a bidirectional parser/generator combinator library for Python.
Define a grammar once.
- Parse text into structured data.
- Generate text back from that same structure.
- Keep both directions consistent by construction.
It provides Packrat-style performance and supports direct left recursion.
Status
Pre-1.0 (Release Candidate) — APIs may change before 1.0.
Philosophy
Syncraft is built on three beliefs.
1. One source of truth
Parsing, generation, and validation should come from a single grammar model.
If you define a language once, you should not need:
- a parser,
- a transformer,
- a serializer,
- and a separate validator.
A single definition should unify all directions.
Roundtripping should fall out of the model, not be manually maintained.
2. Shape matters
A parsing library should deliver values in the shape the user actually wants.
Not:
- raw parse trees,
- intermediate ASTs that require post-processing,
- or mandatory visitor passes.
Transformation is not an afterthought — it is part of the grammar definition.
Grammars describe both structure and meaning.
3. CFGs should feel like regex
Context-free grammars shouldn’t feel heavier than regular expressions.
If you can sketch a regex, you should be able to sketch a recursive grammar just as quickly.
Syntax.rp exists to make recursive grammar fragments feel as lightweight and composable as regex — without giving up context-free power.
Core capabilities
Syncraft provides two core capabilities:
-
Bidirectional grammar + transformation
- Define grammar and data transformation together.
- Parse text into structured values.
- Generate text back from structured values from the same grammar model.
-
Regex++
- Embed named recursive grammar fragments inside a regex-like syntax, effectively turning regular expressions into composable context-free grammar fragments.
- Compose those fragments with grammar combinators.
Scope
Syncraft is a strong fit when you need:
| If you need... | Syncraft provides... |
|---|---|
| A small DSL or config language | Regex++ grammar sketching + structured AST generation |
| Strict roundtrip guarantees | One grammar model for parse() and generate() |
| Evolving grammars during prototyping | Combinator + fragment composition model |
Syncraft is also good for parsing-only/extraction workflows. Generation and roundtrip constraints become additional advantages when you need them, not requirements for adoption.
Quick example: regex++ parsing for a common mini-language
This style is useful when you want to sketch and evolve a small language quickly. Here, we parse a recursive expression grammar:
expr := number | '(' expr op expr ')'op := + | - | * | /
from syncraft.syntax import Syntax as S
num = S.rp(r"[0-9]+").bimap(int, str)
op = S.rp(r"[+\-*/]")
expr = S.lazy(lambda: S.rp(
r"(?&num)|(\((?&expr)\s*(?&op)\s*(?&expr)\))",
num=num, op=op, expr=expr
))
print(expr.parse("7"))
print(expr.parse("(2+3)"))
print(expr.parse("((1+2)*3)"))
Expected output:
7
(2, '+', 3)
((1, '+', 2), '*', 3)
Adding structured data transformations
Transform parsed tuples into dataclasses and generate text back from those dataclasses:
The case() combinator defines bidirectional structural mappings. Each case provides a pair of functions: one to extract values from parsed tuples, and one to construct domain objects."
from dataclasses import dataclass
@dataclass
class Number:
value: int
@dataclass
class BinaryOp:
left: Number | BinaryOp
op: str
right: Number | BinaryOp
expr_ast = expr.case(
(lambda env: env.number, lambda env: Number(env.number)),
(lambda env: (env.left, env.op, env.right), lambda env: BinaryOp(env.left, env.op, env.right))
)
# Parse into dataclasses
result = expr_ast.parse("((1+2)*3)")
print(result)
# Output: BinaryOp(left=BinaryOp(left=Number(value=1), op='+', right=Number(value=2)), op='*', right=Number(value=3))
# Generate text back from dataclasses
text = expr_ast.generate(result)
print(text)
# Output: ((1+2)*3)
Installation
Python 3.10+ is required.
With pip
pip install syncraft
With uv
uv add syncraft
Documentation
Full documentation is available at: https://afmkt.github.io/syncraft/
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 syncraft-0.8.1.tar.gz.
File metadata
- Download URL: syncraft-0.8.1.tar.gz
- Upload date:
- Size: 285.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4881fb045a09c56961bde6157a78403cdc4adbf791c61147e6d3e853203b89db
|
|
| MD5 |
b67b89b4b0f4210f8956baa177b86853
|
|
| BLAKE2b-256 |
9149b4ff6c40f801bdc890f08325042827c0e89de55f37a05bf250b28b9fdb7b
|
File details
Details for the file syncraft-0.8.1-py3-none-any.whl.
File metadata
- Download URL: syncraft-0.8.1-py3-none-any.whl
- Upload date:
- Size: 123.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1f93540a308007eda118b6ee090f4943d49a2b654919e63ee3d4fd7197d5adfc
|
|
| MD5 |
586c07064ad0ab811c1dd4ac775a92d4
|
|
| BLAKE2b-256 |
b1c0577e3cecd76a9e9b8b53575dc37437e1d30fbb6e145514c424dc745acb7c
|