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