IncludeCPP 3.7.3__py3-none-any.whl

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.

Potentially problematic release.


This version of IncludeCPP might be problematic. Click here for more details.

Files changed (49) hide show
  1. includecpp/__init__.py +59 -0
  2. includecpp/__init__.pyi +255 -0
  3. includecpp/__main__.py +4 -0
  4. includecpp/cli/__init__.py +4 -0
  5. includecpp/cli/commands.py +8270 -0
  6. includecpp/cli/config_parser.py +127 -0
  7. includecpp/core/__init__.py +19 -0
  8. includecpp/core/ai_integration.py +2132 -0
  9. includecpp/core/build_manager.py +2416 -0
  10. includecpp/core/cpp_api.py +376 -0
  11. includecpp/core/cpp_api.pyi +95 -0
  12. includecpp/core/cppy_converter.py +3448 -0
  13. includecpp/core/cssl/CSSL_DOCUMENTATION.md +2075 -0
  14. includecpp/core/cssl/__init__.py +42 -0
  15. includecpp/core/cssl/cssl_builtins.py +2271 -0
  16. includecpp/core/cssl/cssl_builtins.pyi +1393 -0
  17. includecpp/core/cssl/cssl_events.py +621 -0
  18. includecpp/core/cssl/cssl_modules.py +2803 -0
  19. includecpp/core/cssl/cssl_parser.py +2575 -0
  20. includecpp/core/cssl/cssl_runtime.py +3051 -0
  21. includecpp/core/cssl/cssl_syntax.py +488 -0
  22. includecpp/core/cssl/cssl_types.py +1512 -0
  23. includecpp/core/cssl_bridge.py +882 -0
  24. includecpp/core/cssl_bridge.pyi +488 -0
  25. includecpp/core/error_catalog.py +802 -0
  26. includecpp/core/error_formatter.py +1016 -0
  27. includecpp/core/exceptions.py +97 -0
  28. includecpp/core/path_discovery.py +77 -0
  29. includecpp/core/project_ui.py +3370 -0
  30. includecpp/core/settings_ui.py +326 -0
  31. includecpp/generator/__init__.py +1 -0
  32. includecpp/generator/parser.cpp +1903 -0
  33. includecpp/generator/parser.h +281 -0
  34. includecpp/generator/type_resolver.cpp +363 -0
  35. includecpp/generator/type_resolver.h +68 -0
  36. includecpp/py.typed +0 -0
  37. includecpp/templates/cpp.proj.template +18 -0
  38. includecpp/vscode/__init__.py +1 -0
  39. includecpp/vscode/cssl/__init__.py +1 -0
  40. includecpp/vscode/cssl/language-configuration.json +38 -0
  41. includecpp/vscode/cssl/package.json +50 -0
  42. includecpp/vscode/cssl/snippets/cssl.snippets.json +1080 -0
  43. includecpp/vscode/cssl/syntaxes/cssl.tmLanguage.json +341 -0
  44. includecpp-3.7.3.dist-info/METADATA +1076 -0
  45. includecpp-3.7.3.dist-info/RECORD +49 -0
  46. includecpp-3.7.3.dist-info/WHEEL +5 -0
  47. includecpp-3.7.3.dist-info/entry_points.txt +2 -0
  48. includecpp-3.7.3.dist-info/licenses/LICENSE +21 -0
  49. includecpp-3.7.3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,2575 @@
1
+ """
2
+ CSSL Parser - Lexer and Parser for CSO Service Script Language
3
+
4
+ Features:
5
+ - Complete tokenization of CSSL syntax
6
+ - AST (Abstract Syntax Tree) generation
7
+ - Enhanced error reporting with line/column info
8
+ - Support for service files and standalone programs
9
+ - Special operators: <== (inject), ==> (receive), -> <- (flow)
10
+ - Module references (@Module) and self-references (s@Struct)
11
+ """
12
+
13
+ import re
14
+ from enum import Enum, auto
15
+ from dataclasses import dataclass, field
16
+ from typing import List, Dict, Any, Optional, Union
17
+
18
+
19
+ class CSSLSyntaxError(Exception):
20
+ """Syntax error with detailed location information"""
21
+
22
+ def __init__(self, message: str, line: int = 0, column: int = 0, source_line: str = ""):
23
+ self.line = line
24
+ self.column = column
25
+ self.source_line = source_line
26
+
27
+ # Build detailed error message
28
+ location = f" at line {line}" if line else ""
29
+ if column:
30
+ location += f", column {column}"
31
+
32
+ full_message = f"CSSL Syntax Error{location}: {message}"
33
+
34
+ if source_line:
35
+ full_message += f"\n {source_line}"
36
+ if column > 0:
37
+ full_message += f"\n {' ' * (column - 1)}^"
38
+
39
+ super().__init__(full_message)
40
+
41
+
42
+ class TokenType(Enum):
43
+ KEYWORD = auto()
44
+ IDENTIFIER = auto()
45
+ STRING = auto()
46
+ STRING_INTERP = auto() # <variable> in strings
47
+ NUMBER = auto()
48
+ BOOLEAN = auto()
49
+ NULL = auto()
50
+ TYPE_LITERAL = auto() # list, dict as type literals
51
+ TYPE_GENERIC = auto() # datastruct<T>, shuffled<T>, iterator<T>, combo<T>
52
+ OPERATOR = auto()
53
+ # Basic injection operators
54
+ INJECT_LEFT = auto() # <==
55
+ INJECT_RIGHT = auto() # ==>
56
+ # BruteForce Injection operators - Copy & Add
57
+ INJECT_PLUS_LEFT = auto() # +<==
58
+ INJECT_PLUS_RIGHT = auto() # ==>+
59
+ # BruteForce Injection operators - Move & Remove
60
+ INJECT_MINUS_LEFT = auto() # -<==
61
+ INJECT_MINUS_RIGHT = auto() # ===>-
62
+ # BruteForce Injection operators - Code Infusion
63
+ INFUSE_LEFT = auto() # <<==
64
+ INFUSE_RIGHT = auto() # ==>>
65
+ INFUSE_PLUS_LEFT = auto() # +<<==
66
+ INFUSE_PLUS_RIGHT = auto() # ==>>+
67
+ INFUSE_MINUS_LEFT = auto() # -<<==
68
+ INFUSE_MINUS_RIGHT = auto() # ==>>-
69
+ # Flow operators
70
+ FLOW_RIGHT = auto()
71
+ FLOW_LEFT = auto()
72
+ EQUALS = auto()
73
+ COMPARE_EQ = auto()
74
+ COMPARE_NE = auto()
75
+ COMPARE_LT = auto()
76
+ COMPARE_GT = auto()
77
+ COMPARE_LE = auto()
78
+ COMPARE_GE = auto()
79
+ PLUS = auto()
80
+ MINUS = auto()
81
+ MULTIPLY = auto()
82
+ DIVIDE = auto()
83
+ MODULO = auto()
84
+ AND = auto()
85
+ OR = auto()
86
+ NOT = auto()
87
+ AMPERSAND = auto() # & for references
88
+ BLOCK_START = auto()
89
+ BLOCK_END = auto()
90
+ PAREN_START = auto()
91
+ PAREN_END = auto()
92
+ BRACKET_START = auto()
93
+ BRACKET_END = auto()
94
+ SEMICOLON = auto()
95
+ COLON = auto()
96
+ DOUBLE_COLON = auto() # :: for injection helpers (string::where, json::key, etc)
97
+ COMMA = auto()
98
+ DOT = auto()
99
+ AT = auto()
100
+ GLOBAL_REF = auto() # r@<name> global variable declaration
101
+ SELF_REF = auto() # s@<name> self-reference to global struct
102
+ SHARED_REF = auto() # $<name> shared object reference
103
+ CAPTURED_REF = auto() # %<name> captured reference (for infusion)
104
+ THIS_REF = auto() # this-><name> class member reference
105
+ PACKAGE = auto()
106
+ PACKAGE_INCLUDES = auto()
107
+ AS = auto()
108
+ COMMENT = auto()
109
+ NEWLINE = auto()
110
+ EOF = auto()
111
+
112
+
113
+ KEYWORDS = {
114
+ # Service structure
115
+ 'service-init', 'service-run', 'service-include', 'struct', 'define', 'main', 'class', 'new', 'this',
116
+ # Control flow
117
+ 'if', 'else', 'elif', 'while', 'for', 'foreach', 'in', 'range',
118
+ 'switch', 'case', 'default', 'break', 'continue', 'return',
119
+ 'try', 'catch', 'finally', 'throw',
120
+ # Literals
121
+ 'True', 'False', 'null', 'None', 'true', 'false',
122
+ # Logical operators
123
+ 'and', 'or', 'not',
124
+ # Async/Events
125
+ 'start', 'stop', 'wait_for', 'on_event', 'emit_event', 'await',
126
+ # Package system
127
+ 'package', 'package-includes', 'exec', 'as', 'global',
128
+ # CSSL Type Keywords
129
+ 'int', 'string', 'float', 'bool', 'void', 'json', 'array', 'vector', 'stack',
130
+ 'list', 'dictionary', 'dict', 'instance', 'map', # Python-like types
131
+ 'dynamic', # No type declaration (slow but flexible)
132
+ 'undefined', # Function errors ignored
133
+ 'open', # Accept any parameter type
134
+ 'datastruct', # Universal container (lazy declarator)
135
+ 'dataspace', # SQL/data storage container
136
+ 'shuffled', # Unorganized fast storage (multiple returns)
137
+ 'iterator', # Advanced iterator with tasks
138
+ 'combo', # Filter/search spaces
139
+ 'structure', # Advanced C++/Py Class
140
+ 'openquote', # SQL openquote container
141
+ # CSSL Function Modifiers
142
+ 'meta', # Source function (must return)
143
+ 'super', # Force execution (no exceptions)
144
+ 'closed', # Protect from external injection
145
+ 'private', # Disable all injections
146
+ 'virtual', # Import cycle safe
147
+ 'sqlbased', # SQL-based function
148
+ # CSSL Include Keywords
149
+ 'include', 'get',
150
+ }
151
+
152
+ # Type literals that create empty instances
153
+ TYPE_LITERALS = {'list', 'dict'}
154
+
155
+ # Generic type keywords that use <T> syntax
156
+ TYPE_GENERICS = {
157
+ 'datastruct', 'dataspace', 'shuffled', 'iterator', 'combo',
158
+ 'vector', 'stack', 'array', 'openquote', 'list', 'dictionary', 'map'
159
+ }
160
+
161
+ # Functions that accept type parameters: FuncName<type>(args)
162
+ TYPE_PARAM_FUNCTIONS = {
163
+ 'OpenFind' # OpenFind<string>(0)
164
+ }
165
+
166
+ # Injection helper prefixes (type::helper=value)
167
+ INJECTION_HELPERS = {
168
+ 'string', 'integer', 'json', 'array', 'vector', 'combo', 'dynamic', 'sql'
169
+ }
170
+
171
+
172
+ @dataclass
173
+ class Token:
174
+ type: TokenType
175
+ value: Any
176
+ line: int
177
+ column: int
178
+
179
+
180
+ class CSSLLexer:
181
+ """Tokenizes CSSL source code into a stream of tokens."""
182
+
183
+ def __init__(self, source: str):
184
+ self.source = source
185
+ self.pos = 0
186
+ self.line = 1
187
+ self.column = 1
188
+ self.tokens: List[Token] = []
189
+ # Store source lines for error messages
190
+ self.source_lines = source.split('\n')
191
+
192
+ def get_source_line(self, line_num: int) -> str:
193
+ """Get a specific source line for error reporting"""
194
+ if 0 < line_num <= len(self.source_lines):
195
+ return self.source_lines[line_num - 1]
196
+ return ""
197
+
198
+ def error(self, message: str):
199
+ """Raise a syntax error with location info"""
200
+ raise CSSLSyntaxError(
201
+ message,
202
+ line=self.line,
203
+ column=self.column,
204
+ source_line=self.get_source_line(self.line)
205
+ )
206
+
207
+ def tokenize(self) -> List[Token]:
208
+ while self.pos < len(self.source):
209
+ self._skip_whitespace()
210
+ if self.pos >= len(self.source):
211
+ break
212
+
213
+ char = self.source[self.pos]
214
+
215
+ # Comments: both # and // style
216
+ if char == '#':
217
+ self._skip_comment()
218
+ elif char == '/' and self._peek(1) == '/':
219
+ # C-style // comment - NEW
220
+ self._skip_comment()
221
+ elif char == '\n':
222
+ self._add_token(TokenType.NEWLINE, '\n')
223
+ self._advance()
224
+ self.line += 1
225
+ self.column = 1
226
+ elif char in '"\'':
227
+ self._read_string(char)
228
+ elif char == '`':
229
+ # Raw string (no escape processing) - useful for JSON
230
+ self._read_raw_string()
231
+ elif char.isdigit() or (char == '-' and self._peek(1).isdigit()):
232
+ self._read_number()
233
+ elif char == 'r' and self._peek(1) == '@':
234
+ # r@<name> global variable declaration (same as 'global')
235
+ self._read_global_ref()
236
+ elif char == 's' and self._peek(1) == '@':
237
+ # s@<name> self-reference to global struct
238
+ self._read_self_ref()
239
+ elif char.isalpha() or char == '_':
240
+ self._read_identifier()
241
+ elif char == '@':
242
+ self._add_token(TokenType.AT, '@')
243
+ self._advance()
244
+ elif char == '$':
245
+ # $<name> shared object reference
246
+ self._read_shared_ref()
247
+ elif char == '%':
248
+ # %<name> captured reference (for infusion)
249
+ self._read_captured_ref()
250
+ elif char == '&':
251
+ # & for references
252
+ if self._peek(1) == '&':
253
+ self._add_token(TokenType.AND, '&&')
254
+ self._advance()
255
+ self._advance()
256
+ else:
257
+ self._add_token(TokenType.AMPERSAND, '&')
258
+ self._advance()
259
+ elif char == '{':
260
+ self._add_token(TokenType.BLOCK_START, '{')
261
+ self._advance()
262
+ elif char == '}':
263
+ self._add_token(TokenType.BLOCK_END, '}')
264
+ self._advance()
265
+ elif char == '(':
266
+ self._add_token(TokenType.PAREN_START, '(')
267
+ self._advance()
268
+ elif char == ')':
269
+ self._add_token(TokenType.PAREN_END, ')')
270
+ self._advance()
271
+ elif char == '[':
272
+ self._add_token(TokenType.BRACKET_START, '[')
273
+ self._advance()
274
+ elif char == ']':
275
+ self._add_token(TokenType.BRACKET_END, ']')
276
+ self._advance()
277
+ elif char == ';':
278
+ self._add_token(TokenType.SEMICOLON, ';')
279
+ self._advance()
280
+ elif char == ':':
281
+ # Check for :: (double colon for injection helpers)
282
+ if self._peek(1) == ':':
283
+ self._add_token(TokenType.DOUBLE_COLON, '::')
284
+ self._advance()
285
+ self._advance()
286
+ else:
287
+ self._add_token(TokenType.COLON, ':')
288
+ self._advance()
289
+ elif char == ',':
290
+ self._add_token(TokenType.COMMA, ',')
291
+ self._advance()
292
+ elif char == '.':
293
+ self._add_token(TokenType.DOT, '.')
294
+ self._advance()
295
+ elif char == '+':
296
+ # Check for BruteForce Injection: +<== or +<<==
297
+ if self._peek(1) == '<' and self._peek(2) == '<' and self._peek(3) == '=' and self._peek(4) == '=':
298
+ self._add_token(TokenType.INFUSE_PLUS_LEFT, '+<<==')
299
+ for _ in range(5): self._advance()
300
+ elif self._peek(1) == '<' and self._peek(2) == '=' and self._peek(3) == '=':
301
+ self._add_token(TokenType.INJECT_PLUS_LEFT, '+<==')
302
+ for _ in range(4): self._advance()
303
+ else:
304
+ self._add_token(TokenType.PLUS, '+')
305
+ self._advance()
306
+ elif char == '*':
307
+ self._add_token(TokenType.MULTIPLY, '*')
308
+ self._advance()
309
+ elif char == '/':
310
+ # Check if this is a // comment (handled above) or division
311
+ if self._peek(1) != '/':
312
+ self._add_token(TokenType.DIVIDE, '/')
313
+ self._advance()
314
+ else:
315
+ # Already handled by // comment check above, but just in case
316
+ self._skip_comment()
317
+ elif char == '%':
318
+ self._add_token(TokenType.MODULO, '%')
319
+ self._advance()
320
+ elif char == '<':
321
+ self._read_less_than()
322
+ elif char == '>':
323
+ self._read_greater_than()
324
+ elif char == '=':
325
+ self._read_equals()
326
+ elif char == '!':
327
+ self._read_not()
328
+ elif char == '-':
329
+ self._read_minus()
330
+ elif char == '|':
331
+ if self._peek(1) == '|':
332
+ self._add_token(TokenType.OR, '||')
333
+ self._advance()
334
+ self._advance()
335
+ else:
336
+ self._advance()
337
+ else:
338
+ self._advance()
339
+
340
+ self._add_token(TokenType.EOF, '')
341
+ return self.tokens
342
+
343
+ def _advance(self):
344
+ self.pos += 1
345
+ self.column += 1
346
+
347
+ def _peek(self, offset=0) -> str:
348
+ pos = self.pos + offset
349
+ if pos < len(self.source):
350
+ return self.source[pos]
351
+ return ''
352
+
353
+ def _add_token(self, token_type: TokenType, value: Any):
354
+ self.tokens.append(Token(token_type, value, self.line, self.column))
355
+
356
+ def _skip_whitespace(self):
357
+ while self.pos < len(self.source) and self.source[self.pos] in ' \t\r':
358
+ self._advance()
359
+
360
+ def _skip_comment(self):
361
+ while self.pos < len(self.source) and self.source[self.pos] != '\n':
362
+ self._advance()
363
+
364
+ def _read_string(self, quote_char: str):
365
+ self._advance()
366
+ start = self.pos
367
+ result = []
368
+ while self.pos < len(self.source) and self.source[self.pos] != quote_char:
369
+ if self.source[self.pos] == '\\' and self.pos + 1 < len(self.source):
370
+ # Handle escape sequences
371
+ next_char = self.source[self.pos + 1]
372
+ if next_char == 'n':
373
+ result.append('\n')
374
+ elif next_char == 't':
375
+ result.append('\t')
376
+ elif next_char == 'r':
377
+ result.append('\r')
378
+ elif next_char == '\\':
379
+ result.append('\\')
380
+ elif next_char == quote_char:
381
+ result.append(quote_char)
382
+ elif next_char == '"':
383
+ result.append('"')
384
+ elif next_char == "'":
385
+ result.append("'")
386
+ else:
387
+ result.append(self.source[self.pos])
388
+ result.append(next_char)
389
+ self._advance()
390
+ self._advance()
391
+ else:
392
+ result.append(self.source[self.pos])
393
+ self._advance()
394
+ value = ''.join(result)
395
+ self._add_token(TokenType.STRING, value)
396
+ self._advance()
397
+
398
+ def _read_raw_string(self):
399
+ """Read raw string with backticks - no escape processing.
400
+
401
+ Useful for JSON: `{"id": "2819e1", "name": "test"}`
402
+ """
403
+ self._advance() # Skip opening backtick
404
+ start = self.pos
405
+ while self.pos < len(self.source) and self.source[self.pos] != '`':
406
+ if self.source[self.pos] == '\n':
407
+ self.line += 1
408
+ self.column = 0
409
+ self._advance()
410
+ value = self.source[start:self.pos]
411
+ self._add_token(TokenType.STRING, value)
412
+ self._advance() # Skip closing backtick
413
+
414
+ def _read_number(self):
415
+ start = self.pos
416
+ if self.source[self.pos] == '-':
417
+ self._advance()
418
+ while self.pos < len(self.source) and (self.source[self.pos].isdigit() or self.source[self.pos] == '.'):
419
+ self._advance()
420
+ value = self.source[start:self.pos]
421
+ if '.' in value:
422
+ self._add_token(TokenType.NUMBER, float(value))
423
+ else:
424
+ self._add_token(TokenType.NUMBER, int(value))
425
+
426
+ def _read_identifier(self):
427
+ start = self.pos
428
+ while self.pos < len(self.source) and (self.source[self.pos].isalnum() or self.source[self.pos] == '_'):
429
+ self._advance()
430
+ value = self.source[start:self.pos]
431
+
432
+ if value in ('True', 'true'):
433
+ self._add_token(TokenType.BOOLEAN, True)
434
+ elif value in ('False', 'false'):
435
+ self._add_token(TokenType.BOOLEAN, False)
436
+ elif value in ('null', 'None', 'none'):
437
+ self._add_token(TokenType.NULL, None)
438
+ elif value in TYPE_LITERALS:
439
+ # NEW: list and dict as type literals (e.g., cache = list;)
440
+ self._add_token(TokenType.TYPE_LITERAL, value)
441
+ elif value == 'as':
442
+ # NEW: 'as' keyword for foreach ... as ... syntax
443
+ self._add_token(TokenType.AS, value)
444
+ elif value in KEYWORDS:
445
+ self._add_token(TokenType.KEYWORD, value)
446
+ else:
447
+ self._add_token(TokenType.IDENTIFIER, value)
448
+
449
+ def _read_self_ref(self):
450
+ """Read s@<name> or s@<name>.<member>... self-reference"""
451
+ start = self.pos
452
+ self._advance() # skip 's'
453
+ self._advance() # skip '@'
454
+
455
+ # Read the identifier path (Name.Member.SubMember)
456
+ path_parts = []
457
+ while self.pos < len(self.source):
458
+ # Read identifier part
459
+ part_start = self.pos
460
+ while self.pos < len(self.source) and (self.source[self.pos].isalnum() or self.source[self.pos] == '_'):
461
+ self._advance()
462
+ if self.pos > part_start:
463
+ path_parts.append(self.source[part_start:self.pos])
464
+
465
+ # Check for dot to continue path
466
+ if self.pos < len(self.source) and self.source[self.pos] == '.':
467
+ self._advance() # skip '.'
468
+ else:
469
+ break
470
+
471
+ value = '.'.join(path_parts)
472
+ self._add_token(TokenType.SELF_REF, value)
473
+
474
+ def _read_global_ref(self):
475
+ """Read r@<name> global variable declaration (equivalent to 'global')"""
476
+ start = self.pos
477
+ self._advance() # skip 'r'
478
+ self._advance() # skip '@'
479
+
480
+ # Read the identifier
481
+ name_start = self.pos
482
+ while self.pos < len(self.source) and (self.source[self.pos].isalnum() or self.source[self.pos] == '_'):
483
+ self._advance()
484
+
485
+ value = self.source[name_start:self.pos]
486
+ self._add_token(TokenType.GLOBAL_REF, value)
487
+
488
+ def _read_shared_ref(self):
489
+ """Read $<name> shared object reference"""
490
+ self._advance() # skip '$'
491
+
492
+ # Read the identifier (shared object name)
493
+ name_start = self.pos
494
+ while self.pos < len(self.source) and (self.source[self.pos].isalnum() or self.source[self.pos] == '_'):
495
+ self._advance()
496
+
497
+ value = self.source[name_start:self.pos]
498
+ if not value:
499
+ self.error("Expected identifier after '$'")
500
+ self._add_token(TokenType.SHARED_REF, value)
501
+
502
+ def _read_captured_ref(self):
503
+ """Read %<name> captured reference (captures value at definition time for infusions)"""
504
+ self._advance() # skip '%'
505
+
506
+ # Read the identifier (captured reference name)
507
+ name_start = self.pos
508
+ while self.pos < len(self.source) and (self.source[self.pos].isalnum() or self.source[self.pos] == '_'):
509
+ self._advance()
510
+
511
+ value = self.source[name_start:self.pos]
512
+ if not value:
513
+ self.error("Expected identifier after '%'")
514
+ self._add_token(TokenType.CAPTURED_REF, value)
515
+
516
+ def _read_less_than(self):
517
+ # Check for <<== (code infusion left)
518
+ if self._peek(1) == '<' and self._peek(2) == '=' and self._peek(3) == '=':
519
+ self._add_token(TokenType.INFUSE_LEFT, '<<==')
520
+ for _ in range(4): self._advance()
521
+ # Check for <== (basic injection left)
522
+ elif self._peek(1) == '=' and self._peek(2) == '=':
523
+ self._add_token(TokenType.INJECT_LEFT, '<==')
524
+ for _ in range(3): self._advance()
525
+ elif self._peek(1) == '=':
526
+ self._add_token(TokenType.COMPARE_LE, '<=')
527
+ self._advance()
528
+ self._advance()
529
+ elif self._peek(1) == '-':
530
+ self._add_token(TokenType.FLOW_LEFT, '<-')
531
+ self._advance()
532
+ self._advance()
533
+ else:
534
+ self._add_token(TokenType.COMPARE_LT, '<')
535
+ self._advance()
536
+
537
+ def _read_greater_than(self):
538
+ if self._peek(1) == '=':
539
+ self._add_token(TokenType.COMPARE_GE, '>=')
540
+ self._advance()
541
+ self._advance()
542
+ else:
543
+ self._add_token(TokenType.COMPARE_GT, '>')
544
+ self._advance()
545
+
546
+ def _read_equals(self):
547
+ # Check for ==>>+ (code infusion right plus)
548
+ if self._peek(1) == '=' and self._peek(2) == '>' and self._peek(3) == '>' and self._peek(4) == '+':
549
+ self._add_token(TokenType.INFUSE_PLUS_RIGHT, '==>>+')
550
+ for _ in range(5): self._advance()
551
+ # Check for ==>>- (code infusion right minus)
552
+ elif self._peek(1) == '=' and self._peek(2) == '>' and self._peek(3) == '>' and self._peek(4) == '-':
553
+ self._add_token(TokenType.INFUSE_MINUS_RIGHT, '==>>-')
554
+ for _ in range(5): self._advance()
555
+ # Check for ==>> (code infusion right)
556
+ elif self._peek(1) == '=' and self._peek(2) == '>' and self._peek(3) == '>':
557
+ self._add_token(TokenType.INFUSE_RIGHT, '==>>')
558
+ for _ in range(4): self._advance()
559
+ # Check for ==>+ (injection right plus)
560
+ elif self._peek(1) == '=' and self._peek(2) == '>' and self._peek(3) == '+':
561
+ self._add_token(TokenType.INJECT_PLUS_RIGHT, '==>+')
562
+ for _ in range(4): self._advance()
563
+ # Check for ==>- (injection right minus - moves & removes)
564
+ elif self._peek(1) == '=' and self._peek(2) == '>' and self._peek(3) == '-':
565
+ self._add_token(TokenType.INJECT_MINUS_RIGHT, '==>-')
566
+ for _ in range(4): self._advance()
567
+ # Check for ==> (basic injection right)
568
+ elif self._peek(1) == '=' and self._peek(2) == '>':
569
+ self._add_token(TokenType.INJECT_RIGHT, '==>')
570
+ for _ in range(3): self._advance()
571
+ elif self._peek(1) == '=':
572
+ self._add_token(TokenType.COMPARE_EQ, '==')
573
+ self._advance()
574
+ self._advance()
575
+ else:
576
+ self._add_token(TokenType.EQUALS, '=')
577
+ self._advance()
578
+
579
+ def _read_not(self):
580
+ if self._peek(1) == '=':
581
+ self._add_token(TokenType.COMPARE_NE, '!=')
582
+ self._advance()
583
+ self._advance()
584
+ else:
585
+ self._add_token(TokenType.NOT, '!')
586
+ self._advance()
587
+
588
+ def _read_minus(self):
589
+ # Check for -<<== (code infusion minus left)
590
+ if self._peek(1) == '<' and self._peek(2) == '<' and self._peek(3) == '=' and self._peek(4) == '=':
591
+ self._add_token(TokenType.INFUSE_MINUS_LEFT, '-<<==')
592
+ for _ in range(5): self._advance()
593
+ # Check for -<== (injection minus left - move & remove)
594
+ elif self._peek(1) == '<' and self._peek(2) == '=' and self._peek(3) == '=':
595
+ self._add_token(TokenType.INJECT_MINUS_LEFT, '-<==')
596
+ for _ in range(4): self._advance()
597
+ # Check for -==> (injection right minus)
598
+ elif self._peek(1) == '=' and self._peek(2) == '=' and self._peek(3) == '>':
599
+ self._add_token(TokenType.INJECT_MINUS_RIGHT, '-==>')
600
+ for _ in range(4): self._advance()
601
+ elif self._peek(1) == '>':
602
+ self._add_token(TokenType.FLOW_RIGHT, '->')
603
+ self._advance()
604
+ self._advance()
605
+ else:
606
+ self._add_token(TokenType.MINUS, '-')
607
+ self._advance()
608
+
609
+
610
+ @dataclass
611
+ class ASTNode:
612
+ type: str
613
+ value: Any = None
614
+ children: List['ASTNode'] = field(default_factory=list)
615
+ line: int = 0
616
+ column: int = 0
617
+
618
+
619
+ class CSSLParser:
620
+ """Parses CSSL tokens into an Abstract Syntax Tree."""
621
+
622
+ def __init__(self, tokens: List[Token], source_lines: List[str] = None):
623
+ self.tokens = [t for t in tokens if t.type != TokenType.NEWLINE]
624
+ self.pos = 0
625
+ self.source_lines = source_lines or []
626
+
627
+ def get_source_line(self, line_num: int) -> str:
628
+ """Get a specific source line for error reporting"""
629
+ if 0 < line_num <= len(self.source_lines):
630
+ return self.source_lines[line_num - 1]
631
+ return ""
632
+
633
+ def error(self, message: str, token: Token = None):
634
+ """Raise a syntax error with location info"""
635
+ if token is None:
636
+ token = self._current()
637
+ raise CSSLSyntaxError(
638
+ message,
639
+ line=token.line,
640
+ column=token.column,
641
+ source_line=self.get_source_line(token.line)
642
+ )
643
+
644
+ def parse(self) -> ASTNode:
645
+ """Parse a service file (wrapped in braces)"""
646
+ root = ASTNode('service', children=[])
647
+
648
+ if not self._match(TokenType.BLOCK_START):
649
+ self.error(f"Expected '{{' at start of service, got {self._current().type.name}")
650
+
651
+ while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
652
+ if self._match_keyword('service-init'):
653
+ root.children.append(self._parse_service_init())
654
+ elif self._match_keyword('service-include'):
655
+ root.children.append(self._parse_service_include())
656
+ elif self._match_keyword('service-run'):
657
+ root.children.append(self._parse_service_run())
658
+ # NEW: package block support
659
+ elif self._match_keyword('package'):
660
+ root.children.append(self._parse_package())
661
+ # NEW: package-includes block support
662
+ elif self._match_keyword('package-includes'):
663
+ root.children.append(self._parse_package_includes())
664
+ # NEW: struct at top level
665
+ elif self._match_keyword('struct'):
666
+ root.children.append(self._parse_struct())
667
+ # NEW: define at top level
668
+ elif self._match_keyword('define'):
669
+ root.children.append(self._parse_define())
670
+ else:
671
+ self._advance()
672
+
673
+ self._match(TokenType.BLOCK_END)
674
+ return root
675
+
676
+ def _is_function_modifier(self, value: str) -> bool:
677
+ """Check if a keyword is a function modifier"""
678
+ return value in ('undefined', 'open', 'meta', 'super', 'closed', 'private', 'virtual', 'sqlbased')
679
+
680
+ def _is_type_keyword(self, value: str) -> bool:
681
+ """Check if a keyword is a type declaration"""
682
+ return value in ('int', 'string', 'float', 'bool', 'void', 'json', 'array', 'vector', 'stack',
683
+ 'list', 'dictionary', 'dict', 'instance', 'map',
684
+ 'dynamic', 'datastruct', 'dataspace', 'shuffled', 'iterator', 'combo', 'structure')
685
+
686
+ def _looks_like_function_declaration(self) -> bool:
687
+ """Check if current position looks like a C-style function declaration.
688
+
689
+ Patterns:
690
+ - int funcName(...)
691
+ - undefined int funcName(...)
692
+ - vector<string> funcName(...)
693
+ - undefined void funcName(...)
694
+ """
695
+ saved_pos = self.pos
696
+
697
+ # Skip modifiers (undefined, open, meta, super, closed, private, virtual)
698
+ while self._check(TokenType.KEYWORD) and self._is_function_modifier(self._current().value):
699
+ self._advance()
700
+
701
+ # Check for type keyword
702
+ if self._check(TokenType.KEYWORD) and self._is_type_keyword(self._current().value):
703
+ self._advance()
704
+
705
+ # Skip generic type parameters <T>
706
+ if self._check(TokenType.COMPARE_LT):
707
+ depth = 1
708
+ self._advance()
709
+ while depth > 0 and not self._is_at_end():
710
+ if self._check(TokenType.COMPARE_LT):
711
+ depth += 1
712
+ elif self._check(TokenType.COMPARE_GT):
713
+ depth -= 1
714
+ self._advance()
715
+
716
+ # Check for identifier followed by (
717
+ if self._check(TokenType.IDENTIFIER):
718
+ self._advance()
719
+ is_func = self._check(TokenType.PAREN_START)
720
+ self.pos = saved_pos
721
+ return is_func
722
+
723
+ self.pos = saved_pos
724
+ return False
725
+
726
+ def _looks_like_typed_variable(self) -> bool:
727
+ """Check if current position looks like a typed variable declaration.
728
+
729
+ Patterns:
730
+ - int x;
731
+ - stack<string> myStack;
732
+ - vector<int> nums = [1,2,3];
733
+
734
+ Distinguishes from function declarations by checking for '(' after identifier.
735
+ """
736
+ saved_pos = self.pos
737
+
738
+ # Check for type keyword
739
+ if self._check(TokenType.KEYWORD) and self._is_type_keyword(self._current().value):
740
+ self._advance()
741
+
742
+ # Skip generic type parameters <T>
743
+ if self._check(TokenType.COMPARE_LT):
744
+ depth = 1
745
+ self._advance()
746
+ while depth > 0 and not self._is_at_end():
747
+ if self._check(TokenType.COMPARE_LT):
748
+ depth += 1
749
+ elif self._check(TokenType.COMPARE_GT):
750
+ depth -= 1
751
+ self._advance()
752
+
753
+ # Check for identifier NOT followed by ( (that would be a function)
754
+ if self._check(TokenType.IDENTIFIER):
755
+ self._advance()
756
+ # If followed by '(' it's a function, not a variable
757
+ is_var = not self._check(TokenType.PAREN_START)
758
+ self.pos = saved_pos
759
+ return is_var
760
+
761
+ self.pos = saved_pos
762
+ return False
763
+
764
+ def _parse_typed_function(self) -> ASTNode:
765
+ """Parse C-style typed function declaration.
766
+
767
+ Patterns:
768
+ - int Add(int a, int b) { }
769
+ - undefined int Func() { }
770
+ - open void Handler(open Params) { }
771
+ - vector<string> GetNames() { }
772
+ """
773
+ modifiers = []
774
+ return_type = None
775
+ generic_type = None
776
+
777
+ # Collect modifiers (undefined, open, meta, super, closed, private, virtual)
778
+ while self._check(TokenType.KEYWORD) and self._is_function_modifier(self._current().value):
779
+ modifiers.append(self._advance().value)
780
+
781
+ # Get return type
782
+ if self._check(TokenType.KEYWORD) and self._is_type_keyword(self._current().value):
783
+ return_type = self._advance().value
784
+
785
+ # Check for generic type <T>
786
+ if self._check(TokenType.COMPARE_LT):
787
+ self._advance() # skip <
788
+ generic_parts = []
789
+ depth = 1
790
+ while depth > 0 and not self._is_at_end():
791
+ if self._check(TokenType.COMPARE_LT):
792
+ depth += 1
793
+ generic_parts.append('<')
794
+ elif self._check(TokenType.COMPARE_GT):
795
+ depth -= 1
796
+ if depth > 0:
797
+ generic_parts.append('>')
798
+ elif self._check(TokenType.COMMA):
799
+ generic_parts.append(',')
800
+ else:
801
+ generic_parts.append(self._current().value)
802
+ self._advance()
803
+ generic_type = ''.join(generic_parts)
804
+
805
+ # Get function name
806
+ name = self._advance().value
807
+
808
+ # Parse parameters
809
+ params = []
810
+ self._expect(TokenType.PAREN_START)
811
+
812
+ while not self._check(TokenType.PAREN_END) and not self._is_at_end():
813
+ param_info = {}
814
+
815
+ # Handle 'open' keyword for open parameters
816
+ if self._match_keyword('open'):
817
+ param_info['open'] = True
818
+
819
+ # Handle type annotations
820
+ if self._check(TokenType.KEYWORD) and self._is_type_keyword(self._current().value):
821
+ param_info['type'] = self._advance().value
822
+
823
+ # Check for generic type parameter <T>
824
+ if self._check(TokenType.COMPARE_LT):
825
+ self._advance()
826
+ generic_parts = []
827
+ depth = 1
828
+ while depth > 0 and not self._is_at_end():
829
+ if self._check(TokenType.COMPARE_LT):
830
+ depth += 1
831
+ generic_parts.append('<')
832
+ elif self._check(TokenType.COMPARE_GT):
833
+ depth -= 1
834
+ if depth > 0:
835
+ generic_parts.append('>')
836
+ elif self._check(TokenType.COMMA):
837
+ generic_parts.append(',')
838
+ else:
839
+ generic_parts.append(self._current().value)
840
+ self._advance()
841
+ param_info['generic'] = ''.join(generic_parts)
842
+
843
+ # Handle reference operator &
844
+ if self._match(TokenType.AMPERSAND):
845
+ param_info['ref'] = True
846
+
847
+ # Get parameter name
848
+ if self._check(TokenType.IDENTIFIER):
849
+ param_name = self._advance().value
850
+ if param_info:
851
+ params.append({'name': param_name, **param_info})
852
+ else:
853
+ params.append(param_name)
854
+ elif self._check(TokenType.KEYWORD):
855
+ # Parameter name could be a keyword
856
+ param_name = self._advance().value
857
+ if param_info:
858
+ params.append({'name': param_name, **param_info})
859
+ else:
860
+ params.append(param_name)
861
+
862
+ self._match(TokenType.COMMA)
863
+
864
+ self._expect(TokenType.PAREN_END)
865
+
866
+ # Parse function body
867
+ node = ASTNode('function', value={
868
+ 'name': name,
869
+ 'params': params,
870
+ 'return_type': return_type,
871
+ 'generic_type': generic_type,
872
+ 'modifiers': modifiers
873
+ }, children=[])
874
+
875
+ self._expect(TokenType.BLOCK_START)
876
+
877
+ while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
878
+ stmt = self._parse_statement()
879
+ if stmt:
880
+ node.children.append(stmt)
881
+
882
+ self._expect(TokenType.BLOCK_END)
883
+ return node
884
+
885
+ def _looks_like_typed_variable(self) -> bool:
886
+ """Check if current position looks like a typed variable declaration:
887
+ type_name varName; or type_name<T> varName; or type_name varName = value;
888
+ """
889
+ # Save position
890
+ saved_pos = self.pos
891
+
892
+ # Must start with a type keyword (int, string, stack, vector, etc.)
893
+ if not self._check(TokenType.KEYWORD):
894
+ return False
895
+
896
+ type_name = self._current().value
897
+
898
+ # Skip known type keywords
899
+ type_keywords = {'int', 'string', 'float', 'bool', 'dynamic', 'void',
900
+ 'stack', 'vector', 'datastruct', 'dataspace', 'shuffled',
901
+ 'iterator', 'combo', 'array', 'openquote', 'json',
902
+ 'list', 'dictionary', 'dict', 'instance', 'map'}
903
+ if type_name not in type_keywords:
904
+ return False
905
+
906
+ self._advance()
907
+
908
+ # Check for optional generic <T>
909
+ if self._match(TokenType.COMPARE_LT):
910
+ # Skip until >
911
+ depth = 1
912
+ while depth > 0 and not self._is_at_end():
913
+ if self._check(TokenType.COMPARE_LT):
914
+ depth += 1
915
+ elif self._check(TokenType.COMPARE_GT):
916
+ depth -= 1
917
+ self._advance()
918
+
919
+ # Next should be an identifier (variable name), not '(' (function) or ';'
920
+ result = self._check(TokenType.IDENTIFIER)
921
+
922
+ # Restore position
923
+ self.pos = saved_pos
924
+ return result
925
+
926
+ def _parse_typed_variable(self) -> Optional[ASTNode]:
927
+ """Parse a typed variable declaration: type varName; or type<T> varName = value;"""
928
+ # Get type name
929
+ type_name = self._advance().value # Consume type keyword
930
+
931
+ # Check for generic type <T> or instance<"name">
932
+ element_type = None
933
+ if self._match(TokenType.COMPARE_LT):
934
+ # For instance<"name">, element_type can be a string literal
935
+ if type_name == 'instance' and self._check(TokenType.STRING):
936
+ element_type = self._advance().value
937
+ elif self._check(TokenType.KEYWORD) or self._check(TokenType.IDENTIFIER):
938
+ element_type = self._advance().value
939
+ self._expect(TokenType.COMPARE_GT)
940
+
941
+ # Get variable name
942
+ if not self._check(TokenType.IDENTIFIER):
943
+ return None
944
+ var_name = self._advance().value
945
+
946
+ # Check for assignment or just declaration
947
+ value = None
948
+ if self._match(TokenType.EQUALS):
949
+ value = self._parse_expression()
950
+
951
+ self._match(TokenType.SEMICOLON)
952
+
953
+ # For instance<"name">, create a special node type
954
+ if type_name == 'instance':
955
+ return ASTNode('instance_declaration', value={
956
+ 'instance_name': element_type,
957
+ 'name': var_name,
958
+ 'value': value
959
+ })
960
+
961
+ return ASTNode('typed_declaration', value={
962
+ 'type': type_name,
963
+ 'element_type': element_type,
964
+ 'name': var_name,
965
+ 'value': value
966
+ })
967
+
968
+ def parse_program(self) -> ASTNode:
969
+ """Parse a standalone program (no service wrapper)"""
970
+ root = ASTNode('program', children=[])
971
+
972
+ while not self._is_at_end():
973
+ if self._match_keyword('struct'):
974
+ root.children.append(self._parse_struct())
975
+ elif self._match_keyword('class'):
976
+ root.children.append(self._parse_class())
977
+ elif self._match_keyword('define'):
978
+ root.children.append(self._parse_define())
979
+ # Check for C-style typed function declarations
980
+ elif self._looks_like_function_declaration():
981
+ root.children.append(self._parse_typed_function())
982
+ # Check for typed variable declarations (int x;, stack<string> s;)
983
+ elif self._looks_like_typed_variable():
984
+ decl = self._parse_typed_variable()
985
+ if decl:
986
+ root.children.append(decl)
987
+ # Handle service blocks
988
+ elif self._match_keyword('service-init'):
989
+ root.children.append(self._parse_service_init())
990
+ elif self._match_keyword('service-include'):
991
+ root.children.append(self._parse_service_include())
992
+ elif self._match_keyword('service-run'):
993
+ root.children.append(self._parse_service_run())
994
+ elif self._match_keyword('package'):
995
+ root.children.append(self._parse_package())
996
+ elif self._match_keyword('package-includes'):
997
+ root.children.append(self._parse_package_includes())
998
+ # Handle global declarations
999
+ elif self._match_keyword('global'):
1000
+ stmt = self._parse_expression_statement()
1001
+ if stmt:
1002
+ # Wrap in global_assignment to mark as global variable
1003
+ global_stmt = ASTNode('global_assignment', value=stmt)
1004
+ root.children.append(global_stmt)
1005
+ elif self._check(TokenType.GLOBAL_REF):
1006
+ stmt = self._parse_expression_statement()
1007
+ if stmt:
1008
+ # Wrap in global_assignment to mark as global variable (same as 'global' keyword)
1009
+ global_stmt = ASTNode('global_assignment', value=stmt)
1010
+ root.children.append(global_stmt)
1011
+ # Handle statements
1012
+ elif self._check(TokenType.IDENTIFIER) or self._check(TokenType.AT) or self._check(TokenType.SELF_REF) or self._check(TokenType.SHARED_REF):
1013
+ stmt = self._parse_expression_statement()
1014
+ if stmt:
1015
+ root.children.append(stmt)
1016
+ elif self._match_keyword('if'):
1017
+ root.children.append(self._parse_if())
1018
+ elif self._match_keyword('while'):
1019
+ root.children.append(self._parse_while())
1020
+ elif self._match_keyword('for'):
1021
+ root.children.append(self._parse_for())
1022
+ elif self._match_keyword('foreach'):
1023
+ root.children.append(self._parse_foreach())
1024
+ # Skip comments and newlines
1025
+ elif self._check(TokenType.COMMENT) or self._check(TokenType.NEWLINE):
1026
+ self._advance()
1027
+ else:
1028
+ self._advance()
1029
+
1030
+ return root
1031
+
1032
+ def _current(self) -> Token:
1033
+ if self.pos < len(self.tokens):
1034
+ return self.tokens[self.pos]
1035
+ return Token(TokenType.EOF, '', 0, 0)
1036
+
1037
+ def _peek(self, offset=0) -> Token:
1038
+ pos = self.pos + offset
1039
+ if pos < len(self.tokens):
1040
+ return self.tokens[pos]
1041
+ return Token(TokenType.EOF, '', 0, 0)
1042
+
1043
+ def _advance(self) -> Token:
1044
+ token = self._current()
1045
+ self.pos += 1
1046
+ return token
1047
+
1048
+ def _is_at_end(self) -> bool:
1049
+ return self._current().type == TokenType.EOF
1050
+
1051
+ def _check(self, token_type: TokenType) -> bool:
1052
+ return self._current().type == token_type
1053
+
1054
+ def _match(self, token_type: TokenType) -> bool:
1055
+ if self._check(token_type):
1056
+ self._advance()
1057
+ return True
1058
+ return False
1059
+
1060
+ def _match_keyword(self, keyword: str) -> bool:
1061
+ if self._current().type == TokenType.KEYWORD and self._current().value == keyword:
1062
+ self._advance()
1063
+ return True
1064
+ return False
1065
+
1066
+ def _expect(self, token_type: TokenType, message: str = None):
1067
+ if not self._match(token_type):
1068
+ msg = message or f"Expected {token_type.name}, got {self._current().type.name}"
1069
+ self.error(msg)
1070
+ return self.tokens[self.pos - 1]
1071
+
1072
+ def _parse_service_init(self) -> ASTNode:
1073
+ node = ASTNode('service-init', children=[])
1074
+ self._expect(TokenType.BLOCK_START)
1075
+
1076
+ while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
1077
+ if self._check(TokenType.IDENTIFIER) or self._check(TokenType.KEYWORD):
1078
+ key = self._advance().value
1079
+ self._expect(TokenType.COLON)
1080
+ value = self._parse_value()
1081
+ node.children.append(ASTNode('property', value={'key': key, 'value': value}))
1082
+ self._match(TokenType.SEMICOLON)
1083
+ else:
1084
+ self._advance()
1085
+
1086
+ self._expect(TokenType.BLOCK_END)
1087
+ return node
1088
+
1089
+ def _parse_service_include(self) -> ASTNode:
1090
+ """Parse service-include block for importing modules and files
1091
+
1092
+ Syntax:
1093
+ service-include {
1094
+ @KernelClient <== get(include(cso_root('/root32/etc/tasks/kernel.cssl')));
1095
+ @Time <== get('time');
1096
+ @Secrets <== get('secrets');
1097
+ }
1098
+ """
1099
+ node = ASTNode('service-include', children=[])
1100
+ self._expect(TokenType.BLOCK_START)
1101
+
1102
+ while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
1103
+ # Parse module injection statements like @ModuleName <== get(...);
1104
+ if self._check(TokenType.AT):
1105
+ stmt = self._parse_expression_statement()
1106
+ if stmt:
1107
+ node.children.append(stmt)
1108
+ elif self._check(TokenType.IDENTIFIER):
1109
+ # Also support identifier-based assignments: moduleName <== get(...);
1110
+ stmt = self._parse_expression_statement()
1111
+ if stmt:
1112
+ node.children.append(stmt)
1113
+ else:
1114
+ self._advance()
1115
+
1116
+ self._expect(TokenType.BLOCK_END)
1117
+ return node
1118
+
1119
+ def _parse_service_run(self) -> ASTNode:
1120
+ node = ASTNode('service-run', children=[])
1121
+ self._expect(TokenType.BLOCK_START)
1122
+
1123
+ while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
1124
+ if self._match_keyword('struct'):
1125
+ node.children.append(self._parse_struct())
1126
+ elif self._match_keyword('define'):
1127
+ node.children.append(self._parse_define())
1128
+ else:
1129
+ self._advance()
1130
+
1131
+ self._expect(TokenType.BLOCK_END)
1132
+ return node
1133
+
1134
+ def _parse_package(self) -> ASTNode:
1135
+ """Parse package {} block for service metadata - NEW
1136
+
1137
+ Syntax:
1138
+ package {
1139
+ service = "ServiceName";
1140
+ exec = @Start();
1141
+ version = "1.0.0";
1142
+ description = "Beschreibung";
1143
+ }
1144
+ """
1145
+ node = ASTNode('package', children=[])
1146
+ self._expect(TokenType.BLOCK_START)
1147
+
1148
+ while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
1149
+ if self._check(TokenType.IDENTIFIER) or self._check(TokenType.KEYWORD):
1150
+ key = self._advance().value
1151
+ self._expect(TokenType.EQUALS)
1152
+ value = self._parse_expression()
1153
+ node.children.append(ASTNode('package_property', value={'key': key, 'value': value}))
1154
+ self._match(TokenType.SEMICOLON)
1155
+ else:
1156
+ self._advance()
1157
+
1158
+ self._expect(TokenType.BLOCK_END)
1159
+ return node
1160
+
1161
+ def _parse_package_includes(self) -> ASTNode:
1162
+ """Parse package-includes {} block for imports - NEW
1163
+
1164
+ Syntax:
1165
+ package-includes {
1166
+ @Lists = get('list');
1167
+ @OS = get('os');
1168
+ @Time = get('time');
1169
+ @VSRam = get('vsramsdk');
1170
+ }
1171
+ """
1172
+ node = ASTNode('package-includes', children=[])
1173
+ self._expect(TokenType.BLOCK_START)
1174
+
1175
+ while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
1176
+ # Parse module injection statements like @ModuleName = get(...);
1177
+ if self._check(TokenType.AT):
1178
+ stmt = self._parse_expression_statement()
1179
+ if stmt:
1180
+ node.children.append(stmt)
1181
+ elif self._check(TokenType.IDENTIFIER):
1182
+ # Also support identifier-based assignments
1183
+ stmt = self._parse_expression_statement()
1184
+ if stmt:
1185
+ node.children.append(stmt)
1186
+ else:
1187
+ self._advance()
1188
+
1189
+ self._expect(TokenType.BLOCK_END)
1190
+ return node
1191
+
1192
+ def _parse_struct(self) -> ASTNode:
1193
+ name = self._advance().value
1194
+ is_global = False
1195
+
1196
+ # Check for (@) decorator: struct Name(@) { ... }
1197
+ if self._match(TokenType.PAREN_START):
1198
+ if self._check(TokenType.AT):
1199
+ self._advance() # skip @
1200
+ is_global = True
1201
+ self._expect(TokenType.PAREN_END)
1202
+
1203
+ node = ASTNode('struct', value={'name': name, 'global': is_global}, children=[])
1204
+ self._expect(TokenType.BLOCK_START)
1205
+
1206
+ while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
1207
+ if self._match_keyword('define'):
1208
+ node.children.append(self._parse_define())
1209
+ elif self._check(TokenType.IDENTIFIER):
1210
+ # Look ahead to determine what kind of statement this is
1211
+ saved_pos = self.pos
1212
+ var_name = self._advance().value
1213
+
1214
+ if self._match(TokenType.INJECT_LEFT):
1215
+ # Injection: var <== expr
1216
+ value = self._parse_expression()
1217
+ node.children.append(ASTNode('injection', value={'name': var_name, 'source': value}))
1218
+ self._match(TokenType.SEMICOLON)
1219
+ elif self._match(TokenType.EQUALS):
1220
+ # Assignment: var = expr
1221
+ value = self._parse_expression()
1222
+ node.children.append(ASTNode('assignment', value={'name': var_name, 'value': value}))
1223
+ self._match(TokenType.SEMICOLON)
1224
+ elif self._check(TokenType.PAREN_START):
1225
+ # Function call: func(args)
1226
+ self.pos = saved_pos # Go back to parse full expression
1227
+ stmt = self._parse_expression_statement()
1228
+ if stmt:
1229
+ node.children.append(stmt)
1230
+ elif self._match(TokenType.DOT):
1231
+ # Method call: obj.method(args)
1232
+ self.pos = saved_pos # Go back to parse full expression
1233
+ stmt = self._parse_expression_statement()
1234
+ if stmt:
1235
+ node.children.append(stmt)
1236
+ else:
1237
+ self._match(TokenType.SEMICOLON)
1238
+ elif self._check(TokenType.AT):
1239
+ # Module reference statement
1240
+ stmt = self._parse_expression_statement()
1241
+ if stmt:
1242
+ node.children.append(stmt)
1243
+ else:
1244
+ self._advance()
1245
+
1246
+ self._expect(TokenType.BLOCK_END)
1247
+ return node
1248
+
1249
+ def _parse_class(self) -> ASTNode:
1250
+ """Parse class declaration with members and methods.
1251
+
1252
+ Syntax:
1253
+ class ClassName {
1254
+ string name;
1255
+ int age;
1256
+
1257
+ void ClassName(string n) { this->name = n; }
1258
+
1259
+ void sayHello() {
1260
+ printl("Hello " + this->name);
1261
+ }
1262
+ }
1263
+ """
1264
+ class_name = self._advance().value
1265
+
1266
+ node = ASTNode('class', value={'name': class_name}, children=[])
1267
+ self._expect(TokenType.BLOCK_START)
1268
+
1269
+ while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
1270
+ # Check for typed function (method) declaration
1271
+ if self._looks_like_function_declaration():
1272
+ method = self._parse_typed_function()
1273
+ method_info = method.value
1274
+ method_name = method_info.get('name')
1275
+
1276
+ # Mark constructor (same name as class or __init__)
1277
+ if method_name == class_name or method_name == '__init__':
1278
+ method.value['is_constructor'] = True
1279
+
1280
+ node.children.append(method)
1281
+
1282
+ # Check for typed member variable declaration
1283
+ elif self._looks_like_typed_variable():
1284
+ member = self._parse_typed_variable()
1285
+ if member:
1286
+ # Mark as class member
1287
+ member.value['is_member'] = True
1288
+ node.children.append(member)
1289
+
1290
+ # Check for define-style method
1291
+ elif self._match_keyword('define'):
1292
+ method = self._parse_define()
1293
+ node.children.append(method)
1294
+
1295
+ else:
1296
+ self._advance()
1297
+
1298
+ self._expect(TokenType.BLOCK_END)
1299
+ return node
1300
+
1301
+ def _parse_define(self) -> ASTNode:
1302
+ name = self._advance().value
1303
+ params = []
1304
+
1305
+ if self._match(TokenType.PAREN_START):
1306
+ while not self._check(TokenType.PAREN_END):
1307
+ param_info = {}
1308
+ # Handle 'open' keyword for open parameters
1309
+ if self._match_keyword('open'):
1310
+ param_info['open'] = True
1311
+ # Handle type annotations (e.g., string, int, dynamic, etc.)
1312
+ if self._check(TokenType.KEYWORD):
1313
+ param_info['type'] = self._advance().value
1314
+ # Handle reference operator &
1315
+ if self._match(TokenType.AMPERSAND):
1316
+ param_info['ref'] = True
1317
+ # Get parameter name
1318
+ if self._check(TokenType.IDENTIFIER):
1319
+ param_name = self._advance().value
1320
+ if param_info:
1321
+ params.append({'name': param_name, **param_info})
1322
+ else:
1323
+ params.append(param_name)
1324
+ self._match(TokenType.COMMA)
1325
+ elif self._check(TokenType.KEYWORD):
1326
+ # Parameter name could be a keyword like 'Params'
1327
+ param_name = self._advance().value
1328
+ if param_info:
1329
+ params.append({'name': param_name, **param_info})
1330
+ else:
1331
+ params.append(param_name)
1332
+ self._match(TokenType.COMMA)
1333
+ else:
1334
+ break
1335
+ self._expect(TokenType.PAREN_END)
1336
+
1337
+ node = ASTNode('function', value={'name': name, 'params': params}, children=[])
1338
+ self._expect(TokenType.BLOCK_START)
1339
+
1340
+ while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
1341
+ stmt = self._parse_statement()
1342
+ if stmt:
1343
+ node.children.append(stmt)
1344
+
1345
+ self._expect(TokenType.BLOCK_END)
1346
+ return node
1347
+
1348
+ def _parse_statement(self) -> Optional[ASTNode]:
1349
+ if self._match_keyword('if'):
1350
+ return self._parse_if()
1351
+ elif self._match_keyword('while'):
1352
+ return self._parse_while()
1353
+ elif self._match_keyword('for'):
1354
+ return self._parse_for()
1355
+ elif self._match_keyword('foreach'):
1356
+ return self._parse_foreach()
1357
+ elif self._match_keyword('switch'):
1358
+ return self._parse_switch()
1359
+ elif self._match_keyword('return'):
1360
+ return self._parse_return()
1361
+ elif self._match_keyword('break'):
1362
+ self._match(TokenType.SEMICOLON)
1363
+ return ASTNode('break')
1364
+ elif self._match_keyword('continue'):
1365
+ self._match(TokenType.SEMICOLON)
1366
+ return ASTNode('continue')
1367
+ elif self._match_keyword('try'):
1368
+ return self._parse_try()
1369
+ elif self._match_keyword('await'):
1370
+ return self._parse_await()
1371
+ elif self._match_keyword('define'):
1372
+ # Nested define function
1373
+ return self._parse_define()
1374
+ elif self._looks_like_typed_variable():
1375
+ # Typed variable declaration (e.g., stack<string> myStack;)
1376
+ return self._parse_typed_variable()
1377
+ elif self._looks_like_function_declaration():
1378
+ # Nested typed function (e.g., void Level2() { ... })
1379
+ return self._parse_typed_function()
1380
+ elif (self._check(TokenType.IDENTIFIER) or self._check(TokenType.AT) or
1381
+ self._check(TokenType.CAPTURED_REF) or self._check(TokenType.SHARED_REF) or
1382
+ self._check(TokenType.GLOBAL_REF) or self._check(TokenType.SELF_REF) or
1383
+ (self._check(TokenType.KEYWORD) and self._current().value in ('this', 'new'))):
1384
+ return self._parse_expression_statement()
1385
+ else:
1386
+ self._advance()
1387
+ return None
1388
+
1389
+ def _parse_if(self) -> ASTNode:
1390
+ """Parse if statement with support for else if AND elif syntax."""
1391
+ self._expect(TokenType.PAREN_START)
1392
+ condition = self._parse_expression()
1393
+ self._expect(TokenType.PAREN_END)
1394
+
1395
+ node = ASTNode('if', value={'condition': condition}, children=[])
1396
+
1397
+ self._expect(TokenType.BLOCK_START)
1398
+ then_block = ASTNode('then', children=[])
1399
+ while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
1400
+ stmt = self._parse_statement()
1401
+ if stmt:
1402
+ then_block.children.append(stmt)
1403
+ self._expect(TokenType.BLOCK_END)
1404
+ node.children.append(then_block)
1405
+
1406
+ # Support both 'else if' AND 'elif' syntax
1407
+ if self._match_keyword('elif'):
1408
+ # elif is shorthand for else if
1409
+ else_block = ASTNode('else', children=[])
1410
+ else_block.children.append(self._parse_if())
1411
+ node.children.append(else_block)
1412
+ elif self._match_keyword('else'):
1413
+ else_block = ASTNode('else', children=[])
1414
+ if self._match_keyword('if'):
1415
+ else_block.children.append(self._parse_if())
1416
+ else:
1417
+ self._expect(TokenType.BLOCK_START)
1418
+ while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
1419
+ stmt = self._parse_statement()
1420
+ if stmt:
1421
+ else_block.children.append(stmt)
1422
+ self._expect(TokenType.BLOCK_END)
1423
+ node.children.append(else_block)
1424
+
1425
+ return node
1426
+
1427
+ def _parse_while(self) -> ASTNode:
1428
+ self._expect(TokenType.PAREN_START)
1429
+ condition = self._parse_expression()
1430
+ self._expect(TokenType.PAREN_END)
1431
+
1432
+ node = ASTNode('while', value={'condition': condition}, children=[])
1433
+ self._expect(TokenType.BLOCK_START)
1434
+
1435
+ while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
1436
+ stmt = self._parse_statement()
1437
+ if stmt:
1438
+ node.children.append(stmt)
1439
+
1440
+ self._expect(TokenType.BLOCK_END)
1441
+ return node
1442
+
1443
+ def _parse_for(self) -> ASTNode:
1444
+ """Parse for loop - supports both syntaxes:
1445
+
1446
+ Python-style: for (i in range(0, n)) { }
1447
+ C-style: for (int i = 0; i < n; i = i + 1) { }
1448
+ for (i = 0; i < n; i++) { }
1449
+ """
1450
+ self._expect(TokenType.PAREN_START)
1451
+
1452
+ # Detect C-style by checking for semicolons in the for header
1453
+ # Look ahead without consuming tokens
1454
+ is_c_style = self._detect_c_style_for()
1455
+
1456
+ if is_c_style:
1457
+ # C-style: for (init; condition; update) { }
1458
+ return self._parse_c_style_for()
1459
+ else:
1460
+ # Python-style: for (var in range(start, end)) { }
1461
+ return self._parse_python_style_for()
1462
+
1463
+ def _detect_c_style_for(self) -> bool:
1464
+ """Detect if this is a C-style for loop by looking for semicolons."""
1465
+ # Scan the tokens list directly without modifying self.pos
1466
+ pos = self.pos
1467
+ paren_depth = 1
1468
+
1469
+ while pos < len(self.tokens) and paren_depth > 0:
1470
+ token = self.tokens[pos]
1471
+ if token.type == TokenType.PAREN_START:
1472
+ paren_depth += 1
1473
+ elif token.type == TokenType.PAREN_END:
1474
+ paren_depth -= 1
1475
+ elif token.type == TokenType.SEMICOLON and paren_depth == 1:
1476
+ # Found semicolon at top level - C-style
1477
+ return True
1478
+ elif token.type == TokenType.KEYWORD and token.value == 'in':
1479
+ # Found 'in' keyword - Python-style
1480
+ return False
1481
+ pos += 1
1482
+
1483
+ return False # Default to Python-style
1484
+
1485
+ def _parse_c_style_for(self) -> ASTNode:
1486
+ """Parse C-style for loop: for (init; condition; update) { }"""
1487
+ # Parse init statement
1488
+ init = None
1489
+ if not self._check(TokenType.SEMICOLON):
1490
+ # Check if it's a typed declaration: int i = 0
1491
+ if self._check(TokenType.KEYWORD) and self._peek().value in ('int', 'float', 'string', 'bool', 'dynamic'):
1492
+ type_name = self._advance().value
1493
+ var_name = self._advance().value
1494
+ self._expect(TokenType.EQUALS)
1495
+ value = self._parse_expression()
1496
+ init = ASTNode('c_for_init', value={
1497
+ 'type': type_name,
1498
+ 'var': var_name,
1499
+ 'value': value
1500
+ })
1501
+ else:
1502
+ # Simple assignment: i = 0
1503
+ var_name = self._advance().value
1504
+ self._expect(TokenType.EQUALS)
1505
+ value = self._parse_expression()
1506
+ init = ASTNode('c_for_init', value={
1507
+ 'type': None,
1508
+ 'var': var_name,
1509
+ 'value': value
1510
+ })
1511
+
1512
+ self._expect(TokenType.SEMICOLON)
1513
+
1514
+ # Parse condition
1515
+ condition = None
1516
+ if not self._check(TokenType.SEMICOLON):
1517
+ condition = self._parse_expression()
1518
+
1519
+ self._expect(TokenType.SEMICOLON)
1520
+
1521
+ # Parse update statement
1522
+ update = None
1523
+ if not self._check(TokenType.PAREN_END):
1524
+ # Could be: i = i + 1, i++, ++i, i += 1
1525
+ update = self._parse_c_for_update()
1526
+
1527
+ self._expect(TokenType.PAREN_END)
1528
+
1529
+ node = ASTNode('c_for', value={
1530
+ 'init': init,
1531
+ 'condition': condition,
1532
+ 'update': update
1533
+ }, children=[])
1534
+
1535
+ self._expect(TokenType.BLOCK_START)
1536
+
1537
+ while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
1538
+ stmt = self._parse_statement()
1539
+ if stmt:
1540
+ node.children.append(stmt)
1541
+
1542
+ self._expect(TokenType.BLOCK_END)
1543
+ return node
1544
+
1545
+ def _parse_c_for_update(self) -> ASTNode:
1546
+ """Parse the update part of a C-style for loop.
1547
+
1548
+ Supports: i = i + 1, i++, ++i, i += 1, i -= 1
1549
+ """
1550
+ # Check for prefix increment/decrement: ++i or --i
1551
+ if self._check(TokenType.PLUS) or self._check(TokenType.MINUS):
1552
+ op_token = self._advance()
1553
+ # Check for double operator (++ or --)
1554
+ if self._check(op_token.type):
1555
+ self._advance()
1556
+ var_name = self._advance().value
1557
+ op = 'increment' if op_token.type == TokenType.PLUS else 'decrement'
1558
+ return ASTNode('c_for_update', value={'var': var_name, 'op': op})
1559
+
1560
+ # Regular variable assignment or postfix
1561
+ var_name = self._advance().value
1562
+
1563
+ # Check for postfix increment/decrement: i++ or i--
1564
+ if self._check(TokenType.PLUS):
1565
+ self._advance()
1566
+ if self._check(TokenType.PLUS):
1567
+ self._advance()
1568
+ return ASTNode('c_for_update', value={'var': var_name, 'op': 'increment'})
1569
+ else:
1570
+ # i += value
1571
+ if self._check(TokenType.EQUALS):
1572
+ self._advance()
1573
+ value = self._parse_expression()
1574
+ return ASTNode('c_for_update', value={'var': var_name, 'op': 'add', 'value': value})
1575
+ elif self._check(TokenType.MINUS):
1576
+ self._advance()
1577
+ if self._check(TokenType.MINUS):
1578
+ self._advance()
1579
+ return ASTNode('c_for_update', value={'var': var_name, 'op': 'decrement'})
1580
+ else:
1581
+ # i -= value
1582
+ if self._check(TokenType.EQUALS):
1583
+ self._advance()
1584
+ value = self._parse_expression()
1585
+ return ASTNode('c_for_update', value={'var': var_name, 'op': 'subtract', 'value': value})
1586
+
1587
+ # Regular assignment: i = expression
1588
+ if self._check(TokenType.EQUALS):
1589
+ self._advance()
1590
+ value = self._parse_expression()
1591
+ return ASTNode('c_for_update', value={'var': var_name, 'op': 'assign', 'value': value})
1592
+
1593
+ # Just the variable (shouldn't happen but handle it)
1594
+ return ASTNode('c_for_update', value={'var': var_name, 'op': 'none'})
1595
+
1596
+ def _parse_python_style_for(self) -> ASTNode:
1597
+ """Parse Python-style for loop: for (i in range(...)) { }
1598
+
1599
+ Supports:
1600
+ for (i in range(n)) { } - 0 to n-1
1601
+ for (i in range(start, end)) { } - start to end-1
1602
+ for (i in range(start, end, step)) { }
1603
+ """
1604
+ var_name = self._advance().value
1605
+ self._expect(TokenType.KEYWORD) # 'in'
1606
+
1607
+ # 'range' can be keyword or identifier
1608
+ if self._check(TokenType.KEYWORD) and self._peek().value == 'range':
1609
+ self._advance() # consume 'range' keyword
1610
+ elif self._check(TokenType.IDENTIFIER) and self._peek().value == 'range':
1611
+ self._advance() # consume 'range' identifier
1612
+ else:
1613
+ self.error(f"Expected 'range', got {self._peek().value}")
1614
+
1615
+ self._expect(TokenType.PAREN_START)
1616
+ first_arg = self._parse_expression()
1617
+
1618
+ # Check if there are more arguments
1619
+ start = None
1620
+ end = None
1621
+ step = None
1622
+
1623
+ if self._check(TokenType.COMMA):
1624
+ # range(start, end) or range(start, end, step)
1625
+ self._advance() # consume comma
1626
+ start = first_arg
1627
+ end = self._parse_expression()
1628
+
1629
+ # Optional step parameter
1630
+ if self._check(TokenType.COMMA):
1631
+ self._advance() # consume comma
1632
+ step = self._parse_expression()
1633
+ else:
1634
+ # range(n) - single argument means 0 to n-1
1635
+ start = ASTNode('literal', value={'type': 'int', 'value': 0})
1636
+ end = first_arg
1637
+
1638
+ self._expect(TokenType.PAREN_END)
1639
+ self._expect(TokenType.PAREN_END)
1640
+
1641
+ node = ASTNode('for', value={'var': var_name, 'start': start, 'end': end, 'step': step}, children=[])
1642
+ self._expect(TokenType.BLOCK_START)
1643
+
1644
+ while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
1645
+ stmt = self._parse_statement()
1646
+ if stmt:
1647
+ node.children.append(stmt)
1648
+
1649
+ self._expect(TokenType.BLOCK_END)
1650
+ return node
1651
+
1652
+ def _parse_foreach(self) -> ASTNode:
1653
+ """Parse foreach loop - supports both syntaxes:
1654
+
1655
+ Traditional: foreach (var in iterable) { }
1656
+ New 'as' syntax: foreach iterable as var { }
1657
+ """
1658
+ # Check if this is the new 'as' syntax or traditional syntax
1659
+ if self._check(TokenType.PAREN_START):
1660
+ # Traditional syntax: foreach (var in iterable) { }
1661
+ self._expect(TokenType.PAREN_START)
1662
+ var_name = self._advance().value
1663
+ self._match_keyword('in')
1664
+ iterable = self._parse_expression()
1665
+ self._expect(TokenType.PAREN_END)
1666
+ else:
1667
+ # NEW: 'as' syntax: foreach iterable as var { }
1668
+ iterable = self._parse_expression()
1669
+ if self._check(TokenType.AS):
1670
+ self._advance() # consume 'as'
1671
+ else:
1672
+ self._match_keyword('as') # try keyword match as fallback
1673
+ var_name = self._advance().value
1674
+
1675
+ node = ASTNode('foreach', value={'var': var_name, 'iterable': iterable}, children=[])
1676
+ self._expect(TokenType.BLOCK_START)
1677
+
1678
+ while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
1679
+ stmt = self._parse_statement()
1680
+ if stmt:
1681
+ node.children.append(stmt)
1682
+
1683
+ self._expect(TokenType.BLOCK_END)
1684
+ return node
1685
+
1686
+ def _parse_switch(self) -> ASTNode:
1687
+ self._expect(TokenType.PAREN_START)
1688
+ value = self._parse_expression()
1689
+ self._expect(TokenType.PAREN_END)
1690
+
1691
+ node = ASTNode('switch', value={'value': value}, children=[])
1692
+ self._expect(TokenType.BLOCK_START)
1693
+
1694
+ while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
1695
+ if self._match_keyword('case'):
1696
+ case_value = self._parse_expression()
1697
+ self._expect(TokenType.COLON)
1698
+ case_node = ASTNode('case', value={'value': case_value}, children=[])
1699
+
1700
+ while not self._check_keyword('case') and not self._check_keyword('default') and not self._check(TokenType.BLOCK_END):
1701
+ stmt = self._parse_statement()
1702
+ if stmt:
1703
+ case_node.children.append(stmt)
1704
+ if self._check_keyword('break'):
1705
+ break
1706
+
1707
+ node.children.append(case_node)
1708
+ elif self._match_keyword('default'):
1709
+ self._expect(TokenType.COLON)
1710
+ default_node = ASTNode('default', children=[])
1711
+
1712
+ while not self._check(TokenType.BLOCK_END):
1713
+ stmt = self._parse_statement()
1714
+ if stmt:
1715
+ default_node.children.append(stmt)
1716
+
1717
+ node.children.append(default_node)
1718
+ else:
1719
+ self._advance()
1720
+
1721
+ self._expect(TokenType.BLOCK_END)
1722
+ return node
1723
+
1724
+ def _parse_return(self) -> ASTNode:
1725
+ value = None
1726
+ if not self._check(TokenType.SEMICOLON) and not self._check(TokenType.BLOCK_END):
1727
+ value = self._parse_expression()
1728
+ self._match(TokenType.SEMICOLON)
1729
+ return ASTNode('return', value=value)
1730
+
1731
+ def _parse_try(self) -> ASTNode:
1732
+ node = ASTNode('try', children=[])
1733
+
1734
+ try_block = ASTNode('try-block', children=[])
1735
+ self._expect(TokenType.BLOCK_START)
1736
+ while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
1737
+ stmt = self._parse_statement()
1738
+ if stmt:
1739
+ try_block.children.append(stmt)
1740
+ self._expect(TokenType.BLOCK_END)
1741
+ node.children.append(try_block)
1742
+
1743
+ if self._match_keyword('catch'):
1744
+ error_var = None
1745
+ if self._match(TokenType.PAREN_START):
1746
+ error_var = self._advance().value
1747
+ self._expect(TokenType.PAREN_END)
1748
+
1749
+ catch_block = ASTNode('catch-block', value={'error_var': error_var}, children=[])
1750
+ self._expect(TokenType.BLOCK_START)
1751
+ while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
1752
+ stmt = self._parse_statement()
1753
+ if stmt:
1754
+ catch_block.children.append(stmt)
1755
+ self._expect(TokenType.BLOCK_END)
1756
+ node.children.append(catch_block)
1757
+
1758
+ return node
1759
+
1760
+ def _parse_await(self) -> ASTNode:
1761
+ """Parse await statement: await expression;"""
1762
+ expr = self._parse_expression()
1763
+ self._match(TokenType.SEMICOLON)
1764
+ return ASTNode('await', value=expr)
1765
+
1766
+ def _parse_action_block(self) -> ASTNode:
1767
+ """Parse an action block { ... } containing statements for createcmd"""
1768
+ node = ASTNode('action_block', children=[])
1769
+ self._expect(TokenType.BLOCK_START)
1770
+
1771
+ while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
1772
+ # Check for define statements inside action block
1773
+ if self._match_keyword('define'):
1774
+ node.children.append(self._parse_define())
1775
+ # Check for typed function definitions (nested functions)
1776
+ elif self._looks_like_function_declaration():
1777
+ node.children.append(self._parse_typed_function())
1778
+ else:
1779
+ stmt = self._parse_statement()
1780
+ if stmt:
1781
+ node.children.append(stmt)
1782
+
1783
+ self._expect(TokenType.BLOCK_END)
1784
+ return node
1785
+
1786
+ def _parse_injection_filter(self) -> Optional[dict]:
1787
+ """Parse injection filter: [type::helper=value]"""
1788
+ if not self._match(TokenType.BRACKET_START):
1789
+ return None
1790
+
1791
+ filter_info = {}
1792
+ # Parse type::helper=value patterns
1793
+ while not self._check(TokenType.BRACKET_END) and not self._is_at_end():
1794
+ if self._check(TokenType.IDENTIFIER) or self._check(TokenType.KEYWORD):
1795
+ filter_type = self._advance().value
1796
+ if self._match(TokenType.DOUBLE_COLON):
1797
+ helper = self._advance().value
1798
+ if self._match(TokenType.EQUALS):
1799
+ value = self._parse_expression()
1800
+ filter_info[f'{filter_type}::{helper}'] = value
1801
+ else:
1802
+ filter_info[f'{filter_type}::{helper}'] = True
1803
+ else:
1804
+ filter_info['type'] = filter_type
1805
+ elif self._check(TokenType.COMMA):
1806
+ self._advance()
1807
+ else:
1808
+ break
1809
+
1810
+ self._expect(TokenType.BRACKET_END)
1811
+ return filter_info if filter_info else None
1812
+
1813
+ def _parse_expression_statement(self) -> Optional[ASTNode]:
1814
+ expr = self._parse_expression()
1815
+
1816
+ # === BASIC INJECTION: <== (replace target with source) ===
1817
+ if self._match(TokenType.INJECT_LEFT):
1818
+ # Check if this is a createcmd injection with a code block
1819
+ is_createcmd = (
1820
+ expr.type == 'call' and
1821
+ expr.value.get('callee') and
1822
+ expr.value.get('callee').type == 'identifier' and
1823
+ expr.value.get('callee').value == 'createcmd'
1824
+ )
1825
+
1826
+ if is_createcmd and self._check(TokenType.BLOCK_START):
1827
+ action_block = self._parse_action_block()
1828
+ self._match(TokenType.SEMICOLON)
1829
+ return ASTNode('createcmd_inject', value={'command_call': expr, 'action': action_block})
1830
+ else:
1831
+ # Check for injection filter [type::helper=value]
1832
+ filter_info = self._parse_injection_filter()
1833
+ source = self._parse_expression()
1834
+ self._match(TokenType.SEMICOLON)
1835
+ return ASTNode('inject', value={'target': expr, 'source': source, 'mode': 'replace', 'filter': filter_info})
1836
+
1837
+ # === PLUS INJECTION: +<== (copy & add to target) ===
1838
+ if self._match(TokenType.INJECT_PLUS_LEFT):
1839
+ filter_info = self._parse_injection_filter()
1840
+ source = self._parse_expression()
1841
+ self._match(TokenType.SEMICOLON)
1842
+ return ASTNode('inject', value={'target': expr, 'source': source, 'mode': 'add', 'filter': filter_info})
1843
+
1844
+ # === MINUS INJECTION: -<== or -<==[n] (move & remove from source) ===
1845
+ if self._match(TokenType.INJECT_MINUS_LEFT):
1846
+ # Check for indexed deletion: -<==[n] (only numbers, not filters)
1847
+ remove_index = None
1848
+ if self._check(TokenType.BRACKET_START):
1849
+ # Peek ahead to see if this is an index [n] or a filter [type::helper=...]
1850
+ # Only consume if it's a simple number index
1851
+ saved_pos = self.pos
1852
+ self._advance() # consume [
1853
+ if self._check(TokenType.NUMBER):
1854
+ remove_index = int(self._advance().value)
1855
+ self._expect(TokenType.BRACKET_END)
1856
+ else:
1857
+ # Not a number - restore position for filter parsing
1858
+ self.pos = saved_pos
1859
+
1860
+ filter_info = self._parse_injection_filter()
1861
+ source = self._parse_expression()
1862
+ self._match(TokenType.SEMICOLON)
1863
+ return ASTNode('inject', value={'target': expr, 'source': source, 'mode': 'move', 'filter': filter_info, 'index': remove_index})
1864
+
1865
+ # === CODE INFUSION: <<== (inject code into function) ===
1866
+ if self._match(TokenType.INFUSE_LEFT):
1867
+ if self._check(TokenType.BLOCK_START):
1868
+ code_block = self._parse_action_block()
1869
+ self._match(TokenType.SEMICOLON)
1870
+ return ASTNode('infuse', value={'target': expr, 'code': code_block, 'mode': 'replace'})
1871
+ else:
1872
+ source = self._parse_expression()
1873
+ self._match(TokenType.SEMICOLON)
1874
+ return ASTNode('infuse', value={'target': expr, 'source': source, 'mode': 'replace'})
1875
+
1876
+ # === CODE INFUSION PLUS: +<<== (add code to function) ===
1877
+ if self._match(TokenType.INFUSE_PLUS_LEFT):
1878
+ if self._check(TokenType.BLOCK_START):
1879
+ code_block = self._parse_action_block()
1880
+ self._match(TokenType.SEMICOLON)
1881
+ return ASTNode('infuse', value={'target': expr, 'code': code_block, 'mode': 'add'})
1882
+ else:
1883
+ source = self._parse_expression()
1884
+ self._match(TokenType.SEMICOLON)
1885
+ return ASTNode('infuse', value={'target': expr, 'source': source, 'mode': 'add'})
1886
+
1887
+ # === CODE INFUSION MINUS: -<<== or -<<==[n] (remove code from function) ===
1888
+ if self._match(TokenType.INFUSE_MINUS_LEFT):
1889
+ # Check for indexed deletion: -<<==[n] (only numbers)
1890
+ remove_index = None
1891
+ if self._check(TokenType.BRACKET_START):
1892
+ # Peek ahead to see if this is an index [n] or something else
1893
+ saved_pos = self.pos
1894
+ self._advance() # consume [
1895
+ if self._check(TokenType.NUMBER):
1896
+ remove_index = int(self._advance().value)
1897
+ self._expect(TokenType.BRACKET_END)
1898
+ else:
1899
+ # Not a number - restore position
1900
+ self.pos = saved_pos
1901
+
1902
+ if self._check(TokenType.BLOCK_START):
1903
+ code_block = self._parse_action_block()
1904
+ self._match(TokenType.SEMICOLON)
1905
+ return ASTNode('infuse', value={'target': expr, 'code': code_block, 'mode': 'remove', 'index': remove_index})
1906
+ else:
1907
+ source = self._parse_expression()
1908
+ self._match(TokenType.SEMICOLON)
1909
+ return ASTNode('infuse', value={'target': expr, 'source': source, 'mode': 'remove', 'index': remove_index})
1910
+
1911
+ # === RIGHT-SIDE OPERATORS ===
1912
+
1913
+ # === BASIC RECEIVE: ==> (move source to target) ===
1914
+ if self._match(TokenType.INJECT_RIGHT):
1915
+ filter_info = self._parse_injection_filter()
1916
+ target = self._parse_expression()
1917
+ self._match(TokenType.SEMICOLON)
1918
+ return ASTNode('receive', value={'source': expr, 'target': target, 'mode': 'replace', 'filter': filter_info})
1919
+
1920
+ # === PLUS RECEIVE: ==>+ (copy source to target) ===
1921
+ if self._match(TokenType.INJECT_PLUS_RIGHT):
1922
+ filter_info = self._parse_injection_filter()
1923
+ target = self._parse_expression()
1924
+ self._match(TokenType.SEMICOLON)
1925
+ return ASTNode('receive', value={'source': expr, 'target': target, 'mode': 'add', 'filter': filter_info})
1926
+
1927
+ # === MINUS RECEIVE: -==> (move & remove from source) ===
1928
+ if self._match(TokenType.INJECT_MINUS_RIGHT):
1929
+ filter_info = self._parse_injection_filter()
1930
+ target = self._parse_expression()
1931
+ self._match(TokenType.SEMICOLON)
1932
+ return ASTNode('receive', value={'source': expr, 'target': target, 'mode': 'move', 'filter': filter_info})
1933
+
1934
+ # === CODE INFUSION RIGHT: ==>> ===
1935
+ if self._match(TokenType.INFUSE_RIGHT):
1936
+ target = self._parse_expression()
1937
+ self._match(TokenType.SEMICOLON)
1938
+ return ASTNode('infuse_right', value={'source': expr, 'target': target, 'mode': 'replace'})
1939
+
1940
+ # === FLOW OPERATORS ===
1941
+ if self._match(TokenType.FLOW_RIGHT):
1942
+ target = self._parse_expression()
1943
+ self._match(TokenType.SEMICOLON)
1944
+ return ASTNode('flow', value={'source': expr, 'target': target})
1945
+
1946
+ if self._match(TokenType.FLOW_LEFT):
1947
+ source = self._parse_expression()
1948
+ self._match(TokenType.SEMICOLON)
1949
+ return ASTNode('flow', value={'source': source, 'target': expr})
1950
+
1951
+ # === BASIC ASSIGNMENT ===
1952
+ if self._match(TokenType.EQUALS):
1953
+ value = self._parse_expression()
1954
+ self._match(TokenType.SEMICOLON)
1955
+ return ASTNode('assignment', value={'target': expr, 'value': value})
1956
+
1957
+ self._match(TokenType.SEMICOLON)
1958
+ return ASTNode('expression', value=expr)
1959
+
1960
+ def _parse_expression(self) -> ASTNode:
1961
+ return self._parse_or()
1962
+
1963
+ def _parse_or(self) -> ASTNode:
1964
+ left = self._parse_and()
1965
+
1966
+ while self._match(TokenType.OR) or self._match_keyword('or'):
1967
+ right = self._parse_and()
1968
+ left = ASTNode('binary', value={'op': 'or', 'left': left, 'right': right})
1969
+
1970
+ return left
1971
+
1972
+ def _parse_and(self) -> ASTNode:
1973
+ left = self._parse_comparison()
1974
+
1975
+ while self._match(TokenType.AND) or self._match_keyword('and'):
1976
+ right = self._parse_comparison()
1977
+ left = ASTNode('binary', value={'op': 'and', 'left': left, 'right': right})
1978
+
1979
+ return left
1980
+
1981
+ def _parse_comparison(self) -> ASTNode:
1982
+ left = self._parse_term()
1983
+
1984
+ while True:
1985
+ if self._match(TokenType.COMPARE_EQ):
1986
+ right = self._parse_term()
1987
+ left = ASTNode('binary', value={'op': '==', 'left': left, 'right': right})
1988
+ elif self._match(TokenType.COMPARE_NE):
1989
+ right = self._parse_term()
1990
+ left = ASTNode('binary', value={'op': '!=', 'left': left, 'right': right})
1991
+ elif self._match(TokenType.COMPARE_LT):
1992
+ right = self._parse_term()
1993
+ left = ASTNode('binary', value={'op': '<', 'left': left, 'right': right})
1994
+ elif self._match(TokenType.COMPARE_GT):
1995
+ right = self._parse_term()
1996
+ left = ASTNode('binary', value={'op': '>', 'left': left, 'right': right})
1997
+ elif self._match(TokenType.COMPARE_LE):
1998
+ right = self._parse_term()
1999
+ left = ASTNode('binary', value={'op': '<=', 'left': left, 'right': right})
2000
+ elif self._match(TokenType.COMPARE_GE):
2001
+ right = self._parse_term()
2002
+ left = ASTNode('binary', value={'op': '>=', 'left': left, 'right': right})
2003
+ else:
2004
+ break
2005
+
2006
+ return left
2007
+
2008
+ def _parse_term(self) -> ASTNode:
2009
+ left = self._parse_factor()
2010
+
2011
+ while True:
2012
+ if self._match(TokenType.PLUS):
2013
+ right = self._parse_factor()
2014
+ left = ASTNode('binary', value={'op': '+', 'left': left, 'right': right})
2015
+ elif self._match(TokenType.MINUS):
2016
+ right = self._parse_factor()
2017
+ left = ASTNode('binary', value={'op': '-', 'left': left, 'right': right})
2018
+ else:
2019
+ break
2020
+
2021
+ return left
2022
+
2023
+ def _parse_factor(self) -> ASTNode:
2024
+ left = self._parse_unary()
2025
+
2026
+ while True:
2027
+ if self._match(TokenType.MULTIPLY):
2028
+ right = self._parse_unary()
2029
+ left = ASTNode('binary', value={'op': '*', 'left': left, 'right': right})
2030
+ elif self._match(TokenType.DIVIDE):
2031
+ right = self._parse_unary()
2032
+ left = ASTNode('binary', value={'op': '/', 'left': left, 'right': right})
2033
+ elif self._match(TokenType.MODULO):
2034
+ right = self._parse_unary()
2035
+ left = ASTNode('binary', value={'op': '%', 'left': left, 'right': right})
2036
+ else:
2037
+ break
2038
+
2039
+ return left
2040
+
2041
+ def _parse_unary(self) -> ASTNode:
2042
+ if self._match(TokenType.NOT) or self._match_keyword('not'):
2043
+ operand = self._parse_unary()
2044
+ return ASTNode('unary', value={'op': 'not', 'operand': operand})
2045
+ if self._match(TokenType.MINUS):
2046
+ operand = self._parse_unary()
2047
+ return ASTNode('unary', value={'op': '-', 'operand': operand})
2048
+ if self._match(TokenType.AMPERSAND):
2049
+ # Reference operator: &variable or &@module
2050
+ operand = self._parse_unary()
2051
+ return ASTNode('reference', value=operand)
2052
+
2053
+ return self._parse_primary()
2054
+
2055
+ def _parse_primary(self) -> ASTNode:
2056
+ # Handle 'this->' member access
2057
+ if self._check(TokenType.KEYWORD) and self._current().value == 'this':
2058
+ self._advance() # consume 'this'
2059
+ if self._match(TokenType.FLOW_RIGHT): # ->
2060
+ member = self._advance().value
2061
+ node = ASTNode('this_access', value={'member': member})
2062
+ # Continue to check for calls, member access, indexing
2063
+ while True:
2064
+ if self._match(TokenType.PAREN_START):
2065
+ # Method call: this->method()
2066
+ args, kwargs = self._parse_call_arguments()
2067
+ self._expect(TokenType.PAREN_END)
2068
+ node = ASTNode('call', value={'callee': node, 'args': args, 'kwargs': kwargs})
2069
+ elif self._match(TokenType.DOT):
2070
+ # Chained access: this->obj.method
2071
+ member = self._advance().value
2072
+ node = ASTNode('member_access', value={'object': node, 'member': member})
2073
+ elif self._match(TokenType.BRACKET_START):
2074
+ # Index access: this->arr[0]
2075
+ index = self._parse_expression()
2076
+ self._expect(TokenType.BRACKET_END)
2077
+ node = ASTNode('index_access', value={'object': node, 'index': index})
2078
+ elif self._match(TokenType.FLOW_RIGHT):
2079
+ # Chained this->a->b style access
2080
+ member = self._advance().value
2081
+ node = ASTNode('this_access', value={'member': member, 'object': node})
2082
+ else:
2083
+ break
2084
+ return node
2085
+ else:
2086
+ # Just 'this' keyword alone - return as identifier for now
2087
+ return ASTNode('identifier', value='this')
2088
+
2089
+ # Handle 'new ClassName(args)' instantiation
2090
+ if self._check(TokenType.KEYWORD) and self._current().value == 'new':
2091
+ self._advance() # consume 'new'
2092
+ class_name = self._advance().value # get class name
2093
+ args = []
2094
+ kwargs = {}
2095
+ if self._match(TokenType.PAREN_START):
2096
+ args, kwargs = self._parse_call_arguments()
2097
+ self._expect(TokenType.PAREN_END)
2098
+ node = ASTNode('new', value={'class': class_name, 'args': args, 'kwargs': kwargs})
2099
+ # Continue to check for member access, calls on the new object
2100
+ while True:
2101
+ if self._match(TokenType.DOT):
2102
+ member = self._advance().value
2103
+ node = ASTNode('member_access', value={'object': node, 'member': member})
2104
+ if self._match(TokenType.PAREN_START):
2105
+ args, kwargs = self._parse_call_arguments()
2106
+ self._expect(TokenType.PAREN_END)
2107
+ node = ASTNode('call', value={'callee': node, 'args': args, 'kwargs': kwargs})
2108
+ elif self._match(TokenType.BRACKET_START):
2109
+ index = self._parse_expression()
2110
+ self._expect(TokenType.BRACKET_END)
2111
+ node = ASTNode('index_access', value={'object': node, 'index': index})
2112
+ else:
2113
+ break
2114
+ return node
2115
+
2116
+ if self._match(TokenType.AT):
2117
+ node = self._parse_module_reference()
2118
+ # Continue to check for calls, indexing, member access on module refs
2119
+ while True:
2120
+ if self._match(TokenType.PAREN_START):
2121
+ # Function call on module ref: @Module.method() - with kwargs support
2122
+ args, kwargs = self._parse_call_arguments()
2123
+ self._expect(TokenType.PAREN_END)
2124
+ node = ASTNode('call', value={'callee': node, 'args': args, 'kwargs': kwargs})
2125
+ elif self._match(TokenType.DOT):
2126
+ # Member access: @Module.property
2127
+ member = self._advance().value
2128
+ node = ASTNode('member_access', value={'object': node, 'member': member})
2129
+ elif self._match(TokenType.BRACKET_START):
2130
+ # Index access: @Module[index]
2131
+ index = self._parse_expression()
2132
+ self._expect(TokenType.BRACKET_END)
2133
+ node = ASTNode('index_access', value={'object': node, 'index': index})
2134
+ else:
2135
+ break
2136
+ return node
2137
+
2138
+ if self._check(TokenType.SELF_REF):
2139
+ # s@<name> self-reference to global struct
2140
+ token = self._advance()
2141
+ node = ASTNode('self_ref', value=token.value, line=token.line, column=token.column)
2142
+ # Check for function call: s@Backend.Loop.Start() - with kwargs support
2143
+ if self._match(TokenType.PAREN_START):
2144
+ args, kwargs = self._parse_call_arguments()
2145
+ self._expect(TokenType.PAREN_END)
2146
+ node = ASTNode('call', value={'callee': node, 'args': args, 'kwargs': kwargs})
2147
+ return node
2148
+
2149
+ if self._check(TokenType.GLOBAL_REF):
2150
+ # r@<name> global variable reference/declaration
2151
+ token = self._advance()
2152
+ node = ASTNode('global_ref', value=token.value, line=token.line, column=token.column)
2153
+ # Check for member access, calls, indexing - with kwargs support
2154
+ while True:
2155
+ if self._match(TokenType.PAREN_START):
2156
+ args, kwargs = self._parse_call_arguments()
2157
+ self._expect(TokenType.PAREN_END)
2158
+ node = ASTNode('call', value={'callee': node, 'args': args, 'kwargs': kwargs})
2159
+ elif self._match(TokenType.DOT):
2160
+ member = self._advance().value
2161
+ node = ASTNode('member_access', value={'object': node, 'member': member})
2162
+ elif self._match(TokenType.BRACKET_START):
2163
+ index = self._parse_expression()
2164
+ self._expect(TokenType.BRACKET_END)
2165
+ node = ASTNode('index_access', value={'object': node, 'index': index})
2166
+ else:
2167
+ break
2168
+ return node
2169
+
2170
+ if self._check(TokenType.SHARED_REF):
2171
+ # $<name> shared object reference
2172
+ token = self._advance()
2173
+ node = ASTNode('shared_ref', value=token.value, line=token.line, column=token.column)
2174
+ # Check for member access, calls, indexing - with kwargs support
2175
+ while True:
2176
+ if self._match(TokenType.PAREN_START):
2177
+ args, kwargs = self._parse_call_arguments()
2178
+ self._expect(TokenType.PAREN_END)
2179
+ node = ASTNode('call', value={'callee': node, 'args': args, 'kwargs': kwargs})
2180
+ elif self._match(TokenType.DOT):
2181
+ member = self._advance().value
2182
+ node = ASTNode('member_access', value={'object': node, 'member': member})
2183
+ elif self._match(TokenType.BRACKET_START):
2184
+ index = self._parse_expression()
2185
+ self._expect(TokenType.BRACKET_END)
2186
+ node = ASTNode('index_access', value={'object': node, 'index': index})
2187
+ else:
2188
+ break
2189
+ return node
2190
+
2191
+ if self._check(TokenType.CAPTURED_REF):
2192
+ # %<name> captured reference (captures value at infusion registration time)
2193
+ token = self._advance()
2194
+ node = ASTNode('captured_ref', value=token.value, line=token.line, column=token.column)
2195
+ # Check for member access, calls, indexing - with kwargs support
2196
+ while True:
2197
+ if self._match(TokenType.PAREN_START):
2198
+ args, kwargs = self._parse_call_arguments()
2199
+ self._expect(TokenType.PAREN_END)
2200
+ node = ASTNode('call', value={'callee': node, 'args': args, 'kwargs': kwargs})
2201
+ elif self._match(TokenType.DOT):
2202
+ member = self._advance().value
2203
+ node = ASTNode('member_access', value={'object': node, 'member': member})
2204
+ elif self._match(TokenType.BRACKET_START):
2205
+ index = self._parse_expression()
2206
+ self._expect(TokenType.BRACKET_END)
2207
+ node = ASTNode('index_access', value={'object': node, 'index': index})
2208
+ else:
2209
+ break
2210
+ return node
2211
+
2212
+ if self._check(TokenType.NUMBER):
2213
+ return ASTNode('literal', value=self._advance().value)
2214
+
2215
+ if self._check(TokenType.STRING):
2216
+ return ASTNode('literal', value=self._advance().value)
2217
+
2218
+ if self._check(TokenType.BOOLEAN):
2219
+ return ASTNode('literal', value=self._advance().value)
2220
+
2221
+ if self._check(TokenType.NULL):
2222
+ self._advance()
2223
+ return ASTNode('literal', value=None)
2224
+
2225
+ # NEW: Type literals (list, dict) - create empty instances
2226
+ if self._check(TokenType.TYPE_LITERAL):
2227
+ type_name = self._advance().value
2228
+ return ASTNode('type_literal', value=type_name)
2229
+
2230
+ if self._match(TokenType.PAREN_START):
2231
+ expr = self._parse_expression()
2232
+ self._expect(TokenType.PAREN_END)
2233
+ return expr
2234
+
2235
+ if self._match(TokenType.BLOCK_START):
2236
+ # Distinguish between object literal { key = value } and action block { expr; }
2237
+ # Object literal: starts with IDENTIFIER = or STRING =
2238
+ # Action block: starts with expression (captured_ref, call, literal, etc.)
2239
+ if self._is_object_literal():
2240
+ return self._parse_object()
2241
+ else:
2242
+ return self._parse_action_block_expression()
2243
+
2244
+ if self._match(TokenType.BRACKET_START):
2245
+ return self._parse_array()
2246
+
2247
+ if self._check(TokenType.IDENTIFIER) or self._check(TokenType.KEYWORD):
2248
+ return self._parse_identifier_or_call()
2249
+
2250
+ return ASTNode('literal', value=None)
2251
+
2252
+ def _parse_module_reference(self) -> ASTNode:
2253
+ """Parse @name, handling method calls and property access.
2254
+
2255
+ @name alone -> module_ref
2256
+ @name.method() -> call with member_access
2257
+ @name.property -> member_access
2258
+ """
2259
+ # Get base name
2260
+ name = self._advance().value
2261
+ node = ASTNode('module_ref', value=name)
2262
+
2263
+ # Continue to handle member access, calls, and indexing
2264
+ while True:
2265
+ if self._match(TokenType.DOT):
2266
+ member = self._advance().value
2267
+ node = ASTNode('member_access', value={'object': node, 'member': member})
2268
+ elif self._match(TokenType.PAREN_START):
2269
+ # Function call - use _parse_call_arguments for kwargs support
2270
+ args, kwargs = self._parse_call_arguments()
2271
+ self._expect(TokenType.PAREN_END)
2272
+ node = ASTNode('call', value={'callee': node, 'args': args, 'kwargs': kwargs})
2273
+ elif self._match(TokenType.BRACKET_START):
2274
+ # Index access
2275
+ index = self._parse_expression()
2276
+ self._expect(TokenType.BRACKET_END)
2277
+ node = ASTNode('index_access', value={'object': node, 'index': index})
2278
+ else:
2279
+ break
2280
+
2281
+ return node
2282
+
2283
+ def _parse_call_arguments(self) -> tuple:
2284
+ """Parse function call arguments, supporting both positional and named (key=value).
2285
+
2286
+ Returns: (args, kwargs) where:
2287
+ args = list of positional argument expressions
2288
+ kwargs = dict of {name: expression} for named arguments
2289
+ """
2290
+ args = []
2291
+ kwargs = {}
2292
+
2293
+ while not self._check(TokenType.PAREN_END) and not self._is_at_end():
2294
+ # Check for named argument: identifier = expression
2295
+ if self._check(TokenType.IDENTIFIER):
2296
+ saved_pos = self.pos # Save token position
2297
+ name_token = self._advance()
2298
+
2299
+ if self._check(TokenType.EQUALS):
2300
+ # Named argument: name=value
2301
+ self._advance() # consume =
2302
+ value = self._parse_expression()
2303
+ kwargs[name_token.value] = value
2304
+ else:
2305
+ # Not named, restore and parse as expression
2306
+ self.pos = saved_pos # Restore token position
2307
+ args.append(self._parse_expression())
2308
+ else:
2309
+ args.append(self._parse_expression())
2310
+
2311
+ if not self._check(TokenType.PAREN_END):
2312
+ self._expect(TokenType.COMMA)
2313
+
2314
+ return args, kwargs
2315
+
2316
+ def _parse_identifier_or_call(self) -> ASTNode:
2317
+ name = self._advance().value
2318
+
2319
+ # Check for namespace syntax: json::read, string::cut, etc.
2320
+ if self._match(TokenType.DOUBLE_COLON):
2321
+ if self._check(TokenType.IDENTIFIER) or self._check(TokenType.KEYWORD):
2322
+ namespace_member = self._advance().value
2323
+ name = f"{name}::{namespace_member}"
2324
+
2325
+ # Check for instance<"name"> syntax - gets/creates shared instance
2326
+ if name == 'instance' and self._check(TokenType.COMPARE_LT):
2327
+ self._advance() # consume <
2328
+ # Expect string literal for instance name
2329
+ if self._check(TokenType.STRING):
2330
+ instance_name = self._advance().value
2331
+ elif self._check(TokenType.IDENTIFIER):
2332
+ instance_name = self._advance().value
2333
+ else:
2334
+ raise CSSLParserError("Expected instance name (string or identifier)", self._current_line())
2335
+ self._expect(TokenType.COMPARE_GT) # consume >
2336
+ return ASTNode('instance_ref', value=instance_name)
2337
+
2338
+ # Check for type generic instantiation: stack<string>, vector<int>, map<string, int>, etc.
2339
+ # This creates a new instance of the type with the specified element type
2340
+ if name in TYPE_GENERICS and self._check(TokenType.COMPARE_LT):
2341
+ self._advance() # consume <
2342
+ element_type = 'dynamic'
2343
+ value_type = None # For map<K, V>
2344
+
2345
+ if self._check(TokenType.KEYWORD) or self._check(TokenType.IDENTIFIER):
2346
+ element_type = self._advance().value
2347
+
2348
+ # Check for second type parameter (map<K, V>)
2349
+ if name == 'map' and self._check(TokenType.COMMA):
2350
+ self._advance() # consume ,
2351
+ if self._check(TokenType.KEYWORD) or self._check(TokenType.IDENTIFIER):
2352
+ value_type = self._advance().value
2353
+ else:
2354
+ value_type = 'dynamic'
2355
+
2356
+ self._expect(TokenType.COMPARE_GT) # consume >
2357
+
2358
+ # Check for inline initialization: map<K,V>{"key": "value", ...}
2359
+ init_values = None
2360
+ if self._check(TokenType.BLOCK_START):
2361
+ self._advance() # consume {
2362
+ init_values = {}
2363
+
2364
+ while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
2365
+ # Parse key
2366
+ if self._check(TokenType.STRING):
2367
+ key = self._advance().value
2368
+ elif self._check(TokenType.IDENTIFIER):
2369
+ key = self._advance().value
2370
+ else:
2371
+ key = str(self._parse_expression().value) if hasattr(self._parse_expression(), 'value') else 'key'
2372
+
2373
+ # Expect : or =
2374
+ if self._check(TokenType.COLON):
2375
+ self._advance()
2376
+ elif self._check(TokenType.EQUALS):
2377
+ self._advance()
2378
+
2379
+ # Parse value
2380
+ value = self._parse_expression()
2381
+ init_values[key] = value
2382
+
2383
+ # Optional comma
2384
+ if self._check(TokenType.COMMA):
2385
+ self._advance()
2386
+
2387
+ self._expect(TokenType.BLOCK_END) # consume }
2388
+
2389
+ return ASTNode('type_instantiation', value={
2390
+ 'type': name,
2391
+ 'element_type': element_type,
2392
+ 'value_type': value_type,
2393
+ 'init_values': init_values
2394
+ })
2395
+
2396
+ # Check for type-parameterized function call: OpenFind<string>(0)
2397
+ if name in TYPE_PARAM_FUNCTIONS and self._check(TokenType.COMPARE_LT):
2398
+ self._advance() # consume <
2399
+ type_param = 'dynamic'
2400
+ if self._check(TokenType.KEYWORD) or self._check(TokenType.IDENTIFIER):
2401
+ type_param = self._advance().value
2402
+ self._expect(TokenType.COMPARE_GT) # consume >
2403
+
2404
+ # Must be followed by ()
2405
+ if self._check(TokenType.PAREN_START):
2406
+ self._advance() # consume (
2407
+ args = []
2408
+ while not self._check(TokenType.PAREN_END):
2409
+ args.append(self._parse_expression())
2410
+ if not self._check(TokenType.PAREN_END):
2411
+ self._expect(TokenType.COMMA)
2412
+ self._expect(TokenType.PAREN_END)
2413
+
2414
+ # Return as typed function call
2415
+ return ASTNode('typed_call', value={
2416
+ 'name': name,
2417
+ 'type_param': type_param,
2418
+ 'args': args
2419
+ })
2420
+
2421
+ node = ASTNode('identifier', value=name)
2422
+
2423
+ while True:
2424
+ if self._match(TokenType.DOT):
2425
+ member = self._advance().value
2426
+ node = ASTNode('member_access', value={'object': node, 'member': member})
2427
+ elif self._match(TokenType.PAREN_START):
2428
+ args, kwargs = self._parse_call_arguments()
2429
+ self._expect(TokenType.PAREN_END)
2430
+ node = ASTNode('call', value={'callee': node, 'args': args, 'kwargs': kwargs})
2431
+ elif self._match(TokenType.BRACKET_START):
2432
+ index = self._parse_expression()
2433
+ self._expect(TokenType.BRACKET_END)
2434
+ node = ASTNode('index_access', value={'object': node, 'index': index})
2435
+ else:
2436
+ break
2437
+
2438
+ return node
2439
+
2440
+ def _is_object_literal(self) -> bool:
2441
+ """Check if current position is an object literal { key = value } vs action block { expr; }
2442
+
2443
+ Object literal: { name = value; } or { "key" = value; }
2444
+ Action block: { %version; } or { "1.0.0" } or { call(); }
2445
+ """
2446
+ # Empty block is action block
2447
+ if self._check(TokenType.BLOCK_END):
2448
+ return False
2449
+
2450
+ # Save position for lookahead
2451
+ saved_pos = self.pos
2452
+
2453
+ # Check if it looks like key = value pattern
2454
+ is_object = False
2455
+ if self._check(TokenType.IDENTIFIER) or self._check(TokenType.STRING):
2456
+ self._advance() # skip key
2457
+ if self._check(TokenType.EQUALS):
2458
+ # Looks like object literal: { key = ...
2459
+ is_object = True
2460
+
2461
+ # Restore position
2462
+ self.pos = saved_pos
2463
+ return is_object
2464
+
2465
+ def _parse_action_block_expression(self) -> ASTNode:
2466
+ """Parse an action block expression: { expr; expr2; } returns last value
2467
+
2468
+ Used for: v <== { %version; } or v <== { "1.0.0" }
2469
+ """
2470
+ children = []
2471
+
2472
+ while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
2473
+ # Parse statement or expression
2474
+ if (self._check(TokenType.IDENTIFIER) or self._check(TokenType.AT) or
2475
+ self._check(TokenType.CAPTURED_REF) or self._check(TokenType.SHARED_REF) or
2476
+ self._check(TokenType.GLOBAL_REF) or self._check(TokenType.SELF_REF) or
2477
+ self._check(TokenType.STRING) or self._check(TokenType.NUMBER) or
2478
+ self._check(TokenType.BOOLEAN) or self._check(TokenType.NULL) or
2479
+ self._check(TokenType.PAREN_START)):
2480
+ # Parse as expression and wrap in expression node for _execute_node
2481
+ expr = self._parse_expression()
2482
+ self._match(TokenType.SEMICOLON)
2483
+ children.append(ASTNode('expression', value=expr))
2484
+ elif self._check(TokenType.KEYWORD):
2485
+ # Parse as statement
2486
+ stmt = self._parse_statement()
2487
+ if stmt:
2488
+ children.append(stmt)
2489
+ else:
2490
+ self._advance()
2491
+
2492
+ self._expect(TokenType.BLOCK_END)
2493
+ return ASTNode('action_block', children=children)
2494
+
2495
+ def _parse_object(self) -> ASTNode:
2496
+ properties = {}
2497
+
2498
+ while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
2499
+ if self._check(TokenType.IDENTIFIER) or self._check(TokenType.STRING):
2500
+ key = self._advance().value
2501
+ self._expect(TokenType.EQUALS)
2502
+ value = self._parse_expression()
2503
+ properties[key] = value
2504
+ self._match(TokenType.SEMICOLON)
2505
+ self._match(TokenType.COMMA)
2506
+ else:
2507
+ self._advance()
2508
+
2509
+ self._expect(TokenType.BLOCK_END)
2510
+ return ASTNode('object', value=properties)
2511
+
2512
+ def _parse_array(self) -> ASTNode:
2513
+ elements = []
2514
+
2515
+ while not self._check(TokenType.BRACKET_END) and not self._is_at_end():
2516
+ elements.append(self._parse_expression())
2517
+ if not self._check(TokenType.BRACKET_END):
2518
+ self._expect(TokenType.COMMA)
2519
+
2520
+ self._expect(TokenType.BRACKET_END)
2521
+ return ASTNode('array', value=elements)
2522
+
2523
+ def _parse_value(self) -> Any:
2524
+ if self._check(TokenType.STRING):
2525
+ return self._advance().value
2526
+ if self._check(TokenType.NUMBER):
2527
+ return self._advance().value
2528
+ if self._check(TokenType.BOOLEAN):
2529
+ return self._advance().value
2530
+ if self._check(TokenType.NULL):
2531
+ self._advance()
2532
+ return None
2533
+ if self._check(TokenType.IDENTIFIER) or self._check(TokenType.KEYWORD):
2534
+ return self._advance().value
2535
+ return None
2536
+
2537
+ def _check_keyword(self, keyword: str) -> bool:
2538
+ return self._current().type == TokenType.KEYWORD and self._current().value == keyword
2539
+
2540
+
2541
+ def parse_cssl(source: str) -> ASTNode:
2542
+ """Parse CSSL source code into an AST - auto-detects service vs program format"""
2543
+ lexer = CSSLLexer(source)
2544
+ tokens = lexer.tokenize()
2545
+ parser = CSSLParser(tokens, lexer.source_lines)
2546
+
2547
+ # Auto-detect: if first token is '{', it's a service file
2548
+ # Otherwise treat as standalone program (whitespace is already filtered by lexer)
2549
+ if tokens and tokens[0].type == TokenType.BLOCK_START:
2550
+ return parser.parse() # Service file format
2551
+ else:
2552
+ return parser.parse_program() # Standalone program format
2553
+
2554
+
2555
+ def parse_cssl_program(source: str) -> ASTNode:
2556
+ """Parse standalone CSSL program (no service wrapper) into an AST"""
2557
+ lexer = CSSLLexer(source)
2558
+ tokens = lexer.tokenize()
2559
+ parser = CSSLParser(tokens, lexer.source_lines)
2560
+ return parser.parse_program()
2561
+
2562
+
2563
+ def tokenize_cssl(source: str) -> List[Token]:
2564
+ """Tokenize CSSL source code (useful for syntax highlighting)"""
2565
+ lexer = CSSLLexer(source)
2566
+ return lexer.tokenize()
2567
+
2568
+
2569
+ # Export public API
2570
+ __all__ = [
2571
+ 'TokenType', 'Token', 'ASTNode',
2572
+ 'CSSLLexer', 'CSSLParser', 'CSSLSyntaxError',
2573
+ 'parse_cssl', 'parse_cssl_program', 'tokenize_cssl',
2574
+ 'KEYWORDS', 'TYPE_LITERALS'
2575
+ ]