zexus 1.6.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 (227) hide show
  1. package/LICENSE +0 -0
  2. package/README.md +2513 -0
  3. package/bin/zexus +2 -0
  4. package/bin/zpics +2 -0
  5. package/bin/zpm +2 -0
  6. package/bin/zx +2 -0
  7. package/bin/zx-deploy +2 -0
  8. package/bin/zx-dev +2 -0
  9. package/bin/zx-run +2 -0
  10. package/package.json +66 -0
  11. package/scripts/README.md +24 -0
  12. package/scripts/postinstall.js +44 -0
  13. package/shared_config.json +24 -0
  14. package/src/README.md +1525 -0
  15. package/src/tests/run_zexus_tests.py +117 -0
  16. package/src/tests/test_all_phases.zx +346 -0
  17. package/src/tests/test_blockchain_features.zx +306 -0
  18. package/src/tests/test_complexity_features.zx +321 -0
  19. package/src/tests/test_core_integration.py +185 -0
  20. package/src/tests/test_phase10_ecosystem.zx +177 -0
  21. package/src/tests/test_phase1_modifiers.zx +87 -0
  22. package/src/tests/test_phase2_plugins.zx +80 -0
  23. package/src/tests/test_phase3_security.zx +97 -0
  24. package/src/tests/test_phase4_vfs.zx +116 -0
  25. package/src/tests/test_phase5_types.zx +117 -0
  26. package/src/tests/test_phase6_metaprogramming.zx +125 -0
  27. package/src/tests/test_phase7_optimization.zx +132 -0
  28. package/src/tests/test_phase9_advanced_types.zx +157 -0
  29. package/src/tests/test_security_features.py +419 -0
  30. package/src/tests/test_security_features.zx +276 -0
  31. package/src/tests/test_simple_zx.zx +1 -0
  32. package/src/tests/test_verification_simple.zx +69 -0
  33. package/src/zexus/__init__.py +28 -0
  34. package/src/zexus/__main__.py +5 -0
  35. package/src/zexus/__pycache__/__init__.cpython-312.pyc +0 -0
  36. package/src/zexus/__pycache__/advanced_types.cpython-312.pyc +0 -0
  37. package/src/zexus/__pycache__/builtin_modules.cpython-312.pyc +0 -0
  38. package/src/zexus/__pycache__/capability_system.cpython-312.pyc +0 -0
  39. package/src/zexus/__pycache__/complexity_system.cpython-312.pyc +0 -0
  40. package/src/zexus/__pycache__/concurrency_system.cpython-312.pyc +0 -0
  41. package/src/zexus/__pycache__/config.cpython-312.pyc +0 -0
  42. package/src/zexus/__pycache__/dependency_injection.cpython-312.pyc +0 -0
  43. package/src/zexus/__pycache__/ecosystem.cpython-312.pyc +0 -0
  44. package/src/zexus/__pycache__/environment.cpython-312.pyc +0 -0
  45. package/src/zexus/__pycache__/error_reporter.cpython-312.pyc +0 -0
  46. package/src/zexus/__pycache__/hybrid_orchestrator.cpython-312.pyc +0 -0
  47. package/src/zexus/__pycache__/lexer.cpython-312.pyc +0 -0
  48. package/src/zexus/__pycache__/metaprogramming.cpython-312.pyc +0 -0
  49. package/src/zexus/__pycache__/module_cache.cpython-312.pyc +0 -0
  50. package/src/zexus/__pycache__/object.cpython-312.pyc +0 -0
  51. package/src/zexus/__pycache__/optimization.cpython-312.pyc +0 -0
  52. package/src/zexus/__pycache__/plugin_system.cpython-312.pyc +0 -0
  53. package/src/zexus/__pycache__/policy_engine.cpython-312.pyc +0 -0
  54. package/src/zexus/__pycache__/security.cpython-312.pyc +0 -0
  55. package/src/zexus/__pycache__/stdlib_integration.cpython-312.pyc +0 -0
  56. package/src/zexus/__pycache__/strategy_recovery.cpython-312.pyc +0 -0
  57. package/src/zexus/__pycache__/syntax_validator.cpython-312.pyc +0 -0
  58. package/src/zexus/__pycache__/type_system.cpython-312.pyc +0 -0
  59. package/src/zexus/__pycache__/virtual_filesystem.cpython-312.pyc +0 -0
  60. package/src/zexus/__pycache__/zexus_ast.cpython-312.pyc +0 -0
  61. package/src/zexus/__pycache__/zexus_token.cpython-312.pyc +0 -0
  62. package/src/zexus/advanced_types.py +401 -0
  63. package/src/zexus/blockchain/__init__.py +40 -0
  64. package/src/zexus/blockchain/__pycache__/__init__.cpython-312.pyc +0 -0
  65. package/src/zexus/blockchain/__pycache__/crypto.cpython-312.pyc +0 -0
  66. package/src/zexus/blockchain/__pycache__/ledger.cpython-312.pyc +0 -0
  67. package/src/zexus/blockchain/__pycache__/transaction.cpython-312.pyc +0 -0
  68. package/src/zexus/blockchain/crypto.py +463 -0
  69. package/src/zexus/blockchain/ledger.py +255 -0
  70. package/src/zexus/blockchain/transaction.py +267 -0
  71. package/src/zexus/builtin_modules.py +284 -0
  72. package/src/zexus/builtin_plugins.py +317 -0
  73. package/src/zexus/capability_system.py +372 -0
  74. package/src/zexus/cli/__init__.py +2 -0
  75. package/src/zexus/cli/__pycache__/__init__.cpython-312.pyc +0 -0
  76. package/src/zexus/cli/__pycache__/main.cpython-312.pyc +0 -0
  77. package/src/zexus/cli/main.py +707 -0
  78. package/src/zexus/cli/zpm.py +203 -0
  79. package/src/zexus/compare_interpreter_compiler.py +146 -0
  80. package/src/zexus/compiler/__init__.py +169 -0
  81. package/src/zexus/compiler/__pycache__/__init__.cpython-312.pyc +0 -0
  82. package/src/zexus/compiler/__pycache__/lexer.cpython-312.pyc +0 -0
  83. package/src/zexus/compiler/__pycache__/parser.cpython-312.pyc +0 -0
  84. package/src/zexus/compiler/__pycache__/zexus_ast.cpython-312.pyc +0 -0
  85. package/src/zexus/compiler/bytecode.py +266 -0
  86. package/src/zexus/compiler/compat_runtime.py +277 -0
  87. package/src/zexus/compiler/lexer.py +257 -0
  88. package/src/zexus/compiler/parser.py +779 -0
  89. package/src/zexus/compiler/semantic.py +118 -0
  90. package/src/zexus/compiler/zexus_ast.py +454 -0
  91. package/src/zexus/complexity_system.py +575 -0
  92. package/src/zexus/concurrency_system.py +493 -0
  93. package/src/zexus/config.py +201 -0
  94. package/src/zexus/crypto_bridge.py +19 -0
  95. package/src/zexus/dependency_injection.py +423 -0
  96. package/src/zexus/ecosystem.py +434 -0
  97. package/src/zexus/environment.py +101 -0
  98. package/src/zexus/environment_manager.py +119 -0
  99. package/src/zexus/error_reporter.py +314 -0
  100. package/src/zexus/evaluator/__init__.py +12 -0
  101. package/src/zexus/evaluator/__pycache__/__init__.cpython-312.pyc +0 -0
  102. package/src/zexus/evaluator/__pycache__/bytecode_compiler.cpython-312.pyc +0 -0
  103. package/src/zexus/evaluator/__pycache__/core.cpython-312.pyc +0 -0
  104. package/src/zexus/evaluator/__pycache__/expressions.cpython-312.pyc +0 -0
  105. package/src/zexus/evaluator/__pycache__/functions.cpython-312.pyc +0 -0
  106. package/src/zexus/evaluator/__pycache__/integration.cpython-312.pyc +0 -0
  107. package/src/zexus/evaluator/__pycache__/statements.cpython-312.pyc +0 -0
  108. package/src/zexus/evaluator/__pycache__/utils.cpython-312.pyc +0 -0
  109. package/src/zexus/evaluator/bytecode_compiler.py +700 -0
  110. package/src/zexus/evaluator/core.py +891 -0
  111. package/src/zexus/evaluator/expressions.py +827 -0
  112. package/src/zexus/evaluator/functions.py +3989 -0
  113. package/src/zexus/evaluator/integration.py +396 -0
  114. package/src/zexus/evaluator/statements.py +4303 -0
  115. package/src/zexus/evaluator/utils.py +126 -0
  116. package/src/zexus/evaluator_original.py +2041 -0
  117. package/src/zexus/external_bridge.py +16 -0
  118. package/src/zexus/find_affected_imports.sh +155 -0
  119. package/src/zexus/hybrid_orchestrator.py +152 -0
  120. package/src/zexus/input_validation.py +259 -0
  121. package/src/zexus/lexer.py +571 -0
  122. package/src/zexus/logging.py +89 -0
  123. package/src/zexus/lsp/__init__.py +9 -0
  124. package/src/zexus/lsp/completion_provider.py +207 -0
  125. package/src/zexus/lsp/definition_provider.py +22 -0
  126. package/src/zexus/lsp/hover_provider.py +71 -0
  127. package/src/zexus/lsp/server.py +269 -0
  128. package/src/zexus/lsp/symbol_provider.py +31 -0
  129. package/src/zexus/metaprogramming.py +321 -0
  130. package/src/zexus/module_cache.py +89 -0
  131. package/src/zexus/module_manager.py +107 -0
  132. package/src/zexus/object.py +973 -0
  133. package/src/zexus/optimization.py +424 -0
  134. package/src/zexus/parser/__init__.py +31 -0
  135. package/src/zexus/parser/__pycache__/__init__.cpython-312.pyc +0 -0
  136. package/src/zexus/parser/__pycache__/parser.cpython-312.pyc +0 -0
  137. package/src/zexus/parser/__pycache__/strategy_context.cpython-312.pyc +0 -0
  138. package/src/zexus/parser/__pycache__/strategy_structural.cpython-312.pyc +0 -0
  139. package/src/zexus/parser/integration.py +86 -0
  140. package/src/zexus/parser/parser.py +3977 -0
  141. package/src/zexus/parser/strategy_context.py +7254 -0
  142. package/src/zexus/parser/strategy_structural.py +1033 -0
  143. package/src/zexus/persistence.py +391 -0
  144. package/src/zexus/plugin_system.py +290 -0
  145. package/src/zexus/policy_engine.py +365 -0
  146. package/src/zexus/profiler/__init__.py +5 -0
  147. package/src/zexus/profiler/profiler.py +233 -0
  148. package/src/zexus/purity_system.py +398 -0
  149. package/src/zexus/runtime/__init__.py +20 -0
  150. package/src/zexus/runtime/async_runtime.py +324 -0
  151. package/src/zexus/search_old_imports.sh +65 -0
  152. package/src/zexus/security.py +1407 -0
  153. package/src/zexus/stack_trace.py +233 -0
  154. package/src/zexus/stdlib/__init__.py +27 -0
  155. package/src/zexus/stdlib/blockchain.py +341 -0
  156. package/src/zexus/stdlib/compression.py +167 -0
  157. package/src/zexus/stdlib/crypto.py +124 -0
  158. package/src/zexus/stdlib/datetime.py +163 -0
  159. package/src/zexus/stdlib/db_mongo.py +199 -0
  160. package/src/zexus/stdlib/db_mysql.py +162 -0
  161. package/src/zexus/stdlib/db_postgres.py +163 -0
  162. package/src/zexus/stdlib/db_sqlite.py +133 -0
  163. package/src/zexus/stdlib/encoding.py +230 -0
  164. package/src/zexus/stdlib/fs.py +195 -0
  165. package/src/zexus/stdlib/http.py +219 -0
  166. package/src/zexus/stdlib/http_server.py +248 -0
  167. package/src/zexus/stdlib/json_module.py +61 -0
  168. package/src/zexus/stdlib/math.py +360 -0
  169. package/src/zexus/stdlib/os_module.py +265 -0
  170. package/src/zexus/stdlib/regex.py +148 -0
  171. package/src/zexus/stdlib/sockets.py +253 -0
  172. package/src/zexus/stdlib/test_framework.zx +208 -0
  173. package/src/zexus/stdlib/test_runner.zx +119 -0
  174. package/src/zexus/stdlib_integration.py +341 -0
  175. package/src/zexus/strategy_recovery.py +256 -0
  176. package/src/zexus/syntax_validator.py +356 -0
  177. package/src/zexus/testing/zpics.py +407 -0
  178. package/src/zexus/testing/zpics_runtime.py +369 -0
  179. package/src/zexus/type_system.py +374 -0
  180. package/src/zexus/validation_system.py +569 -0
  181. package/src/zexus/virtual_filesystem.py +355 -0
  182. package/src/zexus/vm/__init__.py +8 -0
  183. package/src/zexus/vm/__pycache__/__init__.cpython-312.pyc +0 -0
  184. package/src/zexus/vm/__pycache__/async_optimizer.cpython-312.pyc +0 -0
  185. package/src/zexus/vm/__pycache__/bytecode.cpython-312.pyc +0 -0
  186. package/src/zexus/vm/__pycache__/cache.cpython-312.pyc +0 -0
  187. package/src/zexus/vm/__pycache__/jit.cpython-312.pyc +0 -0
  188. package/src/zexus/vm/__pycache__/memory_manager.cpython-312.pyc +0 -0
  189. package/src/zexus/vm/__pycache__/memory_pool.cpython-312.pyc +0 -0
  190. package/src/zexus/vm/__pycache__/optimizer.cpython-312.pyc +0 -0
  191. package/src/zexus/vm/__pycache__/parallel_vm.cpython-312.pyc +0 -0
  192. package/src/zexus/vm/__pycache__/peephole_optimizer.cpython-312.pyc +0 -0
  193. package/src/zexus/vm/__pycache__/profiler.cpython-312.pyc +0 -0
  194. package/src/zexus/vm/__pycache__/register_allocator.cpython-312.pyc +0 -0
  195. package/src/zexus/vm/__pycache__/register_vm.cpython-312.pyc +0 -0
  196. package/src/zexus/vm/__pycache__/ssa_converter.cpython-312.pyc +0 -0
  197. package/src/zexus/vm/__pycache__/vm.cpython-312.pyc +0 -0
  198. package/src/zexus/vm/async_optimizer.py +420 -0
  199. package/src/zexus/vm/bytecode.py +428 -0
  200. package/src/zexus/vm/bytecode_converter.py +297 -0
  201. package/src/zexus/vm/cache.py +532 -0
  202. package/src/zexus/vm/jit.py +720 -0
  203. package/src/zexus/vm/memory_manager.py +520 -0
  204. package/src/zexus/vm/memory_pool.py +511 -0
  205. package/src/zexus/vm/optimizer.py +478 -0
  206. package/src/zexus/vm/parallel_vm.py +899 -0
  207. package/src/zexus/vm/peephole_optimizer.py +452 -0
  208. package/src/zexus/vm/profiler.py +527 -0
  209. package/src/zexus/vm/register_allocator.py +462 -0
  210. package/src/zexus/vm/register_vm.py +520 -0
  211. package/src/zexus/vm/ssa_converter.py +757 -0
  212. package/src/zexus/vm/vm.py +1392 -0
  213. package/src/zexus/zexus_ast.py +1782 -0
  214. package/src/zexus/zexus_token.py +253 -0
  215. package/src/zexus/zpm/__init__.py +15 -0
  216. package/src/zexus/zpm/installer.py +116 -0
  217. package/src/zexus/zpm/package_manager.py +208 -0
  218. package/src/zexus/zpm/publisher.py +98 -0
  219. package/src/zexus/zpm/registry.py +110 -0
  220. package/src/zexus.egg-info/PKG-INFO +2235 -0
  221. package/src/zexus.egg-info/SOURCES.txt +876 -0
  222. package/src/zexus.egg-info/dependency_links.txt +1 -0
  223. package/src/zexus.egg-info/entry_points.txt +3 -0
  224. package/src/zexus.egg-info/not-zip-safe +1 -0
  225. package/src/zexus.egg-info/requires.txt +14 -0
  226. package/src/zexus.egg-info/top_level.txt +2 -0
  227. package/zexus.json +14 -0
@@ -0,0 +1,827 @@
1
+ # src/zexus/evaluator/expressions.py
2
+ from ..zexus_ast import (
3
+ IntegerLiteral, FloatLiteral, StringLiteral, ListLiteral, MapLiteral,
4
+ Identifier, PrefixExpression, InfixExpression, IfExpression,
5
+ Boolean as AST_Boolean, EmbeddedLiteral, ActionLiteral, LambdaExpression
6
+ )
7
+ from ..object import (
8
+ Integer, Float, String, List, Map,
9
+ EvaluationError, Builtin, DateTime
10
+ )
11
+ from .utils import is_error, debug_log, NULL, TRUE, FALSE, is_truthy
12
+
13
+ class ExpressionEvaluatorMixin:
14
+ """Handles evaluation of expressions: Literals, Math, Logic, Identifiers."""
15
+
16
+ def eval_identifier(self, node, env):
17
+ debug_log("eval_identifier", f"Looking up: {node.value}")
18
+
19
+ # Special case: 'this' keyword should be treated like ThisExpression
20
+ if node.value == "this":
21
+ # Look for contract instance first
22
+ contract_instance = env.get("__contract_instance__")
23
+ if contract_instance is not None:
24
+ return contract_instance
25
+
26
+ # Then look for data method instance
27
+ data_instance = env.get("this")
28
+ if data_instance is not None:
29
+ return data_instance
30
+
31
+ # First, check environment for user-defined variables (including DATA dataclasses)
32
+ val = env.get(node.value)
33
+ if val:
34
+ debug_log(" Found in environment", f"{node.value} = {val}")
35
+ return val
36
+
37
+ # Check builtins (self.builtins should be defined in FunctionEvaluatorMixin)
38
+ if hasattr(self, 'builtins'):
39
+ builtin = self.builtins.get(node.value)
40
+ if builtin:
41
+ debug_log(" Found builtin", f"{node.value} = {builtin}")
42
+ return builtin
43
+
44
+ # Special handling for TX - ONLY if not already defined by user
45
+ # This provides blockchain transaction context when TX is not a user dataclass
46
+ if node.value == "TX":
47
+ from ..blockchain.transaction import get_current_tx, create_tx_context
48
+ tx = get_current_tx()
49
+ if tx is None:
50
+ # Auto-create TX context if not exists
51
+ tx = create_tx_context(caller="system", gas_limit=1000000)
52
+ # Wrap TX context as a Zexus Map object for property access
53
+ # Use plain string keys (not String objects) for Map.get() compatibility
54
+ return Map({
55
+ "caller": String(tx.caller),
56
+ "timestamp": Integer(int(tx.timestamp)),
57
+ "block_hash": String(tx.block_hash),
58
+ "gas_used": Integer(tx.gas_used),
59
+ "gas_remaining": Integer(tx.gas_remaining),
60
+ "gas_limit": Integer(tx.gas_limit)
61
+ })
62
+
63
+ try:
64
+ env_keys = []
65
+ if hasattr(env, 'store'):
66
+ env_keys = list(env.store.keys())
67
+ # Use direct print to ensure visibility during debugging
68
+ import traceback as _tb
69
+ stack_snip = ''.join(_tb.format_stack(limit=5)[-3:])
70
+ # print(f"[DEBUG] Identifier not found: {node.value}; env_keys={env_keys}\nStack snippet:\n{stack_snip}")
71
+ except Exception:
72
+ pass # print(f"[DEBUG] Identifier not found: {node.value}")
73
+
74
+ # Try to find similar names for helpful suggestion
75
+ suggestion = None
76
+ if hasattr(env, 'store'):
77
+ env_keys = list(env.store.keys())
78
+
79
+ # Find similar variable names (simple approach)
80
+ def similarity(a, b):
81
+ a, b = a.lower(), b.lower()
82
+ if a == b:
83
+ return 1.0
84
+ if a in b or b in a:
85
+ return 0.8
86
+ if len(a) > 2 and len(b) > 2:
87
+ if a[:3] == b[:3] or a[-3:] == b[-3:]:
88
+ return 0.6
89
+ return 0.0
90
+
91
+ similar = [(key, similarity(node.value, key)) for key in env_keys]
92
+ similar = [(k, s) for k, s in similar if s > 0.5]
93
+ similar.sort(key=lambda x: x[1], reverse=True)
94
+
95
+ if similar:
96
+ suggestion = f"Did you mean '{similar[0][0]}'?"
97
+ elif env_keys:
98
+ suggestion = f"Declare the variable first with 'let' or 'const'. Available: {', '.join(env_keys[:5])}"
99
+ else:
100
+ suggestion = "No variables declared yet. Use 'let variableName = value' to create one."
101
+
102
+ return EvaluationError(
103
+ f"Identifier '{node.value}' not found",
104
+ suggestion=suggestion
105
+ )
106
+
107
+ def eval_integer_infix(self, operator, left, right):
108
+ left_val = left.value
109
+ right_val = right.value
110
+
111
+ if operator == "+":
112
+ return Integer(left_val + right_val)
113
+ elif operator == "-":
114
+ return Integer(left_val - right_val)
115
+ elif operator == "*":
116
+ return Integer(left_val * right_val)
117
+ elif operator == "/":
118
+ if right_val == 0:
119
+ return EvaluationError(
120
+ "Division by zero",
121
+ suggestion="Check your divisor value. Consider adding a condition: if (divisor != 0) { ... }"
122
+ )
123
+ return Integer(left_val // right_val)
124
+ elif operator == "%":
125
+ if right_val == 0:
126
+ return EvaluationError(
127
+ "Modulo by zero",
128
+ suggestion="Check your divisor value. Modulo operation requires a non-zero divisor."
129
+ )
130
+ return Integer(left_val % right_val)
131
+ elif operator == "<":
132
+ return TRUE if left_val < right_val else FALSE
133
+ elif operator == ">":
134
+ return TRUE if left_val > right_val else FALSE
135
+ elif operator == "<=":
136
+ return TRUE if left_val <= right_val else FALSE
137
+ elif operator == ">=":
138
+ return TRUE if left_val >= right_val else FALSE
139
+ elif operator == "==":
140
+ return TRUE if left_val == right_val else FALSE
141
+ elif operator == "!=":
142
+ return TRUE if left_val != right_val else FALSE
143
+
144
+ return EvaluationError(f"Unknown integer operator: {operator}")
145
+
146
+ def eval_float_infix(self, operator, left, right):
147
+ left_val = left.value
148
+ right_val = right.value
149
+
150
+ if operator == "+":
151
+ return Float(left_val + right_val)
152
+ elif operator == "-":
153
+ return Float(left_val - right_val)
154
+ elif operator == "*":
155
+ return Float(left_val * right_val)
156
+ elif operator == "/":
157
+ if right_val == 0:
158
+ return EvaluationError("Division by zero")
159
+ return Float(left_val / right_val)
160
+ elif operator == "<":
161
+ return TRUE if left_val < right_val else FALSE
162
+ elif operator == ">":
163
+ return TRUE if left_val > right_val else FALSE
164
+ elif operator == "<=":
165
+ return TRUE if left_val <= right_val else FALSE
166
+ elif operator == ">=":
167
+ return TRUE if left_val >= right_val else FALSE
168
+ elif operator == "==":
169
+ return TRUE if left_val == right_val else FALSE
170
+ elif operator == "!=":
171
+ return TRUE if left_val != right_val else FALSE
172
+
173
+ return EvaluationError(f"Unknown float operator: {operator}")
174
+
175
+ def eval_string_infix(self, operator, left, right):
176
+ if operator == "+":
177
+ return String(left.value + right.value)
178
+ elif operator == "==":
179
+ return TRUE if left.value == right.value else FALSE
180
+ elif operator == "!=":
181
+ return TRUE if left.value != right.value else FALSE
182
+ elif operator == "*":
183
+ # String repetition: "x" * 3 = "xxx"
184
+ # Only works with String * Integer, not String * String
185
+ return EvaluationError(f"Type mismatch: STRING * STRING (use STRING * INTEGER for repetition)")
186
+ return EvaluationError(f"Unknown string operator: {operator}")
187
+
188
+ def eval_infix_expression(self, node, env, stack_trace):
189
+ debug_log("eval_infix_expression", f"{node.left} {node.operator} {node.right}")
190
+
191
+ left = self.eval_node(node.left, env, stack_trace)
192
+ if is_error(left):
193
+ return left
194
+
195
+ right = self.eval_node(node.right, env, stack_trace)
196
+ if is_error(right):
197
+ return right
198
+
199
+ # (removed debug instrumentation)
200
+
201
+ operator = node.operator
202
+
203
+ # Check for operator overloading in left operand (for dataclasses)
204
+ if isinstance(left, Map) and hasattr(left, 'pairs'):
205
+ operator_key = String(f"__operator_{operator}__")
206
+ if operator_key in left.pairs:
207
+ operator_method = left.pairs[operator_key]
208
+ if isinstance(operator_method, Builtin):
209
+ # Call the operator method with right operand
210
+ result = operator_method.fn(right)
211
+ debug_log(" Operator overload called", f"{operator} on {left}")
212
+ return result
213
+
214
+ # Logical Operators (short-circuiting)
215
+ if operator == "&&":
216
+ return TRUE if is_truthy(left) and is_truthy(right) else FALSE
217
+ elif operator == "||":
218
+ return TRUE if is_truthy(left) or is_truthy(right) else FALSE
219
+
220
+ # Equality operators
221
+ elif operator == "==":
222
+ if hasattr(left, 'value') and hasattr(right, 'value'):
223
+ return TRUE if left.value == right.value else FALSE
224
+ return TRUE if left == right else FALSE
225
+ elif operator == "!=":
226
+ if hasattr(left, 'value') and hasattr(right, 'value'):
227
+ return TRUE if left.value != right.value else FALSE
228
+ return TRUE if left != right else FALSE
229
+
230
+ # Type-specific dispatch
231
+ if isinstance(left, Integer) and isinstance(right, Integer):
232
+ return self.eval_integer_infix(operator, left, right)
233
+ elif isinstance(left, Float) and isinstance(right, Float):
234
+ return self.eval_float_infix(operator, left, right)
235
+ elif isinstance(left, String) and isinstance(right, String):
236
+ return self.eval_string_infix(operator, left, right)
237
+
238
+ # String repetition: "x" * 100 or 100 * "x"
239
+ elif operator == "*":
240
+ if isinstance(left, String) and isinstance(right, Integer):
241
+ # "x" * 100
242
+ return String(left.value * right.value)
243
+ elif isinstance(left, Integer) and isinstance(right, String):
244
+ # 100 * "x"
245
+ return String(right.value * left.value)
246
+
247
+ # Array Concatenation
248
+ elif operator == "+" and isinstance(left, List) and isinstance(right, List):
249
+ # Concatenate two arrays: [1, 2] + [3, 4] = [1, 2, 3, 4]
250
+ new_elements = left.elements[:] + right.elements[:]
251
+ return List(new_elements)
252
+
253
+ # DateTime arithmetic
254
+ elif isinstance(left, DateTime) and isinstance(right, DateTime):
255
+ # DateTime - DateTime = time difference in seconds (as Float)
256
+ if operator == "-":
257
+ diff = left.timestamp - right.timestamp
258
+ # Return the difference as a Float in seconds
259
+ return Float(diff)
260
+ else:
261
+ return EvaluationError(f"Unsupported operation: DATETIME {operator} DATETIME")
262
+ elif isinstance(left, DateTime) and isinstance(right, (Integer, Float)):
263
+ # DateTime + Number or DateTime - Number (add/subtract seconds)
264
+ if operator == "+":
265
+ new_timestamp = left.timestamp + float(right.value)
266
+ return DateTime(new_timestamp)
267
+ elif operator == "-":
268
+ new_timestamp = left.timestamp - float(right.value)
269
+ return DateTime(new_timestamp)
270
+ else:
271
+ return EvaluationError(f"Unsupported operation: DATETIME {operator} {right.type()}")
272
+ elif isinstance(left, (Integer, Float)) and isinstance(right, DateTime):
273
+ # Number + DateTime (add seconds to datetime)
274
+ if operator == "+":
275
+ new_timestamp = right.timestamp + float(left.value)
276
+ return DateTime(new_timestamp)
277
+ else:
278
+ return EvaluationError(f"Unsupported operation: {left.type()} {operator} DATETIME")
279
+
280
+ # Mixed String Concatenation
281
+ elif operator == "+":
282
+ if isinstance(left, String):
283
+ right_str = right.inspect() if not isinstance(right, String) else right.value
284
+ return String(left.value + str(right_str))
285
+ elif isinstance(right, String):
286
+ left_str = left.inspect() if not isinstance(left, String) else left.value
287
+ return String(str(left_str) + right.value)
288
+ # Mixed Numeric
289
+ elif isinstance(left, (Integer, Float)) and isinstance(right, (Integer, Float)):
290
+ l_val = float(left.value)
291
+ r_val = float(right.value)
292
+ return Float(l_val + r_val)
293
+
294
+ # Mixed arithmetic operations (String coerced to number for *, -, /, %)
295
+ elif operator in ("*", "-", "/", "%"):
296
+ # Try to coerce strings to numbers for arithmetic
297
+ l_val = None
298
+ r_val = None
299
+
300
+ # Get left value
301
+ if isinstance(left, (Integer, Float)):
302
+ l_val = float(left.value)
303
+ elif isinstance(left, String):
304
+ try:
305
+ l_val = float(left.value)
306
+ except ValueError:
307
+ pass
308
+
309
+ # Get right value
310
+ if isinstance(right, (Integer, Float)):
311
+ r_val = float(right.value)
312
+ elif isinstance(right, String):
313
+ try:
314
+ r_val = float(right.value)
315
+ except ValueError:
316
+ pass
317
+
318
+ # Perform operation if both values could be coerced
319
+ if l_val is not None and r_val is not None:
320
+ try:
321
+ if operator == "*":
322
+ result = l_val * r_val
323
+ elif operator == "-":
324
+ result = l_val - r_val
325
+ elif operator == "/":
326
+ if r_val == 0:
327
+ return EvaluationError("Division by zero")
328
+ result = l_val / r_val
329
+ elif operator == "%":
330
+ if r_val == 0:
331
+ return EvaluationError("Modulo by zero")
332
+ result = l_val % r_val
333
+
334
+ # Return Integer if result is whole number, Float otherwise
335
+ if result == int(result):
336
+ return Integer(int(result))
337
+ return Float(result)
338
+ except Exception as e:
339
+ return EvaluationError(f"Arithmetic error: {str(e)}")
340
+
341
+ # Comparison with mixed numeric types
342
+ elif operator in ("<", ">", "<=", ">="):
343
+ if isinstance(left, (Integer, Float)) and isinstance(right, (Integer, Float)):
344
+ l_val = float(left.value)
345
+ r_val = float(right.value)
346
+ if operator == "<": return TRUE if l_val < r_val else FALSE
347
+ elif operator == ">": return TRUE if l_val > r_val else FALSE
348
+ elif operator == "<=": return TRUE if l_val <= r_val else FALSE
349
+ elif operator == ">=": return TRUE if l_val >= r_val else FALSE
350
+
351
+ # Mixed String/Number comparison (Coerce to float)
352
+ elif (isinstance(left, (Integer, Float)) and isinstance(right, String)) or \
353
+ (isinstance(left, String) and isinstance(right, (Integer, Float))):
354
+ try:
355
+ l_val = float(left.value)
356
+ r_val = float(right.value)
357
+ if operator == "<": return TRUE if l_val < r_val else FALSE
358
+ elif operator == ">": return TRUE if l_val > r_val else FALSE
359
+ elif operator == "<=": return TRUE if l_val <= r_val else FALSE
360
+ elif operator == ">=": return TRUE if l_val >= r_val else FALSE
361
+ except ValueError:
362
+ # If conversion fails, return FALSE (NaN comparison behavior)
363
+ return FALSE
364
+
365
+ return EvaluationError(f"Type mismatch: {left.type()} {operator} {right.type()}")
366
+
367
+ def eval_prefix_expression(self, node, env, stack_trace):
368
+ debug_log("eval_prefix_expression", f"{node.operator} {node.right}")
369
+
370
+ right = self.eval_node(node.right, env, stack_trace)
371
+ if is_error(right):
372
+ return right
373
+
374
+ operator = node.operator
375
+
376
+ if operator == "!":
377
+ # !true = false, !false = true, !null = true, !anything_else = false
378
+ if right == TRUE:
379
+ return FALSE
380
+ elif right == FALSE or right == NULL:
381
+ return TRUE
382
+ else:
383
+ return FALSE
384
+ elif operator == "-":
385
+ if isinstance(right, Integer):
386
+ return Integer(-right.value)
387
+ elif isinstance(right, Float):
388
+ return Float(-right.value)
389
+ return EvaluationError(f"Unknown operator: -{right.type()}")
390
+
391
+ return EvaluationError(f"Unknown operator: {operator}{right.type()}")
392
+
393
+ def eval_if_expression(self, node, env, stack_trace):
394
+ debug_log("eval_if_expression", "Evaluating condition")
395
+
396
+ condition = self.eval_node(node.condition, env, stack_trace)
397
+ if is_error(condition):
398
+ return condition
399
+
400
+ if is_truthy(condition):
401
+ debug_log(" Condition true, evaluating consequence")
402
+ return self.eval_node(node.consequence, env, stack_trace)
403
+ elif node.alternative:
404
+ debug_log(" Condition false, evaluating alternative")
405
+ return self.eval_node(node.alternative, env, stack_trace)
406
+
407
+ debug_log(" Condition false, no alternative")
408
+ return NULL
409
+
410
+ def eval_expressions(self, exps, env):
411
+ results = []
412
+ for e in exps:
413
+ val = self.eval_node(e, env)
414
+ if is_error(val):
415
+ return val
416
+ results.append(val)
417
+ return results
418
+
419
+ def eval_ternary_expression(self, node, env, stack_trace):
420
+ """Evaluate ternary expression: condition ? true_value : false_value"""
421
+ from .utils import is_truthy
422
+
423
+ condition = self.eval_node(node.condition, env, stack_trace)
424
+ if is_error(condition):
425
+ return condition
426
+
427
+ if is_truthy(condition):
428
+ return self.eval_node(node.true_value, env, stack_trace)
429
+ else:
430
+ return self.eval_node(node.false_value, env, stack_trace)
431
+
432
+ def eval_nullish_expression(self, node, env, stack_trace):
433
+ """Evaluate nullish coalescing: value ?? default
434
+ Returns default if value is null/undefined, otherwise returns value"""
435
+ left = self.eval_node(node.left, env, stack_trace)
436
+
437
+ # If left is an error, return the error
438
+ if is_error(left):
439
+ return left
440
+
441
+ # Check if left is null or undefined (NULL)
442
+ if left is NULL or left is None or (hasattr(left, 'type') and left.type() == 'NULL'):
443
+ return self.eval_node(node.right, env, stack_trace)
444
+
445
+ return left
446
+
447
+ def eval_await_expression(self, node, env, stack_trace):
448
+ """Evaluate await expression: await <expression>
449
+
450
+ Await can handle:
451
+ 1. Promise objects - waits for resolution
452
+ 2. Coroutine objects - resumes until complete
453
+ 3. Async action calls - wraps in Promise
454
+ 4. Regular values - returns immediately
455
+ """
456
+ from ..object import Promise, Coroutine, EvaluationError
457
+
458
+ # Evaluate the expression to await
459
+ awaitable = self.eval_node(node.expression, env, stack_trace)
460
+
461
+ # Check for errors
462
+ if is_error(awaitable):
463
+ return awaitable
464
+
465
+ # Handle different awaitable types
466
+ if hasattr(awaitable, 'type'):
467
+ obj_type = awaitable.type()
468
+
469
+ # Await a Promise
470
+ if obj_type == "PROMISE":
471
+ # Since promises execute immediately in executor, they should be resolved
472
+ if awaitable.is_resolved():
473
+ try:
474
+ result = awaitable.get_value()
475
+ return result if result is not None else NULL
476
+ except Exception as e:
477
+ # Propagate error with stack trace context
478
+ error_msg = f"Promise rejected: {e}"
479
+ if hasattr(awaitable, 'stack_trace') and awaitable.stack_trace:
480
+ error_msg += f"\n Promise created at: {awaitable.stack_trace}"
481
+ return EvaluationError(error_msg)
482
+ else:
483
+ # Promise is still pending - this shouldn't happen with current implementation
484
+ # but we can spin-wait briefly
485
+ import time
486
+ max_wait = 1.0 # 1 second timeout
487
+ waited = 0.0
488
+ while not awaitable.is_resolved() and waited < max_wait:
489
+ time.sleep(0.001) # 1ms
490
+ waited += 0.001
491
+
492
+ if awaitable.is_resolved():
493
+ try:
494
+ result = awaitable.get_value()
495
+ return result if result is not None else NULL
496
+ except Exception as e:
497
+ return EvaluationError(f"Promise rejected: {e}")
498
+ else:
499
+ return EvaluationError("Await timeout: promise did not resolve")
500
+
501
+ # Await a Coroutine
502
+ elif obj_type == "COROUTINE":
503
+ # Resume coroutine until complete
504
+ while not awaitable.is_complete:
505
+ is_done, value = awaitable.resume()
506
+ if is_done:
507
+ # Check if there was an error
508
+ if awaitable.error:
509
+ return EvaluationError(f"Coroutine error: {awaitable.error}")
510
+ return value if value is not None else NULL
511
+
512
+ # If coroutine yielded a value, it might be another awaitable
513
+ if hasattr(value, 'type') and value.type() == "PROMISE":
514
+ # Wait for the promise
515
+ if value.is_resolved():
516
+ try:
517
+ resume_value = value.get_value()
518
+ # Send the value back to the coroutine
519
+ is_done, result = awaitable.resume(resume_value)
520
+ if is_done:
521
+ return result if result is not None else NULL
522
+ except Exception as e:
523
+ return EvaluationError(f"Promise error in coroutine: {e}")
524
+
525
+ return awaitable.result if awaitable.result is not None else NULL
526
+
527
+ # Regular value - return immediately
528
+ else:
529
+ return awaitable
530
+
531
+ # No type method - return as-is
532
+ return awaitable
533
+
534
+ def eval_file_import_expression(self, node, env, stack_trace):
535
+ """Evaluate file import expression: let code << "filename.ext"
536
+
537
+ Reads the file contents and returns as a String object.
538
+ Supports any file extension - returns raw file content.
539
+ """
540
+ import os
541
+
542
+ # 1. Evaluate the filepath expression
543
+ filepath_obj = self.eval_node(node.filepath, env, stack_trace)
544
+ if is_error(filepath_obj):
545
+ return filepath_obj
546
+
547
+ # 2. Convert to string
548
+ if hasattr(filepath_obj, 'value'):
549
+ filepath = str(filepath_obj.value)
550
+ else:
551
+ filepath = str(filepath_obj)
552
+
553
+ # 3. Normalize path (handle relative paths relative to CWD)
554
+ if not os.path.isabs(filepath):
555
+ filepath = os.path.join(os.getcwd(), filepath)
556
+
557
+ # 4. Check if file exists
558
+ if not os.path.exists(filepath):
559
+ return new_error(f"Cannot import file '{filepath}': File not found", stack_trace)
560
+
561
+ # 5. Read file contents
562
+ try:
563
+ with open(filepath, 'r', encoding='utf-8') as f:
564
+ content = f.read()
565
+
566
+ return String(content)
567
+ except UnicodeDecodeError:
568
+ # Try binary mode for non-text files
569
+ try:
570
+ with open(filepath, 'rb') as f:
571
+ content = f.read()
572
+ # Return as string representation of bytes
573
+ return String(str(content))
574
+ except Exception as e:
575
+ return new_error(f"Error reading file '{filepath}': {e}", stack_trace)
576
+ except Exception as e:
577
+ return new_error(f"Error importing file '{filepath}': {e}", stack_trace)
578
+ def eval_match_expression(self, node, env, stack_trace):
579
+ """Evaluate match expression with pattern matching
580
+
581
+ match value {
582
+ Point(x, y) => x + y,
583
+ User(name, _) => name,
584
+ 42 => "the answer",
585
+ _ => "default"
586
+ }
587
+ """
588
+ from .. import zexus_ast
589
+ from ..object import Map, String, Integer, Boolean as BooleanObj, Environment
590
+
591
+ debug_log("eval_match_expression", "Evaluating match expression")
592
+
593
+ # Evaluate the value to match against
594
+ match_value = self.eval_node(node.value, env, stack_trace)
595
+ if is_error(match_value):
596
+ return match_value
597
+
598
+ debug_log(" Match value", str(match_value))
599
+
600
+ # Try each case in order
601
+ for case in node.cases:
602
+ pattern = case.pattern
603
+ result_expr = case.result
604
+
605
+ # Try to match the pattern
606
+ match_result = self._match_pattern(pattern, match_value, env)
607
+
608
+ if match_result is not None:
609
+ # Pattern matched! Create new environment with bindings
610
+ new_env = Environment(outer=env)
611
+
612
+ # Add all bindings to the new environment
613
+ for var_name, var_value in match_result.items():
614
+ new_env.set(var_name, var_value)
615
+
616
+ # Evaluate and return the result expression
617
+ result = self.eval_node(result_expr, new_env, stack_trace)
618
+ debug_log(" ✅ Pattern matched", f"Result: {result}")
619
+ return result
620
+
621
+ # No pattern matched
622
+ return EvaluationError("Match expression: no pattern matched")
623
+
624
+ def _match_pattern(self, pattern, value, env):
625
+ """Try to match a pattern against a value
626
+
627
+ Returns:
628
+ dict: Bindings if matched (variable name -> value)
629
+ None: If pattern doesn't match
630
+ """
631
+ from .. import zexus_ast
632
+ from ..object import Map, String, Integer, Float, Boolean as BooleanObj
633
+
634
+ # Wildcard pattern: always matches, no bindings
635
+ if isinstance(pattern, zexus_ast.WildcardPattern):
636
+ debug_log(" 🎯 Wildcard pattern matched", "_")
637
+ return {}
638
+
639
+ # Variable pattern: always matches, bind variable
640
+ if isinstance(pattern, zexus_ast.VariablePattern):
641
+ debug_log(" 🎯 Variable pattern matched", f"{pattern.name} = {value}")
642
+ return {pattern.name: value}
643
+
644
+ # Literal pattern: check equality
645
+ if isinstance(pattern, zexus_ast.LiteralPattern):
646
+ pattern_value = self.eval_node(pattern.value, env, [])
647
+
648
+ # Compare values
649
+ matches = False
650
+ if isinstance(value, Integer) and isinstance(pattern_value, Integer):
651
+ matches = value.value == pattern_value.value
652
+ elif isinstance(value, Float) and isinstance(pattern_value, Float):
653
+ matches = value.value == pattern_value.value
654
+ elif isinstance(value, String) and isinstance(pattern_value, String):
655
+ matches = value.value == pattern_value.value
656
+ elif isinstance(value, BooleanObj) and isinstance(pattern_value, BooleanObj):
657
+ matches = value.value == pattern_value.value
658
+
659
+ if matches:
660
+ debug_log(" 🎯 Literal pattern matched", str(pattern_value))
661
+ return {}
662
+ else:
663
+ debug_log(" ❌ Literal pattern didn't match", f"{pattern_value} != {value}")
664
+ return None
665
+
666
+ # Constructor pattern: match dataclass instances
667
+ if isinstance(pattern, zexus_ast.ConstructorPattern):
668
+ # Check if value is a Map (dataclass instance)
669
+ if not isinstance(value, Map):
670
+ debug_log(" ❌ Constructor pattern: value is not a dataclass", type(value).__name__)
671
+ return None
672
+
673
+ # Check if value has __type__ field matching constructor name
674
+ type_key = String("__type__")
675
+ if type_key not in value.pairs:
676
+ debug_log(" ❌ Constructor pattern: no __type__ field", "")
677
+ return None
678
+
679
+ type_value = value.pairs[type_key]
680
+ if not isinstance(type_value, String):
681
+ debug_log(" ❌ Constructor pattern: __type__ is not a string", type(type_value).__name__)
682
+ return None
683
+
684
+ # Extract actual type name (handle specialized generics like "Point<number>")
685
+ actual_type = type_value.value
686
+ if '<' in actual_type:
687
+ # Strip generic parameters for matching
688
+ actual_type = actual_type.split('<')[0]
689
+
690
+ if actual_type != pattern.constructor_name:
691
+ debug_log(" ❌ Constructor pattern: type mismatch", f"{actual_type} != {pattern.constructor_name}")
692
+ return None
693
+
694
+ debug_log(" ✅ Constructor type matched", pattern.constructor_name)
695
+
696
+ # Extract field values and match against bindings
697
+ bindings = {}
698
+
699
+ # Get all non-internal, non-method fields from the dataclass
700
+ # Maintain original field order from the dataclass definition
701
+ fields = []
702
+ field_dict = {}
703
+
704
+ for key, val in value.pairs.items():
705
+ if isinstance(key, String):
706
+ field_name = key.value
707
+ # Skip internal fields (__type__, __immutable__, etc.)
708
+ if field_name.startswith("__"):
709
+ continue
710
+ # Skip auto-generated methods (toString, toJSON, clone, equals, hash, verify, fromJSON)
711
+ if field_name in {"toString", "toJSON", "clone", "equals", "hash", "verify", "fromJSON"}:
712
+ continue
713
+ field_dict[field_name] = val
714
+
715
+ # Try to get field order from __field_order__ metadata if available
716
+ # Otherwise, use the order they appear in the Map (which should be insertion order in Python 3.7+)
717
+ field_order_key = String("__field_order__")
718
+ if field_order_key in value.pairs:
719
+ # Use explicit field order if available
720
+ field_order = value.pairs[field_order_key]
721
+ if isinstance(field_order, List):
722
+ for field_name_obj in field_order.elements:
723
+ if isinstance(field_name_obj, String):
724
+ field_name = field_name_obj.value
725
+ if field_name in field_dict:
726
+ fields.append((field_name, field_dict[field_name]))
727
+ else:
728
+ # Use insertion order (dict maintains order in Python 3.7+)
729
+ fields = [(k, v) for k, v in field_dict.items()]
730
+
731
+ # Match each binding pattern against corresponding field value
732
+ if len(pattern.bindings) != len(fields):
733
+ debug_log(" ❌ Constructor pattern: binding count mismatch", f"{len(pattern.bindings)} != {len(fields)}")
734
+ return None
735
+
736
+ for i, (field_name, field_value) in enumerate(fields):
737
+ binding_pattern = pattern.bindings[i]
738
+
739
+ # Recursively match the binding pattern
740
+ binding_result = self._match_pattern(binding_pattern, field_value, env)
741
+
742
+ if binding_result is None:
743
+ debug_log(" ❌ Constructor pattern: binding didn't match", f"field {field_name}")
744
+ return None
745
+
746
+ # Merge bindings
747
+ bindings.update(binding_result)
748
+
749
+ debug_log(" 🎯 Constructor pattern fully matched", f"{pattern.constructor_name} with {len(bindings)} bindings")
750
+ return bindings
751
+
752
+ # Unknown pattern type
753
+ debug_log(" ❌ Unknown pattern type", type(pattern).__name__)
754
+ return None
755
+ def eval_async_expression(self, node, env, stack_trace):
756
+ """Evaluate async expression: async <expression>
757
+
758
+ Executes the expression in a background thread.
759
+ Example: async producer()
760
+ """
761
+ import threading
762
+ import sys
763
+
764
+ # For call expressions, we need to defer evaluation to the thread
765
+ # Otherwise evaluating here will execute the action in the main thread
766
+ if type(node.expression).__name__ == 'CallExpression':
767
+ def run_in_thread():
768
+ try:
769
+ result = self.eval_node(node.expression, env, stack_trace)
770
+
771
+ # If it's a Coroutine (from async action), execute it
772
+ if hasattr(result, '__class__') and result.__class__.__name__ == 'Coroutine':
773
+ try:
774
+ # Prime the generator
775
+ next(result.generator)
776
+ # Execute until completion
777
+ while True:
778
+ next(result.generator)
779
+ except StopIteration:
780
+ pass # Coroutine completed
781
+
782
+ except StopIteration:
783
+ pass # Normal coroutine completion
784
+ except Exception as e:
785
+ import sys
786
+ print(f"[ASYNC ERROR] {type(e).__name__}: {str(e)}", file=sys.stderr, flush=True)
787
+ import traceback
788
+ traceback.print_exc(file=sys.stderr)
789
+
790
+ thread = threading.Thread(target=run_in_thread, daemon=True)
791
+ thread.start()
792
+ return NULL
793
+
794
+ # For other expressions, evaluate first then check if it's a Coroutine
795
+ result = self.eval_node(node.expression, env, stack_trace)
796
+
797
+ if is_error(result):
798
+ return result
799
+
800
+ # print(f"[ASYNC EXPR] Expression evaluated to: {type(result).__name__}", file=sys.stderr)
801
+
802
+ # If it's a Coroutine (from calling an async action), execute it in a thread
803
+ if hasattr(result, '__class__') and result.__class__.__name__ == 'Coroutine':
804
+ def run_coroutine():
805
+ try:
806
+ # Prime the generator
807
+ next(result.generator)
808
+ # Execute until completion
809
+ while True:
810
+ next(result.generator)
811
+ except StopIteration:
812
+ pass # Coroutine completed normally
813
+ except Exception as e:
814
+ import sys
815
+ print(f"[ASYNC ERROR] {type(e).__name__}: {str(e)}", file=sys.stderr, flush=True)
816
+ import traceback
817
+ traceback.print_exc(file=sys.stderr)
818
+
819
+ thread = threading.Thread(target=run_coroutine, daemon=True)
820
+ thread.start()
821
+ return NULL
822
+
823
+ # For any other result (including NULL from regular actions),
824
+ # we can't execute it asynchronously since it already executed.
825
+ # Just return NULL to indicate "async operation initiated"
826
+ # print(f"[ASYNC EXPR] Result is not a coroutine, returning NULL", file=sys.stderr)
827
+ return NULL