solpl 2.2.0__tar.gz → 2.3.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.
- {solpl-2.2.0/solpl.egg-info → solpl-2.3.0}/PKG-INFO +1 -1
- solpl-2.3.0/interpreter/sol.py +342 -0
- {solpl-2.2.0 → solpl-2.3.0}/pyproject.toml +1 -1
- {solpl-2.2.0 → solpl-2.3.0/solpl.egg-info}/PKG-INFO +1 -1
- solpl-2.2.0/interpreter/sol.py +0 -462
- {solpl-2.2.0 → solpl-2.3.0}/LICENSE +0 -0
- {solpl-2.2.0 → solpl-2.3.0}/README.md +0 -0
- {solpl-2.2.0 → solpl-2.3.0}/setup.cfg +0 -0
- {solpl-2.2.0 → solpl-2.3.0}/solpl.egg-info/SOURCES.txt +0 -0
- {solpl-2.2.0 → solpl-2.3.0}/solpl.egg-info/dependency_links.txt +0 -0
- {solpl-2.2.0 → solpl-2.3.0}/solpl.egg-info/entry_points.txt +0 -0
- {solpl-2.2.0 → solpl-2.3.0}/solpl.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
import time
|
|
3
|
+
import sys
|
|
4
|
+
import os
|
|
5
|
+
import urllib.request
|
|
6
|
+
import http.server
|
|
7
|
+
|
|
8
|
+
class ReturnSignal(Exception):
|
|
9
|
+
def __init__(self, value):
|
|
10
|
+
self.value = value
|
|
11
|
+
|
|
12
|
+
class SolInterpreter:
|
|
13
|
+
def __init__(self):
|
|
14
|
+
self.variables = {}
|
|
15
|
+
self.thread_local = threading.local()
|
|
16
|
+
self.functions = {}
|
|
17
|
+
self.struct_blueprints = {}
|
|
18
|
+
self.lines = []
|
|
19
|
+
self.threads = []
|
|
20
|
+
self._server_started = False
|
|
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 _import_file(self, filename):
|
|
29
|
+
if not os.path.exists(filename):
|
|
30
|
+
print(f"Error: Module '{filename}' not found.")
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
with open(filename, "r") as f:
|
|
34
|
+
code = f.read()
|
|
35
|
+
module_name = filename.replace(".sol", "")
|
|
36
|
+
lines = [l.strip() for l in code.split('\n') if l.strip()]
|
|
37
|
+
for pc, line in enumerate(lines):
|
|
38
|
+
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 ", "while ", "for ", "elseif", "else")):
|
|
39
|
+
parts = line.split("(")
|
|
40
|
+
name = parts[0].strip()
|
|
41
|
+
params_str = parts[1].replace(")", "").strip()
|
|
42
|
+
params = [p.strip() for p in params_str.split(",")] if params_str else []
|
|
43
|
+
self.functions[f"{module_name}.{name}"] = {"start_pc": pc + 1, "params": params, "source_lines": lines}
|
|
44
|
+
|
|
45
|
+
def _compute(self, expr):
|
|
46
|
+
expr = str(expr).strip()
|
|
47
|
+
if "+" in expr and any(c.isalpha() or c in ['"', "'"] for c in expr):
|
|
48
|
+
parts = expr.split("+")
|
|
49
|
+
result = ""
|
|
50
|
+
for p in parts:
|
|
51
|
+
val = self._resolve(p.strip())
|
|
52
|
+
result += str(val) if val is not None else ""
|
|
53
|
+
return result
|
|
54
|
+
ops = ['+', '-', '*', ':']
|
|
55
|
+
for op in ops:
|
|
56
|
+
if op in expr:
|
|
57
|
+
try:
|
|
58
|
+
left, right = expr.split(op, 1)
|
|
59
|
+
r1 = self._resolve(left)
|
|
60
|
+
r2 = self._resolve(right)
|
|
61
|
+
if r1 is None or r2 is None: continue
|
|
62
|
+
val1 = float(r1)
|
|
63
|
+
val2 = float(r2)
|
|
64
|
+
if op == '+': return val1 + val2
|
|
65
|
+
if op == '-': return val1 - val2
|
|
66
|
+
if op == '*': return val1 * val2
|
|
67
|
+
if op == ':': return val1 / val2
|
|
68
|
+
except (ValueError, TypeError): continue
|
|
69
|
+
return self._resolve(expr)
|
|
70
|
+
|
|
71
|
+
def _lookup(self, name):
|
|
72
|
+
for scope in reversed(self.scope_stack):
|
|
73
|
+
if name in scope: return scope[name]
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
def _assign(self, name, val):
|
|
77
|
+
self.scope_stack[-1][name] = val
|
|
78
|
+
|
|
79
|
+
def _resolve(self, val):
|
|
80
|
+
val = str(val).strip()
|
|
81
|
+
if (val.startswith('"') and val.endswith('"')) or (val.startswith("'") and val.endswith("'")):
|
|
82
|
+
return val[1:-1]
|
|
83
|
+
if val.startswith("_fetch(") and val.endswith(")"):
|
|
84
|
+
url_expr = val[7:-1].strip()
|
|
85
|
+
return self._native_fetch(str(self._compute(url_expr)))
|
|
86
|
+
if "(" in val and val.endswith(")") and not val.startswith("_"):
|
|
87
|
+
parts = val.split("(", 1)
|
|
88
|
+
name = parts[0].strip()
|
|
89
|
+
if name in self.functions:
|
|
90
|
+
args_str = parts[1][:-1].strip()
|
|
91
|
+
args = [self._compute(a.strip()) for a in args_str.split(",")] if args_str else []
|
|
92
|
+
return self._call_function(name, args)
|
|
93
|
+
if "[" in val and "]" in val:
|
|
94
|
+
try:
|
|
95
|
+
name, idx_part = val.split("[")
|
|
96
|
+
idx = int(idx_part.replace("]", "").strip())
|
|
97
|
+
container = self._lookup(name)
|
|
98
|
+
if isinstance(container, list): 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): return obj.get(prop, 0)
|
|
104
|
+
existing_val = self._lookup(val)
|
|
105
|
+
if existing_val is not None: return existing_val
|
|
106
|
+
try: return float(val) if '.' in val else int(val)
|
|
107
|
+
except: return None
|
|
108
|
+
|
|
109
|
+
def _async_worker(self, expr, action):
|
|
110
|
+
result = self._compute(expr)
|
|
111
|
+
if action == "_out": print(f"[ASYNC OUTPUT] {result}")
|
|
112
|
+
|
|
113
|
+
def _async_input_worker(self, prompt, var_name):
|
|
114
|
+
user_val = input(prompt)
|
|
115
|
+
try:
|
|
116
|
+
if '.' in user_val: user_val = float(user_val)
|
|
117
|
+
else: user_val = int(user_val)
|
|
118
|
+
except ValueError: pass
|
|
119
|
+
self._assign(var_name, user_val)
|
|
120
|
+
|
|
121
|
+
def _native_fetch(self, url):
|
|
122
|
+
try:
|
|
123
|
+
with urllib.request.urlopen(url, timeout=5) as response:
|
|
124
|
+
return response.read().decode('utf-8', errors='replace')
|
|
125
|
+
except Exception as e: return f"Error: {str(e)}"
|
|
126
|
+
|
|
127
|
+
def _native_listen(self, port, handler_name):
|
|
128
|
+
if self._server_started: return
|
|
129
|
+
self._server_started = True
|
|
130
|
+
interpreter_instance = self
|
|
131
|
+
class SolHTTPHandler(http.server.BaseHTTPRequestHandler):
|
|
132
|
+
def do_GET(self):
|
|
133
|
+
self.send_response(200)
|
|
134
|
+
self.send_header("Content-type", "text/html")
|
|
135
|
+
self.end_headers()
|
|
136
|
+
response_body = interpreter_instance._call_function(handler_name, [self.path])
|
|
137
|
+
self.wfile.write(bytes(str(response_body), "utf-8"))
|
|
138
|
+
def log_message(self, format, *args): pass
|
|
139
|
+
def server_thread():
|
|
140
|
+
server = http.server.HTTPServer(("0.0.0.0", int(port)), SolHTTPHandler)
|
|
141
|
+
server.serve_forever()
|
|
142
|
+
t = threading.Thread(target=server_thread, daemon=True)
|
|
143
|
+
t.start()
|
|
144
|
+
self.threads.append(t)
|
|
145
|
+
|
|
146
|
+
def _evaluate_condition(self, cond):
|
|
147
|
+
if "=" in cond:
|
|
148
|
+
left, right = cond.split("=")
|
|
149
|
+
return self._compute(left.strip()) == self._compute(right.strip())
|
|
150
|
+
elif "<" in cond:
|
|
151
|
+
left, right = cond.split("<")
|
|
152
|
+
return self._compute(left.strip()) < self._compute(right.strip())
|
|
153
|
+
elif ">" in cond:
|
|
154
|
+
left, right = cond.split(">")
|
|
155
|
+
return self._compute(left.strip()) > self._compute(right.strip())
|
|
156
|
+
return False
|
|
157
|
+
|
|
158
|
+
def _get_block_end(self, start_pc):
|
|
159
|
+
depth = 1
|
|
160
|
+
pc = start_pc + 1
|
|
161
|
+
while pc < len(self.lines):
|
|
162
|
+
line = self.lines[pc]
|
|
163
|
+
if line.startswith("if ") or line.startswith("while ") or line.startswith("for "): depth += 1
|
|
164
|
+
elif line == "end":
|
|
165
|
+
depth -= 1
|
|
166
|
+
if depth == 0: return pc
|
|
167
|
+
pc += 1
|
|
168
|
+
return pc
|
|
169
|
+
|
|
170
|
+
def execute(self, code):
|
|
171
|
+
self.lines = [l.strip() for l in code.split('\n') if l.strip() and ';' not in l or (';' in l and l.split(';', 1)[0].strip())]
|
|
172
|
+
self.lines = [l.split(';', 1)[0].strip() for l in self.lines]
|
|
173
|
+
self.threads = []
|
|
174
|
+
pc = 0
|
|
175
|
+
while pc < len(self.lines):
|
|
176
|
+
line = self.lines[pc]
|
|
177
|
+
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 ", "while ", "for ", "elseif", "else")):
|
|
178
|
+
parts = line.split("(")
|
|
179
|
+
self.functions[parts[0].strip()] = {"start_pc": pc + 1, "params": [p.strip() for p in parts[1].replace(")", "").split(",")] if parts[1].replace(")", "").strip() else []}
|
|
180
|
+
elif "{" in line: pc = self._parse_struct(pc)
|
|
181
|
+
pc += 1
|
|
182
|
+
for i, line in enumerate(self.lines):
|
|
183
|
+
if line == "main()":
|
|
184
|
+
try: self._run_block(i + 1)
|
|
185
|
+
except ReturnSignal: pass
|
|
186
|
+
break
|
|
187
|
+
for t in self.threads:
|
|
188
|
+
if t.is_alive() and not t.daemon: t.join()
|
|
189
|
+
|
|
190
|
+
def _parse_struct(self, pc):
|
|
191
|
+
struct_name = self.lines[pc].split("{}")[0].strip()
|
|
192
|
+
fields = {}
|
|
193
|
+
pc += 1
|
|
194
|
+
while pc < len(self.lines) and self.lines[pc] != "end":
|
|
195
|
+
if "->" in self.lines[pc]:
|
|
196
|
+
key, val = [x.strip() for x in self.lines[pc].split("->")]
|
|
197
|
+
fields[key] = self._resolve(val)
|
|
198
|
+
pc += 1
|
|
199
|
+
self.struct_blueprints[struct_name] = fields
|
|
200
|
+
self._assign(struct_name, fields.copy())
|
|
201
|
+
return pc
|
|
202
|
+
|
|
203
|
+
def _call_function(self, name, args):
|
|
204
|
+
if "." in name and name in self.functions:
|
|
205
|
+
orig_lines = self.lines
|
|
206
|
+
self.lines = self.functions[name]["source_lines"]
|
|
207
|
+
res = self._run_with_scope(self.functions[name]["start_pc"], self.functions[name]["params"], args)
|
|
208
|
+
self.lines = orig_lines
|
|
209
|
+
return res
|
|
210
|
+
return self._run_with_scope(self.functions[name]["start_pc"], self.functions[name]["params"], args)
|
|
211
|
+
|
|
212
|
+
def _run_with_scope(self, start_pc, params, args):
|
|
213
|
+
local_scope = {p: a for p, a in zip(params, args)}
|
|
214
|
+
self.scope_stack.append(local_scope)
|
|
215
|
+
try: self._run_block(start_pc)
|
|
216
|
+
except ReturnSignal as sig: return sig.value
|
|
217
|
+
finally: self.scope_stack.pop()
|
|
218
|
+
return 0
|
|
219
|
+
|
|
220
|
+
def _execute_line(self, line):
|
|
221
|
+
if "_get(" in line:
|
|
222
|
+
filename = line.split('"')[1]
|
|
223
|
+
self._import_file(filename)
|
|
224
|
+
return
|
|
225
|
+
if ">>" in line and "_input" in line:
|
|
226
|
+
parts = [p.strip() for p in line.split(">>")]
|
|
227
|
+
t = threading.Thread(target=self._async_input_worker, args=(str(self._compute(parts[0])), parts[2]))
|
|
228
|
+
t.start(); self.threads.append(t); return
|
|
229
|
+
is_async_func = ">>" in line and "_async" in line and "(" in line and not line.startswith("_")
|
|
230
|
+
if is_async_func: line = line.split(">>")[0].strip()
|
|
231
|
+
if line.startswith("_return ->"): raise ReturnSignal(self._compute(line.split("->", 1)[1]))
|
|
232
|
+
elif ">>" in line and "_async" in line:
|
|
233
|
+
parts = line.split(">>")
|
|
234
|
+
t = threading.Thread(target=self._async_worker, args=(parts[0].strip(), parts[2].strip()))
|
|
235
|
+
t.start(); self.threads.append(t)
|
|
236
|
+
elif line.startswith("_in"):
|
|
237
|
+
prompt = line.split('(')[1].split(')')[0].replace('"', '').replace("'", "") if "(" in line else f"Input {line.split('->')[1].strip()}: "
|
|
238
|
+
sys.stdout.write(prompt + " "); sys.stdout.flush()
|
|
239
|
+
val = sys.stdin.readline().strip()
|
|
240
|
+
try: self._assign(line.split("->")[1].strip(), float(val) if '.' in val else int(val))
|
|
241
|
+
except: self._assign(line.split("->")[1].strip(), val)
|
|
242
|
+
elif "_add(" in line:
|
|
243
|
+
parts = [p.strip() for p in line.replace("_add(", "").replace(")", "").split(",")]
|
|
244
|
+
container = self._lookup(parts[0])
|
|
245
|
+
if isinstance(container, list): container.insert(int(parts[1]), self._resolve(parts[2]))
|
|
246
|
+
elif "_remove(" in line:
|
|
247
|
+
parts = [p.strip() for p in line.replace("_remove(", "").replace(")", "").split(",")]
|
|
248
|
+
container = self._lookup(parts[0])
|
|
249
|
+
if isinstance(container, list): container.pop(int(parts[1]))
|
|
250
|
+
elif "_listen(" in line:
|
|
251
|
+
p, h = [p.strip() for p in line.replace("_listen(", "").replace(")", "").split(",")]
|
|
252
|
+
self._native_listen(self._compute(p), h)
|
|
253
|
+
elif "_out" in line:
|
|
254
|
+
val = self._compute(line.split("->")[1].strip())
|
|
255
|
+
print(val if val is not None else "Error")
|
|
256
|
+
elif "mylist<" in line: self._assign(line.split("<")[1].split(">")[0], [])
|
|
257
|
+
elif "->" in line:
|
|
258
|
+
var, val = [x.strip() for x in line.split("->")]
|
|
259
|
+
if "." in var:
|
|
260
|
+
obj, prop = var.split(".", 1)
|
|
261
|
+
target = self._lookup(obj)
|
|
262
|
+
if isinstance(target, dict): target[prop] = self._compute(val)
|
|
263
|
+
else: self._assign(var, self._compute(val))
|
|
264
|
+
elif "(" in line and line.endswith(")") and not line.startswith("_"):
|
|
265
|
+
parts = line.split("(")
|
|
266
|
+
if is_async_func:
|
|
267
|
+
t = threading.Thread(target=self._call_function, args=(parts[0].strip(), [self._compute(a.strip()) for a in parts[1].replace(")", "").split(",")]))
|
|
268
|
+
t.start(); self.threads.append(t)
|
|
269
|
+
else: self._call_function(parts[0].strip(), [self._compute(a.strip()) for a in parts[1].replace(")", "").split(",")])
|
|
270
|
+
|
|
271
|
+
def _run_block(self, start_pc):
|
|
272
|
+
pc = start_pc
|
|
273
|
+
control_stack = []
|
|
274
|
+
while pc < len(self.lines):
|
|
275
|
+
line = self.lines[pc]
|
|
276
|
+
is_executing = all(level["executing"] for level in control_stack)
|
|
277
|
+
if line.startswith("if "):
|
|
278
|
+
control_stack.append({"type": "if", "executing": is_executing and self._evaluate_condition(line.split("if ", 1)[1].strip()), "any_branch_true": False})
|
|
279
|
+
pc += 1; continue
|
|
280
|
+
elif line.startswith("elseif "):
|
|
281
|
+
cond = line.split("elseif ", 1)[1].strip()
|
|
282
|
+
parent_active = all(level["executing"] for level in control_stack[:-1]) if len(control_stack) > 1 else True
|
|
283
|
+
if control_stack and control_stack[-1]["type"] == "if":
|
|
284
|
+
if parent_active and not control_stack[-1]["any_branch_true"] and self._evaluate_condition(cond):
|
|
285
|
+
control_stack[-1]["executing"] = True
|
|
286
|
+
control_stack[-1]["any_branch_true"] = True
|
|
287
|
+
else: control_stack[-1]["executing"] = False
|
|
288
|
+
pc += 1; continue
|
|
289
|
+
elif line == "else":
|
|
290
|
+
parent_active = all(level["executing"] for level in control_stack[:-1]) if len(control_stack) > 1 else True
|
|
291
|
+
if control_stack and control_stack[-1]["type"] == "if":
|
|
292
|
+
control_stack[-1]["executing"] = parent_active and not control_stack[-1]["any_branch_true"]
|
|
293
|
+
pc += 1; continue
|
|
294
|
+
elif line.startswith("while "):
|
|
295
|
+
cond = line.split("while ", 1)[1].strip()
|
|
296
|
+
end_pc = self._get_block_end(pc)
|
|
297
|
+
if is_executing and self._evaluate_condition(cond):
|
|
298
|
+
control_stack.append({"type": "while", "executing": True, "start_pc": pc, "end_pc": end_pc})
|
|
299
|
+
pc += 1
|
|
300
|
+
else: pc = end_pc + 1
|
|
301
|
+
continue
|
|
302
|
+
elif line.startswith("for "):
|
|
303
|
+
parts = line.split("for ", 1)[1].split("->")
|
|
304
|
+
var_name = parts[0].strip()
|
|
305
|
+
start_v = int(self._compute(parts[1].split(" to ")[0].strip()))
|
|
306
|
+
end_v = int(self._compute(parts[1].split(" to ")[1].strip()))
|
|
307
|
+
end_pc = self._get_block_end(pc)
|
|
308
|
+
if is_executing and start_v <= end_v:
|
|
309
|
+
self._assign(var_name, start_v)
|
|
310
|
+
control_stack.append({"type": "for", "executing": True, "var": var_name, "end_val": end_v, "start_pc": pc, "end_pc": end_pc})
|
|
311
|
+
pc += 1
|
|
312
|
+
else: pc = end_pc + 1
|
|
313
|
+
continue
|
|
314
|
+
elif line == "end":
|
|
315
|
+
if control_stack:
|
|
316
|
+
top = control_stack.pop()
|
|
317
|
+
if top.get("type") == "while" and top["executing"]: pc = top["start_pc"] - 1
|
|
318
|
+
elif top.get("type") == "for" and top["executing"]:
|
|
319
|
+
curr = self._lookup(top["var"]) + 1
|
|
320
|
+
if curr <= top["end_val"]:
|
|
321
|
+
self._assign(top["var"], curr)
|
|
322
|
+
pc = top["start_pc"] - 1
|
|
323
|
+
pc += 1; continue
|
|
324
|
+
if is_executing:
|
|
325
|
+
if "?" in line:
|
|
326
|
+
cond, action = line.split("?", 1)
|
|
327
|
+
if self._evaluate_condition(cond.strip()): self._execute_line(action.strip())
|
|
328
|
+
else: self._execute_line(line)
|
|
329
|
+
pc += 1
|
|
330
|
+
|
|
331
|
+
def main():
|
|
332
|
+
engine = SolInterpreter()
|
|
333
|
+
if not sys.stdin.isatty():
|
|
334
|
+
code = sys.stdin.read()
|
|
335
|
+
elif len(sys.argv) > 1:
|
|
336
|
+
with open(sys.argv[1], "r") as f: code = f.read()
|
|
337
|
+
else:
|
|
338
|
+
print("Usage: solpl <file.sol> OR cat file.sol | python sol.py"); sys.exit(1)
|
|
339
|
+
engine.execute(code)
|
|
340
|
+
|
|
341
|
+
if __name__ == "__main__":
|
|
342
|
+
main()
|
solpl-2.2.0/interpreter/sol.py
DELETED
|
@@ -1,462 +0,0 @@
|
|
|
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()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|