ppy-lang 0.1.0a1__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.
- ppy_lang-0.1.0a1/.gitignore +23 -0
- ppy_lang-0.1.0a1/CHANGELOG.md +49 -0
- ppy_lang-0.1.0a1/CONTRIBUTING.md +135 -0
- ppy_lang-0.1.0a1/LICENSE +21 -0
- ppy_lang-0.1.0a1/PKG-INFO +325 -0
- ppy_lang-0.1.0a1/README.md +282 -0
- ppy_lang-0.1.0a1/docs/architecture.md +132 -0
- ppy_lang-0.1.0a1/docs/cli.md +353 -0
- ppy_lang-0.1.0a1/docs/compatibility.md +70 -0
- ppy_lang-0.1.0a1/docs/config.md +75 -0
- ppy_lang-0.1.0a1/docs/conversion.md +184 -0
- ppy_lang-0.1.0a1/docs/diagnostics.md +111 -0
- ppy_lang-0.1.0a1/docs/guide.md +108 -0
- ppy_lang-0.1.0a1/docs/language.md +204 -0
- ppy_lang-0.1.0a1/docs/plugins.md +89 -0
- ppy_lang-0.1.0a1/pyproject.toml +232 -0
- ppy_lang-0.1.0a1/scripts/check.sh +17 -0
- ppy_lang-0.1.0a1/src/ppy/__init__.py +154 -0
- ppy_lang-0.1.0a1/src/ppy/_alloc.py +62 -0
- ppy_lang-0.1.0a1/src/ppy/_directives.py +151 -0
- ppy_lang-0.1.0a1/src/ppy/_importer.py +134 -0
- ppy_lang-0.1.0a1/src/ppy/_io.py +381 -0
- ppy_lang-0.1.0a1/src/ppy/_markers.py +290 -0
- ppy_lang-0.1.0a1/src/ppy/py.typed +0 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/__init__.py +0 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/__main__.py +4 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/analysis/__init__.py +0 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/analysis/aliasing.py +410 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/analysis/annotations.py +442 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/analysis/binding.py +99 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/analysis/builtins.py +508 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/analysis/checker.py +3270 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/analysis/codec.py +172 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/analysis/contracts.py +410 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/analysis/decorators.py +266 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/analysis/effects.py +118 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/analysis/env.py +87 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/analysis/global_writes.py +118 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/analysis/inference.py +497 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/analysis/lexical.py +326 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/analysis/refinements.py +156 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/analysis/reflection.py +202 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/analysis/render.py +204 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/analysis/representation.py +112 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/analysis/results.py +104 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/analysis/stdlib.py +232 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/analysis/symbols.py +949 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/analysis/types.py +619 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/backend/__init__.py +0 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/backend/binder.py +5 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/backend/exported_runtime.py +5 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/backend/llvm/__init__.py +745 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/backend/llvm/fused_runtime.py +211 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/backend/llvm/fusion.py +439 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/backend/llvm/jit.py +199 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/backend/llvm/link.py +348 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/backend/llvm/lowering.py +1929 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/backend/llvm/lowering_cache.py +145 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/backend/llvm/parallel.py +89 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/backend/llvm/runtime.py +5 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/backend/llvm/specialize.py +262 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/backend/llvm/standalone.py +300 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/backend/llvm/wrapper.py +536 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/backend/llvm/wrapper_build.py +177 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/backend/python/__init__.py +4 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/backend/python/emit.py +82 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/backend/python/runner.py +11 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/backend/region_runtime.py +5 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/cache/__init__.py +11 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/cache/keys.py +86 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/cache/store.py +509 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/diagnostics/__init__.py +20 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/diagnostics/codes.py +69 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/diagnostics/model.py +176 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/driver/__init__.py +0 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/driver/cli.py +336 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/driver/commands.py +465 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/driver/config.py +225 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/driver/convert.py +1300 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/driver/explain.py +308 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/driver/formatting.py +403 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/driver/linting.py +305 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/driver/pipeline.py +355 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/driver/plan.py +73 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/driver/reporting.py +57 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/driver/rewrite.py +601 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/driver/staging.py +241 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/frontend/__init__.py +0 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/frontend/modules.py +266 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/frontend/parser.py +63 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/frontend/source.py +68 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/lsp/__init__.py +19 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/lsp/protocol.py +75 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/lsp/server.py +294 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/lsp/service.py +523 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/migration/__init__.py +13 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/migration/dynamic.py +325 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/migration/globals.py +92 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/migration/pipeline.py +79 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/migration/report.py +169 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/opt/__init__.py +3 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/opt/annotate.py +64 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/opt/manager.py +272 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/opt/passes.py +833 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/opt/rewrites.py +53 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/plugins/__init__.py +0 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/plugins/base.py +150 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/plugins/jax_export.py +235 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/plugins/jax_plugin.py +629 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/plugins/numpy_plugin.py +463 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/plugins/pydantic_plugin.py +90 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/plugins/registry.py +47 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/plugins/torch_build.py +128 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/plugins/torch_plugin.py +434 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/plugins/torch_region.py +286 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/plugins/uvicorn_plugin.py +265 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/py.typed +0 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/testing/__init__.py +0 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/testing/differential.py +109 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/testing/generate.py +177 -0
- ppy_lang-0.1.0a1/src/ppy_compiler/version.py +48 -0
- ppy_lang-0.1.0a1/src/ppy_runtime/__init__.py +9 -0
- ppy_lang-0.1.0a1/src/ppy_runtime/abi.py +110 -0
- ppy_lang-0.1.0a1/src/ppy_runtime/binding.py +518 -0
- ppy_lang-0.1.0a1/src/ppy_runtime/dispatch.py +60 -0
- ppy_lang-0.1.0a1/src/ppy_runtime/execute.py +197 -0
- ppy_lang-0.1.0a1/src/ppy_runtime/exported.py +74 -0
- ppy_lang-0.1.0a1/src/ppy_runtime/generated.py +120 -0
- ppy_lang-0.1.0a1/src/ppy_runtime/launch.py +142 -0
- ppy_lang-0.1.0a1/src/ppy_runtime/manifest.py +142 -0
- ppy_lang-0.1.0a1/src/ppy_runtime/regions.py +65 -0
- ppy_lang-0.1.0a1/tests/conftest.py +53 -0
- ppy_lang-0.1.0a1/tests/test_analysis.py +2107 -0
- ppy_lang-0.1.0a1/tests/test_backends.py +655 -0
- ppy_lang-0.1.0a1/tests/test_bench.py +253 -0
- ppy_lang-0.1.0a1/tests/test_build.py +782 -0
- ppy_lang-0.1.0a1/tests/test_cache.py +775 -0
- ppy_lang-0.1.0a1/tests/test_cli.py +2290 -0
- ppy_lang-0.1.0a1/tests/test_codec.py +178 -0
- ppy_lang-0.1.0a1/tests/test_conformance.py +994 -0
- ppy_lang-0.1.0a1/tests/test_differential_fuzz.py +56 -0
- ppy_lang-0.1.0a1/tests/test_io.py +195 -0
- ppy_lang-0.1.0a1/tests/test_library_integration.py +777 -0
- ppy_lang-0.1.0a1/tests/test_lsp.py +387 -0
- ppy_lang-0.1.0a1/tests/test_migration.py +282 -0
- ppy_lang-0.1.0a1/tests/test_native.py +3031 -0
- ppy_lang-0.1.0a1/tests/test_plugins.py +576 -0
- ppy_lang-0.1.0a1/tests/test_runtime.py +324 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
.venv/
|
|
4
|
+
testenv/
|
|
5
|
+
dist/
|
|
6
|
+
build/
|
|
7
|
+
*.egg-info/
|
|
8
|
+
.pytest_cache/
|
|
9
|
+
.coverage
|
|
10
|
+
htmlcov/
|
|
11
|
+
.ppy-cache/
|
|
12
|
+
*.md
|
|
13
|
+
!README.md
|
|
14
|
+
!CONTRIBUTING.md
|
|
15
|
+
!CHANGELOG.md
|
|
16
|
+
!docs/*.md
|
|
17
|
+
!examples/**/*.md
|
|
18
|
+
examples/**/uv.lock
|
|
19
|
+
examples/**/.venv/
|
|
20
|
+
|
|
21
|
+
# Compiled C reference binaries built beside an example.
|
|
22
|
+
*_c
|
|
23
|
+
examples/**/native/
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0a1
|
|
4
|
+
|
|
5
|
+
The first release, and an alpha in the ordinary sense: the language and the
|
|
6
|
+
diagnostics are in use and tested, and neither is promised to stay put. Pin
|
|
7
|
+
an exact version.
|
|
8
|
+
|
|
9
|
+
On PyPI as **`ppy-lang`**; the packages it installs are `ppy`,
|
|
10
|
+
`ppy_compiler`, and `ppy_runtime`, so a program still writes `import ppy`.
|
|
11
|
+
|
|
12
|
+
### The language
|
|
13
|
+
|
|
14
|
+
- A `.ppy` file is valid Python 3.12+. Everything PPY adds is carried by
|
|
15
|
+
decorators and annotations from the `ppy` package, all inert under plain
|
|
16
|
+
CPython.
|
|
17
|
+
- Strict analysis by default: an implicit `Any` is an error, dynamic features
|
|
18
|
+
need a `ppy.dynamic` boundary, and a decorator must have vouched semantics.
|
|
19
|
+
- `ppy convert` staticizes Python into strict PPY; `ppy migrate` is the
|
|
20
|
+
permissive form. Both are deterministic, both refuse to write anything when
|
|
21
|
+
the settled analysis holds an error.
|
|
22
|
+
|
|
23
|
+
### Running it
|
|
24
|
+
|
|
25
|
+
Three paths, held to one answer — plain CPython, an optimized Python backend,
|
|
26
|
+
and LLVM-lowered native code. Any observable difference between them is a
|
|
27
|
+
compiler bug; the suite and `examples/run_all.py` compare all three on every
|
|
28
|
+
example.
|
|
29
|
+
|
|
30
|
+
- `ppy build` produces an artifact that runs through `ppy_runtime` with
|
|
31
|
+
machine code from the library beside it, and keeps working with the
|
|
32
|
+
compiler uninstalled.
|
|
33
|
+
- `ppy build --standalone` links a native executable with no CPython inside,
|
|
34
|
+
for a program whose reachable graph is entirely native.
|
|
35
|
+
- `ppy.input[T]`, `ppy.buffer[T]`, and `Buffer[T]` (including one-byte
|
|
36
|
+
`ppy.i8`/`ppy.u8` elements) read and hold data without a Python object per
|
|
37
|
+
value.
|
|
38
|
+
|
|
39
|
+
### Plugins
|
|
40
|
+
|
|
41
|
+
NumPy, PyTorch, JAX/Flax, pydantic, and FastAPI/Uvicorn are modeled; each is
|
|
42
|
+
an optional extra, and a missing runtime disables only its plugin.
|
|
43
|
+
|
|
44
|
+
### Known limits
|
|
45
|
+
|
|
46
|
+
- `ppy.read_token` has no standalone lowering yet, which is the one thing
|
|
47
|
+
keeping the substring-search example off that path.
|
|
48
|
+
- Floats do not print from a standalone binary, pending native formatting
|
|
49
|
+
that reproduces CPython's shortest round-trip repr exactly.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
## Setting up
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
uv sync # the compiler core, LLVM, NumPy, pydantic, and the linters
|
|
7
|
+
./scripts/check.sh # the one gate; CI runs exactly this
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
`uv sync` installs the `dev` group. That is what a contributor to the
|
|
11
|
+
compiler needs and more than a user needs: a user installs `ppy` and gets the
|
|
12
|
+
compiler, the runtime, and nothing else.
|
|
13
|
+
|
|
14
|
+
The plugin runtimes are separate groups, so you install only what you intend
|
|
15
|
+
to test:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
uv sync --group torch # PyTorch, CPU wheels
|
|
19
|
+
uv sync --group jax # JAX and Flax, CPU wheels
|
|
20
|
+
uv sync --group uvicorn # FastAPI and Uvicorn
|
|
21
|
+
uv sync --group all # everything
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Torch and JAX resolve from the CPU index, which works on every platform.
|
|
25
|
+
On a CUDA machine, override it for your checkout rather than for the
|
|
26
|
+
repository:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
uv sync --group torch --index pytorch-cuda=https://download.pytorch.org/whl/cu128
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## The gate
|
|
33
|
+
|
|
34
|
+
`./scripts/check.sh` is the single source of truth for "clean": ruff, ruff
|
|
35
|
+
format, pylint, the test suite, the conversion check, the three-path example
|
|
36
|
+
run, and the example lint. CI runs that script and nothing else, so a local
|
|
37
|
+
pass and a CI pass are the same claim. Run it before every commit.
|
|
38
|
+
|
|
39
|
+
A plugin's own claim is `./scripts/plugin_check.sh <torch|jax|uvicorn>`,
|
|
40
|
+
which fails if the plugin's tests all skip — a skipped test proves nothing.
|
|
41
|
+
|
|
42
|
+
## What a change has to keep true
|
|
43
|
+
|
|
44
|
+
- **The three paths agree.** Plain CPython, the optimized Python backend, and
|
|
45
|
+
the native backend produce the same answer for the same program.
|
|
46
|
+
`examples/run_all.py` checks every example on all three.
|
|
47
|
+
- **A guard that fails falls back.** Native code that cannot keep a promise
|
|
48
|
+
runs the Python body; it never answers differently.
|
|
49
|
+
- **The cache is disposable.** Corrupting or deleting any part of it may cost
|
|
50
|
+
a rebuild and must never cost the answer. See
|
|
51
|
+
[docs/compatibility.md](docs/compatibility.md).
|
|
52
|
+
- **`ppy_runtime` never imports `ppy_compiler`.** A built artifact keeps
|
|
53
|
+
working with the compiler uninstalled, and a test enforces it.
|
|
54
|
+
- **A generated `.ppy` is exactly what `ppy convert` writes.** Never hand-edit
|
|
55
|
+
one; `examples/verify_conversions.py` regenerates and diffs them.
|
|
56
|
+
|
|
57
|
+
## Examples
|
|
58
|
+
|
|
59
|
+
An example is either hand-written or generated, and its README says which.
|
|
60
|
+
A generated one keeps its `.py` source beside it and is regenerated with
|
|
61
|
+
`ppy convert <name>.py` (some folders add `--promote-buffers`; see
|
|
62
|
+
`examples/verify_conversions.py`).
|
|
63
|
+
|
|
64
|
+
`python scripts/refresh.py` reports anything that has drifted — an example
|
|
65
|
+
that no longer checks, a conversion that no longer matches, a measurement
|
|
66
|
+
that has moved, a README table that is behind the record — and `--write`
|
|
67
|
+
brings all of them back in line. `--quick` skips the benchmark; `--record
|
|
68
|
+
FILE` also writes the raw numbers of that run, drift or not.
|
|
69
|
+
|
|
70
|
+
## Measurements
|
|
71
|
+
|
|
72
|
+
Numbers in the documentation come from `examples/15_algorithms/bench.py`,
|
|
73
|
+
are recorded in `measurements.json` with the machine they were taken on, and
|
|
74
|
+
the README tables are rendered from that file rather than typed. They are
|
|
75
|
+
not a per-change gate: a scheduled workflow re-measures and reports drift
|
|
76
|
+
beyond a tolerance, because absolute wall times differ between runners.
|
|
77
|
+
|
|
78
|
+
What counts as drift needs both signals: the milliseconds moved beyond the
|
|
79
|
+
tolerance *and* the ratio to the C reference moved with them. A busy machine
|
|
80
|
+
slows every path at once, so the ratio holds where the times do not; and the
|
|
81
|
+
reference is a few milliseconds on the smaller problems, so a wobble there
|
|
82
|
+
moves every ratio at once. Either on its own reports the machine. `ppy run`
|
|
83
|
+
is exempt from the ratio entirely — it is mostly the compiler, and there is
|
|
84
|
+
no ratio to take against a C program that compiled beforehand — so its
|
|
85
|
+
movement is reported and never fatal.
|
|
86
|
+
|
|
87
|
+
A machine that cannot build every path fails the run and records nothing. A
|
|
88
|
+
record with a column missing would replace a whole one, and the gap would
|
|
89
|
+
read as a result rather than as a machine without `gcc` or without a shared
|
|
90
|
+
libpython; `bench.py` says up front which paths it had to skip and why, and
|
|
91
|
+
`--record` still writes what it measured so a failed scheduled run keeps its
|
|
92
|
+
evidence. That file may not be `measurements.json` itself: the baseline is
|
|
93
|
+
written only once the run is judged worth keeping.
|
|
94
|
+
|
|
95
|
+
## Releasing
|
|
96
|
+
|
|
97
|
+
The distribution is **`ppy-lang`**; the packages it installs are `ppy`,
|
|
98
|
+
`ppy_compiler`, and `ppy_runtime`.
|
|
99
|
+
|
|
100
|
+
`COMPILER_VERSION` in `src/ppy_compiler/version.py` is the version. The
|
|
101
|
+
packaging metadata reads it (`[tool.hatch.version]`), so `pyproject.toml`
|
|
102
|
+
does not repeat it. `ppy.__version__` is a second literal, deliberately: the
|
|
103
|
+
runtime package does not import the compiler, and giving it one just to
|
|
104
|
+
learn a string would be a dependency in the wrong direction. A test holds
|
|
105
|
+
the two together, along with the installed distribution's metadata, because
|
|
106
|
+
the compiler keys its caches on that string and a stale copy would serve
|
|
107
|
+
artifacts from a version that is not running.
|
|
108
|
+
|
|
109
|
+
To cut a release:
|
|
110
|
+
|
|
111
|
+
1. Move `COMPILER_VERSION` and `ppy.__version__` together, and write the
|
|
112
|
+
release into `CHANGELOG.md`.
|
|
113
|
+
2. `./scripts/check.sh`, then `uv build` and
|
|
114
|
+
`uv run --with twine twine check dist/*`.
|
|
115
|
+
3. Tag it `vX.Y.Z` — matching the declared version, which the workflow
|
|
116
|
+
verifies — and push the tag. `.github/workflows/release.yml` runs the
|
|
117
|
+
gate, builds, installs the built wheel into a clean environment and runs
|
|
118
|
+
it, and publishes.
|
|
119
|
+
|
|
120
|
+
A workflow pins actions by **ref**, and a ref is not a release. Some
|
|
121
|
+
publishers cut releases past the last moving major tag they maintain, so
|
|
122
|
+
`gh api repos/OWNER/REPO/releases/latest` can name a version that
|
|
123
|
+
`uses:` cannot resolve. Check the ref itself before changing one:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
gh api repos/astral-sh/setup-uv/git/ref/tags/v10.0.1 --jq .ref
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The workflow publishes through PyPI's trusted publishing, so there is no API
|
|
130
|
+
token in the repository. It needs, once: a pending publisher on PyPI for
|
|
131
|
+
`ppy-lang` naming this repository, the workflow `release.yml`, and the
|
|
132
|
+
environment `pypi`; the same on TestPyPI with the environment `testpypi`;
|
|
133
|
+
and both environments created under the repository's settings.
|
|
134
|
+
`workflow_dispatch` on the workflow publishes to TestPyPI by default, which
|
|
135
|
+
is the way to rehearse one.
|
ppy_lang-0.1.0a1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 franknoh
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: ppy-lang
|
|
3
|
+
Version: 0.1.0a1
|
|
4
|
+
Summary: Pretty Python: a source-compatible, statically analyzable subset of Python
|
|
5
|
+
Project-URL: Homepage, https://github.com/franknoh/PPy
|
|
6
|
+
Project-URL: Repository, https://github.com/franknoh/PPy
|
|
7
|
+
Project-URL: Documentation, https://github.com/franknoh/PPy/blob/main/docs/guide.md
|
|
8
|
+
Project-URL: Issues, https://github.com/franknoh/PPy/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/franknoh/PPy/blob/main/CHANGELOG.md
|
|
10
|
+
Author: franknoh
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: compiler,jit,llvm,native,python,static-analysis
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
21
|
+
Classifier: Topic :: Software Development :: Compilers
|
|
22
|
+
Classifier: Topic :: Software Development :: Interpreters
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.12
|
|
25
|
+
Requires-Dist: libcst>=1.4
|
|
26
|
+
Requires-Dist: typing-extensions>=4.12
|
|
27
|
+
Provides-Extra: jax
|
|
28
|
+
Requires-Dist: flatbuffers>=24.0; extra == 'jax'
|
|
29
|
+
Requires-Dist: jax>=0.4; extra == 'jax'
|
|
30
|
+
Requires-Dist: jaxlib>=0.4; extra == 'jax'
|
|
31
|
+
Provides-Extra: llvm
|
|
32
|
+
Requires-Dist: llvmlite>=0.44; extra == 'llvm'
|
|
33
|
+
Provides-Extra: numpy
|
|
34
|
+
Requires-Dist: numpy>=1.26; extra == 'numpy'
|
|
35
|
+
Provides-Extra: pydantic
|
|
36
|
+
Requires-Dist: pydantic>=2.0; extra == 'pydantic'
|
|
37
|
+
Provides-Extra: torch
|
|
38
|
+
Requires-Dist: ninja>=1.11; extra == 'torch'
|
|
39
|
+
Requires-Dist: torch>=2.4; extra == 'torch'
|
|
40
|
+
Provides-Extra: uvicorn
|
|
41
|
+
Requires-Dist: uvicorn>=0.30; extra == 'uvicorn'
|
|
42
|
+
Description-Content-Type: text/markdown
|
|
43
|
+
|
|
44
|
+
# PPY — Pretty Python
|
|
45
|
+
|
|
46
|
+
[](https://pypi.org/project/ppy-lang/)
|
|
47
|
+
[](https://pypi.org/project/ppy-lang/)
|
|
48
|
+
[](https://github.com/franknoh/PPy/actions/workflows/ci.yml)
|
|
49
|
+
[](LICENSE)
|
|
50
|
+
|
|
51
|
+
A statically analyzable language in Python's syntax. A `.ppy` file *is* valid
|
|
52
|
+
Python: it runs under plain CPython with no compiler involved. The compiler
|
|
53
|
+
adds static checking, an optimized Python backend, and an LLVM native
|
|
54
|
+
backend — and all three must produce the same answer.
|
|
55
|
+
|
|
56
|
+
PPY is source-compatible with Python's syntax and ecosystem, not with every
|
|
57
|
+
dynamic Python behavior: `exec`/`eval`, monkey-patching, dynamic namespace
|
|
58
|
+
mutation, and unrestricted runtime reflection are deliberately restricted —
|
|
59
|
+
or isolated behind an explicit `ppy.dynamic` boundary — in exchange for
|
|
60
|
+
analysis, optimization, and native compilation that can be trusted. Running
|
|
61
|
+
existing Python is a migration feature (`ppy migrate`), not the definition of
|
|
62
|
+
the language.
|
|
63
|
+
|
|
64
|
+
## Install
|
|
65
|
+
|
|
66
|
+
Add PPY to your project with [uv](https://docs.astral.sh/uv/) (Python 3.12+):
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
uv add "ppy-lang[llvm]"
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
or with pip: `pip install "ppy-lang[llvm]"`. The distribution is `ppy-lang`
|
|
73
|
+
and what it installs is `ppy`, so your code writes `import ppy`.
|
|
74
|
+
|
|
75
|
+
Releases are alphas — the language and the diagnostics are in use and
|
|
76
|
+
tested, and neither is promised to stay put — so pin an exact version:
|
|
77
|
+
`uv add "ppy-lang[llvm]==0.1.0a1"`. For the development tip instead:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
uv add "ppy-lang[llvm] @ git+https://github.com/franknoh/PPy.git"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The base package is the compiler and the runtime; extras enable the rest, so
|
|
84
|
+
you install only what you use:
|
|
85
|
+
|
|
86
|
+
| extra | enables |
|
|
87
|
+
|---|---|
|
|
88
|
+
| `llvm` | the native backend (llvmlite) |
|
|
89
|
+
| `numpy` / `pydantic` / `uvicorn` | the matching plugin |
|
|
90
|
+
| `torch` / `jax` | the matching plugin, CPU builds by default — point at a CUDA index in your own project if you want one |
|
|
91
|
+
|
|
92
|
+
Everything degrades cleanly: a missing library only disables its plugin, and
|
|
93
|
+
`uv run ppy doctor` reports what was found. For the fastest native call
|
|
94
|
+
boundary, have the CPython headers installed (`python3-dev`); without them
|
|
95
|
+
PPY says so once and uses the slower boundary.
|
|
96
|
+
|
|
97
|
+
## One file, four ways
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
# collatz.ppy
|
|
101
|
+
import ppy
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@ppy.pure
|
|
105
|
+
@ppy.opt(3)
|
|
106
|
+
def longest(limit: int) -> int:
|
|
107
|
+
best: int = 0
|
|
108
|
+
for start in range(1, limit):
|
|
109
|
+
n: int = start
|
|
110
|
+
steps: int = 0
|
|
111
|
+
while n != 1:
|
|
112
|
+
n = n // 2 if n % 2 == 0 else 3 * n + 1
|
|
113
|
+
steps += 1
|
|
114
|
+
best = max(best, steps)
|
|
115
|
+
return best
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
print(longest(ppy.input[int]()))
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`ppy.input[T]()` reads the next value the way `T` says to read it, straight
|
|
122
|
+
into memory rather than through a Python object per field. Piping the limit
|
|
123
|
+
in, `echo 300000 |` before each of these:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
uv run python collatz.ppy # 1 plain CPython, no compiler 1170.2 ± 9.1 ms
|
|
127
|
+
uv run ppy collatz.ppy # 2 optimized Python backend 1167.9 ± 13.1 ms
|
|
128
|
+
uv run ppy run collatz.ppy # 3 LLVM, JIT every run 45.4 ± 1.3 ms (+ ~1200 ms JIT)
|
|
129
|
+
uv run ppy build collatz.ppy -o dist # 4 LLVM, built once (~800 ms)...
|
|
130
|
+
./dist/collatz # ...then the native binary 33.6 ± 1.1 ms
|
|
131
|
+
# ...--host-cpu, not portable 31.8 ± 0.9 ms
|
|
132
|
+
|
|
133
|
+
gcc -O3 collatz.c && ./a.out # reference: the same loop in C 45.0 ± 0.6 ms (+ ~110 ms gcc)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Numbers are the kernel's wall time on one machine — mean ± standard
|
|
137
|
+
deviation over ten runs, each a fresh process; the parenthesized figure is
|
|
138
|
+
what that row spends turning source into machine code, and how often.
|
|
139
|
+
Ways 3 and 4 are one compilation path, ahead of time or not — the binary is
|
|
140
|
+
`ppy run` in a compiled coat, machine code taken from the library built next
|
|
141
|
+
to it. They differ in one default: `ppy run` keeps Python-integer semantics
|
|
142
|
+
(overflow is guarded and falls back to arbitrary precision — that is the
|
|
143
|
+
45.4 ms, level with C with the guards in), while `ppy build` produces a
|
|
144
|
+
wrap-semantics artifact like every native compiler — that is the 33.6 ms,
|
|
145
|
+
past C. `run --unsafe` and `build --safe` flip either one; bounds
|
|
146
|
+
checks stay in both. The built binary is compiled software: it starts an
|
|
147
|
+
embedded interpreter and imports `ppy_runtime` — about 35 ms before the
|
|
148
|
+
program begins — and keeps working with the compiler uninstalled.
|
|
149
|
+
`ppy build --standalone` removes even that, for a program whose reachable
|
|
150
|
+
graph is entirely native. (Python's floor semantics help too:
|
|
151
|
+
`n // 2` lowers to one arithmetic shift exactly, where C's truncating
|
|
152
|
+
division needs a sign fixup.)
|
|
153
|
+
|
|
154
|
+
The same kernel through the neighbors, same machine and methodology:
|
|
155
|
+
|
|
156
|
+
| compiler | kernel | integer semantics |
|
|
157
|
+
|---|---:|---|
|
|
158
|
+
| **PPY** `ppy build --host-cpu` | **31.8 ± 0.9 ms** | 64-bit, wraps on overflow (this machine's instruction set) |
|
|
159
|
+
| **PPY** `ppy build` binary | **33.6 ± 1.1 ms** | 64-bit, wraps on overflow (`--safe` to keep Python ints) |
|
|
160
|
+
| Codon `-release` | 35.9 ± 1.9 ms | 64-bit, wraps on overflow |
|
|
161
|
+
| Numba `@njit` | 36.5 ± 1.0 ms | 64-bit, wraps on overflow |
|
|
162
|
+
| C (`gcc -O3`) | 45.0 ± 0.6 ms | 64-bit, wraps on overflow |
|
|
163
|
+
| **PPY** `ppy run` | **45.4 ± 1.3 ms** | **Python ints: guarded, falls back to arbitrary precision** |
|
|
164
|
+
| PyPy 3.11 | 56.9 ± 2.5 ms | Python ints |
|
|
165
|
+
| mypyc | 78.4 ± 1.4 ms | Python ints |
|
|
166
|
+
| Cython (`cdef long long`) | 81.4 ± 3.0 ms | 64-bit, wraps on overflow |
|
|
167
|
+
| Nuitka | 776.9 ± 11.6 ms | Python ints, no type specialization |
|
|
168
|
+
| CPython 3.14 | 1170.2 ± 9.1 ms | Python ints |
|
|
169
|
+
|
|
170
|
+
`--host-cpu` is the opt-in that compiles for the machine doing the build
|
|
171
|
+
instead of the portable baseline — 33.6 ms to 31.8 ms here, and about 20% on
|
|
172
|
+
a matmul kernel where the vectorizer has something to work with. It is off
|
|
173
|
+
by default because an artifact is meant to be shipped and host code faults
|
|
174
|
+
on an older CPU; JIT code under `ppy run` always targets the host, which is
|
|
175
|
+
free because it never leaves the machine. Giving C the same option changes
|
|
176
|
+
nothing on this kernel (`gcc -O3 -march=native`: 45.5 ± 1.2 ms), so the row
|
|
177
|
+
above it is not winning on a flag C was denied.
|
|
178
|
+
|
|
179
|
+
Same compiler, `run` and `build`: the 12 ms between them is the price of
|
|
180
|
+
Python's integers, and it is a per-command default rather than a language decision —
|
|
181
|
+
wrap semantics where a native artifact is expected, full Python semantics
|
|
182
|
+
where a Python program is. Ports are the straightforward one for each tool:
|
|
183
|
+
`@njit`, a `cdef long long` `.pyx`, an annotated module for mypyc, `codon
|
|
184
|
+
build -release`, `nuitka --module`; Numba 0.67, Cython 3.3, mypy 2.3.1,
|
|
185
|
+
Nuitka on CPython 3.13, Codon 0.19.6, PyPy 3.11.15 (warm).
|
|
186
|
+
|
|
187
|
+
## Turning ordinary Python into it
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
uv run ppy convert src/ --in-place # strict: the output must be valid strict PPY
|
|
191
|
+
uv run ppy migrate src/ --in-place # permissive: rewrite toward it, report the rest
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
`convert` is the compiler-facing command: it refuses to produce anything
|
|
195
|
+
`ppy check` would reject, and says why. `migrate` is for normal existing
|
|
196
|
+
Python — it first rewrites dynamic-but-static patterns (`setattr` with a
|
|
197
|
+
constant name, `globals()["X"] = ...`, constant `importlib.import_module`),
|
|
198
|
+
then staticizes, and classifies whatever remains (`--report migration.json`,
|
|
199
|
+
`--diff`).
|
|
200
|
+
|
|
201
|
+
Given untyped Python:
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
import math
|
|
205
|
+
|
|
206
|
+
LIMIT = 3.0
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def clamp(value):
|
|
210
|
+
return min(value, LIMIT)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def spread(samples):
|
|
214
|
+
total = 0.0
|
|
215
|
+
for sample in samples:
|
|
216
|
+
total += clamp(sample)
|
|
217
|
+
return math.sqrt(total / len(samples))
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
print(spread([1.0, 2.0, 9.0]), spread((4.0, 5.0)))
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
`ppy convert` writes:
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
import math
|
|
227
|
+
from collections.abc import Sequence
|
|
228
|
+
from typing import Final
|
|
229
|
+
|
|
230
|
+
import ppy
|
|
231
|
+
|
|
232
|
+
LIMIT: Final[float] = 3.0
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
@ppy.pure
|
|
236
|
+
def clamp(value: float) -> float:
|
|
237
|
+
return min(value, LIMIT)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@ppy.pure
|
|
241
|
+
def spread(samples: Sequence[float]) -> float:
|
|
242
|
+
total: float = 0.0
|
|
243
|
+
for sample in samples:
|
|
244
|
+
total += clamp(sample)
|
|
245
|
+
return math.sqrt(total / len(samples))
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
print(spread([1.0, 2.0, 9.0]), spread((4.0, 5.0)))
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Types come from the whole call graph, not one file. `samples` is `Sequence`
|
|
252
|
+
rather than `list` because the body only reads it and one call site passes a
|
|
253
|
+
tuple; `LIMIT` is `Final` because nothing rebinds it; `@ppy.pure` is attached
|
|
254
|
+
only where the checker proved it. Nothing is renamed and no function is split —
|
|
255
|
+
those are design decisions, not mechanical ones.
|
|
256
|
+
|
|
257
|
+
## Works with
|
|
258
|
+
|
|
259
|
+
Each library is a plugin: the compiler learns that library's types and effects,
|
|
260
|
+
takes a faster path where it can prove one is equivalent, and falls back to the
|
|
261
|
+
ordinary Python call everywhere else. A guard that fails is a fallback, never a
|
|
262
|
+
different answer.
|
|
263
|
+
|
|
264
|
+
**NumPy** — elementwise expressions fuse into a single loop with no
|
|
265
|
+
temporaries; `dot`, `matmul`, `inner`, `vdot`, and `tensordot` route to the
|
|
266
|
+
linear-algebra path. Contiguity and shape are guarded at runtime, not assumed.
|
|
267
|
+
Reduction order is preserved unless `@ppy.fastmath` permits reassociation, so a
|
|
268
|
+
sum stays bit-identical to NumPy's.
|
|
269
|
+
|
|
270
|
+
**PyTorch** — a function whose body is entirely curated tensor operations (55 of
|
|
271
|
+
them) compiles into one C++ region calling ATen directly, removing a Python
|
|
272
|
+
round trip per operator. Every call still goes through the dispatcher, so
|
|
273
|
+
autograd, device selection, and backend keys are unchanged; a tensor subclass or
|
|
274
|
+
a `__torch_function__` override trips the guard and the Python body runs. CUDA
|
|
275
|
+
is used when it is there.
|
|
276
|
+
|
|
277
|
+
**JAX / Flax** — a `@jax.jit` function whose inputs carry `ppy.Shape` and
|
|
278
|
+
`ppy.DType` can be exported to StableHLO at build time, so the trace is not
|
|
279
|
+
repeated at startup; shapes may be symbolic, so one artifact serves every
|
|
280
|
+
batch size (export runs project code and is off until the project opts in).
|
|
281
|
+
The same plugin models Flax and optax — layers, activations, `Module.init`/
|
|
282
|
+
`apply` through the external MRO, optimizers — so a Flax training loop
|
|
283
|
+
checks under strict mode as-is.
|
|
284
|
+
|
|
285
|
+
**Pydantic** — models are typed, constructor and output shapes are kept
|
|
286
|
+
distinct, and field constraints become refinements the checker can use.
|
|
287
|
+
|
|
288
|
+
**Uvicorn / FastAPI** — the ASGI application is resolved statically instead of
|
|
289
|
+
re-imported by module string per worker, and the reloader is told to watch
|
|
290
|
+
`.ppy`. FastAPI rides the same plugin: its surface is modeled so strict mode
|
|
291
|
+
checks a FastAPI service as-is, while route handlers keep the exact
|
|
292
|
+
signatures FastAPI reads at import.
|
|
293
|
+
|
|
294
|
+
The plugin's exact version is part of every cache key, so an artifact built
|
|
295
|
+
against one build of a library is never reused against another. `ppy doctor`
|
|
296
|
+
prints what it found.
|
|
297
|
+
|
|
298
|
+
## Docs
|
|
299
|
+
|
|
300
|
+
- [docs/guide.md](docs/guide.md) — overview, measurements, the import hook
|
|
301
|
+
- [docs/language.md](docs/language.md) — the subset, directives, markers
|
|
302
|
+
- [docs/conversion.md](docs/conversion.md) — how `ppy convert` and `ppy migrate` infer what they write
|
|
303
|
+
- [docs/architecture.md](docs/architecture.md) — pipeline, cache, threads
|
|
304
|
+
- [docs/plugins.md](docs/plugins.md) — how each library integration works
|
|
305
|
+
- [docs/config.md](docs/config.md) — every `[tool.ppy]` key
|
|
306
|
+
- [docs/diagnostics.md](docs/diagnostics.md) — every diagnostic code
|
|
307
|
+
- [docs/cli.md](docs/cli.md) — every command and option
|
|
308
|
+
- [docs/compatibility.md](docs/compatibility.md) — what is stable, what moves, and what the cache and artifact ABI promise
|
|
309
|
+
- [examples/README.md](examples/README.md) — 30 folders, 39 runnable programs
|
|
310
|
+
- [CONTRIBUTING.md](CONTRIBUTING.md) — setting up, the gate, and what a change has to keep true
|
|
311
|
+
- [the implementation spec](ppy-compiler-implementation-spec-v1.md) — the normative baseline the source cites by section number (`spec 11.2`, `spec 16.4`, …)
|
|
312
|
+
|
|
313
|
+
## Development
|
|
314
|
+
|
|
315
|
+
```bash
|
|
316
|
+
git clone https://github.com/franknoh/PPy.git
|
|
317
|
+
cd PPy
|
|
318
|
+
uv sync # the compiler core, LLVM, NumPy, pydantic, and the linters
|
|
319
|
+
./scripts/check.sh # the one gate; CI runs exactly this
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
Plugin runtimes are separate groups (`uv sync --group torch|jax|uvicorn|all`),
|
|
323
|
+
and everything else a contributor needs — the gate, the invariants a change
|
|
324
|
+
has to keep, how examples and measurements are kept current — is in
|
|
325
|
+
[CONTRIBUTING.md](CONTRIBUTING.md).
|