parol-pygen
Python parser runtime/generator consuming parol export JSON.
Scope
- Supports
ParserExportModelversions1and2 - Supports export model validation for
algorithm = "Lalr1" | "Llk" - Runtime parsing supports
Lalr1andLlk - Uses
scnr2Python binding as default scanner backend - Supports regex fallback scanner via
--no-scnr2 - Runs LALR shift/reduce and LLK predictive parse loops
Schema compatibility notes:
parser-export-model.v1.schema.jsonandparser-export-model.v2.schema.jsonare shipped side-by-side.- Version
2adds scanner-stateskip_tokens; version1exports remain supported. - Schema selection is version-aware to keep strict validation for both contracts.
Quick start
Export parser metadata JSON from a grammar file (example):
parol export --grammar-file ./my-grammar-exp.par --output-file ./export.json --pretty
Then run parol-pygen commands against ./export.json:
python -m parol_pygen.cli --version
python -m parol_pygen.cli info
python -m parol_pygen.cli validate --export ./export.json
python -m parol_pygen.cli generate --export ./export.json --out . --package arg_generated
python -c "from arg_generated import Parser, UserActions; print(Parser(actions=UserActions()).parse('Var abc End').accepted)"
For a ready-to-run proof/project scaffold (scripts + runner + action skeleton):
python -m parol_pygen.cli init --out ./demo-proof --project "Demo Proof" --package demo_generated --export demo_export.json
This creates a standalone project skeleton in ./demo-proof with:
scripts/bootstrap.ps1scripts/generate-parser.ps1scripts/run-proof.ps1proof_runner.pycustom_actions.pysample.txt
Note: these are generated files inside the scaffold output directory (for example ./demo-proof/scripts/...),
not maintainer scripts in this repository root.
The recommended flow is generation-first:
- Validate export JSON (
validate) - Generate a Python package (
generate) - Import the generated
Parser/UserActionsfrom that package and parse input
Alternative: use init first when you want a full project scaffold instead of only a generated package.
The direct runtime command still exists for low-level diagnostics:
python -m parol_pygen.cli run --export ./export.json --text "Var abc End"
Replace ./export.json with any export file produced by your parol export workflow.
run supports both Lalr1 and Llk exports.
The info command prints JSON metadata (version, contract revision, model contract identifier,
schema id, capabilities, supported algorithm/export model, commands,
and error exit code conventions),
which is useful for tooling integration and diagnostics.
Compatibility rule for tooling consumers:
- If
contract_revisionis unknown and not listed insupported_contract_revisions, treat the payload as potentially incompatible and reject by default.
Example integration check (fail closed by default):
import json
import subprocess
import sys
cp = subprocess.run(
[sys.executable, "-m", "parol_pygen.cli", "info"],
text=True,
capture_output=True,
check=True,
)
info = json.loads(cp.stdout)
revision = int(info["contract_revision"])
supported = {int(x) for x in info["supported_contract_revisions"]}
if revision not in supported:
raise RuntimeError(
f"Unsupported parol-pygen info contract revision {revision}; "
f"supported revisions: {sorted(supported)}"
)
print("Compatible info contract detected")
PowerShell variant (Windows):
$raw = python -m parol_pygen.cli info
$info = $raw | ConvertFrom-Json
$revision = [int]$info.contract_revision
$supported = @($info.supported_contract_revisions | ForEach-Object { [int]$_ })
if ($supported -notcontains $revision) {
throw "Unsupported parol-pygen info contract revision $revision; supported revisions: $($supported -join ', ')"
}
Write-Output "Compatible info contract detected"
cmd.exe variant (Windows):
for /f "delims=" %I in ('python -m parol_pygen.cli info') do @set PAROL_INFO=%I
python -c "import json,os,sys; i=json.loads(os.environ['PAROL_INFO']); r=int(i['contract_revision']); s={int(x) for x in i['supported_contract_revisions']}; sys.exit(0 if r in s else 1)"
if errorlevel 1 (
echo Unsupported parol-pygen info contract revision
exit /b 1
) else (
echo Compatible info contract detected
)
Invalid input returns a non-zero exit code and prints parse diagnostics to stderr:
python -m parol_pygen.cli run --export ./export.json --text "Var abc"
echo $?
Exit code conventions used by the CLI:
0: success2: user/input/data errors (invalid input text, invalid export JSON/schema, missing files)1: internal/unexpected failures
Use --verbose-errors to include exception type names in stderr output:
python -m parol_pygen.cli --verbose-errors run --export ./export.json --text "Var abc"
Development with uv
For local development and CI parity, prefer uv as package/environment manager.
Initial setup:
uv venv
uv sync
Run CLI commands in the managed environment:
uv run python -m parol_pygen.cli --version
uv run python -m parol_pygen.cli info
Run tests:
uv run python -m unittest discover -s tests -p "test_*.py"
Build artifacts:
uv sync --extra dev
uv run python -m build
uv run python -m twine check dist/*
Release preflight:
uv run pwsh ./scripts/check-changelog-entry.ps1
# optional explicit target version
uv run pwsh ./scripts/check-changelog-entry.ps1 -Version 0.1.0
# one-command local release dry-run (gate + build + artifact check + scaffold smoke)
uv run pwsh ./scripts/release-dry-run.ps1 -Version 0.1.0 -Execute
This verifies that the current pyproject.toml version has a matching entry in CHANGELOG.md
before publish workflows are started.
Release checklist (maintainers)
Suggested command order for cutting a release X.Y.Z:
./scripts/check-changelog-entry.ps1 -Version X.Y.Z
./scripts/release-dry-run.ps1 -Version X.Y.Z -Execute
git add pyproject.toml CHANGELOG.md
git commit -m "chore(release): cut vX.Y.Z"
git tag -a vX.Y.Z -m "parol-pygen vX.Y.Z"
git push origin main
git push origin vX.Y.Z
Recommended publish flow:
- Publish to TestPyPI first.
- Run smoke/install checks from a clean environment.
- Publish to PyPI only after TestPyPI checks are green.
TestPyPI smoke runbook
Use this order for a release version X.Y.Z.
- Ensure the target version in pyproject.toml is exactly X.Y.Z.
- Trigger publish.yml with repository=testpypi and version=X.Y.Z.
- Wait until the TestPyPI publish job is successful.
- Verify TestPyPI index lists X.Y.Z for parol-pygen: https://test.pypi.org/simple/parol-pygen/
- Trigger testpypi-smoke.yml with version=X.Y.Z.
- Confirm both matrix jobs pass (Python 3.10 and 3.12).
- Only after smoke is green, trigger publish.yml with repository=pypi and version=X.Y.Z.
If TestPyPI smoke fails, use this quick triage:
- Failure in "Verify requested version is on TestPyPI": X.Y.Z is not indexed yet, or the wrong version was entered.
- Failure in "Install from TestPyPI": likely index/dependency resolution issue; check the install error in logs.
- Failure in CLI or scaffold smoke steps: package installed, but runtime/CLI behavior regressed.
Important: testpypi-smoke.yml validates a version that is already published to TestPyPI. It does not publish the package itself.
Trusted Publishing setup (PyPI/TestPyPI)
This repository uses GitHub OIDC Trusted Publishing in
.github/workflows/publish.yml (id-token: write + pypa/gh-action-pypi-publish).
No API token secrets are required for publish jobs.
Configure publishers in both package indexes:
- In TestPyPI project settings, add a Trusted Publisher:
- owner/repo:
jsinger67/parol-pygen - workflow file:
.github/workflows/publish.yml - environment:
testpypi
- owner/repo:
- In PyPI project settings, add a Trusted Publisher:
- owner/repo:
jsinger67/parol-pygen - workflow file:
.github/workflows/publish.yml - environment:
pypi
- owner/repo:
Recommended first verification:
- Run workflow dispatch with
repository=testpypiandversion=X.Y.Z. - Confirm successful TestPyPI publish.
- Run with
repository=pypifrom the commit taggedvX.Y.Z.
Semantic actions (parol-style)
from arg_generated import Parser, UserActions
from arg_generated.nodes import NonTerminalNode, StartListNode
class Actions(UserActions):
def on_start_list(self, node: StartListNode):
# Typed callback for reduced non-terminal StartList.
return {"kind": "StartList", "children": node.children}
def on_non_terminal(self, name: str, node: NonTerminalNode):
# Optional generic hook for all non-terminals.
return node
parser = Parser(actions=Actions())
result = parser.parse("Var abc End")
print(result.accepted)
print(result.value)
This mirrors the standalone Pascal proof setup:
- generate package once (for example
pascal_generated) - subclass generated
UserActions - override typed
on_<non_terminal>methods - parse through generated
Parser
Callback dispatch remains name-based (on_<non_terminal_in_snake_case>), and callback
return values are pushed as reduced semantic values.
Note: depending on parse table shape (augmented start handling), the start symbol itself may not always appear as a user-visible callback.
Advanced: a low-level on_production(lhs_nt, prod_idx, rhs_values) hook is available for
algorithm-neutral internal experiments. on_reduce(...) remains supported as a
backward-compatible alias; see MIGRATION.md for upgrade guidance.
Generated API Architecture
parol-pygen generate now provides a C#-style generated API surface for Python consumers.
The generated package is intended to be edited/extended by user code, while parser internals
remain in the reusable runtime.
Generated files:
parser.py- Generated parser facade
- Adapts runtime reduction payloads to generated typed node classes
- Dispatches typed semantic action callbacks
nodes.py- Generated concrete node classes (
<NonTerminal>Node) using@dataclass - Shared base types for generic and non-terminal nodes
- Generated concrete node classes (
actions.py- Generated action protocol with typed method signatures per non-terminal
- Base class with generic
on_non_terminal(name, node)fallback hook
user_actions.py- Editable starter implementation intended for user semantic logic
export.json- Embedded parser export model used by the generated parser facade
Semantic action contract:
- Preferred method shape:
on_<non_terminal_in_snake_case>(node: <GeneratedNodeType>) -> Any - Generic fallback:
on_non_terminal(name: str, node: NonTerminalNode) -> Any
Compatibility contract for tooling remains machine-readable through parol-pygen info
(contract_revision, supported_contract_revisions, model_contract, schema_id,
capabilities). Consumers should continue to fail closed on unknown revisions.
Metadata
Release files for parol-pygen 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| parol_pygen-0.2.0.tar.gz | 32.6 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| parol_pygen-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 62.8 kB
Release files / parol_pygen-0.2.0.tar.gz
| Download URL | parol_pygen-0.2.0.tar.gz |
|---|---|
| Size | 32.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
96608604440f5303ab5a5cd4e35fd8501fe45cc7468384373fd5e8765e36a5d0
|
|
BLAKE2b-256 checksum How to use checksums |
ddd7b3905eed07439650c546b3ad9a494008904418b33c22607d9e560f9b3af8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 11, 2026.
Transparency logRelease files / parol_pygen-0.2.0-py3-none-any.whl
| Download URL | parol_pygen-0.2.0-py3-none-any.whl |
|---|---|
| Size | 30.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c16cf4f16125b8080049b5b4bcc8a63675fef48407755ca38175937e1144c922
|
|
BLAKE2b-256 checksum How to use checksums |
e2a77056be62921c0a5c9e609bcb5144feb5d46c01513fc8e0db67933ff956c6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 11, 2026.
Transparency logSecurity for parol-pygen 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for parol_pygen-0.2.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 11, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.