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.
Files changed (159) hide show
  1. package/README.md +3 -3
  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/cli/__pycache__/main.cpython-312.pyc +0 -0
  45. package/src/zexus/cli/main.py +300 -20
  46. package/src/zexus/cli/zpm.py +1 -1
  47. package/src/zexus/compiler/__pycache__/bytecode.cpython-312.pyc +0 -0
  48. package/src/zexus/compiler/__pycache__/lexer.cpython-312.pyc +0 -0
  49. package/src/zexus/compiler/__pycache__/parser.cpython-312.pyc +0 -0
  50. package/src/zexus/compiler/__pycache__/semantic.cpython-312.pyc +0 -0
  51. package/src/zexus/compiler/__pycache__/zexus_ast.cpython-312.pyc +0 -0
  52. package/src/zexus/compiler/lexer.py +10 -5
  53. package/src/zexus/concurrency_system.py +79 -0
  54. package/src/zexus/config.py +54 -0
  55. package/src/zexus/crypto_bridge.py +244 -8
  56. package/src/zexus/dap/__init__.py +10 -0
  57. package/src/zexus/dap/__main__.py +4 -0
  58. package/src/zexus/dap/dap_server.py +391 -0
  59. package/src/zexus/dap/debug_engine.py +298 -0
  60. package/src/zexus/environment.py +10 -1
  61. package/src/zexus/evaluator/__pycache__/bytecode_compiler.cpython-312.pyc +0 -0
  62. package/src/zexus/evaluator/__pycache__/core.cpython-312.pyc +0 -0
  63. package/src/zexus/evaluator/__pycache__/expressions.cpython-312.pyc +0 -0
  64. package/src/zexus/evaluator/__pycache__/functions.cpython-312.pyc +0 -0
  65. package/src/zexus/evaluator/__pycache__/resource_limiter.cpython-312.pyc +0 -0
  66. package/src/zexus/evaluator/__pycache__/statements.cpython-312.pyc +0 -0
  67. package/src/zexus/evaluator/__pycache__/unified_execution.cpython-312.pyc +0 -0
  68. package/src/zexus/evaluator/__pycache__/utils.cpython-312.pyc +0 -0
  69. package/src/zexus/evaluator/bytecode_compiler.py +441 -37
  70. package/src/zexus/evaluator/core.py +560 -49
  71. package/src/zexus/evaluator/expressions.py +122 -49
  72. package/src/zexus/evaluator/functions.py +417 -16
  73. package/src/zexus/evaluator/statements.py +521 -118
  74. package/src/zexus/evaluator/unified_execution.py +573 -72
  75. package/src/zexus/evaluator/utils.py +14 -2
  76. package/src/zexus/event_loop.py +186 -0
  77. package/src/zexus/lexer.py +742 -486
  78. package/src/zexus/lsp/__init__.py +1 -1
  79. package/src/zexus/lsp/definition_provider.py +163 -9
  80. package/src/zexus/lsp/server.py +22 -8
  81. package/src/zexus/lsp/symbol_provider.py +182 -9
  82. package/src/zexus/module_cache.py +237 -9
  83. package/src/zexus/object.py +64 -6
  84. package/src/zexus/parser/__pycache__/parser.cpython-312.pyc +0 -0
  85. package/src/zexus/parser/__pycache__/strategy_context.cpython-312.pyc +0 -0
  86. package/src/zexus/parser/__pycache__/strategy_structural.cpython-312.pyc +0 -0
  87. package/src/zexus/parser/parser.py +786 -285
  88. package/src/zexus/parser/strategy_context.py +407 -66
  89. package/src/zexus/parser/strategy_structural.py +117 -19
  90. package/src/zexus/persistence.py +15 -1
  91. package/src/zexus/renderer/__init__.py +15 -0
  92. package/src/zexus/renderer/__pycache__/__init__.cpython-312.pyc +0 -0
  93. package/src/zexus/renderer/__pycache__/backend.cpython-312.pyc +0 -0
  94. package/src/zexus/renderer/__pycache__/canvas.cpython-312.pyc +0 -0
  95. package/src/zexus/renderer/__pycache__/color_system.cpython-312.pyc +0 -0
  96. package/src/zexus/renderer/__pycache__/layout.cpython-312.pyc +0 -0
  97. package/src/zexus/renderer/__pycache__/main_renderer.cpython-312.pyc +0 -0
  98. package/src/zexus/renderer/__pycache__/painter.cpython-312.pyc +0 -0
  99. package/src/zexus/renderer/tk_backend.py +208 -0
  100. package/src/zexus/renderer/web_backend.py +260 -0
  101. package/src/zexus/runtime/__pycache__/__init__.cpython-312.pyc +0 -0
  102. package/src/zexus/runtime/__pycache__/async_runtime.cpython-312.pyc +0 -0
  103. package/src/zexus/runtime/__pycache__/load_manager.cpython-312.pyc +0 -0
  104. package/src/zexus/runtime/file_flags.py +137 -0
  105. package/src/zexus/safety/__pycache__/__init__.cpython-312.pyc +0 -0
  106. package/src/zexus/safety/__pycache__/memory_safety.cpython-312.pyc +0 -0
  107. package/src/zexus/security.py +424 -34
  108. package/src/zexus/stdlib/fs.py +23 -18
  109. package/src/zexus/stdlib/http.py +289 -186
  110. package/src/zexus/stdlib/sockets.py +207 -163
  111. package/src/zexus/stdlib/websockets.py +282 -0
  112. package/src/zexus/stdlib_integration.py +369 -2
  113. package/src/zexus/strategy_recovery.py +6 -3
  114. package/src/zexus/type_checker.py +423 -0
  115. package/src/zexus/virtual_filesystem.py +189 -2
  116. package/src/zexus/vm/__init__.py +113 -3
  117. package/src/zexus/vm/__pycache__/async_optimizer.cpython-312.pyc +0 -0
  118. package/src/zexus/vm/__pycache__/bytecode.cpython-312.pyc +0 -0
  119. package/src/zexus/vm/__pycache__/bytecode_converter.cpython-312.pyc +0 -0
  120. package/src/zexus/vm/__pycache__/cache.cpython-312.pyc +0 -0
  121. package/src/zexus/vm/__pycache__/compiler.cpython-312.pyc +0 -0
  122. package/src/zexus/vm/__pycache__/gas_metering.cpython-312.pyc +0 -0
  123. package/src/zexus/vm/__pycache__/jit.cpython-312.pyc +0 -0
  124. package/src/zexus/vm/__pycache__/parallel_vm.cpython-312.pyc +0 -0
  125. package/src/zexus/vm/__pycache__/vm.cpython-312.pyc +0 -0
  126. package/src/zexus/vm/async_optimizer.py +14 -1
  127. package/src/zexus/vm/binary_bytecode.py +659 -0
  128. package/src/zexus/vm/bytecode.py +28 -1
  129. package/src/zexus/vm/bytecode_converter.py +26 -12
  130. package/src/zexus/vm/cabi.c +1985 -0
  131. package/src/zexus/vm/cabi.cpython-312-x86_64-linux-gnu.so +0 -0
  132. package/src/zexus/vm/cabi.h +127 -0
  133. package/src/zexus/vm/cache.py +557 -17
  134. package/src/zexus/vm/compiler.py +703 -5
  135. package/src/zexus/vm/fastops.c +15743 -0
  136. package/src/zexus/vm/fastops.cpython-312-x86_64-linux-gnu.so +0 -0
  137. package/src/zexus/vm/fastops.pyx +288 -0
  138. package/src/zexus/vm/gas_metering.py +50 -9
  139. package/src/zexus/vm/jit.py +83 -2
  140. package/src/zexus/vm/native_jit_backend.py +1816 -0
  141. package/src/zexus/vm/native_runtime.cpp +1388 -0
  142. package/src/zexus/vm/native_runtime.cpython-312-x86_64-linux-gnu.so +0 -0
  143. package/src/zexus/vm/optimizer.py +161 -11
  144. package/src/zexus/vm/parallel_vm.py +118 -42
  145. package/src/zexus/vm/peephole_optimizer.py +82 -4
  146. package/src/zexus/vm/profiler.py +38 -18
  147. package/src/zexus/vm/register_allocator.py +16 -5
  148. package/src/zexus/vm/register_vm.py +8 -5
  149. package/src/zexus/vm/vm.py +3411 -573
  150. package/src/zexus/vm/wasm_compiler.py +658 -0
  151. package/src/zexus/zexus_ast.py +63 -11
  152. package/src/zexus/zexus_token.py +13 -5
  153. package/src/zexus/zpm/installer.py +55 -15
  154. package/src/zexus/zpm/package_manager.py +1 -1
  155. package/src/zexus/zpm/registry.py +257 -28
  156. package/src/zexus.egg-info/PKG-INFO +7 -4
  157. package/src/zexus.egg-info/SOURCES.txt +116 -9
  158. package/src/zexus.egg-info/entry_points.txt +1 -0
  159. 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
 
@@ -70,7 +130,11 @@ def get_module_candidates(file_path: str, importer_file: str = None) -> list[str
70
130
  resolved_path = os.path.join(importer_dir, file_path)
71
131
  candidates.append(resolved_path)
72
132
  else:
73
- # 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
74
138
  candidates.append(os.path.join(os.getcwd(), file_path))
75
139
 
76
140
  # Also check zpm_modules directory
@@ -88,4 +152,168 @@ def get_module_candidates(file_path: str, importer_file: str = None) -> list[str
88
152
 
89
153
  def normalize_path(path: str) -> str:
90
154
  """Normalize a path for consistent cache keys"""
91
- 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
@@ -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"""
@@ -1113,14 +1146,39 @@ class EvaluationError(Object):
1113
1146
 
1114
1147
  # Add stack trace if available
1115
1148
  if self.stack_trace:
1116
- 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)
1117
1161
  temp_error.message += f"\n\nStack trace:\n{trace}"
1118
1162
 
1119
1163
  return temp_error.format_error()
1120
1164
  except Exception:
1121
1165
  # Fallback to simple format if error reporter not available
1122
1166
  location = f"Line {self.line}:{self.column}" if self.line and self.column else "Unknown location"
1123
- 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)
1124
1182
  trace_section = f"\n Stack:\n{trace}" if trace else ""
1125
1183
  suggestion_section = f"\n 💡 Suggestion: {self.suggestion}" if self.suggestion else ""
1126
1184
  return f"❌ Runtime Error at {location}\n {self.message}{suggestion_section}{trace_section}"