zexus 1.6.8 → 1.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -5
- package/package.json +1 -1
- package/src/__init__.py +7 -0
- package/src/zexus/__init__.py +1 -1
- package/src/zexus/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/capability_system.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/debug_sanitizer.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/environment.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/error_reporter.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/input_validation.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/lexer.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/module_cache.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/module_manager.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/object.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/security.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/security_enforcement.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/syntax_validator.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/zexus_ast.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/zexus_token.cpython-312.pyc +0 -0
- package/src/zexus/access_control_system/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/access_control_system/__pycache__/access_control.cpython-312.pyc +0 -0
- package/src/zexus/advanced_types.py +17 -2
- package/src/zexus/blockchain/__init__.py +411 -0
- package/src/zexus/blockchain/accelerator.py +1160 -0
- package/src/zexus/blockchain/chain.py +660 -0
- package/src/zexus/blockchain/consensus.py +821 -0
- package/src/zexus/blockchain/contract_vm.py +1019 -0
- package/src/zexus/blockchain/crypto.py +79 -14
- package/src/zexus/blockchain/events.py +526 -0
- package/src/zexus/blockchain/loadtest.py +721 -0
- package/src/zexus/blockchain/monitoring.py +350 -0
- package/src/zexus/blockchain/mpt.py +716 -0
- package/src/zexus/blockchain/multichain.py +951 -0
- package/src/zexus/blockchain/multiprocess_executor.py +338 -0
- package/src/zexus/blockchain/network.py +886 -0
- package/src/zexus/blockchain/node.py +666 -0
- package/src/zexus/blockchain/rpc.py +1203 -0
- package/src/zexus/blockchain/rust_bridge.py +421 -0
- package/src/zexus/blockchain/storage.py +423 -0
- package/src/zexus/blockchain/tokens.py +750 -0
- package/src/zexus/blockchain/upgradeable.py +1004 -0
- package/src/zexus/blockchain/verification.py +1602 -0
- package/src/zexus/blockchain/wallet.py +621 -0
- package/src/zexus/capability_system.py +184 -9
- package/src/zexus/cli/__pycache__/main.cpython-312.pyc +0 -0
- package/src/zexus/cli/main.py +383 -34
- package/src/zexus/cli/zpm.py +1 -1
- package/src/zexus/compiler/__pycache__/bytecode.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/lexer.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/parser.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/semantic.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/zexus_ast.cpython-312.pyc +0 -0
- package/src/zexus/compiler/bytecode.py +124 -7
- package/src/zexus/compiler/compat_runtime.py +6 -2
- package/src/zexus/compiler/lexer.py +16 -5
- package/src/zexus/compiler/parser.py +108 -7
- package/src/zexus/compiler/semantic.py +18 -19
- package/src/zexus/compiler/zexus_ast.py +26 -1
- package/src/zexus/concurrency_system.py +79 -0
- package/src/zexus/config.py +54 -0
- package/src/zexus/crypto_bridge.py +244 -8
- package/src/zexus/dap/__init__.py +10 -0
- package/src/zexus/dap/__main__.py +4 -0
- package/src/zexus/dap/dap_server.py +391 -0
- package/src/zexus/dap/debug_engine.py +298 -0
- package/src/zexus/environment.py +112 -9
- package/src/zexus/evaluator/__pycache__/bytecode_compiler.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/core.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/expressions.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/functions.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/resource_limiter.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/statements.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/unified_execution.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/utils.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/bytecode_compiler.py +457 -37
- package/src/zexus/evaluator/core.py +644 -50
- package/src/zexus/evaluator/expressions.py +358 -62
- package/src/zexus/evaluator/functions.py +458 -20
- package/src/zexus/evaluator/resource_limiter.py +4 -4
- package/src/zexus/evaluator/statements.py +774 -122
- package/src/zexus/evaluator/unified_execution.py +573 -72
- package/src/zexus/evaluator/utils.py +14 -2
- package/src/zexus/evaluator_original.py +1 -1
- package/src/zexus/event_loop.py +186 -0
- package/src/zexus/lexer.py +742 -458
- package/src/zexus/lsp/__init__.py +1 -1
- package/src/zexus/lsp/definition_provider.py +163 -9
- package/src/zexus/lsp/server.py +22 -8
- package/src/zexus/lsp/symbol_provider.py +182 -9
- package/src/zexus/module_cache.py +239 -9
- package/src/zexus/module_manager.py +129 -1
- package/src/zexus/object.py +76 -6
- package/src/zexus/parser/__pycache__/parser.cpython-312.pyc +0 -0
- package/src/zexus/parser/__pycache__/strategy_context.cpython-312.pyc +0 -0
- package/src/zexus/parser/__pycache__/strategy_structural.cpython-312.pyc +0 -0
- package/src/zexus/parser/parser.py +1349 -408
- package/src/zexus/parser/strategy_context.py +755 -58
- package/src/zexus/parser/strategy_structural.py +121 -21
- package/src/zexus/persistence.py +15 -1
- package/src/zexus/renderer/__init__.py +61 -0
- package/src/zexus/renderer/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/backend.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/canvas.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/color_system.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/layout.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/main_renderer.cpython-312.pyc +0 -0
- package/src/zexus/renderer/__pycache__/painter.cpython-312.pyc +0 -0
- package/src/zexus/renderer/backend.py +261 -0
- package/src/zexus/renderer/canvas.py +78 -0
- package/src/zexus/renderer/color_system.py +201 -0
- package/src/zexus/renderer/graphics.py +31 -0
- package/src/zexus/renderer/layout.py +222 -0
- package/src/zexus/renderer/main_renderer.py +66 -0
- package/src/zexus/renderer/painter.py +30 -0
- package/src/zexus/renderer/tk_backend.py +208 -0
- package/src/zexus/renderer/web_backend.py +260 -0
- package/src/zexus/runtime/__init__.py +10 -2
- package/src/zexus/runtime/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/runtime/__pycache__/async_runtime.cpython-312.pyc +0 -0
- package/src/zexus/runtime/__pycache__/load_manager.cpython-312.pyc +0 -0
- package/src/zexus/runtime/file_flags.py +137 -0
- package/src/zexus/runtime/load_manager.py +368 -0
- package/src/zexus/safety/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/safety/__pycache__/memory_safety.cpython-312.pyc +0 -0
- package/src/zexus/security.py +424 -34
- package/src/zexus/stdlib/fs.py +23 -18
- package/src/zexus/stdlib/http.py +289 -186
- package/src/zexus/stdlib/sockets.py +207 -163
- package/src/zexus/stdlib/websockets.py +282 -0
- package/src/zexus/stdlib_integration.py +369 -2
- package/src/zexus/strategy_recovery.py +6 -3
- package/src/zexus/type_checker.py +423 -0
- package/src/zexus/virtual_filesystem.py +189 -2
- package/src/zexus/vm/__init__.py +113 -3
- package/src/zexus/vm/__pycache__/async_optimizer.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/bytecode.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/bytecode_converter.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/cache.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/compiler.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/gas_metering.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/jit.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/parallel_vm.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/vm.cpython-312.pyc +0 -0
- package/src/zexus/vm/async_optimizer.py +80 -6
- package/src/zexus/vm/binary_bytecode.py +659 -0
- package/src/zexus/vm/bytecode.py +59 -11
- package/src/zexus/vm/bytecode_converter.py +26 -12
- package/src/zexus/vm/cabi.c +1985 -0
- package/src/zexus/vm/cabi.cpython-312-x86_64-linux-gnu.so +0 -0
- package/src/zexus/vm/cabi.h +127 -0
- package/src/zexus/vm/cache.py +561 -17
- package/src/zexus/vm/compiler.py +818 -51
- package/src/zexus/vm/fastops.c +15743 -0
- package/src/zexus/vm/fastops.cpython-312-x86_64-linux-gnu.so +0 -0
- package/src/zexus/vm/fastops.pyx +288 -0
- package/src/zexus/vm/gas_metering.py +50 -9
- package/src/zexus/vm/jit.py +364 -20
- package/src/zexus/vm/native_jit_backend.py +1816 -0
- package/src/zexus/vm/native_runtime.cpp +1388 -0
- package/src/zexus/vm/native_runtime.cpython-312-x86_64-linux-gnu.so +0 -0
- package/src/zexus/vm/optimizer.py +161 -11
- package/src/zexus/vm/parallel_vm.py +140 -45
- package/src/zexus/vm/peephole_optimizer.py +82 -4
- package/src/zexus/vm/profiler.py +38 -18
- package/src/zexus/vm/register_allocator.py +16 -5
- package/src/zexus/vm/register_vm.py +8 -5
- package/src/zexus/vm/vm.py +3581 -531
- package/src/zexus/vm/wasm_compiler.py +658 -0
- package/src/zexus/zexus_ast.py +137 -11
- package/src/zexus/zexus_token.py +16 -5
- package/src/zexus/zpm/installer.py +55 -15
- package/src/zexus/zpm/package_manager.py +1 -1
- package/src/zexus/zpm/registry.py +257 -28
- package/src/zexus.egg-info/PKG-INFO +16 -6
- package/src/zexus.egg-info/SOURCES.txt +129 -17
- package/src/zexus.egg-info/entry_points.txt +1 -0
- package/src/zexus.egg-info/requires.txt +4 -0
|
@@ -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
|
-
#
|
|
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
|
-
|
|
198
|
-
|
|
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
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
220
|
-
|
|
528
|
+
if not self.expect_peek(COLON):
|
|
529
|
+
return None
|
|
221
530
|
|
|
222
|
-
#
|
|
223
|
-
|
|
224
|
-
|
|
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
|
-
|
|
234
|
-
|
|
535
|
+
if self.peek_token_is(RBRACE):
|
|
536
|
+
self.next_token() # consume '}'
|
|
537
|
+
break
|
|
235
538
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
|
|
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
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
node =
|
|
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
|
-
|
|
588
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
633
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
663
|
-
|
|
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
|
-
|
|
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
|
-
|
|
676
|
-
|
|
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 '
|
|
681
|
-
suggestion="Add a condition expression:
|
|
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
|
-
|
|
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
|
-
|
|
695
|
-
|
|
696
|
-
|
|
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
|
-
|
|
738
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
793
|
-
|
|
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
|
|
1226
|
+
"""Tolerant const statement parser with destructuring and type annotation support
|
|
810
1227
|
|
|
811
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
822
|
-
if self.peek_token_is(
|
|
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
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
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
|
-
|
|
1520
|
-
|
|
1521
|
-
self.
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
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
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
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
|
|
2100
|
-
|
|
2101
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
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
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
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
|
-
|
|
2138
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2941
|
+
param_name = self.cur_token.literal
|
|
2942
|
+
param_type = None
|
|
2402
2943
|
|
|
2403
|
-
#
|
|
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
|
|
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
|
-
|
|
2958
|
+
param_name = self.cur_token.literal
|
|
2959
|
+
param_type = None
|
|
2415
2960
|
|
|
2416
|
-
#
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2728
|
-
|
|
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
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
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
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
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
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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=
|
|
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(
|
|
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
|
-
#
|
|
3160
|
-
from ..zexus_ast import
|
|
3161
|
-
|
|
3162
|
-
data_stmt
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
+
)
|