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.
- aergia_lang-1.6.0/LICENSE +21 -0
- aergia_lang-1.6.0/PKG-INFO +8 -0
- aergia_lang-1.6.0/README.md +34 -0
- aergia_lang-1.6.0/aergia/__init__.py +0 -0
- aergia_lang-1.6.0/aergia/__main__.py +80 -0
- aergia_lang-1.6.0/aergia/lexer.py +20 -0
- aergia_lang-1.6.0/aergia/nodes.py +295 -0
- aergia_lang-1.6.0/aergia/parser.py +155 -0
- aergia_lang-1.6.0/aergia_lang.egg-info/PKG-INFO +8 -0
- aergia_lang-1.6.0/aergia_lang.egg-info/SOURCES.txt +13 -0
- aergia_lang-1.6.0/aergia_lang.egg-info/dependency_links.txt +1 -0
- aergia_lang-1.6.0/aergia_lang.egg-info/entry_points.txt +2 -0
- aergia_lang-1.6.0/aergia_lang.egg-info/top_level.txt +1 -0
- aergia_lang-1.6.0/pyproject.toml +17 -0
- aergia_lang-1.6.0/setup.cfg +4 -0
|
@@ -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,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,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 @@
|
|
|
1
|
+
|
|
@@ -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*"]
|