the-pyro-lang 0.1.4__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.
- the_pyro_lang-0.1.4/LICENSE +21 -0
- the_pyro_lang-0.1.4/MANIFEST.in +3 -0
- the_pyro_lang-0.1.4/PKG-INFO +127 -0
- the_pyro_lang-0.1.4/README.md +108 -0
- the_pyro_lang-0.1.4/examples/class.pyro +12 -0
- the_pyro_lang-0.1.4/examples/fibonacci.pyro +12 -0
- the_pyro_lang-0.1.4/examples/hello.pyro +17 -0
- the_pyro_lang-0.1.4/pyproject.toml +29 -0
- the_pyro_lang-0.1.4/pyro/__init__.py +8 -0
- the_pyro_lang-0.1.4/pyro/__main__.py +4 -0
- the_pyro_lang-0.1.4/pyro/ast_nodes.py +94 -0
- the_pyro_lang-0.1.4/pyro/cli.py +48 -0
- the_pyro_lang-0.1.4/pyro/compiler.py +27 -0
- the_pyro_lang-0.1.4/pyro/error_utils.py +21 -0
- the_pyro_lang-0.1.4/pyro/lexer.py +75 -0
- the_pyro_lang-0.1.4/pyro/parser.py +308 -0
- the_pyro_lang-0.1.4/pyro/transformer.py +153 -0
- the_pyro_lang-0.1.4/setup.cfg +4 -0
- the_pyro_lang-0.1.4/setup.py +36 -0
- the_pyro_lang-0.1.4/tests/test_integration.py +140 -0
- the_pyro_lang-0.1.4/tests/test_lexer.py +87 -0
- the_pyro_lang-0.1.4/tests/test_parser.py +126 -0
- the_pyro_lang-0.1.4/the_pyro_lang.egg-info/PKG-INFO +127 -0
- the_pyro_lang-0.1.4/the_pyro_lang.egg-info/SOURCES.txt +25 -0
- the_pyro_lang-0.1.4/the_pyro_lang.egg-info/dependency_links.txt +1 -0
- the_pyro_lang-0.1.4/the_pyro_lang.egg-info/entry_points.txt +2 -0
- the_pyro_lang-0.1.4/the_pyro_lang.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Pyro Contributors
|
|
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 DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: the-pyro-lang
|
|
3
|
+
Version: 0.1.4
|
|
4
|
+
Summary: A friendlier Python that compiles to Python
|
|
5
|
+
Home-page: https://github.com/pyro-lang/pyro
|
|
6
|
+
Author: Pyro Contributors
|
|
7
|
+
License: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/pyro-lang/pyro
|
|
9
|
+
Project-URL: Bug Reports, https://github.com/pyro-lang/pyro/issues
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Requires-Python: >=3.10
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Dynamic: home-page
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
Dynamic: requires-python
|
|
19
|
+
|
|
20
|
+
# Pyro – A friendlier Python
|
|
21
|
+
|
|
22
|
+
Pyro is a programming language that feels like Python but removes the rough edges: no indentation errors, no `self`, no mandatory `print()` parentheses, and blocks end with `end`.
|
|
23
|
+
|
|
24
|
+
It **compiles to standard Python** – so you can use any Python library (NumPy, Django, TensorFlow) without change.
|
|
25
|
+
|
|
26
|
+
## Why Pyro?
|
|
27
|
+
|
|
28
|
+
| Feature | Python | Pyro |
|
|
29
|
+
|---------|--------|------|
|
|
30
|
+
| Blocks | Indentation sensitive | `if ... end` (no indentation errors) |
|
|
31
|
+
| Function definition | `def` | `func` |
|
|
32
|
+
| Method first argument | `self` | `this` (shorter) |
|
|
33
|
+
| `print` | `print("hello")` | `print "hello"` (or with parens) |
|
|
34
|
+
| Colons after headers | Required | Optional |
|
|
35
|
+
| Variable assignment | `x = 1` | `make x = 1` or `x = 1` |
|
|
36
|
+
| Ranges | `range(1,6)` | `1..5` (inclusive) |
|
|
37
|
+
|
|
38
|
+
## Example
|
|
39
|
+
|
|
40
|
+
```pyro
|
|
41
|
+
print "Hello from Pyro!"
|
|
42
|
+
|
|
43
|
+
make x = 10
|
|
44
|
+
y = 20
|
|
45
|
+
print "Sum: " + str(x + y)
|
|
46
|
+
|
|
47
|
+
if x > 5
|
|
48
|
+
print "x is large"
|
|
49
|
+
else
|
|
50
|
+
print "x is small"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
func greet(name)
|
|
54
|
+
print "Hello, " + name
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
greet("world")
|
|
58
|
+
|
|
59
|
+
for i in 1..5
|
|
60
|
+
print i
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
class Person
|
|
64
|
+
constructor(name)
|
|
65
|
+
this.name = name
|
|
66
|
+
end
|
|
67
|
+
func say_hello()
|
|
68
|
+
print "My name is " + this.name
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
p = Person("Pyro")
|
|
73
|
+
p.say_hello()
|
|
74
|
+
```
|
|
75
|
+
## Installation
|
|
76
|
+
```
|
|
77
|
+
pip install pyro-lang
|
|
78
|
+
```
|
|
79
|
+
## Usage
|
|
80
|
+
|
|
81
|
+
Compile a .pyro file to Python:
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
pyro compile input.pyro -o output.py
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Run directly:
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
pyro run input.pyro
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Or use as a Python module:
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
from pyro import compile_pyro
|
|
97
|
+
py_code = compile_pyro(source)
|
|
98
|
+
exec(py_code)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Roadmap
|
|
102
|
+
|
|
103
|
+
MVP (variables, arithmetic, if/else, loops, functions, classes)
|
|
104
|
+
|
|
105
|
+
Compile to Python source
|
|
106
|
+
|
|
107
|
+
Import system
|
|
108
|
+
|
|
109
|
+
Exception handling (try/catch)
|
|
110
|
+
|
|
111
|
+
List/dict comprehensions
|
|
112
|
+
|
|
113
|
+
Standalone bytecode compiler (direct .pyc output)
|
|
114
|
+
|
|
115
|
+
Self‑hosting (compiler written in Pyro itself)
|
|
116
|
+
|
|
117
|
+
## Contributing
|
|
118
|
+
|
|
119
|
+
See CONTRIBUTING.md (to be added). For now, open issues or PRs on GitHub.
|
|
120
|
+
License
|
|
121
|
+
|
|
122
|
+
MIT – use it anywhere, even in commercial projects.
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Pyro – A friendlier Python
|
|
2
|
+
|
|
3
|
+
Pyro is a programming language that feels like Python but removes the rough edges: no indentation errors, no `self`, no mandatory `print()` parentheses, and blocks end with `end`.
|
|
4
|
+
|
|
5
|
+
It **compiles to standard Python** – so you can use any Python library (NumPy, Django, TensorFlow) without change.
|
|
6
|
+
|
|
7
|
+
## Why Pyro?
|
|
8
|
+
|
|
9
|
+
| Feature | Python | Pyro |
|
|
10
|
+
|---------|--------|------|
|
|
11
|
+
| Blocks | Indentation sensitive | `if ... end` (no indentation errors) |
|
|
12
|
+
| Function definition | `def` | `func` |
|
|
13
|
+
| Method first argument | `self` | `this` (shorter) |
|
|
14
|
+
| `print` | `print("hello")` | `print "hello"` (or with parens) |
|
|
15
|
+
| Colons after headers | Required | Optional |
|
|
16
|
+
| Variable assignment | `x = 1` | `make x = 1` or `x = 1` |
|
|
17
|
+
| Ranges | `range(1,6)` | `1..5` (inclusive) |
|
|
18
|
+
|
|
19
|
+
## Example
|
|
20
|
+
|
|
21
|
+
```pyro
|
|
22
|
+
print "Hello from Pyro!"
|
|
23
|
+
|
|
24
|
+
make x = 10
|
|
25
|
+
y = 20
|
|
26
|
+
print "Sum: " + str(x + y)
|
|
27
|
+
|
|
28
|
+
if x > 5
|
|
29
|
+
print "x is large"
|
|
30
|
+
else
|
|
31
|
+
print "x is small"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
func greet(name)
|
|
35
|
+
print "Hello, " + name
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
greet("world")
|
|
39
|
+
|
|
40
|
+
for i in 1..5
|
|
41
|
+
print i
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class Person
|
|
45
|
+
constructor(name)
|
|
46
|
+
this.name = name
|
|
47
|
+
end
|
|
48
|
+
func say_hello()
|
|
49
|
+
print "My name is " + this.name
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
p = Person("Pyro")
|
|
54
|
+
p.say_hello()
|
|
55
|
+
```
|
|
56
|
+
## Installation
|
|
57
|
+
```
|
|
58
|
+
pip install pyro-lang
|
|
59
|
+
```
|
|
60
|
+
## Usage
|
|
61
|
+
|
|
62
|
+
Compile a .pyro file to Python:
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
pyro compile input.pyro -o output.py
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Run directly:
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
pyro run input.pyro
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Or use as a Python module:
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
from pyro import compile_pyro
|
|
78
|
+
py_code = compile_pyro(source)
|
|
79
|
+
exec(py_code)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Roadmap
|
|
83
|
+
|
|
84
|
+
MVP (variables, arithmetic, if/else, loops, functions, classes)
|
|
85
|
+
|
|
86
|
+
Compile to Python source
|
|
87
|
+
|
|
88
|
+
Import system
|
|
89
|
+
|
|
90
|
+
Exception handling (try/catch)
|
|
91
|
+
|
|
92
|
+
List/dict comprehensions
|
|
93
|
+
|
|
94
|
+
Standalone bytecode compiler (direct .pyc output)
|
|
95
|
+
|
|
96
|
+
Self‑hosting (compiler written in Pyro itself)
|
|
97
|
+
|
|
98
|
+
## Contributing
|
|
99
|
+
|
|
100
|
+
See CONTRIBUTING.md (to be added). For now, open issues or PRs on GitHub.
|
|
101
|
+
License
|
|
102
|
+
|
|
103
|
+
MIT – use it anywhere, even in commercial projects.
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
print "Welcome to Pyro!"
|
|
2
|
+
|
|
3
|
+
make name = "world"
|
|
4
|
+
print "Hello, " + name
|
|
5
|
+
|
|
6
|
+
x = 10
|
|
7
|
+
if x > 5
|
|
8
|
+
print "x is greater than 5"
|
|
9
|
+
else
|
|
10
|
+
print "x is small"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
func greet(person)
|
|
14
|
+
print "Greetings, " + person
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
greet("Pyro user")
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "the-pyro-lang"
|
|
7
|
+
version = "0.1.4"
|
|
8
|
+
description = "A friendlier Python that compiles to Python"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
authors = [{name = "Pyro Contributors"}]
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"License :: OSI Approved :: MIT License",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
]
|
|
18
|
+
dependencies = []
|
|
19
|
+
|
|
20
|
+
[project.scripts]
|
|
21
|
+
pyro = "pyro.cli:main"
|
|
22
|
+
|
|
23
|
+
[project.urls]
|
|
24
|
+
Homepage = "https://github.com/pyro-lang/pyro"
|
|
25
|
+
"Bug Reports" = "https://github.com/pyro-lang/pyro/issues"
|
|
26
|
+
|
|
27
|
+
[tool.setuptools.packages.find]
|
|
28
|
+
where = ["."]
|
|
29
|
+
include = ["pyro*"]
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
class ASTNode:
|
|
2
|
+
pass
|
|
3
|
+
|
|
4
|
+
class Program(ASTNode):
|
|
5
|
+
def __init__(self, statements):
|
|
6
|
+
self.statements = statements
|
|
7
|
+
|
|
8
|
+
class FuncDef(ASTNode):
|
|
9
|
+
def __init__(self, name, params, body):
|
|
10
|
+
self.name = name
|
|
11
|
+
self.params = params
|
|
12
|
+
self.body = body
|
|
13
|
+
|
|
14
|
+
class ClassDef(ASTNode):
|
|
15
|
+
def __init__(self, name, body):
|
|
16
|
+
self.name = name
|
|
17
|
+
self.body = body
|
|
18
|
+
|
|
19
|
+
class Constructor(ASTNode):
|
|
20
|
+
def __init__(self, params, body):
|
|
21
|
+
self.params = params
|
|
22
|
+
self.body = body
|
|
23
|
+
|
|
24
|
+
class IfStmt(ASTNode):
|
|
25
|
+
def __init__(self, cond, then_body, else_body=None):
|
|
26
|
+
self.cond = cond
|
|
27
|
+
self.then_body = then_body
|
|
28
|
+
self.else_body = else_body
|
|
29
|
+
|
|
30
|
+
class ForStmt(ASTNode):
|
|
31
|
+
def __init__(self, var, iterable, body):
|
|
32
|
+
self.var = var
|
|
33
|
+
self.iterable = iterable
|
|
34
|
+
self.body = body
|
|
35
|
+
|
|
36
|
+
class WhileStmt(ASTNode):
|
|
37
|
+
def __init__(self, cond, body):
|
|
38
|
+
self.cond = cond
|
|
39
|
+
self.body = body
|
|
40
|
+
|
|
41
|
+
class Assign(ASTNode):
|
|
42
|
+
def __init__(self, target, value):
|
|
43
|
+
self.target = target
|
|
44
|
+
self.value = value
|
|
45
|
+
|
|
46
|
+
class MemberAssign(ASTNode):
|
|
47
|
+
def __init__(self, obj, attr, value):
|
|
48
|
+
self.obj = obj
|
|
49
|
+
self.attr = attr
|
|
50
|
+
self.value = value
|
|
51
|
+
|
|
52
|
+
class MemberAccess(ASTNode):
|
|
53
|
+
def __init__(self, obj, attr):
|
|
54
|
+
self.obj = obj
|
|
55
|
+
self.attr = attr
|
|
56
|
+
|
|
57
|
+
class ExprStmt(ASTNode):
|
|
58
|
+
def __init__(self, expr):
|
|
59
|
+
self.expr = expr
|
|
60
|
+
|
|
61
|
+
class Return(ASTNode):
|
|
62
|
+
def __init__(self, value):
|
|
63
|
+
self.value = value
|
|
64
|
+
|
|
65
|
+
class BinOp(ASTNode):
|
|
66
|
+
def __init__(self, op, left, right):
|
|
67
|
+
self.op = op
|
|
68
|
+
self.left = left
|
|
69
|
+
self.right = right
|
|
70
|
+
|
|
71
|
+
class Number(ASTNode):
|
|
72
|
+
def __init__(self, value):
|
|
73
|
+
self.value = value
|
|
74
|
+
|
|
75
|
+
class String(ASTNode):
|
|
76
|
+
def __init__(self, value):
|
|
77
|
+
self.value = value
|
|
78
|
+
|
|
79
|
+
class Var(ASTNode):
|
|
80
|
+
def __init__(self, name):
|
|
81
|
+
self.name = name
|
|
82
|
+
|
|
83
|
+
class This(ASTNode):
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
class Range(ASTNode):
|
|
87
|
+
def __init__(self, start, end):
|
|
88
|
+
self.start = start
|
|
89
|
+
self.end = end
|
|
90
|
+
|
|
91
|
+
class Call(ASTNode):
|
|
92
|
+
def __init__(self, func, args):
|
|
93
|
+
self.func = func
|
|
94
|
+
self.args = args
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import sys
|
|
3
|
+
import subprocess
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from .compiler import compile_file
|
|
6
|
+
|
|
7
|
+
def main():
|
|
8
|
+
parser = argparse.ArgumentParser(description="Pyro compiler")
|
|
9
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
10
|
+
|
|
11
|
+
compile_parser = subparsers.add_parser("compile", help="Compile .pyro to .py")
|
|
12
|
+
compile_parser.add_argument("input", help=".pyro source file")
|
|
13
|
+
compile_parser.add_argument("-o", "--output", help="output .py file")
|
|
14
|
+
|
|
15
|
+
run_parser = subparsers.add_parser("run", help="Compile and run")
|
|
16
|
+
run_parser.add_argument("input", help=".pyro source file")
|
|
17
|
+
|
|
18
|
+
args = parser.parse_args()
|
|
19
|
+
|
|
20
|
+
if args.command == "compile":
|
|
21
|
+
input_path = Path(args.input)
|
|
22
|
+
if not input_path.exists():
|
|
23
|
+
print(f"Error: file {input_path} not found", file=sys.stderr)
|
|
24
|
+
sys.exit(1)
|
|
25
|
+
output_path = args.output if args.output else input_path.with_suffix(".py")
|
|
26
|
+
try:
|
|
27
|
+
compile_file(input_path, output_path)
|
|
28
|
+
print(f"Compiled {input_path} -> {output_path}")
|
|
29
|
+
except SyntaxError as e:
|
|
30
|
+
print(f"SyntaxError: {e}", file=sys.stderr)
|
|
31
|
+
sys.exit(1)
|
|
32
|
+
|
|
33
|
+
elif args.command == "run":
|
|
34
|
+
input_path = Path(args.input)
|
|
35
|
+
if not input_path.exists():
|
|
36
|
+
print(f"Error: file {input_path} not found", file=sys.stderr)
|
|
37
|
+
sys.exit(1)
|
|
38
|
+
try:
|
|
39
|
+
py_code = compile_file(input_path, None)
|
|
40
|
+
# Execute with a shared namespace so recursive functions work
|
|
41
|
+
exec_globals = {"__builtins__": __builtins__}
|
|
42
|
+
exec(py_code, exec_globals)
|
|
43
|
+
except SyntaxError as e:
|
|
44
|
+
print(f"SyntaxError: {e}", file=sys.stderr)
|
|
45
|
+
sys.exit(1)
|
|
46
|
+
|
|
47
|
+
if __name__ == "__main__":
|
|
48
|
+
main()
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from .lexer import tokenize
|
|
2
|
+
from .parser import Parser, ParserError
|
|
3
|
+
from .transformer import PyTransformer
|
|
4
|
+
|
|
5
|
+
def compile_pyro(source):
|
|
6
|
+
try:
|
|
7
|
+
tokens = tokenize(source)
|
|
8
|
+
parser = Parser(tokens)
|
|
9
|
+
ast = parser.parse()
|
|
10
|
+
transformer = PyTransformer()
|
|
11
|
+
py_code = transformer.transform(ast)
|
|
12
|
+
return py_code
|
|
13
|
+
except ParserError as e:
|
|
14
|
+
# Already has suggestions embedded
|
|
15
|
+
raise e
|
|
16
|
+
except Exception as e:
|
|
17
|
+
# Wrap unexpected errors
|
|
18
|
+
raise SyntaxError(f"Compilation error: {str(e)}") from e
|
|
19
|
+
|
|
20
|
+
def compile_file(input_path, output_path=None):
|
|
21
|
+
with open(input_path, 'r', encoding='utf-8') as f:
|
|
22
|
+
source = f.read()
|
|
23
|
+
py_code = compile_pyro(source)
|
|
24
|
+
if output_path:
|
|
25
|
+
with open(output_path, 'w', encoding='utf-8') as f:
|
|
26
|
+
f.write(py_code)
|
|
27
|
+
return py_code
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
def suggest_fixes(error_msg, source_line=None):
|
|
2
|
+
"""Suggest probable fixes based on error message and source line."""
|
|
3
|
+
suggestions = []
|
|
4
|
+
if "end" in error_msg.lower() and "expected" in error_msg.lower():
|
|
5
|
+
suggestions.append("Missing 'end' keyword – add 'end' after the block.")
|
|
6
|
+
if "indent" in error_msg.lower():
|
|
7
|
+
suggestions.append("Indentation error: Use spaces, not tabs. Each level = 4 spaces.")
|
|
8
|
+
if "unexpected token" in error_msg.lower() and source_line:
|
|
9
|
+
if "end" in source_line:
|
|
10
|
+
suggestions.append("Redundant 'end' – remove the extra 'end'.")
|
|
11
|
+
if "else" in source_line:
|
|
12
|
+
suggestions.append("'else' must be at the same indentation level as 'if'.")
|
|
13
|
+
if "NameError" in error_msg or "not defined" in error_msg:
|
|
14
|
+
suggestions.append("Variable/function used before definition. Check spelling and order.")
|
|
15
|
+
if "constructor" in error_msg.lower():
|
|
16
|
+
suggestions.append("'constructor' can only be used inside a 'class' block.")
|
|
17
|
+
if source_line and ".." in source_line:
|
|
18
|
+
suggestions.append("Range '..' works only with numbers: e.g., '1..5'.")
|
|
19
|
+
if not suggestions:
|
|
20
|
+
suggestions.append("Check parentheses, quotes, and block termination ('end').")
|
|
21
|
+
return suggestions
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
KEYWORDS = {
|
|
4
|
+
'func', 'class', 'if', 'else', 'elif', 'for', 'while', 'return',
|
|
5
|
+
'constructor', 'and', 'or', 'not', 'True', 'False',
|
|
6
|
+
'None', 'import', 'from', 'as', 'try', 'except', 'finally',
|
|
7
|
+
'raise', 'with', 'yield', 'end', 'make', 'print', 'in'
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
# Multi-char operators must come before single-char to avoid greedy mismatches
|
|
11
|
+
TOKEN_REGEX = re.compile(
|
|
12
|
+
r'#[^\n]*|' # comments
|
|
13
|
+
r'"[^"\\]*(\\.[^"\\]*)*"|' # double-quoted strings
|
|
14
|
+
r"'[^'\\]*(\\.[^'\\]*)*'|" # single-quoted strings
|
|
15
|
+
r'\d+\.\d+|' # float literals
|
|
16
|
+
r'\d+|' # integer literals
|
|
17
|
+
r'\.\.|' # range operator
|
|
18
|
+
r'[a-zA-Z_][a-zA-Z0-9_]*|' # identifiers/keywords
|
|
19
|
+
r'==|!=|<=|>=|\*\*|//|' # multi-char operators (must be before single-char)
|
|
20
|
+
r'[+\-*/%=<>]|' # single-char operators
|
|
21
|
+
r'[(){}[\],;.:]|' # punctuation
|
|
22
|
+
r'\n|' # newlines
|
|
23
|
+
r'\S' # catch-all
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def tokenize(source):
|
|
28
|
+
tokens = []
|
|
29
|
+
line = 1
|
|
30
|
+
col = 1
|
|
31
|
+
for match in TOKEN_REGEX.finditer(source):
|
|
32
|
+
tok = match.group()
|
|
33
|
+
start_line = line
|
|
34
|
+
start_col = col
|
|
35
|
+
# Update line/col for newlines
|
|
36
|
+
if tok == '\n':
|
|
37
|
+
line += 1
|
|
38
|
+
col = 1
|
|
39
|
+
continue
|
|
40
|
+
# Skip comments
|
|
41
|
+
if tok.startswith('#'):
|
|
42
|
+
col += len(tok)
|
|
43
|
+
continue
|
|
44
|
+
# Classify token
|
|
45
|
+
if tok == 'this':
|
|
46
|
+
typ = 'THIS'
|
|
47
|
+
elif tok in ('and', 'or', 'not'):
|
|
48
|
+
typ = 'OPERATOR'
|
|
49
|
+
elif tok in ('True', 'False', 'None'):
|
|
50
|
+
typ = 'KEYWORD'
|
|
51
|
+
elif tok in KEYWORDS:
|
|
52
|
+
typ = 'KEYWORD'
|
|
53
|
+
elif re.fullmatch(r'\d+\.\d+', tok):
|
|
54
|
+
typ = 'FLOAT'
|
|
55
|
+
tok = float(tok)
|
|
56
|
+
elif tok.isdigit() or re.fullmatch(r'\d+', tok):
|
|
57
|
+
typ = 'NUMBER'
|
|
58
|
+
tok = int(tok)
|
|
59
|
+
elif tok == '..':
|
|
60
|
+
typ = 'RANGE'
|
|
61
|
+
elif tok in ('+', '-', '*', '/', '%', '=', '==', '!=', '<', '>', '<=', '>=',
|
|
62
|
+
'//', '**', '@'):
|
|
63
|
+
typ = 'OPERATOR'
|
|
64
|
+
elif tok in '(){}[],;.:':
|
|
65
|
+
typ = 'PUNCT'
|
|
66
|
+
elif tok.isidentifier():
|
|
67
|
+
typ = 'IDENT'
|
|
68
|
+
elif tok.startswith('"') or tok.startswith("'"):
|
|
69
|
+
typ = 'STRING'
|
|
70
|
+
tok = eval(tok) # safe for simple string literals
|
|
71
|
+
else:
|
|
72
|
+
typ = 'UNKNOWN'
|
|
73
|
+
tokens.append((typ, tok, start_line, start_col))
|
|
74
|
+
col += len(match.group())
|
|
75
|
+
return tokens
|