cmra 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.
- cmra-0.1.0/PKG-INFO +173 -0
- cmra-0.1.0/README.md +156 -0
- cmra-0.1.0/pyproject.toml +31 -0
- cmra-0.1.0/setup.cfg +4 -0
- cmra-0.1.0/setup.py +2 -0
- cmra-0.1.0/src/cmra/__init__.py +4 -0
- cmra-0.1.0/src/cmra/cli.py +83 -0
- cmra-0.1.0/src/cmra/fire.py +198 -0
- cmra-0.1.0/src/cmra/repl.py +59 -0
- cmra-0.1.0/src/cmra/runner.py +40 -0
- cmra-0.1.0/src/cmra/shadow.py +183 -0
- cmra-0.1.0/src/cmra/shadow_cli.py +68 -0
- cmra-0.1.0/src/cmra.egg-info/PKG-INFO +173 -0
- cmra-0.1.0/src/cmra.egg-info/SOURCES.txt +15 -0
- cmra-0.1.0/src/cmra.egg-info/dependency_links.txt +1 -0
- cmra-0.1.0/src/cmra.egg-info/entry_points.txt +3 -0
- cmra-0.1.0/src/cmra.egg-info/top_level.txt +1 -0
cmra-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cmra
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CMRA โ Creative Minimal Recursive Automaton language runtime
|
|
5
|
+
Author: TeapotChimera418
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://cmra-esolang.vercel.app
|
|
8
|
+
Project-URL: Repository, https://github.com/TeapotChimera418/cmra
|
|
9
|
+
Project-URL: Changelog, https://github.com/TeapotChimera418/cmra/blob/main/CHANGELOG.md
|
|
10
|
+
Keywords: esolang,esoteric,programming,language,interpreter
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Topic :: Software Development :: Interpreters
|
|
15
|
+
Requires-Python: >=3.8
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# ๐ CMRA Language (Chimera)
|
|
19
|
+
|
|
20
|
+
> **"Where code breathes fire and flows like flame..."**
|
|
21
|
+
|
|
22
|
+
**CMRA** (Chimera) is an esoteric programming language exploring reversible execution and direction control through a dragon-inspired syntax. Programs can soar *up* or dive *down* through code at runtime.
|
|
23
|
+
|
|
24
|
+
๐ฅ **GitHub:** [TeapotChimera418/cmra](https://github.com/TeapotChimera418/cmra)
|
|
25
|
+
๐ **Playground:** [cmra-esolang.vercel.app/playground.html](https://cmra-esolang.vercel.app/playground.html)
|
|
26
|
+
๐ **Docs:** [cmra-esolang.vercel.app](https://cmra-esolang.vercel.app/index.html)
|
|
27
|
+
๐ **User Guide:** [GUIDE.md](GUIDE.md)
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## ๐ Origin
|
|
32
|
+
|
|
33
|
+
CMRA began as a workshop project I attended by
|
|
34
|
+
[Tushar Sadhwani](https://github.com/tusharsadhwani) during a college multifest. The
|
|
35
|
+
prototype we built is in `prototype.py`. For a nonโaliased baseline, see Tushar's
|
|
36
|
+
[esolangs](https://github.com/tusharsadhwani/esolangs) repository. You can also read his
|
|
37
|
+
blog at [tush.ar](https://tush.ar).
|
|
38
|
+
|
|
39
|
+
This is a tiny side project made for fun and learning. If you build something cool or want
|
|
40
|
+
to improve the interpreter, feel free to reach out on
|
|
41
|
+
[GitHub (@TeapotChimera418)](https://github.com/TeapotChimera418) or Discord [`gamingchimera`](https://discord.com/users/736465046317563915).
|
|
42
|
+
|
|
43
|
+
## โจ Try it Online
|
|
44
|
+
|
|
45
|
+
**๐ฅ [Launch the Interactive Playground](https://cmra-esolang.vercel.app/playground.html)**
|
|
46
|
+
|
|
47
|
+
Write and run CMRA code directly in your browser โ no installation needed. Powered by Pyodide.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## โก Installation & Quick Start
|
|
52
|
+
|
|
53
|
+
### pip install (recommended)
|
|
54
|
+
|
|
55
|
+
```powershell
|
|
56
|
+
# Install from PyPI:
|
|
57
|
+
pip install cmra
|
|
58
|
+
|
|
59
|
+
# Or install from source (development):
|
|
60
|
+
pip install -e .
|
|
61
|
+
|
|
62
|
+
# Now use `cmra` and `cmrash` from anywhere inside the venv:
|
|
63
|
+
cmra "test cases\test.cmra"
|
|
64
|
+
cmrash "test cases\test.cmrash"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### No-install PowerShell wrapper
|
|
68
|
+
|
|
69
|
+
```powershell
|
|
70
|
+
.\cmra.ps1 "test cases\test.cmra"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Run with Python directly
|
|
74
|
+
|
|
75
|
+
```powershell
|
|
76
|
+
python -m cmra.cli "test cases\test.cmra"
|
|
77
|
+
python cmra.py "test cases cmra\test.cmra" # legacy single-file
|
|
78
|
+
python cmra_simplified.py "test cases cmra_simplified\test.cmrasim"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## ๐ฑ The Twin Dragons
|
|
84
|
+
|
|
85
|
+
| Dragon | Entry point | Keywords |
|
|
86
|
+
|--------|-------------|----------|
|
|
87
|
+
| ๐ฒ **Fire Dragon** | `cmra.py` / `src/cmra/fire.py` | `bind` `roar` `sniff` `dive` `soar` `murmur` |
|
|
88
|
+
| ๐ **Shadow Dragon** | `cmra_simplified.py` / `src/cmra/shadow.py` | `=` `print` `check` `reverse` `;` |
|
|
89
|
+
| ๐ **Ancient Wyrm** | `prototype.py` | Compact baseline, inline `sniff` only |
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## ๐ฒ Language at a Glance
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
murmur Fire Dragon โ count 0 to 5
|
|
97
|
+
i bind 0
|
|
98
|
+
flag bind 1
|
|
99
|
+
sniff i <= 5 : sniff flag == 0 : dive
|
|
100
|
+
flag bind 0
|
|
101
|
+
sniff flag == 0 : roar i
|
|
102
|
+
sniff flag == 0 : i bind i + 1
|
|
103
|
+
flag bind 1
|
|
104
|
+
sniff i <= 5 : sniff flag == 1 : soar
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
- `bind` โ assign variable
|
|
108
|
+
- `roar` โ print
|
|
109
|
+
- `sniff โฆ : action` โ conditional (inline or block)
|
|
110
|
+
- `dive` / `soar` โ set execution direction forward / reverse
|
|
111
|
+
- No loop keyword โ direction reversal + flag guards make loops
|
|
112
|
+
|
|
113
|
+
**โ See [GUIDE.md](GUIDE.md) for the full walkthrough.**
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## โก Execution Model
|
|
118
|
+
|
|
119
|
+
A global `DIRECTION` (`1` or `-1`) steps through lines. `soar` reverses it; `dive` restores forward. When direction goes past the first or last line, the program ends. Block `sniff/check` enters at the first (forward) or last (reverse) line of the block.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## ๐ฎ Test Cases & Projects
|
|
124
|
+
|
|
125
|
+
```powershell
|
|
126
|
+
# Test cases (pip-installed cmra)
|
|
127
|
+
cmra "test cases\test.cmra"
|
|
128
|
+
cmra "test cases\test_strings.cmra"
|
|
129
|
+
cmra "test cases\test_arith.cmra"
|
|
130
|
+
|
|
131
|
+
# Projects
|
|
132
|
+
cmra projects\calculator.cmra
|
|
133
|
+
cmra projects\countdown.cmra
|
|
134
|
+
cmra projects\fizzbuzz.cmra
|
|
135
|
+
cmra projects\story_adventure.cmra
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**Test cases** live in `test cases/` (`.cmra`) and `test cases cmra_simplified/` (`.cmrasim`).
|
|
139
|
+
**Projects** live in `projects/`: calculator, countdown, fizzbuzz, story_adventure.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## ๐ฆ File Inventory
|
|
144
|
+
|
|
145
|
+
| Path | Purpose |
|
|
146
|
+
|------|---------|
|
|
147
|
+
| `src/cmra/` | Installable Python package |
|
|
148
|
+
| `src/cmra/cli.py` | Entry point for the `cmra` command |
|
|
149
|
+
| `src/cmra/fire.py` | Fire Dragon interpreter |
|
|
150
|
+
| `src/cmra/shadow.py` | Shadow Dragon interpreter |
|
|
151
|
+
| `src/cmra/runner.py` | Shared dispatch logic |
|
|
152
|
+
| `cmra.py` | Legacy Fire Dragon single-file |
|
|
153
|
+
| `cmra_simplified.py` | Legacy Shadow Dragon single-file |
|
|
154
|
+
| `prototype.py` | Original workshop prototype |
|
|
155
|
+
| `cmra.ps1` / `cmra.bat` | No-install launchers |
|
|
156
|
+
| `pyproject.toml` | Package metadata (pip install) |
|
|
157
|
+
| `index.html` + `styles.css` | Static docs site |
|
|
158
|
+
| `playground.html` | Browser REPL (Pyodide) |
|
|
159
|
+
| `GUIDE.md` | Full user guide |
|
|
160
|
+
| `keybind.txt` | Fire โ Shadow keyword cheatsheet |
|
|
161
|
+
| `projects/` | Showcase programs |
|
|
162
|
+
| `test cases/` | Regression tests (`.cmra`) |
|
|
163
|
+
| `test cases cmra_simplified/` | Regression tests (`.cmrasim`) |
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## ๐ License
|
|
168
|
+
|
|
169
|
+
CMRA is a learning project. Use freely, learn deeply, code fiercely! ๐ฅ
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
**May your code burn bright and your loops reverse true!** ๐๐ฅ
|
cmra-0.1.0/README.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# ๐ CMRA Language (Chimera)
|
|
2
|
+
|
|
3
|
+
> **"Where code breathes fire and flows like flame..."**
|
|
4
|
+
|
|
5
|
+
**CMRA** (Chimera) is an esoteric programming language exploring reversible execution and direction control through a dragon-inspired syntax. Programs can soar *up* or dive *down* through code at runtime.
|
|
6
|
+
|
|
7
|
+
๐ฅ **GitHub:** [TeapotChimera418/cmra](https://github.com/TeapotChimera418/cmra)
|
|
8
|
+
๐ **Playground:** [cmra-esolang.vercel.app/playground.html](https://cmra-esolang.vercel.app/playground.html)
|
|
9
|
+
๐ **Docs:** [cmra-esolang.vercel.app](https://cmra-esolang.vercel.app/index.html)
|
|
10
|
+
๐ **User Guide:** [GUIDE.md](GUIDE.md)
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## ๐ Origin
|
|
15
|
+
|
|
16
|
+
CMRA began as a workshop project I attended by
|
|
17
|
+
[Tushar Sadhwani](https://github.com/tusharsadhwani) during a college multifest. The
|
|
18
|
+
prototype we built is in `prototype.py`. For a nonโaliased baseline, see Tushar's
|
|
19
|
+
[esolangs](https://github.com/tusharsadhwani/esolangs) repository. You can also read his
|
|
20
|
+
blog at [tush.ar](https://tush.ar).
|
|
21
|
+
|
|
22
|
+
This is a tiny side project made for fun and learning. If you build something cool or want
|
|
23
|
+
to improve the interpreter, feel free to reach out on
|
|
24
|
+
[GitHub (@TeapotChimera418)](https://github.com/TeapotChimera418) or Discord [`gamingchimera`](https://discord.com/users/736465046317563915).
|
|
25
|
+
|
|
26
|
+
## โจ Try it Online
|
|
27
|
+
|
|
28
|
+
**๐ฅ [Launch the Interactive Playground](https://cmra-esolang.vercel.app/playground.html)**
|
|
29
|
+
|
|
30
|
+
Write and run CMRA code directly in your browser โ no installation needed. Powered by Pyodide.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## โก Installation & Quick Start
|
|
35
|
+
|
|
36
|
+
### pip install (recommended)
|
|
37
|
+
|
|
38
|
+
```powershell
|
|
39
|
+
# Install from PyPI:
|
|
40
|
+
pip install cmra
|
|
41
|
+
|
|
42
|
+
# Or install from source (development):
|
|
43
|
+
pip install -e .
|
|
44
|
+
|
|
45
|
+
# Now use `cmra` and `cmrash` from anywhere inside the venv:
|
|
46
|
+
cmra "test cases\test.cmra"
|
|
47
|
+
cmrash "test cases\test.cmrash"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### No-install PowerShell wrapper
|
|
51
|
+
|
|
52
|
+
```powershell
|
|
53
|
+
.\cmra.ps1 "test cases\test.cmra"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Run with Python directly
|
|
57
|
+
|
|
58
|
+
```powershell
|
|
59
|
+
python -m cmra.cli "test cases\test.cmra"
|
|
60
|
+
python cmra.py "test cases cmra\test.cmra" # legacy single-file
|
|
61
|
+
python cmra_simplified.py "test cases cmra_simplified\test.cmrasim"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## ๐ฑ The Twin Dragons
|
|
67
|
+
|
|
68
|
+
| Dragon | Entry point | Keywords |
|
|
69
|
+
|--------|-------------|----------|
|
|
70
|
+
| ๐ฒ **Fire Dragon** | `cmra.py` / `src/cmra/fire.py` | `bind` `roar` `sniff` `dive` `soar` `murmur` |
|
|
71
|
+
| ๐ **Shadow Dragon** | `cmra_simplified.py` / `src/cmra/shadow.py` | `=` `print` `check` `reverse` `;` |
|
|
72
|
+
| ๐ **Ancient Wyrm** | `prototype.py` | Compact baseline, inline `sniff` only |
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## ๐ฒ Language at a Glance
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
murmur Fire Dragon โ count 0 to 5
|
|
80
|
+
i bind 0
|
|
81
|
+
flag bind 1
|
|
82
|
+
sniff i <= 5 : sniff flag == 0 : dive
|
|
83
|
+
flag bind 0
|
|
84
|
+
sniff flag == 0 : roar i
|
|
85
|
+
sniff flag == 0 : i bind i + 1
|
|
86
|
+
flag bind 1
|
|
87
|
+
sniff i <= 5 : sniff flag == 1 : soar
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
- `bind` โ assign variable
|
|
91
|
+
- `roar` โ print
|
|
92
|
+
- `sniff โฆ : action` โ conditional (inline or block)
|
|
93
|
+
- `dive` / `soar` โ set execution direction forward / reverse
|
|
94
|
+
- No loop keyword โ direction reversal + flag guards make loops
|
|
95
|
+
|
|
96
|
+
**โ See [GUIDE.md](GUIDE.md) for the full walkthrough.**
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## โก Execution Model
|
|
101
|
+
|
|
102
|
+
A global `DIRECTION` (`1` or `-1`) steps through lines. `soar` reverses it; `dive` restores forward. When direction goes past the first or last line, the program ends. Block `sniff/check` enters at the first (forward) or last (reverse) line of the block.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## ๐ฎ Test Cases & Projects
|
|
107
|
+
|
|
108
|
+
```powershell
|
|
109
|
+
# Test cases (pip-installed cmra)
|
|
110
|
+
cmra "test cases\test.cmra"
|
|
111
|
+
cmra "test cases\test_strings.cmra"
|
|
112
|
+
cmra "test cases\test_arith.cmra"
|
|
113
|
+
|
|
114
|
+
# Projects
|
|
115
|
+
cmra projects\calculator.cmra
|
|
116
|
+
cmra projects\countdown.cmra
|
|
117
|
+
cmra projects\fizzbuzz.cmra
|
|
118
|
+
cmra projects\story_adventure.cmra
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Test cases** live in `test cases/` (`.cmra`) and `test cases cmra_simplified/` (`.cmrasim`).
|
|
122
|
+
**Projects** live in `projects/`: calculator, countdown, fizzbuzz, story_adventure.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## ๐ฆ File Inventory
|
|
127
|
+
|
|
128
|
+
| Path | Purpose |
|
|
129
|
+
|------|---------|
|
|
130
|
+
| `src/cmra/` | Installable Python package |
|
|
131
|
+
| `src/cmra/cli.py` | Entry point for the `cmra` command |
|
|
132
|
+
| `src/cmra/fire.py` | Fire Dragon interpreter |
|
|
133
|
+
| `src/cmra/shadow.py` | Shadow Dragon interpreter |
|
|
134
|
+
| `src/cmra/runner.py` | Shared dispatch logic |
|
|
135
|
+
| `cmra.py` | Legacy Fire Dragon single-file |
|
|
136
|
+
| `cmra_simplified.py` | Legacy Shadow Dragon single-file |
|
|
137
|
+
| `prototype.py` | Original workshop prototype |
|
|
138
|
+
| `cmra.ps1` / `cmra.bat` | No-install launchers |
|
|
139
|
+
| `pyproject.toml` | Package metadata (pip install) |
|
|
140
|
+
| `index.html` + `styles.css` | Static docs site |
|
|
141
|
+
| `playground.html` | Browser REPL (Pyodide) |
|
|
142
|
+
| `GUIDE.md` | Full user guide |
|
|
143
|
+
| `keybind.txt` | Fire โ Shadow keyword cheatsheet |
|
|
144
|
+
| `projects/` | Showcase programs |
|
|
145
|
+
| `test cases/` | Regression tests (`.cmra`) |
|
|
146
|
+
| `test cases cmra_simplified/` | Regression tests (`.cmrasim`) |
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## ๐ License
|
|
151
|
+
|
|
152
|
+
CMRA is a learning project. Use freely, learn deeply, code fiercely! ๐ฅ
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
**May your code burn bright and your loops reverse true!** ๐๐ฅ
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "cmra"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "CMRA โ Creative Minimal Recursive Automaton language runtime"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.8"
|
|
7
|
+
license = { text = "MIT" }
|
|
8
|
+
authors = [{ name = "TeapotChimera418" }]
|
|
9
|
+
keywords = ["esolang", "esoteric", "programming", "language", "interpreter"]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Programming Language :: Python :: 3",
|
|
12
|
+
"License :: OSI Approved :: MIT License",
|
|
13
|
+
"Operating System :: OS Independent",
|
|
14
|
+
"Topic :: Software Development :: Interpreters",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.urls]
|
|
18
|
+
Homepage = "https://cmra-esolang.vercel.app"
|
|
19
|
+
Repository = "https://github.com/TeapotChimera418/cmra"
|
|
20
|
+
Changelog = "https://github.com/TeapotChimera418/cmra/blob/main/CHANGELOG.md"
|
|
21
|
+
|
|
22
|
+
[project.scripts]
|
|
23
|
+
cmra = "cmra.cli:main"
|
|
24
|
+
cmrash = "cmra.shadow_cli:main"
|
|
25
|
+
|
|
26
|
+
[tool.setuptools.packages.find]
|
|
27
|
+
where = ["src"]
|
|
28
|
+
|
|
29
|
+
[build-system]
|
|
30
|
+
requires = ["setuptools>=61", "wheel"]
|
|
31
|
+
build-backend = "setuptools.build_meta"
|
cmra-0.1.0/setup.cfg
ADDED
cmra-0.1.0/setup.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""
|
|
2
|
+
cli.py โ Command-line entry point for CMRA
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
cmra <file> # auto-dispatch by extension
|
|
6
|
+
cmra run <file> [--debug] # explicit run
|
|
7
|
+
cmra repl [--mode fire|shadow] # interactive REPL
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import argparse
|
|
11
|
+
import sys
|
|
12
|
+
import os as _os
|
|
13
|
+
|
|
14
|
+
# โโ Bootstrap: make sure `cmra` package is importable when cli.py is run
|
|
15
|
+
# directly (e.g. `python path/to/cli.py`) rather than via `python -m cmra.cli`
|
|
16
|
+
if "cmra" not in sys.modules:
|
|
17
|
+
_here = _os.path.dirname(_os.path.abspath(__file__)) # โฆ/src/cmra
|
|
18
|
+
_src = _os.path.dirname(_here) # โฆ/src
|
|
19
|
+
if _src not in sys.path:
|
|
20
|
+
sys.path.insert(0, _src)
|
|
21
|
+
|
|
22
|
+
from cmra.runner import dispatch
|
|
23
|
+
from cmra.repl import start_repl
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
27
|
+
parser = argparse.ArgumentParser(
|
|
28
|
+
prog="cmra",
|
|
29
|
+
description="CMRA โ Creative Minimal Recursive Automaton language runtime",
|
|
30
|
+
)
|
|
31
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
32
|
+
|
|
33
|
+
# -- cmra run <file> --------------------------------------------------
|
|
34
|
+
run_parser = subparsers.add_parser("run", help="Run a CMRA source file")
|
|
35
|
+
run_parser.add_argument("file", help="Path to .cmra or .cmrash source file")
|
|
36
|
+
run_parser.add_argument(
|
|
37
|
+
"--debug", action="store_true", help="Print interpreter debug info"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# -- cmra repl --------------------------------------------------------
|
|
41
|
+
repl_parser = subparsers.add_parser("repl", help="Start interactive REPL")
|
|
42
|
+
repl_parser.add_argument(
|
|
43
|
+
"--mode",
|
|
44
|
+
choices=["fire", "shadow"],
|
|
45
|
+
default="fire",
|
|
46
|
+
help="Interpreter mode (default: fire)",
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
return parser
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def main() -> None:
|
|
53
|
+
# Allow `cmra <file>` as shorthand (no subcommand)
|
|
54
|
+
if len(sys.argv) == 2 and not sys.argv[1].startswith("-") \
|
|
55
|
+
and sys.argv[1] not in ("run", "repl"):
|
|
56
|
+
# Treat as: cmra run <file>
|
|
57
|
+
try:
|
|
58
|
+
dispatch(sys.argv[1])
|
|
59
|
+
except (FileNotFoundError, ValueError) as e:
|
|
60
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
61
|
+
sys.exit(1)
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
parser = build_parser()
|
|
65
|
+
args = parser.parse_args()
|
|
66
|
+
|
|
67
|
+
if args.command == "run":
|
|
68
|
+
try:
|
|
69
|
+
dispatch(args.file, debug=args.debug)
|
|
70
|
+
except (FileNotFoundError, ValueError) as e:
|
|
71
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
72
|
+
sys.exit(1)
|
|
73
|
+
|
|
74
|
+
elif args.command == "repl":
|
|
75
|
+
start_repl(mode=args.mode)
|
|
76
|
+
|
|
77
|
+
else:
|
|
78
|
+
parser.print_help()
|
|
79
|
+
sys.exit(0)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
if __name__ == "__main__":
|
|
83
|
+
main()
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"""
|
|
2
|
+
fire.py โ Fire Dragon Interpreter (alias-based syntax)
|
|
3
|
+
Original: cmra.py
|
|
4
|
+
|
|
5
|
+
Exposed API:
|
|
6
|
+
FireInterpreter().run(file_path)
|
|
7
|
+
FireInterpreter().run_source(source)
|
|
8
|
+
run(file_path) # convenience wrapper
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import re
|
|
12
|
+
|
|
13
|
+
PRECEDENCE = {"<": 0, ">": 0, "<=": 0, ">=": 0, "==": 0, "+": 1, "-": 1, "*": 2, "/": 2}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class FireInterpreter:
|
|
17
|
+
"""Stateful Fire Dragon interpreter instance.
|
|
18
|
+
|
|
19
|
+
Each instance has isolated VARIABLES and DIRECTION so multiple
|
|
20
|
+
runs or REPL sessions don't bleed state into each other.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self):
|
|
24
|
+
self.variables: dict = {}
|
|
25
|
+
self.direction: int = 1
|
|
26
|
+
|
|
27
|
+
# ------------------------------------------------------------------
|
|
28
|
+
# Public API
|
|
29
|
+
# ------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
def run(self, file_path: str) -> None:
|
|
32
|
+
"""Execute a .cmra file."""
|
|
33
|
+
with open(file_path, encoding="utf-8") as f:
|
|
34
|
+
source = f.read()
|
|
35
|
+
self.run_source(source)
|
|
36
|
+
|
|
37
|
+
def run_source(self, source: str) -> None:
|
|
38
|
+
"""Execute CMRA source code given as a string."""
|
|
39
|
+
lines = [self._tokenize(line) for line in source.splitlines()]
|
|
40
|
+
line_index = 0
|
|
41
|
+
while 0 <= line_index < len(lines):
|
|
42
|
+
line = lines[line_index]
|
|
43
|
+
new_index = self._run_line(line, lines, line_index)
|
|
44
|
+
if new_index is not None:
|
|
45
|
+
line_index = new_index + self.direction
|
|
46
|
+
else:
|
|
47
|
+
line_index += self.direction
|
|
48
|
+
|
|
49
|
+
# ------------------------------------------------------------------
|
|
50
|
+
# Internal helpers
|
|
51
|
+
# ------------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
def _tokenize(self, line: str) -> list:
|
|
54
|
+
parts = re.findall(r'".*?"|\'.*?\'|\S+', line)
|
|
55
|
+
tokens = []
|
|
56
|
+
for item in parts:
|
|
57
|
+
# comment
|
|
58
|
+
if item == "murmur":
|
|
59
|
+
break
|
|
60
|
+
if (item.startswith('"') and item.endswith('"')) or \
|
|
61
|
+
(item.startswith("'") and item.endswith("'")):
|
|
62
|
+
tokens.append(("__str__", item[1:-1]))
|
|
63
|
+
continue
|
|
64
|
+
try:
|
|
65
|
+
tokens.append(float(item))
|
|
66
|
+
except ValueError:
|
|
67
|
+
tokens.append(item)
|
|
68
|
+
return tokens
|
|
69
|
+
|
|
70
|
+
def _evaluate(self, token_list: list):
|
|
71
|
+
values = []
|
|
72
|
+
operators = []
|
|
73
|
+
|
|
74
|
+
def apply_op(v1, op, v2):
|
|
75
|
+
if op == "+" and (isinstance(v1, str) or isinstance(v2, str)):
|
|
76
|
+
return str(v1) + str(v2)
|
|
77
|
+
return eval(f"{repr(v1)} {op} {repr(v2)}")
|
|
78
|
+
|
|
79
|
+
for token in token_list:
|
|
80
|
+
if type(token) == float:
|
|
81
|
+
values.append(token)
|
|
82
|
+
elif isinstance(token, tuple) and token[0] == "__str__":
|
|
83
|
+
values.append(token[1])
|
|
84
|
+
elif token in PRECEDENCE:
|
|
85
|
+
if not operators:
|
|
86
|
+
operators.append(token)
|
|
87
|
+
continue
|
|
88
|
+
prev = operators[-1]
|
|
89
|
+
if PRECEDENCE[prev] >= PRECEDENCE[token]:
|
|
90
|
+
op = operators.pop()
|
|
91
|
+
v2 = values.pop()
|
|
92
|
+
v1 = values.pop()
|
|
93
|
+
values.append(apply_op(v1, op, v2))
|
|
94
|
+
operators.append(token)
|
|
95
|
+
else:
|
|
96
|
+
values.append(self.variables[token])
|
|
97
|
+
|
|
98
|
+
while operators:
|
|
99
|
+
op = operators.pop()
|
|
100
|
+
v2 = values.pop()
|
|
101
|
+
v1 = values.pop()
|
|
102
|
+
values.append(apply_op(v1, op, v2))
|
|
103
|
+
|
|
104
|
+
return values[0]
|
|
105
|
+
|
|
106
|
+
def _run_line(self, line: list, lines: list, line_index: int):
|
|
107
|
+
if not line:
|
|
108
|
+
return None
|
|
109
|
+
if line[0] == "murmur":
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
# roar <expr> โ print
|
|
113
|
+
if line[0] == "roar":
|
|
114
|
+
print(self._evaluate(line[1:]))
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
# <var> bind <expr> โ assignment
|
|
118
|
+
if len(line) >= 2 and line[1] == "bind":
|
|
119
|
+
self.variables[line[0]] = self._evaluate(line[2:])
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
# sniff <condition> [: <action>] โ conditional
|
|
123
|
+
if line[0] == "sniff":
|
|
124
|
+
return self._handle_sniff(line, lines, line_index)
|
|
125
|
+
|
|
126
|
+
# dive / soar โ direction control
|
|
127
|
+
if line[0] == "dive":
|
|
128
|
+
self.direction = 1
|
|
129
|
+
return None
|
|
130
|
+
if line[0] == "soar":
|
|
131
|
+
self.direction = -1
|
|
132
|
+
return None
|
|
133
|
+
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
def _handle_sniff(self, line: list, lines: list, line_index: int):
|
|
137
|
+
if ":" in line:
|
|
138
|
+
colon_index = line.index(":")
|
|
139
|
+
condition = line[1:colon_index]
|
|
140
|
+
else:
|
|
141
|
+
condition = line[1:]
|
|
142
|
+
|
|
143
|
+
# Inline: sniff <cond> : <action>
|
|
144
|
+
if ":" in line and colon_index < len(line) - 1:
|
|
145
|
+
action = line[colon_index + 1:]
|
|
146
|
+
if self._evaluate(condition):
|
|
147
|
+
self._execute_inline(action)
|
|
148
|
+
return None
|
|
149
|
+
|
|
150
|
+
# Block: sniff <cond>\n{\n ...\n}
|
|
151
|
+
block_start = line_index + 1
|
|
152
|
+
while block_start < len(lines) and "{" not in lines[block_start]:
|
|
153
|
+
block_start += 1
|
|
154
|
+
|
|
155
|
+
block_end = block_start + 1
|
|
156
|
+
brace_count = 1
|
|
157
|
+
while block_end < len(lines) and brace_count > 0:
|
|
158
|
+
if "{" in lines[block_end]:
|
|
159
|
+
brace_count += 1
|
|
160
|
+
elif "}" in lines[block_end]:
|
|
161
|
+
brace_count -= 1
|
|
162
|
+
block_end += 1
|
|
163
|
+
|
|
164
|
+
if self._evaluate(condition):
|
|
165
|
+
first = block_start + 1
|
|
166
|
+
last = block_end - 2
|
|
167
|
+
return first if self.direction >= 0 else last
|
|
168
|
+
|
|
169
|
+
return block_end - 1
|
|
170
|
+
|
|
171
|
+
def _execute_inline(self, tokens: list) -> None:
|
|
172
|
+
if not tokens:
|
|
173
|
+
return
|
|
174
|
+
if tokens[0] == "sniff":
|
|
175
|
+
if ":" in tokens:
|
|
176
|
+
ci = tokens.index(":")
|
|
177
|
+
cond = tokens[1:ci]
|
|
178
|
+
nested = tokens[ci + 1:]
|
|
179
|
+
if self._evaluate(cond):
|
|
180
|
+
self._execute_inline(nested)
|
|
181
|
+
return
|
|
182
|
+
if len(tokens) == 1 and tokens[0] == "dive":
|
|
183
|
+
self.direction = 1
|
|
184
|
+
elif len(tokens) == 1 and tokens[0] == "soar":
|
|
185
|
+
self.direction = -1
|
|
186
|
+
elif len(tokens) >= 2 and tokens[1] == "bind":
|
|
187
|
+
self.variables[tokens[0]] = self._evaluate(tokens[2:])
|
|
188
|
+
elif tokens[0] == "roar":
|
|
189
|
+
print(self._evaluate(tokens[1:]))
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
# ------------------------------------------------------------------
|
|
193
|
+
# Convenience module-level function
|
|
194
|
+
# ------------------------------------------------------------------
|
|
195
|
+
|
|
196
|
+
def run(file_path: str) -> None:
|
|
197
|
+
"""Run a .cmra file with a fresh Fire interpreter."""
|
|
198
|
+
FireInterpreter().run(file_path)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""
|
|
2
|
+
repl.py โ Interactive REPL for CMRA
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
cmra repl (defaults to Fire Dragon)
|
|
6
|
+
cmra repl --mode fire
|
|
7
|
+
cmra repl --mode shadow
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from cmra.fire import FireInterpreter
|
|
11
|
+
from cmra.shadow import ShadowInterpreter
|
|
12
|
+
|
|
13
|
+
DRAGON_NAMES = {
|
|
14
|
+
"fire": "Fire Dragon (.cmra syntax)",
|
|
15
|
+
"shadow": "Shadow Dragon (.cmrash syntax)",
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
PROMPTS = {
|
|
19
|
+
"fire": "fire> ",
|
|
20
|
+
"shadow": "shadow> ",
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def start_repl(mode: str = "fire") -> None:
|
|
25
|
+
"""Start an interactive REPL session.
|
|
26
|
+
|
|
27
|
+
The interpreter instance persists across lines, so variables
|
|
28
|
+
set in one line are accessible in the next.
|
|
29
|
+
"""
|
|
30
|
+
mode = mode.lower()
|
|
31
|
+
if mode not in ("fire", "shadow"):
|
|
32
|
+
raise ValueError(f"Unknown mode '{mode}'. Choose 'fire' or 'shadow'.")
|
|
33
|
+
|
|
34
|
+
interpreter = FireInterpreter() if mode == "fire" else ShadowInterpreter()
|
|
35
|
+
prompt = PROMPTS[mode]
|
|
36
|
+
|
|
37
|
+
print(f"CMRA REPL โ {DRAGON_NAMES[mode]}")
|
|
38
|
+
print("Type 'exit' or 'quit' to leave, Ctrl-C to abort.\n")
|
|
39
|
+
|
|
40
|
+
while True:
|
|
41
|
+
try:
|
|
42
|
+
line = input(prompt)
|
|
43
|
+
except (EOFError, KeyboardInterrupt):
|
|
44
|
+
print("\nBye!")
|
|
45
|
+
break
|
|
46
|
+
|
|
47
|
+
stripped = line.strip()
|
|
48
|
+
if stripped in ("exit", "quit"):
|
|
49
|
+
print("Bye!")
|
|
50
|
+
break
|
|
51
|
+
if not stripped:
|
|
52
|
+
continue
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
interpreter.run_source(stripped)
|
|
56
|
+
except KeyError as e:
|
|
57
|
+
print(f" Error: undefined variable {e}")
|
|
58
|
+
except Exception as e:
|
|
59
|
+
print(f" Error: {e}")
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""
|
|
2
|
+
runner.py โ File-type dispatcher for CMRA
|
|
3
|
+
|
|
4
|
+
Detects interpreter based on file extension:
|
|
5
|
+
.cmra โ Fire Dragon
|
|
6
|
+
.cmrash โ Shadow Dragon
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from cmra.fire import FireInterpreter
|
|
11
|
+
from cmra.shadow import ShadowInterpreter
|
|
12
|
+
|
|
13
|
+
EXTENSION_MAP = {
|
|
14
|
+
".cmra": FireInterpreter,
|
|
15
|
+
".cmrash": ShadowInterpreter,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def dispatch(file_path: str, debug: bool = False) -> None:
|
|
20
|
+
"""Run a CMRA source file, choosing the interpreter by extension."""
|
|
21
|
+
path = Path(file_path)
|
|
22
|
+
|
|
23
|
+
if not path.exists():
|
|
24
|
+
raise FileNotFoundError(f"File not found: '{file_path}'")
|
|
25
|
+
|
|
26
|
+
ext = path.suffix.lower()
|
|
27
|
+
interpreter_cls = EXTENSION_MAP.get(ext)
|
|
28
|
+
|
|
29
|
+
if interpreter_cls is None:
|
|
30
|
+
supported = ", ".join(EXTENSION_MAP.keys())
|
|
31
|
+
raise ValueError(
|
|
32
|
+
f"Unknown file type '{ext}'. Expected one of: {supported}"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
if debug:
|
|
36
|
+
name = "Fire Dragon" if ext == ".cmra" else "Shadow Dragon"
|
|
37
|
+
print(f"[debug] Running with {name} interpreter: {path}")
|
|
38
|
+
|
|
39
|
+
interpreter = interpreter_cls()
|
|
40
|
+
interpreter.run(str(path))
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""
|
|
2
|
+
shadow.py โ Shadow Dragon Interpreter (minimal syntax)
|
|
3
|
+
Original: cmra_simplified.py
|
|
4
|
+
|
|
5
|
+
Exposed API:
|
|
6
|
+
ShadowInterpreter().run(file_path)
|
|
7
|
+
ShadowInterpreter().run_source(source)
|
|
8
|
+
run(file_path) # convenience wrapper
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import re
|
|
12
|
+
|
|
13
|
+
PRECEDENCE = {"<": 0, ">": 0, "<=": 0, ">=": 0, "==": 0, "+": 1, "-": 1, "*": 2, "/": 2}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ShadowInterpreter:
|
|
17
|
+
"""Stateful Shadow Dragon interpreter instance.
|
|
18
|
+
|
|
19
|
+
Each instance has isolated VARIABLES and DIRECTION so multiple
|
|
20
|
+
runs or REPL sessions don't bleed state into each other.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self):
|
|
24
|
+
self.variables: dict = {}
|
|
25
|
+
self.direction: int = 1
|
|
26
|
+
|
|
27
|
+
# ------------------------------------------------------------------
|
|
28
|
+
# Public API
|
|
29
|
+
# ------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
def run(self, file_path: str) -> None:
|
|
32
|
+
"""Execute a .cmrash file."""
|
|
33
|
+
with open(file_path, encoding="utf-8") as f:
|
|
34
|
+
source = f.read()
|
|
35
|
+
self.run_source(source)
|
|
36
|
+
|
|
37
|
+
def run_source(self, source: str) -> None:
|
|
38
|
+
"""Execute Shadow source code given as a string."""
|
|
39
|
+
lines = [self._tokenize(line) for line in source.splitlines()]
|
|
40
|
+
line_index = 0
|
|
41
|
+
while 0 <= line_index < len(lines):
|
|
42
|
+
line = lines[line_index]
|
|
43
|
+
new_index = self._run_line(line, lines, line_index)
|
|
44
|
+
if new_index is not None:
|
|
45
|
+
line_index = new_index + self.direction
|
|
46
|
+
else:
|
|
47
|
+
line_index += self.direction
|
|
48
|
+
|
|
49
|
+
# ------------------------------------------------------------------
|
|
50
|
+
# Internal helpers
|
|
51
|
+
# ------------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
def _tokenize(self, line: str) -> list:
|
|
54
|
+
parts = re.findall(r'".*?"|\'.*?\'|\S+', line)
|
|
55
|
+
tokens = []
|
|
56
|
+
for item in parts:
|
|
57
|
+
if (item.startswith('"') and item.endswith('"')) or \
|
|
58
|
+
(item.startswith("'") and item.endswith("'")):
|
|
59
|
+
tokens.append(("__str__", item[1:-1]))
|
|
60
|
+
continue
|
|
61
|
+
try:
|
|
62
|
+
tokens.append(float(item))
|
|
63
|
+
except ValueError:
|
|
64
|
+
tokens.append(item)
|
|
65
|
+
return tokens
|
|
66
|
+
|
|
67
|
+
def _evaluate(self, token_list: list):
|
|
68
|
+
values = []
|
|
69
|
+
operators = []
|
|
70
|
+
|
|
71
|
+
for token in token_list:
|
|
72
|
+
if type(token) == float:
|
|
73
|
+
values.append(token)
|
|
74
|
+
elif isinstance(token, tuple) and token[0] == "__str__":
|
|
75
|
+
values.append(token[1])
|
|
76
|
+
elif token in PRECEDENCE:
|
|
77
|
+
if not operators:
|
|
78
|
+
operators.append(token)
|
|
79
|
+
continue
|
|
80
|
+
prev = operators[-1]
|
|
81
|
+
if PRECEDENCE[prev] >= PRECEDENCE[token]:
|
|
82
|
+
op = operators.pop()
|
|
83
|
+
v2 = values.pop()
|
|
84
|
+
v1 = values.pop()
|
|
85
|
+
values.append(eval(f"{repr(v1)} {op} {repr(v2)}"))
|
|
86
|
+
operators.append(token)
|
|
87
|
+
else:
|
|
88
|
+
values.append(self.variables[token])
|
|
89
|
+
|
|
90
|
+
while operators:
|
|
91
|
+
op = operators.pop()
|
|
92
|
+
v2 = values.pop()
|
|
93
|
+
v1 = values.pop()
|
|
94
|
+
values.append(eval(f"{repr(v1)} {op} {repr(v2)}"))
|
|
95
|
+
|
|
96
|
+
return values[0]
|
|
97
|
+
|
|
98
|
+
def _run_line(self, line: list, lines: list, line_index: int):
|
|
99
|
+
if not line:
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
# print <expr>
|
|
103
|
+
if line[0] == "print":
|
|
104
|
+
print(self._evaluate(line[1:]))
|
|
105
|
+
return None
|
|
106
|
+
|
|
107
|
+
# <var> = <expr> โ assignment
|
|
108
|
+
if len(line) >= 2 and line[1] == "=":
|
|
109
|
+
self.variables[line[0]] = self._evaluate(line[2:])
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
# check <condition> [: <action>] โ conditional
|
|
113
|
+
if line[0] == "check":
|
|
114
|
+
return self._handle_check(line, lines, line_index)
|
|
115
|
+
|
|
116
|
+
# reverse โ flip direction
|
|
117
|
+
if line[0] == "reverse":
|
|
118
|
+
self.direction *= -1
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
return None
|
|
122
|
+
|
|
123
|
+
def _handle_check(self, line: list, lines: list, line_index: int):
|
|
124
|
+
if ":" in line:
|
|
125
|
+
colon_index = line.index(":")
|
|
126
|
+
condition = line[1:colon_index]
|
|
127
|
+
else:
|
|
128
|
+
condition = line[1:]
|
|
129
|
+
|
|
130
|
+
# Inline: check <cond> : <action>
|
|
131
|
+
if ":" in line and colon_index < len(line) - 1:
|
|
132
|
+
action = line[colon_index + 1:]
|
|
133
|
+
if self._evaluate(condition):
|
|
134
|
+
self._execute_inline(action)
|
|
135
|
+
return None
|
|
136
|
+
|
|
137
|
+
# Block: check <cond>\n{\n ...\n}
|
|
138
|
+
block_start = line_index + 1
|
|
139
|
+
while block_start < len(lines) and "{" not in lines[block_start]:
|
|
140
|
+
block_start += 1
|
|
141
|
+
|
|
142
|
+
block_end = block_start + 1
|
|
143
|
+
brace_count = 1
|
|
144
|
+
while block_end < len(lines) and brace_count > 0:
|
|
145
|
+
if "{" in lines[block_end]:
|
|
146
|
+
brace_count += 1
|
|
147
|
+
elif "}" in lines[block_end]:
|
|
148
|
+
brace_count -= 1
|
|
149
|
+
block_end += 1
|
|
150
|
+
|
|
151
|
+
if self._evaluate(condition):
|
|
152
|
+
first = block_start + 1
|
|
153
|
+
last = block_end - 2
|
|
154
|
+
return first if self.direction >= 0 else last
|
|
155
|
+
|
|
156
|
+
return block_end - 1
|
|
157
|
+
|
|
158
|
+
def _execute_inline(self, tokens: list) -> None:
|
|
159
|
+
if not tokens:
|
|
160
|
+
return
|
|
161
|
+
if tokens[0] == "check":
|
|
162
|
+
if ":" in tokens:
|
|
163
|
+
ci = tokens.index(":")
|
|
164
|
+
cond = tokens[1:ci]
|
|
165
|
+
nested = tokens[ci + 1:]
|
|
166
|
+
if self._evaluate(cond):
|
|
167
|
+
self._execute_inline(nested)
|
|
168
|
+
return
|
|
169
|
+
if len(tokens) == 1 and tokens[0] == "reverse":
|
|
170
|
+
self.direction *= -1
|
|
171
|
+
elif len(tokens) >= 2 and tokens[1] == "=":
|
|
172
|
+
self.variables[tokens[0]] = self._evaluate(tokens[2:])
|
|
173
|
+
elif tokens[0] == "print":
|
|
174
|
+
print(self._evaluate(tokens[1:]))
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
# ------------------------------------------------------------------
|
|
178
|
+
# Convenience module-level function
|
|
179
|
+
# ------------------------------------------------------------------
|
|
180
|
+
|
|
181
|
+
def run(file_path: str) -> None:
|
|
182
|
+
"""Run a .cmrash file with a fresh Shadow interpreter."""
|
|
183
|
+
ShadowInterpreter().run(file_path)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""
|
|
2
|
+
shadow_cli.py โ Entry point for the `cmrash` command.
|
|
3
|
+
|
|
4
|
+
`cmrash <file>` is a convenience alias that always uses the Shadow Dragon
|
|
5
|
+
interpreter, allowing users to omit the .cmrash extension check.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
cmrash <file.cmrash>
|
|
9
|
+
cmrash repl
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import sys
|
|
13
|
+
import os as _os
|
|
14
|
+
|
|
15
|
+
# Bootstrap: allow `python shadow_cli.py` invocation without install
|
|
16
|
+
if "cmra" not in sys.modules:
|
|
17
|
+
_here = _os.path.dirname(_os.path.abspath(__file__))
|
|
18
|
+
_src = _os.path.dirname(_here)
|
|
19
|
+
if _src not in sys.path:
|
|
20
|
+
sys.path.insert(0, _src)
|
|
21
|
+
|
|
22
|
+
import argparse
|
|
23
|
+
from cmra.shadow import ShadowInterpreter
|
|
24
|
+
from cmra.repl import start_repl
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def main() -> None:
|
|
28
|
+
# Allow bare: cmrash <file>
|
|
29
|
+
if len(sys.argv) == 2 and not sys.argv[1].startswith("-") \
|
|
30
|
+
and sys.argv[1] != "repl":
|
|
31
|
+
path = sys.argv[1]
|
|
32
|
+
try:
|
|
33
|
+
interp = ShadowInterpreter()
|
|
34
|
+
interp.run(path)
|
|
35
|
+
except FileNotFoundError:
|
|
36
|
+
print(f"Error: File not found: '{path}'", file=sys.stderr)
|
|
37
|
+
sys.exit(1)
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
parser = argparse.ArgumentParser(
|
|
41
|
+
prog="cmrash",
|
|
42
|
+
description="CMRA Shadow Dragon โ run .cmrash files",
|
|
43
|
+
)
|
|
44
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
45
|
+
|
|
46
|
+
run_p = subparsers.add_parser("run", help="Run a .cmrash source file")
|
|
47
|
+
run_p.add_argument("file", help="Path to .cmrash source file")
|
|
48
|
+
|
|
49
|
+
subparsers.add_parser("repl", help="Start Shadow Dragon REPL")
|
|
50
|
+
|
|
51
|
+
args = parser.parse_args()
|
|
52
|
+
|
|
53
|
+
if args.command == "run":
|
|
54
|
+
try:
|
|
55
|
+
interp = ShadowInterpreter()
|
|
56
|
+
interp.run(args.file)
|
|
57
|
+
except FileNotFoundError:
|
|
58
|
+
print(f"Error: File not found: '{args.file}'", file=sys.stderr)
|
|
59
|
+
sys.exit(1)
|
|
60
|
+
elif args.command == "repl":
|
|
61
|
+
start_repl(mode="shadow")
|
|
62
|
+
else:
|
|
63
|
+
parser.print_help()
|
|
64
|
+
sys.exit(0)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
if __name__ == "__main__":
|
|
68
|
+
main()
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cmra
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CMRA โ Creative Minimal Recursive Automaton language runtime
|
|
5
|
+
Author: TeapotChimera418
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://cmra-esolang.vercel.app
|
|
8
|
+
Project-URL: Repository, https://github.com/TeapotChimera418/cmra
|
|
9
|
+
Project-URL: Changelog, https://github.com/TeapotChimera418/cmra/blob/main/CHANGELOG.md
|
|
10
|
+
Keywords: esolang,esoteric,programming,language,interpreter
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Topic :: Software Development :: Interpreters
|
|
15
|
+
Requires-Python: >=3.8
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# ๐ CMRA Language (Chimera)
|
|
19
|
+
|
|
20
|
+
> **"Where code breathes fire and flows like flame..."**
|
|
21
|
+
|
|
22
|
+
**CMRA** (Chimera) is an esoteric programming language exploring reversible execution and direction control through a dragon-inspired syntax. Programs can soar *up* or dive *down* through code at runtime.
|
|
23
|
+
|
|
24
|
+
๐ฅ **GitHub:** [TeapotChimera418/cmra](https://github.com/TeapotChimera418/cmra)
|
|
25
|
+
๐ **Playground:** [cmra-esolang.vercel.app/playground.html](https://cmra-esolang.vercel.app/playground.html)
|
|
26
|
+
๐ **Docs:** [cmra-esolang.vercel.app](https://cmra-esolang.vercel.app/index.html)
|
|
27
|
+
๐ **User Guide:** [GUIDE.md](GUIDE.md)
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## ๐ Origin
|
|
32
|
+
|
|
33
|
+
CMRA began as a workshop project I attended by
|
|
34
|
+
[Tushar Sadhwani](https://github.com/tusharsadhwani) during a college multifest. The
|
|
35
|
+
prototype we built is in `prototype.py`. For a nonโaliased baseline, see Tushar's
|
|
36
|
+
[esolangs](https://github.com/tusharsadhwani/esolangs) repository. You can also read his
|
|
37
|
+
blog at [tush.ar](https://tush.ar).
|
|
38
|
+
|
|
39
|
+
This is a tiny side project made for fun and learning. If you build something cool or want
|
|
40
|
+
to improve the interpreter, feel free to reach out on
|
|
41
|
+
[GitHub (@TeapotChimera418)](https://github.com/TeapotChimera418) or Discord [`gamingchimera`](https://discord.com/users/736465046317563915).
|
|
42
|
+
|
|
43
|
+
## โจ Try it Online
|
|
44
|
+
|
|
45
|
+
**๐ฅ [Launch the Interactive Playground](https://cmra-esolang.vercel.app/playground.html)**
|
|
46
|
+
|
|
47
|
+
Write and run CMRA code directly in your browser โ no installation needed. Powered by Pyodide.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## โก Installation & Quick Start
|
|
52
|
+
|
|
53
|
+
### pip install (recommended)
|
|
54
|
+
|
|
55
|
+
```powershell
|
|
56
|
+
# Install from PyPI:
|
|
57
|
+
pip install cmra
|
|
58
|
+
|
|
59
|
+
# Or install from source (development):
|
|
60
|
+
pip install -e .
|
|
61
|
+
|
|
62
|
+
# Now use `cmra` and `cmrash` from anywhere inside the venv:
|
|
63
|
+
cmra "test cases\test.cmra"
|
|
64
|
+
cmrash "test cases\test.cmrash"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### No-install PowerShell wrapper
|
|
68
|
+
|
|
69
|
+
```powershell
|
|
70
|
+
.\cmra.ps1 "test cases\test.cmra"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Run with Python directly
|
|
74
|
+
|
|
75
|
+
```powershell
|
|
76
|
+
python -m cmra.cli "test cases\test.cmra"
|
|
77
|
+
python cmra.py "test cases cmra\test.cmra" # legacy single-file
|
|
78
|
+
python cmra_simplified.py "test cases cmra_simplified\test.cmrasim"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## ๐ฑ The Twin Dragons
|
|
84
|
+
|
|
85
|
+
| Dragon | Entry point | Keywords |
|
|
86
|
+
|--------|-------------|----------|
|
|
87
|
+
| ๐ฒ **Fire Dragon** | `cmra.py` / `src/cmra/fire.py` | `bind` `roar` `sniff` `dive` `soar` `murmur` |
|
|
88
|
+
| ๐ **Shadow Dragon** | `cmra_simplified.py` / `src/cmra/shadow.py` | `=` `print` `check` `reverse` `;` |
|
|
89
|
+
| ๐ **Ancient Wyrm** | `prototype.py` | Compact baseline, inline `sniff` only |
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## ๐ฒ Language at a Glance
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
murmur Fire Dragon โ count 0 to 5
|
|
97
|
+
i bind 0
|
|
98
|
+
flag bind 1
|
|
99
|
+
sniff i <= 5 : sniff flag == 0 : dive
|
|
100
|
+
flag bind 0
|
|
101
|
+
sniff flag == 0 : roar i
|
|
102
|
+
sniff flag == 0 : i bind i + 1
|
|
103
|
+
flag bind 1
|
|
104
|
+
sniff i <= 5 : sniff flag == 1 : soar
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
- `bind` โ assign variable
|
|
108
|
+
- `roar` โ print
|
|
109
|
+
- `sniff โฆ : action` โ conditional (inline or block)
|
|
110
|
+
- `dive` / `soar` โ set execution direction forward / reverse
|
|
111
|
+
- No loop keyword โ direction reversal + flag guards make loops
|
|
112
|
+
|
|
113
|
+
**โ See [GUIDE.md](GUIDE.md) for the full walkthrough.**
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## โก Execution Model
|
|
118
|
+
|
|
119
|
+
A global `DIRECTION` (`1` or `-1`) steps through lines. `soar` reverses it; `dive` restores forward. When direction goes past the first or last line, the program ends. Block `sniff/check` enters at the first (forward) or last (reverse) line of the block.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## ๐ฎ Test Cases & Projects
|
|
124
|
+
|
|
125
|
+
```powershell
|
|
126
|
+
# Test cases (pip-installed cmra)
|
|
127
|
+
cmra "test cases\test.cmra"
|
|
128
|
+
cmra "test cases\test_strings.cmra"
|
|
129
|
+
cmra "test cases\test_arith.cmra"
|
|
130
|
+
|
|
131
|
+
# Projects
|
|
132
|
+
cmra projects\calculator.cmra
|
|
133
|
+
cmra projects\countdown.cmra
|
|
134
|
+
cmra projects\fizzbuzz.cmra
|
|
135
|
+
cmra projects\story_adventure.cmra
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**Test cases** live in `test cases/` (`.cmra`) and `test cases cmra_simplified/` (`.cmrasim`).
|
|
139
|
+
**Projects** live in `projects/`: calculator, countdown, fizzbuzz, story_adventure.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## ๐ฆ File Inventory
|
|
144
|
+
|
|
145
|
+
| Path | Purpose |
|
|
146
|
+
|------|---------|
|
|
147
|
+
| `src/cmra/` | Installable Python package |
|
|
148
|
+
| `src/cmra/cli.py` | Entry point for the `cmra` command |
|
|
149
|
+
| `src/cmra/fire.py` | Fire Dragon interpreter |
|
|
150
|
+
| `src/cmra/shadow.py` | Shadow Dragon interpreter |
|
|
151
|
+
| `src/cmra/runner.py` | Shared dispatch logic |
|
|
152
|
+
| `cmra.py` | Legacy Fire Dragon single-file |
|
|
153
|
+
| `cmra_simplified.py` | Legacy Shadow Dragon single-file |
|
|
154
|
+
| `prototype.py` | Original workshop prototype |
|
|
155
|
+
| `cmra.ps1` / `cmra.bat` | No-install launchers |
|
|
156
|
+
| `pyproject.toml` | Package metadata (pip install) |
|
|
157
|
+
| `index.html` + `styles.css` | Static docs site |
|
|
158
|
+
| `playground.html` | Browser REPL (Pyodide) |
|
|
159
|
+
| `GUIDE.md` | Full user guide |
|
|
160
|
+
| `keybind.txt` | Fire โ Shadow keyword cheatsheet |
|
|
161
|
+
| `projects/` | Showcase programs |
|
|
162
|
+
| `test cases/` | Regression tests (`.cmra`) |
|
|
163
|
+
| `test cases cmra_simplified/` | Regression tests (`.cmrasim`) |
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## ๐ License
|
|
168
|
+
|
|
169
|
+
CMRA is a learning project. Use freely, learn deeply, code fiercely! ๐ฅ
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
**May your code burn bright and your loops reverse true!** ๐๐ฅ
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
setup.py
|
|
4
|
+
src/cmra/__init__.py
|
|
5
|
+
src/cmra/cli.py
|
|
6
|
+
src/cmra/fire.py
|
|
7
|
+
src/cmra/repl.py
|
|
8
|
+
src/cmra/runner.py
|
|
9
|
+
src/cmra/shadow.py
|
|
10
|
+
src/cmra/shadow_cli.py
|
|
11
|
+
src/cmra.egg-info/PKG-INFO
|
|
12
|
+
src/cmra.egg-info/SOURCES.txt
|
|
13
|
+
src/cmra.egg-info/dependency_links.txt
|
|
14
|
+
src/cmra.egg-info/entry_points.txt
|
|
15
|
+
src/cmra.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
cmra
|