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