zexus 1.6.8 → 1.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -5
- 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/capability_system.py +184 -9
- package/src/zexus/cli/__pycache__/main.cpython-312.pyc +0 -0
- package/src/zexus/cli/main.py +383 -34
- 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/bytecode.py +124 -7
- package/src/zexus/compiler/compat_runtime.py +6 -2
- package/src/zexus/compiler/lexer.py +16 -5
- package/src/zexus/compiler/parser.py +108 -7
- package/src/zexus/compiler/semantic.py +18 -19
- package/src/zexus/compiler/zexus_ast.py +26 -1
- 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 +112 -9
- 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 +457 -37
- package/src/zexus/evaluator/core.py +644 -50
- package/src/zexus/evaluator/expressions.py +358 -62
- package/src/zexus/evaluator/functions.py +458 -20
- package/src/zexus/evaluator/resource_limiter.py +4 -4
- package/src/zexus/evaluator/statements.py +774 -122
- package/src/zexus/evaluator/unified_execution.py +573 -72
- package/src/zexus/evaluator/utils.py +14 -2
- package/src/zexus/evaluator_original.py +1 -1
- package/src/zexus/event_loop.py +186 -0
- package/src/zexus/lexer.py +742 -458
- 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 +239 -9
- package/src/zexus/module_manager.py +129 -1
- package/src/zexus/object.py +76 -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 +1349 -408
- package/src/zexus/parser/strategy_context.py +755 -58
- package/src/zexus/parser/strategy_structural.py +121 -21
- package/src/zexus/persistence.py +15 -1
- package/src/zexus/renderer/__init__.py +61 -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/backend.py +261 -0
- package/src/zexus/renderer/canvas.py +78 -0
- package/src/zexus/renderer/color_system.py +201 -0
- package/src/zexus/renderer/graphics.py +31 -0
- package/src/zexus/renderer/layout.py +222 -0
- package/src/zexus/renderer/main_renderer.py +66 -0
- package/src/zexus/renderer/painter.py +30 -0
- package/src/zexus/renderer/tk_backend.py +208 -0
- package/src/zexus/renderer/web_backend.py +260 -0
- package/src/zexus/runtime/__init__.py +10 -2
- 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/runtime/load_manager.py +368 -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 +80 -6
- package/src/zexus/vm/binary_bytecode.py +659 -0
- package/src/zexus/vm/bytecode.py +59 -11
- 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 +561 -17
- package/src/zexus/vm/compiler.py +818 -51
- 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 +364 -20
- 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 +140 -45
- 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 +3581 -531
- package/src/zexus/vm/wasm_compiler.py +658 -0
- package/src/zexus/zexus_ast.py +137 -11
- package/src/zexus/zexus_token.py +16 -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 +16 -6
- package/src/zexus.egg-info/SOURCES.txt +129 -17
- package/src/zexus.egg-info/entry_points.txt +1 -0
- package/src/zexus.egg-info/requires.txt +4 -0
|
@@ -8,7 +8,8 @@ from ..zexus_ast import (
|
|
|
8
8
|
TryCatchStatement, UseStatement, FromStatement, ExportStatement,
|
|
9
9
|
ContractStatement, EntityStatement, VerifyStatement, ProtectStatement,
|
|
10
10
|
SealStatement, MiddlewareStatement, AuthStatement, ThrottleStatement, CacheStatement,
|
|
11
|
-
ComponentStatement, ThemeStatement,
|
|
11
|
+
ComponentStatement, ThemeStatement, ColorStatement, CanvasStatement, GraphicsStatement,
|
|
12
|
+
AnimationStatement, ClockStatement, DebugStatement, ExternalDeclaration, AssignmentExpression,
|
|
12
13
|
PrintStatement, ScreenStatement, EmbeddedCodeStatement, ExactlyStatement,
|
|
13
14
|
Identifier, PropertyAccessExpression, RestrictStatement, SandboxStatement, TrailStatement,
|
|
14
15
|
NativeStatement, GCStatement, InlineStatement, BufferStatement, SIMDStatement,
|
|
@@ -30,6 +31,24 @@ from ..security import (
|
|
|
30
31
|
ProtectionPolicy, Middleware, AuthConfig, RateLimiter, CachePolicy
|
|
31
32
|
)
|
|
32
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
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
from ..renderer import (
|
|
38
|
+
create_named_canvas as renderer_create_named_canvas,
|
|
39
|
+
define_color as renderer_define_color,
|
|
40
|
+
register_animation as renderer_register_animation,
|
|
41
|
+
register_clock as renderer_register_clock,
|
|
42
|
+
register_graphics as renderer_register_graphics,
|
|
43
|
+
)
|
|
44
|
+
_RENDERER_AVAILABLE = True
|
|
45
|
+
except Exception: # pragma: no cover - renderer optional during bootstrap
|
|
46
|
+
renderer_create_named_canvas = None
|
|
47
|
+
renderer_define_color = None
|
|
48
|
+
renderer_register_animation = None
|
|
49
|
+
renderer_register_clock = None
|
|
50
|
+
renderer_register_graphics = None
|
|
51
|
+
_RENDERER_AVAILABLE = False
|
|
33
52
|
|
|
34
53
|
# Break exception for loop control flow
|
|
35
54
|
class BreakException:
|
|
@@ -37,9 +56,77 @@ class BreakException:
|
|
|
37
56
|
def __repr__(self):
|
|
38
57
|
return "BreakException()"
|
|
39
58
|
|
|
59
|
+
class ContinueException:
|
|
60
|
+
"""Exception raised when continue statement is encountered in a loop."""
|
|
61
|
+
def __repr__(self):
|
|
62
|
+
return "ContinueException()"
|
|
63
|
+
|
|
64
|
+
|
|
40
65
|
class StatementEvaluatorMixin:
|
|
41
66
|
"""Handles evaluation of statements, flow control, module loading, and security features."""
|
|
42
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
|
+
|
|
43
130
|
def ceval_program(self, statements, env):
|
|
44
131
|
debug_log("eval_program", f"Processing {len(statements)} statements")
|
|
45
132
|
|
|
@@ -55,6 +142,23 @@ class StatementEvaluatorMixin:
|
|
|
55
142
|
try:
|
|
56
143
|
for i, stmt in enumerate(statements):
|
|
57
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
|
|
58
162
|
res = self.eval_node(stmt, env)
|
|
59
163
|
res = _resolve_awaitable(res)
|
|
60
164
|
EVAL_SUMMARY['evaluated_statements'] += 1
|
|
@@ -64,6 +168,14 @@ class StatementEvaluatorMixin:
|
|
|
64
168
|
# Execute deferred cleanup before returning
|
|
65
169
|
self._execute_deferred_cleanup(env, [])
|
|
66
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
|
+
|
|
67
179
|
if is_error(res):
|
|
68
180
|
debug_log(" Error encountered", res)
|
|
69
181
|
try:
|
|
@@ -76,7 +188,8 @@ class StatementEvaluatorMixin:
|
|
|
76
188
|
# Log the error and continue execution
|
|
77
189
|
error_msg = str(res)
|
|
78
190
|
self.error_log.append(error_msg)
|
|
79
|
-
|
|
191
|
+
if zexus_config.should_log('error'):
|
|
192
|
+
print(f"[ERROR] {error_msg}")
|
|
80
193
|
debug_log(" Continuing after error", "continue_on_error=True")
|
|
81
194
|
result = NULL # Continue with null result
|
|
82
195
|
continue
|
|
@@ -106,11 +219,23 @@ class StatementEvaluatorMixin:
|
|
|
106
219
|
result = NULL
|
|
107
220
|
try:
|
|
108
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
|
|
109
234
|
res = self.eval_node(stmt, env, stack_trace)
|
|
110
235
|
res = _resolve_awaitable(res)
|
|
111
236
|
EVAL_SUMMARY['evaluated_statements'] += 1
|
|
112
237
|
|
|
113
|
-
if isinstance(res, (ReturnValue, BreakException, EvaluationError)):
|
|
238
|
+
if isinstance(res, (ReturnValue, BreakException, ContinueException, EvaluationError)):
|
|
114
239
|
debug_log(" Block interrupted", res)
|
|
115
240
|
if is_error(res):
|
|
116
241
|
try:
|
|
@@ -123,7 +248,8 @@ class StatementEvaluatorMixin:
|
|
|
123
248
|
# Log the error and continue execution
|
|
124
249
|
error_msg = str(res)
|
|
125
250
|
self.error_log.append(error_msg)
|
|
126
|
-
|
|
251
|
+
if zexus_config.should_log('error'):
|
|
252
|
+
print(f"[ERROR] {error_msg}")
|
|
127
253
|
debug_log(" Continuing after error in block", "continue_on_error=True")
|
|
128
254
|
result = NULL # Continue with null result
|
|
129
255
|
continue
|
|
@@ -142,23 +268,124 @@ class StatementEvaluatorMixin:
|
|
|
142
268
|
self._execute_deferred_cleanup(env, stack_trace)
|
|
143
269
|
# Restore stdout to previous state (scope-aware)
|
|
144
270
|
self._restore_stdout(env)
|
|
145
|
-
|
|
271
|
+
|
|
272
|
+
# ------------------------------------------------------------------
|
|
273
|
+
# Renderer helpers
|
|
274
|
+
# ------------------------------------------------------------------
|
|
275
|
+
def _evaluate_renderer_block(self, block, env, stack_trace):
|
|
276
|
+
local_env = Environment(outer=env)
|
|
277
|
+
result = self.eval_block_statement(block, local_env, list(stack_trace) if stack_trace else [])
|
|
278
|
+
|
|
279
|
+
if isinstance(result, ReturnValue):
|
|
280
|
+
result = result.value
|
|
281
|
+
if is_error(result):
|
|
282
|
+
return result
|
|
283
|
+
|
|
284
|
+
collected = {}
|
|
285
|
+
for key, value in local_env.store.items():
|
|
286
|
+
if key.startswith("__"):
|
|
287
|
+
continue
|
|
288
|
+
collected[key] = value
|
|
289
|
+
return collected
|
|
290
|
+
|
|
291
|
+
def _evaluate_renderer_properties(self, prop_node, env, stack_trace):
|
|
292
|
+
if prop_node is None:
|
|
293
|
+
return None
|
|
294
|
+
|
|
295
|
+
if isinstance(prop_node, list):
|
|
296
|
+
values = []
|
|
297
|
+
for expr in prop_node:
|
|
298
|
+
val = self.eval_node(expr, env, stack_trace)
|
|
299
|
+
val = _resolve_awaitable(val)
|
|
300
|
+
if isinstance(val, ReturnValue):
|
|
301
|
+
val = val.value
|
|
302
|
+
if is_error(val):
|
|
303
|
+
return val
|
|
304
|
+
values.append(_zexus_to_python(val))
|
|
305
|
+
return values
|
|
306
|
+
|
|
307
|
+
if isinstance(prop_node, BlockStatement):
|
|
308
|
+
bindings = self._evaluate_renderer_block(prop_node, env, stack_trace)
|
|
309
|
+
if is_error(bindings):
|
|
310
|
+
return bindings
|
|
311
|
+
return {key: _zexus_to_python(val) for key, val in bindings.items()}
|
|
312
|
+
|
|
313
|
+
val = self.eval_node(prop_node, env, stack_trace)
|
|
314
|
+
val = _resolve_awaitable(val)
|
|
315
|
+
if isinstance(val, ReturnValue):
|
|
316
|
+
val = val.value
|
|
317
|
+
if is_error(val):
|
|
318
|
+
return val
|
|
319
|
+
return _zexus_to_python(val)
|
|
320
|
+
|
|
146
321
|
def eval_expression_statement(self, node, env, stack_trace):
|
|
147
322
|
# Debug: Check if expression is being evaluated
|
|
148
323
|
if hasattr(node.expression, 'function') and hasattr(node.expression.function, 'value'):
|
|
149
324
|
func_name = node.expression.function.value
|
|
150
325
|
if func_name in ['persist_set', 'persist_get']:
|
|
151
|
-
|
|
326
|
+
debug_log("eval_expression_statement", f"Evaluating {func_name} call", level='info')
|
|
152
327
|
result = self.eval_node(node.expression, env, stack_trace)
|
|
153
328
|
if hasattr(node.expression, 'function') and hasattr(node.expression.function, 'value'):
|
|
154
329
|
func_name = node.expression.function.value
|
|
155
330
|
if func_name in ['persist_set', 'persist_get']:
|
|
156
|
-
|
|
331
|
+
debug_log("eval_expression_statement", f"Result from {func_name}: {result}", level='info')
|
|
157
332
|
return result
|
|
158
333
|
|
|
159
334
|
# === VARIABLE & CONTROL FLOW ===
|
|
160
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
|
+
|
|
161
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
|
+
|
|
162
389
|
debug_log("eval_let_statement", f"let {node.name.value}")
|
|
163
390
|
|
|
164
391
|
# FIXED: Evaluate value FIRST to prevent recursion issues
|
|
@@ -213,6 +440,16 @@ class StatementEvaluatorMixin:
|
|
|
213
440
|
return value_type in expected_types
|
|
214
441
|
|
|
215
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
|
+
|
|
216
453
|
debug_log("eval_const_statement", f"const {node.name.value}")
|
|
217
454
|
|
|
218
455
|
# Evaluate value FIRST
|
|
@@ -745,10 +982,9 @@ class StatementEvaluatorMixin:
|
|
|
745
982
|
return ReturnValue(val)
|
|
746
983
|
|
|
747
984
|
def eval_continue_statement(self, node, env, stack_trace):
|
|
748
|
-
"""
|
|
749
|
-
debug_log("eval_continue_statement", "
|
|
750
|
-
|
|
751
|
-
return NULL
|
|
985
|
+
"""Return ContinueException to signal loop continuation or error recovery."""
|
|
986
|
+
debug_log("eval_continue_statement", "Signaling continue")
|
|
987
|
+
return ContinueException()
|
|
752
988
|
|
|
753
989
|
def eval_break_statement(self, node, env, stack_trace):
|
|
754
990
|
"""Return BreakException to signal loop exit."""
|
|
@@ -842,9 +1078,11 @@ class StatementEvaluatorMixin:
|
|
|
842
1078
|
obj.set(prop_key, value)
|
|
843
1079
|
return value
|
|
844
1080
|
except Exception as e:
|
|
845
|
-
|
|
1081
|
+
obj_type = type(obj).__name__
|
|
1082
|
+
return EvaluationError(f"Assignment to property failed for {prop_key} on {obj_type}: {e}")
|
|
846
1083
|
|
|
847
|
-
|
|
1084
|
+
obj_type = type(obj).__name__
|
|
1085
|
+
return EvaluationError(f"Assignment to property failed for {prop_key} on {obj_type}")
|
|
848
1086
|
|
|
849
1087
|
# Otherwise it's an identifier assignment
|
|
850
1088
|
if isinstance(node.name, Identifier):
|
|
@@ -865,25 +1103,50 @@ class StatementEvaluatorMixin:
|
|
|
865
1103
|
return value
|
|
866
1104
|
|
|
867
1105
|
debug_log("eval_assignment", f"Invalid assignment target - node.name: {node.name}, type: {type(node.name).__name__}")
|
|
868
|
-
|
|
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')}")
|
|
869
1108
|
return EvaluationError('Invalid assignment target')
|
|
870
1109
|
|
|
871
1110
|
def eval_try_catch_statement(self, node, env, stack_trace):
|
|
872
1111
|
debug_log("eval_try_catch", f"error_var: {node.error_variable.value if node.error_variable else 'error'}")
|
|
873
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
|
+
|
|
874
1120
|
try:
|
|
875
1121
|
result = self.eval_node(node.try_block, env, stack_trace)
|
|
876
1122
|
if is_error(result):
|
|
877
1123
|
catch_env = Environment(outer=env)
|
|
878
1124
|
var_name = node.error_variable.value if node.error_variable else "error"
|
|
879
1125
|
catch_env.set(var_name, String(str(result)))
|
|
880
|
-
|
|
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", []))
|
|
881
1136
|
return result
|
|
882
1137
|
except Exception as e:
|
|
883
1138
|
catch_env = Environment(outer=env)
|
|
884
1139
|
var_name = node.error_variable.value if node.error_variable else "error"
|
|
885
1140
|
catch_env.set(var_name, String(str(e)))
|
|
886
|
-
|
|
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)
|
|
887
1150
|
|
|
888
1151
|
def eval_if_statement(self, node, env, stack_trace):
|
|
889
1152
|
cond = self.eval_node(node.condition, env, stack_trace)
|
|
@@ -913,7 +1176,11 @@ class StatementEvaluatorMixin:
|
|
|
913
1176
|
loop_id = id(node) # Unique identifier for this loop
|
|
914
1177
|
|
|
915
1178
|
# Use unified executor if available
|
|
916
|
-
if
|
|
1179
|
+
if (
|
|
1180
|
+
hasattr(self, 'unified_executor')
|
|
1181
|
+
and self.unified_executor
|
|
1182
|
+
and not getattr(env, 'disable_vm', False)
|
|
1183
|
+
):
|
|
917
1184
|
# Unified execution system handles everything automatically
|
|
918
1185
|
try:
|
|
919
1186
|
return self.unified_executor.execute_loop(
|
|
@@ -950,6 +1217,10 @@ class StatementEvaluatorMixin:
|
|
|
950
1217
|
if isinstance(result, BreakException):
|
|
951
1218
|
# Break out of loop, return NULL to continue execution in block
|
|
952
1219
|
return NULL
|
|
1220
|
+
if isinstance(result, ContinueException):
|
|
1221
|
+
# Continue loop iteration
|
|
1222
|
+
result = NULL
|
|
1223
|
+
continue
|
|
953
1224
|
if isinstance(result, EvaluationError):
|
|
954
1225
|
return result
|
|
955
1226
|
def eval_foreach_statement(self, node, env, stack_trace):
|
|
@@ -979,6 +1250,10 @@ class StatementEvaluatorMixin:
|
|
|
979
1250
|
if isinstance(result, BreakException):
|
|
980
1251
|
# Break out of loop, return NULL to continue execution in block
|
|
981
1252
|
return NULL
|
|
1253
|
+
if isinstance(result, ContinueException):
|
|
1254
|
+
# Continue loop iteration
|
|
1255
|
+
result = NULL
|
|
1256
|
+
continue
|
|
982
1257
|
if isinstance(result, EvaluationError):
|
|
983
1258
|
return result
|
|
984
1259
|
|
|
@@ -1119,9 +1394,38 @@ class StatementEvaluatorMixin:
|
|
|
1119
1394
|
return False
|
|
1120
1395
|
|
|
1121
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
|
|
1122
1423
|
|
|
1123
1424
|
def eval_use_statement(self, node, env, stack_trace):
|
|
1124
|
-
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)
|
|
1125
1429
|
from ..builtin_modules import is_builtin_module, get_builtin_module
|
|
1126
1430
|
from ..stdlib_integration import is_stdlib_module, get_stdlib_module
|
|
1127
1431
|
|
|
@@ -1131,6 +1435,7 @@ class StatementEvaluatorMixin:
|
|
|
1131
1435
|
if not file_path:
|
|
1132
1436
|
return EvaluationError("use: missing file path")
|
|
1133
1437
|
|
|
1438
|
+
debug_enabled = zexus_config.enable_debug_logs
|
|
1134
1439
|
debug_log(" UseStatement loading", file_path)
|
|
1135
1440
|
|
|
1136
1441
|
# 1a. Check if this is a stdlib module (fs, http, json, datetime, crypto, blockchain)
|
|
@@ -1155,7 +1460,8 @@ class StatementEvaluatorMixin:
|
|
|
1155
1460
|
debug_log(f" Imported '{name}' from {file_path}", value)
|
|
1156
1461
|
elif alias:
|
|
1157
1462
|
# Import as alias: use "stdlib/fs" as fs
|
|
1158
|
-
|
|
1463
|
+
alias_name = alias.value if hasattr(alias, 'value') else str(alias)
|
|
1464
|
+
env.set(alias_name, module_env)
|
|
1159
1465
|
else:
|
|
1160
1466
|
# Import all functions into current scope
|
|
1161
1467
|
for key in module_env.store.keys():
|
|
@@ -1183,10 +1489,47 @@ class StatementEvaluatorMixin:
|
|
|
1183
1489
|
return EvaluationError(f"Builtin module '{file_path}' not available")
|
|
1184
1490
|
|
|
1185
1491
|
normalized_path = normalize_path(file_path)
|
|
1186
|
-
|
|
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
|
+
|
|
1187
1500
|
# 2. Check Cache
|
|
1188
|
-
module_env =
|
|
1189
|
-
|
|
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
|
+
|
|
1190
1533
|
# 3. Load if not cached
|
|
1191
1534
|
if not module_env:
|
|
1192
1535
|
# Get the importing file's path for relative resolution
|
|
@@ -1197,52 +1540,109 @@ class StatementEvaluatorMixin:
|
|
|
1197
1540
|
importer_file = __file_obj.value
|
|
1198
1541
|
elif isinstance(__file_obj, str):
|
|
1199
1542
|
importer_file = __file_obj
|
|
1200
|
-
|
|
1543
|
+
|
|
1201
1544
|
candidates = get_module_candidates(file_path, importer_file)
|
|
1202
|
-
|
|
1203
|
-
|
|
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()
|
|
1204
1580
|
parse_errors = []
|
|
1205
|
-
|
|
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
|
+
|
|
1206
1588
|
# Circular dependency placeholder
|
|
1207
1589
|
try:
|
|
1208
1590
|
cache_module(normalized_path, module_env)
|
|
1209
1591
|
except Exception:
|
|
1210
1592
|
pass
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
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)
|
|
1246
1646
|
|
|
1247
1647
|
if not loaded:
|
|
1248
1648
|
try:
|
|
@@ -1298,12 +1698,14 @@ class StatementEvaluatorMixin:
|
|
|
1298
1698
|
|
|
1299
1699
|
elif alias:
|
|
1300
1700
|
# Handle: use "./file.zx" as alias
|
|
1301
|
-
|
|
1701
|
+
alias_name = alias.value if hasattr(alias, 'value') else str(alias)
|
|
1702
|
+
env.set(alias_name, module_env)
|
|
1302
1703
|
else:
|
|
1303
1704
|
# Handle: use "./file.zx" (import all exports)
|
|
1304
1705
|
try:
|
|
1305
1706
|
exports = module_env.get_exports()
|
|
1306
|
-
|
|
1707
|
+
if debug_enabled:
|
|
1708
|
+
print(f"[DEBUG USE] Importing from module, exports: {list(exports.keys())}")
|
|
1307
1709
|
__file_obj = env.get("__file__")
|
|
1308
1710
|
importer_file = None
|
|
1309
1711
|
if __file_obj:
|
|
@@ -1313,10 +1715,12 @@ class StatementEvaluatorMixin:
|
|
|
1313
1715
|
if importer_file:
|
|
1314
1716
|
if not self._check_import_permission(value, importer_file):
|
|
1315
1717
|
return EvaluationError(f"Permission denied for export {name}")
|
|
1316
|
-
|
|
1718
|
+
if debug_enabled:
|
|
1719
|
+
print(f"[DEBUG USE] Setting {name} = {value}")
|
|
1317
1720
|
env.set(name, value)
|
|
1318
1721
|
except Exception as e:
|
|
1319
|
-
|
|
1722
|
+
if debug_enabled:
|
|
1723
|
+
print(f"[DEBUG USE] Exception during export import: {e}")
|
|
1320
1724
|
# Fallback: expose module as filename object
|
|
1321
1725
|
module_name = os.path.basename(file_path)
|
|
1322
1726
|
env.set(module_name, module_env)
|
|
@@ -1325,7 +1729,9 @@ class StatementEvaluatorMixin:
|
|
|
1325
1729
|
|
|
1326
1730
|
def eval_from_statement(self, node, env, stack_trace):
|
|
1327
1731
|
"""Full implementation of FromStatement."""
|
|
1328
|
-
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)
|
|
1329
1735
|
|
|
1330
1736
|
# 1. Resolve Path
|
|
1331
1737
|
file_path = node.file_path
|
|
@@ -1333,7 +1739,16 @@ class StatementEvaluatorMixin:
|
|
|
1333
1739
|
return EvaluationError("from: missing file path")
|
|
1334
1740
|
|
|
1335
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
|
+
|
|
1336
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
|
|
1337
1752
|
|
|
1338
1753
|
# 2. Load Logic (Explicitly repeated to ensure isolation)
|
|
1339
1754
|
if not module_env:
|
|
@@ -1349,41 +1764,55 @@ class StatementEvaluatorMixin:
|
|
|
1349
1764
|
candidates = get_module_candidates(file_path, importer_file)
|
|
1350
1765
|
module_env = Environment()
|
|
1351
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}")
|
|
1352
1772
|
|
|
1353
1773
|
try:
|
|
1354
1774
|
cache_module(normalized_path, module_env)
|
|
1355
1775
|
except Exception:
|
|
1356
1776
|
pass
|
|
1357
1777
|
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
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:
|
|
1374
1813
|
continue
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
module_env.set("__file__", String(os.path.abspath(candidate)))
|
|
1378
|
-
# Set __MODULE__ to the module path (not "__main__" since it's imported)
|
|
1379
|
-
module_env.set("__MODULE__", String(file_path))
|
|
1380
|
-
|
|
1381
|
-
self.eval_node(program, module_env)
|
|
1382
|
-
cache_module(normalized_path, module_env)
|
|
1383
|
-
loaded = True
|
|
1384
|
-
break
|
|
1385
|
-
except Exception:
|
|
1386
|
-
continue
|
|
1814
|
+
finally:
|
|
1815
|
+
end_loading(normalized_path)
|
|
1387
1816
|
|
|
1388
1817
|
if not loaded:
|
|
1389
1818
|
try:
|
|
@@ -1635,7 +2064,8 @@ class StatementEvaluatorMixin:
|
|
|
1635
2064
|
|
|
1636
2065
|
Syntax: sandbox { code }
|
|
1637
2066
|
|
|
1638
|
-
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.
|
|
1639
2069
|
"""
|
|
1640
2070
|
|
|
1641
2071
|
# Create isolated environment (child of current)
|
|
@@ -1645,6 +2075,26 @@ class StatementEvaluatorMixin:
|
|
|
1645
2075
|
# Allow caller to specify a policy on the node (future enhancement)
|
|
1646
2076
|
sandbox_policy = getattr(node, 'policy', None) or 'default'
|
|
1647
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
|
+
|
|
1648
2098
|
# Ensure default sandbox policy exists
|
|
1649
2099
|
try:
|
|
1650
2100
|
sec = get_security_context()
|
|
@@ -1664,10 +2114,16 @@ class StatementEvaluatorMixin:
|
|
|
1664
2114
|
|
|
1665
2115
|
result = self.eval_node(node.body, sandbox_env, stack_trace)
|
|
1666
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
|
+
|
|
1667
2124
|
# Register sandbox run for observability
|
|
1668
2125
|
try:
|
|
1669
2126
|
ctx = get_security_context()
|
|
1670
|
-
# store a minimal summary (stringified result) for now
|
|
1671
2127
|
result_summary = None
|
|
1672
2128
|
try:
|
|
1673
2129
|
result_summary = str(result)
|
|
@@ -2525,6 +2981,174 @@ class StatementEvaluatorMixin:
|
|
|
2525
2981
|
obj = EmbeddedCode(node.name.value, node.language, node.code)
|
|
2526
2982
|
env.set(node.name.value, obj)
|
|
2527
2983
|
return NULL
|
|
2984
|
+
|
|
2985
|
+
def eval_color_statement(self, node, env, stack_trace):
|
|
2986
|
+
if node.value is None:
|
|
2987
|
+
return EvaluationError(f"color {node.name.value}: missing definition")
|
|
2988
|
+
|
|
2989
|
+
if isinstance(node.value, BlockStatement):
|
|
2990
|
+
bindings = self._evaluate_renderer_block(node.value, env, stack_trace)
|
|
2991
|
+
if is_error(bindings):
|
|
2992
|
+
return bindings
|
|
2993
|
+
python_spec = {key: _zexus_to_python(val) for key, val in bindings.items()}
|
|
2994
|
+
else:
|
|
2995
|
+
spec_val = self.eval_node(node.value, env, stack_trace)
|
|
2996
|
+
spec_val = _resolve_awaitable(spec_val)
|
|
2997
|
+
if isinstance(spec_val, ReturnValue):
|
|
2998
|
+
spec_val = spec_val.value
|
|
2999
|
+
if is_error(spec_val):
|
|
3000
|
+
return spec_val
|
|
3001
|
+
python_spec = _zexus_to_python(spec_val)
|
|
3002
|
+
|
|
3003
|
+
colour_repr = python_spec
|
|
3004
|
+
if _RENDERER_AVAILABLE and renderer_define_color:
|
|
3005
|
+
try:
|
|
3006
|
+
colour_obj = renderer_define_color(node.name.value, python_spec)
|
|
3007
|
+
colour_repr = str(colour_obj)
|
|
3008
|
+
except Exception as exc:
|
|
3009
|
+
return EvaluationError(f"color {node.name.value}: {exc}")
|
|
3010
|
+
else:
|
|
3011
|
+
registry = getattr(self, 'render_registry', None)
|
|
3012
|
+
if isinstance(registry, dict):
|
|
3013
|
+
registry.setdefault('colours', {})[node.name.value] = python_spec
|
|
3014
|
+
|
|
3015
|
+
env.set(node.name.value, _python_to_zexus(colour_repr))
|
|
3016
|
+
return NULL
|
|
3017
|
+
|
|
3018
|
+
def eval_canvas_statement(self, node, env, stack_trace):
|
|
3019
|
+
prop_data = self._evaluate_renderer_properties(node.properties, env, stack_trace)
|
|
3020
|
+
if is_error(prop_data):
|
|
3021
|
+
return prop_data
|
|
3022
|
+
|
|
3023
|
+
width = 80
|
|
3024
|
+
height = 24
|
|
3025
|
+
|
|
3026
|
+
if isinstance(prop_data, list):
|
|
3027
|
+
if len(prop_data) > 0:
|
|
3028
|
+
width = prop_data[0]
|
|
3029
|
+
if len(prop_data) > 1:
|
|
3030
|
+
height = prop_data[1]
|
|
3031
|
+
elif isinstance(prop_data, dict):
|
|
3032
|
+
width = prop_data.get('width', prop_data.get('w', width))
|
|
3033
|
+
height = prop_data.get('height', prop_data.get('h', height))
|
|
3034
|
+
elif prop_data is not None:
|
|
3035
|
+
width = prop_data
|
|
3036
|
+
|
|
3037
|
+
try:
|
|
3038
|
+
width = int(width)
|
|
3039
|
+
height = int(height)
|
|
3040
|
+
except (ValueError, TypeError):
|
|
3041
|
+
return EvaluationError(f"canvas {node.name.value}: width/height must be numeric")
|
|
3042
|
+
|
|
3043
|
+
canvas_id = None
|
|
3044
|
+
if _RENDERER_AVAILABLE and renderer_create_named_canvas:
|
|
3045
|
+
try:
|
|
3046
|
+
canvas_id = renderer_create_named_canvas(node.name.value, width, height)
|
|
3047
|
+
except Exception as exc:
|
|
3048
|
+
return EvaluationError(f"canvas {node.name.value}: {exc}")
|
|
3049
|
+
else:
|
|
3050
|
+
registry = getattr(self, 'render_registry', None)
|
|
3051
|
+
if isinstance(registry, dict):
|
|
3052
|
+
canvases = registry.setdefault('canvases', {})
|
|
3053
|
+
alias_map = registry.setdefault('canvas_aliases', {})
|
|
3054
|
+
canvas_id = f"canvas_{len(canvases) + 1}"
|
|
3055
|
+
canvases[canvas_id] = {
|
|
3056
|
+
'width': width,
|
|
3057
|
+
'height': height,
|
|
3058
|
+
'draw_ops': [],
|
|
3059
|
+
}
|
|
3060
|
+
alias_map[node.name.value] = canvas_id
|
|
3061
|
+
else:
|
|
3062
|
+
canvas_id = f"{node.name.value}_canvas"
|
|
3063
|
+
|
|
3064
|
+
canvas_obj = _python_to_zexus(canvas_id)
|
|
3065
|
+
env.set(node.name.value, canvas_obj)
|
|
3066
|
+
|
|
3067
|
+
if node.body:
|
|
3068
|
+
local_env = Environment(outer=env)
|
|
3069
|
+
local_env.set(node.name.value, canvas_obj)
|
|
3070
|
+
local_env.set('canvas', canvas_obj)
|
|
3071
|
+
result = self.eval_block_statement(node.body, local_env, stack_trace)
|
|
3072
|
+
if isinstance(result, ReturnValue):
|
|
3073
|
+
return result
|
|
3074
|
+
if is_error(result):
|
|
3075
|
+
return result
|
|
3076
|
+
|
|
3077
|
+
return NULL
|
|
3078
|
+
|
|
3079
|
+
def eval_graphics_statement(self, node, env, stack_trace):
|
|
3080
|
+
payload = {}
|
|
3081
|
+
if node.body:
|
|
3082
|
+
bindings = self._evaluate_renderer_block(node.body, env, stack_trace)
|
|
3083
|
+
if is_error(bindings):
|
|
3084
|
+
return bindings
|
|
3085
|
+
payload = {key: _zexus_to_python(val) for key, val in bindings.items()}
|
|
3086
|
+
|
|
3087
|
+
if _RENDERER_AVAILABLE and renderer_register_graphics:
|
|
3088
|
+
try:
|
|
3089
|
+
renderer_register_graphics(node.name.value, payload)
|
|
3090
|
+
except Exception as exc:
|
|
3091
|
+
return EvaluationError(f"graphics {node.name.value}: {exc}")
|
|
3092
|
+
else:
|
|
3093
|
+
registry = getattr(self, 'render_registry', None)
|
|
3094
|
+
if isinstance(registry, dict):
|
|
3095
|
+
registry.setdefault('graphics', {})[node.name.value] = payload
|
|
3096
|
+
|
|
3097
|
+
env.set(node.name.value, _python_to_zexus(payload))
|
|
3098
|
+
return NULL
|
|
3099
|
+
|
|
3100
|
+
def eval_animation_statement(self, node, env, stack_trace):
|
|
3101
|
+
animation_data = {}
|
|
3102
|
+
|
|
3103
|
+
props = self._evaluate_renderer_properties(node.properties, env, stack_trace)
|
|
3104
|
+
if is_error(props):
|
|
3105
|
+
return props
|
|
3106
|
+
if props is not None:
|
|
3107
|
+
animation_data['properties'] = props
|
|
3108
|
+
|
|
3109
|
+
if node.body:
|
|
3110
|
+
bindings = self._evaluate_renderer_block(node.body, env, stack_trace)
|
|
3111
|
+
if is_error(bindings):
|
|
3112
|
+
return bindings
|
|
3113
|
+
animation_data.update({key: _zexus_to_python(val) for key, val in bindings.items()})
|
|
3114
|
+
|
|
3115
|
+
if _RENDERER_AVAILABLE and renderer_register_animation:
|
|
3116
|
+
try:
|
|
3117
|
+
renderer_register_animation(node.name.value, animation_data)
|
|
3118
|
+
except Exception as exc:
|
|
3119
|
+
return EvaluationError(f"animation {node.name.value}: {exc}")
|
|
3120
|
+
else:
|
|
3121
|
+
registry = getattr(self, 'render_registry', None)
|
|
3122
|
+
if isinstance(registry, dict):
|
|
3123
|
+
registry.setdefault('animations', {})[node.name.value] = animation_data
|
|
3124
|
+
|
|
3125
|
+
env.set(node.name.value, _python_to_zexus(animation_data))
|
|
3126
|
+
return NULL
|
|
3127
|
+
|
|
3128
|
+
def eval_clock_statement(self, node, env, stack_trace):
|
|
3129
|
+
config = self._evaluate_renderer_properties(node.properties, env, stack_trace)
|
|
3130
|
+
if is_error(config):
|
|
3131
|
+
return config
|
|
3132
|
+
|
|
3133
|
+
if isinstance(config, dict):
|
|
3134
|
+
clock_data = dict(config)
|
|
3135
|
+
elif config is None:
|
|
3136
|
+
clock_data = {}
|
|
3137
|
+
else:
|
|
3138
|
+
clock_data = {'value': config}
|
|
3139
|
+
|
|
3140
|
+
if _RENDERER_AVAILABLE and renderer_register_clock:
|
|
3141
|
+
try:
|
|
3142
|
+
renderer_register_clock(node.name.value, clock_data)
|
|
3143
|
+
except Exception as exc:
|
|
3144
|
+
return EvaluationError(f"clock {node.name.value}: {exc}")
|
|
3145
|
+
else:
|
|
3146
|
+
registry = getattr(self, 'render_registry', None)
|
|
3147
|
+
if isinstance(registry, dict):
|
|
3148
|
+
registry.setdefault('clocks', {})[node.name.value] = clock_data
|
|
3149
|
+
|
|
3150
|
+
env.set(node.name.value, _python_to_zexus(clock_data))
|
|
3151
|
+
return NULL
|
|
2528
3152
|
|
|
2529
3153
|
def eval_component_statement(self, node, env, stack_trace):
|
|
2530
3154
|
props = None
|
|
@@ -2611,7 +3235,15 @@ class StatementEvaluatorMixin:
|
|
|
2611
3235
|
return self.eval_node(node.body, env, stack_trace)
|
|
2612
3236
|
|
|
2613
3237
|
def eval_action_statement(self, node, env, stack_trace):
|
|
2614
|
-
|
|
3238
|
+
capture_env = env.clone_for_closure() if hasattr(env, "clone_for_closure") else env
|
|
3239
|
+
action = Action(node.parameters, node.body, capture_env)
|
|
3240
|
+
|
|
3241
|
+
# Ensure recursive references resolve within closures
|
|
3242
|
+
try:
|
|
3243
|
+
if capture_env is not env and hasattr(capture_env, "set"):
|
|
3244
|
+
capture_env.set(node.name.value, action)
|
|
3245
|
+
except Exception:
|
|
3246
|
+
pass
|
|
2615
3247
|
|
|
2616
3248
|
# Check for direct is_async attribute (from UltimateParser)
|
|
2617
3249
|
if hasattr(node, 'is_async') and node.is_async:
|
|
@@ -2644,20 +3276,26 @@ class StatementEvaluatorMixin:
|
|
|
2644
3276
|
|
|
2645
3277
|
def eval_function_statement(self, node, env, stack_trace):
|
|
2646
3278
|
"""Evaluate function statement - identical to action statement in Zexus"""
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
3279
|
+
debug_log("eval_function_statement", f"Start {node.name.value}")
|
|
3280
|
+
capture_env = env.clone_for_closure() if hasattr(env, "clone_for_closure") else env
|
|
3281
|
+
action = Action(node.parameters, node.body, capture_env)
|
|
3282
|
+
try:
|
|
3283
|
+
if capture_env is not env and hasattr(capture_env, "set"):
|
|
3284
|
+
capture_env.set(node.name.value, action)
|
|
3285
|
+
except Exception:
|
|
3286
|
+
pass
|
|
3287
|
+
debug_log("eval_function_statement", "Created Action object")
|
|
2650
3288
|
|
|
2651
3289
|
# Apply modifiers if present
|
|
2652
3290
|
modifiers = getattr(node, 'modifiers', [])
|
|
2653
|
-
|
|
3291
|
+
debug_log("eval_function_statement", f"Modifiers: {modifiers}")
|
|
2654
3292
|
if modifiers:
|
|
2655
3293
|
# Set modifier flags on the action object
|
|
2656
3294
|
if 'inline' in modifiers:
|
|
2657
3295
|
action.is_inlined = True
|
|
2658
3296
|
if 'async' in modifiers:
|
|
2659
3297
|
action.is_async = True
|
|
2660
|
-
|
|
3298
|
+
debug_log("eval_function_statement", "Set is_async=True")
|
|
2661
3299
|
if 'secure' in modifiers:
|
|
2662
3300
|
action.is_secure = True
|
|
2663
3301
|
if 'pure' in modifiers:
|
|
@@ -2672,9 +3310,9 @@ class StatementEvaluatorMixin:
|
|
|
2672
3310
|
except Exception:
|
|
2673
3311
|
pass
|
|
2674
3312
|
|
|
2675
|
-
|
|
3313
|
+
debug_log("eval_function_statement", f"Binding {node.name.value}")
|
|
2676
3314
|
env.set(node.name.value, action)
|
|
2677
|
-
|
|
3315
|
+
debug_log("eval_function_statement", "Binding complete")
|
|
2678
3316
|
return NULL
|
|
2679
3317
|
|
|
2680
3318
|
# === PERFORMANCE OPTIMIZATION STATEMENTS ===
|
|
@@ -3758,10 +4396,12 @@ class StatementEvaluatorMixin:
|
|
|
3758
4396
|
if not is_truthy(condition):
|
|
3759
4397
|
# Execute tolerance block if provided (for conditional allowances)
|
|
3760
4398
|
if node.tolerance_block:
|
|
3761
|
-
|
|
4399
|
+
if zexus_config.should_log('debug'):
|
|
4400
|
+
print(f"⚡ TOLERANCE BLOCK: type={type(node.tolerance_block).__name__}")
|
|
3762
4401
|
debug_log("eval_require_statement", "Condition failed - executing tolerance logic")
|
|
3763
4402
|
tolerance_result = self.eval_node(node.tolerance_block, env, stack_trace)
|
|
3764
|
-
|
|
4403
|
+
if zexus_config.should_log('debug'):
|
|
4404
|
+
print(f"⚡ TOLERANCE RESULT: type={type(tolerance_result).__name__}")
|
|
3765
4405
|
|
|
3766
4406
|
# Check if tolerance logic allows proceeding
|
|
3767
4407
|
if is_error(tolerance_result):
|
|
@@ -3770,18 +4410,22 @@ class StatementEvaluatorMixin:
|
|
|
3770
4410
|
# Unwrap ReturnValue if present
|
|
3771
4411
|
from ..object import ReturnValue
|
|
3772
4412
|
if isinstance(tolerance_result, ReturnValue):
|
|
3773
|
-
|
|
4413
|
+
if zexus_config.should_log('debug'):
|
|
4414
|
+
print(f"⚡ UNWRAPPING ReturnValue")
|
|
3774
4415
|
tolerance_result = tolerance_result.value
|
|
3775
|
-
|
|
4416
|
+
if zexus_config.should_log('debug'):
|
|
4417
|
+
print(f"⚡ UNWRAPPED VALUE: {tolerance_result}")
|
|
3776
4418
|
|
|
3777
4419
|
# If tolerance block returns true/truthy, allow it
|
|
3778
4420
|
if is_truthy(tolerance_result):
|
|
3779
|
-
|
|
4421
|
+
if zexus_config.should_log('debug'):
|
|
4422
|
+
print(f"⚡ TOLERANCE APPROVED")
|
|
3780
4423
|
debug_log("eval_require_statement", "Tolerance logic approved - allowing requirement")
|
|
3781
4424
|
return NULL
|
|
3782
4425
|
|
|
3783
4426
|
# If tolerance block returns false, requirement still fails
|
|
3784
|
-
|
|
4427
|
+
if zexus_config.should_log('debug'):
|
|
4428
|
+
print(f"⚡ TOLERANCE REJECTED")
|
|
3785
4429
|
debug_log("eval_require_statement", "Tolerance logic rejected - requirement fails")
|
|
3786
4430
|
# Fall through to error below
|
|
3787
4431
|
|
|
@@ -3902,25 +4546,32 @@ class StatementEvaluatorMixin:
|
|
|
3902
4546
|
debug_log("_eval_require_resource", f"{req_type} requirement satisfied")
|
|
3903
4547
|
return NULL
|
|
3904
4548
|
|
|
3905
|
-
def
|
|
3906
|
-
"""Evaluate revert statement - rollback transaction.
|
|
3907
|
-
|
|
3908
|
-
revert();
|
|
3909
|
-
revert("Unauthorized");
|
|
3910
|
-
"""
|
|
4549
|
+
def _perform_revert(self, reason_expr, env, stack_trace):
|
|
3911
4550
|
debug_log("eval_revert_statement", "Reverting transaction")
|
|
3912
|
-
|
|
3913
|
-
# Evaluate revert reason if provided
|
|
4551
|
+
|
|
3914
4552
|
reason = "Transaction reverted"
|
|
3915
|
-
if
|
|
3916
|
-
reason_val = self.eval_node(
|
|
4553
|
+
if reason_expr:
|
|
4554
|
+
reason_val = self.eval_node(reason_expr, env, stack_trace)
|
|
3917
4555
|
if isinstance(reason_val, String):
|
|
3918
4556
|
reason = reason_val.value
|
|
3919
4557
|
elif not is_error(reason_val):
|
|
3920
4558
|
reason = str(reason_val.inspect() if hasattr(reason_val, 'inspect') else reason_val)
|
|
3921
|
-
|
|
4559
|
+
|
|
3922
4560
|
debug_log("eval_revert_statement", f"REVERT: {reason}")
|
|
3923
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)
|
|
3924
4575
|
|
|
3925
4576
|
def eval_limit_statement(self, node, env, stack_trace):
|
|
3926
4577
|
"""Evaluate limit statement - set gas limit.
|
|
@@ -4247,7 +4898,8 @@ class StatementEvaluatorMixin:
|
|
|
4247
4898
|
|
|
4248
4899
|
# Print event for debugging (optional)
|
|
4249
4900
|
args_str = ", ".join(str(arg.inspect() if hasattr(arg, 'inspect') else arg) for arg in args)
|
|
4250
|
-
|
|
4901
|
+
if zexus_config.should_log('info'):
|
|
4902
|
+
print(f"📢 Event: {event_name}({args_str})")
|
|
4251
4903
|
|
|
4252
4904
|
return NULL
|
|
4253
4905
|
|