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
|
@@ -13,7 +13,7 @@ from .utils import is_error, debug_log, NULL, TRUE, FALSE, _resolve_awaitable, _
|
|
|
13
13
|
|
|
14
14
|
# Try to import backend, handle failure gracefully (as per your original code)
|
|
15
15
|
try:
|
|
16
|
-
from renderer import backend as _BACKEND
|
|
16
|
+
from ..renderer import backend as _BACKEND
|
|
17
17
|
_BACKEND_AVAILABLE = True
|
|
18
18
|
except Exception:
|
|
19
19
|
_BACKEND_AVAILABLE = False
|
|
@@ -25,6 +25,9 @@ class FunctionEvaluatorMixin:
|
|
|
25
25
|
def __init__(self):
|
|
26
26
|
# Initialize registries
|
|
27
27
|
self.builtins = {}
|
|
28
|
+
self._allow_coroutine_result = False
|
|
29
|
+
self._pending_revert_signature = None
|
|
30
|
+
self._tolerant_skip_counts = {}
|
|
28
31
|
|
|
29
32
|
# Renderer Registry (moved from global scope to instance scope)
|
|
30
33
|
self.render_registry = {
|
|
@@ -32,6 +35,11 @@ class FunctionEvaluatorMixin:
|
|
|
32
35
|
'components': {},
|
|
33
36
|
'themes': {},
|
|
34
37
|
'canvases': {},
|
|
38
|
+
'canvas_aliases': {},
|
|
39
|
+
'colours': {},
|
|
40
|
+
'graphics': {},
|
|
41
|
+
'animations': {},
|
|
42
|
+
'clocks': {},
|
|
35
43
|
'current_theme': None
|
|
36
44
|
}
|
|
37
45
|
|
|
@@ -40,9 +48,60 @@ class FunctionEvaluatorMixin:
|
|
|
40
48
|
self._register_main_entry_point_builtins()
|
|
41
49
|
self._register_renderer_builtins()
|
|
42
50
|
|
|
51
|
+
def _execute_coroutine_to_completion(self, coroutine):
|
|
52
|
+
from ..object import EvaluationError
|
|
53
|
+
|
|
54
|
+
max_steps = 10000
|
|
55
|
+
pending_value = None
|
|
56
|
+
|
|
57
|
+
for _ in range(max_steps):
|
|
58
|
+
done, value = coroutine.resume(pending_value)
|
|
59
|
+
if done:
|
|
60
|
+
if isinstance(value, EvaluationError):
|
|
61
|
+
return value
|
|
62
|
+
return value if value is not None else coroutine.result
|
|
63
|
+
pending_value = None
|
|
64
|
+
|
|
65
|
+
return EvaluationError("Coroutine did not complete within step limit")
|
|
66
|
+
|
|
67
|
+
def _compute_revert_signature(self, reason_node):
|
|
68
|
+
if reason_node is None:
|
|
69
|
+
return ("none",)
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
from .. import zexus_ast
|
|
73
|
+
if isinstance(reason_node, zexus_ast.StringLiteral):
|
|
74
|
+
return ("string", getattr(reason_node, "value", None))
|
|
75
|
+
if isinstance(reason_node, zexus_ast.Identifier):
|
|
76
|
+
return ("identifier", getattr(reason_node, "value", None))
|
|
77
|
+
except Exception:
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
return ("expr", repr(reason_node))
|
|
81
|
+
|
|
43
82
|
def eval_call_expression(self, node, env, stack_trace):
|
|
44
83
|
debug_log("🚀 CallExpression node", f"Calling {node.function}")
|
|
45
84
|
|
|
85
|
+
if (
|
|
86
|
+
isinstance(node.function, zexus_ast.Identifier)
|
|
87
|
+
and getattr(node.function, "value", None) == "revert"
|
|
88
|
+
):
|
|
89
|
+
if len(node.arguments) > 1:
|
|
90
|
+
return EvaluationError(
|
|
91
|
+
"revert() accepts at most one argument",
|
|
92
|
+
stack_trace=stack_trace,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
debug_log(
|
|
96
|
+
"eval_call_expression",
|
|
97
|
+
"Treating call expression as revert statement",
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
reason_expr = node.arguments[0] if node.arguments else None
|
|
101
|
+
result = self._perform_revert(reason_expr, env, stack_trace)
|
|
102
|
+
self._pending_revert_signature = self._compute_revert_signature(reason_expr)
|
|
103
|
+
return result
|
|
104
|
+
|
|
46
105
|
fn = self.eval_node(node.function, env, stack_trace)
|
|
47
106
|
if is_error(fn):
|
|
48
107
|
return fn
|
|
@@ -66,7 +125,6 @@ class FunctionEvaluatorMixin:
|
|
|
66
125
|
|
|
67
126
|
# Check if arguments contain keyword arguments (AssignmentExpression nodes)
|
|
68
127
|
# This handles syntax like: Person(name="Bob", age=25)
|
|
69
|
-
from .. import zexus_ast
|
|
70
128
|
has_keyword_args = any(isinstance(arg, zexus_ast.AssignmentExpression) for arg in node.arguments)
|
|
71
129
|
|
|
72
130
|
if has_keyword_args:
|
|
@@ -106,7 +164,20 @@ class FunctionEvaluatorMixin:
|
|
|
106
164
|
if isinstance(fn, SmartContract):
|
|
107
165
|
return fn.instantiate(args)
|
|
108
166
|
|
|
109
|
-
|
|
167
|
+
should_allow_coroutine = (
|
|
168
|
+
isinstance(fn, (Action, LambdaFunction))
|
|
169
|
+
and getattr(fn, "is_async", False)
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
previous_allow = getattr(self, "_allow_coroutine_result", False)
|
|
173
|
+
if should_allow_coroutine:
|
|
174
|
+
self._allow_coroutine_result = True
|
|
175
|
+
|
|
176
|
+
try:
|
|
177
|
+
return self.apply_function(fn, args, env)
|
|
178
|
+
finally:
|
|
179
|
+
if should_allow_coroutine:
|
|
180
|
+
self._allow_coroutine_result = previous_allow
|
|
110
181
|
|
|
111
182
|
def _create_specialized_generic_constructor(self, template, type_args, env, stack_trace):
|
|
112
183
|
"""Create a specialized constructor for a generic type with concrete type arguments
|
|
@@ -114,7 +185,6 @@ class FunctionEvaluatorMixin:
|
|
|
114
185
|
Example: Box<number> creates a specialized Box constructor with T = number
|
|
115
186
|
"""
|
|
116
187
|
from ..object import EvaluationError, String, Map
|
|
117
|
-
from .. import zexus_ast
|
|
118
188
|
|
|
119
189
|
debug_log("_create_specialized_generic_constructor", f"Specializing with types: {type_args}")
|
|
120
190
|
|
|
@@ -268,10 +338,12 @@ class FunctionEvaluatorMixin:
|
|
|
268
338
|
# Re-raise exception to be caught by coroutine
|
|
269
339
|
raise e
|
|
270
340
|
|
|
271
|
-
# Create and
|
|
341
|
+
# Create coroutine and decide how to surface result
|
|
272
342
|
gen = async_generator()
|
|
273
343
|
coroutine = Coroutine(gen, fn)
|
|
274
|
-
|
|
344
|
+
if getattr(self, "_allow_coroutine_result", False):
|
|
345
|
+
return coroutine
|
|
346
|
+
return self._execute_coroutine_to_completion(coroutine)
|
|
275
347
|
|
|
276
348
|
# Synchronous function execution
|
|
277
349
|
new_env = Environment(outer=fn.env)
|
|
@@ -295,6 +367,7 @@ class FunctionEvaluatorMixin:
|
|
|
295
367
|
except Exception:
|
|
296
368
|
pass
|
|
297
369
|
|
|
370
|
+
result = None
|
|
298
371
|
try:
|
|
299
372
|
res = self.eval_node(fn.body, new_env)
|
|
300
373
|
res = _resolve_awaitable(res)
|
|
@@ -310,6 +383,10 @@ class FunctionEvaluatorMixin:
|
|
|
310
383
|
# Store result for after-call hook
|
|
311
384
|
result = EvaluationError(str(e))
|
|
312
385
|
raise
|
|
386
|
+
except BaseException as e:
|
|
387
|
+
# Ensure result is set even for interrupts/SystemExit
|
|
388
|
+
result = EvaluationError(str(e))
|
|
389
|
+
raise
|
|
313
390
|
finally:
|
|
314
391
|
# CRITICAL: Execute deferred cleanup when function exits
|
|
315
392
|
# This happens in finally block to ensure cleanup runs even on errors
|
|
@@ -429,7 +506,9 @@ class FunctionEvaluatorMixin:
|
|
|
429
506
|
def eval_method_call_expression(self, node, env, stack_trace):
|
|
430
507
|
debug_log(" MethodCallExpression node", f"{node.object}.{node.method}")
|
|
431
508
|
|
|
509
|
+
debug_log("DEBUG evaluating method call object", node.object)
|
|
432
510
|
obj = self.eval_node(node.object, env, stack_trace)
|
|
511
|
+
debug_log("DEBUG object evaluated to", obj)
|
|
433
512
|
if is_error(obj):
|
|
434
513
|
return obj
|
|
435
514
|
|
|
@@ -588,6 +667,32 @@ class FunctionEvaluatorMixin:
|
|
|
588
667
|
if is_error(args):
|
|
589
668
|
return args
|
|
590
669
|
result = obj.call_method(method_name, args)
|
|
670
|
+
|
|
671
|
+
# CRITICAL: Sync state variables back to caller's environment after internal call
|
|
672
|
+
# This ensures that when action A calls this.actionB(), any state changes made
|
|
673
|
+
# by actionB are visible to actionA's local environment
|
|
674
|
+
# OPTIMIZATION: Only sync for 'this' calls (internal calls within same contract)
|
|
675
|
+
from ..security import SmartContract
|
|
676
|
+
if isinstance(obj, SmartContract):
|
|
677
|
+
# Check if this is a 'this.method()' call by checking the caller's env
|
|
678
|
+
this_ref = env.get('this')
|
|
679
|
+
if this_ref is obj and hasattr(obj, 'storage_vars'):
|
|
680
|
+
# This is an internal call - sync state variables
|
|
681
|
+
for var_node in obj.storage_vars:
|
|
682
|
+
var_name = None
|
|
683
|
+
if hasattr(var_node, 'name'):
|
|
684
|
+
var_name = var_node.name.value if hasattr(var_node.name, 'value') else var_node.name
|
|
685
|
+
elif isinstance(var_node, dict):
|
|
686
|
+
var_name = var_node.get("name")
|
|
687
|
+
elif isinstance(var_node, str):
|
|
688
|
+
var_name = var_node
|
|
689
|
+
|
|
690
|
+
if var_name:
|
|
691
|
+
# Read the updated value from storage and refresh the caller's env
|
|
692
|
+
updated_value = obj.storage.get(var_name)
|
|
693
|
+
if updated_value is not None:
|
|
694
|
+
env.set(var_name, updated_value)
|
|
695
|
+
|
|
591
696
|
# Unwrap ReturnValue if needed
|
|
592
697
|
from ..object import ReturnValue
|
|
593
698
|
if isinstance(result, ReturnValue):
|
|
@@ -618,6 +723,7 @@ class FunctionEvaluatorMixin:
|
|
|
618
723
|
return self.apply_function(method_value, args, env)
|
|
619
724
|
|
|
620
725
|
obj_type = obj.type() if hasattr(obj, 'type') and callable(obj.type) else type(obj).__name__
|
|
726
|
+
debug_log("DEBUG method dispatch failure", f"method={method_name}, obj={obj}, obj_type={obj_type}")
|
|
621
727
|
return EvaluationError(f"Method '{method_name}' not supported for {obj_type}")
|
|
622
728
|
|
|
623
729
|
# --- Array Helpers (Internal) ---
|
|
@@ -826,16 +932,56 @@ class FunctionEvaluatorMixin:
|
|
|
826
932
|
except Exception as e:
|
|
827
933
|
return EvaluationError(f"constant_time_compare() error: {str(e)}")
|
|
828
934
|
|
|
829
|
-
# File I/O
|
|
935
|
+
# File I/O — VFS-aware helpers
|
|
936
|
+
# The VFS manager (with its file cache) is obtained lazily so the
|
|
937
|
+
# evaluator can function even when the integration layer is not loaded.
|
|
938
|
+
_vfs_mgr = None
|
|
939
|
+
def _get_vfs_manager():
|
|
940
|
+
nonlocal _vfs_mgr
|
|
941
|
+
if _vfs_mgr is None:
|
|
942
|
+
try:
|
|
943
|
+
from .integration import get_integration
|
|
944
|
+
_vfs_mgr = get_integration().vfs_manager
|
|
945
|
+
except Exception:
|
|
946
|
+
pass
|
|
947
|
+
return _vfs_mgr
|
|
948
|
+
|
|
949
|
+
def _resolve_read_path(path_str):
|
|
950
|
+
"""Resolve a path for reading – uses VFS cache when available."""
|
|
951
|
+
import os
|
|
952
|
+
if not os.path.isabs(path_str):
|
|
953
|
+
path_str = os.path.join(os.getcwd(), path_str)
|
|
954
|
+
return os.path.normpath(path_str)
|
|
955
|
+
|
|
956
|
+
def _cached_read(real_path):
|
|
957
|
+
"""Read via VFS cache (skips disk if file unchanged)."""
|
|
958
|
+
mgr = _get_vfs_manager()
|
|
959
|
+
if mgr is not None:
|
|
960
|
+
return mgr.cached_read(real_path)
|
|
961
|
+
with open(real_path, 'r') as f:
|
|
962
|
+
return f.read()
|
|
963
|
+
|
|
964
|
+
def _invalidate_file_cache(real_path):
|
|
965
|
+
"""Invalidate VFS cache after a write."""
|
|
966
|
+
mgr = _get_vfs_manager()
|
|
967
|
+
if mgr is not None:
|
|
968
|
+
mgr.invalidate_cache(real_path)
|
|
969
|
+
|
|
830
970
|
def _read_text(*a):
|
|
971
|
+
cap_err = _check_io_read_capability()
|
|
972
|
+
if cap_err: return cap_err
|
|
831
973
|
if len(a) != 1 or not isinstance(a[0], String):
|
|
832
974
|
return EvaluationError("file_read_text() takes exactly 1 string argument")
|
|
833
975
|
return File.read_text(a[0])
|
|
834
976
|
|
|
835
977
|
def _write_text(*a):
|
|
978
|
+
cap_err = _check_io_write_capability()
|
|
979
|
+
if cap_err: return cap_err
|
|
836
980
|
if len(a) != 2 or not all(isinstance(x, String) for x in a):
|
|
837
981
|
return EvaluationError("file_write_text() takes exactly 2 string arguments")
|
|
838
|
-
|
|
982
|
+
result = File.write_text(a[0], a[1])
|
|
983
|
+
_invalidate_file_cache(_resolve_read_path(a[0].value))
|
|
984
|
+
return result
|
|
839
985
|
|
|
840
986
|
def _exists(*a):
|
|
841
987
|
if len(a) != 1 or not isinstance(a[0], String):
|
|
@@ -843,26 +989,38 @@ class FunctionEvaluatorMixin:
|
|
|
843
989
|
return File.exists(a[0])
|
|
844
990
|
|
|
845
991
|
def _read_json(*a):
|
|
992
|
+
cap_err = _check_io_read_capability()
|
|
993
|
+
if cap_err: return cap_err
|
|
846
994
|
if len(a) != 1 or not isinstance(a[0], String):
|
|
847
995
|
return EvaluationError("file_read_json() takes exactly 1 string argument")
|
|
848
996
|
return File.read_json(a[0])
|
|
849
997
|
|
|
850
998
|
def _write_json(*a):
|
|
999
|
+
cap_err = _check_io_write_capability()
|
|
1000
|
+
if cap_err: return cap_err
|
|
851
1001
|
if len(a) != 2 or not isinstance(a[0], String):
|
|
852
1002
|
return EvaluationError("file_write_json() takes path string and data")
|
|
853
|
-
|
|
1003
|
+
result = File.write_json(a[0], a[1])
|
|
1004
|
+
_invalidate_file_cache(_resolve_read_path(a[0].value))
|
|
1005
|
+
return result
|
|
854
1006
|
|
|
855
1007
|
def _file_append(*a):
|
|
1008
|
+
cap_err = _check_io_write_capability()
|
|
1009
|
+
if cap_err: return cap_err
|
|
856
1010
|
if len(a) != 2 or not all(isinstance(x, String) for x in a):
|
|
857
1011
|
return EvaluationError("file_append() takes exactly 2 string arguments")
|
|
858
|
-
|
|
1012
|
+
result = File.append_text(a[0], a[1])
|
|
1013
|
+
_invalidate_file_cache(_resolve_read_path(a[0].value))
|
|
1014
|
+
return result
|
|
859
1015
|
|
|
860
1016
|
def _list_dir(*a):
|
|
1017
|
+
cap_err = _check_io_read_capability()
|
|
1018
|
+
if cap_err: return cap_err
|
|
861
1019
|
if len(a) != 1 or not isinstance(a[0], String):
|
|
862
1020
|
return EvaluationError("file_list_dir() takes exactly 1 string argument")
|
|
863
1021
|
return File.list_directory(a[0])
|
|
864
1022
|
|
|
865
|
-
# Extended File System Operations
|
|
1023
|
+
# Extended File System Operations (with capability checks)
|
|
866
1024
|
def _fs_is_file(*a):
|
|
867
1025
|
if len(a) != 1 or not isinstance(a[0], String):
|
|
868
1026
|
return EvaluationError("fs_is_file() takes exactly 1 string argument")
|
|
@@ -876,6 +1034,8 @@ class FunctionEvaluatorMixin:
|
|
|
876
1034
|
return BooleanObj(os.path.isdir(a[0].value))
|
|
877
1035
|
|
|
878
1036
|
def _fs_mkdir(*a):
|
|
1037
|
+
cap_err = _check_io_write_capability()
|
|
1038
|
+
if cap_err: return cap_err
|
|
879
1039
|
if len(a) < 1 or not isinstance(a[0], String):
|
|
880
1040
|
return EvaluationError("fs_mkdir() takes path string and optional parents boolean")
|
|
881
1041
|
from pathlib import Path
|
|
@@ -889,16 +1049,21 @@ class FunctionEvaluatorMixin:
|
|
|
889
1049
|
return EvaluationError(f"fs_mkdir() error: {str(e)}")
|
|
890
1050
|
|
|
891
1051
|
def _fs_remove(*a):
|
|
1052
|
+
cap_err = _check_io_write_capability()
|
|
1053
|
+
if cap_err: return cap_err
|
|
892
1054
|
if len(a) != 1 or not isinstance(a[0], String):
|
|
893
1055
|
return EvaluationError("fs_remove() takes exactly 1 string argument")
|
|
894
1056
|
import os
|
|
895
1057
|
try:
|
|
896
1058
|
os.remove(a[0].value)
|
|
1059
|
+
_invalidate_file_cache(_resolve_read_path(a[0].value))
|
|
897
1060
|
return BooleanObj(True)
|
|
898
1061
|
except Exception as e:
|
|
899
1062
|
return EvaluationError(f"fs_remove() error: {str(e)}")
|
|
900
1063
|
|
|
901
1064
|
def _fs_rmdir(*a):
|
|
1065
|
+
cap_err = _check_io_write_capability()
|
|
1066
|
+
if cap_err: return cap_err
|
|
902
1067
|
if len(a) < 1 or not isinstance(a[0], String):
|
|
903
1068
|
return EvaluationError("fs_rmdir() takes path string and optional recursive boolean")
|
|
904
1069
|
import os
|
|
@@ -916,6 +1081,8 @@ class FunctionEvaluatorMixin:
|
|
|
916
1081
|
return EvaluationError(f"fs_rmdir() error: {str(e)}")
|
|
917
1082
|
|
|
918
1083
|
def _fs_rename(*a):
|
|
1084
|
+
cap_err = _check_io_write_capability()
|
|
1085
|
+
if cap_err: return cap_err
|
|
919
1086
|
if len(a) != 2 or not all(isinstance(x, String) for x in a):
|
|
920
1087
|
return EvaluationError("fs_rename() takes exactly 2 string arguments: old_path, new_path")
|
|
921
1088
|
import os
|
|
@@ -926,6 +1093,8 @@ class FunctionEvaluatorMixin:
|
|
|
926
1093
|
return EvaluationError(f"fs_rename() error: {str(e)}")
|
|
927
1094
|
|
|
928
1095
|
def _fs_copy(*a):
|
|
1096
|
+
cap_err = _check_io_write_capability()
|
|
1097
|
+
if cap_err: return cap_err
|
|
929
1098
|
if len(a) != 2 or not all(isinstance(x, String) for x in a):
|
|
930
1099
|
return EvaluationError("fs_copy() takes exactly 2 string arguments: src, dst")
|
|
931
1100
|
import shutil
|
|
@@ -946,6 +1115,8 @@ class FunctionEvaluatorMixin:
|
|
|
946
1115
|
# Socket/TCP Primitives
|
|
947
1116
|
def _socket_create_server(*a):
|
|
948
1117
|
"""Create TCP server: socket_create_server(host, port, handler, backlog?)"""
|
|
1118
|
+
cap_err = _check_network_capability()
|
|
1119
|
+
if cap_err: return cap_err
|
|
949
1120
|
if len(a) < 3:
|
|
950
1121
|
return EvaluationError("socket_create_server() requires at least 3 arguments: host, port, handler")
|
|
951
1122
|
|
|
@@ -1035,6 +1206,8 @@ class FunctionEvaluatorMixin:
|
|
|
1035
1206
|
|
|
1036
1207
|
def _socket_create_connection(*a):
|
|
1037
1208
|
"""Create TCP client: socket_create_connection(host, port, timeout?)"""
|
|
1209
|
+
cap_err = _check_network_capability()
|
|
1210
|
+
if cap_err: return cap_err
|
|
1038
1211
|
if len(a) < 2:
|
|
1039
1212
|
return EvaluationError("socket_create_connection() requires at least 2 arguments: host, port")
|
|
1040
1213
|
|
|
@@ -1685,9 +1858,42 @@ class FunctionEvaluatorMixin:
|
|
|
1685
1858
|
except Exception as e:
|
|
1686
1859
|
return EvaluationError(f"mongo_connect() error: {str(e)}")
|
|
1687
1860
|
|
|
1861
|
+
# Capability-checked helpers
|
|
1862
|
+
def _check_network_capability():
|
|
1863
|
+
"""Check if network operations are allowed in current context."""
|
|
1864
|
+
try:
|
|
1865
|
+
from .integration import CapabilityChecker
|
|
1866
|
+
if not CapabilityChecker.check_network():
|
|
1867
|
+
return EvaluationError("Network access denied: missing 'network.tcp' capability")
|
|
1868
|
+
except Exception:
|
|
1869
|
+
pass # If capability system isn't loaded, allow by default
|
|
1870
|
+
return None
|
|
1871
|
+
|
|
1872
|
+
def _check_io_read_capability():
|
|
1873
|
+
"""Check if file read operations are allowed."""
|
|
1874
|
+
try:
|
|
1875
|
+
from .integration import CapabilityChecker
|
|
1876
|
+
if not CapabilityChecker.check_io_read():
|
|
1877
|
+
return EvaluationError("File read access denied: missing 'io.read' capability")
|
|
1878
|
+
except Exception:
|
|
1879
|
+
pass
|
|
1880
|
+
return None
|
|
1881
|
+
|
|
1882
|
+
def _check_io_write_capability():
|
|
1883
|
+
"""Check if file write operations are allowed."""
|
|
1884
|
+
try:
|
|
1885
|
+
from .integration import CapabilityChecker
|
|
1886
|
+
if not CapabilityChecker.check_io_write():
|
|
1887
|
+
return EvaluationError("File write access denied: missing 'io.write' capability")
|
|
1888
|
+
except Exception:
|
|
1889
|
+
pass
|
|
1890
|
+
return None
|
|
1891
|
+
|
|
1688
1892
|
# HTTP Client
|
|
1689
1893
|
def _http_get(*a):
|
|
1690
1894
|
"""HTTP GET request: http_get(url, headers?, timeout?)"""
|
|
1895
|
+
cap_err = _check_network_capability()
|
|
1896
|
+
if cap_err: return cap_err
|
|
1691
1897
|
if len(a) < 1:
|
|
1692
1898
|
return EvaluationError("http_get() requires at least 1 argument: url")
|
|
1693
1899
|
|
|
@@ -1713,6 +1919,8 @@ class FunctionEvaluatorMixin:
|
|
|
1713
1919
|
|
|
1714
1920
|
def _http_post(*a):
|
|
1715
1921
|
"""HTTP POST request: http_post(url, data, headers?, timeout?)"""
|
|
1922
|
+
cap_err = _check_network_capability()
|
|
1923
|
+
if cap_err: return cap_err
|
|
1716
1924
|
if len(a) < 2:
|
|
1717
1925
|
return EvaluationError("http_post() requires at least 2 arguments: url, data")
|
|
1718
1926
|
|
|
@@ -1741,6 +1949,8 @@ class FunctionEvaluatorMixin:
|
|
|
1741
1949
|
|
|
1742
1950
|
def _http_put(*a):
|
|
1743
1951
|
"""HTTP PUT request: http_put(url, data, headers?, timeout?)"""
|
|
1952
|
+
cap_err = _check_network_capability()
|
|
1953
|
+
if cap_err: return cap_err
|
|
1744
1954
|
if len(a) < 2:
|
|
1745
1955
|
return EvaluationError("http_put() requires at least 2 arguments: url, data")
|
|
1746
1956
|
|
|
@@ -1766,6 +1976,8 @@ class FunctionEvaluatorMixin:
|
|
|
1766
1976
|
|
|
1767
1977
|
def _http_delete(*a):
|
|
1768
1978
|
"""HTTP DELETE request: http_delete(url, headers?, timeout?)"""
|
|
1979
|
+
cap_err = _check_network_capability()
|
|
1980
|
+
if cap_err: return cap_err
|
|
1769
1981
|
if len(a) < 1:
|
|
1770
1982
|
return EvaluationError("http_delete() requires at least 1 argument: url")
|
|
1771
1983
|
|
|
@@ -1786,6 +1998,62 @@ class FunctionEvaluatorMixin:
|
|
|
1786
1998
|
return _python_to_zexus(result, mark_untrusted=True)
|
|
1787
1999
|
except Exception as e:
|
|
1788
2000
|
return EvaluationError(f"HTTP DELETE error: {str(e)}")
|
|
2001
|
+
|
|
2002
|
+
def _http_request(*a):
|
|
2003
|
+
"""Generic HTTP request: http_request(method, url, data?, headers?, timeout?)"""
|
|
2004
|
+
cap_err = _check_network_capability()
|
|
2005
|
+
if cap_err: return cap_err
|
|
2006
|
+
if len(a) < 2:
|
|
2007
|
+
return EvaluationError("http_request() requires at least 2 arguments: method, url")
|
|
2008
|
+
method_str = a[0].value if isinstance(a[0], String) else str(a[0])
|
|
2009
|
+
url = a[1].value if isinstance(a[1], String) else str(a[1])
|
|
2010
|
+
data = _zexus_to_python(a[2]) if len(a) >= 3 and a[2] != NULL else None
|
|
2011
|
+
headers = _zexus_to_python(a[3]) if len(a) >= 4 and isinstance(a[3], Map) else None
|
|
2012
|
+
timeout = a[4].value if len(a) >= 5 and isinstance(a[4], Integer) else 30
|
|
2013
|
+
try:
|
|
2014
|
+
from ..stdlib.http import HttpModule
|
|
2015
|
+
result = HttpModule.request(method_str, url, data, headers, timeout)
|
|
2016
|
+
return _python_to_zexus(result, mark_untrusted=True)
|
|
2017
|
+
except Exception as e:
|
|
2018
|
+
return EvaluationError(f"HTTP {method_str} error: {str(e)}")
|
|
2019
|
+
|
|
2020
|
+
def _http_parallel_get(*a):
|
|
2021
|
+
"""Parallel GET requests: http_parallel_get([url1, url2, ...], headers?, timeout?)"""
|
|
2022
|
+
cap_err = _check_network_capability()
|
|
2023
|
+
if cap_err: return cap_err
|
|
2024
|
+
if len(a) < 1 or not isinstance(a[0], List):
|
|
2025
|
+
return EvaluationError("http_parallel_get() requires a list of URLs as first argument")
|
|
2026
|
+
urls = [e.value if isinstance(e, String) else str(e) for e in a[0].elements]
|
|
2027
|
+
headers = _zexus_to_python(a[1]) if len(a) >= 2 and isinstance(a[1], Map) else None
|
|
2028
|
+
timeout = a[2].value if len(a) >= 3 and isinstance(a[2], Integer) else 30
|
|
2029
|
+
try:
|
|
2030
|
+
from ..stdlib.http import HttpModule
|
|
2031
|
+
results = HttpModule.parallel_get(urls, headers, timeout)
|
|
2032
|
+
return List([_python_to_zexus(r, mark_untrusted=True) for r in results])
|
|
2033
|
+
except Exception as e:
|
|
2034
|
+
return EvaluationError(f"http_parallel_get() error: {str(e)}")
|
|
2035
|
+
|
|
2036
|
+
def _http_async_get(*a):
|
|
2037
|
+
"""Non-blocking HTTP GET: http_async_get(url, headers?, timeout?) -> Future"""
|
|
2038
|
+
cap_err = _check_network_capability()
|
|
2039
|
+
if cap_err: return cap_err
|
|
2040
|
+
if len(a) < 1:
|
|
2041
|
+
return EvaluationError("http_async_get() requires at least 1 argument: url")
|
|
2042
|
+
url = a[0].value if isinstance(a[0], String) else str(a[0])
|
|
2043
|
+
headers = _zexus_to_python(a[1]) if len(a) >= 2 and isinstance(a[1], Map) else None
|
|
2044
|
+
timeout = a[2].value if len(a) >= 3 and isinstance(a[2], Integer) else 30
|
|
2045
|
+
try:
|
|
2046
|
+
from ..stdlib.http import HttpModule
|
|
2047
|
+
future = HttpModule.async_get(url, headers, timeout)
|
|
2048
|
+
# Wrap future so await_result() works from Zexus
|
|
2049
|
+
from ..object import Map as ZMap
|
|
2050
|
+
return ZMap({
|
|
2051
|
+
"type": String("HttpFuture"),
|
|
2052
|
+
"done": Builtin(lambda *_: BooleanObj(future.done()), "done"),
|
|
2053
|
+
"result": Builtin(lambda *_: _python_to_zexus(future.result(), mark_untrusted=True), "result"),
|
|
2054
|
+
})
|
|
2055
|
+
except Exception as e:
|
|
2056
|
+
return EvaluationError(f"http_async_get() error: {str(e)}")
|
|
1789
2057
|
|
|
1790
2058
|
# Debug
|
|
1791
2059
|
def _debug(*a):
|
|
@@ -1898,6 +2166,22 @@ class FunctionEvaluatorMixin:
|
|
|
1898
2166
|
if isinstance(arg, String):
|
|
1899
2167
|
return String(arg.value.lower())
|
|
1900
2168
|
return EvaluationError(f"lowercase() requires a string argument")
|
|
2169
|
+
|
|
2170
|
+
def _split(*a):
|
|
2171
|
+
"""Split string by delimiter"""
|
|
2172
|
+
if len(a) != 2:
|
|
2173
|
+
return EvaluationError(f"split() takes 2 args: string, delimiter")
|
|
2174
|
+
|
|
2175
|
+
str_obj = a[0]
|
|
2176
|
+
delim_obj = a[1]
|
|
2177
|
+
|
|
2178
|
+
if not isinstance(str_obj, String):
|
|
2179
|
+
return EvaluationError("split() first argument must be a string")
|
|
2180
|
+
if not isinstance(delim_obj, String):
|
|
2181
|
+
return EvaluationError("split() second argument must be a delimiter string")
|
|
2182
|
+
|
|
2183
|
+
parts = str_obj.value.split(delim_obj.value)
|
|
2184
|
+
return List([String(p) for p in parts])
|
|
1901
2185
|
|
|
1902
2186
|
def _random(*a):
|
|
1903
2187
|
"""Generate random number. random() -> 0-1, random(max) -> 0 to max-1"""
|
|
@@ -2099,6 +2383,106 @@ class FunctionEvaluatorMixin:
|
|
|
2099
2383
|
a[0].extend(a[1])
|
|
2100
2384
|
return a[0]
|
|
2101
2385
|
|
|
2386
|
+
def _sort(*a):
|
|
2387
|
+
"""Sort a list: sort(list) or sort(list, key_field) for maps
|
|
2388
|
+
|
|
2389
|
+
- sort([3, 1, 2]) -> [1, 2, 3]
|
|
2390
|
+
- sort([{fee: 5}, {fee: 3}], "fee") -> [{fee: 3}, {fee: 5}]
|
|
2391
|
+
- sort(list, "fee", true) -> descending order
|
|
2392
|
+
"""
|
|
2393
|
+
if len(a) < 1:
|
|
2394
|
+
return EvaluationError("sort() takes 1-3 arguments: sort(list, [key], [descending])")
|
|
2395
|
+
if not isinstance(a[0], List):
|
|
2396
|
+
return EvaluationError("sort() first argument must be a list")
|
|
2397
|
+
|
|
2398
|
+
lst = a[0]
|
|
2399
|
+
key_field = None
|
|
2400
|
+
descending = False
|
|
2401
|
+
|
|
2402
|
+
if len(a) >= 2:
|
|
2403
|
+
if isinstance(a[1], String):
|
|
2404
|
+
key_field = a[1].value
|
|
2405
|
+
elif isinstance(a[1], BooleanObj):
|
|
2406
|
+
descending = a[1].value
|
|
2407
|
+
else:
|
|
2408
|
+
return EvaluationError("sort() second argument must be a key string or boolean for descending")
|
|
2409
|
+
|
|
2410
|
+
if len(a) >= 3:
|
|
2411
|
+
if isinstance(a[2], BooleanObj):
|
|
2412
|
+
descending = a[2].value
|
|
2413
|
+
else:
|
|
2414
|
+
return EvaluationError("sort() third argument must be a boolean for descending order")
|
|
2415
|
+
|
|
2416
|
+
try:
|
|
2417
|
+
# Make a copy of elements for non-destructive sort
|
|
2418
|
+
elements = list(lst.elements)
|
|
2419
|
+
|
|
2420
|
+
def get_sort_key(elem):
|
|
2421
|
+
if key_field is not None:
|
|
2422
|
+
# Sort by field in map
|
|
2423
|
+
if isinstance(elem, Map):
|
|
2424
|
+
# Try string key first
|
|
2425
|
+
val = elem.pairs.get(String(key_field))
|
|
2426
|
+
if val is None:
|
|
2427
|
+
# Try with key_field as-is (for internal dict keys)
|
|
2428
|
+
val = elem.pairs.get(key_field)
|
|
2429
|
+
if val is None:
|
|
2430
|
+
return 0 # Default for missing key
|
|
2431
|
+
if isinstance(val, (Integer, Float)):
|
|
2432
|
+
return val.value
|
|
2433
|
+
if isinstance(val, String):
|
|
2434
|
+
return val.value
|
|
2435
|
+
return 0
|
|
2436
|
+
elif isinstance(elem, dict):
|
|
2437
|
+
val = elem.get(key_field, 0)
|
|
2438
|
+
if hasattr(val, 'value'):
|
|
2439
|
+
return val.value
|
|
2440
|
+
return val
|
|
2441
|
+
return 0
|
|
2442
|
+
else:
|
|
2443
|
+
# Direct sort
|
|
2444
|
+
if isinstance(elem, (Integer, Float)):
|
|
2445
|
+
return elem.value
|
|
2446
|
+
if isinstance(elem, String):
|
|
2447
|
+
return elem.value
|
|
2448
|
+
return 0
|
|
2449
|
+
|
|
2450
|
+
elements.sort(key=get_sort_key, reverse=descending)
|
|
2451
|
+
return List(elements)
|
|
2452
|
+
except Exception as e:
|
|
2453
|
+
return EvaluationError(f"sort() error: {str(e)}")
|
|
2454
|
+
|
|
2455
|
+
def _slice(*a):
|
|
2456
|
+
"""Get a slice of a list: slice(list, start, end?) -> list
|
|
2457
|
+
|
|
2458
|
+
- slice([1, 2, 3, 4], 1) -> [2, 3, 4] (from index 1 to end)
|
|
2459
|
+
- slice([1, 2, 3, 4], 1, 3) -> [2, 3] (from index 1 to 3, exclusive)
|
|
2460
|
+
- slice([1, 2, 3, 4], 0, 2) -> [1, 2] (first 2 elements)
|
|
2461
|
+
"""
|
|
2462
|
+
if len(a) < 2:
|
|
2463
|
+
return EvaluationError("slice() takes 2-3 arguments: slice(list, start, [end])")
|
|
2464
|
+
if not isinstance(a[0], List):
|
|
2465
|
+
return EvaluationError("slice() first argument must be a list")
|
|
2466
|
+
if not isinstance(a[1], Integer):
|
|
2467
|
+
return EvaluationError("slice() start must be an integer")
|
|
2468
|
+
|
|
2469
|
+
lst = a[0]
|
|
2470
|
+
start = a[1].value
|
|
2471
|
+
end = len(lst.elements) # Default: to the end
|
|
2472
|
+
|
|
2473
|
+
if len(a) >= 3:
|
|
2474
|
+
if not isinstance(a[2], Integer):
|
|
2475
|
+
return EvaluationError("slice() end must be an integer")
|
|
2476
|
+
end = a[2].value
|
|
2477
|
+
|
|
2478
|
+
# Handle negative indices
|
|
2479
|
+
if start < 0:
|
|
2480
|
+
start = max(0, len(lst.elements) + start)
|
|
2481
|
+
if end < 0:
|
|
2482
|
+
end = max(0, len(lst.elements) + end)
|
|
2483
|
+
|
|
2484
|
+
return List(lst.elements[start:end])
|
|
2485
|
+
|
|
2102
2486
|
def _reduce(*a):
|
|
2103
2487
|
if len(a) < 2:
|
|
2104
2488
|
return EvaluationError("reduce(arr, fn, [init])")
|
|
@@ -2114,6 +2498,28 @@ class FunctionEvaluatorMixin:
|
|
|
2114
2498
|
return EvaluationError("filter(arr, fn)")
|
|
2115
2499
|
return self._array_filter(a[0], a[1])
|
|
2116
2500
|
|
|
2501
|
+
def _vfs_stats(*a):
|
|
2502
|
+
"""Return VFS file cache statistics as a Map: vfs_stats()"""
|
|
2503
|
+
mgr = _get_vfs_manager()
|
|
2504
|
+
if mgr is None:
|
|
2505
|
+
return Map({"error": String("VFS not initialized")})
|
|
2506
|
+
stats = mgr.file_cache.stats()
|
|
2507
|
+
return Map({
|
|
2508
|
+
"entries": Integer(stats["entries"]),
|
|
2509
|
+
"bytes": Integer(stats["bytes"]),
|
|
2510
|
+
"hits": Integer(stats["hits"]),
|
|
2511
|
+
"misses": Integer(stats["misses"]),
|
|
2512
|
+
"hit_rate": Float(round(stats["hit_rate"], 4)),
|
|
2513
|
+
})
|
|
2514
|
+
|
|
2515
|
+
def _vfs_clear_cache(*a):
|
|
2516
|
+
"""Flush the VFS file content cache: vfs_clear_cache()"""
|
|
2517
|
+
mgr = _get_vfs_manager()
|
|
2518
|
+
if mgr is None:
|
|
2519
|
+
return NULL
|
|
2520
|
+
mgr.file_cache.clear()
|
|
2521
|
+
return NULL
|
|
2522
|
+
|
|
2117
2523
|
# File object creation (for RAII using statements)
|
|
2118
2524
|
def _file(*a):
|
|
2119
2525
|
if len(a) == 0 or len(a) > 2:
|
|
@@ -2133,22 +2539,18 @@ class FunctionEvaluatorMixin:
|
|
|
2133
2539
|
return EvaluationError(f"file() error: {str(e)}")
|
|
2134
2540
|
|
|
2135
2541
|
def _read_file(*a):
|
|
2136
|
-
"""Read entire file contents as string"""
|
|
2542
|
+
"""Read entire file contents as string (VFS-cached)"""
|
|
2543
|
+
cap_err = _check_io_read_capability()
|
|
2544
|
+
if cap_err: return cap_err
|
|
2137
2545
|
if len(a) != 1:
|
|
2138
2546
|
return EvaluationError("read_file() takes exactly 1 argument: path")
|
|
2139
2547
|
if not isinstance(a[0], String):
|
|
2140
2548
|
return EvaluationError("read_file() path must be a string")
|
|
2141
2549
|
|
|
2142
|
-
|
|
2143
|
-
path = a[0].value
|
|
2144
|
-
|
|
2145
|
-
# Normalize path
|
|
2146
|
-
if not os.path.isabs(path):
|
|
2147
|
-
path = os.path.join(os.getcwd(), path)
|
|
2550
|
+
path = _resolve_read_path(a[0].value)
|
|
2148
2551
|
|
|
2149
2552
|
try:
|
|
2150
|
-
|
|
2151
|
-
content = f.read()
|
|
2553
|
+
content = _cached_read(path)
|
|
2152
2554
|
return String(content)
|
|
2153
2555
|
except FileNotFoundError:
|
|
2154
2556
|
return EvaluationError(f"File not found: {path}")
|
|
@@ -2414,8 +2816,13 @@ class FunctionEvaluatorMixin:
|
|
|
2414
2816
|
"http_post": Builtin(_http_post, "http_post"),
|
|
2415
2817
|
"http_put": Builtin(_http_put, "http_put"),
|
|
2416
2818
|
"http_delete": Builtin(_http_delete, "http_delete"),
|
|
2819
|
+
"http_request": Builtin(_http_request, "http_request"),
|
|
2820
|
+
"http_parallel_get": Builtin(_http_parallel_get, "http_parallel_get"),
|
|
2821
|
+
"http_async_get": Builtin(_http_async_get, "http_async_get"),
|
|
2417
2822
|
"read_file": Builtin(_read_file, "read_file"),
|
|
2418
2823
|
"eval_file": Builtin(_eval_file, "eval_file"),
|
|
2824
|
+
"vfs_stats": Builtin(_vfs_stats, "vfs_stats"),
|
|
2825
|
+
"vfs_clear_cache": Builtin(_vfs_clear_cache, "vfs_clear_cache"),
|
|
2419
2826
|
"debug": Builtin(_debug, "debug"),
|
|
2420
2827
|
"debug_log": Builtin(_debug_log, "debug_log"),
|
|
2421
2828
|
"debug_trace": Builtin(_debug_trace, "debug_trace"),
|
|
@@ -2424,6 +2831,7 @@ class FunctionEvaluatorMixin:
|
|
|
2424
2831
|
"float": Builtin(_float, "float"),
|
|
2425
2832
|
"uppercase": Builtin(_uppercase, "uppercase"),
|
|
2426
2833
|
"lowercase": Builtin(_lowercase, "lowercase"),
|
|
2834
|
+
"split": Builtin(_split, "split"),
|
|
2427
2835
|
"random": Builtin(_random, "random"),
|
|
2428
2836
|
"persist_set": Builtin(_persist_set, "persist_set"),
|
|
2429
2837
|
"persist_get": Builtin(_persist_get, "persist_get"),
|
|
@@ -2435,6 +2843,8 @@ class FunctionEvaluatorMixin:
|
|
|
2435
2843
|
"push": Builtin(_push, "push"),
|
|
2436
2844
|
"append": Builtin(_append, "append"), # Mutating list append
|
|
2437
2845
|
"extend": Builtin(_extend, "extend"), # Mutating list extend
|
|
2846
|
+
"sort": Builtin(_sort, "sort"), # Sort list or list of maps by key
|
|
2847
|
+
"slice": Builtin(_slice, "slice"), # Get list slice: slice(list, start, end?)
|
|
2438
2848
|
"reduce": Builtin(_reduce, "reduce"),
|
|
2439
2849
|
"map": Builtin(_map, "map"),
|
|
2440
2850
|
"filter": Builtin(_filter, "filter"),
|
|
@@ -3120,6 +3530,32 @@ class FunctionEvaluatorMixin:
|
|
|
3120
3530
|
String("limit"): Integer(tx.gas_limit)
|
|
3121
3531
|
})
|
|
3122
3532
|
|
|
3533
|
+
# generateKeypair(algorithm?)
|
|
3534
|
+
def _generate_keypair(*a):
|
|
3535
|
+
algorithm = a[0].value if len(a) > 0 and hasattr(a[0], 'value') else 'ECDSA'
|
|
3536
|
+
try:
|
|
3537
|
+
private_key, public_key = CryptoPlugin.generate_keypair(algorithm)
|
|
3538
|
+
return Map({
|
|
3539
|
+
String('private_key'): String(private_key),
|
|
3540
|
+
String('public_key'): String(public_key)
|
|
3541
|
+
})
|
|
3542
|
+
except Exception as e:
|
|
3543
|
+
return EvaluationError(f"Keypair generation error: {str(e)}")
|
|
3544
|
+
|
|
3545
|
+
# deriveAddress(public_key, [prefix])
|
|
3546
|
+
def _derive_address(*a):
|
|
3547
|
+
if len(a) < 1 or len(a) > 2:
|
|
3548
|
+
return EvaluationError("deriveAddress() expects 1 or 2 arguments: public_key, [prefix]")
|
|
3549
|
+
public_key = a[0].value if hasattr(a[0], 'value') else str(a[0])
|
|
3550
|
+
prefix = None
|
|
3551
|
+
if len(a) > 1:
|
|
3552
|
+
prefix = a[1].value if hasattr(a[1], 'value') else str(a[1])
|
|
3553
|
+
try:
|
|
3554
|
+
result = CryptoPlugin.derive_address(public_key, prefix=prefix)
|
|
3555
|
+
return String(result)
|
|
3556
|
+
except Exception as e:
|
|
3557
|
+
return EvaluationError(f"Address derivation error: {str(e)}")
|
|
3558
|
+
|
|
3123
3559
|
self.builtins.update({
|
|
3124
3560
|
"hash": Builtin(_hash, "hash"),
|
|
3125
3561
|
"keccak256": Builtin(_keccak256, "keccak256"),
|
|
@@ -3127,6 +3563,8 @@ class FunctionEvaluatorMixin:
|
|
|
3127
3563
|
"verify_sig": Builtin(_verify_sig, "verify_sig"),
|
|
3128
3564
|
"tx": Builtin(_tx, "tx"),
|
|
3129
3565
|
"gas": Builtin(_gas, "gas"),
|
|
3566
|
+
"generateKeypair": Builtin(_generate_keypair, "generateKeypair"),
|
|
3567
|
+
"deriveAddress": Builtin(_derive_address, "deriveAddress"),
|
|
3130
3568
|
})
|
|
3131
3569
|
|
|
3132
3570
|
# Register advanced feature builtins
|