epy-lang 1.0.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.
- epy_lang-1.0.0/LICENSE +21 -0
- epy_lang-1.0.0/PKG-INFO +94 -0
- epy_lang-1.0.0/README.md +52 -0
- epy_lang-1.0.0/epy_lang.egg-info/PKG-INFO +94 -0
- epy_lang-1.0.0/epy_lang.egg-info/SOURCES.txt +9 -0
- epy_lang-1.0.0/epy_lang.egg-info/dependency_links.txt +1 -0
- epy_lang-1.0.0/epy_lang.egg-info/entry_points.txt +2 -0
- epy_lang-1.0.0/epy_lang.egg-info/top_level.txt +1 -0
- epy_lang-1.0.0/pyproject.toml +33 -0
- epy_lang-1.0.0/setup.cfg +4 -0
- epy_lang-1.0.0/transpiler.py +330 -0
epy_lang-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sk Wasim
|
|
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
|
|
21
|
+
DEALINGS IN THE SOFTWARE.
|
epy_lang-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: epy-lang
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: The English-to-AST Python Language Compiler
|
|
5
|
+
Author: skwasimin
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 Sk Wasim
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
26
|
+
DEALINGS IN THE SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/skwasimin/epy
|
|
29
|
+
Project-URL: Repository, https://github.com/skwasimin/epy
|
|
30
|
+
Project-URL: Issues, https://github.com/skwasimin/epy/issues
|
|
31
|
+
Classifier: Programming Language :: Python :: 3
|
|
32
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
33
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
34
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
36
|
+
Classifier: Operating System :: OS Independent
|
|
37
|
+
Classifier: Topic :: Software Development :: Compilers
|
|
38
|
+
Requires-Python: >=3.9
|
|
39
|
+
Description-Content-Type: text/markdown
|
|
40
|
+
License-File: LICENSE
|
|
41
|
+
Dynamic: license-file
|
|
42
|
+
|
|
43
|
+
# EPY: The English-to-AST Python Language
|
|
44
|
+
|
|
45
|
+
[](https://github.com/skwasimin/epy/actions/workflows/run.yml)
|
|
46
|
+
[](https://opensource.org/licenses/MIT)
|
|
47
|
+
[](https://www.python.org/downloads/)
|
|
48
|
+
|
|
49
|
+
EPY is an enterprise-ready, plain-English programming syntax that compiles directly into Python's native Abstract Syntax Tree (AST).
|
|
50
|
+
|
|
51
|
+
It removes whitespace-sensitive indent traps and esoteric operator boilerplate, delivering 100% compliant, readable Python code with zero runtime overhead.
|
|
52
|
+
|
|
53
|
+
```epy
|
|
54
|
+
define class MicroServer:
|
|
55
|
+
define method init with port:
|
|
56
|
+
set self's port to port
|
|
57
|
+
end method
|
|
58
|
+
|
|
59
|
+
define async function start:
|
|
60
|
+
show "Server bound to port " + convert self's port to text
|
|
61
|
+
while true:
|
|
62
|
+
await handle_traffic()
|
|
63
|
+
end while
|
|
64
|
+
end function
|
|
65
|
+
end class
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Quickstart
|
|
69
|
+
|
|
70
|
+
**Local Installation:**
|
|
71
|
+
```bash
|
|
72
|
+
git clone https://github.com/skwasimin/epy.git
|
|
73
|
+
cd epy
|
|
74
|
+
pip install -e .
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Compile an `.epy` file into a `.py` file:
|
|
78
|
+
```bash
|
|
79
|
+
epy app.epy
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Compile and run immediately:
|
|
83
|
+
```bash
|
|
84
|
+
epy app.epy --run
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Language Reference
|
|
88
|
+
See [SYNTAX.md](SYNTAX.md) for full grammatical rules and examples.
|
|
89
|
+
|
|
90
|
+
## Contributing
|
|
91
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) and [ARCHITECTURE.md](ARCHITECTURE.md) for code submission guidelines.
|
|
92
|
+
|
|
93
|
+
## License
|
|
94
|
+
MIT License.
|
epy_lang-1.0.0/README.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# EPY: The English-to-AST Python Language
|
|
2
|
+
|
|
3
|
+
[](https://github.com/skwasimin/epy/actions/workflows/run.yml)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://www.python.org/downloads/)
|
|
6
|
+
|
|
7
|
+
EPY is an enterprise-ready, plain-English programming syntax that compiles directly into Python's native Abstract Syntax Tree (AST).
|
|
8
|
+
|
|
9
|
+
It removes whitespace-sensitive indent traps and esoteric operator boilerplate, delivering 100% compliant, readable Python code with zero runtime overhead.
|
|
10
|
+
|
|
11
|
+
```epy
|
|
12
|
+
define class MicroServer:
|
|
13
|
+
define method init with port:
|
|
14
|
+
set self's port to port
|
|
15
|
+
end method
|
|
16
|
+
|
|
17
|
+
define async function start:
|
|
18
|
+
show "Server bound to port " + convert self's port to text
|
|
19
|
+
while true:
|
|
20
|
+
await handle_traffic()
|
|
21
|
+
end while
|
|
22
|
+
end function
|
|
23
|
+
end class
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quickstart
|
|
27
|
+
|
|
28
|
+
**Local Installation:**
|
|
29
|
+
```bash
|
|
30
|
+
git clone https://github.com/skwasimin/epy.git
|
|
31
|
+
cd epy
|
|
32
|
+
pip install -e .
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Compile an `.epy` file into a `.py` file:
|
|
36
|
+
```bash
|
|
37
|
+
epy app.epy
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Compile and run immediately:
|
|
41
|
+
```bash
|
|
42
|
+
epy app.epy --run
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Language Reference
|
|
46
|
+
See [SYNTAX.md](SYNTAX.md) for full grammatical rules and examples.
|
|
47
|
+
|
|
48
|
+
## Contributing
|
|
49
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) and [ARCHITECTURE.md](ARCHITECTURE.md) for code submission guidelines.
|
|
50
|
+
|
|
51
|
+
## License
|
|
52
|
+
MIT License.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: epy-lang
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: The English-to-AST Python Language Compiler
|
|
5
|
+
Author: skwasimin
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 Sk Wasim
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
26
|
+
DEALINGS IN THE SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/skwasimin/epy
|
|
29
|
+
Project-URL: Repository, https://github.com/skwasimin/epy
|
|
30
|
+
Project-URL: Issues, https://github.com/skwasimin/epy/issues
|
|
31
|
+
Classifier: Programming Language :: Python :: 3
|
|
32
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
33
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
34
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
36
|
+
Classifier: Operating System :: OS Independent
|
|
37
|
+
Classifier: Topic :: Software Development :: Compilers
|
|
38
|
+
Requires-Python: >=3.9
|
|
39
|
+
Description-Content-Type: text/markdown
|
|
40
|
+
License-File: LICENSE
|
|
41
|
+
Dynamic: license-file
|
|
42
|
+
|
|
43
|
+
# EPY: The English-to-AST Python Language
|
|
44
|
+
|
|
45
|
+
[](https://github.com/skwasimin/epy/actions/workflows/run.yml)
|
|
46
|
+
[](https://opensource.org/licenses/MIT)
|
|
47
|
+
[](https://www.python.org/downloads/)
|
|
48
|
+
|
|
49
|
+
EPY is an enterprise-ready, plain-English programming syntax that compiles directly into Python's native Abstract Syntax Tree (AST).
|
|
50
|
+
|
|
51
|
+
It removes whitespace-sensitive indent traps and esoteric operator boilerplate, delivering 100% compliant, readable Python code with zero runtime overhead.
|
|
52
|
+
|
|
53
|
+
```epy
|
|
54
|
+
define class MicroServer:
|
|
55
|
+
define method init with port:
|
|
56
|
+
set self's port to port
|
|
57
|
+
end method
|
|
58
|
+
|
|
59
|
+
define async function start:
|
|
60
|
+
show "Server bound to port " + convert self's port to text
|
|
61
|
+
while true:
|
|
62
|
+
await handle_traffic()
|
|
63
|
+
end while
|
|
64
|
+
end function
|
|
65
|
+
end class
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Quickstart
|
|
69
|
+
|
|
70
|
+
**Local Installation:**
|
|
71
|
+
```bash
|
|
72
|
+
git clone https://github.com/skwasimin/epy.git
|
|
73
|
+
cd epy
|
|
74
|
+
pip install -e .
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Compile an `.epy` file into a `.py` file:
|
|
78
|
+
```bash
|
|
79
|
+
epy app.epy
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Compile and run immediately:
|
|
83
|
+
```bash
|
|
84
|
+
epy app.epy --run
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Language Reference
|
|
88
|
+
See [SYNTAX.md](SYNTAX.md) for full grammatical rules and examples.
|
|
89
|
+
|
|
90
|
+
## Contributing
|
|
91
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) and [ARCHITECTURE.md](ARCHITECTURE.md) for code submission guidelines.
|
|
92
|
+
|
|
93
|
+
## License
|
|
94
|
+
MIT License.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
transpiler
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "epy-lang"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "The English-to-AST Python Language Compiler"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [{ name = "skwasimin" }]
|
|
11
|
+
license = { file = "LICENSE" }
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Programming Language :: Python :: 3",
|
|
14
|
+
"Programming Language :: Python :: 3.9",
|
|
15
|
+
"Programming Language :: Python :: 3.10",
|
|
16
|
+
"Programming Language :: Python :: 3.11",
|
|
17
|
+
"Programming Language :: Python :: 3.12",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Topic :: Software Development :: Compilers"
|
|
20
|
+
]
|
|
21
|
+
requires-python = ">=3.9"
|
|
22
|
+
|
|
23
|
+
[project.scripts]
|
|
24
|
+
epy = "transpiler:main"
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Homepage = "https://github.com/skwasimin/epy"
|
|
28
|
+
Repository = "https://github.com/skwasimin/epy"
|
|
29
|
+
Issues = "https://github.com/skwasimin/epy/issues"
|
|
30
|
+
|
|
31
|
+
[tool.setuptools]
|
|
32
|
+
py-modules = ["transpiler"]
|
|
33
|
+
|
epy_lang-1.0.0/setup.cfg
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import ast
|
|
3
|
+
import re
|
|
4
|
+
import sys
|
|
5
|
+
import subprocess
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
class EpySyntaxError(Exception): pass
|
|
9
|
+
|
|
10
|
+
QUOTE_RE = re.compile(r'("[^"]*"|\'[^\']*\')')
|
|
11
|
+
|
|
12
|
+
def mask_quotes(line: str):
|
|
13
|
+
quotes = []
|
|
14
|
+
def _stash(m):
|
|
15
|
+
quotes.append(m.group(0))
|
|
16
|
+
return f"__Q{len(quotes) - 1}__"
|
|
17
|
+
return QUOTE_RE.sub(_stash, line), quotes
|
|
18
|
+
|
|
19
|
+
def unmask_quotes(line: str, quotes: list):
|
|
20
|
+
for i, q in enumerate(quotes):
|
|
21
|
+
line = line.replace(f"__Q{i}__", q)
|
|
22
|
+
return line
|
|
23
|
+
|
|
24
|
+
PHRASE_REPLACEMENTS = [
|
|
25
|
+
(r"\blength of (\w+)\b", r"len(\1)"),
|
|
26
|
+
(r"\buppercase of (\w+)\b", r"\1.upper()"),
|
|
27
|
+
(r"\blowercase of (\w+)\b", r"\1.lower()"),
|
|
28
|
+
(r"\bitem at index (\w+) in (\w+)\b", r"\2[\1]"),
|
|
29
|
+
(r"\bvalue of (\w+) in (\w+)\b", r'\2["\1"]'),
|
|
30
|
+
(r"\bconvert (\w+) to text\b", r"str(\1)"),
|
|
31
|
+
(r"\bconvert (\w+) to a number\b", r"float(\1)"),
|
|
32
|
+
(r"\bthe error\b", "e"),
|
|
33
|
+
(r"\bthe remainder of (.+?) divided by (.+?)\b", r"(\1 % \2)"),
|
|
34
|
+
(r"\b(.+?) to the power of (.+?)\b", r"(\1 ** \2)"),
|
|
35
|
+
(r"\b(.+?) divided by (.+?) rounding down\b", r"(\1 // \2)"),
|
|
36
|
+
(r"\b(.+?) divided by (.+?)\b", r"(\1 / \2)"),
|
|
37
|
+
(r"\bthe sum of (\w+)\b", r"sum(\1)"),
|
|
38
|
+
(r"\bthe maximum of (\w+)\b", r"max(\1)"),
|
|
39
|
+
(r"\bthe minimum of (\w+)\b", r"min(\1)"),
|
|
40
|
+
(r"\bthe average of (\w+)\b", r"(sum(\1) / len(\1))"),
|
|
41
|
+
(r"\bthe type of (\w+)\b", r"type(\1)"),
|
|
42
|
+
(r"\bround (\w+) to (\w+) decimal places\b", r"round(\1, \2)"),
|
|
43
|
+
(r"\bround (\w+)\b", r"round(\1)"),
|
|
44
|
+
(r"\bis not equal to\b", "!="),
|
|
45
|
+
(r"\bis equal to\b", "=="),
|
|
46
|
+
(r"\bis greater than or equal to\b", ">="),
|
|
47
|
+
(r"\bis less than or equal to\b", "<="),
|
|
48
|
+
(r"\bis at least\b", ">="),
|
|
49
|
+
(r"\bis at most\b", "<="),
|
|
50
|
+
(r"\bis greater than\b", ">"),
|
|
51
|
+
(r"\bis less than\b", "<"),
|
|
52
|
+
(r"\bis not in\b", "not in"),
|
|
53
|
+
(r"\bis in\b", "in"),
|
|
54
|
+
(r"\btrue\b", "True"),
|
|
55
|
+
(r"\bfalse\b", "False"),
|
|
56
|
+
(r"\bnothing\b", "None"),
|
|
57
|
+
(r"(\w+)'s (\w+)", r"\1.\2")
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
def translate_expr(expr: str) -> str:
|
|
61
|
+
masked, quotes = mask_quotes(expr)
|
|
62
|
+
for pattern, repl in PHRASE_REPLACEMENTS:
|
|
63
|
+
masked = re.sub(pattern, repl, masked, flags=re.IGNORECASE)
|
|
64
|
+
return unmask_quotes(masked, quotes)
|
|
65
|
+
|
|
66
|
+
def make_expr_node(expr: str) -> ast.expr:
|
|
67
|
+
try: return ast.parse(translate_expr(expr.strip()), mode='eval').body
|
|
68
|
+
except SyntaxError as e: raise EpySyntaxError(f"Cannot parse expression '{expr}': {e}")
|
|
69
|
+
|
|
70
|
+
def make_stmt_node(stmt: str) -> ast.stmt:
|
|
71
|
+
try:
|
|
72
|
+
parsed = ast.parse(translate_expr(stmt.strip()), mode='exec')
|
|
73
|
+
return parsed.body[0] if parsed.body else ast.Pass()
|
|
74
|
+
except SyntaxError as e: raise EpySyntaxError(f"Cannot parse statement '{stmt}': {e}")
|
|
75
|
+
|
|
76
|
+
def parse_dict_pairs(text: str) -> tuple:
|
|
77
|
+
keys, values = [], []
|
|
78
|
+
for p in [x.strip() for x in text.split(",") if x.strip()]:
|
|
79
|
+
m = re.match(r"(\w+) as (.+)", p, re.IGNORECASE)
|
|
80
|
+
if m:
|
|
81
|
+
keys.append(ast.Constant(value=m.group(1)))
|
|
82
|
+
values.append(make_expr_node(m.group(2)))
|
|
83
|
+
else: raise EpySyntaxError(f"Invalid dict element '{p}'")
|
|
84
|
+
return keys, values
|
|
85
|
+
|
|
86
|
+
def build_ast(source_text: str) -> ast.Module:
|
|
87
|
+
root = ast.Module(body=[], type_ignores=[])
|
|
88
|
+
stack, block_types, try_stack, if_stack, pending_dec = [root.body], ["module"], [], [], []
|
|
89
|
+
|
|
90
|
+
for line_no, raw_line in enumerate(source_text.splitlines(), start=1):
|
|
91
|
+
line = raw_line.strip()
|
|
92
|
+
if not line or line.startswith("#"): continue
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
m_deco = re.match(r"^apply decorator (.+)$", line, re.IGNORECASE)
|
|
96
|
+
if m_deco: pending_dec.append(make_expr_node(m_deco.group(1))); continue
|
|
97
|
+
|
|
98
|
+
m_imp = re.match(r"^use library ([\w\.]+)(?: as (\w+))?$", line, re.IGNORECASE)
|
|
99
|
+
if m_imp: stack[-1].append(ast.Import(names=[ast.alias(name=m_imp.group(1), asname=m_imp.group(2))])); continue
|
|
100
|
+
|
|
101
|
+
m_from = re.match(r"^from library ([\w\.]+) use (.+)$", line, re.IGNORECASE)
|
|
102
|
+
if m_from:
|
|
103
|
+
aliases = [ast.alias(name=n.strip(), asname=None) for n in m_from.group(2).split(",") if n.strip()]
|
|
104
|
+
stack[-1].append(ast.ImportFrom(module=m_from.group(1), names=aliases, level=0))
|
|
105
|
+
continue
|
|
106
|
+
|
|
107
|
+
m_cls = re.match(r"^define class (\w+)(?: based on (\w+))?:$", line, re.IGNORECASE)
|
|
108
|
+
if m_cls:
|
|
109
|
+
bases = [ast.Name(id=m_cls.group(2), ctx=ast.Load())] if m_cls.group(2) else []
|
|
110
|
+
node = ast.ClassDef(name=m_cls.group(1), bases=bases, keywords=[], body=[], decorator_list=pending_dec, type_params=[])
|
|
111
|
+
pending_dec = []; stack[-1].append(node); stack.append(node.body); block_types.append("class"); continue
|
|
112
|
+
|
|
113
|
+
m_func = re.match(r"^define (async function|function|method) (\w+)(?: with (.+))?:$", line, re.IGNORECASE)
|
|
114
|
+
if m_func:
|
|
115
|
+
kind, f_name, f_args = m_func.group(1).lower(), m_func.group(2), m_func.group(3) or ""
|
|
116
|
+
if f_name.lower() == "init": f_name = "__init__"
|
|
117
|
+
arg_names = [a.strip() for a in f_args.split(",") if a.strip()]
|
|
118
|
+
if kind == "method": arg_names.insert(0, "self")
|
|
119
|
+
args_node = ast.arguments(posonlyargs=[], args=[ast.arg(arg=a) for a in arg_names], kwonlyargs=[], kw_defaults=[], defaults=[])
|
|
120
|
+
node_cls = ast.AsyncFunctionDef if "async" in kind else ast.FunctionDef
|
|
121
|
+
node = node_cls(name=f_name, args=args_node, body=[], decorator_list=pending_dec, returns=None, type_params=[])
|
|
122
|
+
pending_dec = []; stack[-1].append(node); stack.append(node.body); block_types.append(kind); continue
|
|
123
|
+
|
|
124
|
+
m_if = re.match(r"^if (.+):$", line, re.IGNORECASE)
|
|
125
|
+
if m_if:
|
|
126
|
+
node = ast.If(test=make_expr_node(m_if.group(1)), body=[], orelse=[])
|
|
127
|
+
stack[-1].append(node); stack.append(node.body); block_types.append("if"); if_stack.append(node); continue
|
|
128
|
+
|
|
129
|
+
m_elif = re.match(r"^otherwise if (.+):$", line, re.IGNORECASE)
|
|
130
|
+
if m_elif:
|
|
131
|
+
node = ast.If(test=make_expr_node(m_elif.group(1)), body=[], orelse=[])
|
|
132
|
+
if_stack[-1].orelse.append(node); stack.pop(); stack.append(node.body); if_stack[-1] = node; continue
|
|
133
|
+
|
|
134
|
+
m_else = re.match(r"^else:$", line, re.IGNORECASE)
|
|
135
|
+
if m_else: stack.pop(); stack.append(if_stack[-1].orelse); continue
|
|
136
|
+
|
|
137
|
+
m_rep = re.match(r"^repeat (.+) times:$", line, re.IGNORECASE)
|
|
138
|
+
if m_rep:
|
|
139
|
+
range_call = ast.Call(func=ast.Name(id="range", ctx=ast.Load()), args=[make_expr_node(m_rep.group(1))], keywords=[])
|
|
140
|
+
node = ast.For(target=ast.Name(id="_", ctx=ast.Store()), iter=range_call, body=[], orelse=[])
|
|
141
|
+
stack[-1].append(node); stack.append(node.body); block_types.append("repeat"); continue
|
|
142
|
+
|
|
143
|
+
m_while = re.match(r"^while (.+):$", line, re.IGNORECASE)
|
|
144
|
+
if m_while:
|
|
145
|
+
node = ast.While(test=make_expr_node(m_while.group(1)), body=[], orelse=[])
|
|
146
|
+
stack[-1].append(node); stack.append(node.body); block_types.append("while"); continue
|
|
147
|
+
|
|
148
|
+
m_for = re.match(r"^for each (\w+) in (.+):$", line, re.IGNORECASE)
|
|
149
|
+
if m_for:
|
|
150
|
+
node = ast.For(target=ast.Name(id=m_for.group(1), ctx=ast.Store()), iter=make_expr_node(m_for.group(2)), body=[], orelse=[])
|
|
151
|
+
stack[-1].append(node); stack.append(node.body); block_types.append("for"); continue
|
|
152
|
+
|
|
153
|
+
m_file = re.match(r"^open file (.+) for (reading|writing|appending) as (\w+):$", line, re.IGNORECASE)
|
|
154
|
+
if m_file:
|
|
155
|
+
mode_map = {"reading": "r", "writing": "w", "appending": "a"}
|
|
156
|
+
open_call = ast.Call(func=ast.Name(id="open", ctx=ast.Load()), args=[make_expr_node(m_file.group(1)), ast.Constant(value=mode_map[m_file.group(2).lower()])], keywords=[])
|
|
157
|
+
node = ast.With(items=[ast.withitem(context_expr=open_call, optional_vars=ast.Name(id=m_file.group(3), ctx=ast.Store()))], body=[])
|
|
158
|
+
stack[-1].append(node); stack.append(node.body); block_types.append("file"); continue
|
|
159
|
+
|
|
160
|
+
if re.match(r"^try:$", line, re.IGNORECASE):
|
|
161
|
+
node = ast.Try(body=[], handlers=[], orelse=[], finalbody=[])
|
|
162
|
+
stack[-1].append(node); stack.append(node.body); block_types.append("try"); try_stack.append(node); continue
|
|
163
|
+
|
|
164
|
+
m_except = re.match(r"^if that fails(?: with (\w+))?:$", line, re.IGNORECASE)
|
|
165
|
+
if m_except:
|
|
166
|
+
exc_type = ast.Name(id=m_except.group(1), ctx=ast.Load()) if m_except.group(1) else None
|
|
167
|
+
handler = ast.ExceptHandler(type=exc_type, name="e", body=[])
|
|
168
|
+
try_stack[-1].handlers.append(handler); stack.pop(); stack.append(handler.body); block_types[-1] = "except"; continue
|
|
169
|
+
|
|
170
|
+
if re.match(r"^finally:$", line, re.IGNORECASE):
|
|
171
|
+
stack.pop(); stack.append(try_stack[-1].finalbody); block_types[-1] = "finally"; continue
|
|
172
|
+
|
|
173
|
+
m_end = re.match(r"^end (if|while|repeat|for|function|async function|method|class|try|file)$", line, re.IGNORECASE)
|
|
174
|
+
if m_end:
|
|
175
|
+
expected = m_end.group(1).lower()
|
|
176
|
+
stack.pop(); current = block_types.pop()
|
|
177
|
+
if expected == "try" and current in ["except", "finally"]: try_stack.pop()
|
|
178
|
+
if expected == "if": if_stack.pop()
|
|
179
|
+
continue
|
|
180
|
+
|
|
181
|
+
m_await_store = re.match(r"^await (.+) and store in (\w+)$", line, re.IGNORECASE)
|
|
182
|
+
if m_await_store:
|
|
183
|
+
node = ast.Assign(targets=[ast.Name(id=m_await_store.group(2), ctx=ast.Store())], value=ast.Await(value=make_expr_node(m_await_store.group(1))))
|
|
184
|
+
stack[-1].append(node); continue
|
|
185
|
+
|
|
186
|
+
m_await_call = re.match(r"^await (.+)$", line, re.IGNORECASE)
|
|
187
|
+
if m_await_call:
|
|
188
|
+
stack[-1].append(ast.Expr(value=ast.Await(value=make_expr_node(m_await_call.group(1))))); continue
|
|
189
|
+
|
|
190
|
+
m_yield = re.match(r"^yield (.+)$", line, re.IGNORECASE)
|
|
191
|
+
if m_yield: stack[-1].append(ast.Expr(value=ast.Yield(value=make_expr_node(m_yield.group(1))))); continue
|
|
192
|
+
|
|
193
|
+
m_ret = re.match(r"^return (.+)$", line, re.IGNORECASE)
|
|
194
|
+
if m_ret: stack[-1].append(ast.Return(value=make_expr_node(m_ret.group(1)))); continue
|
|
195
|
+
|
|
196
|
+
if re.match(r"^return$", line, re.IGNORECASE): stack[-1].append(ast.Return(value=None)); continue
|
|
197
|
+
|
|
198
|
+
# --- Modifications & Attributes (RESTORED FIX) ---
|
|
199
|
+
m_inc = re.match(r"^increase (.+) by (.+)$", line, re.IGNORECASE)
|
|
200
|
+
if m_inc:
|
|
201
|
+
node = ast.AugAssign(target=make_expr_node(m_inc.group(1)), op=ast.Add(), value=make_expr_node(m_inc.group(2)))
|
|
202
|
+
stack[-1].append(node); continue
|
|
203
|
+
|
|
204
|
+
m_dec = re.match(r"^decrease (.+) by (.+)$", line, re.IGNORECASE)
|
|
205
|
+
if m_dec:
|
|
206
|
+
node = ast.AugAssign(target=make_expr_node(m_dec.group(1)), op=ast.Sub(), value=make_expr_node(m_dec.group(2)))
|
|
207
|
+
stack[-1].append(node); continue
|
|
208
|
+
|
|
209
|
+
m_set_val = re.match(r"^set value of (\w+) in (\w+) to (.+)$", line, re.IGNORECASE)
|
|
210
|
+
if m_set_val:
|
|
211
|
+
node = ast.Assign(
|
|
212
|
+
targets=[ast.Subscript(value=ast.Name(id=m_set_val.group(2), ctx=ast.Load()), slice=ast.Constant(value=m_set_val.group(1)), ctx=ast.Store())],
|
|
213
|
+
value=make_expr_node(m_set_val.group(3))
|
|
214
|
+
)
|
|
215
|
+
stack[-1].append(node); continue
|
|
216
|
+
|
|
217
|
+
m_set_attr = re.match(r"^set (\w+)'s (\w+) to (.+)$", line, re.IGNORECASE)
|
|
218
|
+
if m_set_attr:
|
|
219
|
+
node = ast.Assign(
|
|
220
|
+
targets=[ast.Attribute(value=ast.Name(id=m_set_attr.group(1), ctx=ast.Load()), attr=m_set_attr.group(2), ctx=ast.Store())],
|
|
221
|
+
value=make_expr_node(m_set_attr.group(3))
|
|
222
|
+
)
|
|
223
|
+
stack[-1].append(node); continue
|
|
224
|
+
|
|
225
|
+
# --- Collections (RESTORED FIX) ---
|
|
226
|
+
m_list = re.match(r"^create list (\w+) with (.+)$", line, re.IGNORECASE)
|
|
227
|
+
if m_list:
|
|
228
|
+
elts = [make_expr_node(e.strip()) for e in m_list.group(2).split(",") if e.strip()]
|
|
229
|
+
node = ast.Assign(targets=[ast.Name(id=m_list.group(1), ctx=ast.Store())], value=ast.List(elts=elts, ctx=ast.Load()))
|
|
230
|
+
stack[-1].append(node); continue
|
|
231
|
+
|
|
232
|
+
m_list_empty = re.match(r"^create empty list (\w+)$", line, re.IGNORECASE)
|
|
233
|
+
if m_list_empty:
|
|
234
|
+
node = ast.Assign(targets=[ast.Name(id=m_list_empty.group(1), ctx=ast.Store())], value=ast.List(elts=[], ctx=ast.Load()))
|
|
235
|
+
stack[-1].append(node); continue
|
|
236
|
+
|
|
237
|
+
m_dict = re.match(r"^create dictionary (\w+) with (.+)$", line, re.IGNORECASE)
|
|
238
|
+
if m_dict:
|
|
239
|
+
keys, vals = parse_dict_pairs(m_dict.group(2))
|
|
240
|
+
node = ast.Assign(targets=[ast.Name(id=m_dict.group(1), ctx=ast.Store())], value=ast.Dict(keys=keys, values=vals))
|
|
241
|
+
stack[-1].append(node); continue
|
|
242
|
+
|
|
243
|
+
m_add = re.match(r"^add (.+) to (\w+)$", line, re.IGNORECASE)
|
|
244
|
+
if m_add:
|
|
245
|
+
node = ast.Expr(value=ast.Call(func=ast.Attribute(value=ast.Name(id=m_add.group(2), ctx=ast.Load()), attr="append", ctx=ast.Load()), args=[make_expr_node(m_add.group(1))], keywords=[]))
|
|
246
|
+
stack[-1].append(node); continue
|
|
247
|
+
|
|
248
|
+
m_rem = re.match(r"^remove (.+) from (\w+)$", line, re.IGNORECASE)
|
|
249
|
+
if m_rem:
|
|
250
|
+
node = ast.Expr(value=ast.Call(func=ast.Attribute(value=ast.Name(id=m_rem.group(2), ctx=ast.Load()), attr="remove", ctx=ast.Load()), args=[make_expr_node(m_rem.group(1))], keywords=[]))
|
|
251
|
+
stack[-1].append(node); continue
|
|
252
|
+
|
|
253
|
+
m_sort = re.match(r"^sort (\w+)$", line, re.IGNORECASE)
|
|
254
|
+
if m_sort:
|
|
255
|
+
node = ast.Expr(value=ast.Call(func=ast.Attribute(value=ast.Name(id=m_sort.group(1), ctx=ast.Load()), attr="sort", ctx=ast.Load()), args=[], keywords=[]))
|
|
256
|
+
stack[-1].append(node); continue
|
|
257
|
+
|
|
258
|
+
# --- Standard Operations ---
|
|
259
|
+
m_store_multi = re.match(r"^store (.+) in ((?:\w+\s*,\s*)+\w+)$", line, re.IGNORECASE)
|
|
260
|
+
if m_store_multi:
|
|
261
|
+
targets = [ast.Name(id=t.strip(), ctx=ast.Store()) for t in m_store_multi.group(2).split(",")]
|
|
262
|
+
stack[-1].append(ast.Assign(targets=[ast.Tuple(elts=targets, ctx=ast.Store())], value=make_expr_node(m_store_multi.group(1)))); continue
|
|
263
|
+
|
|
264
|
+
m_store = re.match(r"^store (.+) in (\w+)$", line, re.IGNORECASE)
|
|
265
|
+
if m_store: stack[-1].append(ast.Assign(targets=[ast.Name(id=m_store.group(2), ctx=ast.Store())], value=make_expr_node(m_store.group(1)))); continue
|
|
266
|
+
|
|
267
|
+
m_let = re.match(r"^let (\w+) be (.+)$", line, re.IGNORECASE)
|
|
268
|
+
if m_let: stack[-1].append(ast.Assign(targets=[ast.Name(id=m_let.group(1), ctx=ast.Store())], value=make_expr_node(m_let.group(2)))); continue
|
|
269
|
+
|
|
270
|
+
m_new = re.match(r"^create (\w+) as new (\w+)(?: with (.+))?$", line, re.IGNORECASE)
|
|
271
|
+
if m_new:
|
|
272
|
+
args = [make_expr_node(a.strip()) for a in m_new.group(3).split(",") if a.strip()] if m_new.group(3) else []
|
|
273
|
+
node = ast.Assign(targets=[ast.Name(id=m_new.group(1), ctx=ast.Store())], value=ast.Call(func=ast.Name(id=m_new.group(2), ctx=ast.Load()), args=args, keywords=[]))
|
|
274
|
+
stack[-1].append(node); continue
|
|
275
|
+
|
|
276
|
+
m_show = re.match(r"^show (.+)$", line, re.IGNORECASE)
|
|
277
|
+
if m_show:
|
|
278
|
+
stack[-1].append(ast.Expr(value=ast.Call(func=ast.Name(id="print", ctx=ast.Load()), args=[make_expr_node(m_show.group(1))], keywords=[]))); continue
|
|
279
|
+
|
|
280
|
+
m_meth_store = re.match(r"^call (\w+)'s (\w+)(?: with (.+))? and store in (\w+)$", line, re.IGNORECASE)
|
|
281
|
+
if m_meth_store:
|
|
282
|
+
args = [make_expr_node(a.strip()) for a in m_meth_store.group(3).split(",") if a.strip()] if m_meth_store.group(3) else []
|
|
283
|
+
call = ast.Call(func=ast.Attribute(value=ast.Name(id=m_meth_store.group(1), ctx=ast.Load()), attr=m_meth_store.group(2), ctx=ast.Load()), args=args, keywords=[])
|
|
284
|
+
stack[-1].append(ast.Assign(targets=[ast.Name(id=m_meth_store.group(4), ctx=ast.Store())], value=call)); continue
|
|
285
|
+
|
|
286
|
+
m_meth = re.match(r"^call (\w+)'s (\w+)(?: with (.+))?$", line, re.IGNORECASE)
|
|
287
|
+
if m_meth:
|
|
288
|
+
args = [make_expr_node(a.strip()) for a in m_meth.group(3).split(",") if a.strip()] if m_meth.group(3) else []
|
|
289
|
+
call = ast.Call(func=ast.Attribute(value=ast.Name(id=m_meth.group(1), ctx=ast.Load()), attr=m_meth.group(2), ctx=ast.Load()), args=args, keywords=[])
|
|
290
|
+
stack[-1].append(ast.Expr(value=call)); continue
|
|
291
|
+
|
|
292
|
+
m_fn_store = re.match(r"^call ([\w\.]+)(?: with (.+))? and store in (\w+)$", line, re.IGNORECASE)
|
|
293
|
+
if m_fn_store:
|
|
294
|
+
args_code = translate_expr(m_fn_store.group(2) or "")
|
|
295
|
+
stack[-1].append(ast.Assign(targets=[ast.Name(id=m_fn_store.group(3), ctx=ast.Store())], value=make_expr_node(f"{m_fn_store.group(1)}({args_code})"))); continue
|
|
296
|
+
|
|
297
|
+
m_fn = re.match(r"^call ([\w\.]+)(?: with (.+))?$", line, re.IGNORECASE)
|
|
298
|
+
if m_fn:
|
|
299
|
+
args_code = translate_expr(m_fn.group(2) or "")
|
|
300
|
+
stack[-1].append(ast.Expr(value=make_expr_node(f"{m_fn.group(1)}({args_code})"))); continue
|
|
301
|
+
|
|
302
|
+
stack[-1].append(make_stmt_node(line))
|
|
303
|
+
|
|
304
|
+
except Exception as e: raise EpySyntaxError(f"Error on line {line_no} ('{line}'): {str(e)}")
|
|
305
|
+
|
|
306
|
+
if len(stack) > 1: raise EpySyntaxError(f"Unclosed block detected: Missing 'end {block_types[-1]}'.")
|
|
307
|
+
ast.fix_missing_locations(root)
|
|
308
|
+
return root
|
|
309
|
+
|
|
310
|
+
def compile_source(source_text: str) -> str:
|
|
311
|
+
return ast.unparse(build_ast(source_text))
|
|
312
|
+
|
|
313
|
+
def main():
|
|
314
|
+
if len(sys.argv) < 2:
|
|
315
|
+
print("EPY Compiler v1.0.0\nUsage: epy <file.epy> [--run]")
|
|
316
|
+
sys.exit(1)
|
|
317
|
+
|
|
318
|
+
src_path = Path(sys.argv[1])
|
|
319
|
+
py_path = src_path.with_suffix(".py")
|
|
320
|
+
try: py_code = compile_source(src_path.read_text(encoding="utf-8"))
|
|
321
|
+
except EpySyntaxError as e: print(f"Syntax Error: {e}"); sys.exit(1)
|
|
322
|
+
|
|
323
|
+
py_path.write_text(py_code, encoding="utf-8")
|
|
324
|
+
print(f"Compiled successfully: {src_path} -> {py_path}")
|
|
325
|
+
|
|
326
|
+
if "--run" in sys.argv:
|
|
327
|
+
subprocess.run([sys.executable, str(py_path)])
|
|
328
|
+
|
|
329
|
+
if __name__ == "__main__": main()
|
|
330
|
+
|