solpl 2.7.0__tar.gz → 4.0.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.7.0
3
+ Version: 4.0.0
4
4
  Summary: A lightweight, multi-threaded interpreter for the Sol programming language
5
5
  Author-email: stefand-0 <stefand-0@users.noreply.github.com>
6
6
  License-Expression: Apache-2.0
@@ -41,7 +41,6 @@ class SolInterpreter:
41
41
  return False
42
42
 
43
43
  def _split_on_op(self, string, op):
44
- """Split on first occurrence of op outside quotes. Returns (left, right) or (None, None)."""
45
44
  in_quote = False
46
45
  quote_char = None
47
46
  for i, char in enumerate(string):
@@ -96,7 +95,6 @@ class SolInterpreter:
96
95
 
97
96
  def _compute(self, expr):
98
97
  expr = str(expr).strip()
99
-
100
98
  # Handle parentheses
101
99
  idx = 0
102
100
  while True:
@@ -106,7 +104,6 @@ class SolInterpreter:
106
104
  start_idx = expr.rfind('(', 0, end_idx)
107
105
  if start_idx == -1:
108
106
  break
109
- # Skip function calls
110
107
  if start_idx > 0 and (expr[start_idx - 1].isalnum() or expr[start_idx - 1] == '_'):
111
108
  idx = end_idx + 1
112
109
  continue
@@ -114,8 +111,7 @@ class SolInterpreter:
114
111
  inner_result = self._compute(inner_expr)
115
112
  expr = expr[:start_idx] + str(inner_result) + expr[end_idx + 1:]
116
113
  idx = 0
117
-
118
- # Handle string concatenation with +
114
+ # String concatenation with +
119
115
  plus_parts = self._split_outside_quotes(expr, '+')
120
116
  if len(plus_parts) > 1:
121
117
  is_string_concat = False
@@ -130,16 +126,14 @@ class SolInterpreter:
130
126
  val = self._resolve(p.strip())
131
127
  result += str(val) if val is not None else ""
132
128
  return result
133
-
134
- # Check for equality (=) - comparison operator
129
+ # Equality comparison (=)
135
130
  left, right = self._split_on_op(expr, '=')
136
131
  if left is not None:
137
132
  r1 = self._resolve(left.strip())
138
133
  r2 = self._resolve(right.strip())
139
134
  if r1 is not None and r2 is not None:
140
135
  return 1 if r1 == r2 else 0
141
-
142
- # Level 1: + and -
136
+ # + and -
143
137
  for op in ['+', '-']:
144
138
  idx = self._find_last_op(expr, op)
145
139
  if idx != -1:
@@ -153,8 +147,7 @@ class SolInterpreter:
153
147
  val2 = float(r2)
154
148
  if op == '+': return val1 + val2
155
149
  if op == '-': return val1 - val2
156
-
157
- # Level 2: * and :
150
+ # * and :
158
151
  last_idx = -1
159
152
  last_op = None
160
153
  for op in ['*', ':']:
@@ -174,7 +167,6 @@ class SolInterpreter:
174
167
  val2 = float(r2)
175
168
  if last_op == '*': return val1 * val2
176
169
  if last_op == ':': return val1 / val2
177
-
178
170
  return self._resolve(expr)
179
171
 
180
172
  def _lookup(self, name):
@@ -207,6 +199,11 @@ class SolInterpreter:
207
199
  args_str = parts[1][:-1].strip()
208
200
  args = [self._compute(a.strip()) for a in args_str.split(",")] if args_str else []
209
201
  return self._call_function(name, args)
202
+ for func_name in self.functions:
203
+ if func_name.endswith("." + name):
204
+ args_str = parts[1][:-1].strip()
205
+ args = [self._compute(a.strip()) for a in args_str.split(",")] if args_str else []
206
+ return self._call_function(func_name, args)
210
207
  if "[" in val and "]" in val:
211
208
  try:
212
209
  name, idx_part = val.split("[", 1)
@@ -215,16 +212,23 @@ class SolInterpreter:
215
212
  if isinstance(container, list): return container[idx]
216
213
  except (ValueError, IndexError, KeyError): pass
217
214
  if "." in val:
218
- obj_name, prop = val.split(".", 1)
219
- obj = self._lookup(obj_name)
220
- if isinstance(obj, dict): return obj.get(prop, 0)
221
-
215
+ parts = val.split(".")
216
+ obj = self._lookup(parts[0])
217
+ for part in parts[1:]:
218
+ if isinstance(obj, dict):
219
+ obj = obj.get(part, 0)
220
+ else:
221
+ return 0
222
+ return obj
222
223
  for scope in reversed(self.scope_stack):
223
224
  if val in scope: return scope[val]
224
-
225
+ if val in self.functions:
226
+ return ("func_ref", val)
227
+ for func_name in self.functions:
228
+ if func_name.endswith("." + val):
229
+ return ("func_ref", func_name)
225
230
  if val.replace('.', '', 1).isdigit() or (val.startswith('-') and val[1:].replace('.', '', 1).isdigit()):
226
231
  return float(val) if '.' in val else int(val)
227
-
228
232
  return None
229
233
 
230
234
  def _native_fetch(self, url):
@@ -253,15 +257,12 @@ class SolInterpreter:
253
257
  self.threads.append(t)
254
258
 
255
259
  def _evaluate_condition(self, cond):
256
- # Equality comparison uses =
257
260
  left, right = self._split_on_op(cond, "=")
258
261
  if left is not None:
259
- return self._compute(cond) # _compute handles =
260
- # Less than
262
+ return self._compute(cond)
261
263
  left, right = self._split_on_op(cond, "<")
262
264
  if left is not None:
263
265
  return self._compute(left.strip()) < self._compute(right.strip())
264
- # Greater than
265
266
  left, right = self._split_on_op(cond, ">")
266
267
  if left is not None:
267
268
  return self._compute(left.strip()) > self._compute(right.strip())
@@ -279,33 +280,78 @@ class SolInterpreter:
279
280
  pc += 1
280
281
  return pc
281
282
 
283
+ def _import_file(self, filename):
284
+ if not os.path.exists(filename):
285
+ print(f"Error: Module '{filename}' not found.")
286
+ return
287
+ with open(filename, "r") as f:
288
+ code = f.read()
289
+ module_name = os.path.splitext(os.path.basename(filename))[0]
290
+ lines = []
291
+ for l in code.split('\n'):
292
+ l = l.strip()
293
+ if not l: continue
294
+ if l.startswith(';'): continue
295
+ if l.startswith('//'): continue
296
+ if ';' in l:
297
+ l = l.split(';', 1)[0].strip()
298
+ if l:
299
+ lines.append(l)
300
+ # Parse structs first
301
+ for pc, line in enumerate(lines):
302
+ if line.endswith("{}") and "->" not in line and ">>" not in line and "?" not in line and not line.startswith("_"):
303
+ struct_name = line.split("{}")[0].strip()
304
+ fields = {}
305
+ npc = pc + 1
306
+ while npc < len(lines) and lines[npc] != "end":
307
+ if "->" in lines[npc]:
308
+ key, val = [x.strip() for x in lines[npc].split("->")]
309
+ fields[key] = self._resolve(val)
310
+ npc += 1
311
+ self.struct_blueprints[struct_name] = fields
312
+ self.variables[struct_name] = fields.copy()
313
+ # Parse functions
314
+ for pc, line in enumerate(lines):
315
+ 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")):
316
+ parts = line.split("(")
317
+ name = parts[0].strip()
318
+ params_str = parts[1].replace(")", "").strip()
319
+ params = [p.strip() for p in params_str.split(",")] if params_str else []
320
+ self.functions[f"{module_name}.{name}"] = {"start_pc": pc + 1, "params": params, "source_lines": lines}
321
+
282
322
  def execute(self, code):
283
323
  raw_lines = code.split('\n')
284
324
  self.lines = []
285
325
  for l in raw_lines:
286
326
  l = l.strip()
287
- if not l:
288
- continue
289
- if l.startswith(';'):
290
- continue
291
- if l.startswith('//'):
292
- continue
327
+ if not l: continue
328
+ if l.startswith(';'): continue
329
+ if l.startswith('//'): continue
293
330
  if ';' in l:
294
331
  l = l.split(';', 1)[0].strip()
295
332
  if l:
296
333
  self.lines.append(l)
297
-
298
334
  self.threads = []
299
335
  pc = 0
336
+ depth = 0
300
337
  while pc < len(self.lines):
301
338
  line = self.lines[pc]
302
- 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")):
339
+ if line == "end":
340
+ depth -= 1
341
+ pc += 1
342
+ continue
343
+ if depth == 0 and "(" 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")):
303
344
  parts = line.split("(")
304
- 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 []}
305
- elif line.endswith("{}") and "->" not in line and ">>" not in line and "?" not in line and not line.startswith("_"):
345
+ func_name = parts[0].strip()
346
+ if not func_name.startswith("_"):
347
+ self.functions[func_name] = {"start_pc": pc + 1, "params": [p.strip() for p in parts[1].replace(")", "").split(",")] if parts[1].replace(")", "").strip() else []}
348
+ depth += 1
349
+ elif depth == 0 and line.endswith("{}") and "->" not in line and ">>" not in line and "?" not in line and not line.startswith("_"):
306
350
  pc = self._parse_struct(pc)
307
351
  pc += 1
308
352
  continue
353
+ elif depth > 0 and line.startswith(("if ", "while ", "for ")):
354
+ depth += 1
309
355
  pc += 1
310
356
  for i, line in enumerate(self.lines):
311
357
  if line == "main()":
@@ -325,7 +371,7 @@ class SolInterpreter:
325
371
  fields[key] = self._resolve(val)
326
372
  pc += 1
327
373
  self.struct_blueprints[struct_name] = fields
328
- self._assign(struct_name, fields.copy())
374
+ self.variables[struct_name] = fields.copy()
329
375
  return pc
330
376
 
331
377
  def _call_function(self, name, args):
@@ -348,13 +394,18 @@ class SolInterpreter:
348
394
  return 0
349
395
 
350
396
  def _execute_line(self, line):
351
- # Handle imports
352
397
  if line.startswith("_get("):
353
398
  filename = line.split('"')[1]
354
399
  self._import_file(filename)
355
400
  return
356
-
357
- # Handle piping input
401
+ if line.startswith("_call("):
402
+ inner = line[6:-1].strip()
403
+ parts = [p.strip() for p in inner.split(",")]
404
+ func_name = self._resolve(parts[0])
405
+ if isinstance(func_name, tuple) and func_name[0] == "func_ref":
406
+ func_name = func_name[1]
407
+ args = [self._compute(p) for p in parts[1:]]
408
+ return self._call_function(func_name, args)
358
409
  if ">>" in line and "_in" in line:
359
410
  parts = [p.strip() for p in line.split(">>")]
360
411
  if len(parts) >= 3 and parts[1] == "_in":
@@ -365,19 +416,14 @@ class SolInterpreter:
365
416
  try: self._assign(var_name, float(val) if '.' in val else int(val))
366
417
  except: self._assign(var_name, val)
367
418
  return
368
-
369
- # Check if async
370
419
  is_async = False
371
420
  if ">>" in line:
372
421
  parts = [p.strip() for p in line.split(">>")]
373
422
  if parts[-1] == "_async":
374
423
  line = ">>".join(line.split(">>")[:-1]).strip()
375
424
  is_async = True
376
-
377
425
  if line.startswith("_return ->"):
378
426
  raise ReturnSignal(self._compute(line.split("->", 1)[1]))
379
-
380
- # Handle _out with piping
381
427
  if ">>" in line and "_out" in line:
382
428
  parts = [p.strip() for p in line.split(">>")]
383
429
  if len(parts) == 2 and parts[1] == "_out":
@@ -388,7 +434,6 @@ class SolInterpreter:
388
434
  else:
389
435
  print(val if val is not None else "")
390
436
  return
391
-
392
437
  if line.startswith("_out"):
393
438
  val = self._compute(line.split("->", 1)[1].strip())
394
439
  if is_async:
@@ -397,7 +442,6 @@ class SolInterpreter:
397
442
  else:
398
443
  print(val if val is not None else "")
399
444
  return
400
-
401
445
  if line.startswith("_in"):
402
446
  if "->" in line:
403
447
  var_name = line.split("->", 1)[1].strip()
@@ -406,45 +450,44 @@ class SolInterpreter:
406
450
  try: self._assign(var_name, float(val) if '.' in val else int(val))
407
451
  except: self._assign(var_name, val)
408
452
  return
409
-
410
453
  if "_add(" in line:
411
454
  parts = [p.strip() for p in line.replace("_add(", "").replace(")", "").split(",")]
412
455
  container = self._lookup(parts[0])
413
456
  if isinstance(container, list): container.insert(int(parts[1]), self._resolve(parts[2]))
414
457
  return
415
-
416
458
  if "_remove(" in line:
417
459
  parts = [p.strip() for p in line.replace("_remove(", "").replace(")", "").split(",")]
418
460
  container = self._lookup(parts[0])
419
461
  if isinstance(container, list): container.pop(int(parts[1]))
420
462
  return
421
-
422
463
  if "_listen(" in line:
423
464
  p, h = [p.strip() for p in line.replace("_listen(", "").replace(")", "").split(",")]
424
465
  self._native_listen(self._compute(p), h)
425
466
  return
426
-
427
- # List declaration
428
467
  if "<" in line and ">" in line and "->" not in line and ">>" not in line and "?" not in line and not line.startswith("_"):
429
468
  parts = line.split("<")
430
469
  if len(parts) == 2 and ">" in parts[1]:
431
470
  list_name = parts[1].split(">")[0].strip()
432
471
  self._assign(list_name, [])
433
472
  return
434
-
435
- # Assignment - ONLY with ->
436
473
  if "->" in line:
437
474
  var, val = [x.strip() for x in line.split("->", 1)]
438
475
  if "." in var:
439
- obj, prop = var.split(".", 1)
440
- target = self._lookup(obj)
441
- if isinstance(target, dict): target[prop] = self._compute(val)
476
+ parts = var.split(".")
477
+ target = self._lookup(parts[0])
478
+ if isinstance(target, dict):
479
+ for part in parts[1:-1]:
480
+ if isinstance(target, dict) and part in target:
481
+ target = target[part]
482
+ else:
483
+ target = None
484
+ break
485
+ if target is not None and isinstance(target, dict):
486
+ target[parts[-1]] = self._compute(val)
442
487
  else:
443
488
  computed = self._compute(val)
444
489
  self._assign(var, computed)
445
490
  return
446
-
447
- # Function call
448
491
  if "(" in line and line.endswith(")") and not line.startswith("_"):
449
492
  parts = line.split("(")
450
493
  func_name = parts[0].strip()
@@ -463,6 +506,19 @@ class SolInterpreter:
463
506
  while pc < len(self.lines):
464
507
  line = self.lines[pc]
465
508
  is_executing = all(level["executing"] for level in control_stack)
509
+ if is_executing and "(" 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")):
510
+ func_candidate = line.split("(")[0].strip()
511
+ if func_candidate in self.functions:
512
+ depth = 1
513
+ npc = pc + 1
514
+ while npc < len(self.lines) and depth > 0:
515
+ if self.lines[npc].startswith(("if ", "while ", "for ")):
516
+ depth += 1
517
+ elif self.lines[npc] == "end":
518
+ depth -= 1
519
+ npc += 1
520
+ pc = npc
521
+ continue
466
522
  if line.startswith("if "):
467
523
  control_stack.append({"type": "if", "executing": is_executing and self._evaluate_condition(line.split("if ", 1)[1].strip()), "any_branch_true": False})
468
524
  pc += 1; continue
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "solpl"
7
- version = "2.7.0"
7
+ version = "4.0.0"
8
8
  authors = [
9
9
  { name = "stefand-0", email = "stefand-0@users.noreply.github.com" }
10
10
  ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: solpl
3
- Version: 2.7.0
3
+ Version: 4.0.0
4
4
  Summary: A lightweight, multi-threaded interpreter for the Sol programming language
5
5
  Author-email: stefand-0 <stefand-0@users.noreply.github.com>
6
6
  License-Expression: Apache-2.0
File without changes
File without changes
File without changes
File without changes