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
@@ -1,6 +1,7 @@
1
1
  ## src/zexus/parser.py
2
2
  import tempfile
3
3
  import os
4
+ import sys
4
5
  from ..zexus_token import *
5
6
  from ..lexer import Lexer
6
7
  from ..zexus_ast import *
@@ -19,12 +20,19 @@ LOWEST, TERNARY, ASSIGN_PREC, NULLISH_PREC, LOGICAL, EQUALS, LESSGREATER, SUM, P
19
20
  precedences = {
20
21
  QUESTION: TERNARY, # condition ? true : false (very low precedence)
21
22
  ASSIGN: ASSIGN_PREC,
23
+ PLUS_ASSIGN: ASSIGN_PREC,
24
+ MINUS_ASSIGN: ASSIGN_PREC,
25
+ STAR_ASSIGN: ASSIGN_PREC,
26
+ SLASH_ASSIGN: ASSIGN_PREC,
27
+ MOD_ASSIGN: ASSIGN_PREC,
28
+ POWER_ASSIGN: ASSIGN_PREC,
22
29
  NULLISH: NULLISH_PREC, # value ?? default
23
30
  OR: LOGICAL, AND: LOGICAL,
24
31
  EQ: EQUALS, NOT_EQ: EQUALS,
25
32
  LT: LESSGREATER, GT: LESSGREATER, LTE: LESSGREATER, GTE: LESSGREATER,
26
33
  PLUS: SUM, MINUS: SUM,
27
34
  SLASH: PRODUCT, STAR: PRODUCT, MOD: PRODUCT,
35
+ POWER: PREFIX, # ** has higher precedence than * and /
28
36
  LPAREN: CALL,
29
37
  LBRACKET: CALL,
30
38
  LBRACE: CALL, # Entity{...} constructor syntax
@@ -59,12 +67,89 @@ class UltimateParser:
59
67
  else:
60
68
  self.use_advanced_parsing = False
61
69
 
70
+ # Statement dispatch table (O(1) lookup replacing if/elif chain)
71
+ self._statement_dispatch = {
72
+ LET: self.parse_let_statement,
73
+ CONST: self.parse_const_statement,
74
+ DATA: self.parse_data_statement,
75
+ RETURN: self.parse_return_statement,
76
+ CONTINUE: self.parse_continue_statement,
77
+ BREAK: self.parse_break_statement,
78
+ THROW: self.parse_throw_statement,
79
+ PRINT: self.parse_print_statement,
80
+ FOR: self.parse_for_each_statement,
81
+ SCREEN: self.parse_screen_statement,
82
+ COLOR: self.parse_color_statement,
83
+ CANVAS: self.parse_canvas_statement,
84
+ GRAPHICS: self.parse_graphics_statement,
85
+ ANIMATION: self.parse_animation_statement,
86
+ CLOCK: self.parse_clock_statement,
87
+ ACTION: self.parse_action_statement,
88
+ FUNCTION: self.parse_function_statement,
89
+ IF: self.parse_if_statement,
90
+ WHILE: self.parse_while_statement,
91
+ USE: self.parse_use_statement,
92
+ EXACTLY: self.parse_exactly_statement,
93
+ EXPORT: self.parse_export_statement,
94
+ DEBUG: self.parse_debug_statement,
95
+ TRY: self.parse_try_catch_statement,
96
+ EXTERNAL: self.parse_external_declaration,
97
+ ENTITY: self.parse_entity_statement,
98
+ VERIFY: self.parse_verify_statement,
99
+ CONTRACT: self.parse_contract_statement,
100
+ PROTECT: self.parse_protect_statement,
101
+ SEAL: self.parse_seal_statement,
102
+ AUDIT: self.parse_audit_statement,
103
+ RESTRICT: self.parse_restrict_statement,
104
+ SANDBOX: self.parse_sandbox_statement,
105
+ TRAIL: self.parse_trail_statement,
106
+ TX: self.parse_tx_statement,
107
+ NATIVE: self.parse_native_statement,
108
+ GC: self.parse_gc_statement,
109
+ INLINE: self.parse_inline_statement,
110
+ BUFFER: self.parse_buffer_statement,
111
+ SIMD: self.parse_simd_statement,
112
+ DEFER: self.parse_defer_statement,
113
+ PATTERN: self.parse_pattern_statement,
114
+ ENUM: self.parse_enum_statement,
115
+ STREAM: self.parse_stream_statement,
116
+ WATCH: self.parse_watch_statement,
117
+ EMIT: self.parse_emit_statement,
118
+ MODIFIER: self.parse_modifier_declaration,
119
+ # Security statements
120
+ CAPABILITY: self.parse_capability_statement,
121
+ GRANT: self.parse_grant_statement,
122
+ REVOKE: self.parse_revoke_statement,
123
+ VALIDATE: self.parse_validate_statement,
124
+ SANITIZE: self.parse_sanitize_statement,
125
+ INJECT: self.parse_inject_statement,
126
+ IMMUTABLE: self.parse_immutable_statement,
127
+ # Complexity statements
128
+ INTERFACE: self.parse_interface_statement,
129
+ TYPE_ALIAS: self.parse_type_alias_statement,
130
+ MODULE: self.parse_module_statement,
131
+ PACKAGE: self.parse_package_statement,
132
+ USING: self.parse_using_statement,
133
+ CHANNEL: self.parse_channel_statement,
134
+ SEND: self.parse_send_statement,
135
+ RECEIVE: self.parse_receive_statement,
136
+ ATOMIC: self.parse_atomic_statement,
137
+ # Blockchain statements
138
+ LEDGER: self.parse_ledger_statement,
139
+ STATE: self.parse_state_statement,
140
+ REQUIRE: self.parse_require_statement,
141
+ REVERT: self.parse_revert_statement,
142
+ LIMIT: self.parse_limit_statement,
143
+ }
144
+
62
145
  # Traditional parser setup (fallback)
63
146
  self.prefix_parse_fns = {
64
147
  IDENT: self.parse_identifier,
148
+ EVENT: self.parse_identifier,
65
149
  INT: self.parse_integer_literal,
66
150
  FLOAT: self.parse_float_literal,
67
151
  STRING: self.parse_string_literal,
152
+ INTERP_STRING: self.parse_interpolated_string,
68
153
  BANG: self.parse_prefix_expression,
69
154
  MINUS: self.parse_prefix_expression,
70
155
  TRUE: self.parse_boolean,
@@ -83,7 +168,11 @@ class UltimateParser:
83
168
  TRY: self.parse_try_catch_statement,
84
169
  EXTERNAL: self.parse_external_declaration,
85
170
  ASYNC: self.parse_async_expression, # Support async <expression>
171
+ AWAIT: self.parse_await_expression, # Support await <expression>
86
172
  SANITIZE: self.parse_sanitize_expression, # FIX #4: Support sanitize as expression
173
+ FIND: self.parse_find_expression,
174
+ LOAD: self.parse_load_expression,
175
+ MATCH: self.parse_match_expression,
87
176
  }
88
177
  self.infix_parse_fns = {
89
178
  PLUS: self.parse_infix_expression,
@@ -91,6 +180,7 @@ class UltimateParser:
91
180
  SLASH: self.parse_infix_expression,
92
181
  STAR: self.parse_infix_expression,
93
182
  MOD: self.parse_infix_expression,
183
+ POWER: self.parse_infix_expression,
94
184
  EQ: self.parse_infix_expression,
95
185
  NOT_EQ: self.parse_infix_expression,
96
186
  LT: self.parse_infix_expression,
@@ -102,6 +192,12 @@ class UltimateParser:
102
192
  QUESTION: self.parse_ternary_expression, # condition ? true : false
103
193
  NULLISH: self.parse_nullish_expression, # value ?? default
104
194
  ASSIGN: self.parse_assignment_expression,
195
+ PLUS_ASSIGN: self.parse_compound_assignment_expression,
196
+ MINUS_ASSIGN: self.parse_compound_assignment_expression,
197
+ STAR_ASSIGN: self.parse_compound_assignment_expression,
198
+ SLASH_ASSIGN: self.parse_compound_assignment_expression,
199
+ MOD_ASSIGN: self.parse_compound_assignment_expression,
200
+ POWER_ASSIGN: self.parse_compound_assignment_expression,
105
201
  LAMBDA: self.parse_lambda_infix, # support arrow-style lambdas: params => body
106
202
  LPAREN: self.parse_call_expression,
107
203
  LBRACE: self.parse_constructor_call_expression, # Entity{field: value} syntax
@@ -111,6 +207,71 @@ class UltimateParser:
111
207
  self.next_token()
112
208
  self.next_token()
113
209
 
210
+ def _snapshot_lexer_state(self):
211
+ """Capture the lexer's mutable state so it can be restored after lookahead."""
212
+ lex = self.lexer
213
+ return (
214
+ lex.position,
215
+ lex.read_position,
216
+ lex.ch,
217
+ lex.line,
218
+ lex.column,
219
+ lex.last_token_type,
220
+ getattr(lex, 'at_statement_boundary', True),
221
+ getattr(lex, 'paren_depth', 0),
222
+ getattr(lex, 'bracket_depth', 0),
223
+ getattr(lex, 'brace_depth', 0),
224
+ )
225
+
226
+ def _restore_lexer_state(self, snapshot):
227
+ """Restore the lexer's mutable state captured via _snapshot_lexer_state."""
228
+ if not snapshot:
229
+ return
230
+ (
231
+ self.lexer.position,
232
+ self.lexer.read_position,
233
+ self.lexer.ch,
234
+ self.lexer.line,
235
+ self.lexer.column,
236
+ self.lexer.last_token_type,
237
+ boundary,
238
+ paren_depth,
239
+ bracket_depth,
240
+ brace_depth,
241
+ ) = snapshot
242
+ if hasattr(self.lexer, 'at_statement_boundary'):
243
+ self.lexer.at_statement_boundary = boundary
244
+ if hasattr(self.lexer, 'paren_depth'):
245
+ self.lexer.paren_depth = paren_depth
246
+ if hasattr(self.lexer, 'bracket_depth'):
247
+ self.lexer.bracket_depth = bracket_depth
248
+ if hasattr(self.lexer, 'brace_depth'):
249
+ self.lexer.brace_depth = brace_depth
250
+
251
+ # ------------------------------------------------------------------
252
+ # Legacy compatibility helpers
253
+ # ------------------------------------------------------------------
254
+
255
+ def parse(self, *, raise_on_error: bool = False):
256
+ """Backward compatible entrypoint returning the parsed program.
257
+
258
+ Args:
259
+ raise_on_error: When True, raise the first parse error instead of
260
+ returning a program with errors collected in ``self.errors``.
261
+
262
+ Returns:
263
+ Program AST node produced by :meth:`parse_program`.
264
+ """
265
+ program = self.parse_program()
266
+
267
+ if raise_on_error and self.errors:
268
+ first_error = self.errors[0]
269
+ if isinstance(first_error, Exception):
270
+ raise first_error
271
+ raise self._create_parse_error(str(first_error))
272
+
273
+ return program
274
+
114
275
  def _log(self, message, level="normal"):
115
276
  """Controlled logging based on config"""
116
277
  if not config.enable_debug_logs:
@@ -152,6 +313,13 @@ class UltimateParser:
152
313
  if not self.use_advanced_parsing:
153
314
  return self._parse_traditional()
154
315
 
316
+ # Large-file stability: advanced parsing performs whole-file analysis
317
+ # that can become expensive on very large sources. For long files, fall
318
+ # back to the traditional streaming parser which is more predictable.
319
+ if self._should_disable_advanced_for_size():
320
+ self.use_advanced_parsing = False
321
+ return self._parse_traditional()
322
+
155
323
  try:
156
324
  # OPTIMIZATION: Check if we already have tokens cached
157
325
  if not hasattr(self, '_cached_tokens'):
@@ -159,6 +327,46 @@ class UltimateParser:
159
327
 
160
328
  all_tokens = self._cached_tokens
161
329
 
330
+ # If the token stream is huge, prefer the traditional parser to avoid
331
+ # heavy multi-pass analysis and large intermediate structures.
332
+ try:
333
+ max_tokens = getattr(config, 'advanced_parsing_max_tokens', 50000)
334
+ if isinstance(max_tokens, int) and max_tokens > 0 and len(all_tokens) > max_tokens:
335
+ self.use_advanced_parsing = False
336
+ return self._parse_traditional()
337
+ except Exception:
338
+ pass
339
+
340
+ # Arrow lambdas currently parse reliably via the traditional engine.
341
+ # When the token stream contains the '=>' literal OUTSIDE of a match
342
+ # block, switch to the classic parser to keep AST output deterministic.
343
+ # Match blocks use '=>' for case arms (e.g. 42 => "answer") so we
344
+ # must NOT bail out when arrows only appear inside match bodies.
345
+ has_non_match_arrow = False
346
+ in_match_brace = False
347
+ match_brace_depth = 0
348
+ for idx, t in enumerate(all_tokens):
349
+ if t.type == MATCH:
350
+ # Look ahead for opening brace
351
+ for k in range(idx + 1, min(idx + 10, len(all_tokens))):
352
+ if all_tokens[k].type == LBRACE:
353
+ in_match_brace = True
354
+ match_brace_depth = 1
355
+ break
356
+ elif in_match_brace:
357
+ if t.type == LBRACE:
358
+ match_brace_depth += 1
359
+ elif t.type == RBRACE:
360
+ match_brace_depth -= 1
361
+ if match_brace_depth == 0:
362
+ in_match_brace = False
363
+ elif t.type == LAMBDA and getattr(t, 'literal', None) == '=>':
364
+ has_non_match_arrow = True
365
+ break
366
+ if has_non_match_arrow:
367
+ self.use_advanced_parsing = False
368
+ return self._parse_traditional()
369
+
162
370
  # OPTIMIZATION: Only analyze structure if not done before
163
371
  if not hasattr(self, '_structure_analyzed'):
164
372
  self.block_map = self.structural_analyzer.analyze(all_tokens)
@@ -170,10 +378,30 @@ class UltimateParser:
170
378
  # Phase 2: Parse ALL blocks
171
379
  program = self._parse_all_blocks_tolerantly(all_tokens)
172
380
 
381
+ if self._advanced_result_needs_fallback(program):
382
+ self._log("⚠️ Advanced parser produced incomplete AST, using traditional parser", "normal")
383
+ self.use_advanced_parsing = False
384
+ return self._parse_traditional()
385
+
173
386
  # Fallback if advanced parsing fails
174
387
  if len(program.statements) == 0 and len(all_tokens) > 10:
175
388
  return self._parse_traditional()
176
389
 
390
+ if self._should_verify_with_traditional(program, all_tokens):
391
+ fallback_program, fallback_errors = self._parse_traditional_copy()
392
+ # Only prefer the traditional parser if it produces significantly more
393
+ # statements (>50% more). A small difference often means the advanced
394
+ # parser correctly merged compound constructs (e.g. let x = match {...})
395
+ # that the traditional parser fragments into separate pieces.
396
+ if fallback_program:
397
+ adv_count = len(program.statements)
398
+ trad_count = len(fallback_program.statements)
399
+ if adv_count > 0 and trad_count > adv_count * 1.5:
400
+ self._log("🔁 Traditional parser produced a richer AST; switching to fallback result", "normal")
401
+ self.errors = list(fallback_errors or [])
402
+ self.use_advanced_parsing = False
403
+ return fallback_program
404
+
177
405
  self._log(f"✅ Parsing Complete: {len(program.statements)} statements, {len(self.errors)} errors", "minimal")
178
406
  return program
179
407
 
@@ -182,71 +410,145 @@ class UltimateParser:
182
410
  self.use_advanced_parsing = False
183
411
  return self._parse_traditional()
184
412
 
413
+ def _should_disable_advanced_for_size(self) -> bool:
414
+ """Heuristic guardrail to keep very large inputs stable.
415
+
416
+ Uses source line-count (cheap) to decide whether to skip advanced parsing.
417
+ """
418
+ try:
419
+ source = getattr(self.lexer, 'input', None)
420
+ if not isinstance(source, str) or not source:
421
+ return False
422
+
423
+ max_lines = getattr(config, 'advanced_parsing_max_lines', 2000)
424
+ if isinstance(max_lines, int) and max_lines > 0:
425
+ # count('\n') is linear but cheap compared to tokenization + analysis
426
+ line_count = source.count('\n') + 1
427
+ if line_count > max_lines:
428
+ return True
429
+ except Exception:
430
+ return False
431
+ return False
432
+
433
+ def _advanced_result_needs_fallback(self, program):
434
+ """Detect obvious advanced-parser failures and trigger traditional fallback."""
435
+ try:
436
+ statements = getattr(program, "statements", []) or []
437
+ except Exception:
438
+ return True
439
+
440
+ for stmt in statements:
441
+ if isinstance(stmt, BlockStatement):
442
+ # Top-level block indicates context parser stitched raw blocks
443
+ return True
444
+ if isinstance(stmt, ActionStatement):
445
+ name_obj = getattr(stmt, "name", None)
446
+ name_value = getattr(name_obj, "value", name_obj)
447
+ if not name_value or name_value == "anonymous":
448
+ return True
449
+ body = getattr(stmt, "body", None)
450
+ if not body or not getattr(body, "statements", None):
451
+ return True
452
+ if isinstance(stmt, FunctionStatement):
453
+ name_obj = getattr(stmt, "name", None)
454
+ name_value = getattr(name_obj, "value", name_obj)
455
+ if not name_value:
456
+ return True
457
+ return False
458
+
459
+ def _should_verify_with_traditional(self, program, tokens):
460
+ """Decide if we should cross-check the advanced result against the traditional parser."""
461
+ try:
462
+ statements = getattr(program, "statements", []) or []
463
+ except Exception:
464
+ statements = []
465
+
466
+ # Small programs are cheap to re-parse and are more likely to hit edge cases
467
+ if len(statements) <= 1:
468
+ return True
469
+
470
+ last_token = self._last_meaningful_token(tokens)
471
+ if not last_token:
472
+ return False
473
+
474
+ # If the source ends with an expression-y token but AST does not, verify with traditional parser
475
+ expressiony_tokens = {IDENT, INT, FLOAT, STRING, TRUE, FALSE, NULL, RPAREN, RBRACKET}
476
+ if last_token.type in expressiony_tokens:
477
+ from ..zexus_ast import ExpressionStatement
478
+ if statements and isinstance(statements[-1], ExpressionStatement):
479
+ return False
480
+ return True
481
+
482
+ return False
483
+
484
+ def _last_meaningful_token(self, tokens):
485
+ for tok in reversed(tokens or []):
486
+ if tok.type in {EOF, SEMICOLON}:
487
+ continue
488
+ literal = getattr(tok, "literal", "") or ""
489
+ if literal.strip() == "":
490
+ continue
491
+ return tok
492
+ return None
493
+
494
+ def _parse_traditional_copy(self):
495
+ """Parse the current source with the traditional engine in an isolated parser instance."""
496
+ try:
497
+ clone_lexer = Lexer(self.lexer.input, getattr(self.lexer, "filename", "<stdin>"))
498
+ fallback_parser = UltimateParser(clone_lexer, self.syntax_style, enable_advanced_strategies=False)
499
+ fallback_program = fallback_parser.parse_program()
500
+ return fallback_program, getattr(fallback_parser, "errors", [])
501
+ except Exception:
502
+ return None, []
503
+
185
504
  def parse_map_literal(self):
186
505
  """Parse a map/object literal: { key: value, ... }"""
187
- # consume '{'
506
+ # Consume '{'
188
507
  self.next_token()
189
508
 
190
509
  pairs = []
191
510
 
192
- # Empty map
511
+ # Empty map literal {}
193
512
  if self.cur_token_is(RBRACE):
194
513
  self.next_token()
195
514
  return MapLiteral(pairs=pairs)
196
515
 
197
- # Collect key:value pairs with nesting support
198
- while not self.cur_token_is(EOF) and not self.cur_token_is(RBRACE):
199
- # Parse key
516
+ while not self.cur_token_is(EOF):
517
+ # Parse key (identifier or string)
200
518
  if self.cur_token_is(STRING):
201
519
  key = StringLiteral(self.cur_token.literal)
202
- self.next_token()
203
520
  elif self.cur_token_is(IDENT):
204
521
  key = Identifier(self.cur_token.literal)
205
- self.next_token()
206
522
  else:
207
- # Tolerant: skip unexpected tokens until colon or next pair
208
- self.next_token()
209
- continue
210
-
211
- # Expect colon
212
- if not self.cur_token_is(COLON):
213
- # Tolerant: try to recover by searching forward
214
- while not self.cur_token_is(COLON) and not self.cur_token_is(RBRACE) and not self.cur_token_is(EOF):
215
- self.next_token()
216
- if not self.cur_token_is(COLON):
217
- break
523
+ self.errors.append(
524
+ f"Line {self.cur_token.line}:{self.cur_token.column} - Expected string or identifier for map key"
525
+ )
526
+ return None
218
527
 
219
- # consume ':'
220
- self.next_token()
528
+ if not self.expect_peek(COLON):
529
+ return None
221
530
 
222
- # Parse value expression until comma or closing brace
223
- value_tokens = []
224
- nesting = 0
225
- while not self.cur_token_is(EOF) and not (self.cur_token_is(COMMA) and nesting == 0) and not (self.cur_token_is(RBRACE) and nesting == 0):
226
- if self.cur_token_is(LPAREN) or self.cur_token_is(LBRACE) or self.cur_token_is(LBRACKET):
227
- nesting += 1
228
- elif self.cur_token_is(RPAREN) or self.cur_token_is(RBRACE) or self.cur_token_is(RBRACKET):
229
- nesting -= 1
230
- value_tokens.append(self.cur_token)
231
- self.next_token()
531
+ self.next_token() # move to first token of the value
532
+ value = self.parse_expression(LOWEST)
533
+ pairs.append((key, value))
232
534
 
233
- value_expr = self.parse_expression(LOWEST) if value_tokens else None
234
- pairs.append((key, value_expr))
535
+ if self.peek_token_is(RBRACE):
536
+ self.next_token() # consume '}'
537
+ break
235
538
 
236
- # If current token is comma, consume it and continue
237
- if self.cur_token_is(COMMA):
238
- self.next_token()
539
+ if not self.peek_token_is(COMMA):
540
+ self.errors.append(
541
+ f"Line {self.peek_token.line}:{self.peek_token.column} - Expected ',' or '}}' after map value"
542
+ )
543
+ return None
239
544
 
240
- # Expect closing brace
241
- if not self.cur_token_is(RBRACE):
242
- error = self._create_parse_error(
243
- "Expected '}' to close map literal",
244
- suggestion="Make sure all opening braces { have matching closing braces }"
245
- )
246
- raise error
545
+ # Consume comma and handle optional trailing separator
546
+ self.next_token() # move to comma
547
+ if self.peek_token_is(RBRACE):
548
+ self.next_token() # consume closing brace
549
+ break
247
550
 
248
- # consume '}'
249
- self.next_token()
551
+ self.next_token() # move to next key token
250
552
 
251
553
  return MapLiteral(pairs=pairs)
252
554
 
@@ -254,6 +556,15 @@ class UltimateParser:
254
556
  """Collect all tokens for structural analysis - OPTIMIZED"""
255
557
  tokens = []
256
558
  original_position = self.lexer.position
559
+ original_read_position = self.lexer.read_position
560
+ original_ch = self.lexer.ch
561
+ original_line = self.lexer.line
562
+ original_column = self.lexer.column
563
+ original_last_token_type = self.lexer.last_token_type
564
+ original_boundary = getattr(self.lexer, 'at_statement_boundary', True)
565
+ original_paren_depth = getattr(self.lexer, 'paren_depth', 0)
566
+ original_bracket_depth = getattr(self.lexer, 'bracket_depth', 0)
567
+ original_brace_depth = getattr(self.lexer, 'brace_depth', 0)
257
568
  original_cur = self.cur_token
258
569
  original_peek = self.peek_token
259
570
 
@@ -262,6 +573,14 @@ class UltimateParser:
262
573
  self.lexer.read_position = 0
263
574
  self.lexer.ch = ''
264
575
  self.lexer.last_token_type = None # ✅ CRITICAL: Reset context-aware state
576
+ if hasattr(self.lexer, 'at_statement_boundary'):
577
+ self.lexer.at_statement_boundary = True
578
+ if hasattr(self.lexer, 'paren_depth'):
579
+ self.lexer.paren_depth = 0
580
+ if hasattr(self.lexer, 'bracket_depth'):
581
+ self.lexer.bracket_depth = 0
582
+ if hasattr(self.lexer, 'brace_depth'):
583
+ self.lexer.brace_depth = 0
265
584
  self.lexer.read_char()
266
585
 
267
586
  # OPTIMIZATION: Pre-allocate list with reasonable capacity
@@ -282,6 +601,19 @@ class UltimateParser:
282
601
 
283
602
  # Restore parser state
284
603
  self.lexer.position = original_position
604
+ self.lexer.read_position = original_read_position
605
+ self.lexer.ch = original_ch
606
+ self.lexer.line = original_line
607
+ self.lexer.column = original_column
608
+ self.lexer.last_token_type = original_last_token_type
609
+ if hasattr(self.lexer, 'at_statement_boundary'):
610
+ self.lexer.at_statement_boundary = original_boundary
611
+ if hasattr(self.lexer, 'paren_depth'):
612
+ self.lexer.paren_depth = original_paren_depth
613
+ if hasattr(self.lexer, 'bracket_depth'):
614
+ self.lexer.bracket_depth = original_bracket_depth
615
+ if hasattr(self.lexer, 'brace_depth'):
616
+ self.lexer.brace_depth = original_brace_depth
285
617
  self.cur_token = original_cur
286
618
  self.peek_token = original_peek
287
619
 
@@ -306,11 +638,21 @@ class UltimateParser:
306
638
  try:
307
639
  statement = self.context_parser.parse_block(block_info, all_tokens)
308
640
  if statement:
309
- program.statements.append(statement)
310
- parsed_count += 1
311
- if config.enable_debug_logs: # Only show detailed parsing in verbose mode
312
- stmt_type = type(statement).__name__
313
- self._log(f" ✅ Parsed: {stmt_type} at line {block_info['start_token'].line}", "verbose")
641
+ # Unwrap synthetic BlockStatements emitted by context strategies so inner statements flow to the program
642
+ from ..zexus_ast import BlockStatement as _BlockStatement
643
+
644
+ if isinstance(statement, _BlockStatement) and getattr(statement, "statements", None):
645
+ program.statements.extend(statement.statements)
646
+ parsed_count += len(statement.statements)
647
+ if config.enable_debug_logs:
648
+ stmt_types = ", ".join(type(stmt).__name__ for stmt in statement.statements)
649
+ self._log(f" ✅ Parsed composite block [{stmt_types}] at line {block_info['start_token'].line}", "verbose")
650
+ else:
651
+ program.statements.append(statement)
652
+ parsed_count += 1
653
+ if config.enable_debug_logs: # Only show detailed parsing in verbose mode
654
+ stmt_type = type(statement).__name__
655
+ self._log(f" ✅ Parsed: {stmt_type} at line {block_info['start_token'].line}", "verbose")
314
656
 
315
657
  except Exception as e:
316
658
  error_msg = f"Line {block_info['start_token'].line}: {str(e)}"
@@ -369,165 +711,26 @@ class UltimateParser:
369
711
  modifiers = []
370
712
  if self.cur_token and self.cur_token.type in {PUBLIC, PRIVATE, SEALED, ASYNC, NATIVE, INLINE, SECURE, PURE, VIEW, PAYABLE}:
371
713
  modifiers = self._parse_modifiers()
714
+
715
+ # Skip stray semicolons that may appear between statements
716
+ if self.cur_token_is(SEMICOLON):
717
+ return None
718
+ if self.cur_token_is(RBRACE):
719
+ return None
372
720
  try:
373
721
  node = None
374
- if self.cur_token_is(LET):
375
- node = self.parse_let_statement()
376
- elif self.cur_token_is(CONST):
377
- node = self.parse_const_statement()
378
- elif self.cur_token_is(DATA):
379
- node = self.parse_data_statement()
380
- elif self.cur_token_is(RETURN):
381
- node = self.parse_return_statement()
382
- elif self.cur_token_is(CONTINUE):
383
- node = self.parse_continue_statement()
384
- elif self.cur_token_is(BREAK):
385
- node = self.parse_break_statement()
386
- elif self.cur_token_is(THROW):
387
- node = self.parse_throw_statement()
388
- elif self.cur_token_is(PRINT):
389
- node = self.parse_print_statement()
390
- elif self.cur_token_is(FOR):
391
- node = self.parse_for_each_statement()
392
- elif self.cur_token_is(SCREEN):
393
- node = self.parse_screen_statement()
394
- elif self.cur_token_is(ACTION):
395
- node = self.parse_action_statement()
396
- elif self.cur_token_is(FUNCTION):
397
- node = self.parse_function_statement()
398
- elif self.cur_token_is(IF):
399
- node = self.parse_if_statement()
400
- elif self.cur_token_is(WHILE):
401
- node = self.parse_while_statement()
402
- elif self.cur_token_is(USE):
403
- node = self.parse_use_statement()
404
- elif self.cur_token_is(EXACTLY):
405
- node = self.parse_exactly_statement()
406
- elif self.cur_token_is(EXPORT):
407
- node = self.parse_export_statement()
408
- elif self.cur_token_is(DEBUG):
409
- node = self.parse_debug_statement()
410
- elif self.cur_token_is(TRY):
411
- node = self.parse_try_catch_statement()
412
- elif self.cur_token_is(EXTERNAL):
413
- node = self.parse_external_declaration()
414
- elif self.cur_token_is(ENTITY):
415
- node = self.parse_entity_statement()
416
- elif self.cur_token_is(VERIFY):
417
- node = self.parse_verify_statement()
418
- elif self.cur_token_is(CONTRACT):
419
- node = self.parse_contract_statement()
420
- elif self.cur_token_is(PROTECT):
421
- node = self.parse_protect_statement()
422
- elif self.cur_token_is(SEAL):
423
- node = self.parse_seal_statement()
424
- elif self.cur_token_is(AUDIT):
425
- node = self.parse_audit_statement()
426
- elif self.cur_token_is(RESTRICT):
427
- node = self.parse_restrict_statement()
428
- elif self.cur_token_is(SANDBOX):
429
- node = self.parse_sandbox_statement()
430
- elif self.cur_token_is(TRAIL):
431
- node = self.parse_trail_statement()
432
- elif self.cur_token_is(TX):
433
- node = self.parse_tx_statement()
434
- elif self.cur_token_is(NATIVE):
435
- node = self.parse_native_statement()
436
- elif self.cur_token_is(GC):
437
- node = self.parse_gc_statement()
438
- elif self.cur_token_is(INLINE):
439
- node = self.parse_inline_statement()
440
- elif self.cur_token_is(BUFFER):
441
- node = self.parse_buffer_statement()
442
- elif self.cur_token_is(SIMD):
443
- node = self.parse_simd_statement()
444
- elif self.cur_token_is(DEFER):
445
- node = self.parse_defer_statement()
446
- elif self.cur_token_is(PATTERN):
447
- node = self.parse_pattern_statement()
448
- elif self.cur_token_is(ENUM):
449
- node = self.parse_enum_statement()
450
- elif self.cur_token_is(STREAM):
451
- node = self.parse_stream_statement()
452
- elif self.cur_token_is(WATCH):
453
- print(f"[PARSE_STMT] Matched WATCH", file=sys.stderr, flush=True)
454
- node = self.parse_watch_statement()
455
- elif self.cur_token_is(EMIT):
456
- print(f"[PARSE_STMT] Matched EMIT", file=sys.stderr, flush=True)
457
- node = self.parse_emit_statement()
458
- elif self.cur_token_is(MODIFIER):
459
- print(f"[PARSE_STMT] Matched MODIFIER", file=sys.stderr, flush=True)
460
- node = self.parse_modifier_declaration()
461
- # === SECURITY STATEMENT HANDLERS ===
462
- elif self.cur_token_is(CAPABILITY):
463
- print(f"[PARSE_STMT] Matched CAPABILITY", file=sys.stderr, flush=True)
464
- node = self.parse_capability_statement()
465
- elif self.cur_token_is(GRANT):
466
- print(f"[PARSE_STMT] Matched GRANT", file=sys.stderr, flush=True)
467
- node = self.parse_grant_statement()
468
- elif self.cur_token_is(REVOKE):
469
- print(f"[PARSE_STMT] Matched REVOKE", file=sys.stderr, flush=True)
470
- node = self.parse_revoke_statement()
471
- elif self.cur_token_is(VALIDATE):
472
- print(f"[PARSE_STMT] Matched VALIDATE", file=sys.stderr, flush=True)
473
- node = self.parse_validate_statement()
474
- elif self.cur_token_is(SANITIZE):
475
- print(f"[PARSE_STMT] Matched SANITIZE", file=sys.stderr, flush=True)
476
- node = self.parse_sanitize_statement()
477
- elif self.cur_token_is(INJECT):
478
- print(f"[PARSE_STMT] Matched INJECT", file=sys.stderr, flush=True)
479
- node = self.parse_inject_statement()
480
- elif self.cur_token_is(IMMUTABLE):
481
- print(f"[PARSE_STMT] Matched IMMUTABLE", file=sys.stderr, flush=True)
482
- node = self.parse_immutable_statement()
483
- # === COMPLEXITY STATEMENT HANDLERS ===
484
- elif self.cur_token_is(INTERFACE):
485
- print(f"[PARSE_STMT] Matched INTERFACE", file=sys.stderr, flush=True)
486
- node = self.parse_interface_statement()
487
- elif self.cur_token_is(TYPE_ALIAS):
488
- print(f"[PARSE_STMT] Matched TYPE_ALIAS", file=sys.stderr, flush=True)
489
- node = self.parse_type_alias_statement()
490
- elif self.cur_token_is(MODULE):
491
- print(f"[PARSE_STMT] Matched MODULE", file=sys.stderr, flush=True)
492
- node = self.parse_module_statement()
493
- elif self.cur_token_is(PACKAGE):
494
- print(f"[PARSE_STMT] Matched PACKAGE", file=sys.stderr, flush=True)
495
- node = self.parse_package_statement()
496
- elif self.cur_token_is(USING):
497
- print(f"[PARSE_STMT] Matched USING", file=sys.stderr, flush=True)
498
- node = self.parse_using_statement()
499
- elif self.cur_token_is(CHANNEL):
500
- print(f"[PARSE_STMT] Matched CHANNEL", file=sys.stderr, flush=True)
501
- node = self.parse_channel_statement()
502
- elif self.cur_token_is(SEND):
503
- print(f"[PARSE_STMT] Matched SEND", file=sys.stderr, flush=True)
504
- node = self.parse_send_statement()
505
- elif self.cur_token_is(RECEIVE):
506
- print(f"[PARSE_STMT] Matched RECEIVE", file=sys.stderr, flush=True)
507
- node = self.parse_receive_statement()
508
- elif self.cur_token_is(ATOMIC):
509
- print(f"[PARSE_STMT] Matched ATOMIC", file=sys.stderr, flush=True)
510
- node = self.parse_atomic_statement()
511
- # === BLOCKCHAIN STATEMENT HANDLERS ===
512
- elif self.cur_token_is(LEDGER):
513
- print(f"[PARSE_STMT] Matched LEDGER", file=sys.stderr, flush=True)
514
- node = self.parse_ledger_statement()
515
- elif self.cur_token_is(STATE):
516
- print(f"[PARSE_STMT] Matched STATE", file=sys.stderr, flush=True)
517
- node = self.parse_state_statement()
518
- elif self.cur_token_is(REQUIRE):
519
- node = self.parse_require_statement()
520
- elif self.cur_token_is(REVERT):
521
- print(f"[PARSE_STMT] Matched REVERT", file=sys.stderr, flush=True)
522
- node = self.parse_revert_statement()
523
- elif self.cur_token_is(LIMIT):
524
- print(f"[PARSE_STMT] Matched LIMIT", file=sys.stderr, flush=True)
525
- node = self.parse_limit_statement()
722
+ tok_type = self.cur_token.type
723
+ handler = self._statement_dispatch.get(tok_type)
724
+ if handler is not None:
725
+ node = handler()
526
726
  else:
527
- print(f"[PARSE_STMT] No match, falling back to expression statement", file=sys.stderr, flush=True)
528
727
  node = self.parse_expression_statement()
529
728
 
530
729
  if node is not None:
730
+ # Attach source location for debugger / error reporting
731
+ if self.cur_token and not getattr(node, 'line', 0):
732
+ node.line = getattr(self.cur_token, 'line', 0) or 0
733
+ node.column = getattr(self.cur_token, 'column', 0) or 0
531
734
  return attach_modifiers(node, modifiers)
532
735
  return None
533
736
  except Exception as e:
@@ -584,8 +787,13 @@ class UltimateParser:
584
787
  """Parse { } block with tolerance for missing closing brace"""
585
788
  block = BlockStatement()
586
789
  self.next_token()
587
- import sys
588
- print(f"[BLOCK_START] Entering brace block, first token: {self.cur_token.type}={repr(self.cur_token.literal)}", file=sys.stderr, flush=True)
790
+ debug_enabled = config.enable_debug_logs
791
+ if debug_enabled:
792
+ print(
793
+ f"[BLOCK_START] Entering brace block, first token: {self.cur_token.type}={repr(self.cur_token.literal)}",
794
+ file=sys.stderr,
795
+ flush=True,
796
+ )
589
797
 
590
798
  brace_count = 1
591
799
  stmt_count = 0
@@ -596,19 +804,38 @@ class UltimateParser:
596
804
  brace_count -= 1
597
805
  if brace_count == 0:
598
806
  break
807
+ # Skip standalone closing braces from nested blocks without parsing a statement
808
+ self.next_token()
809
+ continue
599
810
 
600
- print(f"[BLOCK_STMT] About to parse statement {stmt_count}, token: {self.cur_token.type}={repr(self.cur_token.literal)}", file=sys.stderr, flush=True)
811
+ if debug_enabled:
812
+ print(
813
+ f"[BLOCK_STMT] About to parse statement {stmt_count}, token: {self.cur_token.type}={repr(self.cur_token.literal)}",
814
+ file=sys.stderr,
815
+ flush=True,
816
+ )
601
817
  stmt = self.parse_statement()
602
- print(f"[BLOCK_STMT] Parsed statement {stmt_count}: {type(stmt).__name__ if stmt else 'None'}", file=sys.stderr, flush=True)
818
+ if debug_enabled:
819
+ print(
820
+ f"[BLOCK_STMT] Parsed statement {stmt_count}: {type(stmt).__name__ if stmt else 'None'}",
821
+ file=sys.stderr,
822
+ flush=True,
823
+ )
603
824
  if stmt is not None:
604
825
  block.statements.append(stmt)
605
826
  self.next_token()
606
827
  stmt_count += 1
607
828
 
608
- print(f"[BLOCK_END] Finished block with {len(block.statements)} statements", file=sys.stderr, flush=True)
829
+ if debug_enabled:
830
+ print(
831
+ f"[BLOCK_END] Finished block with {len(block.statements)} statements",
832
+ file=sys.stderr,
833
+ flush=True,
834
+ )
609
835
  # TOLERANT: Don't error if we hit EOF without closing brace
610
836
  if self.cur_token_is(EOF) and brace_count > 0:
611
- self.errors.append(f"Line {self.cur_token.line}: Unclosed block (reached EOF)")
837
+ # Tolerant mode: allow missing closing braces at EOF without failing hard
838
+ brace_count = 0
612
839
 
613
840
  return block
614
841
 
@@ -629,8 +856,9 @@ class UltimateParser:
629
856
 
630
857
  def parse_if_statement(self):
631
858
  """Tolerant if statement parser with elif support"""
632
- import sys
633
- print(f"[PARSE_IF] Starting if statement parsing", file=sys.stderr, flush=True)
859
+ debug_enabled = config.enable_debug_logs
860
+ if debug_enabled:
861
+ print("[PARSE_IF] Starting if statement parsing", file=sys.stderr, flush=True)
634
862
  # Skip IF token
635
863
  self.next_token()
636
864
 
@@ -652,53 +880,109 @@ class UltimateParser:
652
880
  )
653
881
  raise error
654
882
 
655
- print(f"[PARSE_IF] Parsed condition, now at token: {self.cur_token.type}={repr(self.cur_token.literal)}", file=sys.stderr, flush=True)
883
+ if debug_enabled:
884
+ print(
885
+ f"[PARSE_IF] Parsed condition, now at token: {self.cur_token.type}={repr(self.cur_token.literal)}",
886
+ file=sys.stderr,
887
+ flush=True,
888
+ )
656
889
  # Parse consequence (flexible block style)
657
890
  consequence = self.parse_block("if")
658
- print(f"[PARSE_IF] Parsed consequence block, now at token: {self.cur_token.type}={repr(self.cur_token.literal)}", file=sys.stderr, flush=True)
891
+ if debug_enabled:
892
+ print(
893
+ f"[PARSE_IF] Parsed consequence block, now at token: {self.cur_token.type}={repr(self.cur_token.literal)}",
894
+ file=sys.stderr,
895
+ flush=True,
896
+ )
659
897
  if not consequence:
660
898
  return None
661
899
 
662
- # Parse elif clauses
663
- elif_parts = []
664
- while self.cur_token_is(ELIF):
665
- self.next_token() # Move past elif
666
-
667
- # Parse elif condition (with or without parentheses)
900
+ def _parse_conditional_clause(keyword):
901
+ """Parse an if/elif/else-if condition allowing optional parentheses."""
668
902
  if self.cur_token_is(LPAREN):
669
903
  self.next_token() # Skip (
670
- elif_condition = self.parse_expression(LOWEST)
904
+ clause_condition = self.parse_expression(LOWEST)
671
905
  if not self.expect_peek(RPAREN):
672
- # Expected closing paren after elif condition
673
906
  return None
674
907
  else:
675
- # No parentheses - parse expression directly
676
- elif_condition = self.parse_expression(LOWEST)
677
-
678
- if not elif_condition:
908
+ clause_condition = self.parse_expression(LOWEST)
909
+
910
+ if not clause_condition:
679
911
  error = self._create_parse_error(
680
- "Expected condition after 'elif'",
681
- suggestion="Add a condition expression: elif (condition) { ... }"
912
+ f"Expected condition after '{keyword}'",
913
+ suggestion=f"Add a condition expression: {keyword} (condition) {{ ... }}"
682
914
  )
683
915
  raise error
684
-
685
- # Parse elif consequence block
686
- elif_consequence = self.parse_block("elif")
687
- if not elif_consequence:
688
- return None
689
-
690
- elif_parts.append((elif_condition, elif_consequence))
691
916
 
692
- # Parse else clause
917
+ return clause_condition
918
+
919
+ # Parse elif / else-if chains (using lookahead so we keep the closing brace as current token)
920
+ elif_parts = []
693
921
  alternative = None
694
- if self.cur_token_is(ELSE):
695
- self.next_token()
696
- alternative = self.parse_block("else")
922
+
923
+ while True:
924
+ if debug_enabled:
925
+ print(
926
+ f"[PARSE_IF] After consequence, current={self.cur_token.type}, peek={self.peek_token.type if self.peek_token else None}",
927
+ file=sys.stderr,
928
+ flush=True,
929
+ )
930
+ if self.peek_token_is(ELIF):
931
+ self.next_token() # Move to 'elif'
932
+ self.next_token() # Advance to first token of condition
933
+ if debug_enabled:
934
+ print("[PARSE_IF] Detected 'elif' clause", file=sys.stderr, flush=True)
935
+ clause_condition = _parse_conditional_clause("elif")
936
+ if clause_condition is None:
937
+ return None
938
+
939
+ clause_block = self.parse_block("elif")
940
+ if not clause_block:
941
+ return None
942
+
943
+ elif_parts.append((clause_condition, clause_block))
944
+ continue
945
+
946
+ if self.peek_token_is(ELSE):
947
+ self.next_token() # Move to 'else'
948
+
949
+ # Support `else if` by converting it into another elif clause
950
+ if self.peek_token_is(IF):
951
+ self.next_token() # Move to 'if'
952
+ self.next_token() # Advance to first token of condition
953
+ if debug_enabled:
954
+ print("[PARSE_IF] Detected 'else if' clause", file=sys.stderr, flush=True)
955
+ clause_condition = _parse_conditional_clause("else if")
956
+ if clause_condition is None:
957
+ return None
958
+
959
+ clause_block = self.parse_block("elif")
960
+ if not clause_block:
961
+ return None
962
+
963
+ elif_parts.append((clause_condition, clause_block))
964
+ if debug_enabled:
965
+ print("[PARSE_IF] Completed 'else if' clause", file=sys.stderr, flush=True)
966
+ continue
967
+
968
+ if debug_enabled:
969
+ print("[PARSE_IF] Detected plain 'else' clause", file=sys.stderr, flush=True)
970
+ alternative = self.parse_block("else")
971
+ if not alternative:
972
+ return None
973
+ break
974
+
975
+ break
697
976
 
698
977
  return IfStatement(condition=condition, consequence=consequence, elif_parts=elif_parts, alternative=alternative)
699
978
 
700
979
  def parse_action_statement(self):
701
980
  """Tolerant action parser supporting both syntax styles"""
981
+ is_async_modifier = False
982
+ if self.peek_token_is(ASYNC):
983
+ self.next_token() # consume inline async modifier
984
+ is_async_modifier = True
985
+
702
986
  if not self.expect_peek(IDENT):
703
987
  self.errors.append("Expected function name after 'action'")
704
988
  return None
@@ -734,8 +1018,26 @@ class UltimateParser:
734
1018
  if not body:
735
1019
  return None
736
1020
 
737
- # Note: is_async will be set by parse_statement if async modifier is present
738
- return ActionStatement(name=name, parameters=parameters, body=body, is_async=False, return_type=return_type)
1021
+ action_node = ActionStatement(
1022
+ name=name,
1023
+ parameters=parameters,
1024
+ body=body,
1025
+ is_async=is_async_modifier,
1026
+ return_type=return_type,
1027
+ )
1028
+
1029
+ if is_async_modifier:
1030
+ # Mirror modifier behavior so downstream consumers find 'async'
1031
+ existing_modifiers = getattr(action_node, 'modifiers', []) or []
1032
+ if 'async' not in (m.lower() if isinstance(m, str) else m for m in existing_modifiers):
1033
+ try:
1034
+ existing_modifiers = list(existing_modifiers)
1035
+ existing_modifiers.append('async')
1036
+ action_node.modifiers = existing_modifiers
1037
+ except Exception:
1038
+ action_node.modifiers = ['async']
1039
+
1040
+ return action_node
739
1041
 
740
1042
  def parse_function_statement(self):
741
1043
  """Tolerant function parser supporting both syntax styles"""
@@ -776,11 +1078,101 @@ class UltimateParser:
776
1078
 
777
1079
  return FunctionStatement(name=name, parameters=parameters, body=body, return_type=return_type)
778
1080
 
1081
+ def _parse_destructure_pattern(self):
1082
+ """Parse a destructuring pattern: {a, b: renamed} or [x, y, ..rest]"""
1083
+ from ..zexus_ast import DestructurePattern
1084
+ if self.cur_token_is(LBRACE):
1085
+ # Map destructuring: {a, b, c: renamed}
1086
+ bindings = []
1087
+ self.next_token() # skip {
1088
+ while not self.cur_token_is(RBRACE) and not self.cur_token_is(EOF):
1089
+ if self.cur_token_is(COMMA):
1090
+ self.next_token()
1091
+ continue
1092
+ if not self.cur_token_is(IDENT):
1093
+ self.errors.append(f"Expected identifier in map destructure, got {self.cur_token.type}")
1094
+ return None
1095
+ source_key = self.cur_token.literal
1096
+ target_name = source_key # default: same name
1097
+ if self.peek_token_is(COLON):
1098
+ self.next_token() # skip :
1099
+ self.next_token() # move to target name
1100
+ if not self.cur_token_is(IDENT):
1101
+ self.errors.append("Expected identifier after ':' in map destructure")
1102
+ return None
1103
+ target_name = self.cur_token.literal
1104
+ bindings.append((source_key, target_name))
1105
+ self.next_token()
1106
+ # cur_token should be RBRACE
1107
+ return DestructurePattern(kind='map', bindings=bindings)
1108
+ elif self.cur_token_is(LBRACKET):
1109
+ # List destructuring: [x, y, ..rest]
1110
+ bindings = []
1111
+ rest = None
1112
+ idx = 0
1113
+ self.next_token() # skip [
1114
+ while not self.cur_token_is(RBRACKET) and not self.cur_token_is(EOF):
1115
+ if self.cur_token_is(COMMA):
1116
+ self.next_token()
1117
+ continue
1118
+ # Check for rest element: ..rest (lexed as DOT DOT IDENT)
1119
+ if self.cur_token.literal == '.':
1120
+ # Consume second dot
1121
+ self.next_token()
1122
+ if self.cur_token.literal == '.':
1123
+ self.next_token() # move to rest identifier
1124
+ if self.cur_token_is(IDENT):
1125
+ rest = self.cur_token.literal
1126
+ self.next_token()
1127
+ continue
1128
+ # If not a valid ..rest, skip
1129
+ continue
1130
+ if self.cur_token_is(IDENT) and self.cur_token.literal.startswith('..'):
1131
+ rest = self.cur_token.literal[2:]
1132
+ self.next_token()
1133
+ continue
1134
+ if not self.cur_token_is(IDENT):
1135
+ self.errors.append(f"Expected identifier in list destructure, got {self.cur_token.type}")
1136
+ return None
1137
+ bindings.append((idx, self.cur_token.literal))
1138
+ idx += 1
1139
+ self.next_token()
1140
+ # cur_token should be RBRACKET
1141
+ return DestructurePattern(kind='list', bindings=bindings, rest=rest)
1142
+ return None
1143
+
779
1144
  def parse_let_statement(self):
780
- """Tolerant let statement parser"""
1145
+ """Tolerant let statement parser with destructuring and type annotation support
1146
+
1147
+ let x = value
1148
+ let x: int = value (type annotation)
1149
+ let {a, b} = map_expr
1150
+ let [x, y] = list_expr
1151
+ """
781
1152
  stmt = LetStatement(name=None, value=None)
782
1153
 
783
- if not self.expect_peek(IDENT):
1154
+ # Check for destructuring pattern
1155
+ if self.peek_token_is(LBRACE) or self.peek_token_is(LBRACKET):
1156
+ self.next_token() # move to { or [
1157
+ pattern = self._parse_destructure_pattern()
1158
+ if pattern is None:
1159
+ return None
1160
+ stmt.name = pattern
1161
+ # Expect = after pattern
1162
+ if self.peek_token_is(ASSIGN):
1163
+ self.next_token()
1164
+ else:
1165
+ self.errors.append("Expected '=' after destructuring pattern")
1166
+ return None
1167
+ self.next_token()
1168
+ stmt.value = self.parse_expression(LOWEST)
1169
+ if self.peek_token_is(SEMICOLON):
1170
+ self.next_token()
1171
+ return stmt
1172
+
1173
+ if self.peek_token_is(IDENT) or self.peek_token_is(EVENT):
1174
+ self.next_token()
1175
+ else:
784
1176
  error = self._create_parse_error(
785
1177
  "Expected variable name after 'let'",
786
1178
  suggestion="Use 'let' to declare a variable: let myVariable = value"
@@ -789,8 +1181,33 @@ class UltimateParser:
789
1181
 
790
1182
  stmt.name = Identifier(value=self.cur_token.literal)
791
1183
 
792
- # TOLERANT: Allow both = and : for assignment
793
- if self.peek_token_is(ASSIGN) or (self.peek_token_is(COLON) and self.peek_token.literal == ":"):
1184
+ # Disambiguate `:` could be type annotation (let x: int = ...) or
1185
+ # old-style assignment (let x: value). If `:` is followed by an
1186
+ # IDENT and then `=`, treat it as a type annotation.
1187
+ if self.peek_token_is(COLON) and self.peek_token.literal == ":":
1188
+ # Peek two ahead to see if this is `name: TYPE = value`
1189
+ saved_pos = getattr(self, '_saved_pos', None)
1190
+ # Manual two-token lookahead
1191
+ self.next_token() # move to :
1192
+ if self.peek_token_is(IDENT):
1193
+ # Could be type annotation — check if IDENT is followed by =
1194
+ type_tok = self.peek_token
1195
+ self.next_token() # move to potential type token
1196
+ if self.peek_token_is(ASSIGN):
1197
+ # It IS a type annotation: let x: int = value
1198
+ stmt.type_annotation = self.cur_token.literal
1199
+ self.next_token() # move to =
1200
+ else:
1201
+ # It's old-style assignment: let x: value
1202
+ # cur_token is the first token of the value expression
1203
+ stmt.value = self.parse_expression(LOWEST)
1204
+ if self.peek_token_is(SEMICOLON):
1205
+ self.next_token()
1206
+ return stmt
1207
+ else:
1208
+ # Not IDENT after `:` — old-style assignment
1209
+ pass # fall through to parse value
1210
+ elif self.peek_token_is(ASSIGN):
794
1211
  self.next_token()
795
1212
  else:
796
1213
  self.errors.append("Expected '=' or ':' after variable name")
@@ -806,20 +1223,59 @@ class UltimateParser:
806
1223
  return stmt
807
1224
 
808
1225
  def parse_const_statement(self):
809
- """Tolerant const statement parser - immutable variable declaration
1226
+ """Tolerant const statement parser with destructuring and type annotation support
810
1227
 
811
- Syntax: const NAME = value;
1228
+ const NAME = value;
1229
+ const PI: float = 3.14;
1230
+ const {a, b} = map_expr;
1231
+ const [x, y] = list_expr;
812
1232
  """
813
1233
  stmt = ConstStatement(name=None, value=None)
814
1234
 
815
- if not self.expect_peek(IDENT):
1235
+ # Check for destructuring pattern
1236
+ if self.peek_token_is(LBRACE) or self.peek_token_is(LBRACKET):
1237
+ self.next_token() # move to { or [
1238
+ pattern = self._parse_destructure_pattern()
1239
+ if pattern is None:
1240
+ return None
1241
+ stmt.name = pattern
1242
+ if self.peek_token_is(ASSIGN):
1243
+ self.next_token()
1244
+ else:
1245
+ self.errors.append("Expected '=' after destructuring pattern")
1246
+ return None
1247
+ self.next_token()
1248
+ stmt.value = self.parse_expression(LOWEST)
1249
+ if self.peek_token_is(SEMICOLON):
1250
+ self.next_token()
1251
+ return stmt
1252
+
1253
+ if self.peek_token_is(IDENT) or self.peek_token_is(EVENT):
1254
+ self.next_token()
1255
+ else:
816
1256
  self.errors.append("Expected variable name after 'const'")
817
1257
  return None
818
1258
 
819
1259
  stmt.name = Identifier(value=self.cur_token.literal)
820
1260
 
821
- # TOLERANT: Allow both = and : for assignment
822
- if self.peek_token_is(ASSIGN) or (self.peek_token_is(COLON) and self.peek_token.literal == ":"):
1261
+ # Disambiguate `:` type annotation vs old-style assignment
1262
+ if self.peek_token_is(COLON) and self.peek_token.literal == ":":
1263
+ self.next_token() # move to :
1264
+ if self.peek_token_is(IDENT):
1265
+ type_tok = self.peek_token
1266
+ self.next_token() # move to potential type token
1267
+ if self.peek_token_is(ASSIGN):
1268
+ stmt.type_annotation = self.cur_token.literal
1269
+ self.next_token() # move to =
1270
+ else:
1271
+ # Old-style assignment: const x: value
1272
+ stmt.value = self.parse_expression(LOWEST)
1273
+ if self.peek_token_is(SEMICOLON):
1274
+ self.next_token()
1275
+ return stmt
1276
+ else:
1277
+ pass # fall through to parse value
1278
+ elif self.peek_token_is(ASSIGN):
823
1279
  self.next_token()
824
1280
  else:
825
1281
  self.errors.append("Expected '=' or ':' after variable name in const declaration")
@@ -1193,13 +1649,14 @@ class UltimateParser:
1193
1649
  """
1194
1650
  import sys
1195
1651
  # Debug logging (fail silently if file operations fail)
1196
- try:
1197
- log_path = os.path.join(tempfile.gettempdir(), 'parser_log.txt')
1198
- with open(log_path, 'a') as f:
1199
- f.write(f"=== parse_print_statement CALLED ===\n")
1200
- f.flush()
1201
- except (IOError, OSError, PermissionError):
1202
- pass # Silently ignore debug logging errors
1652
+ if getattr(config, 'enable_parser_debug', False):
1653
+ try:
1654
+ log_path = os.path.join(tempfile.gettempdir(), 'parser_log.txt')
1655
+ with open(log_path, 'a') as f:
1656
+ f.write(f"=== parse_print_statement CALLED ===\n")
1657
+ f.flush()
1658
+ except (IOError, OSError, PermissionError):
1659
+ pass # Silently ignore debug logging errors
1203
1660
 
1204
1661
  stmt = PrintStatement(values=[])
1205
1662
  self.next_token()
@@ -1267,10 +1724,17 @@ class UltimateParser:
1267
1724
  if not catch_block:
1268
1725
  return None
1269
1726
 
1727
+ # Check for optional 'finally' block
1728
+ finally_block = None
1729
+ if self.peek_token_is(FINALLY):
1730
+ self.next_token() # consume 'finally'
1731
+ finally_block = self.parse_block("finally")
1732
+
1270
1733
  return TryCatchStatement(
1271
1734
  try_block=try_block,
1272
1735
  error_variable=error_var,
1273
- catch_block=catch_block
1736
+ catch_block=catch_block,
1737
+ finally_block=finally_block
1274
1738
  )
1275
1739
 
1276
1740
  def parse_debug_statement(self):
@@ -1490,6 +1954,29 @@ class UltimateParser:
1490
1954
  expression.value = self.parse_expression(LOWEST)
1491
1955
  return expression
1492
1956
 
1957
+ def parse_compound_assignment_expression(self, left):
1958
+ """Parse compound assignment: x += 5 → x = x + 5"""
1959
+ if not isinstance(left, (Identifier, PropertyAccessExpression)):
1960
+ self.errors.append(f"Line {self.cur_token.line}:{self.cur_token.column} - Cannot use compound assignment on {type(left).__name__}, only identifiers and properties allowed")
1961
+ return None
1962
+
1963
+ # Map compound operator token to the underlying arithmetic operator
1964
+ op_map = {
1965
+ PLUS_ASSIGN: "+",
1966
+ MINUS_ASSIGN: "-",
1967
+ STAR_ASSIGN: "*",
1968
+ SLASH_ASSIGN: "/",
1969
+ MOD_ASSIGN: "%",
1970
+ POWER_ASSIGN: "**",
1971
+ }
1972
+ operator = op_map.get(self.cur_token.type, "+")
1973
+ self.next_token()
1974
+ right = self.parse_expression(LOWEST)
1975
+
1976
+ # Desugar: x += expr → x = x + expr
1977
+ infix = InfixExpression(left=left, operator=operator, right=right)
1978
+ return AssignmentExpression(name=left, value=infix)
1979
+
1493
1980
  def parse_method_call_expression(self, left):
1494
1981
  if not self.cur_token_is(DOT):
1495
1982
  return None
@@ -1516,29 +2003,41 @@ class UltimateParser:
1516
2003
  def parse_export_statement(self):
1517
2004
  token = self.cur_token
1518
2005
 
1519
- # Check for syntactic sugar: export action name() {} or export function name() {}
1520
- if self.peek_token_is(ACTION) or self.peek_token_is(FUNCTION):
1521
- self.next_token() # Move to ACTION/FUNCTION token
1522
-
1523
- # Parse the action/function normally
1524
- if self.cur_token_is(ACTION):
1525
- func_stmt = self.parse_action_statement()
1526
- else: # FUNCTION
1527
- func_stmt = self.parse_function_statement()
1528
-
1529
- if func_stmt is None:
2006
+ keyword_parsers = {
2007
+ ACTION: self.parse_action_statement,
2008
+ FUNCTION: self.parse_function_statement,
2009
+ CONTRACT: self.parse_contract_statement,
2010
+ CONST: self.parse_const_statement,
2011
+ LET: self.parse_let_statement,
2012
+ DATA: self.parse_data_statement,
2013
+ }
2014
+
2015
+ if self.peek_token and self.peek_token.type in keyword_parsers:
2016
+ keyword_type = self.peek_token.type
2017
+ self.next_token() # Move to the declaration keyword
2018
+ declaration = keyword_parsers[keyword_type]()
2019
+
2020
+ if declaration is None:
1530
2021
  return None
1531
-
1532
- # Extract the function name for export
1533
- func_name = func_stmt.name.value if hasattr(func_stmt.name, 'value') else str(func_stmt.name)
1534
-
1535
- # Create a compound statement: the function definition + export
1536
- # We'll use a BlockStatement to hold both
1537
- from ..zexus_ast import BlockStatement, ExportStatement
1538
- export_stmt = ExportStatement(names=[Identifier(func_name)])
1539
-
1540
- # Return a block containing both statements
1541
- return BlockStatement(statements=[func_stmt, export_stmt])
2022
+
2023
+ decl_name = getattr(declaration, "name", None)
2024
+ export_names = []
2025
+
2026
+ if isinstance(decl_name, Identifier):
2027
+ export_names.append(decl_name)
2028
+ elif decl_name is not None:
2029
+ export_names.append(Identifier(str(decl_name)))
2030
+
2031
+ if not export_names:
2032
+ self.errors.append(
2033
+ f"Line {token.line}:{token.column} - Unable to determine export name"
2034
+ )
2035
+ return declaration
2036
+
2037
+ export_stmt = ExportStatement(names=export_names)
2038
+ block = BlockStatement()
2039
+ block.statements.extend([declaration, export_stmt])
2040
+ return block
1542
2041
 
1543
2042
  names = []
1544
2043
 
@@ -2085,79 +2584,91 @@ class UltimateParser:
2085
2584
  return DeferStatement(code_block)
2086
2585
 
2087
2586
  def parse_pattern_statement(self):
2088
- """Parse pattern statement - pattern matching.
2089
-
2090
- Syntax:
2091
- pattern value {
2092
- case 1 => print "one";
2093
- case 2 => print "two";
2094
- default => print "other";
2095
- }
2096
- """
2587
+ """Parse pattern statement - pattern matching."""
2097
2588
  token = self.cur_token
2098
2589
 
2099
- # Parse expression to match against
2100
- if not self.expect_peek(IDENT):
2101
- self.errors.append(f"Line {token.line}:{token.column} - Expected identifier after 'pattern'")
2590
+ # Parse the expression being matched
2591
+ self.next_token()
2592
+ match_expression = self.parse_expression(LOWEST)
2593
+ if match_expression is None:
2594
+ self.errors.append(f"Line {token.line}:{token.column} - Expected expression after 'pattern'")
2102
2595
  return None
2103
- expression = Identifier(self.cur_token.literal)
2104
2596
 
2105
- # Expect opening brace
2106
2597
  if not self.expect_peek(LBRACE):
2107
2598
  self.errors.append(f"Line {token.line}:{token.column} - Expected '{{' after pattern expression")
2108
2599
  return None
2109
2600
 
2110
- # Parse pattern cases
2111
2601
  cases = []
2112
2602
  self.next_token()
2603
+ lambda_infix_fn = self.infix_parse_fns.get(LAMBDA)
2113
2604
 
2114
2605
  while not self.cur_token_is(RBRACE) and not self.cur_token_is(EOF):
2115
- # Expect 'case' or 'default'
2606
+ if self.cur_token_is(SEMICOLON):
2607
+ self.next_token()
2608
+ continue
2609
+
2116
2610
  if self.cur_token.literal == "case":
2117
2611
  self.next_token()
2118
- pattern = self.parse_expression(LOWEST)
2119
-
2120
- # Expect '=>'
2121
- if not self.expect_peek(ASSIGN): # Using = as stand-in for =>
2122
- self.errors.append(f"Line {self.cur_token.line}:{self.cur_token.column} - Expected '=>' in pattern case")
2123
- return None
2124
-
2612
+
2613
+ is_default = False
2614
+ pattern_node = None
2615
+
2616
+ if self.cur_token.literal == "default" and self.peek_token_is(LAMBDA):
2617
+ is_default = True
2618
+ pattern_node = "default"
2619
+ else:
2620
+ try:
2621
+ if lambda_infix_fn:
2622
+ self.infix_parse_fns.pop(LAMBDA, None)
2623
+ pattern_node = self.parse_expression(LOWEST)
2624
+ finally:
2625
+ if lambda_infix_fn:
2626
+ self.infix_parse_fns[LAMBDA] = lambda_infix_fn
2627
+
2628
+ if pattern_node is None and not is_default:
2629
+ self.errors.append(f"Line {self.cur_token.line}:{self.cur_token.column} - Expected pattern value before '=>'")
2630
+ return None
2631
+
2632
+ if not self.expect_peek(LAMBDA):
2633
+ self.errors.append(f"Line {self.cur_token.line}:{self.cur_token.column} - Expected '=>' in pattern case")
2634
+ return None
2635
+
2636
+ self.next_token()
2637
+
2638
+ parsed_block = False
2639
+ action_node = None
2640
+ if self.cur_token_is(LBRACE):
2641
+ action_node = self.parse_block("pattern-case")
2642
+ parsed_block = True
2643
+ else:
2644
+ action_node = self.parse_statement()
2645
+ if action_node is None:
2646
+ action_expr = self.parse_expression(LOWEST)
2647
+ if action_expr is not None:
2648
+ action_node = ExpressionStatement(expression=action_expr)
2649
+ else:
2650
+ self.errors.append(f"Line {self.cur_token.line}:{self.cur_token.column} - Expected action after '=>'")
2651
+ return None
2652
+
2653
+ cases.append(PatternCase(pattern_node if not is_default else "default", action_node))
2654
+
2655
+ advanced = False
2656
+ if parsed_block and self.cur_token_is(RBRACE):
2125
2657
  self.next_token()
2126
- action = self.parse_expression(LOWEST)
2127
-
2128
- cases.append(PatternCase(pattern, action))
2129
-
2130
- # Optional semicolon
2131
- if self.peek_token_is(SEMICOLON):
2132
- self.next_token()
2133
-
2134
- elif self.cur_token.literal == "default":
2658
+ advanced = True
2659
+
2660
+ while self.cur_token_is(SEMICOLON):
2135
2661
  self.next_token()
2136
-
2137
- # Expect '=>'
2138
- if not self.expect_peek(ASSIGN):
2139
- self.errors.append(f"Line {self.cur_token.line}:{self.cur_token.column} - Expected '=>' in default case")
2140
- return None
2141
-
2662
+ advanced = True
2663
+
2664
+ if not advanced and not self.cur_token_is(RBRACE):
2142
2665
  self.next_token()
2143
- action = self.parse_expression(LOWEST)
2144
-
2145
- cases.append(PatternCase("default", action))
2146
-
2147
- # Optional semicolon
2148
- if self.peek_token_is(SEMICOLON):
2149
- self.next_token()
2150
-
2151
- break # Default should be last
2152
-
2153
- self.next_token()
2154
2666
 
2155
- # Expect closing brace
2156
2667
  if not self.cur_token_is(RBRACE):
2157
2668
  self.errors.append(f"Line {self.cur_token.line}:{self.cur_token.column} - Expected '}}' after pattern cases")
2158
2669
  return None
2159
2670
 
2160
- return PatternStatement(expression, cases)
2671
+ return PatternStatement(match_expression, cases)
2161
2672
 
2162
2673
  def parse_enum_statement(self):
2163
2674
  """Parse enum statement - type-safe enumerations.
@@ -2242,24 +2753,42 @@ class UltimateParser:
2242
2753
  return None
2243
2754
 
2244
2755
  # Expect event variable name
2245
- if not self.expect_peek(IDENT):
2756
+ if self.peek_token_is(IDENT) or self.peek_token_is(EVENT):
2757
+ self.next_token()
2758
+ else:
2246
2759
  self.errors.append(f"Line {token.line}:{token.column} - Expected event variable name")
2247
2760
  return None
2248
- event_var = Identifier(self.cur_token.literal)
2761
+
2762
+ event_literal = (
2763
+ self.cur_token.literal
2764
+ if hasattr(self.cur_token, "literal") and self.cur_token.literal is not None
2765
+ else getattr(self.cur_token, "value", None)
2766
+ )
2767
+ if not event_literal:
2768
+ event_literal = str(self.cur_token.type).lower()
2769
+ event_var = Identifier(event_literal)
2249
2770
 
2250
2771
  # Expect '=>'
2251
- if not self.expect_peek(ASSIGN): # Using = as stand-in for =>
2772
+ if not self.expect_peek(LAMBDA):
2252
2773
  self.errors.append(f"Line {self.cur_token.line}:{self.cur_token.column} - Expected '=>' after event variable")
2253
2774
  return None
2254
2775
 
2255
- # Expect block
2256
- if not self.expect_peek(LBRACE):
2257
- self.errors.append(f"Line {self.cur_token.line}:{self.cur_token.column} - Expected '{{' for stream handler")
2258
- return None
2776
+ self.next_token()
2259
2777
 
2260
- handler = self.parse_block("stream")
2261
- if handler is None:
2262
- return None
2778
+ if self.cur_token_is(LBRACE):
2779
+ handler = self.parse_block("stream")
2780
+ if handler is None:
2781
+ return None
2782
+ else:
2783
+ handler_expr = self.parse_expression(LOWEST)
2784
+ if handler_expr is None:
2785
+ self.errors.append(f"Line {self.cur_token.line}:{self.cur_token.column} - Expected handler after '=>'")
2786
+ return None
2787
+ handler = BlockStatement()
2788
+ handler.statements.append(ExpressionStatement(handler_expr))
2789
+
2790
+ if self.peek_token_is(SEMICOLON):
2791
+ self.next_token()
2263
2792
 
2264
2793
  return StreamStatement(stream_name, event_var, handler)
2265
2794
 
@@ -2277,7 +2806,12 @@ class UltimateParser:
2277
2806
 
2278
2807
  # Parse watched expression
2279
2808
  self.next_token()
2280
- watched_expr = self.parse_expression(LOWEST)
2809
+ lambda_infix = self.infix_parse_fns.pop(LAMBDA, None)
2810
+ try:
2811
+ watched_expr = self.parse_expression(LOWEST)
2812
+ finally:
2813
+ if lambda_infix is not None:
2814
+ self.infix_parse_fns[LAMBDA] = lambda_infix
2281
2815
 
2282
2816
  if watched_expr is None:
2283
2817
  self.errors.append(f"Line {token.line}:{token.column} - Expected expression after 'watch'")
@@ -2294,7 +2828,13 @@ class UltimateParser:
2294
2828
  reaction = self.parse_block("watch")
2295
2829
  else:
2296
2830
  self.next_token()
2297
- reaction = self.parse_expression(LOWEST)
2831
+ reaction_block = BlockStatement()
2832
+ stmt = self.parse_statement()
2833
+ if stmt is None:
2834
+ self.errors.append(f"Line {self.cur_token.line}:{self.cur_token.column} - Expected reaction after '=>'")
2835
+ return None
2836
+ reaction_block.statements.append(stmt)
2837
+ reaction = reaction_block
2298
2838
 
2299
2839
  if reaction is None:
2300
2840
  self.errors.append(f"Line {self.cur_token.line}:{self.cur_token.column} - Expected reaction after '=>'")
@@ -2398,12 +2938,16 @@ class UltimateParser:
2398
2938
  self.errors.append("Expected parameter name")
2399
2939
  return None
2400
2940
 
2401
- params.append(Identifier(self.cur_token.literal))
2941
+ param_name = self.cur_token.literal
2942
+ param_type = None
2402
2943
 
2403
- # Skip optional type annotation: : type
2944
+ # Capture optional type annotation: : type
2404
2945
  if self.peek_token_is(COLON):
2405
2946
  self.next_token() # Move to :
2406
- self.next_token() # Move to type (skip it)
2947
+ self.next_token() # Move to type
2948
+ param_type = self.cur_token.literal
2949
+
2950
+ params.append(Identifier(param_name, type_annotation=param_type))
2407
2951
 
2408
2952
  while self.peek_token_is(COMMA):
2409
2953
  self.next_token()
@@ -2411,12 +2955,16 @@ class UltimateParser:
2411
2955
  if not self.cur_token_is(IDENT):
2412
2956
  self.errors.append("Expected parameter name after comma")
2413
2957
  return None
2414
- params.append(Identifier(self.cur_token.literal))
2958
+ param_name = self.cur_token.literal
2959
+ param_type = None
2415
2960
 
2416
- # Skip optional type annotation: : type
2961
+ # Capture optional type annotation: : type
2417
2962
  if self.peek_token_is(COLON):
2418
2963
  self.next_token() # Move to :
2419
- self.next_token() # Move to type (skip it)
2964
+ self.next_token() # Move to type
2965
+ param_type = self.cur_token.literal
2966
+
2967
+ params.append(Identifier(param_name, type_annotation=param_type))
2420
2968
 
2421
2969
  if not self.expect_peek(RPAREN):
2422
2970
  self.errors.append("Expected ')' after parameters")
@@ -2586,9 +3134,8 @@ class UltimateParser:
2586
3134
  file_path = self.cur_token.literal
2587
3135
 
2588
3136
  alias = None
2589
- if self.peek_token_is(IDENT) and self.peek_token.literal == "as":
2590
- self.next_token()
2591
- self.next_token()
3137
+ if self.peek_token_is(AS) or (self.peek_token_is(IDENT) and self.peek_token.literal == "as"):
3138
+ self.next_token() # consume 'as'
2592
3139
  if not self.expect_peek(IDENT):
2593
3140
  self.errors.append("Expected alias name after 'as'")
2594
3141
  return None
@@ -2611,10 +3158,149 @@ class UltimateParser:
2611
3158
  stmt.body = self.parse_block_statement()
2612
3159
  return stmt
2613
3160
 
3161
+ def parse_color_statement(self):
3162
+ if not self.expect_peek(IDENT):
3163
+ self.errors.append("Expected color name after 'color'")
3164
+ return None
3165
+
3166
+ name = Identifier(self.cur_token.literal)
3167
+ value = None
3168
+
3169
+ if self.peek_token_is(ASSIGN):
3170
+ self.next_token() # move to '='
3171
+ self.next_token() # move to expression start
3172
+ value = self.parse_expression(LOWEST)
3173
+ elif self.peek_token_is(LBRACE):
3174
+ self.next_token() # consume '{'
3175
+ value = self.parse_block_statement()
3176
+ else:
3177
+ self.errors.append("Expected '=' or '{' after color name")
3178
+ return None
3179
+
3180
+ if self.peek_token_is(SEMICOLON):
3181
+ self.next_token()
3182
+
3183
+ return ColorStatement(name, value)
3184
+
3185
+ def parse_canvas_statement(self):
3186
+ if not self.expect_peek(IDENT):
3187
+ self.errors.append("Expected canvas name after 'canvas'")
3188
+ return None
3189
+
3190
+ name = Identifier(self.cur_token.literal)
3191
+ properties = None
3192
+ body = None
3193
+
3194
+ if self.peek_token_is(ASSIGN):
3195
+ self.next_token()
3196
+ self.next_token()
3197
+ properties = self.parse_expression(LOWEST)
3198
+ elif self.peek_token_is(LPAREN):
3199
+ self.next_token() # consume '('
3200
+ properties = self.parse_expression_list(RPAREN)
3201
+
3202
+ if self.peek_token_is(LBRACE):
3203
+ self.next_token()
3204
+ body = self.parse_block_statement()
3205
+
3206
+ if self.peek_token_is(SEMICOLON):
3207
+ self.next_token()
3208
+
3209
+ return CanvasStatement(name, properties=properties, body=body)
3210
+
3211
+ def parse_graphics_statement(self):
3212
+ if not self.expect_peek(IDENT):
3213
+ self.errors.append("Expected graphics name after 'graphics'")
3214
+ return None
3215
+
3216
+ name = Identifier(self.cur_token.literal)
3217
+ body = None
3218
+ if self.peek_token_is(ASSIGN):
3219
+ self.next_token()
3220
+ self.next_token()
3221
+ expr = self.parse_expression(LOWEST)
3222
+ body = BlockStatement()
3223
+ stmt = ExpressionStatement(expression=expr)
3224
+ body.statements.append(stmt)
3225
+ elif self.peek_token_is(LBRACE):
3226
+ self.next_token()
3227
+ body = self.parse_block_statement()
3228
+ else:
3229
+ self.errors.append("Expected '=' or '{' after graphics name")
3230
+ return None
3231
+
3232
+ if self.peek_token_is(SEMICOLON):
3233
+ self.next_token()
3234
+
3235
+ return GraphicsStatement(name, body=body)
3236
+
3237
+ def parse_animation_statement(self):
3238
+ if not self.expect_peek(IDENT):
3239
+ self.errors.append("Expected animation name after 'animation'")
3240
+ return None
3241
+
3242
+ name = Identifier(self.cur_token.literal)
3243
+ properties = None
3244
+ body = None
3245
+
3246
+ if self.peek_token_is(LPAREN):
3247
+ self.next_token()
3248
+ properties = self.parse_expression_list(RPAREN)
3249
+ elif self.peek_token_is(ASSIGN):
3250
+ self.next_token()
3251
+ self.next_token()
3252
+ properties = self.parse_expression(LOWEST)
3253
+
3254
+ if self.peek_token_is(LBRACE):
3255
+ self.next_token()
3256
+ body = self.parse_block_statement()
3257
+
3258
+ if self.peek_token_is(SEMICOLON):
3259
+ self.next_token()
3260
+
3261
+ return AnimationStatement(name, body=body, properties=properties)
3262
+
3263
+ def parse_clock_statement(self):
3264
+ if not self.expect_peek(IDENT):
3265
+ self.errors.append("Expected clock name after 'clock'")
3266
+ return None
3267
+
3268
+ name = Identifier(self.cur_token.literal)
3269
+ properties = None
3270
+
3271
+ if self.peek_token_is(LPAREN):
3272
+ self.next_token()
3273
+ properties = self.parse_expression_list(RPAREN)
3274
+ elif self.peek_token_is(ASSIGN):
3275
+ self.next_token()
3276
+ self.next_token()
3277
+ properties = self.parse_expression(LOWEST)
3278
+ elif self.peek_token_is(LBRACE):
3279
+ self.next_token()
3280
+ properties = self.parse_block_statement()
3281
+
3282
+ if self.peek_token_is(SEMICOLON):
3283
+ self.next_token()
3284
+
3285
+ return ClockStatement(name, properties=properties)
3286
+
2614
3287
  def parse_return_statement(self):
2615
3288
  stmt = ReturnStatement(return_value=None)
3289
+ # Handle bare `return` without value
3290
+ if self.peek_token_is(SEMICOLON):
3291
+ # Advance to semicolon so outer loop can consume it on next iteration
3292
+ self.next_token()
3293
+ return stmt
3294
+
3295
+ if self.peek_token_is(RBRACE) or self.peek_token_is(EOF):
3296
+ # No explicit return value; leave current token on 'return'
3297
+ return stmt
3298
+
3299
+ # Otherwise parse the return value expression
2616
3300
  self.next_token()
2617
3301
  stmt.return_value = self.parse_expression(LOWEST)
3302
+ if self.peek_token_is(SEMICOLON):
3303
+ self.next_token()
2618
3304
  return stmt
2619
3305
 
2620
3306
  def parse_continue_statement(self):
@@ -2660,20 +3346,29 @@ class UltimateParser:
2660
3346
  if left_exp is None:
2661
3347
  return None
2662
3348
 
3349
+ debug_enabled = config.enable_debug_logs
3350
+
2663
3351
  # Stop parsing when we hit closing delimiters or terminators
2664
3352
  # This prevents the parser from trying to parse beyond expression boundaries
2665
- while (not self.peek_token_is(SEMICOLON) and
3353
+ while (not self.peek_token_is(SEMICOLON) and
2666
3354
  not self.peek_token_is(EOF) and
2667
3355
  not self.peek_token_is(RPAREN) and
2668
3356
  not self.peek_token_is(RBRACE) and
2669
- not self.peek_token_is(RBRACKET) and
3357
+ not self.peek_token_is(RBRACKET) and
3358
+ not self.peek_token_is(LBRACE) and
2670
3359
  precedence <= self.peek_precedence()):
2671
3360
 
2672
- print(f"[EXPR LOOP] cur={self.cur_token.literal}@L{self.cur_token.line}, peek={self.peek_token.literal}@L{self.peek_token.line}, precedence={precedence}, peek_prec={self.peek_precedence()}")
3361
+ if debug_enabled:
3362
+ print(
3363
+ f"[EXPR LOOP] cur={self.cur_token.literal}@L{self.cur_token.line}, peek={self.peek_token.literal}@L{self.peek_token.line}, precedence={precedence}, peek_prec={self.peek_precedence()}"
3364
+ )
2673
3365
  # CRITICAL FIX: Stop if next token is on a new line and could start a new statement
2674
3366
  # This prevents expressions from spanning multiple logical lines
2675
3367
  if self.cur_token.line < self.peek_token.line:
2676
- print(f"[NEWLINE CHECK] cur_line={self.cur_token.line}, peek_line={self.peek_token.line}, peek_type={self.peek_token.type}, peek_lit={self.peek_token.literal}")
3368
+ if debug_enabled:
3369
+ print(
3370
+ f"[NEWLINE CHECK] cur_line={self.cur_token.line}, peek_line={self.peek_token.line}, peek_type={self.peek_token.type}, peek_lit={self.peek_token.literal}"
3371
+ )
2677
3372
  # Next token is on a new line - check if it could start a new statement
2678
3373
  next_could_be_statement = (
2679
3374
  self.peek_token.type == IDENT or
@@ -2682,9 +3377,12 @@ class UltimateParser:
2682
3377
  self.peek_token.type == RETURN or
2683
3378
  self.peek_token.type == IF or
2684
3379
  self.peek_token.type == WHILE or
2685
- self.peek_token.type == FOR
3380
+ self.peek_token.type == FOR or
3381
+ self.peek_token.type == FUNCTION or
3382
+ self.peek_token.type == ACTION
2686
3383
  )
2687
- print(f"[NEWLINE CHECK] next_could_be_statement={next_could_be_statement}")
3384
+ if debug_enabled:
3385
+ print(f"[NEWLINE CHECK] next_could_be_statement={next_could_be_statement}")
2688
3386
  if next_could_be_statement:
2689
3387
  # Additional check: is the next token followed by [ or = ?
2690
3388
  # This would indicate it's an assignment/index expression starting
@@ -2692,16 +3390,16 @@ class UltimateParser:
2692
3390
  # Save current state to peek ahead
2693
3391
  saved_cur = self.cur_token
2694
3392
  saved_peek = self.peek_token
2695
- saved_pos = self.cur_pos
2696
-
3393
+ lexer_snapshot = self._snapshot_lexer_state()
3394
+
2697
3395
  # Peek ahead one more token
2698
3396
  self.next_token() # Now peek_token is what we want to check
2699
3397
  next_next = self.peek_token
2700
-
3398
+
2701
3399
  # Restore state
3400
+ self._restore_lexer_state(lexer_snapshot)
2702
3401
  self.cur_token = saved_cur
2703
3402
  self.peek_token = saved_peek
2704
- self.cur_pos = saved_pos
2705
3403
 
2706
3404
  # If next token after IDENT is LBRACKET or ASSIGN, it's likely a new statement
2707
3405
  if next_next.type in (LBRACKET, ASSIGN, LPAREN):
@@ -2712,6 +3410,9 @@ class UltimateParser:
2712
3410
  if self.peek_token.type not in self.infix_parse_fns:
2713
3411
  return left_exp
2714
3412
 
3413
+ if self.peek_token.type == LBRACE and not self._can_start_constructor(left_exp):
3414
+ break
3415
+
2715
3416
  infix = self.infix_parse_fns[self.peek_token.type]
2716
3417
  self.next_token()
2717
3418
  left_exp = infix(left_exp)
@@ -2719,13 +3420,67 @@ class UltimateParser:
2719
3420
  if left_exp is None:
2720
3421
  return None
2721
3422
 
3423
+ if (self.peek_token_is(LBRACE) and
3424
+ self._can_start_constructor(left_exp) and
3425
+ self._brace_starts_constructor()):
3426
+ self.next_token()
3427
+ left_exp = self.parse_constructor_call_expression(left_exp)
3428
+
2722
3429
  return left_exp
2723
3430
 
3431
+ def _can_start_constructor(self, left_exp):
3432
+ from ..zexus_ast import Identifier, CallExpression, ExpressionStatement
3433
+ # Allow constructor syntax for identifiers or chained calls
3434
+ if isinstance(left_exp, Identifier):
3435
+ return True
3436
+ if isinstance(left_exp, CallExpression):
3437
+ return True
3438
+ if isinstance(left_exp, ExpressionStatement):
3439
+ return self._can_start_constructor(left_exp.expression)
3440
+ return False
3441
+
3442
+ def _brace_starts_constructor(self):
3443
+ """Look ahead to determine if the upcoming '{' begins a constructor literal."""
3444
+ if not self.peek_token_is(LBRACE):
3445
+ return False
3446
+
3447
+ saved_cur = self.cur_token
3448
+ saved_peek = self.peek_token
3449
+ lexer_snapshot = self._snapshot_lexer_state()
3450
+
3451
+ try:
3452
+ # Move to '{'
3453
+ self.next_token()
3454
+ # Move to first token inside braces
3455
+ self.next_token()
3456
+ inner_first = self.cur_token
3457
+ inner_second = self.peek_token
3458
+
3459
+ if inner_first is None:
3460
+ return False
3461
+
3462
+ # Empty constructor literal like Foo{}
3463
+ if inner_first.type == RBRACE:
3464
+ return True
3465
+
3466
+ if inner_first.type not in (IDENT, STRING):
3467
+ return False
3468
+
3469
+ if inner_second is None:
3470
+ return False
3471
+
3472
+ return inner_second.type == COLON
3473
+ finally:
3474
+ self._restore_lexer_state(lexer_snapshot)
3475
+ self.cur_token = saved_cur
3476
+ self.peek_token = saved_peek
3477
+
2724
3478
  def parse_identifier(self):
2725
3479
  # Allow DEBUG keyword to be used as identifier in expression contexts
2726
3480
  # This enables debug(value) function calls while keeping debug value; statements
2727
- if self.cur_token.type == DEBUG:
2728
- return Identifier(value="debug")
3481
+ if self.cur_token.type in {DEBUG, EVENT}:
3482
+ literal = getattr(self.cur_token, 'literal', None)
3483
+ return Identifier(value=literal if literal is not None else self.cur_token.type.lower())
2729
3484
  return Identifier(value=self.cur_token.literal)
2730
3485
 
2731
3486
  def parse_integer_literal(self):
@@ -2745,17 +3500,44 @@ class UltimateParser:
2745
3500
  def parse_string_literal(self):
2746
3501
  return StringLiteral(value=self.cur_token.literal)
2747
3502
 
3503
+ def parse_interpolated_string(self):
3504
+ """Parse a string with ${expr} interpolation.
3505
+
3506
+ The token literal is a list of ("str", text) or ("expr", source) tuples
3507
+ produced by the lexer. For each "expr" part, we create a sub-lexer and
3508
+ sub-parser to parse the expression source into an AST node.
3509
+ """
3510
+ raw_parts = self.cur_token.literal
3511
+ parsed_parts = []
3512
+ for part_type, part_value in raw_parts:
3513
+ if part_type == "str":
3514
+ parsed_parts.append(("str", part_value))
3515
+ elif part_type == "expr":
3516
+ # Parse the expression using a sub-parser
3517
+ sub_lexer = Lexer(part_value)
3518
+ sub_parser = Parser(sub_lexer)
3519
+ expr_node = sub_parser.parse_expression(LOWEST)
3520
+ if expr_node is None:
3521
+ # Fallback: treat as empty string
3522
+ parsed_parts.append(("str", ""))
3523
+ else:
3524
+ parsed_parts.append(("expr", expr_node))
3525
+ return StringInterpolationExpression(parts=parsed_parts)
3526
+
2748
3527
  def parse_boolean(self):
2749
3528
  lit = getattr(self.cur_token, 'literal', '')
2750
3529
  val = True if isinstance(lit, str) and lit.lower() == 'true' else False
2751
3530
  # Transient trace to diagnose boolean parsing
2752
- try:
2753
- if lit.lower() == 'false':
2754
- import traceback as _tb
2755
- stack = ''.join(_tb.format_stack(limit=4)[-2:])
2756
- print(f"[PARSE_BOOL_TRACE] false token at position {self.lexer.position}: literal={lit}, val={val}\n{stack}")
2757
- except Exception:
2758
- pass
3531
+ if config.enable_debug_logs:
3532
+ try:
3533
+ if lit.lower() == 'false':
3534
+ import traceback as _tb
3535
+ stack = ''.join(_tb.format_stack(limit=4)[-2:])
3536
+ print(
3537
+ f"[PARSE_BOOL_TRACE] false token at position {self.lexer.position}: literal={lit}, val={val}\n{stack}"
3538
+ )
3539
+ except Exception:
3540
+ pass
2759
3541
  return Boolean(value=val)
2760
3542
 
2761
3543
  def parse_null(self):
@@ -2804,6 +3586,48 @@ class UltimateParser:
2804
3586
  expr = self.parse_expression(PREFIX)
2805
3587
  return AsyncExpression(expression=expr)
2806
3588
 
3589
+ def parse_await_expression(self):
3590
+ """Parse await expression: await <expression>"""
3591
+ from ..zexus_ast import AwaitExpression
3592
+
3593
+ self.next_token() # consume 'await'
3594
+ awaited = self.parse_expression(PREFIX)
3595
+ return AwaitExpression(expression=awaited)
3596
+
3597
+ def parse_find_expression(self):
3598
+ from ..zexus_ast import FindExpression
3599
+
3600
+ token = self.cur_token
3601
+ self.next_token()
3602
+ target = self.parse_expression(LOWEST)
3603
+
3604
+ scope = None
3605
+ if self.peek_token_is(IN):
3606
+ self.next_token() # move to IN
3607
+ self.next_token() # move to first token of scope expression
3608
+ scope = self.parse_expression(LOWEST)
3609
+
3610
+ expr = FindExpression(target=target, scope=scope)
3611
+ setattr(expr, 'token', token)
3612
+ return expr
3613
+
3614
+ def parse_load_expression(self):
3615
+ from ..zexus_ast import LoadExpression
3616
+
3617
+ token = self.cur_token
3618
+ self.next_token()
3619
+ target = self.parse_expression(LOWEST)
3620
+
3621
+ source = None
3622
+ if (self.peek_token_is(IDENT) and self.peek_token.literal == "from"):
3623
+ self.next_token() # move to 'from'
3624
+ self.next_token() # move to start of source expression
3625
+ source = self.parse_expression(LOWEST)
3626
+
3627
+ expr = LoadExpression(target=target, source=source)
3628
+ setattr(expr, 'token', token)
3629
+ return expr
3630
+
2807
3631
  def parse_infix_expression(self, left):
2808
3632
  expression = InfixExpression(left=left, operator=self.cur_token.literal, right=None)
2809
3633
  precedence = self.cur_precedence()
@@ -2812,34 +3636,23 @@ class UltimateParser:
2812
3636
  return expression
2813
3637
 
2814
3638
  def parse_grouped_expression(self):
2815
- # Special-case: if this parenthesized group is followed by a lambda arrow
2816
- # treat its contents as a parameter list for an arrow-style lambda: (a, b) => ...
2817
- # The lexer sets a hint flag when it detects a ')' followed by '=>'. Use
2818
- # that as a fast-path check to parse the contents as parameter identifiers.
2819
- if getattr(self.lexer, '_next_paren_has_lambda', False) or self._lookahead_token_after_matching_paren() == LAMBDA:
2820
- # Consume '('
2821
- self.next_token()
2822
- self.lexer._next_paren_has_lambda = False # Clear lexer hint after consuming parenthesis
2823
- params = []
2824
- # If immediate RPAREN, empty params
2825
- if self.cur_token_is(RPAREN):
2826
- self.next_token()
2827
- return ListLiteral(elements=params)
3639
+ is_lambda_param_list = (
3640
+ getattr(self.lexer, '_next_paren_has_lambda', False)
3641
+ or self._lookahead_token_after_matching_paren() == LAMBDA
3642
+ )
2828
3643
 
2829
- # Collect identifiers separated by commas
2830
- if self.cur_token_is(IDENT):
2831
- params.append(Identifier(self.cur_token.literal))
3644
+ if is_lambda_param_list:
3645
+ self.next_token() # move to first token inside the parentheses
3646
+ self.lexer._next_paren_has_lambda = False
2832
3647
 
2833
- while self.peek_token_is(COMMA):
2834
- self.next_token() # move to comma
2835
- self.next_token() # move to next identifier
2836
- if self.cur_token_is(IDENT):
2837
- params.append(Identifier(self.cur_token.literal))
2838
- else:
2839
- self.errors.append(f"Line {self.cur_token.line}:{self.cur_token.column} - Expected parameter name")
2840
- break
3648
+ params = []
2841
3649
 
2842
-
3650
+ if not self.cur_token_is(RPAREN):
3651
+ params = self._parse_parameter_list()
3652
+ if not self.expect_peek(RPAREN):
3653
+ return None
3654
+ # When the parameter list is empty, the current token is already RPAREN.
3655
+ return ListLiteral(elements=params)
2843
3656
 
2844
3657
  # Default grouped expression behavior
2845
3658
  self.next_token()
@@ -2853,11 +3666,39 @@ class UltimateParser:
2853
3666
  # current token is LBRACKET (parser calls this after advancing to that token)
2854
3667
  # Move to the first token inside the brackets
2855
3668
  self.next_token()
2856
- index_expr = self.parse_expression(LOWEST)
3669
+ start_expr = None
3670
+ end_expr = None
3671
+
3672
+ if self.cur_token_is(COLON):
3673
+ # Slice with omitted start: obj[:end]
3674
+ if self.peek_token_is(RBRACKET):
3675
+ # obj[:]
3676
+ self.next_token()
3677
+ return SliceExpression(object=left, start=None, end=None)
3678
+ self.next_token()
3679
+ end_expr = self.parse_expression(LOWEST)
3680
+ if not self.expect_peek(RBRACKET):
3681
+ return None
3682
+ return SliceExpression(object=left, start=None, end=end_expr)
3683
+
3684
+ start_expr = self.parse_expression(LOWEST)
3685
+
3686
+ if self.peek_token_is(COLON):
3687
+ # Slice with explicit start: obj[start:end]
3688
+ self.next_token() # move to ':'
3689
+ if self.peek_token_is(RBRACKET):
3690
+ self.next_token() # move to ']'
3691
+ return SliceExpression(object=left, start=start_expr, end=None)
3692
+ self.next_token()
3693
+ end_expr = self.parse_expression(LOWEST)
3694
+ if not self.expect_peek(RBRACKET):
3695
+ return None
3696
+ return SliceExpression(object=left, start=start_expr, end=end_expr)
3697
+
2857
3698
  # Expect closing bracket
2858
3699
  if not self.expect_peek(RBRACKET):
2859
3700
  return None
2860
- return PropertyAccessExpression(object=left, property=index_expr, computed=True)
3701
+ return PropertyAccessExpression(object=left, property=start_expr, computed=True)
2861
3702
 
2862
3703
  def _lookahead_token_after_matching_paren(self):
2863
3704
  """Character-level lookahead: detect if the matching ')' is followed by '=>' (arrow).
@@ -2892,6 +3733,73 @@ class UltimateParser:
2892
3733
 
2893
3734
  return None
2894
3735
 
3736
+ def parse_match_expression(self):
3737
+ """Parse match expression: match value { case p: r, ... } or match value { p => r, ... }"""
3738
+ expression = MatchExpression(value=None, cases=[])
3739
+
3740
+ self.next_token() # Consume MATCH
3741
+
3742
+ expression.value = self.parse_expression(LOWEST)
3743
+
3744
+ if not self.expect_peek(LBRACE):
3745
+ return None
3746
+
3747
+ while not self.peek_token_is(RBRACE) and not self.peek_token_is(EOF):
3748
+ if self.peek_token_is(CASE):
3749
+ # case pattern: result syntax
3750
+ self.next_token() # Consume CASE
3751
+ if not self.peek_token_is(COLON):
3752
+ self.next_token()
3753
+ pattern = self.parse_expression(LOWEST)
3754
+ if not self.expect_peek(COLON):
3755
+ return None
3756
+ result = None
3757
+ if self.peek_token_is(LBRACE):
3758
+ if not self.expect_peek(LBRACE):
3759
+ return None
3760
+ result = self.parse_block_statement()
3761
+ else:
3762
+ self.next_token()
3763
+ result = self.parse_expression(LOWEST)
3764
+ if self.peek_token_is(COMMA) or self.peek_token_is(SEMICOLON):
3765
+ self.next_token()
3766
+ case = MatchCase(pattern=pattern, result=result)
3767
+ expression.cases.append(case)
3768
+ elif self.peek_token_is(DEFAULT):
3769
+ # default: result syntax
3770
+ self.next_token() # Consume DEFAULT
3771
+ if not self.expect_peek(COLON):
3772
+ return None
3773
+ self.next_token()
3774
+ result = self.parse_expression(LOWEST)
3775
+ if self.peek_token_is(COMMA) or self.peek_token_is(SEMICOLON):
3776
+ self.next_token()
3777
+ pattern = Identifier(value="_")
3778
+ case = MatchCase(pattern=pattern, result=result)
3779
+ expression.cases.append(case)
3780
+ else:
3781
+ # Arrow syntax: pattern => result
3782
+ self.next_token() # Move to pattern token
3783
+ pattern = self.parse_expression(LOWEST)
3784
+
3785
+ # Expect => (LAMBDA token with literal '=>')
3786
+ if self.peek_token_is(LAMBDA):
3787
+ self.next_token() # Consume =>
3788
+ self.next_token() # Move to result
3789
+ result = self.parse_expression(LOWEST)
3790
+ if self.peek_token_is(COMMA) or self.peek_token_is(SEMICOLON):
3791
+ self.next_token()
3792
+ case = MatchCase(pattern=pattern, result=result)
3793
+ expression.cases.append(case)
3794
+ else:
3795
+ # Skip unexpected tokens
3796
+ pass
3797
+
3798
+ if not self.expect_peek(RBRACE):
3799
+ return None
3800
+
3801
+ return expression
3802
+
2895
3803
  def parse_if_expression(self):
2896
3804
  """Parse if expression - handles both statement form and expression form
2897
3805
 
@@ -3131,17 +4039,27 @@ class UltimateParser:
3131
4039
  storage_vars = []
3132
4040
  actions = []
3133
4041
 
3134
- while not self.cur_token_is(RBRACE) and not self.cur_token_is(EOF):
4042
+ while not self.cur_token_is(EOF):
3135
4043
  self.next_token()
4044
+
4045
+ # Parse modifiers preceding the declaration
4046
+ modifiers = self._parse_modifiers()
3136
4047
 
3137
4048
  if self.cur_token_is(RBRACE):
4049
+ # If more declarations follow, skip this brace (close of inner block)
4050
+ if (self.peek_token_is(ACTION) or self.peek_token_is(STATE) or self.peek_token_is(DATA) or
4051
+ (self.peek_token_is(IDENT) and getattr(self.peek_token, 'literal', None) == 'persistent')):
4052
+ continue
3138
4053
  break
3139
4054
 
3140
4055
  # Check for state variable declaration
3141
4056
  if self.cur_token_is(STATE):
3142
4057
  state_stmt = self.parse_state_statement()
3143
4058
  if state_stmt:
4059
+ # Attach parsed modifiers
4060
+ state_stmt.modifiers = modifiers
3144
4061
  storage_vars.append(state_stmt)
4062
+ print(f"DEBUG: Parsed state {state_stmt.name.value} modifiers={modifiers}")
3145
4063
 
3146
4064
  # Check for data member declaration
3147
4065
  elif self.cur_token_is(DATA):
@@ -3156,12 +4074,12 @@ class UltimateParser:
3156
4074
  self.next_token() # Move to value
3157
4075
  data_value = self.parse_expression(LOWEST)
3158
4076
 
3159
- # Create a let statement for the data member
3160
- from ..zexus_ast import LetStatement
3161
- data_stmt = LetStatement()
3162
- data_stmt.name = Identifier(data_name)
3163
- data_stmt.value = data_value
4077
+ # Treat contract data as state with default value
4078
+ from ..zexus_ast import StateStatement
4079
+ # Pass modifiers to constructor
4080
+ data_stmt = StateStatement(Identifier(data_name), data_value, modifiers=modifiers)
3164
4081
  storage_vars.append(data_stmt)
4082
+ print(f"DEBUG: Parsed data {data_name} modifiers={modifiers}")
3165
4083
 
3166
4084
  # Consume optional semicolon (same as parse_state_statement)
3167
4085
  if self.peek_token_is(SEMICOLON):
@@ -3174,21 +4092,31 @@ class UltimateParser:
3174
4092
  self.next_token()
3175
4093
  if self.cur_token_is(IDENT):
3176
4094
  storage_name = self.cur_token.literal
4095
+ # Note: Persistent storage doesn't support standard modifiers yet
3177
4096
  storage_vars.append({"name": storage_name})
3178
4097
 
3179
4098
  # Check for action definition
3180
4099
  elif self.cur_token_is(ACTION):
3181
4100
  action = self.parse_action_statement()
3182
4101
  if action:
4102
+ # Attach parsed modifiers
4103
+ action.modifiers = modifiers
3183
4104
  actions.append(action)
3184
4105
 
3185
- self.expect_peek(RBRACE)
4106
+ if not self.cur_token_is(RBRACE):
4107
+ # Tolerant: if the contract body ends at EOF, don't emit a hard error
4108
+ if not self.peek_token_is(EOF):
4109
+ self.expect_peek(RBRACE)
3186
4110
 
3187
4111
  # Create body block with storage vars and actions
3188
4112
  body = BlockStatement()
3189
4113
  body.statements = storage_vars + actions
4114
+
4115
+ contract_node = ContractStatement(contract_name, body, modifiers=[], implements=implements)
4116
+ contract_node.storage_vars = storage_vars
4117
+ contract_node.actions = actions
3190
4118
 
3191
- return ContractStatement(contract_name, body, modifiers=[], implements=implements)
4119
+ return contract_node
3192
4120
 
3193
4121
  def parse_protect_statement(self):
3194
4122
  """Parse protect statement
@@ -3815,7 +4743,8 @@ class UltimateParser:
3815
4743
 
3816
4744
  Asserts condition, reverts transaction if false.
3817
4745
  """
3818
- print(f"[DEBUG PARSER] parse_require_statement called", flush=True)
4746
+ if config.enable_debug_logs:
4747
+ print("[DEBUG PARSER] parse_require_statement called", flush=True)
3819
4748
  token = self.cur_token
3820
4749
 
3821
4750
  if not self.expect_peek(LPAREN):
@@ -3839,7 +4768,11 @@ class UltimateParser:
3839
4768
  if self.peek_token_is(SEMICOLON):
3840
4769
  self.next_token()
3841
4770
 
3842
- print(f"[DEBUG PARSER] Creating RequireStatement with condition={condition}, message={message}", flush=True)
4771
+ if config.enable_debug_logs:
4772
+ print(
4773
+ f"[DEBUG PARSER] Creating RequireStatement with condition={condition}, message={message}",
4774
+ flush=True,
4775
+ )
3843
4776
  return RequireStatement(condition=condition, message=message)
3844
4777
 
3845
4778
  def parse_revert_statement(self):
@@ -4092,5 +5025,13 @@ class UltimateParser:
4092
5025
 
4093
5026
  return ModifierDeclaration(name, parameters, body)
4094
5027
 
4095
- # Backward compatibility
4096
- Parser = UltimateParser
5028
+ # Backward compatibility facade: defaults to traditional parsing pipeline.
5029
+ class Parser(UltimateParser):
5030
+ def __init__(self, lexer, syntax_style=None, enable_advanced_strategies=True):
5031
+ if enable_advanced_strategies is None:
5032
+ enable_advanced_strategies = True
5033
+ super().__init__(
5034
+ lexer,
5035
+ syntax_style=syntax_style,
5036
+ enable_advanced_strategies=enable_advanced_strategies,
5037
+ )