Skip to content

Commit e60e44b

Browse files
committed
feat: implement PyReflect
- pyreflect CLI: A transpiler for reflective programming in Python, which converts a template JSON to executable Python nodes. - pyreflect module: A Python library, which provides a template parser, validator, transpiler, and auxiliary functions. - Example templates: quine, mutual_quine and trinity_quine. - README.md: Documentation Signed-off-by: Takuma IMAMURA <takuma.imamura@acompany-ac.com>
1 parent c080baa commit e60e44b

12 files changed

Lines changed: 617 additions & 1 deletion

File tree

.github/workflows/ruff.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Ruff
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- '**/*.py'
7+
- 'pyproject.toml'
8+
- '.github/workflows/ruff.yml'
9+
push:
10+
branches:
11+
- main
12+
paths:
13+
- '**/*.py'
14+
- 'pyproject.toml'
15+
- '.github/workflows/ruff.yml'
16+
workflow_dispatch:
17+
18+
permissions:
19+
contents: read
20+
21+
jobs:
22+
ruff:
23+
runs-on: ubuntu-latest
24+
timeout-minutes: 10
25+
steps:
26+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
27+
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
28+
with:
29+
python-version: "3.12"
30+
- name: Run ruff check
31+
run: uv run ruff check
32+
- name: Run ruff format --check
33+
run: uv run ruff format --check

.github/workflows/spdx.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: SPDX Check
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- '**/*.py'
7+
- 'pyproject.toml'
8+
- '.github/workflows/spdx.yml'
9+
push:
10+
branches:
11+
- main
12+
paths:
13+
- '**/*.py'
14+
- 'pyproject.toml'
15+
- '.github/workflows/spdx.yml'
16+
workflow_dispatch:
17+
18+
concurrency:
19+
group: ${{ github.workflow }}-${{ github.ref }}
20+
cancel-in-progress: true
21+
22+
permissions:
23+
contents: read
24+
25+
jobs:
26+
check-spdx-headers:
27+
runs-on: ubuntu-latest
28+
timeout-minutes: 5
29+
steps:
30+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
31+
- uses: enarx/spdx@d4020ee98e3101dd487c5184f27c6a6fb4f88709 # master
32+
with:
33+
licenses: MIT

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
# Transpiler output
2+
manifest.json
3+
node__*.py
4+
5+
# System
6+
.DS_Store
7+
18
# Byte-compiled / optimized / DLL files
29
__pycache__/
310
*.py[codz]

README.md

Lines changed: 135 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,135 @@
1-
# PyReflect
1+
# PyReflect
2+
3+
![SemVer](https://img.shields.io/badge/PyReflect-0.1.0-white)
4+
![Python Version](https://img.shields.io/badge/Python-3.12-blue)
5+
[![License](https://img.shields.io/badge/License-MIT-red)](/LICENSE)
6+
7+
A transpiler for mutually-referential reflective programming in Python. It is available as a library (`pyreflect`) and a command-line tool (`pyreflect`).
8+
9+
## Overview
10+
11+
PyReflect is a transpiler that makes *mutually-referential* programs — programs made of several nodes — possible. Every node can reference the source code of itself and of the other nodes, without relying on any external source (e.g. file, stdin, registry).
12+
13+
Suppose node A and node B each want to reference the other's code. The naïve approach is to embed A's code inside B and B's code inside A. But then A's code has changed, so the copy of A embedded in B must be updated; that changes B's code, so the copy of B embedded in A must be updated; that changes A's code again, *ad infinitum*. In general this "fixed point" problem has no solution: for A to contain B we need $\mathsf{len}\left(A\right) > \mathsf{len}\left(B\right)$, and for B to contain A we need $\mathsf{len}\left(B\right) > \mathsf{len}\left(A\right)$ — a contradiction.
14+
15+
This infinite regress can be resolved by **Kleene's second recursion theorem**. Instead of embedding the code, each node embeds a *code generator* together with its input data. The generator can reconstruct the exact code of every node (itself and its peers) from the embedded input data — derived intrinsically, never fetched from any external source.
16+
17+
## Install
18+
19+
```bash
20+
uv pip install git+https://github.com/acompany-develop/PyReflect
21+
```
22+
23+
This exposes the `pyreflect` command and the importable `pyreflect` package.
24+
25+
## Usage
26+
27+
### Library API
28+
29+
```py
30+
from pyreflect import parse_template, transpile
31+
32+
with open(path, encoding="utf-8") as f:
33+
# Read
34+
text = f.read()
35+
# Parse
36+
template = parse_template(text)
37+
# Transpile
38+
nodes = transpile(template)
39+
```
40+
41+
### Transpiler CLI
42+
43+
```bash
44+
# Input from file
45+
pyreflect TEMPLATE.json OUTPUT_DIR
46+
47+
# Input from stdin
48+
cat TEMPLATE.json | pyreflect - OUTPUT_DIR
49+
```
50+
51+
It writes one file per node (`node_<id>.py`) with a `manifest.json` mapping node-id to filename.
52+
53+
## Transpiler details
54+
55+
The `pyreflect` transpiler converts a template into N standalone Python programs. A template is a JSON list of node objects:
56+
57+
```json
58+
[ {"node-id": "__NODE1", "code": "<python source>"},
59+
{"node-id": "__NODE2", "code": "<python source>"},
60+
... ]
61+
```
62+
63+
Each node has a `node-id` and a `code` body that may reference any subset of the node-ids (itself included).
64+
65+
The transpiler does two things:
66+
67+
1. rewrites every node-id token in a code body into `__pyreflect_render__("<target-node-id>")`;
68+
2. wraps each body with an identical framework, including the definition of the `__pyreflect_render__` function.
69+
70+
The `__pyreflect_render__` function reconstructs the exact source code of the specified node from data embedded in the emitted source code without any external source of information.
71+
72+
The framework exposes the following API to each body:
73+
74+
```plaintext
75+
__pyreflect_SELF__ this node's id
76+
__pyreflect_DATA__ Base64-encoded template
77+
__pyreflect_self_id__() this node's id
78+
__pyreflect_node_ids__() all node ids, in template order
79+
__pyreflect_render__(target) exact source code (string) of the target node
80+
```
81+
82+
A body must not redefine the reserved names of the form `__pyreflect_*__`.
83+
84+
## Example code
85+
86+
### quine
87+
88+
Single node that prints its own code.
89+
90+
```bash
91+
# Transpile
92+
pyreflect examples/quine/template.json examples/quine/
93+
94+
# Run Node: Display its own code
95+
uv run examples/quine/node___NODE.py
96+
97+
# Verify
98+
cat examples/quine/node___NODE.py
99+
```
100+
101+
### mutual_quine
102+
103+
Two nodes, each of which prints the other's *SHA-256 digest*. The peer value is obtained intrinsically — node 1 reconstructs node 2's source from its own embedded data and hashes it, and vice versa — so node 1's self hash equals the value node 2 reports as its expected peer reference, and vice versa.
104+
105+
```bash
106+
# Transpile
107+
pyreflect examples/mutual_quine/template.json examples/mutual_quine/
108+
109+
# Run Node 1: Display the SHA-256 digest of Node 2
110+
uv run examples/mutual_quine/node___NODE1.py
111+
# Run Node 2: Display the SHA-256 digest of Node 1
112+
uv run examples/mutual_quine/node___NODE2.py
113+
114+
# Verify
115+
sha256sum examples/mutual_quine/*.py
116+
```
117+
118+
### trinity_quine
119+
120+
A variant of `mutual_quine` with three nodes wired into a cycle: node 1 prints node 2's digest, node 2 prints node 3's, node 3 prints node 1's (1 → 2 → 3 → 1). It demonstrates that the transpiler handles arbitrary n-node reference graphs, not just the symmetric two-node case.
121+
122+
```bash
123+
# Transpile
124+
pyreflect examples/trinity_quine/template.json examples/trinity_quine/
125+
126+
# Run Node 1: Display the SHA-256 digest of Node 2
127+
uv run examples/trinity_quine/node___NODE1.py
128+
# Run Node 2: Display the SHA-256 digest of Node 3
129+
uv run examples/trinity_quine/node___NODE2.py
130+
# Run Node 3: Display the SHA-256 digest of Node 1
131+
uv run examples/trinity_quine/node___NODE3.py
132+
133+
# Verify
134+
sha256sum examples/trinity_quine/*.py
135+
```
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[
2+
{
3+
"node-id": "__NODE1",
4+
"code": "# ========== BODY ========== \nimport hashlib\nprint(hashlib.sha256(__NODE2.encode('utf-8')).hexdigest())"
5+
},
6+
{
7+
"node-id": "__NODE2",
8+
"code": "# ========== BODY ========== \nimport hashlib\nprint(hashlib.sha256(__NODE1.encode('utf-8')).hexdigest())"
9+
}
10+
]

examples/quine/template.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[
2+
{
3+
"node-id": "__NODE",
4+
"code": "# ========== BODY ========== \nprint(__NODE)"
5+
}
6+
]
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[
2+
{
3+
"node-id": "__NODE1",
4+
"code": "# ========== BODY ========== \nimport hashlib\nprint(hashlib.sha256(__NODE2.encode('utf-8')).hexdigest())"
5+
},
6+
{
7+
"node-id": "__NODE2",
8+
"code": "# ========== BODY ========== \nimport hashlib\nprint(hashlib.sha256(__NODE3.encode('utf-8')).hexdigest())"
9+
},
10+
{
11+
"node-id": "__NODE3",
12+
"code": "# ========== BODY ========== \nimport hashlib\nprint(hashlib.sha256(__NODE1.encode('utf-8')).hexdigest())"
13+
}
14+
]

pyproject.toml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[project]
2+
name = "PyReflect"
3+
version = "0.1.0"
4+
readme = "README.md"
5+
requires-python = ">= 3.12"
6+
license = "MIT"
7+
dependencies = []
8+
9+
[project.scripts]
10+
pyreflect = "pyreflect.cli:main"
11+
12+
[build-system]
13+
requires = ["hatchling>=1.30"]
14+
build-backend = "hatchling.build"
15+
16+
[tool.hatch.build.targets.wheel]
17+
packages = ["src/pyreflect"]
18+
19+
[tool.ruff]
20+
target-version = "py312"

src/pyreflect/__init__.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# SPDX-License-Identifier: MIT
2+
3+
"""PyReflect -- a general mutual-reference transpiler.
4+
5+
Turns a template describing N nodes into N standalone Python programs, each able
6+
to reconstruct the exact source of every node (itself included) from data
7+
embedded in itself.
8+
9+
Public API::
10+
11+
from pyreflect import load_template, parse_template, transpile
12+
13+
template = load_template("template.json")
14+
nodes = transpile(template) # {node_id: source_code}
15+
"""
16+
17+
__version__ = "0.1.0"
18+
19+
from .template import (
20+
Node,
21+
Template,
22+
node_ids,
23+
parse_template,
24+
validate_template,
25+
)
26+
from .transpiler import (
27+
FRAMEWORK,
28+
HEADER,
29+
build_blob,
30+
build_bodies,
31+
render_node,
32+
rewrite_placeholders,
33+
transpile,
34+
)
35+
36+
__all__ = [
37+
"__version__",
38+
"Node",
39+
"Template",
40+
"load_template",
41+
"parse_template",
42+
"validate_template",
43+
"node_ids",
44+
"transpile",
45+
"build_blob",
46+
"build_bodies",
47+
"rewrite_placeholders",
48+
"render_node",
49+
"FRAMEWORK",
50+
"HEADER",
51+
]

0 commit comments

Comments
 (0)