zexus 1.7.1 → 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.
- package/README.md +3 -3
- package/package.json +1 -1
- package/src/__init__.py +7 -0
- package/src/zexus/__init__.py +1 -1
- package/src/zexus/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/capability_system.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/debug_sanitizer.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/environment.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/error_reporter.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/input_validation.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/lexer.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/module_cache.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/module_manager.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/object.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/security.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/security_enforcement.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/syntax_validator.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/zexus_ast.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/zexus_token.cpython-312.pyc +0 -0
- package/src/zexus/access_control_system/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/access_control_system/__pycache__/access_control.cpython-312.pyc +0 -0
- package/src/zexus/advanced_types.py +17 -2
- package/src/zexus/blockchain/__init__.py +411 -0
- package/src/zexus/blockchain/accelerator.py +1160 -0
- package/src/zexus/blockchain/chain.py +660 -0
- package/src/zexus/blockchain/consensus.py +821 -0
- package/src/zexus/blockchain/contract_vm.py +1019 -0
- package/src/zexus/blockchain/crypto.py +79 -14
- package/src/zexus/blockchain/events.py +526 -0
- package/src/zexus/blockchain/loadtest.py +721 -0
- package/src/zexus/blockchain/monitoring.py +350 -0
- package/src/zexus/blockchain/mpt.py +716 -0
- package/src/zexus/blockchain/multichain.py +951 -0
- package/src/zexus/blockchain/multiprocess_executor.py +338 -0
- package/src/zexus/blockchain/network.py +886 -0
- package/src/zexus/blockchain/node.py +666 -0
- package/src/zexus/blockchain/rpc.py +1203 -0
- package/src/zexus/blockchain/rust_bridge.py +421 -0
- package/src/zexus/blockchain/storage.py +423 -0
- package/src/zexus/blockchain/tokens.py +750 -0
- package/src/zexus/blockchain/upgradeable.py +1004 -0
- package/src/zexus/blockchain/verification.py +1602 -0
- package/src/zexus/blockchain/wallet.py +621 -0
- package/src/zexus/cli/__pycache__/main.cpython-312.pyc +0 -0
- package/src/zexus/cli/main.py +300 -20
- package/src/zexus/cli/zpm.py +1 -1
- package/src/zexus/compiler/__pycache__/bytecode.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/lexer.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/parser.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/semantic.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/zexus_ast.cpython-312.pyc +0 -0
- package/src/zexus/compiler/lexer.py +10 -5
- package/src/zexus/concurrency_system.py +79 -0
- package/src/zexus/config.py +54 -0
- package/src/zexus/crypto_bridge.py +244 -8
- package/src/zexus/dap/__init__.py +10 -0
- package/src/zexus/dap/__main__.py +4 -0
- package/src/zexus/dap/dap_server.py +391 -0
- package/src/zexus/dap/debug_engine.py +298 -0
- package/src/zexus/environment.py +10 -1
- package/src/zexus/evaluator/__pycache__/bytecode_compiler.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/core.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/expressions.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/functions.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/resource_limiter.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/statements.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/unified_execution.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/utils.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/bytecode_compiler.py +441 -37
- package/src/zexus/evaluator/core.py +560 -49
- package/src/zexus/evaluator/expressions.py +122 -49
- package/src/zexus/evaluator/functions.py +417 -16
- package/src/zexus/evaluator/statements.py +521 -118
- package/src/zexus/evaluator/unified_execution.py +573 -72
- package/src/zexus/evaluator/utils.py +14 -2
- package/src/zexus/event_loop.py +186 -0
- package/src/zexus/lexer.py +742 -486
- package/src/zexus/lsp/__init__.py +1 -1
- package/src/zexus/lsp/definition_provider.py +163 -9
- package/src/zexus/lsp/server.py +22 -8
- package/src/zexus/lsp/symbol_provider.py +182 -9
- package/src/zexus/module_cache.py +237 -9
- package/src/zexus/object.py +64 -6
- package/src/zexus/parser/__pycache__/parser.cpython-312.pyc +0 -0
- package/src/zexus/parser/__pycache__/strategy_context.cpython-312.pyc +0 -0
- package/src/zexus/parser/__pycache__/strategy_structural.cpython-312.pyc +0 -0
- package/src/zexus/parser/parser.py +786 -285
- package/src/zexus/parser/strategy_context.py +407 -66
- package/src/zexus/parser/strategy_structural.py +117 -19
- package/src/zexus/persistence.py +15 -1
- package/src/zexus/renderer/__init__.py +15 -0
- package/src/zexus/renderer/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/backend.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/canvas.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/color_system.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/layout.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/main_renderer.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/painter.cpython-312.pyc +0 -0
- package/src/zexus/renderer/tk_backend.py +208 -0
- package/src/zexus/renderer/web_backend.py +260 -0
- package/src/zexus/runtime/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/runtime/__pycache__/async_runtime.cpython-312.pyc +0 -0
- package/src/zexus/runtime/__pycache__/load_manager.cpython-312.pyc +0 -0
- package/src/zexus/runtime/file_flags.py +137 -0
- package/src/zexus/safety/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/safety/__pycache__/memory_safety.cpython-312.pyc +0 -0
- package/src/zexus/security.py +424 -34
- package/src/zexus/stdlib/fs.py +23 -18
- package/src/zexus/stdlib/http.py +289 -186
- package/src/zexus/stdlib/sockets.py +207 -163
- package/src/zexus/stdlib/websockets.py +282 -0
- package/src/zexus/stdlib_integration.py +369 -2
- package/src/zexus/strategy_recovery.py +6 -3
- package/src/zexus/type_checker.py +423 -0
- package/src/zexus/virtual_filesystem.py +189 -2
- package/src/zexus/vm/__init__.py +113 -3
- package/src/zexus/vm/__pycache__/async_optimizer.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/bytecode.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/bytecode_converter.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/cache.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/compiler.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/gas_metering.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/jit.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/parallel_vm.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/vm.cpython-312.pyc +0 -0
- package/src/zexus/vm/async_optimizer.py +14 -1
- package/src/zexus/vm/binary_bytecode.py +659 -0
- package/src/zexus/vm/bytecode.py +28 -1
- package/src/zexus/vm/bytecode_converter.py +26 -12
- package/src/zexus/vm/cabi.c +1985 -0
- package/src/zexus/vm/cabi.cpython-312-x86_64-linux-gnu.so +0 -0
- package/src/zexus/vm/cabi.h +127 -0
- package/src/zexus/vm/cache.py +557 -17
- package/src/zexus/vm/compiler.py +703 -5
- package/src/zexus/vm/fastops.c +15743 -0
- package/src/zexus/vm/fastops.cpython-312-x86_64-linux-gnu.so +0 -0
- package/src/zexus/vm/fastops.pyx +288 -0
- package/src/zexus/vm/gas_metering.py +50 -9
- package/src/zexus/vm/jit.py +83 -2
- package/src/zexus/vm/native_jit_backend.py +1816 -0
- package/src/zexus/vm/native_runtime.cpp +1388 -0
- package/src/zexus/vm/native_runtime.cpython-312-x86_64-linux-gnu.so +0 -0
- package/src/zexus/vm/optimizer.py +161 -11
- package/src/zexus/vm/parallel_vm.py +118 -42
- package/src/zexus/vm/peephole_optimizer.py +82 -4
- package/src/zexus/vm/profiler.py +38 -18
- package/src/zexus/vm/register_allocator.py +16 -5
- package/src/zexus/vm/register_vm.py +8 -5
- package/src/zexus/vm/vm.py +3411 -573
- package/src/zexus/vm/wasm_compiler.py +658 -0
- package/src/zexus/zexus_ast.py +63 -11
- package/src/zexus/zexus_token.py +13 -5
- package/src/zexus/zpm/installer.py +55 -15
- package/src/zexus/zpm/package_manager.py +1 -1
- package/src/zexus/zpm/registry.py +257 -28
- package/src/zexus.egg-info/PKG-INFO +7 -4
- package/src/zexus.egg-info/SOURCES.txt +116 -9
- package/src/zexus.egg-info/entry_points.txt +1 -0
- package/src/zexus.egg-info/requires.txt +4 -0
|
@@ -31,6 +31,7 @@ from ..security import (
|
|
|
31
31
|
ProtectionPolicy, Middleware, AuthConfig, RateLimiter, CachePolicy
|
|
32
32
|
)
|
|
33
33
|
from .utils import is_error, debug_log, EVAL_SUMMARY, NULL, TRUE, FALSE, _resolve_awaitable, _zexus_to_python, _python_to_zexus, is_truthy
|
|
34
|
+
from ..config import config as zexus_config
|
|
34
35
|
|
|
35
36
|
try:
|
|
36
37
|
from ..renderer import (
|
|
@@ -55,9 +56,77 @@ class BreakException:
|
|
|
55
56
|
def __repr__(self):
|
|
56
57
|
return "BreakException()"
|
|
57
58
|
|
|
59
|
+
class ContinueException:
|
|
60
|
+
"""Exception raised when continue statement is encountered in a loop."""
|
|
61
|
+
def __repr__(self):
|
|
62
|
+
return "ContinueException()"
|
|
63
|
+
|
|
64
|
+
|
|
58
65
|
class StatementEvaluatorMixin:
|
|
59
66
|
"""Handles evaluation of statements, flow control, module loading, and security features."""
|
|
60
67
|
|
|
68
|
+
def _statement_signature(self, stmt):
|
|
69
|
+
"""Create a stable, cheap signature for tolerant duplicate-skipping.
|
|
70
|
+
|
|
71
|
+
The tolerant parser/recovery can sometimes enqueue duplicate statements.
|
|
72
|
+
For long files, calling ``str(stmt)`` for every statement can become
|
|
73
|
+
expensive because AST __repr__ often includes nested expressions.
|
|
74
|
+
Prefer source location (line/column) when available.
|
|
75
|
+
"""
|
|
76
|
+
stmt_type = type(stmt).__name__
|
|
77
|
+
line = int(getattr(stmt, 'line', 0) or 0)
|
|
78
|
+
column = int(getattr(stmt, 'column', 0) or 0)
|
|
79
|
+
|
|
80
|
+
if line or column:
|
|
81
|
+
extra = ""
|
|
82
|
+
try:
|
|
83
|
+
if isinstance(stmt, ExpressionStatement):
|
|
84
|
+
expr = getattr(stmt, "expression", None)
|
|
85
|
+
func = getattr(expr, "function", None)
|
|
86
|
+
if isinstance(func, Identifier) and getattr(func, "value", None):
|
|
87
|
+
extra = f":{func.value}"
|
|
88
|
+
except Exception:
|
|
89
|
+
extra = ""
|
|
90
|
+
return f"{stmt_type}@{line}:{column}{extra}"
|
|
91
|
+
|
|
92
|
+
# Fallback: retain old behavior for nodes without location metadata.
|
|
93
|
+
try:
|
|
94
|
+
return str(stmt)
|
|
95
|
+
except Exception:
|
|
96
|
+
return f"{stmt_type}@{id(stmt)}"
|
|
97
|
+
|
|
98
|
+
def _enqueue_tolerant_duplicates(self, statements):
|
|
99
|
+
if not statements:
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
counts = getattr(self, "_tolerant_skip_counts", None)
|
|
103
|
+
if counts is None:
|
|
104
|
+
counts = {}
|
|
105
|
+
self._tolerant_skip_counts = counts
|
|
106
|
+
|
|
107
|
+
local_counts = {}
|
|
108
|
+
for stmt in statements:
|
|
109
|
+
if isinstance(stmt, ExpressionStatement):
|
|
110
|
+
expr = getattr(stmt, "expression", None)
|
|
111
|
+
func = getattr(expr, "function", None)
|
|
112
|
+
if (
|
|
113
|
+
isinstance(func, Identifier)
|
|
114
|
+
and getattr(func, "value", None) == "revert"
|
|
115
|
+
):
|
|
116
|
+
continue
|
|
117
|
+
sig = self._statement_signature(stmt)
|
|
118
|
+
local_counts[sig] = local_counts.get(sig, 0) + 1
|
|
119
|
+
|
|
120
|
+
duplicates_added = 0
|
|
121
|
+
for sig, occurrence in local_counts.items():
|
|
122
|
+
if occurrence > 1:
|
|
123
|
+
counts[sig] = counts.get(sig, 0) + (occurrence - 1)
|
|
124
|
+
duplicates_added += occurrence - 1
|
|
125
|
+
|
|
126
|
+
if duplicates_added:
|
|
127
|
+
if zexus_config.should_log('debug'):
|
|
128
|
+
print(f"[TOLERANT] enqueue {duplicates_added} duplicate statements")
|
|
129
|
+
|
|
61
130
|
def ceval_program(self, statements, env):
|
|
62
131
|
debug_log("eval_program", f"Processing {len(statements)} statements")
|
|
63
132
|
|
|
@@ -73,6 +142,23 @@ class StatementEvaluatorMixin:
|
|
|
73
142
|
try:
|
|
74
143
|
for i, stmt in enumerate(statements):
|
|
75
144
|
debug_log(f" Statement {i+1}", type(stmt).__name__)
|
|
145
|
+
counts = getattr(self, "_tolerant_skip_counts", None)
|
|
146
|
+
if counts:
|
|
147
|
+
signature = self._statement_signature(stmt)
|
|
148
|
+
remaining = counts.get(signature, 0)
|
|
149
|
+
if remaining > 0:
|
|
150
|
+
if zexus_config.should_log('debug'):
|
|
151
|
+
print(f"[TOLERANT] skipping program stmt {type(stmt).__name__} sig={signature} remaining={remaining}")
|
|
152
|
+
if remaining == 1:
|
|
153
|
+
counts.pop(signature, None)
|
|
154
|
+
else:
|
|
155
|
+
counts[signature] = remaining - 1
|
|
156
|
+
continue
|
|
157
|
+
if (
|
|
158
|
+
getattr(self, "_pending_revert_signature", None) is not None
|
|
159
|
+
and not isinstance(stmt, RevertStatement)
|
|
160
|
+
):
|
|
161
|
+
self._pending_revert_signature = None
|
|
76
162
|
res = self.eval_node(stmt, env)
|
|
77
163
|
res = _resolve_awaitable(res)
|
|
78
164
|
EVAL_SUMMARY['evaluated_statements'] += 1
|
|
@@ -82,6 +168,14 @@ class StatementEvaluatorMixin:
|
|
|
82
168
|
# Execute deferred cleanup before returning
|
|
83
169
|
self._execute_deferred_cleanup(env, [])
|
|
84
170
|
return res.value
|
|
171
|
+
|
|
172
|
+
# Check for ContinueException (Top-Level = Enable Error Recovery)
|
|
173
|
+
if isinstance(res, ContinueException):
|
|
174
|
+
self.continue_on_error = True
|
|
175
|
+
debug_log("ceval_program", "Enabling error recovery mode via continue")
|
|
176
|
+
result = NULL
|
|
177
|
+
continue
|
|
178
|
+
|
|
85
179
|
if is_error(res):
|
|
86
180
|
debug_log(" Error encountered", res)
|
|
87
181
|
try:
|
|
@@ -94,7 +188,8 @@ class StatementEvaluatorMixin:
|
|
|
94
188
|
# Log the error and continue execution
|
|
95
189
|
error_msg = str(res)
|
|
96
190
|
self.error_log.append(error_msg)
|
|
97
|
-
|
|
191
|
+
if zexus_config.should_log('error'):
|
|
192
|
+
print(f"[ERROR] {error_msg}")
|
|
98
193
|
debug_log(" Continuing after error", "continue_on_error=True")
|
|
99
194
|
result = NULL # Continue with null result
|
|
100
195
|
continue
|
|
@@ -124,11 +219,23 @@ class StatementEvaluatorMixin:
|
|
|
124
219
|
result = NULL
|
|
125
220
|
try:
|
|
126
221
|
for stmt in block.statements:
|
|
222
|
+
counts = getattr(self, "_tolerant_skip_counts", None)
|
|
223
|
+
if counts:
|
|
224
|
+
signature = self._statement_signature(stmt)
|
|
225
|
+
remaining = counts.get(signature, 0)
|
|
226
|
+
if remaining > 0:
|
|
227
|
+
if zexus_config.should_log('debug'):
|
|
228
|
+
print(f"[TOLERANT] skipping block stmt {type(stmt).__name__} sig={signature} remaining={remaining}")
|
|
229
|
+
if remaining == 1:
|
|
230
|
+
counts.pop(signature, None)
|
|
231
|
+
else:
|
|
232
|
+
counts[signature] = remaining - 1
|
|
233
|
+
continue
|
|
127
234
|
res = self.eval_node(stmt, env, stack_trace)
|
|
128
235
|
res = _resolve_awaitable(res)
|
|
129
236
|
EVAL_SUMMARY['evaluated_statements'] += 1
|
|
130
237
|
|
|
131
|
-
if isinstance(res, (ReturnValue, BreakException, EvaluationError)):
|
|
238
|
+
if isinstance(res, (ReturnValue, BreakException, ContinueException, EvaluationError)):
|
|
132
239
|
debug_log(" Block interrupted", res)
|
|
133
240
|
if is_error(res):
|
|
134
241
|
try:
|
|
@@ -141,7 +248,8 @@ class StatementEvaluatorMixin:
|
|
|
141
248
|
# Log the error and continue execution
|
|
142
249
|
error_msg = str(res)
|
|
143
250
|
self.error_log.append(error_msg)
|
|
144
|
-
|
|
251
|
+
if zexus_config.should_log('error'):
|
|
252
|
+
print(f"[ERROR] {error_msg}")
|
|
145
253
|
debug_log(" Continuing after error in block", "continue_on_error=True")
|
|
146
254
|
result = NULL # Continue with null result
|
|
147
255
|
continue
|
|
@@ -215,17 +323,69 @@ class StatementEvaluatorMixin:
|
|
|
215
323
|
if hasattr(node.expression, 'function') and hasattr(node.expression.function, 'value'):
|
|
216
324
|
func_name = node.expression.function.value
|
|
217
325
|
if func_name in ['persist_set', 'persist_get']:
|
|
218
|
-
|
|
326
|
+
debug_log("eval_expression_statement", f"Evaluating {func_name} call", level='info')
|
|
219
327
|
result = self.eval_node(node.expression, env, stack_trace)
|
|
220
328
|
if hasattr(node.expression, 'function') and hasattr(node.expression.function, 'value'):
|
|
221
329
|
func_name = node.expression.function.value
|
|
222
330
|
if func_name in ['persist_set', 'persist_get']:
|
|
223
|
-
|
|
331
|
+
debug_log("eval_expression_statement", f"Result from {func_name}: {result}", level='info')
|
|
224
332
|
return result
|
|
225
333
|
|
|
226
334
|
# === VARIABLE & CONTROL FLOW ===
|
|
227
335
|
|
|
336
|
+
def _eval_destructure(self, pattern, value, env, stack_trace):
|
|
337
|
+
"""Bind variables from a DestructurePattern against a runtime value.
|
|
338
|
+
|
|
339
|
+
let {a, b} = {"a": 1, "b": 2} -> env.a=1, env.b=2
|
|
340
|
+
let [x, y] = [10, 20] -> env.x=10, env.y=20
|
|
341
|
+
"""
|
|
342
|
+
from ..zexus_ast import DestructurePattern
|
|
343
|
+
if pattern.kind == 'map':
|
|
344
|
+
# Value must be a Map or dict-like
|
|
345
|
+
if not hasattr(value, 'pairs'):
|
|
346
|
+
return EvaluationError(
|
|
347
|
+
f"Cannot destructure {type(value).__name__} as map — expected a Map"
|
|
348
|
+
)
|
|
349
|
+
pairs = value.pairs
|
|
350
|
+
for source_key, target_name in pattern.bindings:
|
|
351
|
+
# Map keys are stored as strings
|
|
352
|
+
val = pairs.get(source_key)
|
|
353
|
+
if val is None:
|
|
354
|
+
# Try with String wrapper
|
|
355
|
+
val = pairs.get(f'"{source_key}"')
|
|
356
|
+
if val is None:
|
|
357
|
+
val = NULL
|
|
358
|
+
env.set(target_name, val)
|
|
359
|
+
elif pattern.kind == 'list':
|
|
360
|
+
# Value must be a List with .elements
|
|
361
|
+
if not hasattr(value, 'elements'):
|
|
362
|
+
return EvaluationError(
|
|
363
|
+
f"Cannot destructure {type(value).__name__} as list — expected a List"
|
|
364
|
+
)
|
|
365
|
+
elements = value.elements
|
|
366
|
+
for idx, target_name in pattern.bindings:
|
|
367
|
+
if idx < len(elements):
|
|
368
|
+
env.set(target_name, elements[idx])
|
|
369
|
+
else:
|
|
370
|
+
env.set(target_name, NULL)
|
|
371
|
+
# Handle rest element
|
|
372
|
+
if pattern.rest:
|
|
373
|
+
rest_start = len(pattern.bindings)
|
|
374
|
+
from ..object import List as ListObj
|
|
375
|
+
env.set(pattern.rest, ListObj(elements[rest_start:]))
|
|
376
|
+
return NULL
|
|
377
|
+
|
|
228
378
|
def eval_let_statement(self, node, env, stack_trace):
|
|
379
|
+
from ..zexus_ast import DestructurePattern
|
|
380
|
+
|
|
381
|
+
# Handle destructuring: let {a, b} = expr or let [x, y] = expr
|
|
382
|
+
if isinstance(node.name, DestructurePattern):
|
|
383
|
+
debug_log("eval_let_statement", f"let destructure ({node.name.kind})")
|
|
384
|
+
value = self.eval_node(node.value, env, stack_trace)
|
|
385
|
+
if is_error(value):
|
|
386
|
+
return value
|
|
387
|
+
return self._eval_destructure(node.name, value, env, stack_trace)
|
|
388
|
+
|
|
229
389
|
debug_log("eval_let_statement", f"let {node.name.value}")
|
|
230
390
|
|
|
231
391
|
# FIXED: Evaluate value FIRST to prevent recursion issues
|
|
@@ -280,6 +440,16 @@ class StatementEvaluatorMixin:
|
|
|
280
440
|
return value_type in expected_types
|
|
281
441
|
|
|
282
442
|
def eval_const_statement(self, node, env, stack_trace):
|
|
443
|
+
from ..zexus_ast import DestructurePattern
|
|
444
|
+
|
|
445
|
+
# Handle destructuring: const {a, b} = expr or const [x, y] = expr
|
|
446
|
+
if isinstance(node.name, DestructurePattern):
|
|
447
|
+
debug_log("eval_const_statement", f"const destructure ({node.name.kind})")
|
|
448
|
+
value = self.eval_node(node.value, env, stack_trace)
|
|
449
|
+
if is_error(value):
|
|
450
|
+
return value
|
|
451
|
+
return self._eval_destructure(node.name, value, env, stack_trace)
|
|
452
|
+
|
|
283
453
|
debug_log("eval_const_statement", f"const {node.name.value}")
|
|
284
454
|
|
|
285
455
|
# Evaluate value FIRST
|
|
@@ -812,10 +982,9 @@ class StatementEvaluatorMixin:
|
|
|
812
982
|
return ReturnValue(val)
|
|
813
983
|
|
|
814
984
|
def eval_continue_statement(self, node, env, stack_trace):
|
|
815
|
-
"""
|
|
816
|
-
debug_log("eval_continue_statement", "
|
|
817
|
-
|
|
818
|
-
return NULL
|
|
985
|
+
"""Return ContinueException to signal loop continuation or error recovery."""
|
|
986
|
+
debug_log("eval_continue_statement", "Signaling continue")
|
|
987
|
+
return ContinueException()
|
|
819
988
|
|
|
820
989
|
def eval_break_statement(self, node, env, stack_trace):
|
|
821
990
|
"""Return BreakException to signal loop exit."""
|
|
@@ -909,9 +1078,11 @@ class StatementEvaluatorMixin:
|
|
|
909
1078
|
obj.set(prop_key, value)
|
|
910
1079
|
return value
|
|
911
1080
|
except Exception as e:
|
|
912
|
-
|
|
1081
|
+
obj_type = type(obj).__name__
|
|
1082
|
+
return EvaluationError(f"Assignment to property failed for {prop_key} on {obj_type}: {e}")
|
|
913
1083
|
|
|
914
|
-
|
|
1084
|
+
obj_type = type(obj).__name__
|
|
1085
|
+
return EvaluationError(f"Assignment to property failed for {prop_key} on {obj_type}")
|
|
915
1086
|
|
|
916
1087
|
# Otherwise it's an identifier assignment
|
|
917
1088
|
if isinstance(node.name, Identifier):
|
|
@@ -932,25 +1103,50 @@ class StatementEvaluatorMixin:
|
|
|
932
1103
|
return value
|
|
933
1104
|
|
|
934
1105
|
debug_log("eval_assignment", f"Invalid assignment target - node.name: {node.name}, type: {type(node.name).__name__}")
|
|
935
|
-
|
|
1106
|
+
if zexus_config.should_log('debug'):
|
|
1107
|
+
print(f"[ASSIGN ERROR] node.name: {node.name}, type: {type(node.name).__name__}, value: {getattr(node.name, 'value', 'N/A')}")
|
|
936
1108
|
return EvaluationError('Invalid assignment target')
|
|
937
1109
|
|
|
938
1110
|
def eval_try_catch_statement(self, node, env, stack_trace):
|
|
939
1111
|
debug_log("eval_try_catch", f"error_var: {node.error_variable.value if node.error_variable else 'error'}")
|
|
940
1112
|
|
|
1113
|
+
# Clear any pending tolerant skips when entering a try/catch,
|
|
1114
|
+
# as the protected block defines a new execution context
|
|
1115
|
+
self._tolerant_skip_counts = {}
|
|
1116
|
+
|
|
1117
|
+
finally_block = getattr(node, 'finally_block', None)
|
|
1118
|
+
result = None
|
|
1119
|
+
|
|
941
1120
|
try:
|
|
942
1121
|
result = self.eval_node(node.try_block, env, stack_trace)
|
|
943
1122
|
if is_error(result):
|
|
944
1123
|
catch_env = Environment(outer=env)
|
|
945
1124
|
var_name = node.error_variable.value if node.error_variable else "error"
|
|
946
1125
|
catch_env.set(var_name, String(str(result)))
|
|
947
|
-
|
|
1126
|
+
if zexus_config.should_log('debug'):
|
|
1127
|
+
print(f"[TRY_CATCH] caught error: {result}")
|
|
1128
|
+
result = self.eval_node(node.catch_block, catch_env, stack_trace)
|
|
1129
|
+
self._tolerant_skip_counts = {}
|
|
1130
|
+
self._enqueue_tolerant_duplicates(getattr(node.try_block, "statements", []))
|
|
1131
|
+
self._enqueue_tolerant_duplicates(getattr(node.catch_block, "statements", []))
|
|
1132
|
+
return result
|
|
1133
|
+
self._tolerant_skip_counts = {}
|
|
1134
|
+
self._enqueue_tolerant_duplicates(getattr(node.try_block, "statements", []))
|
|
1135
|
+
self._enqueue_tolerant_duplicates(getattr(node.catch_block, "statements", []))
|
|
948
1136
|
return result
|
|
949
1137
|
except Exception as e:
|
|
950
1138
|
catch_env = Environment(outer=env)
|
|
951
1139
|
var_name = node.error_variable.value if node.error_variable else "error"
|
|
952
1140
|
catch_env.set(var_name, String(str(e)))
|
|
953
|
-
|
|
1141
|
+
result = self.eval_node(node.catch_block, catch_env, stack_trace)
|
|
1142
|
+
self._tolerant_skip_counts = {}
|
|
1143
|
+
self._enqueue_tolerant_duplicates(getattr(node.try_block, "statements", []))
|
|
1144
|
+
self._enqueue_tolerant_duplicates(getattr(node.catch_block, "statements", []))
|
|
1145
|
+
return result
|
|
1146
|
+
finally:
|
|
1147
|
+
# Always execute the finally block if present
|
|
1148
|
+
if finally_block is not None:
|
|
1149
|
+
self.eval_node(finally_block, env, stack_trace)
|
|
954
1150
|
|
|
955
1151
|
def eval_if_statement(self, node, env, stack_trace):
|
|
956
1152
|
cond = self.eval_node(node.condition, env, stack_trace)
|
|
@@ -980,7 +1176,11 @@ class StatementEvaluatorMixin:
|
|
|
980
1176
|
loop_id = id(node) # Unique identifier for this loop
|
|
981
1177
|
|
|
982
1178
|
# Use unified executor if available
|
|
983
|
-
if
|
|
1179
|
+
if (
|
|
1180
|
+
hasattr(self, 'unified_executor')
|
|
1181
|
+
and self.unified_executor
|
|
1182
|
+
and not getattr(env, 'disable_vm', False)
|
|
1183
|
+
):
|
|
984
1184
|
# Unified execution system handles everything automatically
|
|
985
1185
|
try:
|
|
986
1186
|
return self.unified_executor.execute_loop(
|
|
@@ -1017,6 +1217,10 @@ class StatementEvaluatorMixin:
|
|
|
1017
1217
|
if isinstance(result, BreakException):
|
|
1018
1218
|
# Break out of loop, return NULL to continue execution in block
|
|
1019
1219
|
return NULL
|
|
1220
|
+
if isinstance(result, ContinueException):
|
|
1221
|
+
# Continue loop iteration
|
|
1222
|
+
result = NULL
|
|
1223
|
+
continue
|
|
1020
1224
|
if isinstance(result, EvaluationError):
|
|
1021
1225
|
return result
|
|
1022
1226
|
def eval_foreach_statement(self, node, env, stack_trace):
|
|
@@ -1046,6 +1250,10 @@ class StatementEvaluatorMixin:
|
|
|
1046
1250
|
if isinstance(result, BreakException):
|
|
1047
1251
|
# Break out of loop, return NULL to continue execution in block
|
|
1048
1252
|
return NULL
|
|
1253
|
+
if isinstance(result, ContinueException):
|
|
1254
|
+
# Continue loop iteration
|
|
1255
|
+
result = NULL
|
|
1256
|
+
continue
|
|
1049
1257
|
if isinstance(result, EvaluationError):
|
|
1050
1258
|
return result
|
|
1051
1259
|
|
|
@@ -1186,9 +1394,38 @@ class StatementEvaluatorMixin:
|
|
|
1186
1394
|
return False
|
|
1187
1395
|
|
|
1188
1396
|
return False
|
|
1397
|
+
|
|
1398
|
+
def _try_vm_module_exec(self, program, module_env, candidate_path: str):
|
|
1399
|
+
"""Attempt to execute a module via VM bytecode for faster imports.
|
|
1400
|
+
|
|
1401
|
+
Returns compiled bytecodes if VM execution succeeds, else None.
|
|
1402
|
+
"""
|
|
1403
|
+
if not getattr(self, "use_vm", False):
|
|
1404
|
+
return None
|
|
1405
|
+
bytecode_compiler = getattr(self, "bytecode_compiler", None)
|
|
1406
|
+
if bytecode_compiler is None:
|
|
1407
|
+
return None
|
|
1408
|
+
try:
|
|
1409
|
+
if getattr(self, "vm_instance", None) is None and hasattr(self, "_initialize_vm"):
|
|
1410
|
+
self._initialize_vm()
|
|
1411
|
+
except Exception:
|
|
1412
|
+
return None
|
|
1413
|
+
try:
|
|
1414
|
+
compiled = bytecode_compiler.compile_file(candidate_path, program, optimize=True)
|
|
1415
|
+
if not compiled:
|
|
1416
|
+
return None
|
|
1417
|
+
result = self._execute_bytecode_sequence(compiled, module_env, debug_mode=False)
|
|
1418
|
+
if result is None:
|
|
1419
|
+
return None
|
|
1420
|
+
return compiled
|
|
1421
|
+
except Exception:
|
|
1422
|
+
return None
|
|
1189
1423
|
|
|
1190
1424
|
def eval_use_statement(self, node, env, stack_trace):
|
|
1191
|
-
from ..module_cache import get_cached_module, cache_module, get_module_candidates,
|
|
1425
|
+
from ..module_cache import (get_cached_module, cache_module, get_module_candidates,
|
|
1426
|
+
normalize_path, invalidate_module,
|
|
1427
|
+
begin_loading, end_loading, CircularImportError,
|
|
1428
|
+
is_loading)
|
|
1192
1429
|
from ..builtin_modules import is_builtin_module, get_builtin_module
|
|
1193
1430
|
from ..stdlib_integration import is_stdlib_module, get_stdlib_module
|
|
1194
1431
|
|
|
@@ -1198,6 +1435,7 @@ class StatementEvaluatorMixin:
|
|
|
1198
1435
|
if not file_path:
|
|
1199
1436
|
return EvaluationError("use: missing file path")
|
|
1200
1437
|
|
|
1438
|
+
debug_enabled = zexus_config.enable_debug_logs
|
|
1201
1439
|
debug_log(" UseStatement loading", file_path)
|
|
1202
1440
|
|
|
1203
1441
|
# 1a. Check if this is a stdlib module (fs, http, json, datetime, crypto, blockchain)
|
|
@@ -1222,7 +1460,8 @@ class StatementEvaluatorMixin:
|
|
|
1222
1460
|
debug_log(f" Imported '{name}' from {file_path}", value)
|
|
1223
1461
|
elif alias:
|
|
1224
1462
|
# Import as alias: use "stdlib/fs" as fs
|
|
1225
|
-
|
|
1463
|
+
alias_name = alias.value if hasattr(alias, 'value') else str(alias)
|
|
1464
|
+
env.set(alias_name, module_env)
|
|
1226
1465
|
else:
|
|
1227
1466
|
# Import all functions into current scope
|
|
1228
1467
|
for key in module_env.store.keys():
|
|
@@ -1250,10 +1489,47 @@ class StatementEvaluatorMixin:
|
|
|
1250
1489
|
return EvaluationError(f"Builtin module '{file_path}' not available")
|
|
1251
1490
|
|
|
1252
1491
|
normalized_path = normalize_path(file_path)
|
|
1253
|
-
|
|
1492
|
+
|
|
1493
|
+
# 1c. Circular import detection — check before cache lookup
|
|
1494
|
+
# because the cache may contain a partially-loaded placeholder env
|
|
1495
|
+
if is_loading(normalized_path):
|
|
1496
|
+
return EvaluationError(
|
|
1497
|
+
f"Circular import detected: {file_path} is already being loaded"
|
|
1498
|
+
)
|
|
1499
|
+
|
|
1254
1500
|
# 2. Check Cache
|
|
1255
|
-
module_env =
|
|
1256
|
-
|
|
1501
|
+
module_env = None
|
|
1502
|
+
cached_bytecode = None
|
|
1503
|
+
cached_ast = None
|
|
1504
|
+
_cache_entry = get_cached_module(normalized_path)
|
|
1505
|
+
if isinstance(_cache_entry, tuple) and _cache_entry:
|
|
1506
|
+
module_env, cached_bytecode, cached_ast = _cache_entry
|
|
1507
|
+
|
|
1508
|
+
# 2a. Handle pre-compiled but not-yet-evaluated modules
|
|
1509
|
+
if module_env and getattr(module_env, '_precompiled', False):
|
|
1510
|
+
module_env._precompiled = False # Clear flag to prevent re-entrant eval
|
|
1511
|
+
if cached_bytecode is not None:
|
|
1512
|
+
# Execute pre-compiled bytecode into module env (fastest)
|
|
1513
|
+
try:
|
|
1514
|
+
self._execute_bytecode_sequence(
|
|
1515
|
+
cached_bytecode if isinstance(cached_bytecode, (list, tuple)) else [cached_bytecode],
|
|
1516
|
+
module_env, debug_mode=False)
|
|
1517
|
+
cache_module(normalized_path, module_env, cached_bytecode, cached_ast)
|
|
1518
|
+
debug_log(" Pre-compiled module bytecode executed:", file_path)
|
|
1519
|
+
except Exception:
|
|
1520
|
+
# Bytecode execution failed — fall through to AST eval
|
|
1521
|
+
module_env._precompiled = True
|
|
1522
|
+
module_env = None
|
|
1523
|
+
elif cached_ast is not None:
|
|
1524
|
+
# Fall back to evaluating pre-parsed AST
|
|
1525
|
+
try:
|
|
1526
|
+
self.eval_node(cached_ast, module_env)
|
|
1527
|
+
cache_module(normalized_path, module_env, None, cached_ast)
|
|
1528
|
+
debug_log(" Pre-compiled module AST evaluated:", file_path)
|
|
1529
|
+
except Exception:
|
|
1530
|
+
module_env._precompiled = True
|
|
1531
|
+
module_env = None
|
|
1532
|
+
|
|
1257
1533
|
# 3. Load if not cached
|
|
1258
1534
|
if not module_env:
|
|
1259
1535
|
# Get the importing file's path for relative resolution
|
|
@@ -1264,52 +1540,109 @@ class StatementEvaluatorMixin:
|
|
|
1264
1540
|
importer_file = __file_obj.value
|
|
1265
1541
|
elif isinstance(__file_obj, str):
|
|
1266
1542
|
importer_file = __file_obj
|
|
1267
|
-
|
|
1543
|
+
|
|
1268
1544
|
candidates = get_module_candidates(file_path, importer_file)
|
|
1269
|
-
|
|
1270
|
-
|
|
1545
|
+
for candidate in candidates:
|
|
1546
|
+
try:
|
|
1547
|
+
cached = get_cached_module(normalize_path(candidate))
|
|
1548
|
+
if cached:
|
|
1549
|
+
_env, _bc, _ast = cached
|
|
1550
|
+
if getattr(_env, '_precompiled', False):
|
|
1551
|
+
# Pre-compiled but not evaluated — try executing
|
|
1552
|
+
_env._precompiled = False
|
|
1553
|
+
if _bc is not None:
|
|
1554
|
+
try:
|
|
1555
|
+
self._execute_bytecode_sequence(
|
|
1556
|
+
_bc if isinstance(_bc, (list, tuple)) else [_bc],
|
|
1557
|
+
_env, debug_mode=False)
|
|
1558
|
+
cache_module(normalize_path(candidate), _env, _bc, _ast)
|
|
1559
|
+
module_env = _env
|
|
1560
|
+
break
|
|
1561
|
+
except Exception:
|
|
1562
|
+
_env._precompiled = True
|
|
1563
|
+
elif _ast is not None:
|
|
1564
|
+
try:
|
|
1565
|
+
self.eval_node(_ast, _env)
|
|
1566
|
+
cache_module(normalize_path(candidate), _env, None, _ast)
|
|
1567
|
+
module_env = _env
|
|
1568
|
+
break
|
|
1569
|
+
except Exception:
|
|
1570
|
+
_env._precompiled = True
|
|
1571
|
+
else:
|
|
1572
|
+
module_env = _env
|
|
1573
|
+
break
|
|
1574
|
+
except Exception:
|
|
1575
|
+
continue
|
|
1576
|
+
|
|
1577
|
+
loaded = module_env is not None
|
|
1578
|
+
if not module_env:
|
|
1579
|
+
module_env = Environment()
|
|
1271
1580
|
parse_errors = []
|
|
1272
|
-
|
|
1581
|
+
|
|
1582
|
+
# Circular import detection — register this path as in-progress
|
|
1583
|
+
try:
|
|
1584
|
+
begin_loading(normalized_path)
|
|
1585
|
+
except CircularImportError as e:
|
|
1586
|
+
return EvaluationError(str(e))
|
|
1587
|
+
|
|
1273
1588
|
# Circular dependency placeholder
|
|
1274
1589
|
try:
|
|
1275
1590
|
cache_module(normalized_path, module_env)
|
|
1276
1591
|
except Exception:
|
|
1277
1592
|
pass
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1593
|
+
|
|
1594
|
+
try:
|
|
1595
|
+
if not loaded:
|
|
1596
|
+
for candidate in candidates:
|
|
1597
|
+
try:
|
|
1598
|
+
if not os.path.exists(candidate):
|
|
1599
|
+
continue
|
|
1600
|
+
|
|
1601
|
+
debug_log(" Found module file", candidate)
|
|
1602
|
+
# Use VFS cache for module reads (hot path)
|
|
1603
|
+
try:
|
|
1604
|
+
from .integration import get_integration
|
|
1605
|
+
code = get_integration().vfs_manager.cached_read(candidate)
|
|
1606
|
+
except Exception:
|
|
1607
|
+
with open(candidate, 'r', encoding='utf-8') as f:
|
|
1608
|
+
code = f.read()
|
|
1609
|
+
|
|
1610
|
+
from ..lexer import Lexer
|
|
1611
|
+
from ..parser import Parser
|
|
1612
|
+
|
|
1613
|
+
lexer = Lexer(code)
|
|
1614
|
+
parser = Parser(lexer)
|
|
1615
|
+
program = parser.parse_program()
|
|
1616
|
+
|
|
1617
|
+
if getattr(parser, 'errors', None):
|
|
1618
|
+
parse_errors.append((candidate, parser.errors))
|
|
1619
|
+
continue
|
|
1620
|
+
|
|
1621
|
+
# Set __file__ in module environment so it can do relative imports
|
|
1622
|
+
module_env.set("__file__", String(os.path.abspath(candidate)))
|
|
1623
|
+
# Set __MODULE__ to the module path (not "__main__" since it's imported)
|
|
1624
|
+
module_env.set("__MODULE__", String(file_path))
|
|
1625
|
+
|
|
1626
|
+
compiled = self._try_vm_module_exec(program, module_env, os.path.abspath(candidate))
|
|
1627
|
+
if compiled:
|
|
1628
|
+
cache_module(normalized_path, module_env, compiled, program)
|
|
1629
|
+
cache_module(normalize_path(candidate), module_env, compiled, program)
|
|
1630
|
+
loaded = True
|
|
1631
|
+
break
|
|
1632
|
+
|
|
1633
|
+
# Recursive evaluation (interpreter fallback)
|
|
1634
|
+
self.eval_node(program, module_env)
|
|
1635
|
+
|
|
1636
|
+
# Update cache with fully loaded env
|
|
1637
|
+
cache_module(normalized_path, module_env, None, program)
|
|
1638
|
+
cache_module(normalize_path(candidate), module_env, None, program)
|
|
1639
|
+
loaded = True
|
|
1640
|
+
break
|
|
1641
|
+
except Exception as e:
|
|
1642
|
+
parse_errors.append((candidate, str(e)))
|
|
1643
|
+
finally:
|
|
1644
|
+
# Always unmark, even if loading threw
|
|
1645
|
+
end_loading(normalized_path)
|
|
1313
1646
|
|
|
1314
1647
|
if not loaded:
|
|
1315
1648
|
try:
|
|
@@ -1365,12 +1698,14 @@ class StatementEvaluatorMixin:
|
|
|
1365
1698
|
|
|
1366
1699
|
elif alias:
|
|
1367
1700
|
# Handle: use "./file.zx" as alias
|
|
1368
|
-
|
|
1701
|
+
alias_name = alias.value if hasattr(alias, 'value') else str(alias)
|
|
1702
|
+
env.set(alias_name, module_env)
|
|
1369
1703
|
else:
|
|
1370
1704
|
# Handle: use "./file.zx" (import all exports)
|
|
1371
1705
|
try:
|
|
1372
1706
|
exports = module_env.get_exports()
|
|
1373
|
-
|
|
1707
|
+
if debug_enabled:
|
|
1708
|
+
print(f"[DEBUG USE] Importing from module, exports: {list(exports.keys())}")
|
|
1374
1709
|
__file_obj = env.get("__file__")
|
|
1375
1710
|
importer_file = None
|
|
1376
1711
|
if __file_obj:
|
|
@@ -1380,10 +1715,12 @@ class StatementEvaluatorMixin:
|
|
|
1380
1715
|
if importer_file:
|
|
1381
1716
|
if not self._check_import_permission(value, importer_file):
|
|
1382
1717
|
return EvaluationError(f"Permission denied for export {name}")
|
|
1383
|
-
|
|
1718
|
+
if debug_enabled:
|
|
1719
|
+
print(f"[DEBUG USE] Setting {name} = {value}")
|
|
1384
1720
|
env.set(name, value)
|
|
1385
1721
|
except Exception as e:
|
|
1386
|
-
|
|
1722
|
+
if debug_enabled:
|
|
1723
|
+
print(f"[DEBUG USE] Exception during export import: {e}")
|
|
1387
1724
|
# Fallback: expose module as filename object
|
|
1388
1725
|
module_name = os.path.basename(file_path)
|
|
1389
1726
|
env.set(module_name, module_env)
|
|
@@ -1392,7 +1729,9 @@ class StatementEvaluatorMixin:
|
|
|
1392
1729
|
|
|
1393
1730
|
def eval_from_statement(self, node, env, stack_trace):
|
|
1394
1731
|
"""Full implementation of FromStatement."""
|
|
1395
|
-
from ..module_cache import get_cached_module, cache_module, get_module_candidates,
|
|
1732
|
+
from ..module_cache import (get_cached_module, cache_module, get_module_candidates,
|
|
1733
|
+
normalize_path, invalidate_module,
|
|
1734
|
+
begin_loading, end_loading, is_loading)
|
|
1396
1735
|
|
|
1397
1736
|
# 1. Resolve Path
|
|
1398
1737
|
file_path = node.file_path
|
|
@@ -1400,7 +1739,16 @@ class StatementEvaluatorMixin:
|
|
|
1400
1739
|
return EvaluationError("from: missing file path")
|
|
1401
1740
|
|
|
1402
1741
|
normalized_path = normalize_path(file_path)
|
|
1742
|
+
|
|
1743
|
+
# Circular import detection
|
|
1744
|
+
if is_loading(normalized_path):
|
|
1745
|
+
return EvaluationError(
|
|
1746
|
+
f"Circular import detected: {file_path} is already being loaded"
|
|
1747
|
+
)
|
|
1748
|
+
|
|
1403
1749
|
module_env = get_cached_module(normalized_path)
|
|
1750
|
+
if isinstance(module_env, tuple):
|
|
1751
|
+
module_env = module_env[0] if module_env else None
|
|
1404
1752
|
|
|
1405
1753
|
# 2. Load Logic (Explicitly repeated to ensure isolation)
|
|
1406
1754
|
if not module_env:
|
|
@@ -1416,41 +1764,55 @@ class StatementEvaluatorMixin:
|
|
|
1416
1764
|
candidates = get_module_candidates(file_path, importer_file)
|
|
1417
1765
|
module_env = Environment()
|
|
1418
1766
|
loaded = False
|
|
1767
|
+
|
|
1768
|
+
try:
|
|
1769
|
+
begin_loading(normalized_path)
|
|
1770
|
+
except Exception:
|
|
1771
|
+
return EvaluationError(f"Circular import detected while loading {file_path}")
|
|
1419
1772
|
|
|
1420
1773
|
try:
|
|
1421
1774
|
cache_module(normalized_path, module_env)
|
|
1422
1775
|
except Exception:
|
|
1423
1776
|
pass
|
|
1424
1777
|
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1778
|
+
try:
|
|
1779
|
+
for candidate in candidates:
|
|
1780
|
+
try:
|
|
1781
|
+
if not os.path.exists(candidate):
|
|
1782
|
+
continue
|
|
1783
|
+
|
|
1784
|
+
with open(candidate, 'r', encoding='utf-8') as f:
|
|
1785
|
+
code = f.read()
|
|
1786
|
+
|
|
1787
|
+
from ..lexer import Lexer
|
|
1788
|
+
from ..parser import Parser
|
|
1789
|
+
|
|
1790
|
+
lexer = Lexer(code)
|
|
1791
|
+
parser = Parser(lexer)
|
|
1792
|
+
program = parser.parse_program()
|
|
1793
|
+
|
|
1794
|
+
if getattr(parser, 'errors', None):
|
|
1795
|
+
continue
|
|
1796
|
+
|
|
1797
|
+
# Set __file__ in module environment so it can do relative imports
|
|
1798
|
+
module_env.set("__file__", String(os.path.abspath(candidate)))
|
|
1799
|
+
# Set __MODULE__ to the module path (not "__main__" since it's imported)
|
|
1800
|
+
module_env.set("__MODULE__", String(file_path))
|
|
1801
|
+
|
|
1802
|
+
compiled = self._try_vm_module_exec(program, module_env, os.path.abspath(candidate))
|
|
1803
|
+
if compiled:
|
|
1804
|
+
cache_module(normalized_path, module_env, compiled, program)
|
|
1805
|
+
loaded = True
|
|
1806
|
+
break
|
|
1807
|
+
|
|
1808
|
+
self.eval_node(program, module_env)
|
|
1809
|
+
cache_module(normalized_path, module_env, None, program)
|
|
1810
|
+
loaded = True
|
|
1811
|
+
break
|
|
1812
|
+
except Exception:
|
|
1441
1813
|
continue
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
module_env.set("__file__", String(os.path.abspath(candidate)))
|
|
1445
|
-
# Set __MODULE__ to the module path (not "__main__" since it's imported)
|
|
1446
|
-
module_env.set("__MODULE__", String(file_path))
|
|
1447
|
-
|
|
1448
|
-
self.eval_node(program, module_env)
|
|
1449
|
-
cache_module(normalized_path, module_env)
|
|
1450
|
-
loaded = True
|
|
1451
|
-
break
|
|
1452
|
-
except Exception:
|
|
1453
|
-
continue
|
|
1814
|
+
finally:
|
|
1815
|
+
end_loading(normalized_path)
|
|
1454
1816
|
|
|
1455
1817
|
if not loaded:
|
|
1456
1818
|
try:
|
|
@@ -1702,7 +2064,8 @@ class StatementEvaluatorMixin:
|
|
|
1702
2064
|
|
|
1703
2065
|
Syntax: sandbox { code }
|
|
1704
2066
|
|
|
1705
|
-
Creates a new isolated environment
|
|
2067
|
+
Creates a new isolated environment with VFS-backed file access
|
|
2068
|
+
and restricted capability policy, then executes code within it.
|
|
1706
2069
|
"""
|
|
1707
2070
|
|
|
1708
2071
|
# Create isolated environment (child of current)
|
|
@@ -1712,6 +2075,26 @@ class StatementEvaluatorMixin:
|
|
|
1712
2075
|
# Allow caller to specify a policy on the node (future enhancement)
|
|
1713
2076
|
sandbox_policy = getattr(node, 'policy', None) or 'default'
|
|
1714
2077
|
sandbox_env.set('__sandbox_policy__', sandbox_policy)
|
|
2078
|
+
|
|
2079
|
+
# --- VFS integration: create a per-sandbox VFS with temp-only write ---
|
|
2080
|
+
sandbox_id = f"sandbox_{id(node)}_{id(env)}"
|
|
2081
|
+
try:
|
|
2082
|
+
from .integration import get_integration
|
|
2083
|
+
integration = get_integration()
|
|
2084
|
+
from ..virtual_filesystem import SandboxBuilder, FileAccessMode
|
|
2085
|
+
builder = SandboxBuilder(integration.vfs_manager, sandbox_id)
|
|
2086
|
+
builder.with_temp_access() # /tmp read-write
|
|
2087
|
+
# Mount CWD as read-only so sandbox can read source files
|
|
2088
|
+
import os
|
|
2089
|
+
cwd = os.getcwd()
|
|
2090
|
+
if os.path.isdir(cwd):
|
|
2091
|
+
builder.add_mount("/workspace", cwd, FileAccessMode.READ)
|
|
2092
|
+
sandbox_fs = builder.build()
|
|
2093
|
+
sandbox_env.set('__sandbox_vfs__', sandbox_fs)
|
|
2094
|
+
sandbox_env.set('__sandbox_id__', sandbox_id)
|
|
2095
|
+
except Exception:
|
|
2096
|
+
pass
|
|
2097
|
+
|
|
1715
2098
|
# Ensure default sandbox policy exists
|
|
1716
2099
|
try:
|
|
1717
2100
|
sec = get_security_context()
|
|
@@ -1731,10 +2114,16 @@ class StatementEvaluatorMixin:
|
|
|
1731
2114
|
|
|
1732
2115
|
result = self.eval_node(node.body, sandbox_env, stack_trace)
|
|
1733
2116
|
|
|
2117
|
+
# --- Cleanup VFS sandbox ---
|
|
2118
|
+
try:
|
|
2119
|
+
integration = get_integration()
|
|
2120
|
+
integration.vfs_manager.delete_sandbox(sandbox_id)
|
|
2121
|
+
except Exception:
|
|
2122
|
+
pass
|
|
2123
|
+
|
|
1734
2124
|
# Register sandbox run for observability
|
|
1735
2125
|
try:
|
|
1736
2126
|
ctx = get_security_context()
|
|
1737
|
-
# store a minimal summary (stringified result) for now
|
|
1738
2127
|
result_summary = None
|
|
1739
2128
|
try:
|
|
1740
2129
|
result_summary = str(result)
|
|
@@ -2887,7 +3276,7 @@ class StatementEvaluatorMixin:
|
|
|
2887
3276
|
|
|
2888
3277
|
def eval_function_statement(self, node, env, stack_trace):
|
|
2889
3278
|
"""Evaluate function statement - identical to action statement in Zexus"""
|
|
2890
|
-
|
|
3279
|
+
debug_log("eval_function_statement", f"Start {node.name.value}")
|
|
2891
3280
|
capture_env = env.clone_for_closure() if hasattr(env, "clone_for_closure") else env
|
|
2892
3281
|
action = Action(node.parameters, node.body, capture_env)
|
|
2893
3282
|
try:
|
|
@@ -2895,18 +3284,18 @@ class StatementEvaluatorMixin:
|
|
|
2895
3284
|
capture_env.set(node.name.value, action)
|
|
2896
3285
|
except Exception:
|
|
2897
3286
|
pass
|
|
2898
|
-
|
|
3287
|
+
debug_log("eval_function_statement", "Created Action object")
|
|
2899
3288
|
|
|
2900
3289
|
# Apply modifiers if present
|
|
2901
3290
|
modifiers = getattr(node, 'modifiers', [])
|
|
2902
|
-
|
|
3291
|
+
debug_log("eval_function_statement", f"Modifiers: {modifiers}")
|
|
2903
3292
|
if modifiers:
|
|
2904
3293
|
# Set modifier flags on the action object
|
|
2905
3294
|
if 'inline' in modifiers:
|
|
2906
3295
|
action.is_inlined = True
|
|
2907
3296
|
if 'async' in modifiers:
|
|
2908
3297
|
action.is_async = True
|
|
2909
|
-
|
|
3298
|
+
debug_log("eval_function_statement", "Set is_async=True")
|
|
2910
3299
|
if 'secure' in modifiers:
|
|
2911
3300
|
action.is_secure = True
|
|
2912
3301
|
if 'pure' in modifiers:
|
|
@@ -2921,9 +3310,9 @@ class StatementEvaluatorMixin:
|
|
|
2921
3310
|
except Exception:
|
|
2922
3311
|
pass
|
|
2923
3312
|
|
|
2924
|
-
|
|
3313
|
+
debug_log("eval_function_statement", f"Binding {node.name.value}")
|
|
2925
3314
|
env.set(node.name.value, action)
|
|
2926
|
-
|
|
3315
|
+
debug_log("eval_function_statement", "Binding complete")
|
|
2927
3316
|
return NULL
|
|
2928
3317
|
|
|
2929
3318
|
# === PERFORMANCE OPTIMIZATION STATEMENTS ===
|
|
@@ -4007,10 +4396,12 @@ class StatementEvaluatorMixin:
|
|
|
4007
4396
|
if not is_truthy(condition):
|
|
4008
4397
|
# Execute tolerance block if provided (for conditional allowances)
|
|
4009
4398
|
if node.tolerance_block:
|
|
4010
|
-
|
|
4399
|
+
if zexus_config.should_log('debug'):
|
|
4400
|
+
print(f"⚡ TOLERANCE BLOCK: type={type(node.tolerance_block).__name__}")
|
|
4011
4401
|
debug_log("eval_require_statement", "Condition failed - executing tolerance logic")
|
|
4012
4402
|
tolerance_result = self.eval_node(node.tolerance_block, env, stack_trace)
|
|
4013
|
-
|
|
4403
|
+
if zexus_config.should_log('debug'):
|
|
4404
|
+
print(f"⚡ TOLERANCE RESULT: type={type(tolerance_result).__name__}")
|
|
4014
4405
|
|
|
4015
4406
|
# Check if tolerance logic allows proceeding
|
|
4016
4407
|
if is_error(tolerance_result):
|
|
@@ -4019,18 +4410,22 @@ class StatementEvaluatorMixin:
|
|
|
4019
4410
|
# Unwrap ReturnValue if present
|
|
4020
4411
|
from ..object import ReturnValue
|
|
4021
4412
|
if isinstance(tolerance_result, ReturnValue):
|
|
4022
|
-
|
|
4413
|
+
if zexus_config.should_log('debug'):
|
|
4414
|
+
print(f"⚡ UNWRAPPING ReturnValue")
|
|
4023
4415
|
tolerance_result = tolerance_result.value
|
|
4024
|
-
|
|
4416
|
+
if zexus_config.should_log('debug'):
|
|
4417
|
+
print(f"⚡ UNWRAPPED VALUE: {tolerance_result}")
|
|
4025
4418
|
|
|
4026
4419
|
# If tolerance block returns true/truthy, allow it
|
|
4027
4420
|
if is_truthy(tolerance_result):
|
|
4028
|
-
|
|
4421
|
+
if zexus_config.should_log('debug'):
|
|
4422
|
+
print(f"⚡ TOLERANCE APPROVED")
|
|
4029
4423
|
debug_log("eval_require_statement", "Tolerance logic approved - allowing requirement")
|
|
4030
4424
|
return NULL
|
|
4031
4425
|
|
|
4032
4426
|
# If tolerance block returns false, requirement still fails
|
|
4033
|
-
|
|
4427
|
+
if zexus_config.should_log('debug'):
|
|
4428
|
+
print(f"⚡ TOLERANCE REJECTED")
|
|
4034
4429
|
debug_log("eval_require_statement", "Tolerance logic rejected - requirement fails")
|
|
4035
4430
|
# Fall through to error below
|
|
4036
4431
|
|
|
@@ -4151,25 +4546,32 @@ class StatementEvaluatorMixin:
|
|
|
4151
4546
|
debug_log("_eval_require_resource", f"{req_type} requirement satisfied")
|
|
4152
4547
|
return NULL
|
|
4153
4548
|
|
|
4154
|
-
def
|
|
4155
|
-
"""Evaluate revert statement - rollback transaction.
|
|
4156
|
-
|
|
4157
|
-
revert();
|
|
4158
|
-
revert("Unauthorized");
|
|
4159
|
-
"""
|
|
4549
|
+
def _perform_revert(self, reason_expr, env, stack_trace):
|
|
4160
4550
|
debug_log("eval_revert_statement", "Reverting transaction")
|
|
4161
|
-
|
|
4162
|
-
# Evaluate revert reason if provided
|
|
4551
|
+
|
|
4163
4552
|
reason = "Transaction reverted"
|
|
4164
|
-
if
|
|
4165
|
-
reason_val = self.eval_node(
|
|
4553
|
+
if reason_expr:
|
|
4554
|
+
reason_val = self.eval_node(reason_expr, env, stack_trace)
|
|
4166
4555
|
if isinstance(reason_val, String):
|
|
4167
4556
|
reason = reason_val.value
|
|
4168
4557
|
elif not is_error(reason_val):
|
|
4169
4558
|
reason = str(reason_val.inspect() if hasattr(reason_val, 'inspect') else reason_val)
|
|
4170
|
-
|
|
4559
|
+
|
|
4171
4560
|
debug_log("eval_revert_statement", f"REVERT: {reason}")
|
|
4172
4561
|
return EvaluationError(f"Transaction reverted: {reason}", stack_trace=stack_trace)
|
|
4562
|
+
|
|
4563
|
+
def eval_revert_statement(self, node, env, stack_trace):
|
|
4564
|
+
"""Evaluate revert statement - rollback transaction."""
|
|
4565
|
+
|
|
4566
|
+
signature = self._compute_revert_signature(getattr(node, "reason", None))
|
|
4567
|
+
pending = getattr(self, "_pending_revert_signature", None)
|
|
4568
|
+
if pending is not None and pending == signature:
|
|
4569
|
+
debug_log("eval_revert_statement", "Skipping duplicate revert statement")
|
|
4570
|
+
self._pending_revert_signature = None
|
|
4571
|
+
return NULL
|
|
4572
|
+
|
|
4573
|
+
self._pending_revert_signature = None
|
|
4574
|
+
return self._perform_revert(getattr(node, "reason", None), env, stack_trace)
|
|
4173
4575
|
|
|
4174
4576
|
def eval_limit_statement(self, node, env, stack_trace):
|
|
4175
4577
|
"""Evaluate limit statement - set gas limit.
|
|
@@ -4496,7 +4898,8 @@ class StatementEvaluatorMixin:
|
|
|
4496
4898
|
|
|
4497
4899
|
# Print event for debugging (optional)
|
|
4498
4900
|
args_str = ", ".join(str(arg.inspect() if hasattr(arg, 'inspect') else arg) for arg in args)
|
|
4499
|
-
|
|
4901
|
+
if zexus_config.should_log('info'):
|
|
4902
|
+
print(f"📢 Event: {event_name}({args_str})")
|
|
4500
4903
|
|
|
4501
4904
|
return NULL
|
|
4502
4905
|
|