aergia-lang 1.6.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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nayif Ehan
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,8 @@
1
+ Metadata-Version: 2.4
2
+ Name: aergia-lang
3
+ Version: 1.6.0
4
+ Summary: The Aergia programming language
5
+ Author: las-r
6
+ Requires-Python: >=3.7
7
+ License-File: LICENSE
8
+ Dynamic: license-file
@@ -0,0 +1,34 @@
1
+ # Aergia
2
+ **Aergia** is a minimalist yet still usable programming language, depending on your definition of usable.
3
+
4
+ ## Installation
5
+ ### Global Installation (pip)
6
+ ```bash
7
+ pip install git+https://github.com/las-r/aergia.git
8
+ ```
9
+
10
+ ### Local Installation (in a directory)
11
+ Copy the contents of `/aergia` to your working directory.
12
+
13
+ ## Usage
14
+ ```bash
15
+ aergia <filename.aer>
16
+ ```
17
+ Alternatively (for local installations):
18
+ ```bash
19
+ python -m aergia <filename.aer>
20
+ ```
21
+
22
+ ## Updating
23
+ ### ≥v1.5.0
24
+ ```
25
+ aergia --ghupdate
26
+ ```
27
+
28
+ ### <v1.5.0
29
+ ```
30
+ pip install --upgrade git+https://github.com/las-r/aergia.git
31
+ ```
32
+
33
+ ## Documentation
34
+ Documentation can be found [here](https://las-r.github.io/aergia/).
File without changes
@@ -0,0 +1,80 @@
1
+ import argparse
2
+ import subprocess
3
+ import sys
4
+ from .lexer import tokenize
5
+ from .parser import parse, ExitException
6
+
7
+ # aergia main
8
+ # made by las-r on github
9
+
10
+ def main():
11
+ # constants
12
+ VER = "Aergia v1.6.0"
13
+ REPO = "git+https://github.com/las-r/aergia.git"
14
+
15
+ # arguments
16
+ aparser = argparse.ArgumentParser(
17
+ prog="aergia",
18
+ description="Aergia Language Interpreter"
19
+ )
20
+ aparser.add_argument("filename", nargs="?", help="the .aer file to execute")
21
+ aparser.add_argument("--version", action="version", version=VER)
22
+ aparser.add_argument("-d", "--debug", action="store_true", help="print tokens and abstract syntax tree")
23
+ aparser.add_argument("-gu", "--ghupdate", action="store_true", help="update aergia to the latest version from github")
24
+ args = aparser.parse_args()
25
+
26
+ # handle update
27
+ if args.ghupdate:
28
+ print("Checking for updates...")
29
+ try:
30
+ subprocess.check_call([sys.executable, "-m", "pip", "install", "--upgrade", REPO])
31
+ sys.exit(0)
32
+ except subprocess.CalledProcessError as e:
33
+ print(f"Update failed: {e}")
34
+ sys.exit(1)
35
+
36
+ # global environment
37
+ env = {}
38
+
39
+ try:
40
+ # run file
41
+ if args.filename:
42
+ # read file
43
+ with open(args.filename, "r") as f:
44
+ code = f.read()
45
+
46
+ # create tokens and tree
47
+ tokens = tokenize(code)
48
+ ast = parse(tokens)
49
+
50
+ # debug
51
+ if args.debug:
52
+ print(f"DEBUG - Tokens: {tokens}")
53
+ print(f"DEBUG - AST: {ast}")
54
+
55
+ # interpret
56
+ for node in ast:
57
+ if node:
58
+ node.eval(env)
59
+
60
+ # run repl
61
+ else:
62
+ print(f"{VER} REPL")
63
+ while True:
64
+ line = input(";> ")
65
+ tokens = tokenize(line)
66
+ ast = parse(tokens)
67
+ for node in ast:
68
+ if node:
69
+ print(node.eval(env))
70
+
71
+ except ExitException as e:
72
+ sys.exit(e.value)
73
+ except FileNotFoundError:
74
+ print(f"Aergia Error: File '{args.filename}' not found")
75
+ except Exception as e:
76
+ print(f"Aergia Error: {e}")
77
+
78
+
79
+ if __name__ == "__main__":
80
+ main()
@@ -0,0 +1,20 @@
1
+ import re
2
+
3
+ # aergia lexer
4
+ # made by las-r on github
5
+
6
+ # constants
7
+ TOKENPATTERN = r'"(?:[^"\\]|\\.)*"|==|<<|>>|<=|>=|\+>|\*>|\*<|~>|[\(\)\[\]\{\}\+\-\*\/%\^=;\&\|!\~<>:@\?,.\'`]|\b[\w.]+\b'
8
+
9
+ # preprocessor
10
+ def preprocess(code):
11
+ lines = code.splitlines()
12
+ cleaned = []
13
+ for line in lines:
14
+ cleaned.append(line.split("#")[0])
15
+ return " ".join(cleaned)
16
+
17
+ # tokenizer
18
+ def tokenize(code):
19
+ tokens = re.findall(TOKENPATTERN, preprocess(code))
20
+ return tokens
@@ -0,0 +1,295 @@
1
+ import importlib
2
+ import operator
3
+ from .lexer import tokenize
4
+
5
+ # aergia nodes
6
+ # made by las-r on github
7
+
8
+ # constants
9
+ OPS = {
10
+ "+": lambda l, r: str(l) + str(r) if isinstance(l, str) or isinstance(r, str) else l + r,
11
+ "-": operator.sub,
12
+ "*": operator.mul,
13
+ "/": operator.truediv,
14
+ "^": operator.pow,
15
+ "%": operator.mod,
16
+ "|": operator.or_,
17
+ "&": operator.and_,
18
+ "$": operator.xor,
19
+ "==": lambda l, r: 1 if l == r else 0,
20
+ "!=": lambda l, r: 1 if l != r else 0,
21
+ "<<": lambda l, r: 1 if l < r else 0,
22
+ ">>": lambda l, r: 1 if l > r else 0,
23
+ "<=": lambda l, r: 1 if l <= r else 0,
24
+ ">=": lambda l, r: 1 if l >= r else 0,
25
+ }
26
+
27
+ # exceptions
28
+ class ReturnException(Exception):
29
+ def __init__(self, value):
30
+ self.value = value
31
+
32
+ class ExitException(Exception):
33
+ def __init__(self, value):
34
+ self.value = value
35
+
36
+ # value nodes
37
+ class LiteralNode:
38
+ def __init__(self, value):
39
+ self.value = value
40
+
41
+ def eval(self, env):
42
+ return self.value
43
+
44
+ class VariableNode:
45
+ def __init__(self, name):
46
+ self.name = name
47
+
48
+ def eval(self, env):
49
+ if self.name not in env:
50
+ raise NameError(f"No variable '{self.name}' found")
51
+ return env[self.name]
52
+
53
+ # assignment
54
+ class AssignNode:
55
+ def __init__(self, name, child):
56
+ self.name = name
57
+ self.child = child
58
+
59
+ def eval(self, env):
60
+ value = self.child.eval(env)
61
+ env[self.name] = value
62
+ return value
63
+
64
+ # output
65
+ class OutputNode:
66
+ def __init__(self, child):
67
+ self.child = child
68
+
69
+ def eval(self, env):
70
+ print(self.child.eval(env))
71
+ return 0
72
+
73
+ # input
74
+ class StringInputNode:
75
+ def __init__(self):
76
+ pass
77
+
78
+ def eval(self, env):
79
+ return input()
80
+
81
+ class IntInputNode:
82
+ def __init__(self):
83
+ pass
84
+
85
+ def eval(self, env):
86
+ return int(input())
87
+
88
+ class FloatInputNode:
89
+ def __init__(self):
90
+ pass
91
+
92
+ def eval(self, env):
93
+ return float(input())
94
+
95
+ # operation nodes
96
+ class UnaryOpNode:
97
+ def __init__(self, op, child):
98
+ self.op = op
99
+ self.child = child
100
+
101
+ def eval(self, env):
102
+ v = self.child.eval(env)
103
+ if self.op == "!":
104
+ if not v: return 1
105
+ else: return 0
106
+ elif self.op == "~": return ~v
107
+
108
+ class BinaryOpNode:
109
+ def __init__(self, op, left, right):
110
+ self.op = op
111
+ self.left = left
112
+ self.right = right
113
+
114
+ def eval(self, env):
115
+ vl = self.left.eval(env)
116
+ vr = self.right.eval(env)
117
+ return OPS[self.op](vl, vr)
118
+
119
+ # array nodes
120
+ class ArrayNode:
121
+ def __init__(self, elements):
122
+ self.elements = elements
123
+
124
+ def eval(self, env):
125
+ return [e.eval(env) for e in self.elements]
126
+
127
+ class IndexNode:
128
+ def __init__(self, array_node, index_node):
129
+ self.array_node = array_node
130
+ self.index_node = index_node
131
+
132
+ def eval(self, env):
133
+ arr = self.array_node.eval(env)
134
+ idx = self.index_node.eval(env)
135
+ if not isinstance(arr, list):
136
+ raise TypeError(f"Object of type {type(arr)} is not indexable")
137
+ return arr[int(idx)]
138
+
139
+ # control flow nodes
140
+ class IfNode:
141
+ def __init__(self, cond, mainbody, elsebody=[]):
142
+ self.cond = cond
143
+ self.mainbody = mainbody
144
+ self.elsebody = elsebody
145
+
146
+ def eval(self, env):
147
+ last = 0
148
+ if self.cond.eval(env):
149
+ for node in self.mainbody:
150
+ last = node.eval(env)
151
+ else:
152
+ for node in self.elsebody:
153
+ last = node.eval(env)
154
+ return last
155
+
156
+ class WhileNode:
157
+ def __init__(self, cond, body):
158
+ self.cond = cond
159
+ self.body = body
160
+
161
+ def eval(self, env):
162
+ last = 0
163
+ while self.cond.eval(env):
164
+ for node in self.body:
165
+ last = node.eval(env)
166
+ return last
167
+
168
+ class ForNode:
169
+ def __init__(self, array, iname, body):
170
+ self.array = array
171
+ self.iname = iname
172
+ self.body = body
173
+
174
+ def eval(self, env):
175
+ iterable = self.array.eval(env)
176
+ if not hasattr(iterable, "__iter__"):
177
+ raise TypeError(f"'{type(iterable).__name__}' object is not iterable")
178
+ last = 0
179
+ for item in iterable:
180
+ env[self.iname] = item
181
+ for node in self.body:
182
+ last = node.eval(env)
183
+ return last
184
+
185
+ # function nodes
186
+ class FunctionNode:
187
+ def __init__(self, name, para, body):
188
+ self.name = name
189
+ self.para = para
190
+ self.body = body
191
+
192
+ def eval(self, env):
193
+ env[self.name] = self
194
+ return 0
195
+
196
+ class CallNode:
197
+ def __init__(self, name, args):
198
+ self.name = name
199
+ self.args = args
200
+
201
+ def eval(self, env):
202
+ func = env.get(self.name)
203
+ if not func:
204
+ raise Exception(f"Function {self.name} not defined")
205
+ eargs = [arg.eval(env) for arg in self.args]
206
+ if callable(func):
207
+ return func(*eargs)
208
+ fenv = env.copy()
209
+ for name, val in zip(func.para, eargs):
210
+ fenv[name] = val
211
+ try:
212
+ for node in func.body:
213
+ node.eval(fenv)
214
+ return 0
215
+ except ReturnException as e:
216
+ return e.value
217
+
218
+ class ReturnNode:
219
+ def __init__(self, value):
220
+ self.value = value
221
+
222
+ def eval(self, env):
223
+ raise ReturnException(self.value.eval(env))
224
+
225
+ # import nodes
226
+ class ImportNode:
227
+ def __init__(self, file):
228
+ self.file = file
229
+
230
+ def eval(self, env):
231
+ from .parser import parse
232
+ file = self.file.eval(env)
233
+ if "__imports__" not in env:
234
+ env["__imports__"] = set()
235
+ if file in env["__imports__"]:
236
+ return 0
237
+ env["__imports__"].add(file)
238
+ with open(file, "r") as f:
239
+ code = f.read()
240
+ try:
241
+ tokens = tokenize(code)
242
+ ast = parse(tokens)
243
+ for node in ast:
244
+ if node:
245
+ node.eval(env)
246
+ except Exception as e:
247
+ print(f"Aergia Error ({file}): {e}")
248
+ return 0
249
+
250
+ class PyImportNode:
251
+ def __init__(self, name, rname, closed):
252
+ self.name = name
253
+ self.rname = rname
254
+ self.closed = closed
255
+
256
+ def eval(self, env):
257
+ module = importlib.import_module(self.name)
258
+ for name, value in vars(module).items():
259
+ if not name.startswith("_"):
260
+ if not self.closed:
261
+ env[name] = value
262
+ else:
263
+ env[f"{self.rname}_{name}"] = value
264
+ if isinstance(value, type):
265
+ for sname, sval in vars(value).items():
266
+ if not sname.startswith("_"):
267
+ if not self.closed:
268
+ env[f"{name}_{sname}"] = sval
269
+ else:
270
+ env[f"{self.rname}_{name}_{sname}"] = sval
271
+ return 0
272
+
273
+ # low control nodes
274
+ class EvaluationNode:
275
+ def __init__(self, value):
276
+ self.value = value
277
+
278
+ def eval(self, env):
279
+ from .parser import parse
280
+ code = self.value.eval(env)
281
+ tokens = tokenize(code)
282
+ ast = parse(tokens)
283
+ last = 0
284
+ for node in ast:
285
+ if node:
286
+ last = node.eval(env)
287
+ return last
288
+
289
+ class ExitNode:
290
+ def __init__(self, value):
291
+ self.value = value
292
+
293
+ def eval(self, env):
294
+ exitc = self.value.eval(env)
295
+ raise ExitException(exitc)
@@ -0,0 +1,155 @@
1
+ import re
2
+ from .nodes import *
3
+
4
+ # aergia parser
5
+ # made by las-r on github
6
+
7
+ # expression parser
8
+ def parseexpr(tokens):
9
+ if not tokens:
10
+ return None
11
+ token = tokens.pop(0)
12
+
13
+ # if block
14
+ if token == "(":
15
+ cond = parseexpr(tokens)
16
+ body = []
17
+ while tokens and tokens[0] != ")":
18
+ body.append(parseexpr(tokens))
19
+ if tokens: tokens.pop(0)
20
+ elsebody = []
21
+ if tokens and tokens[0] == "(":
22
+ tokens.pop(0)
23
+ while tokens and tokens[0] != ")":
24
+ elsebody.append(parseexpr(tokens))
25
+ if tokens: tokens.pop(0)
26
+ return IfNode(cond, body, elsebody)
27
+
28
+ # while/for block
29
+ if token == "[":
30
+ if tokens and tokens[0] == "`":
31
+ tokens.pop(0)
32
+ array = parseexpr(tokens)
33
+ iname = tokens.pop(0)
34
+ body = []
35
+ while tokens and tokens[0] != "]":
36
+ body.append(parseexpr(tokens))
37
+ if tokens: tokens.pop(0)
38
+ return ForNode(array, iname, body)
39
+ else:
40
+ cond = parseexpr(tokens)
41
+ body = []
42
+ while tokens and tokens[0] != "]":
43
+ body.append(parseexpr(tokens))
44
+ if tokens: tokens.pop(0)
45
+ return WhileNode(cond, body)
46
+
47
+ # function block
48
+ if token == "{":
49
+ name = tokens.pop(0)
50
+ para = []
51
+ if tokens and tokens[0] == ":":
52
+ tokens.pop(0)
53
+ while tokens and tokens[0] != ":":
54
+ para.append(tokens.pop(0))
55
+ if tokens: tokens.pop(0)
56
+ body = []
57
+ while tokens and tokens[0] != "}":
58
+ body.append(parseexpr(tokens))
59
+ if tokens: tokens.pop(0)
60
+ return FunctionNode(name, para, body)
61
+
62
+ # array definition
63
+ if token == "<":
64
+ elements = []
65
+ while tokens and tokens[0] != ">":
66
+ elements.append(parseexpr(tokens))
67
+ if tokens: tokens.pop(0)
68
+ return ArrayNode(elements)
69
+
70
+ # array index
71
+ if token == ":":
72
+ array = parseexpr(tokens)
73
+ index = parseexpr(tokens)
74
+ return IndexNode(array, index)
75
+
76
+ # exit
77
+ if token == "~>":
78
+ value = parseexpr(tokens)
79
+ return ExitNode(value)
80
+
81
+ # imports
82
+ if token == "+>":
83
+ file = parseexpr(tokens)
84
+ return ImportNode(file)
85
+ if token == "*>":
86
+ name = tokens.pop(0)
87
+ return PyImportNode(name, name, False)
88
+ if token == "*<":
89
+ name = tokens.pop(0)
90
+ rname = tokens.pop(0)
91
+ return PyImportNode(name, rname, True)
92
+
93
+ # evaluation
94
+ if token == ";":
95
+ value = parseexpr(tokens)
96
+ return EvaluationNode(value)
97
+
98
+ # assignments
99
+ if token == "=":
100
+ name = tokens.pop(0)
101
+ value = parseexpr(tokens)
102
+ return AssignNode(name, value)
103
+
104
+ # binary ops
105
+ if token in ("+", "-", "*", "/", "==", "!=", ">>", "<<", ">=", "<=", "^", "%", "&", "|", "$"):
106
+ left = parseexpr(tokens)
107
+ right = parseexpr(tokens)
108
+ return BinaryOpNode(token, left, right)
109
+
110
+ # unary ops and output
111
+ if token in ("!", "~", ">"):
112
+ child = parseexpr(tokens)
113
+ if token == ">": return OutputNode(child)
114
+ return UnaryOpNode(token, child)
115
+
116
+ # input
117
+ if token == ".": return IntInputNode()
118
+ if token == ",": return StringInputNode()
119
+ if token == "'": return FloatInputNode()
120
+
121
+ # function return
122
+ if token == "?": return ReturnNode(parseexpr(tokens))
123
+
124
+ # function call
125
+ if token == "@":
126
+ name = tokens.pop(0)
127
+ args = []
128
+ if tokens and tokens[0] == ":":
129
+ tokens.pop(0)
130
+ while tokens and tokens[0] != ":":
131
+ args.append(parseexpr(tokens))
132
+ if tokens: tokens.pop(0)
133
+ return CallNode(name, args)
134
+
135
+ # string
136
+ if token.startswith('"'):
137
+ content = token[1:-1]
138
+ unescaped = re.sub(r'\\(.)', r'\1', content)
139
+ return LiteralNode(unescaped)
140
+
141
+ # number and variable
142
+ try:
143
+ val = float(token)
144
+ return LiteralNode(int(val) if val.is_integer() else val)
145
+ except ValueError:
146
+ return VariableNode(token)
147
+
148
+ # tree maker
149
+ def parse(tokens):
150
+ ast = []
151
+ while tokens:
152
+ node = parseexpr(tokens)
153
+ if node:
154
+ ast.append(node)
155
+ return ast
@@ -0,0 +1,8 @@
1
+ Metadata-Version: 2.4
2
+ Name: aergia-lang
3
+ Version: 1.6.0
4
+ Summary: The Aergia programming language
5
+ Author: las-r
6
+ Requires-Python: >=3.7
7
+ License-File: LICENSE
8
+ Dynamic: license-file
@@ -0,0 +1,13 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ aergia/__init__.py
5
+ aergia/__main__.py
6
+ aergia/lexer.py
7
+ aergia/nodes.py
8
+ aergia/parser.py
9
+ aergia_lang.egg-info/PKG-INFO
10
+ aergia_lang.egg-info/SOURCES.txt
11
+ aergia_lang.egg-info/dependency_links.txt
12
+ aergia_lang.egg-info/entry_points.txt
13
+ aergia_lang.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ aergia = aergia.__main__:main
@@ -0,0 +1 @@
1
+ aergia
@@ -0,0 +1,17 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "aergia-lang"
7
+ version = "1.6.0"
8
+ description = "The Aergia programming language"
9
+ authors = [{ name = "las-r" }]
10
+ dependencies = []
11
+ requires-python = ">=3.7"
12
+
13
+ [project.scripts]
14
+ aergia = "aergia.__main__:main"
15
+
16
+ [tool.setuptools.packages.find]
17
+ include = ["aergia*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+