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.
@@ -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,3 @@
1
+ include README.md
2
+ include LICENSE
3
+ recursive-include examples *.pyro
@@ -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,12 @@
1
+ class Person
2
+ constructor(name, age)
3
+ this.name = name
4
+ this.age = age
5
+ end
6
+ func introduce()
7
+ print "I'm " + this.name + ", age " + str(this.age)
8
+ end
9
+ end
10
+
11
+ p = Person("Pyro", 1)
12
+ p.introduce()
@@ -0,0 +1,12 @@
1
+ func fib(n)
2
+ if n <= 1
3
+ return n
4
+ else
5
+ return fib(n-1) + fib(n-2)
6
+ end
7
+ end
8
+
9
+ print "Fibonacci sequence:"
10
+ for i in 0..10
11
+ print fib(i)
12
+ end
@@ -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,8 @@
1
+ import sys
2
+ from .compiler import compile_pyro, compile_file
3
+ from .cli import main
4
+
5
+ if sys.version_info < (3, 10):
6
+ raise ImportError("Pyro requires Python 3.10 or higher")
7
+ __version__ = "0.1.3"
8
+ __all__ = ["compile_pyro", "compile_file", "main"]
@@ -0,0 +1,4 @@
1
+ from .cli import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
@@ -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