|
1 | | -# PyReflect |
| 1 | +# PyReflect |
| 2 | + |
| 3 | + |
| 4 | + |
| 5 | +[](/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 | +``` |
0 commit comments