hexus-lang 0.1.0b1__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,57 @@
1
+ import sys
2
+ import os
3
+ from Hexus.lexer import tokenizer_tokens
4
+ from Hexus.parser import HexusParser
5
+ from Hexus.interpreter import HexusInterpreter
6
+
7
+
8
+ def main():
9
+ if len(sys.argv) < 2:
10
+ print("Hexus Programming Language")
11
+ print("Use: hexus <file.he>")
12
+ sys.exit(1)
13
+
14
+ file_name = sys.argv[1]
15
+
16
+ if file_name in ["--help", "-h"]:
17
+ print("Hexus Programming Language CLI")
18
+ print("Use: hexus <file.he>")
19
+ sys.exit(0)
20
+
21
+ if not os.path.exists(file_name):
22
+ print(f"Error: File '{file_name}' does not exist!")
23
+ sys.exit(1)
24
+
25
+ try:
26
+ with open(file_name, "r", encoding="utf-8") as file:
27
+ source_code = file.read()
28
+ except Exception as e:
29
+ print(f"Error while openning file: {e}")
30
+ sys.exit(1)
31
+
32
+ try:
33
+ token_list = tokenizer_tokens(source_code)
34
+ clear_tokens = [t for t in token_list if t[0] != "SKIP"]
35
+
36
+ parser = HexusParser(clear_tokens)
37
+ program_tree = parser.parse()
38
+
39
+ interpreter = HexusInterpreter()
40
+ interpreter.interpret(program_tree)
41
+
42
+ except SyntaxError as e:
43
+ print(f"\n{e}")
44
+ sys.exit(1)
45
+ except NameError as e:
46
+ print(f"\n\n{e}")
47
+ sys.exit(1)
48
+ except KeyboardInterrupt:
49
+ print("\nThe program's operation was interrupted (Ctrl+C).")
50
+ sys.exit(0)
51
+ except Exception as e:
52
+ print(f"\nUnexpected error:\n{e}")
53
+ sys.exit(1)
54
+
55
+
56
+ if __name__ == "__main__":
57
+ main()
@@ -0,0 +1,515 @@
1
+ from Hexus.parser import FunctionDefNode
2
+ from typing import Any, Callable
3
+ import importlib
4
+ import pkgutil
5
+ from Hexus import modules
6
+
7
+
8
+ class BreakException(Exception):
9
+ pass
10
+
11
+ class ContinueException(Exception):
12
+ pass
13
+
14
+ class ReturnException(Exception):
15
+ def __init__(self, value):
16
+ self.value = value
17
+
18
+ class Environment:
19
+ def __init__(self, parent=None):
20
+ self.vars = {}
21
+ self.parent = parent
22
+
23
+ def get(self, name):
24
+ if name in self.vars:
25
+ return self.vars[name]
26
+ if self.parent:
27
+ return self.parent.get(name)
28
+ raise NameError(f"Variable '{name}' is not defined!!!")
29
+
30
+ def set(self, name, value):
31
+ self.vars[name] = value
32
+
33
+ def __contains__(self, name):
34
+ try:
35
+ self.get(name)
36
+ return True
37
+ except NameError:
38
+ return False
39
+
40
+
41
+
42
+ class HexusInterpreter:
43
+ def __init__(self):
44
+ self.env = Environment()
45
+ self._register_type1_modules()
46
+ self.timer = 0
47
+ self.timer2 = 0
48
+ self.timerset = False
49
+
50
+ def _register_type1_modules(self):
51
+ for _, mod_name, _ in pkgutil.iter_modules(modules.__path__):
52
+ if mod_name.endswith("_ext"):
53
+ continue
54
+
55
+ py_mod = importlib.import_module(f".modules.{mod_name}", package=__package__)
56
+ for attr_name in dir(py_mod):
57
+ attr = getattr(py_mod, attr_name)
58
+ if attr_name.endswith("Interpreter") and hasattr(attr, "register_handlers"):
59
+ attr.register_handlers(self)
60
+
61
+ def visit(self, node) -> Any:
62
+ method_name = f"visit_{type(node).__name__}"
63
+ visitor: Callable[[Any], Any] = getattr(self, method_name, self.generic_visit)
64
+ return visitor(node)
65
+
66
+ def generic_visit(self, node, *args, **kwargs):
67
+ _ = args, kwargs, self
68
+ raise Exception(f"Interpreter error: No visit method defined for {type(node).__name__}")
69
+
70
+ def visit_NumberNode(self, node):
71
+ _ = self
72
+ value = str(node.value)
73
+ if "." in value:
74
+ return float(value)
75
+ return int(value)
76
+
77
+
78
+ def visit_StringNode(self, node):
79
+ if isinstance(node.value, str):
80
+ words_list = node.value.split()
81
+ else:
82
+ words_list = node.value
83
+
84
+
85
+ if len(words_list) == 1:
86
+ text = words_list[0][1:-1]
87
+ if text.startswith("{") and text.endswith("}"):
88
+ var_name = text[1:-1]
89
+ if var_name in self.env:
90
+ return str(self.env.get(var_name))
91
+ return text
92
+ first = words_list[0][1:]
93
+ middle = words_list[1:-1]
94
+ last = words_list[-1][:-1]
95
+ newtxt = [first] + middle + [last]
96
+ for i, t in enumerate(newtxt):
97
+ if t.startswith("{") and t.endswith("}"):
98
+ t = t[1:-1]
99
+ if t in self.env:
100
+ txt = self.env.get(t)
101
+ newtxt[i] = str(txt)
102
+
103
+ full_txt = " ".join(newtxt)
104
+ return full_txt
105
+
106
+ def visit_VariableNode(self, node):
107
+ if node.name in self.env:
108
+ return self.env.get(node.name)
109
+ raise NameError(f"Variable '{node.name}' is not defined!!!")
110
+
111
+ def visit_NowNode(self, node):
112
+ _ = node, self
113
+ import time
114
+ now = time.strftime("%Y-%m-%d %H:%M:%S")
115
+ return now
116
+
117
+ def visit_BoolNode(self, node):
118
+ _ = self
119
+ return bool(node.value)
120
+
121
+
122
+ def visit_BinaryOpNode(self, node):
123
+ left_val = self.visit(node.left)
124
+ right_val = self.visit(node.right)
125
+
126
+ if node.op == "+": return left_val + right_val
127
+ if node.op == "-": return left_val - right_val
128
+ if node.op == "*": return left_val * right_val
129
+ if node.op == "/": return left_val / right_val
130
+ if node.op == "==": return left_val == right_val
131
+ if node.op == "!=": return left_val != right_val
132
+ if node.op == "and": return bool(left_val) and bool(right_val)
133
+ if node.op == "or": return bool(left_val) or bool(right_val)
134
+ if node.op == ">": return left_val > right_val
135
+ if node.op == "<": return left_val < right_val
136
+ if node.op == "<=": return left_val <= right_val
137
+ if node.op == ">=": return left_val >= right_val
138
+ if node.op == "%": return left_val % right_val
139
+ raise ValueError(f"Unknown binary operator: {node.op}")
140
+
141
+ def visit_SendCommandNode(self, node):
142
+ result = self.visit(node.text_value)
143
+ if node.target == "console":
144
+ print(result)
145
+
146
+ def visit_SetVar(self, node):
147
+ var = node.var_name.strip()
148
+ if node.list is True:
149
+ if node.value is None:
150
+ self.env.set(var, [])
151
+ return
152
+ elif node.value:
153
+ list_val = []
154
+ for v in node.value:
155
+ result = self.visit(v)
156
+ list_val.append(result)
157
+ self.env.set(var, list_val)
158
+ return
159
+ value = self.visit(node.value)
160
+ self.env.set(var, value)
161
+
162
+ def visit_ReadCommandNode(self, node):
163
+ value = self.visit(node.text_value)
164
+ var = node.var_name.strip()
165
+ v = None
166
+ if node.target == "console":
167
+ v = input(value)
168
+
169
+ if v.isdigit():
170
+ v = int(v)
171
+ else:
172
+ try:
173
+ v = float(v)
174
+ except ValueError:
175
+ pass
176
+
177
+ self.env.set(var, v)
178
+
179
+ def visit_ComNode(self, node):
180
+ pass
181
+
182
+ def visit_IfNode(self, node):
183
+ exp = self.visit(node.exp)
184
+ if exp:
185
+ for val in node.value:
186
+ self.visit(val)
187
+ return
188
+ elifv = node.elifv
189
+ if elifv:
190
+ for e_exp, e_value in elifv.items():
191
+ if bool(self.visit(e_exp)):
192
+ for val in e_value:
193
+ self.visit(val)
194
+ return
195
+
196
+ if node.value2:
197
+ for val in node.value2:
198
+ self.visit(val)
199
+
200
+
201
+ def visit_ListAddNode(self, node):
202
+ var_name = node.var.name
203
+ current_value = self.env.get(var_name)
204
+ value = self.visit(node.value)
205
+ if isinstance(current_value, list):
206
+ if not node.pos:
207
+ pos = len(current_value) + 1
208
+ else:
209
+ pos = self.visit(node.pos)
210
+ pos -= 1
211
+ current_value.insert(pos, value)
212
+ self.env.set(var_name, current_value)
213
+ else:
214
+ raise NameError(f"Interpreter Error: Variable '{var_name}' (value: {current_value}) is not a list")
215
+
216
+ def visit_ListRemoveNode(self, node):
217
+ value = None
218
+ var_name = node.var.name
219
+ current_value = self.env.get(var_name)
220
+ if node.value:
221
+ value = self.visit(node.value)
222
+ if isinstance(current_value, list):
223
+ if node.pos:
224
+ pos = self.visit(node.pos)
225
+ pos -= 1
226
+ current_value.pop(pos)
227
+ self.env.set(var_name, current_value)
228
+ elif value:
229
+ current_value.remove(value)
230
+ self.env.set(var_name, current_value)
231
+ else:
232
+ raise NameError(f"Interpreter Error: Variable '{var_name}' (value: {current_value}) is not a list")
233
+
234
+
235
+ def visit_WaitNode(self, node):
236
+ import time
237
+
238
+ value = self.visit(node.value)
239
+
240
+ if hasattr(node.value2, "name"):
241
+ value2 = node.value2.name
242
+ else:
243
+ value2 = self.visit(node.value2)
244
+
245
+ if value2 == "s":
246
+ time.sleep(value)
247
+ elif value2 == "m":
248
+ time.sleep(value * 60)
249
+ elif value2 == "h":
250
+ time.sleep(value * 60 * 60)
251
+ elif value2 == "d":
252
+ time.sleep(value * 60 * 60 * 24)
253
+ else:
254
+ raise ValueError(f"Unknown wait unit: {value2}")
255
+
256
+
257
+ def visit_WhileNode(self, node):
258
+ while bool(self.visit(node.exp)):
259
+ try:
260
+ for val in node.value:
261
+ try:
262
+ self.visit(val)
263
+ except ContinueException:
264
+ break
265
+ except BreakException:
266
+ break
267
+
268
+
269
+ def visit_RepeatNode(self, node):
270
+ value = self.visit(node.value)
271
+ value2 = node.value2
272
+ time = 1
273
+ while time <= value:
274
+ try:
275
+ for val in value2:
276
+ try:
277
+ self.visit(val)
278
+ except ContinueException:
279
+ break
280
+ time += 1
281
+ except BreakException:
282
+ break
283
+
284
+ def visit_ClearNode(self, node):
285
+ _ = node, self
286
+ import subprocess
287
+ import os
288
+ subprocess.run('cls' if os.name == 'nt' else 'clear', shell=True)
289
+
290
+
291
+ def visit_StopNode(self, node):
292
+ if node.value is None:
293
+ value = None
294
+ else:
295
+ value = self.visit(node.value)
296
+ import sys
297
+ if value is None:
298
+ sys.exit("Program stop")
299
+ else:
300
+ sys.exit(value)
301
+
302
+
303
+ def visit_MakeNode(self, node):
304
+ var = node.var.strip()
305
+ value = node.value
306
+ current_str = self.env.get(var)
307
+ if value == "lower":
308
+ self.env.set(var, current_str.lower())
309
+ elif value == "upper":
310
+ self.env.set(var, current_str.upper())
311
+
312
+
313
+ def visit_MinusNode(self, node):
314
+ return -self.visit(node.value)
315
+
316
+
317
+ def visit_PlusNode(self, node):
318
+ return +self.visit(node.value)
319
+
320
+
321
+ def visit_NotNode(self, node):
322
+ value = self.visit(node.value)
323
+ return not value
324
+
325
+
326
+ def visit_LengthNode(self, node):
327
+ var = self.visit(node.var)
328
+ return len(var)
329
+
330
+ def visit_TimeNode(self, node):
331
+ _ = self
332
+ import time
333
+ value = node.value
334
+ if value == "hour":
335
+ return time.strftime("%H")
336
+ elif value == "minute":
337
+ return time.strftime("%M")
338
+ elif value == "second":
339
+ return time.strftime("%S")
340
+ else:
341
+ raise NameError(f"Don't know {value}")
342
+
343
+
344
+ def visit_BreakNode(self, node):
345
+ raise BreakException
346
+
347
+
348
+ def visit_ContinueNode(self, node):
349
+ raise ContinueException
350
+
351
+
352
+ def visit_FunctionDefNode(self, node):
353
+ self.env.set(node.name, node)
354
+
355
+
356
+ def visit_ReturnNode(self, node):
357
+ value = None
358
+ if node.value is not None:
359
+ value = self.visit(node.value)
360
+ raise ReturnException(value)
361
+
362
+
363
+ def visit_FunctionCallNode(self, node):
364
+ func_node = self.env.get(node.name)
365
+ if not isinstance(func_node, FunctionDefNode):
366
+ raise TypeError(f"'{node.name}' is not a function")
367
+ if len(node.args) != len(func_node.para):
368
+ raise TypeError(f"Function '{node.name}' expects {len(func_node.para)} arguments, but got {len(node.args)}")
369
+ arg_values = [self.visit(arg) for arg in node.args]
370
+ previous_env = self.env
371
+ local_env = Environment(parent=previous_env)
372
+ for para_name, val in zip (func_node.para, arg_values):
373
+ local_env.set(para_name, val)
374
+ self.env = local_env
375
+ return_value = None
376
+ try:
377
+ for stmt in func_node.body:
378
+ self.visit(stmt)
379
+ except ReturnException as ret:
380
+ return_value = ret.value
381
+ finally:
382
+ self.env = previous_env
383
+
384
+ return return_value
385
+
386
+
387
+ def visit_StartTimerNode(self, node):
388
+ _ = node
389
+ import time
390
+ self.timer = time.time()
391
+ self.timerset = False
392
+
393
+
394
+ def visit_StopTimerNode(self, node):
395
+ _ = node
396
+ import time
397
+ now = time.time()
398
+ self.timer2 = round(now - self.timer)
399
+ self.timerset = True
400
+
401
+ def visit_TimerNode(self, node):
402
+ _ = node
403
+ if not self.timerset:
404
+ import time
405
+ now = time.time()
406
+ self.timer2 = round(now - self.timer)
407
+ return self.timer2
408
+ else:
409
+ return self.timer2
410
+
411
+
412
+
413
+ def visit_IndexNode(self, node):
414
+ target = self.visit(node.target)
415
+ pos = self.visit(node.pos)
416
+ try:
417
+ return target[pos - 1]
418
+ except Exception:
419
+ raise TypeError(f"{target} is not a list")
420
+
421
+
422
+ def visit_RandomNode(self, node):
423
+ value1 = self.visit(node.value1)
424
+ value2 = self.visit(node.value2)
425
+ import random
426
+ return random.randrange(value1, value2)
427
+
428
+
429
+
430
+ def visit_ForNode(self, node):
431
+ if hasattr(node.value, "name"):
432
+ var_name = node.value.name
433
+ else:
434
+ raise TypeError("???")
435
+ list_value = self.visit(node.list_value)
436
+ had_var = var_name in self.env.vars
437
+ old_value = self.env.vars.get(var_name)
438
+ try:
439
+ for item in list(list_value):
440
+ self.env.set(var_name, item)
441
+ try:
442
+ for stmt in node.value2:
443
+ try:
444
+ self.visit(stmt)
445
+ except ContinueException:
446
+ break
447
+ except BreakException:
448
+ break
449
+ finally:
450
+ if had_var:
451
+ self.env.vars[var_name] = old_value
452
+ else:
453
+ self.env.vars.pop(var_name, None)
454
+
455
+
456
+
457
+ def visit_VarPlusNode(self, node):
458
+ value = node.value
459
+ var = node.var.strip()
460
+ old_value = self.env.vars[var]
461
+ value = old_value + int(value)
462
+ self.env.vars[var] = value
463
+
464
+
465
+
466
+
467
+ def visit_VarMinusNode(self, node):
468
+ value = node.value
469
+ var = node.var.strip()
470
+ old_value = self.env.vars[var]
471
+ value = old_value - int(value)
472
+ self.env.vars[var] = value
473
+
474
+
475
+
476
+ def visit_NumberForNode(self, node):
477
+ if hasattr(node.var, "name"):
478
+ var_name = node.var.name
479
+ else:
480
+ raise TypeError("???")
481
+ start_value = self.visit(node.value)
482
+ end_value = self.visit(node.value2)
483
+ had_var = var_name in self.env.vars
484
+ old_value = self.env.vars.get(var_name)
485
+ try:
486
+ for item in range(start_value, end_value):
487
+ self.env.set(var_name, item)
488
+
489
+ try:
490
+ for stmt in node.block:
491
+ try:
492
+ self.visit(stmt)
493
+ except ContinueException:
494
+ break
495
+ except BreakException:
496
+ break
497
+ finally:
498
+ if had_var:
499
+ self.env.vars[var_name] = old_value
500
+ else:
501
+ self.env.vars.pop(var_name, None)
502
+
503
+
504
+
505
+
506
+
507
+
508
+
509
+
510
+
511
+
512
+
513
+ def interpret(self, nodes):
514
+ for node in nodes:
515
+ self.visit(node)
@@ -0,0 +1,42 @@
1
+ import re
2
+
3
+ token_pattern = [
4
+ ("HASH", r"#"),
5
+ ("INT", r"\d+"),
6
+ ("BOOL", r"\b(True|False)\b"),
7
+ ("DASHS", r"\/\/"),
8
+ ("OP", r">=|<=|==|!=|%|[+\-=*/><]"),
9
+ ("STRING", r'"[^"\\]*(?:\\.[^"\\]*)*"'),
10
+ ("VAR", r'[a-zA-Z_][a-zA-Z0-9_]*'),
11
+ ("LBRACE", r'\{'),
12
+ ("RBRACE", r'\}'),
13
+ ("LSBRACE", r"\["),
14
+ ("RSBRACE", r"\]"),
15
+ ("SKIP", r'[ \t]+'),
16
+ ("NEWLINE", r'\n'),
17
+ ("COMMA", r"\,"),
18
+ ("DOT", r"\."),
19
+ ("LPAREN", r"\("),
20
+ ("RPAREN", r"\)")
21
+ ]
22
+
23
+ def tokenizer(text, definition):
24
+ patter = []
25
+ for name, regex in definition:
26
+ patter.append(f"(?P<{name}>{regex})")
27
+
28
+ full_regex = "|".join(patter)
29
+
30
+ tokens = []
31
+
32
+ for match in re.finditer(full_regex, text):
33
+ kind = match.lastgroup
34
+ value = match.group()
35
+
36
+ tokens.append((kind, value))
37
+
38
+ return tokens
39
+
40
+ def tokenizer_tokens(text):
41
+ tokens = tokenizer(text, token_pattern)
42
+ return tokens
@@ -0,0 +1,34 @@
1
+ from Hexus.interpreter import HexusInterpreter
2
+ from Hexus.parser import HexusParser
3
+ from Hexus.lexer import tokenizer_tokens
4
+
5
+ def main():
6
+ file_name = "test.he"
7
+ try:
8
+ with open(file_name, "r", encoding="utf-8") as file:
9
+ source_code = file.read()
10
+
11
+ except FileNotFoundError:
12
+ print(f"File {file_name} not found!!!")
13
+ return
14
+ token_list = tokenizer_tokens(source_code)
15
+
16
+ clear_tokens = [t for t in token_list if t[0] != "SKIP"]
17
+ try:
18
+ for token in clear_tokens:
19
+ print(token)
20
+ parser = HexusParser(clear_tokens)
21
+
22
+ program_tree = parser.parse()
23
+
24
+ print(program_tree)
25
+
26
+ interpreter = HexusInterpreter()
27
+
28
+ interpreter.interpret(program_tree)
29
+
30
+ except SyntaxError as e:
31
+ print(e)
32
+
33
+ if __name__ == "__main__":
34
+ main()
File without changes