solpl 2.1.0__tar.gz → 2.2.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: solpl
3
- Version: 2.1.0
3
+ Version: 2.2.0
4
4
  Summary: Sol Programming Language Interpreter
5
5
  Requires-Python: >=3.7
6
6
  License-File: LICENSE
@@ -0,0 +1,462 @@
1
+ import math
2
+ import threading
3
+ import time
4
+ import sys
5
+ import os
6
+ import urllib.request
7
+ import http.server
8
+
9
+ class ReturnSignal(Exception):
10
+ def __init__(self, value):
11
+ self.value = value
12
+
13
+ class SolInterpreter:
14
+ def __init__(self):
15
+ self.variables = {}
16
+ self.thread_local = threading.local()
17
+ self.functions = {}
18
+ self.struct_blueprints = {}
19
+ self.lines = []
20
+ self.threads = []
21
+
22
+ @property
23
+ def scope_stack(self):
24
+ if not hasattr(self.thread_local, "stack"):
25
+ self.thread_local.stack = [self.variables]
26
+ return self.thread_local.stack
27
+
28
+ def _compute(self, expr):
29
+ expr = str(expr).strip()
30
+
31
+ if "+" in expr and any(c.isalpha() or c in ['"', "'"] for c in expr):
32
+ parts = expr.split("+")
33
+ result = ""
34
+ for p in parts:
35
+ val = str(self._resolve(p.strip()))
36
+ result += val
37
+ return result
38
+
39
+ ops = ['+', '-', '*', ':']
40
+ for op in ops:
41
+ if op in expr:
42
+ try:
43
+ left, right = expr.split(op, 1)
44
+ val1 = float(self._resolve(left))
45
+ val2 = float(self._resolve(right))
46
+ if op == '+': return val1 + val2
47
+ if op == '-': return val1 - val2
48
+ if op == '*': return val1 * val2
49
+ if op == ':': return val1 / val2
50
+ except ValueError:
51
+ continue
52
+ return self._resolve(expr)
53
+
54
+ def _lookup(self, name):
55
+ for scope in reversed(self.scope_stack):
56
+ if name in scope:
57
+ return scope[name]
58
+ return None
59
+
60
+ def _assign(self, name, val):
61
+ self.scope_stack[-1][name] = val
62
+
63
+ def _resolve(self, val):
64
+ val = str(val).strip()
65
+
66
+ if val.startswith("_fetch(") and val.endswith(")"):
67
+ url_expr = val[7:-1].strip()
68
+ url = str(self._compute(url_expr))
69
+ return self._native_fetch(url)
70
+
71
+ if "(" in val and val.endswith(")") and not val.startswith("_"):
72
+ parts = val.split("(", 1)
73
+ name = parts[0].strip()
74
+ if name in self.functions:
75
+ args_str = parts[1][:-1].strip()
76
+ args = [self._compute(a.strip()) for a in args_str.split(",")] if args_str else []
77
+ return self._call_function(name, args)
78
+
79
+ if "[" in val and "]" in val:
80
+ try:
81
+ name, idx_part = val.split("[")
82
+ idx = int(idx_part.replace("]", "").strip())
83
+ container = self._lookup(name)
84
+ if isinstance(container, list):
85
+ return container[idx]
86
+ except (ValueError, IndexError, KeyError): pass
87
+ if "." in val:
88
+ obj_name, prop = val.split(".", 1)
89
+ obj = self._lookup(obj_name)
90
+ if isinstance(obj, dict):
91
+ return obj.get(prop, 0)
92
+
93
+ existing_val = self._lookup(val)
94
+ if existing_val is not None: return existing_val
95
+ try: return float(val) if '.' in val else int(val)
96
+ except: return val.replace('"', '').replace("'", "")
97
+
98
+ def _async_worker(self, expr, action):
99
+ result = self._compute(expr)
100
+ if action == "_out":
101
+ print(f"[ASYNC OUTPUT] {result}")
102
+
103
+ def _async_input_worker(self, prompt, var_name):
104
+ user_val = input(prompt)
105
+ try:
106
+ if '.' in user_val: user_val = float(user_val)
107
+ else: user_val = int(user_val)
108
+ except ValueError: pass
109
+ self._assign(var_name, user_val)
110
+
111
+ def _native_fetch(self, url):
112
+ try:
113
+ with urllib.request.urlopen(url, timeout=5) as response:
114
+ # Fix: Added errors='replace' to handle non-UTF-8 bytes safely
115
+ return response.read().decode('utf-8', errors='replace')
116
+ except Exception as e:
117
+ return f"Error fetching URL: {str(e)}"
118
+
119
+ def _native_listen(self, port, handler_name):
120
+ interpreter_instance = self
121
+ class SolHTTPHandler(http.server.BaseHTTPRequestHandler):
122
+ def do_GET(self):
123
+ self.send_response(200)
124
+ self.send_header("Content-type", "text/html")
125
+ self.end_headers()
126
+ response_body = interpreter_instance._call_function(handler_name, [self.path])
127
+ self.wfile.write(bytes(str(response_body), "utf-8"))
128
+ def log_message(self, format, *args):
129
+ pass
130
+
131
+ def server_thread():
132
+ server = http.server.HTTPServer(("0.0.0.0", int(port)), SolHTTPHandler)
133
+ server.serve_forever()
134
+
135
+ t = threading.Thread(target=server_thread, daemon=True)
136
+ t.start()
137
+ self.threads.append(t)
138
+
139
+ def _evaluate_condition(self, cond):
140
+ if "=" in cond:
141
+ left, right = cond.split("=")
142
+ return self._compute(left.strip()) == self._compute(right.strip())
143
+ elif "<" in cond:
144
+ left, right = cond.split("<")
145
+ return self._compute(left.strip()) < self._compute(right.strip())
146
+ elif ">" in cond:
147
+ left, right = cond.split(">")
148
+ return self._compute(left.strip()) > self._compute(right.strip())
149
+ return False
150
+
151
+ def _get_block_end(self, start_pc):
152
+ depth = 1
153
+ pc = start_pc + 1
154
+ while pc < len(self.lines):
155
+ line = self.lines[pc]
156
+ if line.startswith("if ") or line.startswith("while ") or line.startswith("for "):
157
+ depth += 1
158
+ elif line == "end":
159
+ depth -= 1
160
+ if depth == 0:
161
+ return pc
162
+ pc += 1
163
+ return pc
164
+
165
+ def execute(self, code):
166
+ self.lines = []
167
+ for raw_line in code.split('\n'):
168
+ if ';' in raw_line:
169
+ raw_line = raw_line.split(';', 1)[0]
170
+ trimmed = raw_line.strip()
171
+ if trimmed:
172
+ self.lines.append(trimmed)
173
+
174
+ self.threads = []
175
+
176
+ pc = 0
177
+ while pc < len(self.lines):
178
+ line = self.lines[pc]
179
+ if "(" in line and ")" in line and "main" not in line and "end" not in line and "->" not in line and ">>" not in line and "?" not in line and not line.startswith("if ") and not line.startswith("while ") and not line.startswith("for "):
180
+ parts = line.split("(")
181
+ name = parts[0].strip()
182
+ params_str = parts[1].replace(")", "").strip()
183
+ params = [p.strip() for p in params_str.split(",")] if params_str else []
184
+ self.functions[name] = {"start_pc": pc + 1, "params": params}
185
+ elif "{" in line:
186
+ pc = self._parse_struct(pc)
187
+ continue
188
+ pc += 1
189
+
190
+ for i, line in enumerate(self.lines):
191
+ if line == "main()":
192
+ try:
193
+ self._run_block(i + 1)
194
+ except ReturnSignal:
195
+ pass
196
+ break
197
+
198
+ for t in self.threads:
199
+ if t.is_alive() and not t.daemon:
200
+ t.join()
201
+
202
+ def _parse_struct(self, pc):
203
+ line = self.lines[pc]
204
+ struct_name = line.split("{}")[0].strip()
205
+ fields = {}
206
+ pc += 1
207
+ while pc < len(self.lines) and self.lines[pc] != "end":
208
+ if "->" in self.lines[pc]:
209
+ key, val = [x.strip() for x in self.lines[pc].split("->")]
210
+ fields[key] = self._resolve(val)
211
+ pc += 1
212
+ self.struct_blueprints[struct_name] = fields
213
+ self._assign(struct_name, fields.copy())
214
+ return pc
215
+
216
+ def _call_function(self, name, args):
217
+ func_info = self.functions[name]
218
+ start_pc = func_info["start_pc"]
219
+ params = func_info["params"]
220
+
221
+ local_scope = {}
222
+ for param, arg in zip(params, args):
223
+ local_scope[param] = arg
224
+
225
+ self.scope_stack.append(local_scope)
226
+
227
+ return_value = 0
228
+ try:
229
+ self._run_block(start_pc)
230
+ except ReturnSignal as sig:
231
+ return_value = sig.value
232
+ finally:
233
+ self.scope_stack.pop()
234
+
235
+ return return_value
236
+
237
+ def _execute_line(self, line):
238
+ if ">>" in line and "_input" in line:
239
+ parts = [p.strip() for p in line.split(">>")]
240
+ if len(parts) == 3 and parts[1] == "_input":
241
+ prompt_string = str(self._compute(parts[0]))
242
+ target_variable = parts[2]
243
+
244
+ t = threading.Thread(target=self._async_input_worker, args=(prompt_string, target_variable))
245
+ t.start()
246
+ self.threads.append(t)
247
+ return
248
+
249
+ is_async_func = False
250
+ if ">>" in line and "_async" in line and "(" in line and not line.startswith("_"):
251
+ is_async_func = True
252
+ line = line.split(">>")[0].strip()
253
+
254
+ if line.startswith("_return ->"):
255
+ val_expr = line.split("->", 1)[1].strip()
256
+ result = self._compute(val_expr)
257
+ raise ReturnSignal(result)
258
+
259
+ elif ">>" in line and "_async" in line and not is_async_func:
260
+ parts = line.split(">>")
261
+ t = threading.Thread(target=self._async_worker, args=(parts[0].strip(), parts[2].strip()))
262
+ t.start()
263
+ self.threads.append(t)
264
+
265
+ elif line.startswith("_in"):
266
+ if "(" in line and ")" in line:
267
+ parts = line.split("(")
268
+ prompt = parts[1].split(")")[0].strip().replace('"', '').replace("'", "")
269
+ var_name = line.split("->")[1].strip()
270
+ else:
271
+ var_name = line.split("->")[1].strip()
272
+ prompt = f"[INPUT REQUIRED FOR %s]: " % var_name
273
+
274
+ user_val = input(prompt + " ")
275
+ try:
276
+ if '.' in user_val: user_val = float(user_val)
277
+ else: user_val = int(user_val)
278
+ except ValueError: pass
279
+ self._assign(var_name, user_val)
280
+
281
+ elif "_add(" in line:
282
+ content = line.replace("_add(", "").replace(")", "")
283
+ parts = [p.strip() for p in content.split(",")]
284
+ container = self._lookup(parts[0])
285
+ if isinstance(container, list):
286
+ container.insert(int(parts[1]), self._resolve(parts[2]))
287
+ elif "_remove(" in line:
288
+ content = line.replace("_remove(", "").replace(")", "")
289
+ parts = [p.strip() for p in content.split(",")]
290
+ container = self._lookup(parts[0])
291
+ if isinstance(container, list):
292
+ container.pop(int(parts[1]))
293
+ elif "_listen(" in line:
294
+ content = line.replace("_listen(", "").replace(")", "")
295
+ port_expr, handler_expr = [p.strip() for p in content.split(",")]
296
+ port = self._compute(port_expr)
297
+ self._native_listen(port, handler_expr)
298
+ elif "_out" in line:
299
+ val = line.split("->")[1].strip()
300
+ print(self._compute(val))
301
+
302
+ elif "mylist<" in line and ">" in line:
303
+ var_name = line.split("<")[1].split(">")[0]
304
+ self._assign(var_name, [])
305
+
306
+ elif "->" in line:
307
+ var, val = [x.strip() for x in line.split("->")]
308
+ if "." in var:
309
+ obj, prop = var.split(".", 1)
310
+ target_obj = self._lookup(obj)
311
+ if isinstance(target_obj, dict):
312
+ target_obj[prop] = self._compute(val)
313
+ else:
314
+ self._assign(var, self._compute(val))
315
+ else:
316
+ if "(" in line and line.endswith(")") and not line.startswith("_"):
317
+ parts = line.split("(")
318
+ name = parts[0].strip()
319
+ if name in self.functions:
320
+ args_str = parts[1][:-1].strip()
321
+ args = [self._compute(a.strip()) for a in args_str.split(",")] if args_str else []
322
+
323
+ if is_async_func:
324
+ t = threading.Thread(target=self._call_function, args=(name, args))
325
+ t.start()
326
+ self.threads.append(t)
327
+ else:
328
+ self._call_function(name, args)
329
+
330
+ def _run_block(self, start_pc):
331
+ pc = start_pc
332
+ control_stack = []
333
+
334
+ while pc < len(self.lines):
335
+ line = self.lines[pc]
336
+ is_executing = all(level["executing"] for level in control_stack)
337
+
338
+ if line.startswith("if "):
339
+ cond = line.split("if ", 1)[1].strip()
340
+ if is_executing:
341
+ cond_true = self._evaluate_condition(cond)
342
+ control_stack.append({"type": "if", "executing": cond_true, "any_branch_true": cond_true})
343
+ else:
344
+ control_stack.append({"type": "if", "executing": False, "any_branch_true": True})
345
+ pc += 1
346
+ continue
347
+
348
+ elif line.startswith("elseif "):
349
+ cond = line.split("elseif ", 1)[1].strip()
350
+ parent_active = all(level["executing"] for level in control_stack[:-1]) if len(control_stack) > 1 else True
351
+
352
+ if control_stack and control_stack[-1]["type"] == "if":
353
+ if parent_active and not control_stack[-1]["any_branch_true"]:
354
+ cond_true = self._evaluate_condition(cond)
355
+ control_stack[-1]["executing"] = cond_true
356
+ control_stack[-1]["any_branch_true"] = cond_true
357
+ else:
358
+ control_stack[-1]["executing"] = False
359
+ pc += 1
360
+ continue
361
+
362
+ elif line == "else":
363
+ parent_active = all(level["executing"] for level in control_stack[:-1]) if len(control_stack) > 1 else True
364
+ if control_stack and control_stack[-1]["type"] == "if":
365
+ if parent_active and not control_stack[-1]["any_branch_true"]:
366
+ control_stack[-1]["executing"] = True
367
+ control_stack[-1]["any_branch_true"] = True
368
+ else:
369
+ control_stack[-1]["executing"] = False
370
+ pc += 1
371
+ continue
372
+
373
+ elif line.startswith("while "):
374
+ cond = line.split("while ", 1)[1].strip()
375
+ if is_executing:
376
+ end_pc = self._get_block_end(pc)
377
+ if self._evaluate_condition(cond):
378
+ control_stack.append({"type": "while", "executing": True, "start_pc": pc, "end_pc": end_pc})
379
+ pc += 1
380
+ else:
381
+ pc = end_pc + 1
382
+ else:
383
+ control_stack.append({"type": "while", "executing": False})
384
+ pc += 1
385
+ continue
386
+
387
+ elif line.startswith("for "):
388
+ if is_executing:
389
+ parts = line.split("for ", 1)[1].split("->")
390
+ var_name = parts[0].strip()
391
+ range_parts = parts[1].split(" to ")
392
+ start_val = int(self._compute(range_parts[0].strip()))
393
+ end_val = int(self._compute(range_parts[1].strip()))
394
+
395
+ self._assign(var_name, start_val)
396
+ end_pc = self._get_block_end(pc)
397
+
398
+ if start_val <= end_val:
399
+ control_stack.append({"type": "for", "executing": True, "var": var_name, "end_val": end_val, "start_pc": pc, "end_pc": end_pc})
400
+ pc += 1
401
+ else:
402
+ pc = end_pc + 1
403
+ else:
404
+ control_stack.append({"type": "for", "executing": False})
405
+ pc += 1
406
+ continue
407
+
408
+ elif line == "end":
409
+ if control_stack:
410
+ top = control_stack[-1]
411
+ if top.get("type") == "while" and top["executing"]:
412
+ pc = top["start_pc"]
413
+ control_stack.pop()
414
+ continue
415
+ elif top.get("type") == "for" and top["executing"]:
416
+ var_name = top["var"]
417
+ next_val = self._lookup(var_name) + 1
418
+ if next_val <= top["end_val"]:
419
+ self._assign(var_name, next_val)
420
+ pc = top["start_pc"] + 1
421
+ continue
422
+ else:
423
+ control_stack.pop()
424
+ pc += 1
425
+ continue
426
+ else:
427
+ control_stack.pop()
428
+ pc += 1
429
+ continue
430
+ else:
431
+ break
432
+
433
+ if is_executing:
434
+ if "?" in line:
435
+ cond, action = line.split("?", 1)
436
+ if self._evaluate_condition(cond.strip()):
437
+ self._execute_line(action.strip())
438
+ else:
439
+ self._execute_line(line)
440
+ pc += 1
441
+
442
+ def main():
443
+ if len(sys.argv) < 2:
444
+ print("Error: No script file provided.\nUsage: solpl <filename.sol>", file=sys.stderr)
445
+ sys.exit(1)
446
+
447
+ filename = sys.argv[1]
448
+ if not filename.endswith(".sol"):
449
+ print("Error: File must have a '.sol' extension.", file=sys.stderr)
450
+ sys.exit(1)
451
+ if not os.path.exists(filename):
452
+ print(f"Error: File '{filename}' not found.", file=sys.stderr)
453
+ sys.exit(1)
454
+
455
+ with open(filename, "r") as f:
456
+ code_content = f.read()
457
+
458
+ engine = SolInterpreter()
459
+ engine.execute(code_content)
460
+
461
+ if __name__ == "__main__":
462
+ main()
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "solpl"
7
- version = "2.1.0"
7
+ version = "2.2.0"
8
8
  description = "Sol Programming Language Interpreter"
9
9
  requires-python = ">=3.7"
10
10
 
11
11
  [project.scripts]
12
- solpl = "sol:main"
12
+ solpl = "interpreter.sol:main"
13
13
 
14
14
  [tool.setuptools]
15
- py-modules = ["sol"]
15
+ packages = ["interpreter"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: solpl
3
- Version: 2.1.0
3
+ Version: 2.2.0
4
4
  Summary: Sol Programming Language Interpreter
5
5
  Requires-Python: >=3.7
6
6
  License-File: LICENSE
@@ -1,6 +1,7 @@
1
1
  LICENSE
2
2
  README.md
3
3
  pyproject.toml
4
+ interpreter/sol.py
4
5
  solpl.egg-info/PKG-INFO
5
6
  solpl.egg-info/SOURCES.txt
6
7
  solpl.egg-info/dependency_links.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ solpl = interpreter.sol:main
@@ -0,0 +1 @@
1
+ interpreter
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- solpl = sol:main
@@ -1 +0,0 @@
1
- sol
File without changes
File without changes
File without changes