zexus 1.8.2 → 1.8.3

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.
Files changed (37) hide show
  1. package/README.md +89 -64
  2. package/package.json +1 -1
  3. package/rust_core/Cargo.lock +1 -1
  4. package/src/zexus/__init__.py +1 -1
  5. package/src/zexus/builtin_modules.py +50 -13
  6. package/src/zexus/cli/main.py +46 -1
  7. package/src/zexus/cli/zpm.py +1 -1
  8. package/src/zexus/evaluator/bytecode_compiler.py +11 -2
  9. package/src/zexus/evaluator/core.py +4 -1
  10. package/src/zexus/evaluator/expressions.py +11 -2
  11. package/src/zexus/evaluator/functions.py +72 -0
  12. package/src/zexus/evaluator/resource_limiter.py +1 -1
  13. package/src/zexus/evaluator/statements.py +44 -4
  14. package/src/zexus/kernel/__init__.py +34 -0
  15. package/src/zexus/kernel/hooks.py +276 -0
  16. package/src/zexus/kernel/registry.py +203 -0
  17. package/src/zexus/kernel/zir/__init__.py +145 -0
  18. package/src/zexus/lexer.py +7 -0
  19. package/src/zexus/object.py +28 -5
  20. package/src/zexus/parser/parser.py +53 -11
  21. package/src/zexus/parser/strategy_context.py +179 -10
  22. package/src/zexus/security.py +26 -2
  23. package/src/zexus/stdlib/blockchain.py +84 -0
  24. package/src/zexus/stdlib/http_server.py +2 -2
  25. package/src/zexus/stdlib/math.py +25 -17
  26. package/src/zexus/stdlib_integration.py +119 -2
  27. package/src/zexus/type_checker.py +17 -12
  28. package/src/zexus/vm/compiler.py +57 -6
  29. package/src/zexus/vm/fastops.c +4704 -1263
  30. package/src/zexus/vm/fastops.cpython-312-x86_64-linux-gnu.so +0 -0
  31. package/src/zexus/vm/fastops.pyx +81 -3
  32. package/src/zexus/vm/optimizer.py +65 -27
  33. package/src/zexus/vm/vm.py +871 -98
  34. package/src/zexus/zexus_ast.py +4 -1
  35. package/src/zexus/zpm/package_manager.py +1 -1
  36. package/src/zexus.egg-info/PKG-INFO +90 -65
  37. package/src/zexus.egg-info/SOURCES.txt +51 -0
@@ -148,7 +148,12 @@ def execute(list instrs, list consts, dict env, dict builtins, dict closure_cell
148
148
  a = a.value
149
149
  if hasattr(b, "value"):
150
150
  b = b.value
151
- stack.append(a / b if b != 0 else 0)
151
+ if b == 0:
152
+ raise ZeroDivisionError("Division by zero")
153
+ elif isinstance(a, int) and not isinstance(a, bool) and isinstance(b, int) and not isinstance(b, bool):
154
+ stack.append(a // b if a % b == 0 else a / b)
155
+ else:
156
+ stack.append(a / b)
152
157
  elif op_name == "MOD":
153
158
  b = stack.pop() if stack else 1
154
159
  a = stack.pop() if stack else 0
@@ -197,10 +202,14 @@ def execute(list instrs, list consts, dict env, dict builtins, dict closure_cell
197
202
  stack.append(elements)
198
203
  elif op_name == "BUILD_MAP":
199
204
  count = operand if operand is not None else 0
200
- result = {}
205
+ pairs = []
201
206
  for _ in range(count):
202
207
  val = stack.pop(); key = stack.pop()
203
- result[key] = val
208
+ pairs.append((key, val))
209
+ pairs.reverse()
210
+ result = {}
211
+ for k, v in pairs:
212
+ result[k] = v
204
213
  stack.append(result)
205
214
  elif op_name == "BUILD_SET":
206
215
  count = operand if operand is not None else 0
@@ -214,10 +223,79 @@ def execute(list instrs, list consts, dict env, dict builtins, dict closure_cell
214
223
  stack.append(obj.get(idx))
215
224
  elif isinstance(obj, ZMap):
216
225
  stack.append(obj.get(idx))
226
+ elif hasattr(obj, 'data') and hasattr(obj, 'entity_def'):
227
+ idx_str = idx.value if hasattr(idx, 'value') else str(idx) if idx is not None else None
228
+ if idx_str:
229
+ val = obj.get(idx_str)
230
+ if hasattr(val, 'value'):
231
+ stack.append(val.value)
232
+ else:
233
+ stack.append(val)
234
+ else:
235
+ stack.append(None)
236
+ elif isinstance(obj, dict):
237
+ raw_idx = idx.value if hasattr(idx, 'value') else idx
238
+ if raw_idx in obj:
239
+ stack.append(obj[raw_idx])
240
+ elif isinstance(raw_idx, int) and not isinstance(raw_idx, bool):
241
+ keys = list(obj.keys())
242
+ if 0 <= raw_idx < len(keys):
243
+ stack.append(obj[keys[raw_idx]])
244
+ else:
245
+ stack.append(None)
246
+ else:
247
+ stack.append(None)
217
248
  else:
218
249
  stack.append(obj[idx] if obj is not None else None)
219
250
  except Exception:
220
251
  stack.append(None)
252
+ elif op_name == "FOR_ITER":
253
+ var_count = operand if operand else 1
254
+ idx = stack.pop() if stack else 0
255
+ obj = stack.pop() if stack else None
256
+ try:
257
+ if isinstance(obj, dict):
258
+ keys = list(obj.keys())
259
+ if isinstance(idx, int) and 0 <= idx < len(keys):
260
+ key = keys[idx]
261
+ if var_count == 2:
262
+ stack.append(key)
263
+ stack.append(obj[key])
264
+ else:
265
+ stack.append(key)
266
+ else:
267
+ for _ in range(var_count):
268
+ stack.append(None)
269
+ elif isinstance(obj, ZMap):
270
+ keys = list(obj.pairs.keys())
271
+ if isinstance(idx, int) and 0 <= idx < len(keys):
272
+ key = keys[idx]
273
+ if var_count == 2:
274
+ stack.append(key)
275
+ stack.append(obj.pairs[key])
276
+ else:
277
+ stack.append(key)
278
+ else:
279
+ for _ in range(var_count):
280
+ stack.append(None)
281
+ elif isinstance(obj, (list, ZList)):
282
+ elems = obj.elements if isinstance(obj, ZList) else obj
283
+ if isinstance(idx, int) and 0 <= idx < len(elems):
284
+ if var_count == 2:
285
+ stack.append(idx)
286
+ stack.append(elems[idx])
287
+ else:
288
+ stack.append(elems[idx])
289
+ else:
290
+ for _ in range(var_count):
291
+ stack.append(None)
292
+ else:
293
+ raise NotImplementedError("FOR_ITER target type not supported")
294
+ except NotImplementedError:
295
+ raise
296
+ except Exception:
297
+ for _ in range(var_count):
298
+ stack.append(None)
221
299
  elif op_name == "SLICE":
222
300
  end = stack.pop() if stack else None
223
301
  start = stack.pop() if stack else None
@@ -81,6 +81,9 @@ class BytecodeOptimizer:
81
81
  "JUMP_BACKWARD",
82
82
  "LABEL",
83
83
  "JUMP_TARGET",
84
+ "SETUP_TRY",
85
+ "POP_TRY",
86
+ "THROW",
84
87
  }
85
88
 
86
89
  def _has_control_flow(self, instructions: List[Tuple[str, Any]]) -> bool:
@@ -94,9 +97,14 @@ class BytecodeOptimizer:
94
97
  """Validate that jump targets are sane and within bounds."""
95
98
  max_index = len(instructions) - 1
96
99
  for idx, (op, operand) in enumerate(instructions):
97
- if op in ("JUMP", "JUMP_IF_FALSE", "JUMP_IF_TRUE", "JUMP_FORWARD", "JUMP_BACKWARD"):
100
+ if op in ("JUMP", "JUMP_IF_FALSE", "JUMP_IF_TRUE", "JUMP_FORWARD", "JUMP_BACKWARD", "SETUP_TRY"):
98
101
  if not isinstance(operand, int):
99
- return False
102
+ # SETUP_TRY and others require an int target
103
+ if op == "SETUP_TRY":
104
+ return False
105
+ if op in ("JUMP", "JUMP_IF_FALSE", "JUMP_IF_TRUE"):
106
+ return False
107
+ continue
100
108
  if operand < 0 or operand > max_index:
101
109
  return False
102
110
  if op == "JUMP" and operand == idx:
@@ -404,8 +412,9 @@ class BytecodeOptimizer:
404
412
 
405
413
  def _dead_code_elimination(self, instructions: List[Tuple[str, Any]]) -> List[Tuple[str, Any]]:
406
414
  """
407
- Remove unreachable code after RETURN, unconditional JUMP, etc.
415
+ Remove unreachable code after RETURN, unconditional JUMP, THROW, etc.
408
416
  Jump targets are remapped after dead code removal to keep control flow valid.
417
+ SETUP_TRY handler targets are treated as jump targets (catch blocks are reachable).
409
418
  """
410
419
  result = []
411
420
  in_dead_code = False
@@ -413,14 +422,24 @@ class BytecodeOptimizer:
413
422
  removed_indices: Set[int] = set()
414
423
 
415
424
  for idx, (op, operand) in enumerate(instructions):
425
+ # Standard jumps
416
426
  if op in ("JUMP", "JUMP_IF_FALSE", "JUMP_IF_TRUE", "JUMP_FORWARD", "JUMP_BACKWARD"):
417
427
  if isinstance(operand, int):
418
428
  jump_targets.add(operand)
429
+ # SETUP_TRY handler addresses are also jump targets (catch blocks)
430
+ elif op == "SETUP_TRY":
431
+ if isinstance(operand, int):
432
+ jump_targets.add(operand)
419
433
 
420
434
  for idx, (op, operand) in enumerate(instructions):
421
435
  if in_dead_code:
422
- # Skip until we hit a jump target or label
423
- if op in ("LABEL", "JUMP_TARGET") or idx in jump_targets:
436
+ # Stop skipping at jump targets, labels, or NOP labels
437
+ is_label = (
438
+ op in ("LABEL", "JUMP_TARGET")
439
+ or idx in jump_targets
440
+ or (op == "NOP" and isinstance(operand, str) and operand.startswith("L"))
441
+ )
442
+ if is_label:
424
443
  in_dead_code = False
425
444
  result.append((op, operand))
426
445
  else:
@@ -429,7 +448,7 @@ class BytecodeOptimizer:
429
448
  else:
430
449
  result.append((op, operand))
431
450
  # Mark dead code after unconditional control flow
432
- if op in ("RETURN", "JUMP"):
451
+ if op in ("RETURN", "JUMP", "THROW"):
433
452
  in_dead_code = True
434
453
 
435
454
  # Remap jump targets after dead code removal
@@ -441,7 +460,8 @@ class BytecodeOptimizer:
441
460
  index_map[old_idx] = new_idx
442
461
  new_idx += 1
443
462
 
444
- jump_ops = ("JUMP", "JUMP_IF_FALSE", "JUMP_IF_TRUE", "JUMP_FORWARD", "JUMP_BACKWARD")
463
+ # Include SETUP_TRY in jump remapping — its operand is a handler address
464
+ jump_ops = ("JUMP", "JUMP_IF_FALSE", "JUMP_IF_TRUE", "JUMP_FORWARD", "JUMP_BACKWARD", "SETUP_TRY")
445
465
  remapped = []
446
466
  for op, operand in result:
447
467
  if op in jump_ops and isinstance(operand, int) and operand in index_map:
@@ -569,40 +589,58 @@ class BytecodeOptimizer:
569
589
 
570
590
  def _strength_reduction(self, instructions: List[Tuple[str, Any]]) -> List[Tuple[str, Any]]:
571
591
  """
572
- Replace expensive operations with cheaper equivalents
573
-
574
- Examples:
575
- x * 2 x + x (addition cheaper than multiplication)
576
- x ** 2 → x * x
577
- x / 2 → x * 0.5 (if floating point)
592
+ Replace expensive operations with cheaper equivalents.
593
+
594
+ Patterns recognised (where *val* comes from ``LOAD_CONST``):
595
+ ``val * 2`` ``val + val`` (addition cheaper than multiplication)
596
+ ``val ** 2````val * val`` (avoid exponentiation)
597
+
598
+ Only fires when the constant is a literal ``2`` so that the
599
+ semantics are bit-identical for integers.
578
600
  """
579
601
  result = []
580
602
  i = 0
581
-
603
+
582
604
  while i < len(instructions):
583
- # Pattern: multiply by power of 2
605
+ # We need a 3-instruction window: LOAD_x LOAD_CONST OP
584
606
  if i + 2 < len(instructions):
585
607
  op1, operand1 = instructions[i]
586
608
  op2, operand2 = instructions[i + 1]
587
- op3, operand3 = instructions[i + 2]
588
-
589
- # x * 2 x + x (cheaper)
590
- if op1 == "LOAD_NAME" and op2 == "LOAD_CONST" and op3 == "MUL":
591
- # This is simplified - would need constant value check
592
- pass
593
-
609
+ op3, _ = instructions[i + 2]
610
+
611
+ if op2 == "LOAD_CONST" and operand2 == 2:
612
+ # x * 2 x + x (DUP + ADD)
613
+ if op3 == "MUL":
614
+ result.append(instructions[i]) # LOAD_x
615
+ result.append(("DUP", None))
616
+ result.append(("ADD", None))
617
+ self.stats.strength_reductions += 1
618
+ i += 3
619
+ continue
620
+
621
+ # x ** 2 → x * x (DUP + MUL)
622
+ if op3 == "POW":
623
+ result.append(instructions[i]) # LOAD_x
624
+ result.append(("DUP", None))
625
+ result.append(("MUL", None))
626
+ self.stats.strength_reductions += 1
627
+ i += 3
628
+ continue
629
+
594
630
  result.append(instructions[i])
595
631
  i += 1
596
-
632
+
597
633
  return result
598
634
 
599
635
  def _loop_invariant_code_motion(self, instructions: List[Tuple[str, Any]]) -> List[Tuple[str, Any]]:
600
636
  """
601
- Move loop-invariant computations outside loops
602
-
603
- This is experimental and requires loop detection
637
+ Move loop-invariant computations outside loops.
638
+
639
+ This is experimental and requires loop detection (back-edge
640
+ analysis). Currently a **no-op** — the instruction list is
641
+ returned unchanged. Gated behind ``level >= 3`` so it never
642
+ fires unless explicitly requested.
604
643
  """
605
- # Placeholder for future implementation
606
644
  return instructions
607
645
 
608
646
  def get_stats(self) -> Dict[str, Any]: