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
@@ -10,6 +10,20 @@ from .bytecode import Opcode, Bytecode
10
10
  from .. import zexus_ast
11
11
 
12
12
 
13
+ class BytecodeCompilationError(Exception):
14
+ """Raised when bytecode compilation cannot complete successfully."""
15
+
16
+
17
+ class UnsupportedNodeError(BytecodeCompilationError):
18
+ """Raised when the compiler encounters an unsupported AST node."""
19
+
20
+ def __init__(self, node_type: str, detail: Optional[str] = None):
21
+ message = detail or f"Node type '{node_type}' is not supported by the bytecode compiler"
22
+ super().__init__(message)
23
+ self.node_type = node_type
24
+ self.detail = detail
25
+
26
+
13
27
  class BytecodeCompiler:
14
28
  """
15
29
  Compiles Zexus AST to VM bytecode.
@@ -28,6 +42,7 @@ class BytecodeCompiler:
28
42
  self.constant_map = {} # Deduplicate constants
29
43
  self.label_counter = 0
30
44
  self.loop_stack = [] # For break/continue
45
+ self.temp_counter = 0
31
46
 
32
47
  def compile(self, node) -> Bytecode:
33
48
  """Compile AST node to bytecode"""
@@ -35,21 +50,48 @@ class BytecodeCompiler:
35
50
  self.instructions = []
36
51
  self.constant_map = {}
37
52
  self.label_counter = 0
53
+ self.temp_counter = 0
38
54
 
39
55
  self._compile_node(node)
40
56
 
57
+ self._resolve_labels()
58
+
41
59
  return Bytecode(self.instructions, self.constants)
42
60
 
61
+ def _resolve_labels(self):
62
+ """Resolve label strings to instruction indices"""
63
+ labels = {}
64
+
65
+ # 1. Map labels
66
+ for i, (op, operand) in enumerate(self.instructions):
67
+ if op == Opcode.NOP and isinstance(operand, str) and operand.startswith('L'):
68
+ labels[operand] = i
69
+
70
+ # 2. Update jumps
71
+ for i, (op, operand) in enumerate(self.instructions):
72
+ if op in (Opcode.JUMP, Opcode.JUMP_IF_FALSE, Opcode.JUMP_IF_TRUE):
73
+ if isinstance(operand, str) and operand in labels:
74
+ self.instructions[i] = (op, labels[operand])
75
+
43
76
  def _add_constant(self, value) -> int:
44
77
  """Add constant to pool, return index"""
45
- # Deduplicate constants
46
- key = (type(value).__name__, str(value))
47
- if key in self.constant_map:
78
+ # Deduplicate only cheap, stable primitive constants.
79
+ # Avoid calling str(value) for large/complex values (AST nodes, dicts,
80
+ # bytecode specs) because that can be expensive on large programs and
81
+ # can balloon memory.
82
+ key = None
83
+ if value is None or isinstance(value, (bool, int, float, str)):
84
+ if isinstance(value, str) and len(value) > 256:
85
+ key = None
86
+ else:
87
+ key = (type(value), value)
88
+ if key is not None and key in self.constant_map:
48
89
  return self.constant_map[key]
49
90
 
50
91
  idx = len(self.constants)
51
92
  self.constants.append(value)
52
- self.constant_map[key] = idx
93
+ if key is not None:
94
+ self.constant_map[key] = idx
53
95
  return idx
54
96
 
55
97
  def _emit(self, opcode: Opcode, *args):
@@ -66,6 +108,15 @@ class BytecodeCompiler:
66
108
  label = f"L{self.label_counter}"
67
109
  self.label_counter += 1
68
110
  return label
111
+
112
+ def _make_temp_name(self, prefix: str = "tmp") -> str:
113
+ name = f"__{prefix}_{self.temp_counter}"
114
+ self.temp_counter += 1
115
+ return name
116
+
117
+ def _mark_label(self, label: str):
118
+ self._emit(Opcode.NOP)
119
+ self.instructions[-1] = (Opcode.NOP, label)
69
120
 
70
121
  def _compile_node(self, node):
71
122
  """Dispatch to specific compiler method"""
@@ -76,10 +127,39 @@ class BytecodeCompiler:
76
127
  method_name = f'_compile_{node_type}'
77
128
  method = getattr(self, method_name, None)
78
129
 
79
- if method:
130
+ if method is None:
131
+ raise UnsupportedNodeError(node_type, self._unsupported_message(node_type))
132
+
133
+ try:
80
134
  method(node)
81
- else:
82
- raise NotImplementedError(f"Compiler not implemented for {node_type}")
135
+ except UnsupportedNodeError:
136
+ raise
137
+ except Exception as exc:
138
+ raise BytecodeCompilationError(
139
+ f"Failed to compile {node_type}: {exc}"
140
+ ) from exc
141
+
142
+ def _unsupported_message(self, node_type: str) -> str:
143
+ """Return a friendly message for unsupported nodes."""
144
+ hints = {
145
+ "UseStatement": "Module imports",
146
+ "FromStatement": "Module imports",
147
+ "EntityStatement": "Entity declarations",
148
+ "ContractStatement": "Contract declarations",
149
+ "ActionStatement": "Action/function declarations",
150
+ "FunctionStatement": "Function declarations",
151
+ "IfStatement": "If statements",
152
+ "WhileStatement": "While loops",
153
+ "ForStatement": "For loops",
154
+ "ForEachStatement": "For-each loops",
155
+ "TxStatement": "Transaction blocks",
156
+ "RequireStatement": "Require statements",
157
+ "RevertStatement": "Revert statements",
158
+ }
159
+ prefix = hints.get(node_type)
160
+ if prefix:
161
+ return f"{prefix} are not yet supported by the VM bytecode compiler"
162
+ return f"Node type '{node_type}' is not supported by the VM bytecode compiler"
83
163
 
84
164
  # ==================== Program & Statements ====================
85
165
 
@@ -117,7 +197,53 @@ class BytecodeCompiler:
117
197
  self._emit(Opcode.PRINT)
118
198
 
119
199
  def _compile_LetStatement(self, node):
120
- """Compile let/const declaration"""
200
+ """Compile let/const declaration, including destructuring patterns."""
201
+ from ..zexus_ast import DestructurePattern
202
+
203
+ if isinstance(node.name, DestructurePattern):
204
+ # Compile the RHS value expression — leaves it on the stack
205
+ self._compile_node(node.value)
206
+ pattern = node.name
207
+
208
+ if pattern.kind == 'map':
209
+ for source_key, target_name in pattern.bindings:
210
+ # DUP the map on the stack so we can index multiple times
211
+ self._emit(Opcode.DUP)
212
+ # Push the key string
213
+ key_idx = self._add_constant(source_key)
214
+ self._emit(Opcode.LOAD_CONST, key_idx)
215
+ # Index into the map: map[key]
216
+ self._emit(Opcode.INDEX)
217
+ # Store to local name
218
+ name_idx = self._add_constant(target_name)
219
+ self._emit(Opcode.STORE_NAME, name_idx)
220
+ # Pop the original map off the stack
221
+ self._emit(Opcode.POP)
222
+ elif pattern.kind == 'list':
223
+ for idx, target_name in pattern.bindings:
224
+ self._emit(Opcode.DUP)
225
+ idx_const = self._add_constant(idx)
226
+ self._emit(Opcode.LOAD_CONST, idx_const)
227
+ self._emit(Opcode.INDEX)
228
+ name_idx = self._add_constant(target_name)
229
+ self._emit(Opcode.STORE_NAME, name_idx)
230
+ # Handle rest element: ..rest captures remaining items
231
+ if pattern.rest:
232
+ rest_start = len(pattern.bindings)
233
+ # SLICE expects stack: [obj, start, end]
234
+ self._emit(Opcode.DUP) # list
235
+ start_idx = self._add_constant(rest_start)
236
+ self._emit(Opcode.LOAD_CONST, start_idx) # start
237
+ none_idx = self._add_constant(None)
238
+ self._emit(Opcode.LOAD_CONST, none_idx) # end (None = to end)
239
+ self._emit(Opcode.SLICE)
240
+ rest_name_idx = self._add_constant(pattern.rest)
241
+ self._emit(Opcode.STORE_NAME, rest_name_idx)
242
+ # Pop the original list off the stack
243
+ self._emit(Opcode.POP)
244
+ return
245
+
246
+ # Normal (non-destructuring) let/const
121
247
  # Compile value
122
248
  self._compile_node(node.value)
123
249
 
@@ -171,6 +297,30 @@ class BytecodeCompiler:
171
297
  """Load boolean constant"""
172
298
  const_idx = self._add_constant(node.value)
173
299
  self._emit(Opcode.LOAD_CONST, const_idx)
300
+
301
+ def _compile_NullLiteral(self, node):
302
+ """Load null constant"""
303
+ const_idx = self._add_constant(None)
304
+ self._emit(Opcode.LOAD_CONST, const_idx)
305
+
306
+ def _compile_PropertyAccessExpression(self, node):
307
+ """Compile property access"""
308
+ # Compile object
309
+ self._compile_node(node.object)
310
+
311
+ if node.computed:
312
+ # Bracket notation: obj[expr]
313
+ self._compile_node(node.property)
314
+ self._emit(Opcode.INDEX)
315
+ else:
316
+ # Dot notation: obj.prop
317
+ if hasattr(node.property, 'value'):
318
+ name = node.property.value
319
+ else:
320
+ name = str(node.property)
321
+ const_idx = self._add_constant(name)
322
+ self._emit(Opcode.LOAD_CONST, const_idx)
323
+ self._emit(Opcode.GET_ATTR)
174
324
 
175
325
  # ==================== Binary Operations ====================
176
326
 
@@ -247,32 +397,58 @@ class BytecodeCompiler:
247
397
  self._emit(Opcode.NOP)
248
398
  self.instructions[-1] = (Opcode.NOP, end_label)
249
399
 
250
- def _compile_WhileStatement(self, node):
251
- """Compile while loop"""
252
- start_label = self._make_label()
400
+ _compile_IfStatement = _compile_IfExpression
401
+
402
+ def _compile_TernaryExpression(self, node):
403
+ """Compile ternary expression (cond ? true : false)"""
404
+ # Compile condition
405
+ self._compile_node(node.condition)
406
+
407
+ # Jump if false
408
+ else_label = self._make_label()
253
409
  end_label = self._make_label()
254
410
 
255
- # Track loop for break/continue
256
- self.loop_stack.append((start_label, end_label))
411
+ self._emit(Opcode.JUMP_IF_FALSE, else_label)
257
412
 
258
- # Start of loop
259
- self._emit(Opcode.NOP)
260
- self.instructions[-1] = (Opcode.NOP, start_label)
413
+ # True branch
414
+ self._compile_node(node.true_value)
415
+ self._emit(Opcode.JUMP, end_label)
261
416
 
262
- # Compile condition
263
- self._compile_node(node.condition)
264
- self._emit(Opcode.JUMP_IF_FALSE, end_label)
417
+ # False branch
418
+ self._mark_label(else_label)
419
+ self._compile_node(node.false_value)
265
420
 
266
- # Compile body
421
+ self._mark_label(end_label)
422
+
423
+ def _compile_WhileStatement(self, node):
424
+ """Compile while loop"""
425
+ start_label = self._make_label()
426
+ end_label = self._make_label()
427
+ loop_info = {
428
+ 'start': start_label,
429
+ 'end': end_label,
430
+ 'continue': start_label,
431
+ }
432
+ self.loop_stack.append(loop_info)
433
+
434
+ # Start label before evaluating condition
435
+ self._mark_label(start_label)
436
+
437
+ # Condition
438
+ self._compile_node(node.condition)
439
+ exit_jump_idx = len(self.instructions)
440
+ self._emit(Opcode.JUMP_IF_FALSE, None)
441
+
442
+ # Body
267
443
  self._compile_node(node.body)
268
-
269
- # Loop back
444
+
445
+ # Jump back to start
270
446
  self._emit(Opcode.JUMP, start_label)
271
-
272
- # End of loop
273
- self._emit(Opcode.NOP)
274
- self.instructions[-1] = (Opcode.NOP, end_label)
275
-
447
+
448
+ # End label
449
+ self._mark_label(end_label)
450
+ self.instructions[exit_jump_idx] = (Opcode.JUMP_IF_FALSE, end_label)
451
+
276
452
  self.loop_stack.pop()
277
453
 
278
454
  def _compile_ForStatement(self, node):
@@ -283,33 +459,40 @@ class BytecodeCompiler:
283
459
 
284
460
  start_label = self._make_label()
285
461
  end_label = self._make_label()
286
-
287
- self.loop_stack.append((start_label, end_label))
288
-
289
- # Start of loop
290
- self._emit(Opcode.NOP)
291
- self.instructions[-1] = (Opcode.NOP, start_label)
292
-
293
- # Condition
462
+ continue_label = start_label if node.increment is None else self._make_label()
463
+
464
+ self.loop_stack.append({
465
+ 'start': start_label,
466
+ 'end': end_label,
467
+ 'continue': continue_label,
468
+ })
469
+
470
+ # Loop start (condition)
471
+ self._mark_label(start_label)
472
+
473
+ exit_jump_idx = None
294
474
  if node.condition:
295
475
  self._compile_node(node.condition)
296
- self._emit(Opcode.JUMP_IF_FALSE, end_label)
476
+ exit_jump_idx = len(self.instructions)
477
+ self._emit(Opcode.JUMP_IF_FALSE, None)
297
478
 
298
479
  # Body
299
480
  self._compile_node(node.body)
300
-
301
- # Increment
481
+
482
+ # Continue label placed before increment (if present)
302
483
  if node.increment:
484
+ self._mark_label(continue_label)
303
485
  self._compile_node(node.increment)
304
486
  self._emit(Opcode.POP)
305
-
306
- # Loop back
487
+
488
+ # Jump back to condition
307
489
  self._emit(Opcode.JUMP, start_label)
308
-
309
- # End
310
- self._emit(Opcode.NOP)
311
- self.instructions[-1] = (Opcode.NOP, end_label)
312
-
490
+
491
+ # Loop end label
492
+ self._mark_label(end_label)
493
+ if exit_jump_idx is not None:
494
+ self.instructions[exit_jump_idx] = (Opcode.JUMP_IF_FALSE, end_label)
495
+
313
496
  self.loop_stack.pop()
314
497
 
315
498
  # ==================== Function Calls ====================
@@ -360,15 +543,25 @@ class BytecodeCompiler:
360
543
  count = len(node.elements)
361
544
  self._emit(Opcode.BUILD_LIST, count)
362
545
 
546
+ _compile_ListLiteral = _compile_ArrayLiteral
547
+
363
548
  def _compile_MapLiteral(self, node):
364
549
  """Compile map/dictionary literal"""
365
550
  # Compile key-value pairs
366
- for key, value in node.pairs.items():
551
+ pairs = node.pairs or []
552
+
553
+ if isinstance(pairs, dict):
554
+ iterable = pairs.items()
555
+ count = len(pairs)
556
+ else:
557
+ iterable = pairs
558
+ count = len(pairs)
559
+
560
+ for key, value in iterable:
367
561
  self._compile_node(key)
368
562
  self._compile_node(value)
369
563
 
370
564
  # Build map
371
- count = len(node.pairs)
372
565
  self._emit(Opcode.BUILD_MAP, count)
373
566
 
374
567
  def _compile_IndexExpression(self, node):
@@ -376,7 +569,583 @@ class BytecodeCompiler:
376
569
  self._compile_node(node.left)
377
570
  self._compile_node(node.index)
378
571
  self._emit(Opcode.INDEX)
572
+
573
+ def _compile_PropertyAccessExpression(self, node):
574
+ """Compile property access (obj.prop or obj[expr])."""
575
+ self._compile_node(node.object)
576
+
577
+ if getattr(node, "computed", False):
578
+ self._compile_node(node.property)
579
+ else:
580
+ prop_name = node.property.value if hasattr(node.property, 'value') else str(node.property)
581
+ prop_idx = self._add_constant(prop_name)
582
+ self._emit(Opcode.LOAD_CONST, prop_idx)
583
+
584
+ self._emit(Opcode.INDEX)
585
+
586
+ def _compile_SliceExpression(self, node):
587
+ """Compile slice expression: obj[start:end]"""
588
+ self._compile_node(node.object)
589
+
590
+ if node.start is not None:
591
+ self._compile_node(node.start)
592
+ else:
593
+ self._emit(Opcode.LOAD_CONST, self._add_constant(None))
594
+
595
+ if node.end is not None:
596
+ self._compile_node(node.end)
597
+ else:
598
+ self._emit(Opcode.LOAD_CONST, self._add_constant(None))
599
+
600
+ self._emit(Opcode.SLICE)
601
+
602
+ # ==================== Advanced Control Flow & Declarations ====================
603
+
604
+ def _compile_ActionStatement(self, node):
605
+ """Compile action/function definition"""
606
+ # Create a new compiler for the function body
607
+ func_compiler = self.__class__()
608
+
609
+ # Compile body
610
+ func_compiler._compile_node(node.body)
611
+
612
+ # Ensure implicit return (null) if execution flows off the end
613
+ null_idx = func_compiler._add_constant(None)
614
+ func_compiler._emit(Opcode.LOAD_CONST, null_idx)
615
+ func_compiler._emit(Opcode.RETURN)
616
+
617
+ # Get bytecode
618
+ from .bytecode import Bytecode
619
+ func_compiler._resolve_labels()
620
+ func_bytecode = Bytecode(func_compiler.instructions, func_compiler.constants)
621
+
622
+ # Prepare function descriptor
623
+ params = [p.value if hasattr(p, 'value') else str(p) for p in node.parameters]
624
+ func_desc = {
625
+ "bytecode": func_bytecode,
626
+ "params": params,
627
+ "is_async": getattr(node, 'is_async', False),
628
+ "name": node.name.value if hasattr(node.name, 'value') else str(node.name)
629
+ }
630
+
631
+ # Store using STORE_FUNC
632
+ # Operand: (name_idx, func_const_idx)
633
+ func_idx = self._add_constant(func_desc)
634
+ name_idx = self._add_constant(func_desc["name"])
635
+
636
+ self._emit(Opcode.STORE_FUNC, (name_idx, func_idx))
637
+
638
+ # Aliases for other function types
639
+ _compile_FunctionStatement = _compile_ActionStatement
640
+ _compile_PureFunctionStatement = _compile_ActionStatement
641
+
642
+ def _compile_ConstStatement(self, node):
643
+ """Compile const declaration (same as Let for current VM)"""
644
+ self._compile_LetStatement(node)
645
+
646
+ def _compile_FileImportExpression(self, node):
647
+ """Compile file import expression (<<)"""
648
+ self._compile_node(node.filepath)
649
+ self._emit(Opcode.READ)
650
+
651
+ def _compile_AssignmentExpression(self, node):
652
+ """Compile variable or property assignment"""
653
+
654
+ # Check target type
655
+ target = node.name
656
+
657
+ # 1. Variable Assignment (target is Identifier or string)
658
+ if isinstance(target, (zexus_ast.Identifier, str)):
659
+ self._compile_node(node.value)
660
+ self._emit(Opcode.DUP)
661
+ name = target.value if hasattr(target, 'value') else str(target)
662
+ name_idx = self._add_constant(name)
663
+ self._emit(Opcode.STORE_NAME, name_idx)
664
+ return
665
+
666
+ # 2. Property/Index Assignment (target[key] = value, target.prop = value)
667
+ if hasattr(zexus_ast, 'IndexExpression') and isinstance(target, zexus_ast.IndexExpression) or \
668
+ isinstance(target, zexus_ast.PropertyAccessExpression):
669
+ # Transform to obj.set(key, value)
670
+
671
+ # 2a. Compile object
672
+ obj = target.object if isinstance(target, zexus_ast.PropertyAccessExpression) else target.left
673
+ self._compile_node(obj)
674
+
675
+ # 2b. Compile key
676
+ if isinstance(target, zexus_ast.PropertyAccessExpression):
677
+ if target.computed:
678
+ self._compile_node(target.property)
679
+ else:
680
+ if hasattr(target.property, 'value'):
681
+ prop_name = target.property.value
682
+ else:
683
+ prop_name = str(target.property)
684
+ idx = self._add_constant(prop_name)
685
+ self._emit(Opcode.LOAD_CONST, idx)
686
+ else: # IndexExpression
687
+ self._compile_node(target.index)
688
+
689
+ # 2c. Compile value
690
+ self._compile_node(node.value)
691
+
692
+ # 2d. Call set(key, value)
693
+ method_idx = self._add_constant("set")
694
+ self._emit(Opcode.CALL_METHOD, (method_idx, 2))
695
+ return
696
+
697
+ raise UnsupportedNodeError(type(target).__name__, "Assignment target not supported")
698
+
699
+ def _compile_UseStatement(self, node):
700
+ """Compile module import via VM import helper."""
701
+ file_path_attr = getattr(node, 'file_path', None) or getattr(node, 'embedded_ref', None)
702
+ path = file_path_attr.value if hasattr(file_path_attr, 'value') else str(file_path_attr or "")
703
+ alias = node.alias.value if node.alias and hasattr(node.alias, 'value') else (node.alias or "")
704
+
705
+ names_list = []
706
+ if getattr(node, 'names', None):
707
+ for entry in node.names:
708
+ if entry is None:
709
+ continue
710
+ names_list.append(entry.value if hasattr(entry, 'value') else str(entry))
711
+
712
+ spec = {
713
+ "file": path,
714
+ "alias": alias,
715
+ "names": names_list,
716
+ "is_named": bool(getattr(node, 'is_named_import', False)) and len(names_list) > 0,
717
+ }
718
+
719
+ spec_idx = self._add_constant(spec)
720
+ self._emit(Opcode.LOAD_CONST, spec_idx)
721
+ name_idx = self._add_constant("__vm_use_module__")
722
+ self._emit(Opcode.CALL_NAME, (name_idx, 1))
723
+ self._emit(Opcode.POP)
724
+
725
+ def _compile_FromStatement(self, node):
726
+ """Compile from-import statements into VM helper calls."""
727
+ file_path_attr = getattr(node, 'file_path', None) or getattr(node, 'embedded_ref', None)
728
+ file_path = file_path_attr.value if hasattr(file_path_attr, 'value') else str(file_path_attr or "")
729
+ entries = []
730
+ for entry in getattr(node, 'imports', []) or []:
731
+ if isinstance(entry, (list, tuple)):
732
+ base = entry[0] if len(entry) > 0 else None
733
+ alias = entry[1] if len(entry) > 1 else None
734
+ else:
735
+ base = entry
736
+ alias = None
737
+ if base is None:
738
+ continue
739
+ entries.append({
740
+ "name": base.value if hasattr(base, 'value') else str(base),
741
+ "alias": alias.value if hasattr(alias, 'value') else (str(alias) if alias else ""),
742
+ })
743
+
744
+ spec = {
745
+ "file": file_path,
746
+ "imports": entries,
747
+ }
748
+
749
+ spec_idx = self._add_constant(spec)
750
+ self._emit(Opcode.LOAD_CONST, spec_idx)
751
+ name_idx = self._add_constant("__vm_from_module__")
752
+ self._emit(Opcode.CALL_NAME, (name_idx, 1))
753
+ self._emit(Opcode.POP)
754
+
755
+ def _compile_TxStatement(self, node):
756
+ """Compile transaction block with automatic revert on error.
757
+
758
+ Emits:
759
+ TX_BEGIN
760
+ SETUP_TRY catch_label
761
+ <body>
762
+ POP_TRY
763
+ TX_COMMIT
764
+ JUMP end_label
765
+ catch_label:
766
+ POP ; discard exception value
767
+ TX_REVERT
768
+ THROW ; re-raise so callers see the error
769
+ end_label:
770
+ """
771
+ catch_label = self._make_label()
772
+ end_label = self._make_label()
773
+
774
+ self._emit(Opcode.TX_BEGIN)
775
+ self._emit(Opcode.SETUP_TRY, catch_label)
776
+ self._compile_node(node.body)
777
+ self._emit(Opcode.POP_TRY)
778
+ self._emit(Opcode.TX_COMMIT)
779
+ self._emit(Opcode.JUMP, end_label)
780
+
781
+ # Exception handler — revert and re-raise
782
+ self._mark_label(catch_label)
783
+ self._emit(Opcode.TX_REVERT)
784
+ self._emit(Opcode.THROW)
785
+
786
+ self._mark_label(end_label)
787
+
788
+ def _compile_GCStatement(self, node):
789
+ """Compile GC statement"""
790
+ # Call builtin gc(action)
791
+ action = node.action.value if hasattr(node.action, 'value') else str(node.action)
792
+
793
+ # Load 'gc' function name
794
+ gc_idx = self._add_constant("gc")
795
+
796
+ # Load action argument
797
+ action_idx = self._add_constant(action)
798
+ self._emit(Opcode.LOAD_CONST, action_idx)
799
+
800
+ # Call gc(action)
801
+ self._emit(Opcode.CALL_NAME, (gc_idx, 1))
802
+ self._emit(Opcode.POP)
803
+
379
804
 
805
+ def _compile_ContractStatement(self, node):
806
+ """Compile smart contract definition into bytecode that creates a real
807
+ SmartContract object at runtime.
808
+
809
+ Stack layout at DEFINE_CONTRACT execution (top → bottom):
810
+ contract_name
811
+ value_N, name_N ← last storage var
812
+ ...
813
+ value_1, name_1 ← first storage var
814
+
815
+ The AST node is stored in the constants pool so the VM can create
816
+ Action objects (which hold the AST body + closure environment) and
817
+ access storage_vars metadata for SmartContract construction.
818
+ """
819
+ contract_name = node.name.value
820
+
821
+ # Store the full AST node in constants so the VM has access to
822
+ # action definitions (parameters, body) and storage_vars metadata.
823
+ ast_idx = self._add_constant(node)
824
+
825
+ # Compile storage initial values to bytecode.
826
+ # Each var pushes (name, value) onto the stack.
827
+ storage_vars = getattr(node, 'storage_vars', [])
828
+ for sv in storage_vars:
829
+ # Push var name
830
+ var_name = sv.name.value if hasattr(sv, 'name') and hasattr(sv.name, 'value') else str(getattr(sv, 'name', ''))
831
+ self._emit(Opcode.LOAD_CONST, self._add_constant(var_name))
832
+ # Push initial value (or None)
833
+ if getattr(sv, 'initial_value', None):
834
+ self._compile_node(sv.initial_value)
835
+ else:
836
+ self._emit(Opcode.LOAD_CONST, self._add_constant(None))
837
+
838
+ # Push contract name
839
+ name_idx = self._add_constant(contract_name)
840
+ self._emit(Opcode.LOAD_CONST, name_idx)
841
+
842
+ # Emit DEFINE_CONTRACT with (ast_idx, storage_count) so the VM can
843
+ # reconstruct the full SmartContract.
844
+ self._emit(Opcode.DEFINE_CONTRACT, (ast_idx, len(storage_vars)))
845
+
846
+ # Store the contract in the environment
847
+ self._emit(Opcode.STORE_NAME, name_idx)
848
+
849
+ def _compile_BreakStatement(self, node):
850
+ """Compile break statement"""
851
+ if not self.loop_stack:
852
+ raise BytecodeCompilationError("Break statement outside loop")
853
+ self._emit(Opcode.JUMP, self.loop_stack[-1]['end'])
854
+
855
+ def _compile_ContinueStatement(self, node):
856
+ """Compile continue statement
857
+
858
+ Behavior:
859
+ - If inside a loop: Jump to loop start (next iteration).
860
+ - If outside a loop: Enable 'Continue On Error' mode globally.
861
+ """
862
+ if self.loop_stack:
863
+ # Inside loop: Standard control flow
864
+ self._emit(Opcode.JUMP, self.loop_stack[-1]['continue'])
865
+ else:
866
+ # Outside loop: Enable error resilience
867
+ self._emit(Opcode.ENABLE_ERROR_MODE)
868
+
869
+ def _compile_RequireStatement(self, node):
870
+ """Compile require(condition, message)"""
871
+ self._compile_node(node.condition)
872
+
873
+ if node.message:
874
+ self._compile_node(node.message)
875
+ else:
876
+ self._emit(Opcode.LOAD_CONST, self._add_constant("Requirement failed"))
877
+
878
+ self._emit(Opcode.REQUIRE)
879
+
880
+ def _compile_RevertStatement(self, node):
881
+ """Compile revert(reason)"""
882
+ if node.reason:
883
+ self._compile_node(node.reason)
884
+ else:
885
+ self._emit(Opcode.LOAD_CONST, self._add_constant("Transaction reverted"))
886
+
887
+ self._emit(Opcode.TX_REVERT)
888
+
889
+ def _compile_TryCatchStatement(self, node):
890
+ """Compile try-catch block"""
891
+ catch_label = self._make_label()
892
+ end_label = self._make_label()
893
+
894
+ # SETUP_TRY catch_label
895
+ # (Assuming SETUP_TRY takes a jump target)
896
+ # Note: Opcode must handle jump target resolution.
897
+ # Typically JUMP opcodes use labels. Here we treat SETUP_TRY like a jump-setup.
898
+ self._emit(Opcode.SETUP_TRY, catch_label)
899
+
900
+ self._compile_node(node.try_block)
901
+
902
+ self._emit(Opcode.POP_TRY)
903
+ self._emit(Opcode.JUMP, end_label)
904
+
905
+ # Catch block
906
+ self._mark_label(catch_label)
907
+
908
+ # Exception provided on stack?
909
+ if node.error_variable:
910
+ self._emit(Opcode.STORE_NAME, self._add_constant(node.error_variable.value))
911
+ else:
912
+ self._emit(Opcode.POP)
913
+
914
+ self._compile_node(node.catch_block)
915
+
916
+ self._mark_label(end_label)
917
+
918
+ def _unsupported_message(self, node_type):
919
+ """Helper for unsupported messages"""
920
+ return f"Node type '{node_type}' is not currently supported by the bytecode compiler."
921
+
922
+ def _compile_AwaitExpression(self, node):
923
+ """Compile await expression"""
924
+ self._compile_node(node.argument)
925
+ self._emit(Opcode.AWAIT)
926
+
927
+ def _compile_ThisExpression(self, node):
928
+ """Compile 'this' expression"""
929
+ # Load 'this' from environment (it's passed as implicit argument in methods)
930
+ name_idx = self._add_constant("this")
931
+ self._emit(Opcode.LOAD_NAME, name_idx)
932
+
933
+ def _compile_EntityStatement(self, node):
934
+ """Compile Entity definition"""
935
+ # Similar to Contract, but uses DEFINE_ENTITY
936
+ entity_name = node.name.value
937
+ name_idx = self._add_constant(entity_name)
938
+
939
+ # Push name
940
+ self._emit(Opcode.LOAD_CONST, name_idx)
941
+
942
+ # Compile body members
943
+ member_count = 0
944
+ if hasattr(node.body, 'statements'):
945
+ for stmt in node.body.statements:
946
+ stmt_type = type(stmt).__name__
947
+ # Reuse state/action compilation
948
+ if stmt_type in ('StateStatement', 'ActionStatement'):
949
+ if stmt_type == 'StateStatement':
950
+ # Value
951
+ if getattr(stmt, 'initial_value', None):
952
+ self._compile_node(stmt.initial_value)
953
+ else:
954
+ self._emit(Opcode.LOAD_CONST, self._add_constant(None))
955
+ # Name
956
+ self._emit(Opcode.LOAD_CONST, self._add_constant(stmt.name.value))
957
+ elif stmt_type == 'ActionStatement':
958
+ self._compile_node(stmt)
959
+ self._emit(Opcode.LOAD_NAME, self._add_constant(stmt.name.value))
960
+ self._emit(Opcode.LOAD_CONST, self._add_constant(stmt.name.value))
961
+
962
+ member_count += 1
963
+
964
+ self._emit(Opcode.DEFINE_ENTITY, member_count)
965
+ self._emit(Opcode.STORE_NAME, name_idx)
966
+
967
+ def _compile_DataStatement(self, node):
968
+ """Compile Data/Dataclass definition"""
969
+ # Data objects are simpler entities
970
+ # For now, map to Entity logic or simple Struct
971
+ # We'll use DEFINE_ENTITY for now to keep it uniform
972
+
973
+ data_name = node.name.value
974
+ name_idx = self._add_constant(data_name)
975
+ self._emit(Opcode.LOAD_CONST, name_idx)
976
+
977
+ member_count = 0
978
+ if node.fields:
979
+ for field in node.fields:
980
+ # Default value
981
+ if field.default_value:
982
+ self._compile_node(field.default_value)
983
+ else:
984
+ self._emit(Opcode.LOAD_CONST, self._add_constant(None))
985
+
986
+ # Field name
987
+ field_name = field.name.value if hasattr(field.name, 'value') else str(field.name)
988
+ self._emit(Opcode.LOAD_CONST, self._add_constant(field_name))
989
+ member_count += 1
990
+
991
+ self._emit(Opcode.DEFINE_ENTITY, member_count)
992
+ self._emit(Opcode.STORE_NAME, name_idx)
993
+
994
+ def _compile_CapabilityStatement(self, node):
995
+ """Compile capability definition"""
996
+ # Load definition map
997
+ if node.definition:
998
+ self._compile_node(node.definition)
999
+ else:
1000
+ self._emit(Opcode.LOAD_CONST, self._add_constant(None))
1001
+
1002
+ # Load name
1003
+ name = node.name.value if hasattr(node.name, 'value') else str(node.name)
1004
+ self._emit(Opcode.LOAD_CONST, self._add_constant(name))
1005
+
1006
+ self._emit(Opcode.DEFINE_CAPABILITY)
1007
+
1008
+ def _compile_GrantStatement(self, node):
1009
+ """Compile grant capability"""
1010
+ # Logic: Push Entity (or name), Push Caps... Emit GRANT
1011
+
1012
+ # Push entity name (string)
1013
+ entity = node.entity_name.value if hasattr(node.entity_name, 'value') else str(node.entity_name)
1014
+ self._emit(Opcode.LOAD_CONST, self._add_constant(entity))
1015
+
1016
+ # Traverse capabilities
1017
+ count = 0
1018
+ for cap in node.capabilities:
1019
+ cap_name = cap.value if hasattr(cap, 'value') else str(cap)
1020
+ self._emit(Opcode.LOAD_CONST, self._add_constant(cap_name))
1021
+ count += 1
1022
+
1023
+ self._emit(Opcode.GRANT_CAPABILITY, count)
1024
+
1025
+ def _compile_RevokeStatement(self, node):
1026
+ """Compile revoke capability"""
1027
+ # Push entity name
1028
+ entity = node.entity_name.value if hasattr(node.entity_name, 'value') else str(node.entity_name)
1029
+ self._emit(Opcode.LOAD_CONST, self._add_constant(entity))
1030
+
1031
+ # Traverse capabilities
1032
+ count = 0
1033
+ for cap in node.capabilities:
1034
+ cap_name = cap.value if hasattr(cap, 'value') else str(cap)
1035
+ self._emit(Opcode.LOAD_CONST, self._add_constant(cap_name))
1036
+ count += 1
1037
+
1038
+ self._emit(Opcode.REVOKE_CAPABILITY, count)
1039
+
1040
+ def _compile_AuditStatement(self, node):
1041
+ """Compile audit log"""
1042
+ # AuditStatement(data_name, action_type, timestamp)
1043
+
1044
+ # Load data name (variable being audited)
1045
+ data_name = node.data_name.value if hasattr(node.data_name, 'value') else str(node.data_name)
1046
+ self._emit(Opcode.LOAD_CONST, self._add_constant(data_name))
1047
+
1048
+ # Load action type
1049
+ if hasattr(node.action_type, 'value'):
1050
+ self._emit(Opcode.LOAD_CONST, self._add_constant(node.action_type.value))
1051
+ else:
1052
+ self._emit(Opcode.LOAD_CONST, self._add_constant(str(node.action_type)))
1053
+
1054
+ # Load timestamp (if any)
1055
+ if node.timestamp:
1056
+ self._compile_node(node.timestamp)
1057
+ else:
1058
+ self._emit(Opcode.LOAD_CONST, self._add_constant(None))
1059
+
1060
+ self._emit(Opcode.AUDIT_LOG)
1061
+
1062
+ def _compile_RestrictStatement(self, node):
1063
+ """Compile restrict access"""
1064
+ # RestrictStatement(target, restriction_type)
1065
+ # target is PropertyAccessExpression usually (obj.field) or Identifier
1066
+
1067
+ if hasattr(node.target, 'object'):
1068
+ # Property access: obj.field
1069
+ self._compile_node(node.target.object) # Push object
1070
+
1071
+ # Helper to get property name
1072
+ prop_name = node.target.property.value if hasattr(node.target.property, 'value') else str(node.target.property)
1073
+ self._emit(Opcode.LOAD_CONST, self._add_constant(prop_name))
1074
+ else:
1075
+ # Just identifier
1076
+ self._emit(Opcode.LOAD_NAME, self._add_constant(node.target.value))
1077
+ self._emit(Opcode.LOAD_CONST, self._add_constant(None)) # No property
1078
+
1079
+ # Load restriction string
1080
+ res_type = node.restriction_type if isinstance(node.restriction_type, str) else str(node.restriction_type)
1081
+ self._emit(Opcode.LOAD_CONST, self._add_constant(res_type))
1082
+
1083
+ self._emit(Opcode.RESTRICT_ACCESS)
1084
+
1085
+
1086
+ def _compile_PatternStatement(self, node):
1087
+ """Compile pattern matching (match statement)"""
1088
+ # Logic:
1089
+ # evaluate value -> store in temp
1090
+ # for each case:
1091
+ # load value
1092
+ # evaluate pattern
1093
+ # check match (simulated EQ)
1094
+ # jump_if_false -> next_case
1095
+ # execute action
1096
+ # jump -> end
1097
+
1098
+ # Evaluate match subject
1099
+ self._compile_node(node.value if hasattr(node, 'value') else node.expression)
1100
+
1101
+ end_label = self._make_label()
1102
+
1103
+ # Iterate cases
1104
+ for case in node.cases:
1105
+ next_case_lbl = self._make_label()
1106
+
1107
+ # Duplicate Subject (Stack: [..., subj, subj])
1108
+ self._emit(Opcode.DUP)
1109
+
1110
+ # Evaluate Pattern
1111
+ # Note: AST might call it 'pattern' or something else depending on Case node type
1112
+ pattern_node = getattr(case, 'pattern', None)
1113
+ if pattern_node:
1114
+ # LiteralPattern holds a literal node in .value
1115
+ if isinstance(pattern_node, zexus_ast.LiteralPattern):
1116
+ self._compile_node(pattern_node.value)
1117
+ # Wildcard/Variable patterns always match; skip pattern compilation
1118
+ elif isinstance(pattern_node, (zexus_ast.WildcardPattern, zexus_ast.VariablePattern)):
1119
+ pass
1120
+ else:
1121
+ self._compile_node(pattern_node)
1122
+
1123
+ # Equality check
1124
+ self._emit(Opcode.EQ)
1125
+
1126
+ # Jump if False
1127
+ self._emit(Opcode.JUMP_IF_FALSE, next_case_lbl)
1128
+
1129
+ # Match!
1130
+ # Pop subject
1131
+ self._emit(Opcode.POP)
1132
+
1133
+ # Execute Action/Consequence
1134
+ # MatchExpression uses 'consequence', PatternStatement uses 'action', MatchCase uses 'result'
1135
+ body = getattr(case, 'consequence', getattr(case, 'action', getattr(case, 'result', None)))
1136
+ self._compile_node(body)
1137
+ self._emit(Opcode.JUMP, end_label)
1138
+
1139
+ # Next Case
1140
+ self._mark_label(next_case_lbl)
1141
+
1142
+ # No match? Pop subject
1143
+ self._emit(Opcode.POP)
1144
+ self._mark_label(end_label)
1145
+
1146
+ # Alias MatchExpression to logic (it's similar enough for basic cases)
1147
+ _compile_MatchExpression = _compile_PatternStatement
1148
+
380
1149
  # ==================== Fallback for unsupported nodes ====================
381
1150
 
382
1151
  def __getattr__(self, name):
@@ -384,10 +1153,8 @@ class BytecodeCompiler:
384
1153
  if name.startswith('_compile_'):
385
1154
  # Return a stub that logs and continues
386
1155
  def stub(node):
387
- print(f"⚠️ Bytecode compiler: {name[9:]} not yet implemented, skipping")
388
- # Push null as placeholder
389
- null_idx = self._add_constant(None)
390
- self._emit(Opcode.LOAD_CONST, null_idx)
1156
+ node_type = name[9:]
1157
+ raise UnsupportedNodeError(node_type, self._unsupported_message(node_type))
391
1158
  return stub
392
1159
  raise AttributeError(name)
393
1160