zexus 1.7.1 → 1.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/package.json +1 -1
- package/src/__init__.py +7 -0
- package/src/zexus/__init__.py +1 -1
- package/src/zexus/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/capability_system.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/debug_sanitizer.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/environment.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/error_reporter.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/input_validation.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/lexer.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/module_cache.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/module_manager.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/object.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/security.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/security_enforcement.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/syntax_validator.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/zexus_ast.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/zexus_token.cpython-312.pyc +0 -0
- package/src/zexus/access_control_system/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/access_control_system/__pycache__/access_control.cpython-312.pyc +0 -0
- package/src/zexus/advanced_types.py +17 -2
- package/src/zexus/blockchain/__init__.py +411 -0
- package/src/zexus/blockchain/accelerator.py +1160 -0
- package/src/zexus/blockchain/chain.py +660 -0
- package/src/zexus/blockchain/consensus.py +821 -0
- package/src/zexus/blockchain/contract_vm.py +1019 -0
- package/src/zexus/blockchain/crypto.py +79 -14
- package/src/zexus/blockchain/events.py +526 -0
- package/src/zexus/blockchain/loadtest.py +721 -0
- package/src/zexus/blockchain/monitoring.py +350 -0
- package/src/zexus/blockchain/mpt.py +716 -0
- package/src/zexus/blockchain/multichain.py +951 -0
- package/src/zexus/blockchain/multiprocess_executor.py +338 -0
- package/src/zexus/blockchain/network.py +886 -0
- package/src/zexus/blockchain/node.py +666 -0
- package/src/zexus/blockchain/rpc.py +1203 -0
- package/src/zexus/blockchain/rust_bridge.py +421 -0
- package/src/zexus/blockchain/storage.py +423 -0
- package/src/zexus/blockchain/tokens.py +750 -0
- package/src/zexus/blockchain/upgradeable.py +1004 -0
- package/src/zexus/blockchain/verification.py +1602 -0
- package/src/zexus/blockchain/wallet.py +621 -0
- package/src/zexus/cli/__pycache__/main.cpython-312.pyc +0 -0
- package/src/zexus/cli/main.py +300 -20
- package/src/zexus/cli/zpm.py +1 -1
- package/src/zexus/compiler/__pycache__/bytecode.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/lexer.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/parser.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/semantic.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/zexus_ast.cpython-312.pyc +0 -0
- package/src/zexus/compiler/lexer.py +10 -5
- package/src/zexus/concurrency_system.py +79 -0
- package/src/zexus/config.py +54 -0
- package/src/zexus/crypto_bridge.py +244 -8
- package/src/zexus/dap/__init__.py +10 -0
- package/src/zexus/dap/__main__.py +4 -0
- package/src/zexus/dap/dap_server.py +391 -0
- package/src/zexus/dap/debug_engine.py +298 -0
- package/src/zexus/environment.py +10 -1
- package/src/zexus/evaluator/__pycache__/bytecode_compiler.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/core.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/expressions.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/functions.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/resource_limiter.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/statements.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/unified_execution.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/utils.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/bytecode_compiler.py +441 -37
- package/src/zexus/evaluator/core.py +560 -49
- package/src/zexus/evaluator/expressions.py +122 -49
- package/src/zexus/evaluator/functions.py +417 -16
- package/src/zexus/evaluator/statements.py +521 -118
- package/src/zexus/evaluator/unified_execution.py +573 -72
- package/src/zexus/evaluator/utils.py +14 -2
- package/src/zexus/event_loop.py +186 -0
- package/src/zexus/lexer.py +742 -486
- package/src/zexus/lsp/__init__.py +1 -1
- package/src/zexus/lsp/definition_provider.py +163 -9
- package/src/zexus/lsp/server.py +22 -8
- package/src/zexus/lsp/symbol_provider.py +182 -9
- package/src/zexus/module_cache.py +237 -9
- package/src/zexus/object.py +64 -6
- package/src/zexus/parser/__pycache__/parser.cpython-312.pyc +0 -0
- package/src/zexus/parser/__pycache__/strategy_context.cpython-312.pyc +0 -0
- package/src/zexus/parser/__pycache__/strategy_structural.cpython-312.pyc +0 -0
- package/src/zexus/parser/parser.py +786 -285
- package/src/zexus/parser/strategy_context.py +407 -66
- package/src/zexus/parser/strategy_structural.py +117 -19
- package/src/zexus/persistence.py +15 -1
- package/src/zexus/renderer/__init__.py +15 -0
- package/src/zexus/renderer/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/backend.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/canvas.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/color_system.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/layout.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/main_renderer.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/painter.cpython-312.pyc +0 -0
- package/src/zexus/renderer/tk_backend.py +208 -0
- package/src/zexus/renderer/web_backend.py +260 -0
- package/src/zexus/runtime/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/runtime/__pycache__/async_runtime.cpython-312.pyc +0 -0
- package/src/zexus/runtime/__pycache__/load_manager.cpython-312.pyc +0 -0
- package/src/zexus/runtime/file_flags.py +137 -0
- package/src/zexus/safety/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/safety/__pycache__/memory_safety.cpython-312.pyc +0 -0
- package/src/zexus/security.py +424 -34
- package/src/zexus/stdlib/fs.py +23 -18
- package/src/zexus/stdlib/http.py +289 -186
- package/src/zexus/stdlib/sockets.py +207 -163
- package/src/zexus/stdlib/websockets.py +282 -0
- package/src/zexus/stdlib_integration.py +369 -2
- package/src/zexus/strategy_recovery.py +6 -3
- package/src/zexus/type_checker.py +423 -0
- package/src/zexus/virtual_filesystem.py +189 -2
- package/src/zexus/vm/__init__.py +113 -3
- package/src/zexus/vm/__pycache__/async_optimizer.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/bytecode.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/bytecode_converter.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/cache.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/compiler.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/gas_metering.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/jit.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/parallel_vm.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/vm.cpython-312.pyc +0 -0
- package/src/zexus/vm/async_optimizer.py +14 -1
- package/src/zexus/vm/binary_bytecode.py +659 -0
- package/src/zexus/vm/bytecode.py +28 -1
- package/src/zexus/vm/bytecode_converter.py +26 -12
- package/src/zexus/vm/cabi.c +1985 -0
- package/src/zexus/vm/cabi.cpython-312-x86_64-linux-gnu.so +0 -0
- package/src/zexus/vm/cabi.h +127 -0
- package/src/zexus/vm/cache.py +557 -17
- package/src/zexus/vm/compiler.py +703 -5
- package/src/zexus/vm/fastops.c +15743 -0
- package/src/zexus/vm/fastops.cpython-312-x86_64-linux-gnu.so +0 -0
- package/src/zexus/vm/fastops.pyx +288 -0
- package/src/zexus/vm/gas_metering.py +50 -9
- package/src/zexus/vm/jit.py +83 -2
- package/src/zexus/vm/native_jit_backend.py +1816 -0
- package/src/zexus/vm/native_runtime.cpp +1388 -0
- package/src/zexus/vm/native_runtime.cpython-312-x86_64-linux-gnu.so +0 -0
- package/src/zexus/vm/optimizer.py +161 -11
- package/src/zexus/vm/parallel_vm.py +118 -42
- package/src/zexus/vm/peephole_optimizer.py +82 -4
- package/src/zexus/vm/profiler.py +38 -18
- package/src/zexus/vm/register_allocator.py +16 -5
- package/src/zexus/vm/register_vm.py +8 -5
- package/src/zexus/vm/vm.py +3411 -573
- package/src/zexus/vm/wasm_compiler.py +658 -0
- package/src/zexus/zexus_ast.py +63 -11
- package/src/zexus/zexus_token.py +13 -5
- package/src/zexus/zpm/installer.py +55 -15
- package/src/zexus/zpm/package_manager.py +1 -1
- package/src/zexus/zpm/registry.py +257 -28
- package/src/zexus.egg-info/PKG-INFO +7 -4
- package/src/zexus.egg-info/SOURCES.txt +116 -9
- package/src/zexus.egg-info/entry_points.txt +1 -0
- package/src/zexus.egg-info/requires.txt +4 -0
|
@@ -26,6 +26,8 @@ class FunctionEvaluatorMixin:
|
|
|
26
26
|
# Initialize registries
|
|
27
27
|
self.builtins = {}
|
|
28
28
|
self._allow_coroutine_result = False
|
|
29
|
+
self._pending_revert_signature = None
|
|
30
|
+
self._tolerant_skip_counts = {}
|
|
29
31
|
|
|
30
32
|
# Renderer Registry (moved from global scope to instance scope)
|
|
31
33
|
self.render_registry = {
|
|
@@ -62,9 +64,44 @@ class FunctionEvaluatorMixin:
|
|
|
62
64
|
|
|
63
65
|
return EvaluationError("Coroutine did not complete within step limit")
|
|
64
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
|
+
|
|
65
82
|
def eval_call_expression(self, node, env, stack_trace):
|
|
66
83
|
debug_log("🚀 CallExpression node", f"Calling {node.function}")
|
|
67
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
|
+
|
|
68
105
|
fn = self.eval_node(node.function, env, stack_trace)
|
|
69
106
|
if is_error(fn):
|
|
70
107
|
return fn
|
|
@@ -88,7 +125,6 @@ class FunctionEvaluatorMixin:
|
|
|
88
125
|
|
|
89
126
|
# Check if arguments contain keyword arguments (AssignmentExpression nodes)
|
|
90
127
|
# This handles syntax like: Person(name="Bob", age=25)
|
|
91
|
-
from .. import zexus_ast
|
|
92
128
|
has_keyword_args = any(isinstance(arg, zexus_ast.AssignmentExpression) for arg in node.arguments)
|
|
93
129
|
|
|
94
130
|
if has_keyword_args:
|
|
@@ -149,7 +185,6 @@ class FunctionEvaluatorMixin:
|
|
|
149
185
|
Example: Box<number> creates a specialized Box constructor with T = number
|
|
150
186
|
"""
|
|
151
187
|
from ..object import EvaluationError, String, Map
|
|
152
|
-
from .. import zexus_ast
|
|
153
188
|
|
|
154
189
|
debug_log("_create_specialized_generic_constructor", f"Specializing with types: {type_args}")
|
|
155
190
|
|
|
@@ -332,6 +367,7 @@ class FunctionEvaluatorMixin:
|
|
|
332
367
|
except Exception:
|
|
333
368
|
pass
|
|
334
369
|
|
|
370
|
+
result = None
|
|
335
371
|
try:
|
|
336
372
|
res = self.eval_node(fn.body, new_env)
|
|
337
373
|
res = _resolve_awaitable(res)
|
|
@@ -347,6 +383,10 @@ class FunctionEvaluatorMixin:
|
|
|
347
383
|
# Store result for after-call hook
|
|
348
384
|
result = EvaluationError(str(e))
|
|
349
385
|
raise
|
|
386
|
+
except BaseException as e:
|
|
387
|
+
# Ensure result is set even for interrupts/SystemExit
|
|
388
|
+
result = EvaluationError(str(e))
|
|
389
|
+
raise
|
|
350
390
|
finally:
|
|
351
391
|
# CRITICAL: Execute deferred cleanup when function exits
|
|
352
392
|
# This happens in finally block to ensure cleanup runs even on errors
|
|
@@ -466,7 +506,9 @@ class FunctionEvaluatorMixin:
|
|
|
466
506
|
def eval_method_call_expression(self, node, env, stack_trace):
|
|
467
507
|
debug_log(" MethodCallExpression node", f"{node.object}.{node.method}")
|
|
468
508
|
|
|
509
|
+
debug_log("DEBUG evaluating method call object", node.object)
|
|
469
510
|
obj = self.eval_node(node.object, env, stack_trace)
|
|
511
|
+
debug_log("DEBUG object evaluated to", obj)
|
|
470
512
|
if is_error(obj):
|
|
471
513
|
return obj
|
|
472
514
|
|
|
@@ -625,6 +667,32 @@ class FunctionEvaluatorMixin:
|
|
|
625
667
|
if is_error(args):
|
|
626
668
|
return args
|
|
627
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
|
+
|
|
628
696
|
# Unwrap ReturnValue if needed
|
|
629
697
|
from ..object import ReturnValue
|
|
630
698
|
if isinstance(result, ReturnValue):
|
|
@@ -655,6 +723,7 @@ class FunctionEvaluatorMixin:
|
|
|
655
723
|
return self.apply_function(method_value, args, env)
|
|
656
724
|
|
|
657
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}")
|
|
658
727
|
return EvaluationError(f"Method '{method_name}' not supported for {obj_type}")
|
|
659
728
|
|
|
660
729
|
# --- Array Helpers (Internal) ---
|
|
@@ -863,16 +932,56 @@ class FunctionEvaluatorMixin:
|
|
|
863
932
|
except Exception as e:
|
|
864
933
|
return EvaluationError(f"constant_time_compare() error: {str(e)}")
|
|
865
934
|
|
|
866
|
-
# 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
|
+
|
|
867
970
|
def _read_text(*a):
|
|
971
|
+
cap_err = _check_io_read_capability()
|
|
972
|
+
if cap_err: return cap_err
|
|
868
973
|
if len(a) != 1 or not isinstance(a[0], String):
|
|
869
974
|
return EvaluationError("file_read_text() takes exactly 1 string argument")
|
|
870
975
|
return File.read_text(a[0])
|
|
871
976
|
|
|
872
977
|
def _write_text(*a):
|
|
978
|
+
cap_err = _check_io_write_capability()
|
|
979
|
+
if cap_err: return cap_err
|
|
873
980
|
if len(a) != 2 or not all(isinstance(x, String) for x in a):
|
|
874
981
|
return EvaluationError("file_write_text() takes exactly 2 string arguments")
|
|
875
|
-
|
|
982
|
+
result = File.write_text(a[0], a[1])
|
|
983
|
+
_invalidate_file_cache(_resolve_read_path(a[0].value))
|
|
984
|
+
return result
|
|
876
985
|
|
|
877
986
|
def _exists(*a):
|
|
878
987
|
if len(a) != 1 or not isinstance(a[0], String):
|
|
@@ -880,26 +989,38 @@ class FunctionEvaluatorMixin:
|
|
|
880
989
|
return File.exists(a[0])
|
|
881
990
|
|
|
882
991
|
def _read_json(*a):
|
|
992
|
+
cap_err = _check_io_read_capability()
|
|
993
|
+
if cap_err: return cap_err
|
|
883
994
|
if len(a) != 1 or not isinstance(a[0], String):
|
|
884
995
|
return EvaluationError("file_read_json() takes exactly 1 string argument")
|
|
885
996
|
return File.read_json(a[0])
|
|
886
997
|
|
|
887
998
|
def _write_json(*a):
|
|
999
|
+
cap_err = _check_io_write_capability()
|
|
1000
|
+
if cap_err: return cap_err
|
|
888
1001
|
if len(a) != 2 or not isinstance(a[0], String):
|
|
889
1002
|
return EvaluationError("file_write_json() takes path string and data")
|
|
890
|
-
|
|
1003
|
+
result = File.write_json(a[0], a[1])
|
|
1004
|
+
_invalidate_file_cache(_resolve_read_path(a[0].value))
|
|
1005
|
+
return result
|
|
891
1006
|
|
|
892
1007
|
def _file_append(*a):
|
|
1008
|
+
cap_err = _check_io_write_capability()
|
|
1009
|
+
if cap_err: return cap_err
|
|
893
1010
|
if len(a) != 2 or not all(isinstance(x, String) for x in a):
|
|
894
1011
|
return EvaluationError("file_append() takes exactly 2 string arguments")
|
|
895
|
-
|
|
1012
|
+
result = File.append_text(a[0], a[1])
|
|
1013
|
+
_invalidate_file_cache(_resolve_read_path(a[0].value))
|
|
1014
|
+
return result
|
|
896
1015
|
|
|
897
1016
|
def _list_dir(*a):
|
|
1017
|
+
cap_err = _check_io_read_capability()
|
|
1018
|
+
if cap_err: return cap_err
|
|
898
1019
|
if len(a) != 1 or not isinstance(a[0], String):
|
|
899
1020
|
return EvaluationError("file_list_dir() takes exactly 1 string argument")
|
|
900
1021
|
return File.list_directory(a[0])
|
|
901
1022
|
|
|
902
|
-
# Extended File System Operations
|
|
1023
|
+
# Extended File System Operations (with capability checks)
|
|
903
1024
|
def _fs_is_file(*a):
|
|
904
1025
|
if len(a) != 1 or not isinstance(a[0], String):
|
|
905
1026
|
return EvaluationError("fs_is_file() takes exactly 1 string argument")
|
|
@@ -913,6 +1034,8 @@ class FunctionEvaluatorMixin:
|
|
|
913
1034
|
return BooleanObj(os.path.isdir(a[0].value))
|
|
914
1035
|
|
|
915
1036
|
def _fs_mkdir(*a):
|
|
1037
|
+
cap_err = _check_io_write_capability()
|
|
1038
|
+
if cap_err: return cap_err
|
|
916
1039
|
if len(a) < 1 or not isinstance(a[0], String):
|
|
917
1040
|
return EvaluationError("fs_mkdir() takes path string and optional parents boolean")
|
|
918
1041
|
from pathlib import Path
|
|
@@ -926,16 +1049,21 @@ class FunctionEvaluatorMixin:
|
|
|
926
1049
|
return EvaluationError(f"fs_mkdir() error: {str(e)}")
|
|
927
1050
|
|
|
928
1051
|
def _fs_remove(*a):
|
|
1052
|
+
cap_err = _check_io_write_capability()
|
|
1053
|
+
if cap_err: return cap_err
|
|
929
1054
|
if len(a) != 1 or not isinstance(a[0], String):
|
|
930
1055
|
return EvaluationError("fs_remove() takes exactly 1 string argument")
|
|
931
1056
|
import os
|
|
932
1057
|
try:
|
|
933
1058
|
os.remove(a[0].value)
|
|
1059
|
+
_invalidate_file_cache(_resolve_read_path(a[0].value))
|
|
934
1060
|
return BooleanObj(True)
|
|
935
1061
|
except Exception as e:
|
|
936
1062
|
return EvaluationError(f"fs_remove() error: {str(e)}")
|
|
937
1063
|
|
|
938
1064
|
def _fs_rmdir(*a):
|
|
1065
|
+
cap_err = _check_io_write_capability()
|
|
1066
|
+
if cap_err: return cap_err
|
|
939
1067
|
if len(a) < 1 or not isinstance(a[0], String):
|
|
940
1068
|
return EvaluationError("fs_rmdir() takes path string and optional recursive boolean")
|
|
941
1069
|
import os
|
|
@@ -953,6 +1081,8 @@ class FunctionEvaluatorMixin:
|
|
|
953
1081
|
return EvaluationError(f"fs_rmdir() error: {str(e)}")
|
|
954
1082
|
|
|
955
1083
|
def _fs_rename(*a):
|
|
1084
|
+
cap_err = _check_io_write_capability()
|
|
1085
|
+
if cap_err: return cap_err
|
|
956
1086
|
if len(a) != 2 or not all(isinstance(x, String) for x in a):
|
|
957
1087
|
return EvaluationError("fs_rename() takes exactly 2 string arguments: old_path, new_path")
|
|
958
1088
|
import os
|
|
@@ -963,6 +1093,8 @@ class FunctionEvaluatorMixin:
|
|
|
963
1093
|
return EvaluationError(f"fs_rename() error: {str(e)}")
|
|
964
1094
|
|
|
965
1095
|
def _fs_copy(*a):
|
|
1096
|
+
cap_err = _check_io_write_capability()
|
|
1097
|
+
if cap_err: return cap_err
|
|
966
1098
|
if len(a) != 2 or not all(isinstance(x, String) for x in a):
|
|
967
1099
|
return EvaluationError("fs_copy() takes exactly 2 string arguments: src, dst")
|
|
968
1100
|
import shutil
|
|
@@ -983,6 +1115,8 @@ class FunctionEvaluatorMixin:
|
|
|
983
1115
|
# Socket/TCP Primitives
|
|
984
1116
|
def _socket_create_server(*a):
|
|
985
1117
|
"""Create TCP server: socket_create_server(host, port, handler, backlog?)"""
|
|
1118
|
+
cap_err = _check_network_capability()
|
|
1119
|
+
if cap_err: return cap_err
|
|
986
1120
|
if len(a) < 3:
|
|
987
1121
|
return EvaluationError("socket_create_server() requires at least 3 arguments: host, port, handler")
|
|
988
1122
|
|
|
@@ -1072,6 +1206,8 @@ class FunctionEvaluatorMixin:
|
|
|
1072
1206
|
|
|
1073
1207
|
def _socket_create_connection(*a):
|
|
1074
1208
|
"""Create TCP client: socket_create_connection(host, port, timeout?)"""
|
|
1209
|
+
cap_err = _check_network_capability()
|
|
1210
|
+
if cap_err: return cap_err
|
|
1075
1211
|
if len(a) < 2:
|
|
1076
1212
|
return EvaluationError("socket_create_connection() requires at least 2 arguments: host, port")
|
|
1077
1213
|
|
|
@@ -1722,9 +1858,42 @@ class FunctionEvaluatorMixin:
|
|
|
1722
1858
|
except Exception as e:
|
|
1723
1859
|
return EvaluationError(f"mongo_connect() error: {str(e)}")
|
|
1724
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
|
+
|
|
1725
1892
|
# HTTP Client
|
|
1726
1893
|
def _http_get(*a):
|
|
1727
1894
|
"""HTTP GET request: http_get(url, headers?, timeout?)"""
|
|
1895
|
+
cap_err = _check_network_capability()
|
|
1896
|
+
if cap_err: return cap_err
|
|
1728
1897
|
if len(a) < 1:
|
|
1729
1898
|
return EvaluationError("http_get() requires at least 1 argument: url")
|
|
1730
1899
|
|
|
@@ -1750,6 +1919,8 @@ class FunctionEvaluatorMixin:
|
|
|
1750
1919
|
|
|
1751
1920
|
def _http_post(*a):
|
|
1752
1921
|
"""HTTP POST request: http_post(url, data, headers?, timeout?)"""
|
|
1922
|
+
cap_err = _check_network_capability()
|
|
1923
|
+
if cap_err: return cap_err
|
|
1753
1924
|
if len(a) < 2:
|
|
1754
1925
|
return EvaluationError("http_post() requires at least 2 arguments: url, data")
|
|
1755
1926
|
|
|
@@ -1778,6 +1949,8 @@ class FunctionEvaluatorMixin:
|
|
|
1778
1949
|
|
|
1779
1950
|
def _http_put(*a):
|
|
1780
1951
|
"""HTTP PUT request: http_put(url, data, headers?, timeout?)"""
|
|
1952
|
+
cap_err = _check_network_capability()
|
|
1953
|
+
if cap_err: return cap_err
|
|
1781
1954
|
if len(a) < 2:
|
|
1782
1955
|
return EvaluationError("http_put() requires at least 2 arguments: url, data")
|
|
1783
1956
|
|
|
@@ -1803,6 +1976,8 @@ class FunctionEvaluatorMixin:
|
|
|
1803
1976
|
|
|
1804
1977
|
def _http_delete(*a):
|
|
1805
1978
|
"""HTTP DELETE request: http_delete(url, headers?, timeout?)"""
|
|
1979
|
+
cap_err = _check_network_capability()
|
|
1980
|
+
if cap_err: return cap_err
|
|
1806
1981
|
if len(a) < 1:
|
|
1807
1982
|
return EvaluationError("http_delete() requires at least 1 argument: url")
|
|
1808
1983
|
|
|
@@ -1823,6 +1998,62 @@ class FunctionEvaluatorMixin:
|
|
|
1823
1998
|
return _python_to_zexus(result, mark_untrusted=True)
|
|
1824
1999
|
except Exception as e:
|
|
1825
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)}")
|
|
1826
2057
|
|
|
1827
2058
|
# Debug
|
|
1828
2059
|
def _debug(*a):
|
|
@@ -1935,6 +2166,22 @@ class FunctionEvaluatorMixin:
|
|
|
1935
2166
|
if isinstance(arg, String):
|
|
1936
2167
|
return String(arg.value.lower())
|
|
1937
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])
|
|
1938
2185
|
|
|
1939
2186
|
def _random(*a):
|
|
1940
2187
|
"""Generate random number. random() -> 0-1, random(max) -> 0 to max-1"""
|
|
@@ -2136,6 +2383,106 @@ class FunctionEvaluatorMixin:
|
|
|
2136
2383
|
a[0].extend(a[1])
|
|
2137
2384
|
return a[0]
|
|
2138
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
|
+
|
|
2139
2486
|
def _reduce(*a):
|
|
2140
2487
|
if len(a) < 2:
|
|
2141
2488
|
return EvaluationError("reduce(arr, fn, [init])")
|
|
@@ -2151,6 +2498,28 @@ class FunctionEvaluatorMixin:
|
|
|
2151
2498
|
return EvaluationError("filter(arr, fn)")
|
|
2152
2499
|
return self._array_filter(a[0], a[1])
|
|
2153
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
|
+
|
|
2154
2523
|
# File object creation (for RAII using statements)
|
|
2155
2524
|
def _file(*a):
|
|
2156
2525
|
if len(a) == 0 or len(a) > 2:
|
|
@@ -2170,22 +2539,18 @@ class FunctionEvaluatorMixin:
|
|
|
2170
2539
|
return EvaluationError(f"file() error: {str(e)}")
|
|
2171
2540
|
|
|
2172
2541
|
def _read_file(*a):
|
|
2173
|
-
"""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
|
|
2174
2545
|
if len(a) != 1:
|
|
2175
2546
|
return EvaluationError("read_file() takes exactly 1 argument: path")
|
|
2176
2547
|
if not isinstance(a[0], String):
|
|
2177
2548
|
return EvaluationError("read_file() path must be a string")
|
|
2178
2549
|
|
|
2179
|
-
|
|
2180
|
-
path = a[0].value
|
|
2181
|
-
|
|
2182
|
-
# Normalize path
|
|
2183
|
-
if not os.path.isabs(path):
|
|
2184
|
-
path = os.path.join(os.getcwd(), path)
|
|
2550
|
+
path = _resolve_read_path(a[0].value)
|
|
2185
2551
|
|
|
2186
2552
|
try:
|
|
2187
|
-
|
|
2188
|
-
content = f.read()
|
|
2553
|
+
content = _cached_read(path)
|
|
2189
2554
|
return String(content)
|
|
2190
2555
|
except FileNotFoundError:
|
|
2191
2556
|
return EvaluationError(f"File not found: {path}")
|
|
@@ -2451,8 +2816,13 @@ class FunctionEvaluatorMixin:
|
|
|
2451
2816
|
"http_post": Builtin(_http_post, "http_post"),
|
|
2452
2817
|
"http_put": Builtin(_http_put, "http_put"),
|
|
2453
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"),
|
|
2454
2822
|
"read_file": Builtin(_read_file, "read_file"),
|
|
2455
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"),
|
|
2456
2826
|
"debug": Builtin(_debug, "debug"),
|
|
2457
2827
|
"debug_log": Builtin(_debug_log, "debug_log"),
|
|
2458
2828
|
"debug_trace": Builtin(_debug_trace, "debug_trace"),
|
|
@@ -2461,6 +2831,7 @@ class FunctionEvaluatorMixin:
|
|
|
2461
2831
|
"float": Builtin(_float, "float"),
|
|
2462
2832
|
"uppercase": Builtin(_uppercase, "uppercase"),
|
|
2463
2833
|
"lowercase": Builtin(_lowercase, "lowercase"),
|
|
2834
|
+
"split": Builtin(_split, "split"),
|
|
2464
2835
|
"random": Builtin(_random, "random"),
|
|
2465
2836
|
"persist_set": Builtin(_persist_set, "persist_set"),
|
|
2466
2837
|
"persist_get": Builtin(_persist_get, "persist_get"),
|
|
@@ -2472,6 +2843,8 @@ class FunctionEvaluatorMixin:
|
|
|
2472
2843
|
"push": Builtin(_push, "push"),
|
|
2473
2844
|
"append": Builtin(_append, "append"), # Mutating list append
|
|
2474
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?)
|
|
2475
2848
|
"reduce": Builtin(_reduce, "reduce"),
|
|
2476
2849
|
"map": Builtin(_map, "map"),
|
|
2477
2850
|
"filter": Builtin(_filter, "filter"),
|
|
@@ -3157,6 +3530,32 @@ class FunctionEvaluatorMixin:
|
|
|
3157
3530
|
String("limit"): Integer(tx.gas_limit)
|
|
3158
3531
|
})
|
|
3159
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
|
+
|
|
3160
3559
|
self.builtins.update({
|
|
3161
3560
|
"hash": Builtin(_hash, "hash"),
|
|
3162
3561
|
"keccak256": Builtin(_keccak256, "keccak256"),
|
|
@@ -3164,6 +3563,8 @@ class FunctionEvaluatorMixin:
|
|
|
3164
3563
|
"verify_sig": Builtin(_verify_sig, "verify_sig"),
|
|
3165
3564
|
"tx": Builtin(_tx, "tx"),
|
|
3166
3565
|
"gas": Builtin(_gas, "gas"),
|
|
3566
|
+
"generateKeypair": Builtin(_generate_keypair, "generateKeypair"),
|
|
3567
|
+
"deriveAddress": Builtin(_derive_address, "deriveAddress"),
|
|
3167
3568
|
})
|
|
3168
3569
|
|
|
3169
3570
|
# Register advanced feature builtins
|