zexus 1.8.1 → 1.8.2

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.
@@ -31,6 +31,7 @@ from ..object import (
31
31
  Map as ZMap,
32
32
  Null as ZNull,
33
33
  EvaluationError as ZEvaluationError,
34
+ DateTime as ZDateTime,
34
35
  )
35
36
 
36
37
  # ==================== Backend / Optional Imports ====================
@@ -782,6 +783,9 @@ class VM:
782
783
  vm.enable_fast_loop = getattr(parent_vm, "enable_fast_loop", False)
783
784
  vm.fast_loop_threshold = getattr(parent_vm, "fast_loop_threshold", 512)
784
785
  vm._fast_loop_stats = {"used": False, "reason": ""}
786
+ # SECURITY (H9): Call-depth tracking for child VMs
787
+ vm._call_depth = 0
788
+ vm._MAX_CALL_DEPTH = getattr(parent_vm, "_MAX_CALL_DEPTH", 256)
785
789
 
786
790
  # Settings
787
791
  vm.worker_count = parent_vm.worker_count
@@ -921,6 +925,82 @@ class VM:
921
925
  self.builtins["__vm_use_module__"] = self._vm_use_module
922
926
  if "__vm_from_module__" not in self.builtins:
923
927
  self.builtins["__vm_from_module__"] = self._vm_from_module
928
+ # Channel builtins for VM concurrency support
929
+ if "__create_channel__" not in self.builtins:
930
+ self.builtins["__create_channel__"] = self._vm_create_channel
931
+ if "send" not in self.builtins:
932
+ self.builtins["send"] = self._vm_send
933
+ if "receive" not in self.builtins:
934
+ self.builtins["receive"] = self._vm_receive
935
+ if "close_channel" not in self.builtins:
936
+ self.builtins["close_channel"] = self._vm_close_channel
937
+
938
+ # --- Channel builtins for VM ---
939
+ def _vm_create_channel(self, name, element_type=None, capacity=0):
940
+ """Create a channel and store it in the VM environment."""
941
+ try:
942
+ from ..concurrency_system import Channel
943
+ except ImportError:
944
+ # Fallback: use a simple queue-based channel
945
+ import queue as _queue_mod
946
+
947
+ class _SimpleChannel:
948
+ def __init__(self, name, capacity=0):
949
+ self.name = name
950
+ self._queue = _queue_mod.Queue(maxsize=capacity if capacity else 0)
951
+ self._closed = False
952
+
953
+ def send(self, value, timeout=5.0):
954
+ if self._closed:
955
+ raise RuntimeError(f"Channel '{self.name}' is closed")
956
+ self._queue.put(value, timeout=timeout)
957
+
958
+ def receive(self, timeout=5.0):
959
+ if self._closed and self._queue.empty():
960
+ return None
961
+ try:
962
+ return self._queue.get(timeout=timeout)
963
+ except _queue_mod.Empty:
964
+ return None
965
+
966
+ def close(self):
967
+ self._closed = True
968
+
969
+ Channel = _SimpleChannel
970
+
971
+ cap = int(capacity) if capacity else 0
972
+ ch = Channel(name=name, capacity=cap) if element_type is None else Channel(name=name, element_type=element_type, capacity=cap)
973
+ self.env[name] = ch
974
+ return ch
975
+
976
+ def _vm_send(self, channel, value):
977
+ """Send value to channel."""
978
+ if channel is None or not hasattr(channel, 'send'):
979
+ return None
980
+ try:
981
+ channel.send(value, timeout=5.0)
982
+ except Exception:
983
+ pass
984
+ return None
985
+
986
+ def _vm_receive(self, channel):
987
+ """Receive value from channel."""
988
+ if channel is None or not hasattr(channel, 'receive'):
989
+ return None
990
+ try:
991
+ return channel.receive(timeout=5.0)
992
+ except Exception:
993
+ return None
994
+
995
+ def _vm_close_channel(self, channel):
996
+ """Close a channel."""
997
+ if channel is None or not hasattr(channel, 'close'):
998
+ return None
999
+ try:
1000
+ channel.close()
1001
+ except Exception:
1002
+ pass
1003
+ return None
924
1004
 
925
1005
  def _vm_use_module(self, spec):
926
1006
  if spec is None:
@@ -2027,15 +2107,32 @@ class VM:
2027
2107
  a = stack_pop() if stack else 0
2028
2108
  if hasattr(a, 'value'): a = a.value
2029
2109
  if hasattr(b, 'value'): b = b.value
2030
- stack_append(a + b)
2110
+ # Type coercion: if either operand is a string, convert both to string
2111
+ if isinstance(a, str) or isinstance(b, str):
2112
+ stack_append(str(a if a is not None else '') + str(b if b is not None else ''))
2113
+ else:
2114
+ if a is None: a = 0
2115
+ if b is None: b = 0
2116
+ stack_append(a + b)
2031
2117
  elif op_name == "SUB":
2032
- b = stack_pop() if stack else 0
2033
- a = stack_pop() if stack else 0
2034
- if hasattr(a, 'value'): a = a.value
2035
- if hasattr(b, 'value'): b = b.value
2036
- if a is None: a = 0
2037
- if b is None: b = 0
2038
- stack_append(a - b)
2118
+ b_raw = stack_pop() if stack else 0
2119
+ a_raw = stack_pop() if stack else 0
2120
+ # DateTime arithmetic
2121
+ if isinstance(a_raw, ZDateTime) and isinstance(b_raw, ZDateTime):
2122
+ stack_append(a_raw.timestamp - b_raw.timestamp)
2123
+ elif isinstance(a_raw, ZDateTime):
2124
+ b = b_raw.value if hasattr(b_raw, 'value') else b_raw
2125
+ if b is None: b = 0
2126
+ stack_append(ZDateTime(a_raw.timestamp - float(b)))
2127
+ else:
2128
+ a = a_raw.value if hasattr(a_raw, 'value') else a_raw
2129
+ b = b_raw.value if hasattr(b_raw, 'value') else b_raw
2130
+ if a is None: a = 0
2131
+ if b is None: b = 0
2132
+ try:
2133
+ stack_append(a - b)
2134
+ except TypeError:
2135
+ stack_append(0)
2039
2136
  elif op_name == "MUL":
2040
2137
  b = stack_pop() if stack else 0
2041
2138
  a = stack_pop() if stack else 0
@@ -2578,6 +2675,48 @@ class VM:
2578
2675
  if caller and caller not in allowed:
2579
2676
  raise ZEvaluationError(f"Access denied: '{r_key}' restricted to allowed addresses")
2580
2677
 
2678
+ elif op_name == "SPAWN":
2679
+ # Sync SPAWN: schedule the function to run in a background thread
2680
+ import threading
2681
+ val = stack_pop() if stack else None
2682
+ if callable(val):
2683
+ # val is a callable — run it in a daemon thread
2684
+ t = threading.Thread(target=val, daemon=True)
2685
+ t.start()
2686
+ stack_append(t)
2687
+ elif isinstance(val, dict) and "bytecode" in val:
2688
+ # val is a function descriptor — execute its bytecode in a thread
2689
+ func_desc = val
2690
+ def _run_func_desc(fd=func_desc):
2691
+ try:
2692
+ child_vm = VM.create_child(parent_vm=self, env=dict(env))
2693
+ child_vm._run_stack_bytecode_sync(fd["bytecode"], debug=False)
2694
+ self._return_vm_to_pool(child_vm)
2695
+ except Exception:
2696
+ pass
2697
+ t = threading.Thread(target=_run_func_desc, daemon=True)
2698
+ t.start()
2699
+ stack_append(t)
2700
+ else:
2701
+ # Already a result (not spawnable) — just push it back
2702
+ stack_append(val)
2703
+
2704
+ elif op_name == "AWAIT":
2705
+ import threading
2706
+ val = stack_pop() if stack else None
2707
+ if isinstance(val, threading.Thread):
2708
+ val.join(timeout=30)
2709
+ stack_append(None)
2710
+ elif isinstance(val, str) and val in self._tasks:
2711
+ # asyncio task — run in event loop
2712
+ try:
2713
+ result = self._run_coroutine_sync(self._tasks[val])
2714
+ stack_append(result)
2715
+ except Exception:
2716
+ stack_append(None)
2717
+ else:
2718
+ stack_append(val)
2719
+
2581
2720
  else:
2582
2721
  # Truly unknown op — fallback to async path
2583
2722
  return self._run_coroutine_sync(self._run_stack_bytecode(bytecode, debug))
@@ -2693,16 +2832,18 @@ class VM:
2693
2832
  if fn is None:
2694
2833
  return None
2695
2834
  # SECURITY (H9): Enforce call-depth limit
2696
- if self._call_depth >= self._MAX_CALL_DEPTH:
2835
+ call_depth = getattr(self, '_call_depth', 0)
2836
+ max_depth = getattr(self, '_MAX_CALL_DEPTH', 256)
2837
+ if call_depth >= max_depth:
2697
2838
  raise RecursionError(
2698
- f"VM call depth exceeded maximum ({self._MAX_CALL_DEPTH}). "
2839
+ f"VM call depth exceeded maximum ({max_depth}). "
2699
2840
  "Possible infinite recursion."
2700
2841
  )
2701
- self._call_depth += 1
2842
+ self._call_depth = call_depth + 1
2702
2843
  try:
2703
2844
  return self._invoke_callable_sync_inner(fn, args)
2704
2845
  finally:
2705
- self._call_depth -= 1
2846
+ self._call_depth = getattr(self, '_call_depth', 1) - 1
2706
2847
 
2707
2848
  def _invoke_callable_sync_inner(self, fn, args):
2708
2849
  """Inner implementation of callable invocation."""
@@ -3074,22 +3215,57 @@ class VM:
3074
3215
 
3075
3216
  def _binary_op(func):
3076
3217
  def wrapper(_):
3077
- b = _unwrap(stack.pop() if stack else 0)
3078
- a = _unwrap(stack.pop() if stack else 0)
3079
- if a is None: a = 0
3080
- if b is None: b = 0
3218
+ b_raw = stack.pop() if stack else 0
3219
+ a_raw = stack.pop() if stack else 0
3220
+ # Keep raw objects for DateTime-aware arithmetic
3221
+ a = _unwrap(a_raw)
3222
+ b = _unwrap(b_raw)
3081
3223
  if isinstance(a, ZEvaluationError):
3082
3224
  stack.append(a)
3083
3225
  return
3084
3226
  if isinstance(b, ZEvaluationError):
3085
3227
  stack.append(b)
3086
3228
  return
3229
+ # DateTime arithmetic
3230
+ if isinstance(a_raw, ZDateTime) or isinstance(b_raw, ZDateTime):
3231
+ a_dt = a_raw if isinstance(a_raw, ZDateTime) else None
3232
+ b_dt = b_raw if isinstance(b_raw, ZDateTime) else None
3233
+ if a_dt and b_dt:
3234
+ # DateTime - DateTime = seconds difference
3235
+ stack.append(a_dt.timestamp - b_dt.timestamp)
3236
+ elif a_dt:
3237
+ # DateTime +/- number
3238
+ val = float(b) if b is not None else 0.0
3239
+ try:
3240
+ result = func(a_dt.timestamp, val)
3241
+ stack.append(ZDateTime(result))
3242
+ except Exception:
3243
+ stack.append(0)
3244
+ else:
3245
+ val = float(a) if a is not None else 0.0
3246
+ try:
3247
+ result = func(val, b_dt.timestamp)
3248
+ stack.append(result)
3249
+ except Exception:
3250
+ stack.append(0)
3251
+ return
3252
+ if a is None: a = 0
3253
+ if b is None: b = 0
3254
+ # Type coercion for string concatenation in ADD
3255
+ if isinstance(a, str) or isinstance(b, str):
3256
+ try:
3257
+ stack.append(func(a, b))
3258
+ except TypeError:
3259
+ stack.append(str(a) + str(b))
3260
+ return
3087
3261
  try:
3088
3262
  stack.append(func(a, b))
3089
- except Exception as exc:
3090
- if os.environ.get("ZEXUS_VM_PROFILE_OPS"):
3091
- print(f"[VM DEBUG] binary op error func={func} a={a!r} b={b!r} exc={exc}")
3092
- raise
3263
+ except TypeError:
3264
+ # Graceful fallback — coerce to strings
3265
+ try:
3266
+ stack.append(str(a) + str(b))
3267
+ except Exception:
3268
+ stack.append(0)
3093
3269
  return wrapper
3094
3270
 
3095
3271
  def _binary_bool_op(func):
@@ -3394,19 +3570,41 @@ class VM:
3394
3570
  stack.append(-a)
3395
3571
 
3396
3572
  def _op_add(_):
3397
- b = _unwrap(stack_pop() if stack else 0)
3398
- a = _unwrap(stack_pop() if stack else 0)
3399
- if a is None:
3400
- a = 0
3401
- if b is None:
3402
- b = 0
3573
+ b_raw = stack_pop() if stack else 0
3574
+ a_raw = stack_pop() if stack else 0
3575
+ # DateTime arithmetic
3576
+ if isinstance(a_raw, ZDateTime) or isinstance(b_raw, ZDateTime):
3577
+ a_dt = a_raw if isinstance(a_raw, ZDateTime) else None
3578
+ b_dt = b_raw if isinstance(b_raw, ZDateTime) else None
3579
+ if a_dt and b_dt:
3580
+ stack_append(a_dt.timestamp + b_dt.timestamp)
3581
+ elif a_dt:
3582
+ b = _unwrap(b_raw)
3583
+ if b is None: b = 0
3584
+ stack_append(ZDateTime(a_dt.timestamp + float(b)))
3585
+ else:
3586
+ a = _unwrap(a_raw)
3587
+ if a is None: a = 0
3588
+ stack_append(ZDateTime(b_raw.timestamp + float(a)))
3589
+ return
3590
+ b = _unwrap(b_raw)
3591
+ a = _unwrap(a_raw)
3403
3592
  if isinstance(a, ZEvaluationError):
3404
3593
  stack_append(a)
3405
3594
  return
3406
3595
  if isinstance(b, ZEvaluationError):
3407
3596
  stack_append(b)
3408
3597
  return
3409
- stack_append(a + b)
3598
+ # Type coercion: if either operand is a string, convert both to string
3599
+ if isinstance(a, str) or isinstance(b, str):
3600
+ stack_append(str(a if a is not None else '') + str(b if b is not None else ''))
3601
+ else:
3602
+ if a is None: a = 0
3603
+ if b is None: b = 0
3604
+ try:
3605
+ stack_append(a + b)
3606
+ except TypeError:
3607
+ stack_append(str(a) + str(b))
3410
3608
 
3411
3609
  def _op_not(_):
3412
3610
  a = stack.pop() if stack else False
@@ -4135,12 +4333,31 @@ class VM:
4135
4333
  # Auto-unwrap evaluator objects
4136
4334
  if hasattr(a, 'value'): a = a.value
4137
4335
  if hasattr(b, 'value'): b = b.value
4138
- stack.append(a + b)
4336
+ # Type coercion: if either operand is a string, convert both to string
4337
+ if isinstance(a, str) or isinstance(b, str):
4338
+ stack.append(str(a if a is not None else '') + str(b if b is not None else ''))
4339
+ else:
4340
+ if a is None: a = 0
4341
+ if b is None: b = 0
4342
+ stack.append(a + b)
4139
4343
  elif op_name == "SUB":
4140
- b = stack.pop() if stack else 0; a = stack.pop() if stack else 0
4141
- if hasattr(a, 'value'): a = a.value
4142
- if hasattr(b, 'value'): b = b.value
4143
- stack.append(a - b)
4344
+ b_raw = stack.pop() if stack else 0; a_raw = stack.pop() if stack else 0
4345
+ # DateTime arithmetic
4346
+ if isinstance(a_raw, ZDateTime) and isinstance(b_raw, ZDateTime):
4347
+ stack.append(a_raw.timestamp - b_raw.timestamp)
4348
+ elif isinstance(a_raw, ZDateTime):
4349
+ b = b_raw.value if hasattr(b_raw, 'value') else b_raw
4350
+ if b is None: b = 0
4351
+ stack.append(ZDateTime(a_raw.timestamp - float(b)))
4352
+ else:
4353
+ a = a_raw.value if hasattr(a_raw, 'value') else a_raw
4354
+ b = b_raw.value if hasattr(b_raw, 'value') else b_raw
4355
+ if a is None: a = 0
4356
+ if b is None: b = 0
4357
+ try:
4358
+ stack.append(a - b)
4359
+ except TypeError:
4360
+ stack.append(0)
4144
4361
  elif op_name == "MUL":
4145
4362
  b = stack.pop() if stack else 0; a = stack.pop() if stack else 0
4146
4363
  if hasattr(a, 'value'): a = a.value
@@ -23,7 +23,7 @@ class PackageManager:
23
23
  self.installer = PackageInstaller(self.zpm_dir)
24
24
  self.publisher = PackagePublisher(self.registry)
25
25
 
26
- def init(self, name: str = None, version: str = "1.8.1") -> Dict:
26
+ def init(self, name: str = None, version: str = "1.8.2") -> Dict:
27
27
  """Initialize a new Zexus project with package.json"""
28
28
  if self.config_file.exists():
29
29
  print(f"⚠️ {self.config_file} already exists")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: zexus
3
- Version: 1.8.1
3
+ Version: 1.8.2
4
4
  Summary: A modern, security-first programming language with blockchain support
5
5
  Home-page: https://github.com/Zaidux/zexus-interpreter
6
6
  Author: Zaidux
@@ -1,5 +1,6 @@
1
1
  .gitattributes
2
2
  .gitignore
3
+ .npmignore
3
4
  ADDITIONAL_FIXES_1.6.7.md
4
5
  BLOCKCHAIN_MODULE_FULL_SOURCE.md
5
6
  CHANGELOG.md
@@ -165,18 +166,25 @@ bin/zx-dev
165
166
  bin/zx-run
166
167
  blockchain_demo/address_prefix.zx
167
168
  blockchain_demo/dex.zx
169
+ blockchain_demo/dex.zxc
168
170
  blockchain_demo/governance.zx
171
+ blockchain_demo/governance.zxc
169
172
  blockchain_demo/main.zx
170
173
  blockchain_demo/multichain_bridge.zx
171
174
  blockchain_demo/nft.zx
175
+ blockchain_demo/nft.zxc
172
176
  blockchain_demo/staking.zx
177
+ blockchain_demo/staking.zxc
173
178
  blockchain_demo/test_import_closure.zx
174
179
  blockchain_demo/test_module.zx
175
180
  blockchain_demo/test_utils_import.zx
176
181
  blockchain_demo/test_utils_import2.zx
177
182
  blockchain_demo/token.zx
183
+ blockchain_demo/token.zxc
178
184
  blockchain_demo/utils.zx
185
+ blockchain_demo/utils.zxc
179
186
  blockchain_demo/wallet.zx
187
+ blockchain_demo/wallet.zxc
180
188
  blockchain_test/PARSER_FIX_DATA_DECLARATIONS.md
181
189
  blockchain_test/PERFORMANCE_ANALYSIS.md
182
190
  blockchain_test/PERFORMANCE_FINAL_REPORT.md
@@ -583,6 +591,7 @@ src/zexus/zexus_token.py
583
591
  src/zexus.egg-info/PKG-INFO
584
592
  src/zexus.egg-info/SOURCES.txt
585
593
  src/zexus.egg-info/dependency_links.txt
594
+ src/zexus.egg-info/entry_points.txt
586
595
  src/zexus.egg-info/not-zip-safe
587
596
  src/zexus.egg-info/requires.txt
588
597
  src/zexus.egg-info/top_level.txt
@@ -0,0 +1,8 @@
1
+ [console_scripts]
2
+ zexus = zexus.cli.main:cli
3
+ zpics = zexus.cli.main:cli
4
+ zpm = zexus.cli.zpm:cli
5
+ zx = zexus.cli.main:cli
6
+ zx-deploy = zexus.cli.main:cli
7
+ zx-dev = zexus.cli.main:cli
8
+ zx-run = zexus.cli.main:cli