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.
Files changed (177) hide show
  1. package/README.md +12 -5
  2. package/package.json +1 -1
  3. package/src/__init__.py +7 -0
  4. package/src/zexus/__init__.py +1 -1
  5. package/src/zexus/__pycache__/__init__.cpython-312.pyc +0 -0
  6. package/src/zexus/__pycache__/capability_system.cpython-312.pyc +0 -0
  7. package/src/zexus/__pycache__/debug_sanitizer.cpython-312.pyc +0 -0
  8. package/src/zexus/__pycache__/environment.cpython-312.pyc +0 -0
  9. package/src/zexus/__pycache__/error_reporter.cpython-312.pyc +0 -0
  10. package/src/zexus/__pycache__/input_validation.cpython-312.pyc +0 -0
  11. package/src/zexus/__pycache__/lexer.cpython-312.pyc +0 -0
  12. package/src/zexus/__pycache__/module_cache.cpython-312.pyc +0 -0
  13. package/src/zexus/__pycache__/module_manager.cpython-312.pyc +0 -0
  14. package/src/zexus/__pycache__/object.cpython-312.pyc +0 -0
  15. package/src/zexus/__pycache__/security.cpython-312.pyc +0 -0
  16. package/src/zexus/__pycache__/security_enforcement.cpython-312.pyc +0 -0
  17. package/src/zexus/__pycache__/syntax_validator.cpython-312.pyc +0 -0
  18. package/src/zexus/__pycache__/zexus_ast.cpython-312.pyc +0 -0
  19. package/src/zexus/__pycache__/zexus_token.cpython-312.pyc +0 -0
  20. package/src/zexus/access_control_system/__pycache__/__init__.cpython-312.pyc +0 -0
  21. package/src/zexus/access_control_system/__pycache__/access_control.cpython-312.pyc +0 -0
  22. package/src/zexus/advanced_types.py +17 -2
  23. package/src/zexus/blockchain/__init__.py +411 -0
  24. package/src/zexus/blockchain/accelerator.py +1160 -0
  25. package/src/zexus/blockchain/chain.py +660 -0
  26. package/src/zexus/blockchain/consensus.py +821 -0
  27. package/src/zexus/blockchain/contract_vm.py +1019 -0
  28. package/src/zexus/blockchain/crypto.py +79 -14
  29. package/src/zexus/blockchain/events.py +526 -0
  30. package/src/zexus/blockchain/loadtest.py +721 -0
  31. package/src/zexus/blockchain/monitoring.py +350 -0
  32. package/src/zexus/blockchain/mpt.py +716 -0
  33. package/src/zexus/blockchain/multichain.py +951 -0
  34. package/src/zexus/blockchain/multiprocess_executor.py +338 -0
  35. package/src/zexus/blockchain/network.py +886 -0
  36. package/src/zexus/blockchain/node.py +666 -0
  37. package/src/zexus/blockchain/rpc.py +1203 -0
  38. package/src/zexus/blockchain/rust_bridge.py +421 -0
  39. package/src/zexus/blockchain/storage.py +423 -0
  40. package/src/zexus/blockchain/tokens.py +750 -0
  41. package/src/zexus/blockchain/upgradeable.py +1004 -0
  42. package/src/zexus/blockchain/verification.py +1602 -0
  43. package/src/zexus/blockchain/wallet.py +621 -0
  44. package/src/zexus/capability_system.py +184 -9
  45. package/src/zexus/cli/__pycache__/main.cpython-312.pyc +0 -0
  46. package/src/zexus/cli/main.py +383 -34
  47. package/src/zexus/cli/zpm.py +1 -1
  48. package/src/zexus/compiler/__pycache__/bytecode.cpython-312.pyc +0 -0
  49. package/src/zexus/compiler/__pycache__/lexer.cpython-312.pyc +0 -0
  50. package/src/zexus/compiler/__pycache__/parser.cpython-312.pyc +0 -0
  51. package/src/zexus/compiler/__pycache__/semantic.cpython-312.pyc +0 -0
  52. package/src/zexus/compiler/__pycache__/zexus_ast.cpython-312.pyc +0 -0
  53. package/src/zexus/compiler/bytecode.py +124 -7
  54. package/src/zexus/compiler/compat_runtime.py +6 -2
  55. package/src/zexus/compiler/lexer.py +16 -5
  56. package/src/zexus/compiler/parser.py +108 -7
  57. package/src/zexus/compiler/semantic.py +18 -19
  58. package/src/zexus/compiler/zexus_ast.py +26 -1
  59. package/src/zexus/concurrency_system.py +79 -0
  60. package/src/zexus/config.py +54 -0
  61. package/src/zexus/crypto_bridge.py +244 -8
  62. package/src/zexus/dap/__init__.py +10 -0
  63. package/src/zexus/dap/__main__.py +4 -0
  64. package/src/zexus/dap/dap_server.py +391 -0
  65. package/src/zexus/dap/debug_engine.py +298 -0
  66. package/src/zexus/environment.py +112 -9
  67. package/src/zexus/evaluator/__pycache__/bytecode_compiler.cpython-312.pyc +0 -0
  68. package/src/zexus/evaluator/__pycache__/core.cpython-312.pyc +0 -0
  69. package/src/zexus/evaluator/__pycache__/expressions.cpython-312.pyc +0 -0
  70. package/src/zexus/evaluator/__pycache__/functions.cpython-312.pyc +0 -0
  71. package/src/zexus/evaluator/__pycache__/resource_limiter.cpython-312.pyc +0 -0
  72. package/src/zexus/evaluator/__pycache__/statements.cpython-312.pyc +0 -0
  73. package/src/zexus/evaluator/__pycache__/unified_execution.cpython-312.pyc +0 -0
  74. package/src/zexus/evaluator/__pycache__/utils.cpython-312.pyc +0 -0
  75. package/src/zexus/evaluator/bytecode_compiler.py +457 -37
  76. package/src/zexus/evaluator/core.py +644 -50
  77. package/src/zexus/evaluator/expressions.py +358 -62
  78. package/src/zexus/evaluator/functions.py +458 -20
  79. package/src/zexus/evaluator/resource_limiter.py +4 -4
  80. package/src/zexus/evaluator/statements.py +774 -122
  81. package/src/zexus/evaluator/unified_execution.py +573 -72
  82. package/src/zexus/evaluator/utils.py +14 -2
  83. package/src/zexus/evaluator_original.py +1 -1
  84. package/src/zexus/event_loop.py +186 -0
  85. package/src/zexus/lexer.py +742 -458
  86. package/src/zexus/lsp/__init__.py +1 -1
  87. package/src/zexus/lsp/definition_provider.py +163 -9
  88. package/src/zexus/lsp/server.py +22 -8
  89. package/src/zexus/lsp/symbol_provider.py +182 -9
  90. package/src/zexus/module_cache.py +239 -9
  91. package/src/zexus/module_manager.py +129 -1
  92. package/src/zexus/object.py +76 -6
  93. package/src/zexus/parser/__pycache__/parser.cpython-312.pyc +0 -0
  94. package/src/zexus/parser/__pycache__/strategy_context.cpython-312.pyc +0 -0
  95. package/src/zexus/parser/__pycache__/strategy_structural.cpython-312.pyc +0 -0
  96. package/src/zexus/parser/parser.py +1349 -408
  97. package/src/zexus/parser/strategy_context.py +755 -58
  98. package/src/zexus/parser/strategy_structural.py +121 -21
  99. package/src/zexus/persistence.py +15 -1
  100. package/src/zexus/renderer/__init__.py +61 -0
  101. package/src/zexus/renderer/__pycache__/__init__.cpython-312.pyc +0 -0
  102. package/src/zexus/renderer/__pycache__/backend.cpython-312.pyc +0 -0
  103. package/src/zexus/renderer/__pycache__/canvas.cpython-312.pyc +0 -0
  104. package/src/zexus/renderer/__pycache__/color_system.cpython-312.pyc +0 -0
  105. package/src/zexus/renderer/__pycache__/layout.cpython-312.pyc +0 -0
  106. package/src/zexus/renderer/__pycache__/main_renderer.cpython-312.pyc +0 -0
  107. package/src/zexus/renderer/__pycache__/painter.cpython-312.pyc +0 -0
  108. package/src/zexus/renderer/backend.py +261 -0
  109. package/src/zexus/renderer/canvas.py +78 -0
  110. package/src/zexus/renderer/color_system.py +201 -0
  111. package/src/zexus/renderer/graphics.py +31 -0
  112. package/src/zexus/renderer/layout.py +222 -0
  113. package/src/zexus/renderer/main_renderer.py +66 -0
  114. package/src/zexus/renderer/painter.py +30 -0
  115. package/src/zexus/renderer/tk_backend.py +208 -0
  116. package/src/zexus/renderer/web_backend.py +260 -0
  117. package/src/zexus/runtime/__init__.py +10 -2
  118. package/src/zexus/runtime/__pycache__/__init__.cpython-312.pyc +0 -0
  119. package/src/zexus/runtime/__pycache__/async_runtime.cpython-312.pyc +0 -0
  120. package/src/zexus/runtime/__pycache__/load_manager.cpython-312.pyc +0 -0
  121. package/src/zexus/runtime/file_flags.py +137 -0
  122. package/src/zexus/runtime/load_manager.py +368 -0
  123. package/src/zexus/safety/__pycache__/__init__.cpython-312.pyc +0 -0
  124. package/src/zexus/safety/__pycache__/memory_safety.cpython-312.pyc +0 -0
  125. package/src/zexus/security.py +424 -34
  126. package/src/zexus/stdlib/fs.py +23 -18
  127. package/src/zexus/stdlib/http.py +289 -186
  128. package/src/zexus/stdlib/sockets.py +207 -163
  129. package/src/zexus/stdlib/websockets.py +282 -0
  130. package/src/zexus/stdlib_integration.py +369 -2
  131. package/src/zexus/strategy_recovery.py +6 -3
  132. package/src/zexus/type_checker.py +423 -0
  133. package/src/zexus/virtual_filesystem.py +189 -2
  134. package/src/zexus/vm/__init__.py +113 -3
  135. package/src/zexus/vm/__pycache__/async_optimizer.cpython-312.pyc +0 -0
  136. package/src/zexus/vm/__pycache__/bytecode.cpython-312.pyc +0 -0
  137. package/src/zexus/vm/__pycache__/bytecode_converter.cpython-312.pyc +0 -0
  138. package/src/zexus/vm/__pycache__/cache.cpython-312.pyc +0 -0
  139. package/src/zexus/vm/__pycache__/compiler.cpython-312.pyc +0 -0
  140. package/src/zexus/vm/__pycache__/gas_metering.cpython-312.pyc +0 -0
  141. package/src/zexus/vm/__pycache__/jit.cpython-312.pyc +0 -0
  142. package/src/zexus/vm/__pycache__/parallel_vm.cpython-312.pyc +0 -0
  143. package/src/zexus/vm/__pycache__/vm.cpython-312.pyc +0 -0
  144. package/src/zexus/vm/async_optimizer.py +80 -6
  145. package/src/zexus/vm/binary_bytecode.py +659 -0
  146. package/src/zexus/vm/bytecode.py +59 -11
  147. package/src/zexus/vm/bytecode_converter.py +26 -12
  148. package/src/zexus/vm/cabi.c +1985 -0
  149. package/src/zexus/vm/cabi.cpython-312-x86_64-linux-gnu.so +0 -0
  150. package/src/zexus/vm/cabi.h +127 -0
  151. package/src/zexus/vm/cache.py +561 -17
  152. package/src/zexus/vm/compiler.py +818 -51
  153. package/src/zexus/vm/fastops.c +15743 -0
  154. package/src/zexus/vm/fastops.cpython-312-x86_64-linux-gnu.so +0 -0
  155. package/src/zexus/vm/fastops.pyx +288 -0
  156. package/src/zexus/vm/gas_metering.py +50 -9
  157. package/src/zexus/vm/jit.py +364 -20
  158. package/src/zexus/vm/native_jit_backend.py +1816 -0
  159. package/src/zexus/vm/native_runtime.cpp +1388 -0
  160. package/src/zexus/vm/native_runtime.cpython-312-x86_64-linux-gnu.so +0 -0
  161. package/src/zexus/vm/optimizer.py +161 -11
  162. package/src/zexus/vm/parallel_vm.py +140 -45
  163. package/src/zexus/vm/peephole_optimizer.py +82 -4
  164. package/src/zexus/vm/profiler.py +38 -18
  165. package/src/zexus/vm/register_allocator.py +16 -5
  166. package/src/zexus/vm/register_vm.py +8 -5
  167. package/src/zexus/vm/vm.py +3581 -531
  168. package/src/zexus/vm/wasm_compiler.py +658 -0
  169. package/src/zexus/zexus_ast.py +137 -11
  170. package/src/zexus/zexus_token.py +16 -5
  171. package/src/zexus/zpm/installer.py +55 -15
  172. package/src/zexus/zpm/package_manager.py +1 -1
  173. package/src/zexus/zpm/registry.py +257 -28
  174. package/src/zexus.egg-info/PKG-INFO +16 -6
  175. package/src/zexus.egg-info/SOURCES.txt +129 -17
  176. package/src/zexus.egg-info/entry_points.txt +1 -0
  177. 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
- _MODULE_CACHE: Dict[str, Environment] = {}
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
- def get_cached_module(module_path: str) -> Optional[Environment]:
17
- """Get a cached module environment if available"""
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
- # Relative to current working directory
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)
@@ -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
- return self.pairs.get(key)
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
- existing = self.pairs.get(key)
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[key] = value
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
- trace = "\n".join(self.stack_trace[-5:])
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
- trace = "\n".join(self.stack_trace[-3:]) if self.stack_trace else ""
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}"