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
|
@@ -6,22 +6,72 @@ re-parsing and re-evaluating modules that have already been loaded.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import os
|
|
9
|
+
import hashlib
|
|
9
10
|
import threading
|
|
10
|
-
from typing import Dict, Optional
|
|
11
|
+
from typing import Dict, Optional, Any, Tuple, Set
|
|
11
12
|
from .object import Environment
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
# Module cache stores: (environment, bytecode, ast)
|
|
15
|
+
_MODULE_CACHE: Dict[str, Tuple[Environment, Any, Any]] = {}
|
|
14
16
|
_MODULE_CACHE_LOCK = threading.Lock()
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
# Circular-import detection: set of normalized paths currently being loaded
|
|
19
|
+
_LOADING_SET: Set[str] = set()
|
|
20
|
+
_LOADING_SET_LOCK = threading.Lock()
|
|
21
|
+
|
|
22
|
+
# Contract AST cache by source hash
|
|
23
|
+
_CONTRACT_AST_CACHE: Dict[str, Any] = {}
|
|
24
|
+
_CONTRACT_AST_LOCK = threading.Lock()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class CircularImportError(Exception):
|
|
28
|
+
"""Raised when a circular import dependency is detected."""
|
|
29
|
+
def __init__(self, path: str, chain: Optional[list] = None):
|
|
30
|
+
self.path = path
|
|
31
|
+
self.chain = chain or []
|
|
32
|
+
if chain:
|
|
33
|
+
cycle = " -> ".join(chain + [path])
|
|
34
|
+
msg = f"Circular import detected: {cycle}"
|
|
35
|
+
else:
|
|
36
|
+
msg = f"Circular import detected while loading: {path}"
|
|
37
|
+
super().__init__(msg)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def begin_loading(module_path: str) -> None:
|
|
41
|
+
"""Mark *module_path* as currently being loaded.
|
|
42
|
+
|
|
43
|
+
Raises ``CircularImportError`` if the module is already in the loading set
|
|
44
|
+
(i.e. a circular dependency has been encountered).
|
|
45
|
+
"""
|
|
46
|
+
norm = normalize_path(module_path)
|
|
47
|
+
with _LOADING_SET_LOCK:
|
|
48
|
+
if norm in _LOADING_SET:
|
|
49
|
+
raise CircularImportError(norm, list(_LOADING_SET))
|
|
50
|
+
_LOADING_SET.add(norm)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def end_loading(module_path: str) -> None:
|
|
54
|
+
"""Remove *module_path* from the loading set after it has finished loading."""
|
|
55
|
+
norm = normalize_path(module_path)
|
|
56
|
+
with _LOADING_SET_LOCK:
|
|
57
|
+
_LOADING_SET.discard(norm)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def is_loading(module_path: str) -> bool:
|
|
61
|
+
"""Check whether *module_path* is currently being loaded."""
|
|
62
|
+
norm = normalize_path(module_path)
|
|
63
|
+
with _LOADING_SET_LOCK:
|
|
64
|
+
return norm in _LOADING_SET
|
|
65
|
+
|
|
66
|
+
def get_cached_module(module_path: str) -> Optional[Tuple[Environment, Any, Any]]:
|
|
67
|
+
"""Get a cached module (environment, bytecode, ast) if available"""
|
|
18
68
|
with _MODULE_CACHE_LOCK:
|
|
19
69
|
return _MODULE_CACHE.get(module_path)
|
|
20
70
|
|
|
21
|
-
def cache_module(module_path: str, module_env: Environment) -> None:
|
|
22
|
-
"""Cache a loaded module environment"""
|
|
71
|
+
def cache_module(module_path: str, module_env: Environment, bytecode: Any = None, ast: Any = None) -> None:
|
|
72
|
+
"""Cache a loaded module environment with optional bytecode and AST"""
|
|
23
73
|
with _MODULE_CACHE_LOCK:
|
|
24
|
-
_MODULE_CACHE[module_path] = module_env
|
|
74
|
+
_MODULE_CACHE[module_path] = (module_env, bytecode, ast)
|
|
25
75
|
|
|
26
76
|
def clear_module_cache() -> None:
|
|
27
77
|
"""Clear the entire module cache"""
|
|
@@ -40,6 +90,16 @@ def list_cached_modules() -> list[str]:
|
|
|
40
90
|
with _MODULE_CACHE_LOCK:
|
|
41
91
|
return list(_MODULE_CACHE.keys())
|
|
42
92
|
|
|
93
|
+
def get_cached_contract_ast(source_hash: str) -> Optional[Any]:
|
|
94
|
+
"""Get a cached contract AST by source hash"""
|
|
95
|
+
with _CONTRACT_AST_LOCK:
|
|
96
|
+
return _CONTRACT_AST_CACHE.get(source_hash)
|
|
97
|
+
|
|
98
|
+
def cache_contract_ast(source_hash: str, ast: Any) -> None:
|
|
99
|
+
"""Cache a parsed contract AST"""
|
|
100
|
+
with _CONTRACT_AST_LOCK:
|
|
101
|
+
_CONTRACT_AST_CACHE[source_hash] = ast
|
|
102
|
+
|
|
43
103
|
def get_module_candidates(file_path: str, importer_file: str = None) -> list[str]:
|
|
44
104
|
"""Get candidate paths for a module, checking zpm_modules etc.
|
|
45
105
|
|
|
@@ -62,13 +122,19 @@ def get_module_candidates(file_path: str, importer_file: str = None) -> list[str
|
|
|
62
122
|
importer_dir = os.path.dirname(importer_file)
|
|
63
123
|
resolved_path = os.path.join(importer_dir, file_path[2:]) # Remove './'
|
|
64
124
|
candidates.append(resolved_path)
|
|
125
|
+
# Also consider project-root relative paths like "./tests/..."
|
|
126
|
+
candidates.append(os.path.join(os.getcwd(), file_path[2:]))
|
|
65
127
|
elif importer_file and file_path.startswith('../'):
|
|
66
128
|
# Parent directory relative to importing file
|
|
67
129
|
importer_dir = os.path.dirname(importer_file)
|
|
68
130
|
resolved_path = os.path.join(importer_dir, file_path)
|
|
69
131
|
candidates.append(resolved_path)
|
|
70
132
|
else:
|
|
71
|
-
#
|
|
133
|
+
# For bare imports (no ./ or ../ prefix), check relative to importer first
|
|
134
|
+
if importer_file:
|
|
135
|
+
importer_dir = os.path.dirname(importer_file)
|
|
136
|
+
candidates.append(os.path.join(importer_dir, file_path))
|
|
137
|
+
# Then relative to current working directory
|
|
72
138
|
candidates.append(os.path.join(os.getcwd(), file_path))
|
|
73
139
|
|
|
74
140
|
# Also check zpm_modules directory
|
|
@@ -86,4 +152,168 @@ def get_module_candidates(file_path: str, importer_file: str = None) -> list[str
|
|
|
86
152
|
|
|
87
153
|
def normalize_path(path: str) -> str:
|
|
88
154
|
"""Normalize a path for consistent cache keys"""
|
|
89
|
-
return os.path.abspath(os.path.expanduser(path))
|
|
155
|
+
return os.path.abspath(os.path.expanduser(path))
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
# ---------------------------------------------------------------------------
|
|
159
|
+
# Module Pre-compilation
|
|
160
|
+
# ---------------------------------------------------------------------------
|
|
161
|
+
|
|
162
|
+
def _extract_import_paths(node) -> list:
|
|
163
|
+
"""Recursively walk an AST and return all import file paths."""
|
|
164
|
+
from . import zexus_ast
|
|
165
|
+
paths = []
|
|
166
|
+
if isinstance(node, zexus_ast.Program):
|
|
167
|
+
for stmt in getattr(node, 'statements', []):
|
|
168
|
+
paths.extend(_extract_import_paths(stmt))
|
|
169
|
+
elif isinstance(node, zexus_ast.UseStatement):
|
|
170
|
+
fp = node.file_path
|
|
171
|
+
if hasattr(fp, 'value'):
|
|
172
|
+
fp = fp.value
|
|
173
|
+
if isinstance(fp, str):
|
|
174
|
+
paths.append(fp)
|
|
175
|
+
elif isinstance(node, zexus_ast.FromStatement):
|
|
176
|
+
fp = node.file_path
|
|
177
|
+
if hasattr(fp, 'value'):
|
|
178
|
+
fp = fp.value
|
|
179
|
+
if isinstance(fp, str):
|
|
180
|
+
paths.append(fp)
|
|
181
|
+
elif isinstance(node, (zexus_ast.BlockStatement,)):
|
|
182
|
+
for stmt in getattr(node, 'statements', []):
|
|
183
|
+
paths.extend(_extract_import_paths(stmt))
|
|
184
|
+
elif isinstance(node, zexus_ast.IfStatement):
|
|
185
|
+
paths.extend(_extract_import_paths(node.consequence))
|
|
186
|
+
if node.alternative:
|
|
187
|
+
paths.extend(_extract_import_paths(node.alternative))
|
|
188
|
+
for cond, body in getattr(node, 'elif_parts', []):
|
|
189
|
+
paths.extend(_extract_import_paths(body))
|
|
190
|
+
elif isinstance(node, (zexus_ast.WhileStatement, zexus_ast.ForEachStatement)):
|
|
191
|
+
paths.extend(_extract_import_paths(getattr(node, 'body', None) or getattr(node, 'block', None)))
|
|
192
|
+
elif isinstance(node, zexus_ast.FunctionStatement):
|
|
193
|
+
paths.extend(_extract_import_paths(getattr(node, 'body', None)))
|
|
194
|
+
elif isinstance(node, zexus_ast.ActionStatement):
|
|
195
|
+
paths.extend(_extract_import_paths(getattr(node, 'body', None)))
|
|
196
|
+
elif isinstance(node, zexus_ast.TryCatchStatement):
|
|
197
|
+
paths.extend(_extract_import_paths(getattr(node, 'try_body', None)))
|
|
198
|
+
paths.extend(_extract_import_paths(getattr(node, 'catch_body', None)))
|
|
199
|
+
if getattr(node, 'finally_body', None):
|
|
200
|
+
paths.extend(_extract_import_paths(node.finally_body))
|
|
201
|
+
return [p for p in paths if p]
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def precompile_modules(program, main_file: str, compile_bytecode: bool = True) -> dict:
|
|
205
|
+
"""Pre-compile all imported modules before execution.
|
|
206
|
+
|
|
207
|
+
Walks the AST of *program*, resolves every ``use`` / ``from`` import,
|
|
208
|
+
lexes + parses each module file, optionally compiles to bytecode, and
|
|
209
|
+
stores everything in the global ``_MODULE_CACHE``. Processes modules
|
|
210
|
+
recursively so transitive dependencies are also cached.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
program: The parsed AST (Program node) of the main file.
|
|
214
|
+
main_file: Absolute path of the main source file.
|
|
215
|
+
compile_bytecode: If True, also compile each module to VM bytecode.
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
dict mapping normalised module path → (ast, bytecode_or_None).
|
|
219
|
+
"""
|
|
220
|
+
from .lexer import Lexer
|
|
221
|
+
from .parser import Parser
|
|
222
|
+
from .stdlib_integration import is_stdlib_module
|
|
223
|
+
from .builtin_modules import is_builtin_module
|
|
224
|
+
|
|
225
|
+
compiler_mod = None
|
|
226
|
+
if compile_bytecode:
|
|
227
|
+
try:
|
|
228
|
+
from .vm.compiler import compile_ast_to_bytecode
|
|
229
|
+
compiler_mod = compile_ast_to_bytecode
|
|
230
|
+
except Exception:
|
|
231
|
+
compiler_mod = None
|
|
232
|
+
|
|
233
|
+
results: Dict[str, Any] = {}
|
|
234
|
+
visited: Set[str] = set()
|
|
235
|
+
|
|
236
|
+
def _resolve_and_cache(import_path: str, importer_file: str):
|
|
237
|
+
"""Resolve a single import and cache it, then recurse."""
|
|
238
|
+
# Skip stdlib / builtin modules — they aren't file-based
|
|
239
|
+
if is_stdlib_module(import_path) or is_builtin_module(import_path):
|
|
240
|
+
return
|
|
241
|
+
|
|
242
|
+
candidates = get_module_candidates(import_path, importer_file)
|
|
243
|
+
resolved = None
|
|
244
|
+
for cand in candidates:
|
|
245
|
+
norm = normalize_path(cand)
|
|
246
|
+
if norm in visited:
|
|
247
|
+
import warnings
|
|
248
|
+
warnings.warn(
|
|
249
|
+
f"Circular import detected during pre-compilation: {import_path} "
|
|
250
|
+
f"(resolved to {norm})",
|
|
251
|
+
stacklevel=2,
|
|
252
|
+
)
|
|
253
|
+
return # Already processed (or in progress — avoids cycles)
|
|
254
|
+
if os.path.isfile(cand):
|
|
255
|
+
resolved = cand
|
|
256
|
+
break
|
|
257
|
+
|
|
258
|
+
if resolved is None:
|
|
259
|
+
return # Unresolvable — runtime will report the error
|
|
260
|
+
|
|
261
|
+
norm = normalize_path(resolved)
|
|
262
|
+
if norm in visited:
|
|
263
|
+
return
|
|
264
|
+
visited.add(norm)
|
|
265
|
+
|
|
266
|
+
# Already cached from a previous run?
|
|
267
|
+
cached = get_cached_module(norm)
|
|
268
|
+
if cached is not None:
|
|
269
|
+
results[norm] = (cached[2], cached[1]) # (ast, bytecode)
|
|
270
|
+
# Still recurse into dependencies of the cached module
|
|
271
|
+
if cached[2] is not None:
|
|
272
|
+
sub_paths = _extract_import_paths(cached[2])
|
|
273
|
+
for sp in sub_paths:
|
|
274
|
+
_resolve_and_cache(sp, resolved)
|
|
275
|
+
return
|
|
276
|
+
|
|
277
|
+
# Read, lex, parse
|
|
278
|
+
try:
|
|
279
|
+
with open(resolved, 'r', encoding='utf-8') as f:
|
|
280
|
+
source = f.read()
|
|
281
|
+
except (OSError, IOError):
|
|
282
|
+
return
|
|
283
|
+
|
|
284
|
+
try:
|
|
285
|
+
lexer = Lexer(source, filename=resolved)
|
|
286
|
+
parser = Parser(lexer, 'universal', enable_advanced_strategies=True)
|
|
287
|
+
mod_ast = parser.parse_program()
|
|
288
|
+
except Exception:
|
|
289
|
+
return # Parse failure — runtime will handle
|
|
290
|
+
|
|
291
|
+
# Optionally compile to bytecode
|
|
292
|
+
mod_bytecode = None
|
|
293
|
+
if compiler_mod is not None:
|
|
294
|
+
try:
|
|
295
|
+
mod_bytecode = compiler_mod(mod_ast, optimize=True)
|
|
296
|
+
except Exception:
|
|
297
|
+
pass # Compilation failure is non-fatal; interpreter fallback
|
|
298
|
+
|
|
299
|
+
# Cache a placeholder env + ast + bytecode so runtime finds them.
|
|
300
|
+
# Mark the env as pre-compiled but not yet evaluated — the runtime
|
|
301
|
+
# will execute the bytecode/AST to populate it on first use.
|
|
302
|
+
mod_env = Environment()
|
|
303
|
+
mod_env.set("__file__", resolved)
|
|
304
|
+
mod_env.set("__MODULE__", os.path.splitext(os.path.basename(resolved))[0])
|
|
305
|
+
mod_env._precompiled = True # Marker for eval_use_statement
|
|
306
|
+
cache_module(norm, mod_env, mod_bytecode, mod_ast)
|
|
307
|
+
results[norm] = (mod_ast, mod_bytecode)
|
|
308
|
+
|
|
309
|
+
# Recurse into sub-imports
|
|
310
|
+
sub_paths = _extract_import_paths(mod_ast)
|
|
311
|
+
for sp in sub_paths:
|
|
312
|
+
_resolve_and_cache(sp, resolved)
|
|
313
|
+
|
|
314
|
+
# Kick off from the main program's imports
|
|
315
|
+
import_paths = _extract_import_paths(program)
|
|
316
|
+
for ip in import_paths:
|
|
317
|
+
_resolve_and_cache(ip, main_file)
|
|
318
|
+
|
|
319
|
+
return results
|
|
@@ -13,6 +13,7 @@ class ModuleManager:
|
|
|
13
13
|
self.base_path / "lib"
|
|
14
14
|
]
|
|
15
15
|
self._debug = False
|
|
16
|
+
self._max_find_results = 50
|
|
16
17
|
|
|
17
18
|
def normalize_path(self, path):
|
|
18
19
|
"""Normalize a module path"""
|
|
@@ -87,6 +88,129 @@ class ModuleManager:
|
|
|
87
88
|
"""Disable debug logging"""
|
|
88
89
|
self._debug = False
|
|
89
90
|
|
|
91
|
+
def find_files(self, pattern, current_dir=None, scope=None, max_results=None):
|
|
92
|
+
"""Search for files matching pattern within known search paths.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
pattern: File name or relative pattern to search for.
|
|
96
|
+
current_dir: Directory of the importing file (for relative priority).
|
|
97
|
+
scope: Optional directory hint (absolute or relative) to limit search.
|
|
98
|
+
max_results: Maximum matches to return (defaults to manager limit).
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
List of normalized absolute paths matching the pattern.
|
|
102
|
+
"""
|
|
103
|
+
max_results = max_results or self._max_find_results
|
|
104
|
+
|
|
105
|
+
if not pattern:
|
|
106
|
+
return []
|
|
107
|
+
|
|
108
|
+
# Absolute pattern short-circuit
|
|
109
|
+
try:
|
|
110
|
+
pattern_path = Path(pattern)
|
|
111
|
+
except TypeError:
|
|
112
|
+
return []
|
|
113
|
+
|
|
114
|
+
if pattern_path.is_absolute():
|
|
115
|
+
if pattern_path.exists():
|
|
116
|
+
return [self.normalize_path(pattern_path)]
|
|
117
|
+
# Try with known extensions
|
|
118
|
+
for ext in (".zx", ".zexus"):
|
|
119
|
+
candidate = pattern_path.with_suffix(ext)
|
|
120
|
+
if candidate.exists():
|
|
121
|
+
return [self.normalize_path(candidate)]
|
|
122
|
+
return []
|
|
123
|
+
|
|
124
|
+
roots = []
|
|
125
|
+
|
|
126
|
+
def _append_root(path_candidate):
|
|
127
|
+
if not path_candidate:
|
|
128
|
+
return
|
|
129
|
+
try:
|
|
130
|
+
resolved = Path(path_candidate).resolve()
|
|
131
|
+
except (OSError, RuntimeError):
|
|
132
|
+
return
|
|
133
|
+
if resolved not in roots and resolved.exists():
|
|
134
|
+
roots.append(resolved)
|
|
135
|
+
|
|
136
|
+
# Scope hint first (if provided)
|
|
137
|
+
if scope:
|
|
138
|
+
scope_path = Path(scope)
|
|
139
|
+
if not scope_path.is_absolute() and current_dir:
|
|
140
|
+
_append_root(Path(current_dir) / scope_path)
|
|
141
|
+
_append_root(self.base_path / scope_path)
|
|
142
|
+
if scope_path.is_absolute():
|
|
143
|
+
_append_root(scope_path)
|
|
144
|
+
|
|
145
|
+
# Current file directory has highest priority after scope
|
|
146
|
+
if current_dir:
|
|
147
|
+
_append_root(current_dir)
|
|
148
|
+
|
|
149
|
+
# Standard search paths (project root, modules, lib, etc.)
|
|
150
|
+
for search_path in self.search_paths:
|
|
151
|
+
_append_root(search_path)
|
|
152
|
+
|
|
153
|
+
# Deduplicate while preserving order
|
|
154
|
+
seen = set()
|
|
155
|
+
ordered_roots = []
|
|
156
|
+
for root in roots:
|
|
157
|
+
if root in seen:
|
|
158
|
+
continue
|
|
159
|
+
seen.add(root)
|
|
160
|
+
ordered_roots.append(root)
|
|
161
|
+
|
|
162
|
+
matches = []
|
|
163
|
+
match_set = set()
|
|
164
|
+
|
|
165
|
+
def _record(path_obj):
|
|
166
|
+
normalized = self.normalize_path(path_obj)
|
|
167
|
+
if normalized not in match_set:
|
|
168
|
+
match_set.add(normalized)
|
|
169
|
+
matches.append(normalized)
|
|
170
|
+
|
|
171
|
+
# Exact relative matches via direct join first
|
|
172
|
+
for root in ordered_roots:
|
|
173
|
+
candidate = root / pattern_path
|
|
174
|
+
if candidate.exists() and candidate.is_file():
|
|
175
|
+
_record(candidate)
|
|
176
|
+
if len(matches) >= max_results:
|
|
177
|
+
return matches
|
|
178
|
+
else:
|
|
179
|
+
for ext in (".zx", ".zexus"):
|
|
180
|
+
candidate_ext = candidate.with_suffix(ext)
|
|
181
|
+
if candidate_ext.exists() and candidate_ext.is_file():
|
|
182
|
+
_record(candidate_ext)
|
|
183
|
+
if len(matches) >= max_results:
|
|
184
|
+
return matches
|
|
185
|
+
|
|
186
|
+
# Fallback to glob search across roots
|
|
187
|
+
if pattern_path.name:
|
|
188
|
+
search_pattern = str(pattern_path)
|
|
189
|
+
basename_pattern = pattern_path.name
|
|
190
|
+
else:
|
|
191
|
+
search_pattern = pattern
|
|
192
|
+
basename_pattern = pattern
|
|
193
|
+
|
|
194
|
+
for root in ordered_roots:
|
|
195
|
+
if len(matches) >= max_results:
|
|
196
|
+
break
|
|
197
|
+
try:
|
|
198
|
+
iterator = root.rglob(search_pattern)
|
|
199
|
+
except (ValueError, OSError):
|
|
200
|
+
try:
|
|
201
|
+
iterator = root.rglob(basename_pattern)
|
|
202
|
+
except (ValueError, OSError):
|
|
203
|
+
continue
|
|
204
|
+
|
|
205
|
+
for found in iterator:
|
|
206
|
+
if not found.is_file():
|
|
207
|
+
continue
|
|
208
|
+
_record(found)
|
|
209
|
+
if len(matches) >= max_results:
|
|
210
|
+
break
|
|
211
|
+
|
|
212
|
+
return matches
|
|
213
|
+
|
|
90
214
|
# Create a default instance for backwards compatibility
|
|
91
215
|
_default_manager = ModuleManager()
|
|
92
216
|
|
|
@@ -104,4 +228,8 @@ def cache_module(path, module_env):
|
|
|
104
228
|
return _default_manager.cache_module(path, module_env)
|
|
105
229
|
|
|
106
230
|
def clear_cache():
|
|
107
|
-
return _default_manager.clear_cache()
|
|
231
|
+
return _default_manager.clear_cache()
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def find_files(pattern, current_dir=None, scope=None, max_results=None):
|
|
235
|
+
return _default_manager.find_files(pattern, current_dir=current_dir, scope=scope, max_results=max_results)
|
package/src/zexus/object.py
CHANGED
|
@@ -98,6 +98,20 @@ class List(Object):
|
|
|
98
98
|
except Exception:
|
|
99
99
|
return NULL
|
|
100
100
|
|
|
101
|
+
def set(self, index, value):
|
|
102
|
+
"""Set element at index, mirroring array-style assignment."""
|
|
103
|
+
try:
|
|
104
|
+
idx = index.value if hasattr(index, 'value') else index
|
|
105
|
+
idx = int(idx)
|
|
106
|
+
except Exception as exc:
|
|
107
|
+
raise EvaluationError(f"Invalid index for assignment: {index}") from exc
|
|
108
|
+
|
|
109
|
+
if idx < 0 or idx >= len(self.elements):
|
|
110
|
+
raise EvaluationError(f"Index out of range for List assignment: {idx}")
|
|
111
|
+
|
|
112
|
+
self.elements[idx] = value
|
|
113
|
+
return value
|
|
114
|
+
|
|
101
115
|
def append(self, item):
|
|
102
116
|
"""Append item to list in-place (mutating operation)"""
|
|
103
117
|
self.elements.append(item)
|
|
@@ -122,16 +136,35 @@ class Map(Object):
|
|
|
122
136
|
pairs.append(f"{key_str}: {value_str}")
|
|
123
137
|
return "{" + ", ".join(pairs) + "}"
|
|
124
138
|
|
|
139
|
+
def _normalize_key(self, key):
|
|
140
|
+
if hasattr(key, 'inspect'):
|
|
141
|
+
return key.inspect()
|
|
142
|
+
return str(key)
|
|
143
|
+
|
|
125
144
|
def get(self, key):
|
|
126
|
-
"""Get value by key (compatible with string keys)"""
|
|
127
|
-
|
|
145
|
+
"""Get value by key (compatible with both string and String keys)"""
|
|
146
|
+
# Try direct lookup first (works for String-keyed and plain-keyed maps)
|
|
147
|
+
val = self.pairs.get(key)
|
|
148
|
+
if val is not None:
|
|
149
|
+
return val
|
|
150
|
+
# Try with String object key (for maps created by _python_to_zexus)
|
|
151
|
+
if isinstance(key, str):
|
|
152
|
+
str_key = String(key)
|
|
153
|
+
val = self.pairs.get(str_key)
|
|
154
|
+
if val is not None:
|
|
155
|
+
return val
|
|
156
|
+
# Try with normalized plain string key (for maps with plain string keys)
|
|
157
|
+
norm_key = self._normalize_key(key)
|
|
158
|
+
return self.pairs.get(norm_key)
|
|
128
159
|
|
|
129
160
|
def set(self, key, value):
|
|
130
161
|
"""Set value for key, blocking modification if key is sealed."""
|
|
131
|
-
|
|
162
|
+
norm_key = self._normalize_key(key)
|
|
163
|
+
existing = self.pairs.get(norm_key)
|
|
132
164
|
if existing is not None and existing.__class__.__name__ == 'SealedObject':
|
|
133
165
|
raise EvaluationError(f"Cannot modify sealed map key: {key}")
|
|
134
|
-
self.pairs[
|
|
166
|
+
self.pairs[norm_key] = value
|
|
167
|
+
return value
|
|
135
168
|
|
|
136
169
|
def keys(self):
|
|
137
170
|
"""Return array of map keys"""
|
|
@@ -860,6 +893,18 @@ class Environment:
|
|
|
860
893
|
self.store[name] = val
|
|
861
894
|
self.notify_watchers(name, val)
|
|
862
895
|
return val
|
|
896
|
+
|
|
897
|
+
def clone_for_closure(self):
|
|
898
|
+
"""Create a shallow copy of the environment for closure capture."""
|
|
899
|
+
cloned = Environment(outer=self.outer, persistence_scope=self.persistence_scope)
|
|
900
|
+
cloned.store = dict(self.store)
|
|
901
|
+
cloned.const_vars = set(self.const_vars)
|
|
902
|
+
cloned.exports = dict(self.exports)
|
|
903
|
+
cloned.watchers = {k: list(v) for k, v in self.watchers.items()}
|
|
904
|
+
cloned.debug_mode = self.debug_mode
|
|
905
|
+
cloned._persistent_storage = self._persistent_storage
|
|
906
|
+
cloned._memory_tracker = self._memory_tracker
|
|
907
|
+
return cloned
|
|
863
908
|
|
|
864
909
|
def assign(self, name, val):
|
|
865
910
|
"""Assign to an existing variable in the scope chain, or create in current if not found."""
|
|
@@ -1101,14 +1146,39 @@ class EvaluationError(Object):
|
|
|
1101
1146
|
|
|
1102
1147
|
# Add stack trace if available
|
|
1103
1148
|
if self.stack_trace:
|
|
1104
|
-
|
|
1149
|
+
formatted_trace = []
|
|
1150
|
+
for frame in self.stack_trace[-5:]:
|
|
1151
|
+
if isinstance(frame, str):
|
|
1152
|
+
formatted_trace.append(frame)
|
|
1153
|
+
elif isinstance(frame, tuple) and len(frame) == 2:
|
|
1154
|
+
node_type, line = frame
|
|
1155
|
+
item = f" at {node_type.__name__}"
|
|
1156
|
+
if line:
|
|
1157
|
+
item += f" (line {line})"
|
|
1158
|
+
formatted_trace.append(item)
|
|
1159
|
+
|
|
1160
|
+
trace = "\n".join(formatted_trace)
|
|
1105
1161
|
temp_error.message += f"\n\nStack trace:\n{trace}"
|
|
1106
1162
|
|
|
1107
1163
|
return temp_error.format_error()
|
|
1108
1164
|
except Exception:
|
|
1109
1165
|
# Fallback to simple format if error reporter not available
|
|
1110
1166
|
location = f"Line {self.line}:{self.column}" if self.line and self.column else "Unknown location"
|
|
1111
|
-
|
|
1167
|
+
|
|
1168
|
+
# Format simple trace
|
|
1169
|
+
formatted_trace = []
|
|
1170
|
+
if self.stack_trace:
|
|
1171
|
+
for frame in self.stack_trace[-3:]:
|
|
1172
|
+
if isinstance(frame, str):
|
|
1173
|
+
formatted_trace.append(frame)
|
|
1174
|
+
elif isinstance(frame, tuple) and len(frame) == 2:
|
|
1175
|
+
node_type, line = frame
|
|
1176
|
+
item = f" at {node_type.__name__}"
|
|
1177
|
+
if line:
|
|
1178
|
+
item += f" (line {line})"
|
|
1179
|
+
formatted_trace.append(item)
|
|
1180
|
+
|
|
1181
|
+
trace = "\n".join(formatted_trace)
|
|
1112
1182
|
trace_section = f"\n Stack:\n{trace}" if trace else ""
|
|
1113
1183
|
suggestion_section = f"\n 💡 Suggestion: {self.suggestion}" if self.suggestion else ""
|
|
1114
1184
|
return f"❌ Runtime Error at {location}\n {self.message}{suggestion_section}{trace_section}"
|
|
Binary file
|
|
Binary file
|
|
Binary file
|