zexus 1.6.8 → 1.7.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.
Files changed (177) hide show
  1. package/README.md +12 -5
  2. package/package.json +1 -1
  3. package/src/__init__.py +7 -0
  4. package/src/zexus/__init__.py +1 -1
  5. package/src/zexus/__pycache__/__init__.cpython-312.pyc +0 -0
  6. package/src/zexus/__pycache__/capability_system.cpython-312.pyc +0 -0
  7. package/src/zexus/__pycache__/debug_sanitizer.cpython-312.pyc +0 -0
  8. package/src/zexus/__pycache__/environment.cpython-312.pyc +0 -0
  9. package/src/zexus/__pycache__/error_reporter.cpython-312.pyc +0 -0
  10. package/src/zexus/__pycache__/input_validation.cpython-312.pyc +0 -0
  11. package/src/zexus/__pycache__/lexer.cpython-312.pyc +0 -0
  12. package/src/zexus/__pycache__/module_cache.cpython-312.pyc +0 -0
  13. package/src/zexus/__pycache__/module_manager.cpython-312.pyc +0 -0
  14. package/src/zexus/__pycache__/object.cpython-312.pyc +0 -0
  15. package/src/zexus/__pycache__/security.cpython-312.pyc +0 -0
  16. package/src/zexus/__pycache__/security_enforcement.cpython-312.pyc +0 -0
  17. package/src/zexus/__pycache__/syntax_validator.cpython-312.pyc +0 -0
  18. package/src/zexus/__pycache__/zexus_ast.cpython-312.pyc +0 -0
  19. package/src/zexus/__pycache__/zexus_token.cpython-312.pyc +0 -0
  20. package/src/zexus/access_control_system/__pycache__/__init__.cpython-312.pyc +0 -0
  21. package/src/zexus/access_control_system/__pycache__/access_control.cpython-312.pyc +0 -0
  22. package/src/zexus/advanced_types.py +17 -2
  23. package/src/zexus/blockchain/__init__.py +411 -0
  24. package/src/zexus/blockchain/accelerator.py +1160 -0
  25. package/src/zexus/blockchain/chain.py +660 -0
  26. package/src/zexus/blockchain/consensus.py +821 -0
  27. package/src/zexus/blockchain/contract_vm.py +1019 -0
  28. package/src/zexus/blockchain/crypto.py +79 -14
  29. package/src/zexus/blockchain/events.py +526 -0
  30. package/src/zexus/blockchain/loadtest.py +721 -0
  31. package/src/zexus/blockchain/monitoring.py +350 -0
  32. package/src/zexus/blockchain/mpt.py +716 -0
  33. package/src/zexus/blockchain/multichain.py +951 -0
  34. package/src/zexus/blockchain/multiprocess_executor.py +338 -0
  35. package/src/zexus/blockchain/network.py +886 -0
  36. package/src/zexus/blockchain/node.py +666 -0
  37. package/src/zexus/blockchain/rpc.py +1203 -0
  38. package/src/zexus/blockchain/rust_bridge.py +421 -0
  39. package/src/zexus/blockchain/storage.py +423 -0
  40. package/src/zexus/blockchain/tokens.py +750 -0
  41. package/src/zexus/blockchain/upgradeable.py +1004 -0
  42. package/src/zexus/blockchain/verification.py +1602 -0
  43. package/src/zexus/blockchain/wallet.py +621 -0
  44. package/src/zexus/capability_system.py +184 -9
  45. package/src/zexus/cli/__pycache__/main.cpython-312.pyc +0 -0
  46. package/src/zexus/cli/main.py +383 -34
  47. package/src/zexus/cli/zpm.py +1 -1
  48. package/src/zexus/compiler/__pycache__/bytecode.cpython-312.pyc +0 -0
  49. package/src/zexus/compiler/__pycache__/lexer.cpython-312.pyc +0 -0
  50. package/src/zexus/compiler/__pycache__/parser.cpython-312.pyc +0 -0
  51. package/src/zexus/compiler/__pycache__/semantic.cpython-312.pyc +0 -0
  52. package/src/zexus/compiler/__pycache__/zexus_ast.cpython-312.pyc +0 -0
  53. package/src/zexus/compiler/bytecode.py +124 -7
  54. package/src/zexus/compiler/compat_runtime.py +6 -2
  55. package/src/zexus/compiler/lexer.py +16 -5
  56. package/src/zexus/compiler/parser.py +108 -7
  57. package/src/zexus/compiler/semantic.py +18 -19
  58. package/src/zexus/compiler/zexus_ast.py +26 -1
  59. package/src/zexus/concurrency_system.py +79 -0
  60. package/src/zexus/config.py +54 -0
  61. package/src/zexus/crypto_bridge.py +244 -8
  62. package/src/zexus/dap/__init__.py +10 -0
  63. package/src/zexus/dap/__main__.py +4 -0
  64. package/src/zexus/dap/dap_server.py +391 -0
  65. package/src/zexus/dap/debug_engine.py +298 -0
  66. package/src/zexus/environment.py +112 -9
  67. package/src/zexus/evaluator/__pycache__/bytecode_compiler.cpython-312.pyc +0 -0
  68. package/src/zexus/evaluator/__pycache__/core.cpython-312.pyc +0 -0
  69. package/src/zexus/evaluator/__pycache__/expressions.cpython-312.pyc +0 -0
  70. package/src/zexus/evaluator/__pycache__/functions.cpython-312.pyc +0 -0
  71. package/src/zexus/evaluator/__pycache__/resource_limiter.cpython-312.pyc +0 -0
  72. package/src/zexus/evaluator/__pycache__/statements.cpython-312.pyc +0 -0
  73. package/src/zexus/evaluator/__pycache__/unified_execution.cpython-312.pyc +0 -0
  74. package/src/zexus/evaluator/__pycache__/utils.cpython-312.pyc +0 -0
  75. package/src/zexus/evaluator/bytecode_compiler.py +457 -37
  76. package/src/zexus/evaluator/core.py +644 -50
  77. package/src/zexus/evaluator/expressions.py +358 -62
  78. package/src/zexus/evaluator/functions.py +458 -20
  79. package/src/zexus/evaluator/resource_limiter.py +4 -4
  80. package/src/zexus/evaluator/statements.py +774 -122
  81. package/src/zexus/evaluator/unified_execution.py +573 -72
  82. package/src/zexus/evaluator/utils.py +14 -2
  83. package/src/zexus/evaluator_original.py +1 -1
  84. package/src/zexus/event_loop.py +186 -0
  85. package/src/zexus/lexer.py +742 -458
  86. package/src/zexus/lsp/__init__.py +1 -1
  87. package/src/zexus/lsp/definition_provider.py +163 -9
  88. package/src/zexus/lsp/server.py +22 -8
  89. package/src/zexus/lsp/symbol_provider.py +182 -9
  90. package/src/zexus/module_cache.py +239 -9
  91. package/src/zexus/module_manager.py +129 -1
  92. package/src/zexus/object.py +76 -6
  93. package/src/zexus/parser/__pycache__/parser.cpython-312.pyc +0 -0
  94. package/src/zexus/parser/__pycache__/strategy_context.cpython-312.pyc +0 -0
  95. package/src/zexus/parser/__pycache__/strategy_structural.cpython-312.pyc +0 -0
  96. package/src/zexus/parser/parser.py +1349 -408
  97. package/src/zexus/parser/strategy_context.py +755 -58
  98. package/src/zexus/parser/strategy_structural.py +121 -21
  99. package/src/zexus/persistence.py +15 -1
  100. package/src/zexus/renderer/__init__.py +61 -0
  101. package/src/zexus/renderer/__pycache__/__init__.cpython-312.pyc +0 -0
  102. package/src/zexus/renderer/__pycache__/backend.cpython-312.pyc +0 -0
  103. package/src/zexus/renderer/__pycache__/canvas.cpython-312.pyc +0 -0
  104. package/src/zexus/renderer/__pycache__/color_system.cpython-312.pyc +0 -0
  105. package/src/zexus/renderer/__pycache__/layout.cpython-312.pyc +0 -0
  106. package/src/zexus/renderer/__pycache__/main_renderer.cpython-312.pyc +0 -0
  107. package/src/zexus/renderer/__pycache__/painter.cpython-312.pyc +0 -0
  108. package/src/zexus/renderer/backend.py +261 -0
  109. package/src/zexus/renderer/canvas.py +78 -0
  110. package/src/zexus/renderer/color_system.py +201 -0
  111. package/src/zexus/renderer/graphics.py +31 -0
  112. package/src/zexus/renderer/layout.py +222 -0
  113. package/src/zexus/renderer/main_renderer.py +66 -0
  114. package/src/zexus/renderer/painter.py +30 -0
  115. package/src/zexus/renderer/tk_backend.py +208 -0
  116. package/src/zexus/renderer/web_backend.py +260 -0
  117. package/src/zexus/runtime/__init__.py +10 -2
  118. package/src/zexus/runtime/__pycache__/__init__.cpython-312.pyc +0 -0
  119. package/src/zexus/runtime/__pycache__/async_runtime.cpython-312.pyc +0 -0
  120. package/src/zexus/runtime/__pycache__/load_manager.cpython-312.pyc +0 -0
  121. package/src/zexus/runtime/file_flags.py +137 -0
  122. package/src/zexus/runtime/load_manager.py +368 -0
  123. package/src/zexus/safety/__pycache__/__init__.cpython-312.pyc +0 -0
  124. package/src/zexus/safety/__pycache__/memory_safety.cpython-312.pyc +0 -0
  125. package/src/zexus/security.py +424 -34
  126. package/src/zexus/stdlib/fs.py +23 -18
  127. package/src/zexus/stdlib/http.py +289 -186
  128. package/src/zexus/stdlib/sockets.py +207 -163
  129. package/src/zexus/stdlib/websockets.py +282 -0
  130. package/src/zexus/stdlib_integration.py +369 -2
  131. package/src/zexus/strategy_recovery.py +6 -3
  132. package/src/zexus/type_checker.py +423 -0
  133. package/src/zexus/virtual_filesystem.py +189 -2
  134. package/src/zexus/vm/__init__.py +113 -3
  135. package/src/zexus/vm/__pycache__/async_optimizer.cpython-312.pyc +0 -0
  136. package/src/zexus/vm/__pycache__/bytecode.cpython-312.pyc +0 -0
  137. package/src/zexus/vm/__pycache__/bytecode_converter.cpython-312.pyc +0 -0
  138. package/src/zexus/vm/__pycache__/cache.cpython-312.pyc +0 -0
  139. package/src/zexus/vm/__pycache__/compiler.cpython-312.pyc +0 -0
  140. package/src/zexus/vm/__pycache__/gas_metering.cpython-312.pyc +0 -0
  141. package/src/zexus/vm/__pycache__/jit.cpython-312.pyc +0 -0
  142. package/src/zexus/vm/__pycache__/parallel_vm.cpython-312.pyc +0 -0
  143. package/src/zexus/vm/__pycache__/vm.cpython-312.pyc +0 -0
  144. package/src/zexus/vm/async_optimizer.py +80 -6
  145. package/src/zexus/vm/binary_bytecode.py +659 -0
  146. package/src/zexus/vm/bytecode.py +59 -11
  147. package/src/zexus/vm/bytecode_converter.py +26 -12
  148. package/src/zexus/vm/cabi.c +1985 -0
  149. package/src/zexus/vm/cabi.cpython-312-x86_64-linux-gnu.so +0 -0
  150. package/src/zexus/vm/cabi.h +127 -0
  151. package/src/zexus/vm/cache.py +561 -17
  152. package/src/zexus/vm/compiler.py +818 -51
  153. package/src/zexus/vm/fastops.c +15743 -0
  154. package/src/zexus/vm/fastops.cpython-312-x86_64-linux-gnu.so +0 -0
  155. package/src/zexus/vm/fastops.pyx +288 -0
  156. package/src/zexus/vm/gas_metering.py +50 -9
  157. package/src/zexus/vm/jit.py +364 -20
  158. package/src/zexus/vm/native_jit_backend.py +1816 -0
  159. package/src/zexus/vm/native_runtime.cpp +1388 -0
  160. package/src/zexus/vm/native_runtime.cpython-312-x86_64-linux-gnu.so +0 -0
  161. package/src/zexus/vm/optimizer.py +161 -11
  162. package/src/zexus/vm/parallel_vm.py +140 -45
  163. package/src/zexus/vm/peephole_optimizer.py +82 -4
  164. package/src/zexus/vm/profiler.py +38 -18
  165. package/src/zexus/vm/register_allocator.py +16 -5
  166. package/src/zexus/vm/register_vm.py +8 -5
  167. package/src/zexus/vm/vm.py +3581 -531
  168. package/src/zexus/vm/wasm_compiler.py +658 -0
  169. package/src/zexus/zexus_ast.py +137 -11
  170. package/src/zexus/zexus_token.py +16 -5
  171. package/src/zexus/zpm/installer.py +55 -15
  172. package/src/zexus/zpm/package_manager.py +1 -1
  173. package/src/zexus/zpm/registry.py +257 -28
  174. package/src/zexus.egg-info/PKG-INFO +16 -6
  175. package/src/zexus.egg-info/SOURCES.txt +129 -17
  176. package/src/zexus.egg-info/entry_points.txt +1 -0
  177. package/src/zexus.egg-info/requires.txt +4 -0
@@ -1,23 +1,95 @@
1
1
  # src/zexus/evaluator/expressions.py
2
+ import os
3
+
2
4
  from ..zexus_ast import (
3
- IntegerLiteral, FloatLiteral, StringLiteral, ListLiteral, MapLiteral,
4
- Identifier, PrefixExpression, InfixExpression, IfExpression,
5
- Boolean as AST_Boolean, EmbeddedLiteral, ActionLiteral, LambdaExpression
5
+ IntegerLiteral, FloatLiteral, StringLiteral, ListLiteral, MapLiteral,
6
+ Identifier, PrefixExpression, InfixExpression, IfExpression,
7
+ Boolean as AST_Boolean, EmbeddedLiteral, ActionLiteral, LambdaExpression,
8
+ PropertyAccessExpression,
6
9
  )
7
10
  from ..object import (
8
11
  Integer, Float, String, List, Map,
9
12
  EvaluationError, Builtin, DateTime
10
13
  )
11
- from .utils import is_error, debug_log, NULL, TRUE, FALSE, is_truthy
14
+ from ..config import config as zexus_config
15
+ from .utils import is_error, debug_log, NULL, TRUE, FALSE, is_truthy, _python_to_zexus
12
16
 
13
17
  class ExpressionEvaluatorMixin:
14
18
  """Handles evaluation of expressions: Literals, Math, Logic, Identifiers."""
15
19
 
20
+ def eval_property_access_expression(self, node, env, stack_trace=None):
21
+ obj = self.eval_node(node.object, env, stack_trace)
22
+ if is_error(obj): return obj
23
+
24
+ from ..object import String, Map, List, EvaluationError
25
+
26
+ if node.computed:
27
+ # Index notation: obj[expr]
28
+ idx = self.eval_node(node.property, env, stack_trace)
29
+ if is_error(idx): return idx
30
+
31
+ if isinstance(obj, List):
32
+ return obj.get(idx)
33
+ elif isinstance(obj, Map):
34
+ key = idx
35
+ if isinstance(key, str): key = String(key)
36
+ return obj.get(key) or NULL
37
+ elif isinstance(obj, String):
38
+ return obj.get(idx)
39
+ else:
40
+ # Fallback for Python objects
41
+ try:
42
+ raw_idx = idx.value if hasattr(idx, 'value') else idx
43
+ return obj[raw_idx]
44
+ except (IndexError, KeyError, TypeError):
45
+ return NULL
46
+ else:
47
+ # Dot notation: obj.prop
48
+ if not hasattr(node.property, 'value'):
49
+ return EvaluationError(f"Invalid property identifier: {node.property}")
50
+
51
+ prop_name = str(node.property.value)
52
+
53
+ # Check security restrictions (redact, read-only, etc.)
54
+ try:
55
+ from ..security import get_security_context
56
+ ctx = get_security_context()
57
+ target = f"{getattr(node.object, 'value', str(node.object))}.{prop_name}"
58
+ restriction = ctx.get_restriction(target)
59
+ if restriction:
60
+ rule = restriction.get('restriction')
61
+ if rule == 'redact':
62
+ return String('***REDACTED***')
63
+ except Exception:
64
+ pass
65
+
66
+ if isinstance(obj, Map):
67
+ return obj.get(String(prop_name)) or NULL
68
+ elif isinstance(obj, dict):
69
+ return obj.get(prop_name, NULL)
70
+ elif hasattr(obj, 'get') and hasattr(obj, 'set') and callable(getattr(obj, 'get', None)):
71
+ # Contract-like objects (e.g., SmartContract) expose state via get/set.
72
+ # This makes `contract.state_var` reliable and deterministic.
73
+ try:
74
+ res = obj.get(prop_name)
75
+ return res if res is not None else NULL
76
+ except Exception:
77
+ return NULL
78
+ else:
79
+ # Try getattr (for data classes, etc.)
80
+ try:
81
+ res = getattr(obj, prop_name, NULL)
82
+ return res
83
+ except Exception:
84
+ return NULL
85
+
16
86
  def eval_identifier(self, node, env):
17
- debug_log("eval_identifier", f"Looking up: {node.value}")
87
+ name = node.value
88
+ if zexus_config.fast_debug_enabled:
89
+ debug_log("eval_identifier", f"Looking up: {name}")
18
90
 
19
91
  # Special case: 'this' keyword should be treated like ThisExpression
20
- if node.value == "this":
92
+ if name == "this":
21
93
  # Look for contract instance first
22
94
  contract_instance = env.get("__contract_instance__")
23
95
  if contract_instance is not None:
@@ -29,21 +101,23 @@ class ExpressionEvaluatorMixin:
29
101
  return data_instance
30
102
 
31
103
  # First, check environment for user-defined variables (including DATA dataclasses)
32
- val = env.get(node.value)
104
+ val = env.get(name)
33
105
  if val:
34
- debug_log(" Found in environment", f"{node.value} = {val}")
106
+ if zexus_config.fast_debug_enabled:
107
+ debug_log(" Found in environment", f"{name} = {val}")
35
108
  return val
36
109
 
37
110
  # Check builtins (self.builtins should be defined in FunctionEvaluatorMixin)
38
111
  if hasattr(self, 'builtins'):
39
- builtin = self.builtins.get(node.value)
112
+ builtin = self.builtins.get(name)
40
113
  if builtin:
41
- debug_log(" Found builtin", f"{node.value} = {builtin}")
114
+ if zexus_config.fast_debug_enabled:
115
+ debug_log(" Found builtin", f"{name} = {builtin}")
42
116
  return builtin
43
117
 
44
118
  # Special handling for TX - ONLY if not already defined by user
45
119
  # This provides blockchain transaction context when TX is not a user dataclass
46
- if node.value == "TX":
120
+ if name == "TX":
47
121
  from ..blockchain.transaction import get_current_tx, create_tx_context
48
122
  tx = get_current_tx()
49
123
  if tx is None:
@@ -111,16 +185,22 @@ class ExpressionEvaluatorMixin:
111
185
  # SECURITY FIX #6: Integer overflow protection
112
186
  # Python integers have arbitrary precision, but we enforce safe ranges
113
187
  # to prevent resource exhaustion and match real-world integer behavior
114
- MAX_SAFE_INT = 2**63 - 1 # 64-bit signed integer max
115
- MIN_SAFE_INT = -(2**63) # 64-bit signed integer min
116
-
188
+ MAX_SAFE_BIT_LENGTH = 4096 # Allow values up to ~10^1233 before flagging overflow
189
+
117
190
  def check_overflow(result, operation):
118
191
  """Check if integer operation resulted in overflow"""
119
- if result > MAX_SAFE_INT or result < MIN_SAFE_INT:
120
- return EvaluationError(
121
- f"Integer overflow in {operation}",
122
- suggestion=f"Result {result} exceeds safe integer range [{MIN_SAFE_INT}, {MAX_SAFE_INT}]. Use require() to validate inputs or break calculation into smaller parts."
123
- )
192
+ if isinstance(result, int):
193
+ bit_length = abs(result).bit_length()
194
+ if bit_length > MAX_SAFE_BIT_LENGTH:
195
+ # Use a quick log10 approximation without importing decimal-heavy helpers
196
+ approx_digits = int(bit_length * 0.30103) + 1 if bit_length else 1
197
+ return EvaluationError(
198
+ f"Integer overflow in {operation}",
199
+ suggestion=(
200
+ f"Result requires {bit_length} bits (~{approx_digits} digits), exceeding the safe limit of "
201
+ f"{MAX_SAFE_BIT_LENGTH} bits. Break the calculation into smaller parts or enable big-integer support."
202
+ )
203
+ )
124
204
  return Integer(result)
125
205
 
126
206
  if operator == "+":
@@ -147,6 +227,12 @@ class ExpressionEvaluatorMixin:
147
227
  suggestion="Check your divisor value. Modulo operation requires a non-zero divisor."
148
228
  )
149
229
  return Integer(left_val % right_val)
230
+ elif operator == "**":
231
+ if right_val < 0:
232
+ # Negative exponent returns float
233
+ return Float(left_val ** right_val)
234
+ result = left_val ** right_val
235
+ return check_overflow(result, "exponentiation")
150
236
  elif operator == "<":
151
237
  return TRUE if left_val < right_val else FALSE
152
238
  elif operator == ">":
@@ -188,6 +274,11 @@ class ExpressionEvaluatorMixin:
188
274
  return TRUE if left_val == right_val else FALSE
189
275
  elif operator == "!=":
190
276
  return TRUE if left_val != right_val else FALSE
277
+ elif operator == "**":
278
+ try:
279
+ return Float(left_val ** right_val)
280
+ except (OverflowError, ValueError) as e:
281
+ return EvaluationError(f"Exponentiation error: {e}")
191
282
 
192
283
  return EvaluationError(f"Unknown float operator: {operator}")
193
284
 
@@ -354,7 +445,7 @@ class ExpressionEvaluatorMixin:
354
445
 
355
446
  # SECURITY FIX #8: Strict Type Checking for Arithmetic
356
447
  # All arithmetic operations require numeric types (Integer or Float)
357
- elif operator in ("*", "-", "/", "%"):
448
+ elif operator in ("*", "-", "/", "%", "**"):
358
449
  # Get type names for error messages
359
450
  left_type = type(left).__name__.replace("Obj", "").upper()
360
451
  right_type = type(right).__name__.replace("Obj", "").upper()
@@ -391,9 +482,11 @@ class ExpressionEvaluatorMixin:
391
482
  if r_val == 0:
392
483
  return EvaluationError("Modulo by zero")
393
484
  result = l_val % r_val
485
+ elif operator == "**":
486
+ result = l_val ** r_val
394
487
 
395
488
  # Return Integer if result is whole number, Float otherwise
396
- if result == int(result) and operator != "/": # Division always returns float
489
+ if result == int(result) and operator not in ("/", "**"): # Division/power always returns float
397
490
  return Integer(int(result))
398
491
  return Float(result)
399
492
  except Exception as e:
@@ -404,6 +497,10 @@ class ExpressionEvaluatorMixin:
404
497
 
405
498
  # Comparison with mixed numeric types (Integer/Float comparison allowed)
406
499
  elif operator in ("<", ">", "<=", ">="):
500
+ # Safe null handling: Any comparison with NULL is False (except != handled above)
501
+ if left == NULL or right == NULL:
502
+ return FALSE
503
+
407
504
  if isinstance(left, (Integer, Float)) and isinstance(right, (Integer, Float)):
408
505
  l_val = float(left.value)
409
506
  r_val = float(right.value)
@@ -434,12 +531,11 @@ class ExpressionEvaluatorMixin:
434
531
 
435
532
  if operator == "!":
436
533
  # !true = false, !false = true, !null = true, !anything_else = false
437
- if right == TRUE:
534
+ # Use is_truthy for robust comparison (handles non-singleton BooleanObj)
535
+ if is_truthy(right):
438
536
  return FALSE
439
- elif right == FALSE or right == NULL:
440
- return TRUE
441
537
  else:
442
- return FALSE
538
+ return TRUE
443
539
  elif operator == "-":
444
540
  if isinstance(right, Integer):
445
541
  return Integer(-right.value)
@@ -503,6 +599,215 @@ class ExpressionEvaluatorMixin:
503
599
 
504
600
  return left
505
601
 
602
+ def _current_directory_from_env(self, env):
603
+ if not env or not hasattr(env, 'get'):
604
+ return None
605
+ try:
606
+ file_obj = env.get("__file__")
607
+ except Exception:
608
+ file_obj = None
609
+ if not file_obj:
610
+ return None
611
+ path = file_obj.value if hasattr(file_obj, 'value') else str(file_obj)
612
+ if not path:
613
+ return None
614
+ return os.path.dirname(path)
615
+
616
+ def _evaluate_expression_to_string(self, expr, env, stack_trace, literal_identifiers=False):
617
+ if literal_identifiers and isinstance(expr, Identifier):
618
+ return expr.value, None
619
+ if isinstance(expr, StringLiteral):
620
+ return expr.value, None
621
+ if isinstance(expr, IntegerLiteral):
622
+ return str(expr.value), None
623
+ if isinstance(expr, FloatLiteral):
624
+ return str(expr.value), None
625
+ if isinstance(expr, AST_Boolean):
626
+ return "true" if expr.value else "false", None
627
+
628
+ value = self.eval_node(expr, env, stack_trace)
629
+ if is_error(value):
630
+ return None, value
631
+
632
+ if hasattr(value, 'value'):
633
+ return str(value.value), None
634
+ return str(value), None
635
+
636
+ def _flatten_property_chain(self, expr, env, stack_trace):
637
+ segments = []
638
+ current = expr
639
+ while isinstance(current, PropertyAccessExpression):
640
+ literal_mode = not getattr(current, 'computed', False)
641
+ segment, error = self._evaluate_expression_to_string(
642
+ current.property,
643
+ env,
644
+ stack_trace,
645
+ literal_identifiers=literal_mode,
646
+ )
647
+ if error:
648
+ return None, error
649
+ segments.insert(0, segment)
650
+ current = current.object
651
+
652
+ segment, error = self._evaluate_expression_to_string(
653
+ current,
654
+ env,
655
+ stack_trace,
656
+ literal_identifiers=True,
657
+ )
658
+ if error:
659
+ return None, error
660
+ segments.insert(0, segment)
661
+ return segments, None
662
+
663
+ def eval_find_expression(self, node, env, stack_trace):
664
+ debug_log("eval_find_expression", "find keyword")
665
+
666
+ pattern, error = self._evaluate_expression_to_string(
667
+ node.target,
668
+ env,
669
+ stack_trace,
670
+ literal_identifiers=True,
671
+ )
672
+ if error:
673
+ return error
674
+
675
+ if pattern is None or not str(pattern).strip():
676
+ return EvaluationError("find requires a target path")
677
+
678
+ pattern = str(pattern).strip()
679
+
680
+ scope = None
681
+ if getattr(node, 'scope', None) is not None:
682
+ scope, error = self._evaluate_expression_to_string(
683
+ node.scope,
684
+ env,
685
+ stack_trace,
686
+ literal_identifiers=True,
687
+ )
688
+ if error:
689
+ return error
690
+ scope = str(scope).strip() if scope else None
691
+
692
+ current_dir = self._current_directory_from_env(env)
693
+
694
+ from .. import module_manager
695
+
696
+ matches = module_manager.find_files(
697
+ pattern,
698
+ current_dir=current_dir,
699
+ scope=scope,
700
+ )
701
+
702
+ if not matches:
703
+ suggestion = "Verify the file exists or adjust the scope provided to find."
704
+ return EvaluationError(
705
+ f"find could not locate '{pattern}'",
706
+ suggestion=suggestion,
707
+ )
708
+
709
+ if len(matches) > 1:
710
+ preview = ", ".join(matches[:3])
711
+ suggestion = "Provide a more specific path or scope to disambiguate the match."
712
+ return EvaluationError(
713
+ f"find found multiple matches for '{pattern}': {preview}",
714
+ suggestion=suggestion,
715
+ )
716
+
717
+ return String(matches[0], is_trusted=True)
718
+
719
+ def eval_load_expression(self, node, env, stack_trace):
720
+ debug_log("eval_load_expression", "load keyword")
721
+
722
+ from ..runtime import get_load_manager
723
+
724
+ provider_hint = getattr(node, 'provider_hint', None)
725
+ provider = provider_hint.lower() if isinstance(provider_hint, str) else None
726
+
727
+ segments = None
728
+ if isinstance(node.target, PropertyAccessExpression):
729
+ segments, error = self._flatten_property_chain(node.target, env, stack_trace)
730
+ if error:
731
+ return error
732
+
733
+ manager = get_load_manager()
734
+
735
+ key = None
736
+ if segments:
737
+ candidate = (segments[0] or "").lower()
738
+ if provider is None and manager.is_provider_registered(candidate):
739
+ provider = candidate
740
+ remainder = [seg for seg in segments[1:] if seg is not None]
741
+ key = ".".join(remainder)
742
+ else:
743
+ key = ".".join(seg for seg in segments if seg is not None)
744
+
745
+ if key is None:
746
+ key, error = self._evaluate_expression_to_string(
747
+ node.target,
748
+ env,
749
+ stack_trace,
750
+ literal_identifiers=True,
751
+ )
752
+ if error:
753
+ return error
754
+
755
+ if isinstance(key, str):
756
+ key = key.strip()
757
+
758
+ if provider:
759
+ provider = provider.strip().lower()
760
+
761
+ if provider and not key:
762
+ return EvaluationError(
763
+ "load requires a key when a provider is specified",
764
+ suggestion="Append the key after the provider, for example load env.API_KEY.",
765
+ )
766
+
767
+ source = None
768
+ if getattr(node, 'source', None) is not None:
769
+ source, error = self._evaluate_expression_to_string(
770
+ node.source,
771
+ env,
772
+ stack_trace,
773
+ )
774
+ if error:
775
+ return error
776
+ if isinstance(source, str):
777
+ source = source.strip()
778
+
779
+ current_dir = self._current_directory_from_env(env)
780
+
781
+ if provider and not manager.is_provider_registered(provider):
782
+ return EvaluationError(
783
+ f"Unknown load provider '{provider}'",
784
+ suggestion="Register a provider before using it or choose a supported provider.",
785
+ )
786
+
787
+ try:
788
+ value = manager.load(
789
+ key,
790
+ provider=provider,
791
+ source=source,
792
+ current_dir=current_dir,
793
+ )
794
+ except FileNotFoundError as exc:
795
+ suggestion = "Check the referenced file path or provide an absolute path."
796
+ return EvaluationError(str(exc), suggestion=suggestion)
797
+ except KeyError:
798
+ missing = key or "<empty>"
799
+ message = f"load could not resolve '{missing}'"
800
+ if provider:
801
+ message += f" via provider '{provider}'"
802
+ if source:
803
+ message += f" from '{source}'"
804
+ suggestion = "Ensure the value exists or configure a fallback source."
805
+ return EvaluationError(message, suggestion=suggestion)
806
+ except Exception as exc:
807
+ return EvaluationError(f"load failed: {exc}")
808
+
809
+ return _python_to_zexus(value, mark_untrusted=True)
810
+
506
811
  def eval_await_expression(self, node, env, stack_trace):
507
812
  """Evaluate await expression: await <expression>
508
813
 
@@ -814,73 +1119,64 @@ class ExpressionEvaluatorMixin:
814
1119
  def eval_async_expression(self, node, env, stack_trace):
815
1120
  """Evaluate async expression: async <expression>
816
1121
 
817
- Executes the expression in a background thread.
818
- Example: async producer()
1122
+ Schedules the expression on the shared Zexus event loop.
1123
+ For call expressions, evaluation is deferred entirely to the loop.
1124
+ For coroutine results, driving is done inside a loop task.
819
1125
  """
820
- import threading
1126
+ from ..event_loop import spawn as _spawn
1127
+ import asyncio
821
1128
  import sys
822
-
823
- # For call expressions, we need to defer evaluation to the thread
824
- # Otherwise evaluating here will execute the action in the main thread
1129
+
1130
+ # For call expressions, we need to defer evaluation to the event loop
825
1131
  if type(node.expression).__name__ == 'CallExpression':
826
- def run_in_thread():
1132
+ async def _run_call():
827
1133
  try:
828
1134
  result = self.eval_node(node.expression, env, stack_trace)
829
-
1135
+
830
1136
  # If it's a Coroutine (from async action), execute it
831
1137
  if hasattr(result, '__class__') and result.__class__.__name__ == 'Coroutine':
832
1138
  try:
833
- # Prime the generator
834
1139
  next(result.generator)
835
- # Execute until completion
836
1140
  while True:
837
1141
  next(result.generator)
838
1142
  except StopIteration:
839
- pass # Coroutine completed
840
-
1143
+ pass
841
1144
  except StopIteration:
842
- pass # Normal coroutine completion
1145
+ pass
843
1146
  except Exception as e:
844
- import sys
845
1147
  print(f"[ASYNC ERROR] {type(e).__name__}: {str(e)}", file=sys.stderr, flush=True)
846
1148
  import traceback
847
1149
  traceback.print_exc(file=sys.stderr)
848
-
849
- thread = threading.Thread(target=run_in_thread, daemon=True)
850
- thread.start()
1150
+
1151
+ _spawn(_run_call())
851
1152
  return NULL
852
-
1153
+
853
1154
  # For other expressions, evaluate first then check if it's a Coroutine
854
- result = self.eval_node(node.expression, env, stack_trace)
855
-
1155
+ previous_allow = getattr(self, "_allow_coroutine_result", False)
1156
+ self._allow_coroutine_result = True
1157
+ try:
1158
+ result = self.eval_node(node.expression, env, stack_trace)
1159
+ finally:
1160
+ self._allow_coroutine_result = previous_allow
1161
+
856
1162
  if is_error(result):
857
1163
  return result
858
-
859
- # print(f"[ASYNC EXPR] Expression evaluated to: {type(result).__name__}", file=sys.stderr)
860
-
861
- # If it's a Coroutine (from calling an async action), execute it in a thread
1164
+
1165
+ # If it's a Coroutine, drive it on the shared event loop
862
1166
  if hasattr(result, '__class__') and result.__class__.__name__ == 'Coroutine':
863
- def run_coroutine():
1167
+ async def _drive_coroutine():
864
1168
  try:
865
- # Prime the generator
866
1169
  next(result.generator)
867
- # Execute until completion
868
1170
  while True:
869
1171
  next(result.generator)
870
1172
  except StopIteration:
871
- pass # Coroutine completed normally
1173
+ pass
872
1174
  except Exception as e:
873
- import sys
874
1175
  print(f"[ASYNC ERROR] {type(e).__name__}: {str(e)}", file=sys.stderr, flush=True)
875
1176
  import traceback
876
1177
  traceback.print_exc(file=sys.stderr)
877
-
878
- thread = threading.Thread(target=run_coroutine, daemon=True)
879
- thread.start()
1178
+
1179
+ _spawn(_drive_coroutine())
880
1180
  return NULL
881
-
882
- # For any other result (including NULL from regular actions),
883
- # we can't execute it asynchronously since it already executed.
884
- # Just return NULL to indicate "async operation initiated"
885
- # print(f"[ASYNC EXPR] Result is not a coroutine, returning NULL", file=sys.stderr)
1181
+
886
1182
  return NULL