runar-compiler 0.3.1__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.
- runar_compiler-0.3.1/PKG-INFO +7 -0
- runar_compiler-0.3.1/README.md +168 -0
- runar_compiler-0.3.1/pyproject.toml +16 -0
- runar_compiler-0.3.1/runar_compiler/__init__.py +3 -0
- runar_compiler-0.3.1/runar_compiler/__main__.py +198 -0
- runar_compiler-0.3.1/runar_compiler/codegen/__init__.py +0 -0
- runar_compiler-0.3.1/runar_compiler/codegen/blake3.py +644 -0
- runar_compiler-0.3.1/runar_compiler/codegen/ec.py +920 -0
- runar_compiler-0.3.1/runar_compiler/codegen/emit.py +442 -0
- runar_compiler-0.3.1/runar_compiler/codegen/optimizer.py +243 -0
- runar_compiler-0.3.1/runar_compiler/codegen/sha256.py +562 -0
- runar_compiler-0.3.1/runar_compiler/codegen/slh_dsa.py +1181 -0
- runar_compiler-0.3.1/runar_compiler/codegen/stack.py +3184 -0
- runar_compiler-0.3.1/runar_compiler/compiler.py +530 -0
- runar_compiler-0.3.1/runar_compiler/frontend/__init__.py +0 -0
- runar_compiler-0.3.1/runar_compiler/frontend/anf_lower.py +1068 -0
- runar_compiler-0.3.1/runar_compiler/frontend/anf_optimize.py +388 -0
- runar_compiler-0.3.1/runar_compiler/frontend/ast_nodes.py +322 -0
- runar_compiler-0.3.1/runar_compiler/frontend/constant_fold.py +488 -0
- runar_compiler-0.3.1/runar_compiler/frontend/parser_dispatch.py +41 -0
- runar_compiler-0.3.1/runar_compiler/frontend/parser_go.py +1648 -0
- runar_compiler-0.3.1/runar_compiler/frontend/parser_move.py +1183 -0
- runar_compiler-0.3.1/runar_compiler/frontend/parser_python.py +1402 -0
- runar_compiler-0.3.1/runar_compiler/frontend/parser_rust.py +1229 -0
- runar_compiler-0.3.1/runar_compiler/frontend/parser_sol.py +1156 -0
- runar_compiler-0.3.1/runar_compiler/frontend/parser_ts.py +1343 -0
- runar_compiler-0.3.1/runar_compiler/frontend/typecheck.py +872 -0
- runar_compiler-0.3.1/runar_compiler/frontend/validator.py +544 -0
- runar_compiler-0.3.1/runar_compiler/ir/__init__.py +0 -0
- runar_compiler-0.3.1/runar_compiler/ir/loader.py +186 -0
- runar_compiler-0.3.1/runar_compiler/ir/types.py +315 -0
- runar_compiler-0.3.1/runar_compiler.egg-info/PKG-INFO +7 -0
- runar_compiler-0.3.1/runar_compiler.egg-info/SOURCES.txt +46 -0
- runar_compiler-0.3.1/runar_compiler.egg-info/dependency_links.txt +1 -0
- runar_compiler-0.3.1/runar_compiler.egg-info/requires.txt +3 -0
- runar_compiler-0.3.1/runar_compiler.egg-info/top_level.txt +1 -0
- runar_compiler-0.3.1/setup.cfg +4 -0
- runar_compiler-0.3.1/tests/test_anf_optimize.py +547 -0
- runar_compiler-0.3.1/tests/test_compiler.py +410 -0
- runar_compiler-0.3.1/tests/test_constant_fold.py +658 -0
- runar_compiler-0.3.1/tests/test_emit.py +779 -0
- runar_compiler-0.3.1/tests/test_frontend.py +2900 -0
- runar_compiler-0.3.1/tests/test_ir_loader.py +578 -0
- runar_compiler-0.3.1/tests/test_multiformat.py +210 -0
- runar_compiler-0.3.1/tests/test_optimizer.py +667 -0
- runar_compiler-0.3.1/tests/test_parsers.py +671 -0
- runar_compiler-0.3.1/tests/test_source_compile.py +130 -0
- runar_compiler-0.3.1/tests/test_stack.py +971 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Rúnar Python Compiler
|
|
2
|
+
|
|
3
|
+
**Alternative Rúnar compiler implemented in Python.**
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Status
|
|
8
|
+
|
|
9
|
+
| Phase | Description | Status |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| **Phase 1** | IR consumer: accepts canonical ANF IR JSON, performs stack lowering and emission (Passes 5-6). | Implemented |
|
|
12
|
+
| **Phase 2** | Full frontend: parses source files directly (Passes 1-4), produces canonical ANF IR. | Implemented |
|
|
13
|
+
|
|
14
|
+
Phase 1 validates that the Python implementation can produce identical Bitcoin Script from the same ANF IR as the reference compiler. Phase 2 adds an independent frontend that must produce byte-identical ANF IR.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Architecture
|
|
19
|
+
|
|
20
|
+
### Phase 1: IR Consumer
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
ANF IR (JSON) --> [Stack Lower] --> [Peephole] --> [Emit] --> Bitcoin Script
|
|
24
|
+
Python pass 5 Optimize Python pass 6
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The Python compiler reads canonical ANF IR JSON and performs stack scheduling and opcode emission.
|
|
28
|
+
|
|
29
|
+
### Phase 2: Full Frontend
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
.runar.* --> [Parse] --> [Validate] --> [Typecheck] --> [ANF Lower]
|
|
33
|
+
hand-written Python pass 2 Python pass 3 Python pass 4
|
|
34
|
+
parsers
|
|
35
|
+
|
|
|
36
|
+
v
|
|
37
|
+
ANF IR (JSON)
|
|
38
|
+
|
|
|
39
|
+
v
|
|
40
|
+
[Stack Lower] --> [Peephole] --> [Emit] --> Bitcoin Script
|
|
41
|
+
Python pass 5 Optimize Python pass 6
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The Python compiler supports **all six input formats** via hand-written recursive descent parsers — the most of any compiler:
|
|
45
|
+
|
|
46
|
+
| Extension | Parser Module |
|
|
47
|
+
|-----------|---------------|
|
|
48
|
+
| `.runar.ts` | `frontend/parser_ts.py` |
|
|
49
|
+
| `.runar.sol` | `frontend/parser_sol.py` |
|
|
50
|
+
| `.runar.move` | `frontend/parser_move.py` |
|
|
51
|
+
| `.runar.go` | `frontend/parser_go.py` |
|
|
52
|
+
| `.runar.rs` | `frontend/parser_rust.py` |
|
|
53
|
+
| `.runar.py` | `frontend/parser_python.py` |
|
|
54
|
+
|
|
55
|
+
All parsers produce the same Rúnar AST (`ContractNode`), and from that point the pipeline is identical.
|
|
56
|
+
|
|
57
|
+
### Dedicated Codegen Modules
|
|
58
|
+
|
|
59
|
+
- `codegen/ec.py` — EC point operations (`ecAdd`, `ecMul`, `ecMulGen`, `ecNegate`, `ecOnCurve`, etc.)
|
|
60
|
+
- `codegen/slh_dsa.py` — SLH-DSA (SPHINCS+) signature verification
|
|
61
|
+
- `codegen/optimizer.py` — Peephole optimizer (runs on Stack IR between stack lowering and emit)
|
|
62
|
+
|
|
63
|
+
### ANF EC Optimizer (Pass 4.5)
|
|
64
|
+
|
|
65
|
+
The `frontend/anf_optimize.py` module implements 12 algebraic EC simplification rules that run between ANF lowering and stack lowering. This pass is always enabled and eliminates redundant EC operations (e.g., `ecAdd(P, ecNegate(P))` → identity, `ecMul(G, k)` → `ecMulGen(k)`).
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Building
|
|
70
|
+
|
|
71
|
+
No build step required — the compiler is pure Python.
|
|
72
|
+
|
|
73
|
+
### Prerequisites
|
|
74
|
+
|
|
75
|
+
- Python 3.10+
|
|
76
|
+
- No external dependencies
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Running
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# Full compilation from source (outputs artifact JSON)
|
|
84
|
+
python -m runar_compiler --source MyContract.runar.ts
|
|
85
|
+
|
|
86
|
+
# Output only hex
|
|
87
|
+
python -m runar_compiler --source MyContract.runar.ts --hex
|
|
88
|
+
|
|
89
|
+
# Output only ASM
|
|
90
|
+
python -m runar_compiler --source MyContract.runar.ts --asm
|
|
91
|
+
|
|
92
|
+
# Dump ANF IR for conformance checking
|
|
93
|
+
python -m runar_compiler --source MyContract.runar.ts --emit-ir
|
|
94
|
+
|
|
95
|
+
# Write output to a file
|
|
96
|
+
python -m runar_compiler --source MyContract.runar.ts --output artifacts/MyContract.json
|
|
97
|
+
|
|
98
|
+
# Compile from ANF IR JSON
|
|
99
|
+
python -m runar_compiler --ir input-anf.json
|
|
100
|
+
python -m runar_compiler --ir input-anf.json --hex
|
|
101
|
+
python -m runar_compiler --ir input-anf.json --output artifact.json
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Conformance Testing
|
|
107
|
+
|
|
108
|
+
The Python compiler must pass the same conformance suite as the TypeScript reference compiler.
|
|
109
|
+
|
|
110
|
+
For each test case in `conformance/tests/`:
|
|
111
|
+
|
|
112
|
+
1. Read source files as input.
|
|
113
|
+
2. Run the full pipeline (Passes 1-6).
|
|
114
|
+
3. Compare script hex output with `expected-script.hex` (string equality).
|
|
115
|
+
4. If `expected-ir.json` exists, also compile from IR and verify the IR-compiled script matches the source-compiled script.
|
|
116
|
+
|
|
117
|
+
Conformance tests include multi-format variants (`.runar.sol`, `.runar.move`, `.runar.go`, `.runar.rs`, `.runar.py`) that are all tested through the same pipeline.
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
# Run conformance from repo root
|
|
121
|
+
pnpm run conformance:python
|
|
122
|
+
|
|
123
|
+
# Or directly
|
|
124
|
+
cd conformance
|
|
125
|
+
python3 -m pytest test_conformance.py -v
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Testing
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
cd compilers/python
|
|
134
|
+
python3 -m pytest
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Project Structure
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
runar_compiler/
|
|
143
|
+
__init__.py
|
|
144
|
+
__main__.py # CLI entry point
|
|
145
|
+
compiler.py # Pipeline orchestrator (parse → validate → typecheck → ANF → stack → emit)
|
|
146
|
+
frontend/
|
|
147
|
+
ast_nodes.py # Rúnar AST node types (ContractNode, PropertyNode, MethodNode, etc.)
|
|
148
|
+
parser_dispatch.py # File extension → parser dispatch
|
|
149
|
+
parser_ts.py # .runar.ts parser
|
|
150
|
+
parser_sol.py # .runar.sol parser
|
|
151
|
+
parser_move.py # .runar.move parser
|
|
152
|
+
parser_go.py # .runar.go parser
|
|
153
|
+
parser_rust.py # .runar.rs parser
|
|
154
|
+
parser_python.py # .runar.py parser
|
|
155
|
+
validator.py # Pass 2: Language subset validation
|
|
156
|
+
typecheck.py # Pass 3: Type checking
|
|
157
|
+
anf_lower.py # Pass 4: AST → ANF IR
|
|
158
|
+
anf_optimize.py # Pass 4.5: ANF EC optimizer (12 algebraic rules)
|
|
159
|
+
ir/
|
|
160
|
+
types.py # ANF IR type definitions
|
|
161
|
+
loader.py # ANF IR JSON loader
|
|
162
|
+
codegen/
|
|
163
|
+
stack.py # Pass 5: ANF → Stack IR
|
|
164
|
+
optimizer.py # Peephole optimizer
|
|
165
|
+
emit.py # Pass 6: Stack IR → Bitcoin Script
|
|
166
|
+
ec.py # EC point operation codegen
|
|
167
|
+
slh_dsa.py # SLH-DSA signature verification codegen
|
|
168
|
+
```
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "runar-compiler"
|
|
3
|
+
version = "0.3.1"
|
|
4
|
+
description = "Runar smart contract compiler - Python implementation"
|
|
5
|
+
requires-python = ">=3.10"
|
|
6
|
+
dependencies = []
|
|
7
|
+
|
|
8
|
+
[project.optional-dependencies]
|
|
9
|
+
dev = ["pytest>=7.0"]
|
|
10
|
+
|
|
11
|
+
[build-system]
|
|
12
|
+
requires = ["setuptools>=64"]
|
|
13
|
+
build-backend = "setuptools.build_meta"
|
|
14
|
+
|
|
15
|
+
[tool.pytest.ini_options]
|
|
16
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"""CLI entry point for the Runar Python compiler.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
python -m runar_compiler --source Contract.runar.py --output artifact.json
|
|
5
|
+
python -m runar_compiler --ir program.json --output artifact.json
|
|
6
|
+
python -m runar_compiler --source Contract.runar.py --hex
|
|
7
|
+
python -m runar_compiler --source Contract.runar.py --asm
|
|
8
|
+
python -m runar_compiler --source Contract.runar.py --emit-ir
|
|
9
|
+
|
|
10
|
+
Direct port of ``compilers/go/main.go``.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import argparse
|
|
16
|
+
import json
|
|
17
|
+
import sys
|
|
18
|
+
|
|
19
|
+
from runar_compiler.compiler import (
|
|
20
|
+
CompilationError,
|
|
21
|
+
artifact_to_json,
|
|
22
|
+
compile_from_ir,
|
|
23
|
+
compile_from_source,
|
|
24
|
+
compile_source_to_ir,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def main() -> None:
|
|
29
|
+
parser = argparse.ArgumentParser(
|
|
30
|
+
prog="runar-compiler-python",
|
|
31
|
+
description="Runar smart contract compiler (Python implementation).",
|
|
32
|
+
)
|
|
33
|
+
parser.add_argument(
|
|
34
|
+
"--ir",
|
|
35
|
+
metavar="PATH",
|
|
36
|
+
help="Path to ANF IR JSON file",
|
|
37
|
+
)
|
|
38
|
+
parser.add_argument(
|
|
39
|
+
"--source",
|
|
40
|
+
metavar="PATH",
|
|
41
|
+
help="Path to .runar.* source file",
|
|
42
|
+
)
|
|
43
|
+
parser.add_argument(
|
|
44
|
+
"--output",
|
|
45
|
+
metavar="PATH",
|
|
46
|
+
help="Output artifact path (default: stdout)",
|
|
47
|
+
)
|
|
48
|
+
parser.add_argument(
|
|
49
|
+
"--hex",
|
|
50
|
+
action="store_true",
|
|
51
|
+
help="Output only the script hex (no artifact JSON)",
|
|
52
|
+
)
|
|
53
|
+
parser.add_argument(
|
|
54
|
+
"--asm",
|
|
55
|
+
action="store_true",
|
|
56
|
+
help="Output only the script ASM (no artifact JSON)",
|
|
57
|
+
)
|
|
58
|
+
parser.add_argument(
|
|
59
|
+
"--emit-ir",
|
|
60
|
+
action="store_true",
|
|
61
|
+
help="Output only the ANF IR JSON (requires --source)",
|
|
62
|
+
)
|
|
63
|
+
parser.add_argument(
|
|
64
|
+
"--disable-constant-folding",
|
|
65
|
+
action="store_true",
|
|
66
|
+
help="Disable the ANF constant folding pass",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
args = parser.parse_args()
|
|
70
|
+
|
|
71
|
+
if not args.ir and not args.source:
|
|
72
|
+
print(
|
|
73
|
+
"Usage: runar-compiler-python [--ir <path> | --source <path>] "
|
|
74
|
+
"[--output <path>] [--hex] [--asm] [--emit-ir]",
|
|
75
|
+
file=sys.stderr,
|
|
76
|
+
)
|
|
77
|
+
print("", file=sys.stderr)
|
|
78
|
+
print(
|
|
79
|
+
"Phase 1: Compile from ANF IR JSON to Bitcoin Script (--ir).",
|
|
80
|
+
file=sys.stderr,
|
|
81
|
+
)
|
|
82
|
+
print(
|
|
83
|
+
"Phase 2: Compile from source to Bitcoin Script (--source).",
|
|
84
|
+
file=sys.stderr,
|
|
85
|
+
)
|
|
86
|
+
sys.exit(1)
|
|
87
|
+
|
|
88
|
+
# Handle --emit-ir: dump ANF IR JSON and exit
|
|
89
|
+
if args.emit_ir:
|
|
90
|
+
if not args.source:
|
|
91
|
+
print("--emit-ir requires --source", file=sys.stderr)
|
|
92
|
+
sys.exit(1)
|
|
93
|
+
try:
|
|
94
|
+
program = compile_source_to_ir(
|
|
95
|
+
args.source,
|
|
96
|
+
disable_constant_folding=args.disable_constant_folding,
|
|
97
|
+
)
|
|
98
|
+
except CompilationError as e:
|
|
99
|
+
print(f"Compilation error: {e}", file=sys.stderr)
|
|
100
|
+
sys.exit(1)
|
|
101
|
+
# Serialize the ANFProgram to camelCase JSON (matching Go/TS output)
|
|
102
|
+
ir_json = json.dumps(_anf_to_camel_dict(program), indent=2, default=str)
|
|
103
|
+
print(ir_json)
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
if args.source:
|
|
108
|
+
artifact = compile_from_source(
|
|
109
|
+
args.source,
|
|
110
|
+
disable_constant_folding=args.disable_constant_folding,
|
|
111
|
+
)
|
|
112
|
+
else:
|
|
113
|
+
artifact = compile_from_ir(
|
|
114
|
+
args.ir,
|
|
115
|
+
disable_constant_folding=args.disable_constant_folding,
|
|
116
|
+
)
|
|
117
|
+
except CompilationError as e:
|
|
118
|
+
print(f"Compilation error: {e}", file=sys.stderr)
|
|
119
|
+
sys.exit(1)
|
|
120
|
+
except Exception as e:
|
|
121
|
+
print(f"Compilation error: {e}", file=sys.stderr)
|
|
122
|
+
sys.exit(1)
|
|
123
|
+
|
|
124
|
+
# Determine output
|
|
125
|
+
if args.hex:
|
|
126
|
+
output = artifact.script
|
|
127
|
+
elif args.asm:
|
|
128
|
+
output = artifact.asm
|
|
129
|
+
else:
|
|
130
|
+
output = artifact_to_json(artifact)
|
|
131
|
+
|
|
132
|
+
# Write output
|
|
133
|
+
if args.output:
|
|
134
|
+
with open(args.output, "w") as f:
|
|
135
|
+
f.write(output)
|
|
136
|
+
print(f"Output written to {args.output}", file=sys.stderr)
|
|
137
|
+
else:
|
|
138
|
+
print(output)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
_SNAKE_TO_CAMEL = {
|
|
142
|
+
"contract_name": "contractName",
|
|
143
|
+
"is_public": "isPublic",
|
|
144
|
+
"iter_var": "iterVar",
|
|
145
|
+
"state_values": "stateValues",
|
|
146
|
+
"initial_value": "initialValue",
|
|
147
|
+
"else_": "else",
|
|
148
|
+
# These stay as snake_case to match Go/TS IR format
|
|
149
|
+
"result_type": "result_type",
|
|
150
|
+
# Both raw_value and value_ref map to "value" in Go JSON (they never coexist)
|
|
151
|
+
"value_ref": "value",
|
|
152
|
+
"raw_value": "value",
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
# Fields that should be excluded from IR output (internal decoded fields)
|
|
156
|
+
_IR_EXCLUDED_FIELDS = frozenset({
|
|
157
|
+
"const_string", "const_big_int", "const_bool", "const_int",
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _snake_key(k: str) -> str:
|
|
162
|
+
return _SNAKE_TO_CAMEL.get(k, k)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _anf_to_camel_dict(obj: object) -> object:
|
|
166
|
+
"""Convert an ANF dataclass tree to a dict matching Go/TS IR JSON format."""
|
|
167
|
+
import json as _json
|
|
168
|
+
from dataclasses import fields, is_dataclass
|
|
169
|
+
if is_dataclass(obj) and not isinstance(obj, type):
|
|
170
|
+
d: dict = {}
|
|
171
|
+
has_raw_value = False
|
|
172
|
+
for f in fields(obj):
|
|
173
|
+
if f.name in _IR_EXCLUDED_FIELDS:
|
|
174
|
+
continue
|
|
175
|
+
v = getattr(obj, f.name)
|
|
176
|
+
if v is None:
|
|
177
|
+
continue
|
|
178
|
+
# raw_value is the canonical Go JSON "value" field — parse and emit its content
|
|
179
|
+
if f.name == "raw_value":
|
|
180
|
+
try:
|
|
181
|
+
d["value"] = _json.loads(v)
|
|
182
|
+
except (ValueError, TypeError):
|
|
183
|
+
d["value"] = v
|
|
184
|
+
has_raw_value = True
|
|
185
|
+
continue
|
|
186
|
+
# Skip value_ref if raw_value was already emitted as "value"
|
|
187
|
+
if f.name == "value_ref" and has_raw_value:
|
|
188
|
+
continue
|
|
189
|
+
key = _snake_key(f.name)
|
|
190
|
+
d[key] = _anf_to_camel_dict(v)
|
|
191
|
+
return d
|
|
192
|
+
if isinstance(obj, list):
|
|
193
|
+
return [_anf_to_camel_dict(item) for item in obj]
|
|
194
|
+
return obj
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
if __name__ == "__main__":
|
|
198
|
+
main()
|
|
File without changes
|