parol-pygen 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. parol_pygen-0.1.0/PKG-INFO +340 -0
  2. parol_pygen-0.1.0/README.md +315 -0
  3. parol_pygen-0.1.0/pyproject.toml +51 -0
  4. parol_pygen-0.1.0/setup.cfg +4 -0
  5. parol_pygen-0.1.0/src/parol_pygen/__init__.py +13 -0
  6. parol_pygen-0.1.0/src/parol_pygen/cli.py +199 -0
  7. parol_pygen-0.1.0/src/parol_pygen/diagnostics.py +16 -0
  8. parol_pygen-0.1.0/src/parol_pygen/generator.py +287 -0
  9. parol_pygen-0.1.0/src/parol_pygen/lalr_engine.py +100 -0
  10. parol_pygen-0.1.0/src/parol_pygen/llk_engine.py +149 -0
  11. parol_pygen-0.1.0/src/parol_pygen/loader.py +18 -0
  12. parol_pygen-0.1.0/src/parol_pygen/model.py +276 -0
  13. parol_pygen-0.1.0/src/parol_pygen/parser.py +45 -0
  14. parol_pygen-0.1.0/src/parol_pygen/scaffold.py +304 -0
  15. parol_pygen-0.1.0/src/parol_pygen/scanner_adapter.py +198 -0
  16. parol_pygen-0.1.0/src/parol_pygen/schemas/__init__.py +1 -0
  17. parol_pygen-0.1.0/src/parol_pygen/schemas/parser-export-model.v1.schema.json +690 -0
  18. parol_pygen-0.1.0/src/parol_pygen/semantic_actions.py +68 -0
  19. parol_pygen-0.1.0/src/parol_pygen/table_compiler.py +41 -0
  20. parol_pygen-0.1.0/src/parol_pygen/validator.py +103 -0
  21. parol_pygen-0.1.0/src/parol_pygen.egg-info/PKG-INFO +340 -0
  22. parol_pygen-0.1.0/src/parol_pygen.egg-info/SOURCES.txt +27 -0
  23. parol_pygen-0.1.0/src/parol_pygen.egg-info/dependency_links.txt +1 -0
  24. parol_pygen-0.1.0/src/parol_pygen.egg-info/entry_points.txt +2 -0
  25. parol_pygen-0.1.0/src/parol_pygen.egg-info/requires.txt +6 -0
  26. parol_pygen-0.1.0/src/parol_pygen.egg-info/top_level.txt +1 -0
  27. parol_pygen-0.1.0/tests/test_cli.py +549 -0
  28. parol_pygen-0.1.0/tests/test_poc.py +224 -0
  29. parol_pygen-0.1.0/tests/test_scanner_definition.py +90 -0
@@ -0,0 +1,340 @@
1
+ Metadata-Version: 2.4
2
+ Name: parol-pygen
3
+ Version: 0.1.0
4
+ Summary: Python parser generator/runtime consuming parol export JSON
5
+ Author: parol contributors
6
+ License-Expression: MIT OR Apache-2.0
7
+ Project-URL: Homepage, https://github.com/jsinger67/parol
8
+ Project-URL: Repository, https://github.com/jsinger67/parol
9
+ Project-URL: Issues, https://github.com/jsinger67/parol/issues
10
+ Keywords: parser,generator,lalr,llk,parol
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Software Development :: Compilers
18
+ Requires-Python: >=3.10
19
+ Description-Content-Type: text/markdown
20
+ Requires-Dist: jsonschema>=4.21.0
21
+ Requires-Dist: scnr2
22
+ Provides-Extra: dev
23
+ Requires-Dist: build>=1.2.1; extra == "dev"
24
+ Requires-Dist: twine>=5.0.0; extra == "dev"
25
+
26
+ # parol-pygen
27
+
28
+ ![ParolPyLogo](https://raw.githubusercontent.com/jsinger67/parol-pygen/main/logo/ParolPy.png)
29
+
30
+ Python parser runtime/generator consuming `parol export` JSON.
31
+
32
+ ## Scope
33
+
34
+ - Supports `ParserExportModel` version `1`
35
+ - Supports export model validation for `algorithm = "Lalr1" | "Llk"`
36
+ - Runtime parsing supports `Lalr1` and `Llk`
37
+ - Uses `scnr2` Python binding as default scanner backend
38
+ - Supports regex fallback scanner via `--no-scnr2`
39
+ - Runs LALR shift/reduce and LLK predictive parse loops
40
+
41
+ ## Quick start
42
+
43
+ Export parser metadata JSON from a grammar file (example):
44
+
45
+ ```bash
46
+ parol export --grammar-file ./my-grammar-exp.par --output-file ./export.json --pretty
47
+ ```
48
+
49
+ Then run `parol-pygen` commands against `./export.json`:
50
+
51
+ ```bash
52
+ python -m parol_pygen.cli --version
53
+ python -m parol_pygen.cli info
54
+ python -m parol_pygen.cli validate --export ./export.json
55
+ python -m parol_pygen.cli generate --export ./export.json --out . --package arg_generated
56
+ python -c "from arg_generated import Parser, UserActions; print(Parser(actions=UserActions()).parse('Var abc End').accepted)"
57
+ ```
58
+
59
+ For a ready-to-run proof/project scaffold (scripts + runner + action skeleton):
60
+
61
+ ```bash
62
+ python -m parol_pygen.cli init --out ./demo-proof --project "Demo Proof" --package demo_generated --export demo_export.json
63
+ ```
64
+
65
+ This creates a standalone project skeleton in `./demo-proof` with:
66
+
67
+ - `scripts/bootstrap.ps1`
68
+ - `scripts/generate-parser.ps1`
69
+ - `scripts/run-proof.ps1`
70
+ - `proof_runner.py`
71
+ - `custom_actions.py`
72
+ - `sample.txt`
73
+
74
+ Note: these are generated files inside the scaffold output directory (for example `./demo-proof/scripts/...`),
75
+ not maintainer scripts in this repository root.
76
+
77
+ The recommended flow is generation-first:
78
+
79
+ 1. Validate export JSON (`validate`)
80
+ 2. Generate a Python package (`generate`)
81
+ 3. Import the generated `Parser`/`UserActions` from that package and parse input
82
+
83
+ Alternative: use `init` first when you want a full project scaffold instead of only a generated package.
84
+
85
+ The direct runtime command still exists for low-level diagnostics:
86
+
87
+ ```bash
88
+ python -m parol_pygen.cli run --export ./export.json --text "Var abc End"
89
+ ```
90
+
91
+ Replace `./export.json` with any export file produced by your `parol export` workflow.
92
+
93
+ `run` supports both `Lalr1` and `Llk` exports.
94
+
95
+ The `info` command prints JSON metadata (version, contract revision, model contract identifier,
96
+ schema id, capabilities, supported algorithm/export model, commands,
97
+ and error exit code conventions),
98
+ which is useful for tooling integration and diagnostics.
99
+
100
+ Compatibility rule for tooling consumers:
101
+
102
+ - If `contract_revision` is unknown and not listed in `supported_contract_revisions`,
103
+ treat the payload as potentially incompatible and reject by default.
104
+
105
+ Example integration check (fail closed by default):
106
+
107
+ ```python
108
+ import json
109
+ import subprocess
110
+ import sys
111
+
112
+
113
+ cp = subprocess.run(
114
+ [sys.executable, "-m", "parol_pygen.cli", "info"],
115
+ text=True,
116
+ capture_output=True,
117
+ check=True,
118
+ )
119
+
120
+ info = json.loads(cp.stdout)
121
+ revision = int(info["contract_revision"])
122
+ supported = {int(x) for x in info["supported_contract_revisions"]}
123
+
124
+ if revision not in supported:
125
+ raise RuntimeError(
126
+ f"Unsupported parol-pygen info contract revision {revision}; "
127
+ f"supported revisions: {sorted(supported)}"
128
+ )
129
+
130
+ print("Compatible info contract detected")
131
+ ```
132
+
133
+ PowerShell variant (Windows):
134
+
135
+ ```powershell
136
+ $raw = python -m parol_pygen.cli info
137
+ $info = $raw | ConvertFrom-Json
138
+
139
+ $revision = [int]$info.contract_revision
140
+ $supported = @($info.supported_contract_revisions | ForEach-Object { [int]$_ })
141
+
142
+ if ($supported -notcontains $revision) {
143
+ throw "Unsupported parol-pygen info contract revision $revision; supported revisions: $($supported -join ', ')"
144
+ }
145
+
146
+ Write-Output "Compatible info contract detected"
147
+ ```
148
+
149
+ cmd.exe variant (Windows):
150
+
151
+ ```bat
152
+ for /f "delims=" %I in ('python -m parol_pygen.cli info') do @set PAROL_INFO=%I
153
+ 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)"
154
+ if errorlevel 1 (
155
+ echo Unsupported parol-pygen info contract revision
156
+ exit /b 1
157
+ ) else (
158
+ echo Compatible info contract detected
159
+ )
160
+ ```
161
+
162
+ Invalid input returns a non-zero exit code and prints parse diagnostics to stderr:
163
+
164
+ ```bash
165
+ python -m parol_pygen.cli run --export ./export.json --text "Var abc"
166
+ echo $?
167
+ ```
168
+
169
+ Exit code conventions used by the CLI:
170
+
171
+ - `0`: success
172
+ - `2`: user/input/data errors (invalid input text, invalid export JSON/schema, missing files)
173
+ - `1`: internal/unexpected failures
174
+
175
+ Use `--verbose-errors` to include exception type names in stderr output:
176
+
177
+ ```bash
178
+ python -m parol_pygen.cli --verbose-errors run --export ./export.json --text "Var abc"
179
+ ```
180
+
181
+ ## Development with uv
182
+
183
+ For local development and CI parity, prefer `uv` as package/environment manager.
184
+
185
+ Initial setup:
186
+
187
+ ```bash
188
+ uv venv
189
+ uv sync
190
+ ```
191
+
192
+ Run CLI commands in the managed environment:
193
+
194
+ ```bash
195
+ uv run python -m parol_pygen.cli --version
196
+ uv run python -m parol_pygen.cli info
197
+ ```
198
+
199
+ Run tests:
200
+
201
+ ```bash
202
+ uv run python -m unittest discover -s tests -p "test_*.py"
203
+ ```
204
+
205
+ Build artifacts:
206
+
207
+ ```bash
208
+ uv sync --extra dev
209
+ uv run python -m build
210
+ uv run python -m twine check dist/*
211
+ ```
212
+
213
+ Release preflight:
214
+
215
+ ```bash
216
+ uv run pwsh ./scripts/check-changelog-entry.ps1
217
+ # optional explicit target version
218
+ uv run pwsh ./scripts/check-changelog-entry.ps1 -Version 0.1.0
219
+
220
+ # one-command local release dry-run (gate + build + artifact check + scaffold smoke)
221
+ uv run pwsh ./scripts/release-dry-run.ps1 -Version 0.1.0 -Execute
222
+ ```
223
+
224
+ This verifies that the current `pyproject.toml` version has a matching entry in `CHANGELOG.md`
225
+ before publish workflows are started.
226
+
227
+ ## Release checklist (maintainers)
228
+
229
+ Suggested command order for cutting a release `X.Y.Z`:
230
+
231
+ ```powershell
232
+ ./scripts/check-changelog-entry.ps1 -Version X.Y.Z
233
+ ./scripts/release-dry-run.ps1 -Version X.Y.Z -Execute
234
+ git add pyproject.toml CHANGELOG.md
235
+ git commit -m "chore(release): cut vX.Y.Z"
236
+ git tag -a vX.Y.Z -m "parol-pygen vX.Y.Z"
237
+ git push origin main
238
+ git push origin vX.Y.Z
239
+ ```
240
+
241
+ Recommended publish flow:
242
+
243
+ 1. Publish to TestPyPI first.
244
+ 2. Run smoke/install checks from a clean environment.
245
+ 3. Publish to PyPI only after TestPyPI checks are green.
246
+
247
+ ### Trusted Publishing setup (PyPI/TestPyPI)
248
+
249
+ This repository uses GitHub OIDC Trusted Publishing in
250
+ `.github/workflows/publish.yml` (`id-token: write` + `pypa/gh-action-pypi-publish`).
251
+ No API token secrets are required for publish jobs.
252
+
253
+ Configure publishers in both package indexes:
254
+
255
+ 1. In TestPyPI project settings, add a Trusted Publisher:
256
+ - owner/repo: `jsinger67/parol-pygen`
257
+ - workflow file: `.github/workflows/publish.yml`
258
+ - environment: `testpypi`
259
+ 2. In PyPI project settings, add a Trusted Publisher:
260
+ - owner/repo: `jsinger67/parol-pygen`
261
+ - workflow file: `.github/workflows/publish.yml`
262
+ - environment: `pypi`
263
+
264
+ Recommended first verification:
265
+
266
+ 1. Run workflow dispatch with `repository=testpypi` and `version=X.Y.Z`.
267
+ 2. Confirm successful TestPyPI publish.
268
+ 3. Run with `repository=pypi` from the commit tagged `vX.Y.Z`.
269
+
270
+ ## Semantic actions (parol-style)
271
+
272
+ ```python
273
+ from arg_generated import Parser, UserActions
274
+ from arg_generated.nodes import NonTerminalNode, StartListNode
275
+
276
+
277
+ class Actions(UserActions):
278
+ def on_start_list(self, node: StartListNode):
279
+ # Typed callback for reduced non-terminal StartList.
280
+ return {"kind": "StartList", "children": node.children}
281
+
282
+ def on_non_terminal(self, name: str, node: NonTerminalNode):
283
+ # Optional generic hook for all non-terminals.
284
+ return node
285
+
286
+
287
+ parser = Parser(actions=Actions())
288
+ result = parser.parse("Var abc End")
289
+ print(result.accepted)
290
+ print(result.value)
291
+ ```
292
+
293
+ This mirrors the standalone Pascal proof setup:
294
+
295
+ - generate package once (for example `pascal_generated`)
296
+ - subclass generated `UserActions`
297
+ - override typed `on_<non_terminal>` methods
298
+ - parse through generated `Parser`
299
+
300
+ Callback dispatch remains name-based (`on_<non_terminal_in_snake_case>`), and callback
301
+ return values are pushed as reduced semantic values.
302
+
303
+ Note: depending on parse table shape (augmented start handling), the start symbol itself may
304
+ not always appear as a user-visible callback.
305
+
306
+ Advanced: a low-level `on_production(lhs_nt, prod_idx, rhs_values)` hook is available for
307
+ algorithm-neutral internal experiments. `on_reduce(...)` remains supported as a
308
+ backward-compatible alias; see `MIGRATION.md` for upgrade guidance.
309
+
310
+ ## Generated API Architecture
311
+
312
+ `parol-pygen generate` now provides a C#-style generated API surface for Python consumers.
313
+ The generated package is intended to be edited/extended by user code, while parser internals
314
+ remain in the reusable runtime.
315
+
316
+ Generated files:
317
+
318
+ - `parser.py`
319
+ - Generated parser facade
320
+ - Adapts runtime reduction payloads to generated typed node classes
321
+ - Dispatches typed semantic action callbacks
322
+ - `nodes.py`
323
+ - Generated concrete node classes (`<NonTerminal>Node`) using `@dataclass`
324
+ - Shared base types for generic and non-terminal nodes
325
+ - `actions.py`
326
+ - Generated action protocol with typed method signatures per non-terminal
327
+ - Base class with generic `on_non_terminal(name, node)` fallback hook
328
+ - `user_actions.py`
329
+ - Editable starter implementation intended for user semantic logic
330
+ - `export.json`
331
+ - Embedded parser export model used by the generated parser facade
332
+
333
+ Semantic action contract:
334
+
335
+ - Preferred method shape: `on_<non_terminal_in_snake_case>(node: <GeneratedNodeType>) -> Any`
336
+ - Generic fallback: `on_non_terminal(name: str, node: NonTerminalNode) -> Any`
337
+
338
+ Compatibility contract for tooling remains machine-readable through `parol-pygen info`
339
+ (`contract_revision`, `supported_contract_revisions`, `model_contract`, `schema_id`,
340
+ `capabilities`). Consumers should continue to fail closed on unknown revisions.
@@ -0,0 +1,315 @@
1
+ # parol-pygen
2
+
3
+ ![ParolPyLogo](https://raw.githubusercontent.com/jsinger67/parol-pygen/main/logo/ParolPy.png)
4
+
5
+ Python parser runtime/generator consuming `parol export` JSON.
6
+
7
+ ## Scope
8
+
9
+ - Supports `ParserExportModel` version `1`
10
+ - Supports export model validation for `algorithm = "Lalr1" | "Llk"`
11
+ - Runtime parsing supports `Lalr1` and `Llk`
12
+ - Uses `scnr2` Python binding as default scanner backend
13
+ - Supports regex fallback scanner via `--no-scnr2`
14
+ - Runs LALR shift/reduce and LLK predictive parse loops
15
+
16
+ ## Quick start
17
+
18
+ Export parser metadata JSON from a grammar file (example):
19
+
20
+ ```bash
21
+ parol export --grammar-file ./my-grammar-exp.par --output-file ./export.json --pretty
22
+ ```
23
+
24
+ Then run `parol-pygen` commands against `./export.json`:
25
+
26
+ ```bash
27
+ python -m parol_pygen.cli --version
28
+ python -m parol_pygen.cli info
29
+ python -m parol_pygen.cli validate --export ./export.json
30
+ python -m parol_pygen.cli generate --export ./export.json --out . --package arg_generated
31
+ python -c "from arg_generated import Parser, UserActions; print(Parser(actions=UserActions()).parse('Var abc End').accepted)"
32
+ ```
33
+
34
+ For a ready-to-run proof/project scaffold (scripts + runner + action skeleton):
35
+
36
+ ```bash
37
+ python -m parol_pygen.cli init --out ./demo-proof --project "Demo Proof" --package demo_generated --export demo_export.json
38
+ ```
39
+
40
+ This creates a standalone project skeleton in `./demo-proof` with:
41
+
42
+ - `scripts/bootstrap.ps1`
43
+ - `scripts/generate-parser.ps1`
44
+ - `scripts/run-proof.ps1`
45
+ - `proof_runner.py`
46
+ - `custom_actions.py`
47
+ - `sample.txt`
48
+
49
+ Note: these are generated files inside the scaffold output directory (for example `./demo-proof/scripts/...`),
50
+ not maintainer scripts in this repository root.
51
+
52
+ The recommended flow is generation-first:
53
+
54
+ 1. Validate export JSON (`validate`)
55
+ 2. Generate a Python package (`generate`)
56
+ 3. Import the generated `Parser`/`UserActions` from that package and parse input
57
+
58
+ Alternative: use `init` first when you want a full project scaffold instead of only a generated package.
59
+
60
+ The direct runtime command still exists for low-level diagnostics:
61
+
62
+ ```bash
63
+ python -m parol_pygen.cli run --export ./export.json --text "Var abc End"
64
+ ```
65
+
66
+ Replace `./export.json` with any export file produced by your `parol export` workflow.
67
+
68
+ `run` supports both `Lalr1` and `Llk` exports.
69
+
70
+ The `info` command prints JSON metadata (version, contract revision, model contract identifier,
71
+ schema id, capabilities, supported algorithm/export model, commands,
72
+ and error exit code conventions),
73
+ which is useful for tooling integration and diagnostics.
74
+
75
+ Compatibility rule for tooling consumers:
76
+
77
+ - If `contract_revision` is unknown and not listed in `supported_contract_revisions`,
78
+ treat the payload as potentially incompatible and reject by default.
79
+
80
+ Example integration check (fail closed by default):
81
+
82
+ ```python
83
+ import json
84
+ import subprocess
85
+ import sys
86
+
87
+
88
+ cp = subprocess.run(
89
+ [sys.executable, "-m", "parol_pygen.cli", "info"],
90
+ text=True,
91
+ capture_output=True,
92
+ check=True,
93
+ )
94
+
95
+ info = json.loads(cp.stdout)
96
+ revision = int(info["contract_revision"])
97
+ supported = {int(x) for x in info["supported_contract_revisions"]}
98
+
99
+ if revision not in supported:
100
+ raise RuntimeError(
101
+ f"Unsupported parol-pygen info contract revision {revision}; "
102
+ f"supported revisions: {sorted(supported)}"
103
+ )
104
+
105
+ print("Compatible info contract detected")
106
+ ```
107
+
108
+ PowerShell variant (Windows):
109
+
110
+ ```powershell
111
+ $raw = python -m parol_pygen.cli info
112
+ $info = $raw | ConvertFrom-Json
113
+
114
+ $revision = [int]$info.contract_revision
115
+ $supported = @($info.supported_contract_revisions | ForEach-Object { [int]$_ })
116
+
117
+ if ($supported -notcontains $revision) {
118
+ throw "Unsupported parol-pygen info contract revision $revision; supported revisions: $($supported -join ', ')"
119
+ }
120
+
121
+ Write-Output "Compatible info contract detected"
122
+ ```
123
+
124
+ cmd.exe variant (Windows):
125
+
126
+ ```bat
127
+ for /f "delims=" %I in ('python -m parol_pygen.cli info') do @set PAROL_INFO=%I
128
+ 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)"
129
+ if errorlevel 1 (
130
+ echo Unsupported parol-pygen info contract revision
131
+ exit /b 1
132
+ ) else (
133
+ echo Compatible info contract detected
134
+ )
135
+ ```
136
+
137
+ Invalid input returns a non-zero exit code and prints parse diagnostics to stderr:
138
+
139
+ ```bash
140
+ python -m parol_pygen.cli run --export ./export.json --text "Var abc"
141
+ echo $?
142
+ ```
143
+
144
+ Exit code conventions used by the CLI:
145
+
146
+ - `0`: success
147
+ - `2`: user/input/data errors (invalid input text, invalid export JSON/schema, missing files)
148
+ - `1`: internal/unexpected failures
149
+
150
+ Use `--verbose-errors` to include exception type names in stderr output:
151
+
152
+ ```bash
153
+ python -m parol_pygen.cli --verbose-errors run --export ./export.json --text "Var abc"
154
+ ```
155
+
156
+ ## Development with uv
157
+
158
+ For local development and CI parity, prefer `uv` as package/environment manager.
159
+
160
+ Initial setup:
161
+
162
+ ```bash
163
+ uv venv
164
+ uv sync
165
+ ```
166
+
167
+ Run CLI commands in the managed environment:
168
+
169
+ ```bash
170
+ uv run python -m parol_pygen.cli --version
171
+ uv run python -m parol_pygen.cli info
172
+ ```
173
+
174
+ Run tests:
175
+
176
+ ```bash
177
+ uv run python -m unittest discover -s tests -p "test_*.py"
178
+ ```
179
+
180
+ Build artifacts:
181
+
182
+ ```bash
183
+ uv sync --extra dev
184
+ uv run python -m build
185
+ uv run python -m twine check dist/*
186
+ ```
187
+
188
+ Release preflight:
189
+
190
+ ```bash
191
+ uv run pwsh ./scripts/check-changelog-entry.ps1
192
+ # optional explicit target version
193
+ uv run pwsh ./scripts/check-changelog-entry.ps1 -Version 0.1.0
194
+
195
+ # one-command local release dry-run (gate + build + artifact check + scaffold smoke)
196
+ uv run pwsh ./scripts/release-dry-run.ps1 -Version 0.1.0 -Execute
197
+ ```
198
+
199
+ This verifies that the current `pyproject.toml` version has a matching entry in `CHANGELOG.md`
200
+ before publish workflows are started.
201
+
202
+ ## Release checklist (maintainers)
203
+
204
+ Suggested command order for cutting a release `X.Y.Z`:
205
+
206
+ ```powershell
207
+ ./scripts/check-changelog-entry.ps1 -Version X.Y.Z
208
+ ./scripts/release-dry-run.ps1 -Version X.Y.Z -Execute
209
+ git add pyproject.toml CHANGELOG.md
210
+ git commit -m "chore(release): cut vX.Y.Z"
211
+ git tag -a vX.Y.Z -m "parol-pygen vX.Y.Z"
212
+ git push origin main
213
+ git push origin vX.Y.Z
214
+ ```
215
+
216
+ Recommended publish flow:
217
+
218
+ 1. Publish to TestPyPI first.
219
+ 2. Run smoke/install checks from a clean environment.
220
+ 3. Publish to PyPI only after TestPyPI checks are green.
221
+
222
+ ### Trusted Publishing setup (PyPI/TestPyPI)
223
+
224
+ This repository uses GitHub OIDC Trusted Publishing in
225
+ `.github/workflows/publish.yml` (`id-token: write` + `pypa/gh-action-pypi-publish`).
226
+ No API token secrets are required for publish jobs.
227
+
228
+ Configure publishers in both package indexes:
229
+
230
+ 1. In TestPyPI project settings, add a Trusted Publisher:
231
+ - owner/repo: `jsinger67/parol-pygen`
232
+ - workflow file: `.github/workflows/publish.yml`
233
+ - environment: `testpypi`
234
+ 2. In PyPI project settings, add a Trusted Publisher:
235
+ - owner/repo: `jsinger67/parol-pygen`
236
+ - workflow file: `.github/workflows/publish.yml`
237
+ - environment: `pypi`
238
+
239
+ Recommended first verification:
240
+
241
+ 1. Run workflow dispatch with `repository=testpypi` and `version=X.Y.Z`.
242
+ 2. Confirm successful TestPyPI publish.
243
+ 3. Run with `repository=pypi` from the commit tagged `vX.Y.Z`.
244
+
245
+ ## Semantic actions (parol-style)
246
+
247
+ ```python
248
+ from arg_generated import Parser, UserActions
249
+ from arg_generated.nodes import NonTerminalNode, StartListNode
250
+
251
+
252
+ class Actions(UserActions):
253
+ def on_start_list(self, node: StartListNode):
254
+ # Typed callback for reduced non-terminal StartList.
255
+ return {"kind": "StartList", "children": node.children}
256
+
257
+ def on_non_terminal(self, name: str, node: NonTerminalNode):
258
+ # Optional generic hook for all non-terminals.
259
+ return node
260
+
261
+
262
+ parser = Parser(actions=Actions())
263
+ result = parser.parse("Var abc End")
264
+ print(result.accepted)
265
+ print(result.value)
266
+ ```
267
+
268
+ This mirrors the standalone Pascal proof setup:
269
+
270
+ - generate package once (for example `pascal_generated`)
271
+ - subclass generated `UserActions`
272
+ - override typed `on_<non_terminal>` methods
273
+ - parse through generated `Parser`
274
+
275
+ Callback dispatch remains name-based (`on_<non_terminal_in_snake_case>`), and callback
276
+ return values are pushed as reduced semantic values.
277
+
278
+ Note: depending on parse table shape (augmented start handling), the start symbol itself may
279
+ not always appear as a user-visible callback.
280
+
281
+ Advanced: a low-level `on_production(lhs_nt, prod_idx, rhs_values)` hook is available for
282
+ algorithm-neutral internal experiments. `on_reduce(...)` remains supported as a
283
+ backward-compatible alias; see `MIGRATION.md` for upgrade guidance.
284
+
285
+ ## Generated API Architecture
286
+
287
+ `parol-pygen generate` now provides a C#-style generated API surface for Python consumers.
288
+ The generated package is intended to be edited/extended by user code, while parser internals
289
+ remain in the reusable runtime.
290
+
291
+ Generated files:
292
+
293
+ - `parser.py`
294
+ - Generated parser facade
295
+ - Adapts runtime reduction payloads to generated typed node classes
296
+ - Dispatches typed semantic action callbacks
297
+ - `nodes.py`
298
+ - Generated concrete node classes (`<NonTerminal>Node`) using `@dataclass`
299
+ - Shared base types for generic and non-terminal nodes
300
+ - `actions.py`
301
+ - Generated action protocol with typed method signatures per non-terminal
302
+ - Base class with generic `on_non_terminal(name, node)` fallback hook
303
+ - `user_actions.py`
304
+ - Editable starter implementation intended for user semantic logic
305
+ - `export.json`
306
+ - Embedded parser export model used by the generated parser facade
307
+
308
+ Semantic action contract:
309
+
310
+ - Preferred method shape: `on_<non_terminal_in_snake_case>(node: <GeneratedNodeType>) -> Any`
311
+ - Generic fallback: `on_non_terminal(name: str, node: NonTerminalNode) -> Any`
312
+
313
+ Compatibility contract for tooling remains machine-readable through `parol-pygen info`
314
+ (`contract_revision`, `supported_contract_revisions`, `model_contract`, `schema_id`,
315
+ `capabilities`). Consumers should continue to fail closed on unknown revisions.
@@ -0,0 +1,51 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "parol-pygen"
7
+ version = "0.1.0"
8
+ description = "Python parser generator/runtime consuming parol export JSON"
9
+ readme = "README.md"
10
+ license = "MIT OR Apache-2.0"
11
+ authors = [
12
+ { name = "parol contributors" },
13
+ ]
14
+ requires-python = ">=3.10"
15
+ keywords = ["parser", "generator", "lalr", "llk", "parol"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Intended Audience :: Developers",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Topic :: Software Development :: Compilers",
24
+ ]
25
+ dependencies = [
26
+ "jsonschema>=4.21.0",
27
+ "scnr2",
28
+ ]
29
+
30
+ [project.optional-dependencies]
31
+ dev = [
32
+ "build>=1.2.1",
33
+ "twine>=5.0.0",
34
+ ]
35
+
36
+ [project.urls]
37
+ Homepage = "https://github.com/jsinger67/parol"
38
+ Repository = "https://github.com/jsinger67/parol"
39
+ Issues = "https://github.com/jsinger67/parol/issues"
40
+
41
+ [project.scripts]
42
+ parol-pygen = "parol_pygen.cli:main"
43
+
44
+ [tool.setuptools]
45
+ package-dir = {"" = "src"}
46
+
47
+ [tool.setuptools.packages.find]
48
+ where = ["src"]
49
+
50
+ [tool.setuptools.package-data]
51
+ parol_pygen = ["schemas/*.json"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+