IncludeCPP 4.0.2__py3-none-any.whl → 4.3.0__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.
- includecpp/CHANGELOG.md +142 -0
- includecpp/DOCUMENTATION.md +446 -0
- includecpp/__init__.py +1 -1
- includecpp/__init__.pyi +4 -1
- includecpp/cli/commands.py +700 -86
- includecpp/core/ai_integration.py +46 -13
- includecpp/core/cpp_api_extensions.pyi +350 -0
- includecpp/core/cssl/CSSL_DOCUMENTATION.md +1535 -1316
- includecpp/core/cssl/cssl_builtins.py +230 -23
- includecpp/core/cssl/cssl_languages.py +1757 -0
- includecpp/core/cssl/cssl_parser.py +967 -126
- includecpp/core/cssl/cssl_runtime.py +1133 -84
- includecpp/core/cssl/cssl_syntax.py +88 -4
- includecpp/core/cssl/cssl_types.py +361 -1
- includecpp/core/cssl_bridge.py +194 -8
- includecpp/core/cssl_bridge.pyi +148 -10
- includecpp/generator/parser.cpp +121 -4
- includecpp/generator/parser.h +6 -0
- includecpp/vscode/cssl/package.json +43 -1
- includecpp/vscode/cssl/syntaxes/cssl.tmLanguage.json +178 -19
- includecpp-4.3.0.dist-info/METADATA +277 -0
- {includecpp-4.0.2.dist-info → includecpp-4.3.0.dist-info}/RECORD +26 -22
- includecpp-4.0.2.dist-info/METADATA +0 -908
- {includecpp-4.0.2.dist-info → includecpp-4.3.0.dist-info}/WHEEL +0 -0
- {includecpp-4.0.2.dist-info → includecpp-4.3.0.dist-info}/entry_points.txt +0 -0
- {includecpp-4.0.2.dist-info → includecpp-4.3.0.dist-info}/licenses/LICENSE +0 -0
- {includecpp-4.0.2.dist-info → includecpp-4.3.0.dist-info}/top_level.txt +0 -0
|
@@ -113,15 +113,18 @@ class TokenType(Enum):
|
|
|
113
113
|
# Append operator for constructor/function extension
|
|
114
114
|
PLUS_PLUS = auto() # ++ for constructor/function append (keeps old + adds new)
|
|
115
115
|
MINUS_MINUS = auto() # -- for potential future use
|
|
116
|
+
# Multi-language support (v4.1.0)
|
|
117
|
+
LANG_INSTANCE_REF = auto() # cpp$InstanceName, py$Object - cross-language instance access
|
|
116
118
|
|
|
117
119
|
|
|
118
120
|
KEYWORDS = {
|
|
119
121
|
# Service structure
|
|
120
|
-
'service-init', 'service-run', 'service-include', 'struct', 'define', 'main', 'class', 'constr', 'extends', 'overwrites', 'new', 'this', 'super',
|
|
122
|
+
'service-init', 'service-run', 'service-include', 'struct', 'define', 'main', 'class', 'constr', 'extends', 'overwrites', 'new', 'this', 'super', 'enum',
|
|
121
123
|
# Control flow
|
|
122
124
|
'if', 'else', 'elif', 'while', 'for', 'foreach', 'in', 'range',
|
|
123
125
|
'switch', 'case', 'default', 'break', 'continue', 'return',
|
|
124
126
|
'try', 'catch', 'finally', 'throw',
|
|
127
|
+
'except', 'always', # v4.2.5: param switch keywords
|
|
125
128
|
# Literals
|
|
126
129
|
'True', 'False', 'null', 'None', 'true', 'false',
|
|
127
130
|
# Logical operators
|
|
@@ -139,6 +142,7 @@ KEYWORDS = {
|
|
|
139
142
|
'datastruct', # Universal container (lazy declarator)
|
|
140
143
|
'dataspace', # SQL/data storage container
|
|
141
144
|
'shuffled', # Unorganized fast storage (multiple returns)
|
|
145
|
+
'bytearrayed', # Function-to-byte mapping with pattern matching (v4.2.5)
|
|
142
146
|
'iterator', # Advanced iterator with tasks
|
|
143
147
|
'combo', # Filter/search spaces
|
|
144
148
|
'structure', # Advanced C++/Py Class
|
|
@@ -153,14 +157,17 @@ KEYWORDS = {
|
|
|
153
157
|
'sqlbased', # SQL-based function
|
|
154
158
|
'public', # Explicitly public (default)
|
|
155
159
|
'static', # Static method/function
|
|
160
|
+
'embedded', # Immediate &target replacement at registration (v4.2.5)
|
|
156
161
|
# CSSL Include Keywords
|
|
157
162
|
'include', 'get',
|
|
163
|
+
# Multi-language support (v4.1.0)
|
|
164
|
+
'supports', 'libinclude',
|
|
158
165
|
}
|
|
159
166
|
|
|
160
167
|
# Function modifiers that can appear in any order before function name
|
|
161
168
|
FUNCTION_MODIFIERS = {
|
|
162
169
|
'undefined', 'open', 'meta', 'super', 'closed', 'private', 'virtual',
|
|
163
|
-
'sqlbased', 'const', 'public', 'static', 'global', 'shuffled'
|
|
170
|
+
'sqlbased', 'const', 'public', 'static', 'global', 'shuffled', 'embedded'
|
|
164
171
|
}
|
|
165
172
|
|
|
166
173
|
# Type literals that create empty instances
|
|
@@ -182,6 +189,12 @@ INJECTION_HELPERS = {
|
|
|
182
189
|
'string', 'integer', 'json', 'array', 'vector', 'combo', 'dynamic', 'sql'
|
|
183
190
|
}
|
|
184
191
|
|
|
192
|
+
# Language identifiers for multi-language support (v4.1.0)
|
|
193
|
+
# Used in lang$instance patterns like cpp$MyClass, py$Object
|
|
194
|
+
LANGUAGE_IDS = {
|
|
195
|
+
'cpp', 'py', 'python', 'java', 'csharp', 'js', 'javascript'
|
|
196
|
+
}
|
|
197
|
+
|
|
185
198
|
|
|
186
199
|
@dataclass
|
|
187
200
|
class Token:
|
|
@@ -338,13 +351,16 @@ class CSSLLexer:
|
|
|
338
351
|
self._add_token(TokenType.MULTIPLY, '*')
|
|
339
352
|
self._advance()
|
|
340
353
|
elif char == '/':
|
|
341
|
-
# Check
|
|
342
|
-
if self._peek(1)
|
|
354
|
+
# Check for // comment, /* block comment */, or division
|
|
355
|
+
if self._peek(1) == '/':
|
|
356
|
+
# Single-line comment
|
|
357
|
+
self._skip_comment()
|
|
358
|
+
elif self._peek(1) == '*':
|
|
359
|
+
# Block comment /* ... */
|
|
360
|
+
self._skip_block_comment()
|
|
361
|
+
else:
|
|
343
362
|
self._add_token(TokenType.DIVIDE, '/')
|
|
344
363
|
self._advance()
|
|
345
|
-
else:
|
|
346
|
-
# Already handled by // comment check above, but just in case
|
|
347
|
-
self._skip_comment()
|
|
348
364
|
elif char == '<':
|
|
349
365
|
self._read_less_than()
|
|
350
366
|
elif char == '>':
|
|
@@ -389,6 +405,26 @@ class CSSLLexer:
|
|
|
389
405
|
while self.pos < len(self.source) and self.source[self.pos] != '\n':
|
|
390
406
|
self._advance()
|
|
391
407
|
|
|
408
|
+
def _skip_block_comment(self):
|
|
409
|
+
"""Skip block comment /* ... */ including nested comments"""
|
|
410
|
+
self._advance() # skip /
|
|
411
|
+
self._advance() # skip *
|
|
412
|
+
depth = 1
|
|
413
|
+
while self.pos < len(self.source) and depth > 0:
|
|
414
|
+
if self.source[self.pos] == '/' and self._peek(1) == '*':
|
|
415
|
+
depth += 1
|
|
416
|
+
self._advance()
|
|
417
|
+
self._advance()
|
|
418
|
+
elif self.source[self.pos] == '*' and self._peek(1) == '/':
|
|
419
|
+
depth -= 1
|
|
420
|
+
self._advance()
|
|
421
|
+
self._advance()
|
|
422
|
+
else:
|
|
423
|
+
if self.source[self.pos] == '\n':
|
|
424
|
+
self.line += 1
|
|
425
|
+
self.column = 0
|
|
426
|
+
self._advance()
|
|
427
|
+
|
|
392
428
|
def _read_string(self, quote_char: str):
|
|
393
429
|
self._advance()
|
|
394
430
|
start = self.pos
|
|
@@ -443,6 +479,19 @@ class CSSLLexer:
|
|
|
443
479
|
start = self.pos
|
|
444
480
|
if self.source[self.pos] == '-':
|
|
445
481
|
self._advance()
|
|
482
|
+
|
|
483
|
+
# Check for hex number: 0x... or 0X...
|
|
484
|
+
if (self.pos < len(self.source) and self.source[self.pos] == '0' and
|
|
485
|
+
self.pos + 1 < len(self.source) and self.source[self.pos + 1] in 'xX'):
|
|
486
|
+
self._advance() # skip '0'
|
|
487
|
+
self._advance() # skip 'x' or 'X'
|
|
488
|
+
# Read hex digits
|
|
489
|
+
while self.pos < len(self.source) and self.source[self.pos] in '0123456789abcdefABCDEF':
|
|
490
|
+
self._advance()
|
|
491
|
+
value = self.source[start:self.pos]
|
|
492
|
+
self._add_token(TokenType.NUMBER, int(value, 16))
|
|
493
|
+
return
|
|
494
|
+
|
|
446
495
|
while self.pos < len(self.source) and (self.source[self.pos].isdigit() or self.source[self.pos] == '.'):
|
|
447
496
|
self._advance()
|
|
448
497
|
value = self.source[start:self.pos]
|
|
@@ -457,6 +506,25 @@ class CSSLLexer:
|
|
|
457
506
|
self._advance()
|
|
458
507
|
value = self.source[start:self.pos]
|
|
459
508
|
|
|
509
|
+
# Check for language$instance pattern (v4.1.0)
|
|
510
|
+
# e.g., cpp$MyClass, py$Object, java$Service
|
|
511
|
+
if value.lower() in LANGUAGE_IDS and self.pos < len(self.source) and self.source[self.pos] == '$':
|
|
512
|
+
lang_id = value
|
|
513
|
+
self._advance() # skip '$'
|
|
514
|
+
# Read instance name
|
|
515
|
+
instance_start = self.pos
|
|
516
|
+
while self.pos < len(self.source) and (self.source[self.pos].isalnum() or self.source[self.pos] == '_'):
|
|
517
|
+
self._advance()
|
|
518
|
+
instance_name = self.source[instance_start:self.pos]
|
|
519
|
+
if instance_name:
|
|
520
|
+
self._add_token(TokenType.LANG_INSTANCE_REF, {'lang': lang_id, 'instance': instance_name})
|
|
521
|
+
return
|
|
522
|
+
# If no instance name, revert and treat as normal identifier
|
|
523
|
+
self.pos = start
|
|
524
|
+
while self.pos < len(self.source) and (self.source[self.pos].isalnum() or self.source[self.pos] == '_'):
|
|
525
|
+
self._advance()
|
|
526
|
+
value = self.source[start:self.pos]
|
|
527
|
+
|
|
460
528
|
if value in ('True', 'true'):
|
|
461
529
|
self._add_token(TokenType.BOOLEAN, True)
|
|
462
530
|
elif value in ('False', 'false'):
|
|
@@ -672,10 +740,11 @@ class ASTNode:
|
|
|
672
740
|
class CSSLParser:
|
|
673
741
|
"""Parses CSSL tokens into an Abstract Syntax Tree."""
|
|
674
742
|
|
|
675
|
-
def __init__(self, tokens: List[Token], source_lines: List[str] = None):
|
|
743
|
+
def __init__(self, tokens: List[Token], source_lines: List[str] = None, source: str = None):
|
|
676
744
|
self.tokens = [t for t in tokens if t.type != TokenType.NEWLINE]
|
|
677
745
|
self.pos = 0
|
|
678
746
|
self.source_lines = source_lines or []
|
|
747
|
+
self.source = source or '\n'.join(self.source_lines) # v4.2.0: Full source for raw extraction
|
|
679
748
|
|
|
680
749
|
def get_source_line(self, line_num: int) -> str:
|
|
681
750
|
"""Get a specific source line for error reporting"""
|
|
@@ -736,6 +805,41 @@ class CSSLParser:
|
|
|
736
805
|
'list', 'dictionary', 'dict', 'instance', 'map', 'openquote', 'parameter',
|
|
737
806
|
'dynamic', 'datastruct', 'dataspace', 'shuffled', 'iterator', 'combo', 'structure')
|
|
738
807
|
|
|
808
|
+
def _parse_generic_type_content(self) -> str:
|
|
809
|
+
"""Parse generic type content including nested generics.
|
|
810
|
+
|
|
811
|
+
Handles: <int>, <string, dynamic>, <map<string, dynamic>>, etc.
|
|
812
|
+
Returns the full type string including nested generics.
|
|
813
|
+
|
|
814
|
+
Called AFTER consuming the opening '<'.
|
|
815
|
+
"""
|
|
816
|
+
parts = []
|
|
817
|
+
depth = 1 # Already inside first <
|
|
818
|
+
|
|
819
|
+
while depth > 0 and not self._is_at_end():
|
|
820
|
+
if self._check(TokenType.COMPARE_LT):
|
|
821
|
+
parts.append('<')
|
|
822
|
+
depth += 1
|
|
823
|
+
self._advance()
|
|
824
|
+
elif self._check(TokenType.COMPARE_GT):
|
|
825
|
+
depth -= 1
|
|
826
|
+
if depth > 0: # Only add > if not the final closing >
|
|
827
|
+
parts.append('>')
|
|
828
|
+
self._advance()
|
|
829
|
+
elif self._check(TokenType.COMMA):
|
|
830
|
+
parts.append(', ')
|
|
831
|
+
self._advance()
|
|
832
|
+
elif self._check(TokenType.KEYWORD) or self._check(TokenType.IDENTIFIER):
|
|
833
|
+
parts.append(self._advance().value)
|
|
834
|
+
elif self._check(TokenType.STRING):
|
|
835
|
+
# For instance<"name">
|
|
836
|
+
parts.append(f'"{self._advance().value}"')
|
|
837
|
+
else:
|
|
838
|
+
# Skip whitespace/other tokens
|
|
839
|
+
self._advance()
|
|
840
|
+
|
|
841
|
+
return ''.join(parts)
|
|
842
|
+
|
|
739
843
|
def _looks_like_function_declaration(self) -> bool:
|
|
740
844
|
"""Check if current position looks like a C-style function declaration.
|
|
741
845
|
|
|
@@ -870,7 +974,7 @@ class CSSLParser:
|
|
|
870
974
|
self.pos = saved_pos
|
|
871
975
|
return False
|
|
872
976
|
|
|
873
|
-
def _parse_typed_function(self, is_global: bool = False) -> ASTNode:
|
|
977
|
+
def _parse_typed_function(self, is_global: bool = False, is_embedded: bool = False) -> ASTNode:
|
|
874
978
|
"""Parse C-style typed function declaration with flexible modifier ordering.
|
|
875
979
|
|
|
876
980
|
Supports any order of modifiers, types, non-null (*), and global (@):
|
|
@@ -900,6 +1004,8 @@ class CSSLParser:
|
|
|
900
1004
|
non_null = False
|
|
901
1005
|
exclude_type = None
|
|
902
1006
|
is_const = False
|
|
1007
|
+
# v4.2.5: embedded = immediate &target replacement (can come from param or modifier)
|
|
1008
|
+
_is_embedded = is_embedded
|
|
903
1009
|
|
|
904
1010
|
# Phase 1: Collect all modifiers, type, non-null, and global indicators
|
|
905
1011
|
# These can appear in any order before the function name
|
|
@@ -914,6 +1020,9 @@ class CSSLParser:
|
|
|
914
1020
|
elif mod == 'const':
|
|
915
1021
|
is_const = True
|
|
916
1022
|
modifiers.append(mod)
|
|
1023
|
+
elif mod == 'embedded':
|
|
1024
|
+
_is_embedded = True
|
|
1025
|
+
modifiers.append(mod)
|
|
917
1026
|
else:
|
|
918
1027
|
modifiers.append(mod)
|
|
919
1028
|
continue
|
|
@@ -1153,6 +1262,7 @@ class CSSLParser:
|
|
|
1153
1262
|
'name': name,
|
|
1154
1263
|
'is_global': is_global,
|
|
1155
1264
|
'is_const': is_const,
|
|
1265
|
+
'is_embedded': _is_embedded, # v4.2.5: immediate &target replacement
|
|
1156
1266
|
'params': params,
|
|
1157
1267
|
'return_type': return_type,
|
|
1158
1268
|
'generic_type': generic_type,
|
|
@@ -1250,19 +1360,16 @@ class CSSLParser:
|
|
|
1250
1360
|
|
|
1251
1361
|
The * prefix indicates a non-nullable variable (can never be None/null).
|
|
1252
1362
|
Example: vector<dynamic> *MyVector - can never contain None values.
|
|
1363
|
+
Supports nested generics: datastruct<map<string, dynamic>> zipped;
|
|
1253
1364
|
"""
|
|
1254
1365
|
# Get type name
|
|
1255
1366
|
type_name = self._advance().value # Consume type keyword
|
|
1256
1367
|
|
|
1257
|
-
# Check for generic type <T> or instance<"name">
|
|
1368
|
+
# Check for generic type <T> or instance<"name"> or nested <map<K,V>>
|
|
1258
1369
|
element_type = None
|
|
1259
1370
|
if self._match(TokenType.COMPARE_LT):
|
|
1260
|
-
#
|
|
1261
|
-
|
|
1262
|
-
element_type = self._advance().value
|
|
1263
|
-
elif self._check(TokenType.KEYWORD) or self._check(TokenType.IDENTIFIER):
|
|
1264
|
-
element_type = self._advance().value
|
|
1265
|
-
self._expect(TokenType.COMPARE_GT)
|
|
1371
|
+
# Use helper to parse nested generic content
|
|
1372
|
+
element_type = self._parse_generic_type_content()
|
|
1266
1373
|
|
|
1267
1374
|
# Check for * prefix (non-nullable indicator)
|
|
1268
1375
|
non_null = False
|
|
@@ -1307,6 +1414,10 @@ class CSSLParser:
|
|
|
1307
1414
|
root.children.append(self._parse_struct())
|
|
1308
1415
|
elif self._match_keyword('class'):
|
|
1309
1416
|
root.children.append(self._parse_class())
|
|
1417
|
+
elif self._match_keyword('enum'):
|
|
1418
|
+
root.children.append(self._parse_enum())
|
|
1419
|
+
elif self._match_keyword('bytearrayed'):
|
|
1420
|
+
root.children.append(self._parse_bytearrayed())
|
|
1310
1421
|
elif self._match_keyword('define'):
|
|
1311
1422
|
root.children.append(self._parse_define())
|
|
1312
1423
|
# Check for C-style typed function declarations
|
|
@@ -1329,6 +1440,14 @@ class CSSLParser:
|
|
|
1329
1440
|
elif self._match_keyword('package-includes'):
|
|
1330
1441
|
root.children.append(self._parse_package_includes())
|
|
1331
1442
|
# Handle global declarations
|
|
1443
|
+
# v4.2.5: Handle 'embedded' keyword for immediate &target replacement
|
|
1444
|
+
elif self._match_keyword('embedded'):
|
|
1445
|
+
if self._match_keyword('class'):
|
|
1446
|
+
root.children.append(self._parse_class(is_embedded=True))
|
|
1447
|
+
elif self._looks_like_function_declaration():
|
|
1448
|
+
root.children.append(self._parse_typed_function(is_embedded=True))
|
|
1449
|
+
else:
|
|
1450
|
+
self.error("Expected 'class' or function declaration after 'embedded'")
|
|
1332
1451
|
elif self._match_keyword('global'):
|
|
1333
1452
|
# Check if followed by class or define (global class/function)
|
|
1334
1453
|
if self._match_keyword('class'):
|
|
@@ -1350,13 +1469,7 @@ class CSSLParser:
|
|
|
1350
1469
|
# Wrap in global_assignment to mark as global variable (same as 'global' keyword)
|
|
1351
1470
|
global_stmt = ASTNode('global_assignment', value=stmt)
|
|
1352
1471
|
root.children.append(global_stmt)
|
|
1353
|
-
#
|
|
1354
|
-
elif (self._check(TokenType.IDENTIFIER) or self._check(TokenType.AT) or
|
|
1355
|
-
self._check(TokenType.SELF_REF) or self._check(TokenType.SHARED_REF) or
|
|
1356
|
-
self._check(TokenType.KEYWORD)):
|
|
1357
|
-
stmt = self._parse_expression_statement()
|
|
1358
|
-
if stmt:
|
|
1359
|
-
root.children.append(stmt)
|
|
1472
|
+
# Control flow keywords must be checked BEFORE generic KEYWORD handling
|
|
1360
1473
|
elif self._match_keyword('if'):
|
|
1361
1474
|
root.children.append(self._parse_if())
|
|
1362
1475
|
elif self._match_keyword('while'):
|
|
@@ -1365,6 +1478,14 @@ class CSSLParser:
|
|
|
1365
1478
|
root.children.append(self._parse_for())
|
|
1366
1479
|
elif self._match_keyword('foreach'):
|
|
1367
1480
|
root.children.append(self._parse_foreach())
|
|
1481
|
+
# Handle statements - keywords like 'instance', 'list', 'map' can be variable names
|
|
1482
|
+
# v4.2.1: Added LANG_INSTANCE_REF for lang$instance statements (js$GameData.score = 1337)
|
|
1483
|
+
elif (self._check(TokenType.IDENTIFIER) or self._check(TokenType.AT) or
|
|
1484
|
+
self._check(TokenType.SELF_REF) or self._check(TokenType.SHARED_REF) or
|
|
1485
|
+
self._check(TokenType.KEYWORD) or self._check(TokenType.LANG_INSTANCE_REF)):
|
|
1486
|
+
stmt = self._parse_expression_statement()
|
|
1487
|
+
if stmt:
|
|
1488
|
+
root.children.append(stmt)
|
|
1368
1489
|
# Skip comments and newlines
|
|
1369
1490
|
elif self._check(TokenType.COMMENT) or self._check(TokenType.NEWLINE):
|
|
1370
1491
|
self._advance()
|
|
@@ -1590,7 +1711,236 @@ class CSSLParser:
|
|
|
1590
1711
|
self._expect(TokenType.BLOCK_END)
|
|
1591
1712
|
return node
|
|
1592
1713
|
|
|
1593
|
-
def
|
|
1714
|
+
def _parse_enum(self) -> ASTNode:
|
|
1715
|
+
"""Parse enum declaration.
|
|
1716
|
+
|
|
1717
|
+
Syntax:
|
|
1718
|
+
enum EnumName {
|
|
1719
|
+
VALUE1, // Auto value 0
|
|
1720
|
+
VALUE2, // Auto value 1
|
|
1721
|
+
VALUE3 = 10, // Explicit value 10
|
|
1722
|
+
VALUE4 // Auto value 11
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
Access values via EnumName::VALUE1
|
|
1726
|
+
"""
|
|
1727
|
+
enum_name = self._advance().value
|
|
1728
|
+
self._expect(TokenType.BLOCK_START)
|
|
1729
|
+
|
|
1730
|
+
members = []
|
|
1731
|
+
current_value = 0
|
|
1732
|
+
|
|
1733
|
+
while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
|
|
1734
|
+
# Skip newlines/whitespace between enum values
|
|
1735
|
+
if self._check(TokenType.NEWLINE):
|
|
1736
|
+
self._advance()
|
|
1737
|
+
continue
|
|
1738
|
+
|
|
1739
|
+
# Get member name
|
|
1740
|
+
if self._check(TokenType.IDENTIFIER):
|
|
1741
|
+
member_name = self._advance().value
|
|
1742
|
+
|
|
1743
|
+
# Check for explicit value: VALUE = 10
|
|
1744
|
+
if self._match(TokenType.EQUALS):
|
|
1745
|
+
value_node = self._parse_expression()
|
|
1746
|
+
if isinstance(value_node, ASTNode) and value_node.type == 'literal':
|
|
1747
|
+
val = value_node.value
|
|
1748
|
+
if isinstance(val, dict) and 'value' in val:
|
|
1749
|
+
current_value = val['value']
|
|
1750
|
+
else:
|
|
1751
|
+
current_value = val
|
|
1752
|
+
else:
|
|
1753
|
+
current_value = value_node
|
|
1754
|
+
|
|
1755
|
+
members.append({'name': member_name, 'value': current_value})
|
|
1756
|
+
current_value = current_value + 1 if isinstance(current_value, int) else current_value
|
|
1757
|
+
|
|
1758
|
+
# Skip comma if present
|
|
1759
|
+
self._match(TokenType.COMMA)
|
|
1760
|
+
else:
|
|
1761
|
+
self._advance()
|
|
1762
|
+
|
|
1763
|
+
self._expect(TokenType.BLOCK_END)
|
|
1764
|
+
|
|
1765
|
+
return ASTNode('enum', value={
|
|
1766
|
+
'name': enum_name,
|
|
1767
|
+
'members': members
|
|
1768
|
+
})
|
|
1769
|
+
|
|
1770
|
+
def _parse_bytearrayed(self) -> ASTNode:
|
|
1771
|
+
"""Parse bytearrayed declaration - function-to-byte mapping with pattern matching.
|
|
1772
|
+
|
|
1773
|
+
Syntax:
|
|
1774
|
+
bytearrayed MyBytes {
|
|
1775
|
+
&func1; // Position 0x0
|
|
1776
|
+
&func2; // Position 0x1
|
|
1777
|
+
&func3; // Position 0x2
|
|
1778
|
+
case {0, 1, 0} { // Pattern match on return values
|
|
1779
|
+
// Execute if func1=0, func2=1, func3=0
|
|
1780
|
+
}
|
|
1781
|
+
case {1, _, _} { // Wildcards with _
|
|
1782
|
+
// Execute if func1=1, others any value
|
|
1783
|
+
}
|
|
1784
|
+
default {
|
|
1785
|
+
// Execute if no case matches
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
Access:
|
|
1790
|
+
MyBytes() // Execute pattern matching
|
|
1791
|
+
MyBytes["0x0"] // Get value at position 0
|
|
1792
|
+
MyBytes[0] // Get value at position 0
|
|
1793
|
+
"""
|
|
1794
|
+
bytearrayed_name = self._advance().value
|
|
1795
|
+
self._expect(TokenType.BLOCK_START)
|
|
1796
|
+
|
|
1797
|
+
func_refs = [] # List of function references at each byte position
|
|
1798
|
+
cases = [] # List of case blocks with patterns
|
|
1799
|
+
default_block = None # Default block if no case matches
|
|
1800
|
+
|
|
1801
|
+
while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
|
|
1802
|
+
# Skip newlines
|
|
1803
|
+
if self._check(TokenType.NEWLINE):
|
|
1804
|
+
self._advance()
|
|
1805
|
+
continue
|
|
1806
|
+
|
|
1807
|
+
# Parse case block
|
|
1808
|
+
if self._match_keyword('case'):
|
|
1809
|
+
pattern = []
|
|
1810
|
+
self._expect(TokenType.BLOCK_START)
|
|
1811
|
+
|
|
1812
|
+
# Parse pattern: {value, value, value, ...}
|
|
1813
|
+
while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
|
|
1814
|
+
if self._check(TokenType.COMMA):
|
|
1815
|
+
self._advance()
|
|
1816
|
+
continue
|
|
1817
|
+
if self._check(TokenType.NEWLINE):
|
|
1818
|
+
self._advance()
|
|
1819
|
+
continue
|
|
1820
|
+
|
|
1821
|
+
# Parse pattern element
|
|
1822
|
+
if self._check(TokenType.IDENTIFIER) and self._current().value == '_':
|
|
1823
|
+
# Wildcard - matches any value
|
|
1824
|
+
pattern.append({'type': 'wildcard'})
|
|
1825
|
+
self._advance()
|
|
1826
|
+
elif self._check(TokenType.NUMBER):
|
|
1827
|
+
# Check for hex format like 0x28
|
|
1828
|
+
token = self._current()
|
|
1829
|
+
value = token.value
|
|
1830
|
+
if isinstance(value, dict) and 'value' in value:
|
|
1831
|
+
value = value['value']
|
|
1832
|
+
# Check for position=value syntax: 0x28="Gut"
|
|
1833
|
+
self._advance()
|
|
1834
|
+
if self._match(TokenType.EQUALS):
|
|
1835
|
+
match_value = self._parse_expression()
|
|
1836
|
+
pattern.append({'type': 'indexed', 'index': value, 'value': match_value})
|
|
1837
|
+
else:
|
|
1838
|
+
pattern.append({'type': 'value', 'value': value})
|
|
1839
|
+
elif self._check(TokenType.STRING):
|
|
1840
|
+
value = self._advance().value
|
|
1841
|
+
pattern.append({'type': 'value', 'value': value})
|
|
1842
|
+
elif self._check(TokenType.KEYWORD):
|
|
1843
|
+
kw = self._current().value
|
|
1844
|
+
if kw in ('true', 'True'):
|
|
1845
|
+
pattern.append({'type': 'value', 'value': True})
|
|
1846
|
+
self._advance()
|
|
1847
|
+
elif kw in ('false', 'False'):
|
|
1848
|
+
pattern.append({'type': 'value', 'value': False})
|
|
1849
|
+
self._advance()
|
|
1850
|
+
elif kw in TYPE_GENERICS or kw in ('int', 'string', 'float', 'bool', 'dynamic'):
|
|
1851
|
+
# Type pattern: vector<string>, int, etc.
|
|
1852
|
+
type_name = self._advance().value
|
|
1853
|
+
if self._check(TokenType.COMPARE_LT):
|
|
1854
|
+
# Generic type
|
|
1855
|
+
self._advance()
|
|
1856
|
+
inner = self._parse_generic_type_content()
|
|
1857
|
+
type_name = f"{type_name}<{inner}>"
|
|
1858
|
+
pattern.append({'type': 'type_match', 'type_name': type_name})
|
|
1859
|
+
else:
|
|
1860
|
+
# Skip unknown keywords
|
|
1861
|
+
self._advance()
|
|
1862
|
+
elif self._check(TokenType.IDENTIFIER):
|
|
1863
|
+
# Could be a variable reference or type
|
|
1864
|
+
ident = self._advance().value
|
|
1865
|
+
# Check for indexed syntax: 2x35="value"
|
|
1866
|
+
if self._check(TokenType.IDENTIFIER) and ident.isdigit():
|
|
1867
|
+
# Pattern like 2x35 (position 2, repeat 35)
|
|
1868
|
+
second = self._advance().value
|
|
1869
|
+
if self._match(TokenType.EQUALS):
|
|
1870
|
+
match_value = self._parse_expression()
|
|
1871
|
+
pattern.append({'type': 'repeat', 'pos': int(ident), 'repeat': second, 'value': match_value})
|
|
1872
|
+
else:
|
|
1873
|
+
pattern.append({'type': 'repeat', 'pos': int(ident), 'repeat': second, 'value': None})
|
|
1874
|
+
else:
|
|
1875
|
+
pattern.append({'type': 'variable', 'name': ident})
|
|
1876
|
+
else:
|
|
1877
|
+
self._advance()
|
|
1878
|
+
|
|
1879
|
+
self._expect(TokenType.BLOCK_END)
|
|
1880
|
+
|
|
1881
|
+
# Parse case body - supports both:
|
|
1882
|
+
# case {pattern}: statement; (colon syntax)
|
|
1883
|
+
# case {pattern} { statements } (block syntax)
|
|
1884
|
+
body_children = []
|
|
1885
|
+
if self._match(TokenType.COLON):
|
|
1886
|
+
# Colon syntax: single statement or until next case/default/}
|
|
1887
|
+
stmt = self._parse_statement()
|
|
1888
|
+
if stmt:
|
|
1889
|
+
body_children.append(stmt)
|
|
1890
|
+
elif self._check(TokenType.BLOCK_START):
|
|
1891
|
+
self._advance() # consume {
|
|
1892
|
+
while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
|
|
1893
|
+
stmt = self._parse_statement()
|
|
1894
|
+
if stmt:
|
|
1895
|
+
body_children.append(stmt)
|
|
1896
|
+
self._expect(TokenType.BLOCK_END)
|
|
1897
|
+
|
|
1898
|
+
cases.append({'pattern': pattern, 'body': body_children})
|
|
1899
|
+
|
|
1900
|
+
# Parse default block - supports both:
|
|
1901
|
+
# default: statement; (colon syntax)
|
|
1902
|
+
# default { statements } (block syntax)
|
|
1903
|
+
elif self._match_keyword('default'):
|
|
1904
|
+
body_children = []
|
|
1905
|
+
if self._match(TokenType.COLON):
|
|
1906
|
+
# Colon syntax: single statement
|
|
1907
|
+
stmt = self._parse_statement()
|
|
1908
|
+
if stmt:
|
|
1909
|
+
body_children.append(stmt)
|
|
1910
|
+
elif self._check(TokenType.BLOCK_START):
|
|
1911
|
+
self._advance() # consume {
|
|
1912
|
+
while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
|
|
1913
|
+
stmt = self._parse_statement()
|
|
1914
|
+
if stmt:
|
|
1915
|
+
body_children.append(stmt)
|
|
1916
|
+
self._expect(TokenType.BLOCK_END)
|
|
1917
|
+
default_block = body_children
|
|
1918
|
+
|
|
1919
|
+
# Parse function reference: &funcName;
|
|
1920
|
+
elif self._check(TokenType.AMPERSAND):
|
|
1921
|
+
self._advance() # consume &
|
|
1922
|
+
if self._check(TokenType.IDENTIFIER):
|
|
1923
|
+
func_name = self._advance().value
|
|
1924
|
+
func_refs.append({
|
|
1925
|
+
'position': len(func_refs),
|
|
1926
|
+
'hex_pos': f"0x{len(func_refs):x}",
|
|
1927
|
+
'func_ref': func_name
|
|
1928
|
+
})
|
|
1929
|
+
self._match(TokenType.SEMICOLON)
|
|
1930
|
+
|
|
1931
|
+
else:
|
|
1932
|
+
self._advance()
|
|
1933
|
+
|
|
1934
|
+
self._expect(TokenType.BLOCK_END)
|
|
1935
|
+
|
|
1936
|
+
return ASTNode('bytearrayed', value={
|
|
1937
|
+
'name': bytearrayed_name,
|
|
1938
|
+
'func_refs': func_refs,
|
|
1939
|
+
'cases': cases,
|
|
1940
|
+
'default': default_block
|
|
1941
|
+
})
|
|
1942
|
+
|
|
1943
|
+
def _parse_class(self, is_global: bool = False, is_embedded: bool = False) -> ASTNode:
|
|
1594
1944
|
"""Parse class declaration with members and methods.
|
|
1595
1945
|
|
|
1596
1946
|
Syntax:
|
|
@@ -1598,6 +1948,7 @@ class CSSLParser:
|
|
|
1598
1948
|
global class ClassName { ... } // Global class
|
|
1599
1949
|
class @ClassName { ... } // Global class (alternative)
|
|
1600
1950
|
class *ClassName { ... } // Non-null class
|
|
1951
|
+
embedded class ClassName &$Target { ... } // Immediate replacement (v4.2.5)
|
|
1601
1952
|
|
|
1602
1953
|
Non-null class (all methods return non-null):
|
|
1603
1954
|
class *MyClass { ... }
|
|
@@ -1620,6 +1971,28 @@ class CSSLParser:
|
|
|
1620
1971
|
class_params = self._parse_parameter_list()
|
|
1621
1972
|
self._expect(TokenType.PAREN_END)
|
|
1622
1973
|
|
|
1974
|
+
# v4.2.5: Check for &target reference for class replacement
|
|
1975
|
+
# Syntax: embedded class BetterGame() &$Game { ... }
|
|
1976
|
+
# class NewClass &OldClass { ... }
|
|
1977
|
+
append_ref_class = None
|
|
1978
|
+
append_ref_member = None
|
|
1979
|
+
if self._match(TokenType.AMPERSAND):
|
|
1980
|
+
if self._check(TokenType.IDENTIFIER):
|
|
1981
|
+
append_ref_class = self._advance().value
|
|
1982
|
+
elif self._check(TokenType.AT):
|
|
1983
|
+
self._advance()
|
|
1984
|
+
if self._check(TokenType.IDENTIFIER):
|
|
1985
|
+
append_ref_class = '@' + self._advance().value
|
|
1986
|
+
elif self._check(TokenType.SHARED_REF):
|
|
1987
|
+
append_ref_class = f'${self._advance().value}'
|
|
1988
|
+
else:
|
|
1989
|
+
raise CSSLSyntaxError("Expected class name after '&' in class reference")
|
|
1990
|
+
|
|
1991
|
+
# Check for ::member or .member (for targeting specific members)
|
|
1992
|
+
if self._match(TokenType.DOUBLE_COLON) or self._match(TokenType.DOT):
|
|
1993
|
+
if self._check(TokenType.IDENTIFIER) or self._check(TokenType.KEYWORD):
|
|
1994
|
+
append_ref_member = self._advance().value
|
|
1995
|
+
|
|
1623
1996
|
# Check for inheritance and overwrites:
|
|
1624
1997
|
# class Child : extends Parent { ... }
|
|
1625
1998
|
# class Child : extends $PythonObject { ... }
|
|
@@ -1627,15 +2000,22 @@ class CSSLParser:
|
|
|
1627
2000
|
# class Child : extends Parent (param1, param2) { ... } <- constructor args for parent
|
|
1628
2001
|
extends_class = None
|
|
1629
2002
|
extends_is_python = False
|
|
2003
|
+
extends_lang_ref = None # v4.1.0: Cross-language inheritance (cpp$ClassName)
|
|
1630
2004
|
extends_args = []
|
|
1631
2005
|
overwrites_class = None
|
|
1632
2006
|
overwrites_is_python = False
|
|
2007
|
+
supports_language = None # v4.1.0: Multi-language syntax support
|
|
1633
2008
|
|
|
1634
2009
|
if self._match(TokenType.DOUBLE_COLON) or self._match(TokenType.COLON):
|
|
1635
2010
|
# Parse extends and/or overwrites (can be chained with : or ::)
|
|
1636
2011
|
while True:
|
|
1637
2012
|
if self._match_keyword('extends'):
|
|
1638
|
-
|
|
2013
|
+
# v4.1.0: Check for cross-language inheritance: extends cpp$ClassName
|
|
2014
|
+
if self._check(TokenType.LANG_INSTANCE_REF):
|
|
2015
|
+
ref = self._advance().value
|
|
2016
|
+
extends_lang_ref = ref # {'lang': 'cpp', 'instance': 'ClassName'}
|
|
2017
|
+
extends_class = ref['instance']
|
|
2018
|
+
elif self._check(TokenType.IDENTIFIER):
|
|
1639
2019
|
extends_class = self._advance().value
|
|
1640
2020
|
elif self._check(TokenType.SHARED_REF):
|
|
1641
2021
|
extends_class = self._advance().value
|
|
@@ -1660,23 +2040,62 @@ class CSSLParser:
|
|
|
1660
2040
|
# Skip optional () after class name
|
|
1661
2041
|
if self._match(TokenType.PAREN_START):
|
|
1662
2042
|
self._expect(TokenType.PAREN_END)
|
|
2043
|
+
# v4.1.0: Parse 'supports' keyword for multi-language syntax
|
|
2044
|
+
elif self._match_keyword('supports'):
|
|
2045
|
+
if self._check(TokenType.AT):
|
|
2046
|
+
self._advance() # consume @
|
|
2047
|
+
if self._check(TokenType.IDENTIFIER):
|
|
2048
|
+
supports_language = '@' + self._advance().value
|
|
2049
|
+
else:
|
|
2050
|
+
raise CSSLSyntaxError("Expected language identifier after '@' in 'supports'")
|
|
2051
|
+
elif self._check(TokenType.IDENTIFIER):
|
|
2052
|
+
supports_language = self._advance().value
|
|
2053
|
+
else:
|
|
2054
|
+
raise CSSLSyntaxError("Expected language identifier after 'supports'")
|
|
1663
2055
|
else:
|
|
1664
|
-
raise CSSLSyntaxError("Expected 'extends' or '
|
|
2056
|
+
raise CSSLSyntaxError("Expected 'extends', 'overwrites', or 'supports' after ':' or '::' in class declaration")
|
|
1665
2057
|
# Check for another : or :: for chaining
|
|
1666
2058
|
if not (self._match(TokenType.DOUBLE_COLON) or self._match(TokenType.COLON)):
|
|
1667
2059
|
break
|
|
1668
2060
|
|
|
2061
|
+
# v4.2.0: If supports_language is set, capture raw body for runtime transformation
|
|
2062
|
+
raw_body = None
|
|
2063
|
+
if supports_language:
|
|
2064
|
+
raw_body = self._extract_raw_block_body()
|
|
2065
|
+
|
|
1669
2066
|
node = ASTNode('class', value={
|
|
1670
2067
|
'name': class_name,
|
|
1671
2068
|
'is_global': is_global,
|
|
2069
|
+
'is_embedded': is_embedded, # v4.2.5: immediate &target replacement
|
|
1672
2070
|
'non_null': non_null,
|
|
1673
2071
|
'class_params': class_params,
|
|
1674
2072
|
'extends': extends_class,
|
|
1675
2073
|
'extends_is_python': extends_is_python,
|
|
2074
|
+
'extends_lang_ref': extends_lang_ref, # v4.1.0
|
|
1676
2075
|
'extends_args': extends_args,
|
|
1677
2076
|
'overwrites': overwrites_class,
|
|
1678
|
-
'overwrites_is_python': overwrites_is_python
|
|
2077
|
+
'overwrites_is_python': overwrites_is_python,
|
|
2078
|
+
'supports_language': supports_language, # v4.1.0
|
|
2079
|
+
'raw_body': raw_body, # v4.2.0: Raw body for language transformation
|
|
2080
|
+
'append_ref_class': append_ref_class, # v4.2.5: &target class reference
|
|
2081
|
+
'append_ref_member': append_ref_member # v4.2.5: &target member reference
|
|
1679
2082
|
}, children=[])
|
|
2083
|
+
|
|
2084
|
+
# v4.2.0: If we have raw_body for language transformation, skip regular parsing
|
|
2085
|
+
if raw_body is not None:
|
|
2086
|
+
# Skip the block entirely - runtime will transform and parse
|
|
2087
|
+
self._expect(TokenType.BLOCK_START)
|
|
2088
|
+
brace_count = 1
|
|
2089
|
+
while brace_count > 0 and not self._is_at_end():
|
|
2090
|
+
if self._check(TokenType.BLOCK_START):
|
|
2091
|
+
brace_count += 1
|
|
2092
|
+
elif self._check(TokenType.BLOCK_END):
|
|
2093
|
+
brace_count -= 1
|
|
2094
|
+
if brace_count > 0:
|
|
2095
|
+
self._advance()
|
|
2096
|
+
self._expect(TokenType.BLOCK_END)
|
|
2097
|
+
return node
|
|
2098
|
+
|
|
1680
2099
|
self._expect(TokenType.BLOCK_START)
|
|
1681
2100
|
|
|
1682
2101
|
while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
|
|
@@ -1907,13 +2326,14 @@ class CSSLParser:
|
|
|
1907
2326
|
"""Parse define function declaration.
|
|
1908
2327
|
|
|
1909
2328
|
Syntax:
|
|
1910
|
-
define MyFunc(args) { }
|
|
1911
|
-
global define MyFunc(args) { }
|
|
1912
|
-
define @MyFunc(args) { }
|
|
1913
|
-
define *MyFunc(args) { }
|
|
1914
|
-
define MyFunc : extends OtherFunc
|
|
1915
|
-
define MyFunc : overwrites OtherFunc
|
|
1916
|
-
define MyFunc
|
|
2329
|
+
define MyFunc(args) { } // Local function
|
|
2330
|
+
global define MyFunc(args) { } // Global function
|
|
2331
|
+
define @MyFunc(args) { } // Global function (alternative)
|
|
2332
|
+
define *MyFunc(args) { } // Non-null: must never return None
|
|
2333
|
+
define MyFunc(args) : extends OtherFunc { } // Inherit local vars
|
|
2334
|
+
define MyFunc(args) : overwrites OtherFunc { } // Replace OtherFunc
|
|
2335
|
+
define MyFunc(args) : supports python { } // Multi-language syntax
|
|
2336
|
+
define MyFunc(args) :: extends Parent::Method { } // Method-level inheritance
|
|
1917
2337
|
"""
|
|
1918
2338
|
# Check for * prefix (non-null function - must return non-null)
|
|
1919
2339
|
# Also *[type] for type exclusion (must NOT return that type)
|
|
@@ -1935,8 +2355,56 @@ class CSSLParser:
|
|
|
1935
2355
|
|
|
1936
2356
|
name = self._advance().value
|
|
1937
2357
|
|
|
1938
|
-
#
|
|
1939
|
-
#
|
|
2358
|
+
# Parse parameters FIRST (before :extends/:overwrites/:supports)
|
|
2359
|
+
# Syntax: define funcName(params) : extends/overwrites/supports { }
|
|
2360
|
+
params = []
|
|
2361
|
+
|
|
2362
|
+
if self._match(TokenType.PAREN_START):
|
|
2363
|
+
while not self._check(TokenType.PAREN_END):
|
|
2364
|
+
param_info = {}
|
|
2365
|
+
# Handle 'open' keyword for open parameters
|
|
2366
|
+
if self._match_keyword('open'):
|
|
2367
|
+
param_info['open'] = True
|
|
2368
|
+
# Handle type annotations (e.g., string, int, dynamic, etc.)
|
|
2369
|
+
if self._check(TokenType.KEYWORD):
|
|
2370
|
+
param_info['type'] = self._advance().value
|
|
2371
|
+
# Handle reference operator &
|
|
2372
|
+
if self._match(TokenType.AMPERSAND):
|
|
2373
|
+
param_info['ref'] = True
|
|
2374
|
+
# Handle * prefix for non-null parameters
|
|
2375
|
+
if self._match(TokenType.MULTIPLY):
|
|
2376
|
+
param_info['non_null'] = True
|
|
2377
|
+
# Get parameter name
|
|
2378
|
+
if self._check(TokenType.IDENTIFIER):
|
|
2379
|
+
param_name = self._advance().value
|
|
2380
|
+
# v4.2.0: Handle default parameter values (param = value)
|
|
2381
|
+
if self._match(TokenType.EQUALS):
|
|
2382
|
+
default_value = self._parse_expression()
|
|
2383
|
+
param_info['default'] = default_value
|
|
2384
|
+
if param_info:
|
|
2385
|
+
params.append({'name': param_name, **param_info})
|
|
2386
|
+
else:
|
|
2387
|
+
params.append(param_name)
|
|
2388
|
+
self._match(TokenType.COMMA)
|
|
2389
|
+
elif self._check(TokenType.KEYWORD):
|
|
2390
|
+
# Parameter name could be a keyword like 'Params'
|
|
2391
|
+
param_name = self._advance().value
|
|
2392
|
+
# v4.2.0: Handle default parameter values (param = value)
|
|
2393
|
+
if self._match(TokenType.EQUALS):
|
|
2394
|
+
default_value = self._parse_expression()
|
|
2395
|
+
param_info['default'] = default_value
|
|
2396
|
+
if param_info:
|
|
2397
|
+
params.append({'name': param_name, **param_info})
|
|
2398
|
+
else:
|
|
2399
|
+
params.append(param_name)
|
|
2400
|
+
self._match(TokenType.COMMA)
|
|
2401
|
+
else:
|
|
2402
|
+
break
|
|
2403
|
+
self._expect(TokenType.PAREN_END)
|
|
2404
|
+
|
|
2405
|
+
# Check for extends/overwrites/supports AFTER parameters
|
|
2406
|
+
# Syntax: define func(params) : extends/overwrites target { }
|
|
2407
|
+
# Also supports method-level :: syntax: define func() :: extends Parent::method
|
|
1940
2408
|
extends_func = None
|
|
1941
2409
|
overwrites_func = None
|
|
1942
2410
|
extends_is_python = False
|
|
@@ -1945,6 +2413,7 @@ class CSSLParser:
|
|
|
1945
2413
|
extends_method_ref = None
|
|
1946
2414
|
overwrites_class_ref = None
|
|
1947
2415
|
overwrites_method_ref = None
|
|
2416
|
+
supports_language = None # v4.1.0: Multi-language syntax support
|
|
1948
2417
|
|
|
1949
2418
|
if self._match(TokenType.DOUBLE_COLON) or self._match(TokenType.COLON):
|
|
1950
2419
|
# Parse extends and/or overwrites (supports :: method-level syntax)
|
|
@@ -2005,49 +2474,24 @@ class CSSLParser:
|
|
|
2005
2474
|
# Skip optional () after function/method name
|
|
2006
2475
|
if self._match(TokenType.PAREN_START):
|
|
2007
2476
|
self._expect(TokenType.PAREN_END)
|
|
2477
|
+
# v4.1.0: Parse 'supports' keyword for multi-language syntax
|
|
2478
|
+
elif self._match_keyword('supports'):
|
|
2479
|
+
if self._check(TokenType.AT):
|
|
2480
|
+
self._advance() # consume @
|
|
2481
|
+
if self._check(TokenType.IDENTIFIER):
|
|
2482
|
+
supports_language = '@' + self._advance().value
|
|
2483
|
+
else:
|
|
2484
|
+
raise CSSLSyntaxError("Expected language identifier after '@' in 'supports'")
|
|
2485
|
+
elif self._check(TokenType.IDENTIFIER):
|
|
2486
|
+
supports_language = self._advance().value
|
|
2487
|
+
else:
|
|
2488
|
+
raise CSSLSyntaxError("Expected language identifier after 'supports'")
|
|
2008
2489
|
else:
|
|
2009
2490
|
break
|
|
2010
2491
|
# Check for another :: or : for chaining extends/overwrites
|
|
2011
2492
|
if not (self._match(TokenType.DOUBLE_COLON) or self._match(TokenType.COLON)):
|
|
2012
2493
|
break
|
|
2013
2494
|
|
|
2014
|
-
params = []
|
|
2015
|
-
|
|
2016
|
-
if self._match(TokenType.PAREN_START):
|
|
2017
|
-
while not self._check(TokenType.PAREN_END):
|
|
2018
|
-
param_info = {}
|
|
2019
|
-
# Handle 'open' keyword for open parameters
|
|
2020
|
-
if self._match_keyword('open'):
|
|
2021
|
-
param_info['open'] = True
|
|
2022
|
-
# Handle type annotations (e.g., string, int, dynamic, etc.)
|
|
2023
|
-
if self._check(TokenType.KEYWORD):
|
|
2024
|
-
param_info['type'] = self._advance().value
|
|
2025
|
-
# Handle reference operator &
|
|
2026
|
-
if self._match(TokenType.AMPERSAND):
|
|
2027
|
-
param_info['ref'] = True
|
|
2028
|
-
# Handle * prefix for non-null parameters
|
|
2029
|
-
if self._match(TokenType.MULTIPLY):
|
|
2030
|
-
param_info['non_null'] = True
|
|
2031
|
-
# Get parameter name
|
|
2032
|
-
if self._check(TokenType.IDENTIFIER):
|
|
2033
|
-
param_name = self._advance().value
|
|
2034
|
-
if param_info:
|
|
2035
|
-
params.append({'name': param_name, **param_info})
|
|
2036
|
-
else:
|
|
2037
|
-
params.append(param_name)
|
|
2038
|
-
self._match(TokenType.COMMA)
|
|
2039
|
-
elif self._check(TokenType.KEYWORD):
|
|
2040
|
-
# Parameter name could be a keyword like 'Params'
|
|
2041
|
-
param_name = self._advance().value
|
|
2042
|
-
if param_info:
|
|
2043
|
-
params.append({'name': param_name, **param_info})
|
|
2044
|
-
else:
|
|
2045
|
-
params.append(param_name)
|
|
2046
|
-
self._match(TokenType.COMMA)
|
|
2047
|
-
else:
|
|
2048
|
-
break
|
|
2049
|
-
self._expect(TokenType.PAREN_END)
|
|
2050
|
-
|
|
2051
2495
|
# New: Append mode and reference tracking for functions
|
|
2052
2496
|
# Syntax: define XYZ(int zahl) &overwrittenclass::functionyouwanttokeep ++ { ... }
|
|
2053
2497
|
append_mode = False
|
|
@@ -2074,9 +2518,41 @@ class CSSLParser:
|
|
|
2074
2518
|
if self._match(TokenType.PLUS_PLUS):
|
|
2075
2519
|
append_mode = True
|
|
2076
2520
|
|
|
2521
|
+
# v4.2.0: Allow 'supports' AFTER &Class::member reference
|
|
2522
|
+
# Syntax: define func() &$pyclass::method : supports python { }
|
|
2523
|
+
if self._match(TokenType.COLON) or self._match(TokenType.DOUBLE_COLON):
|
|
2524
|
+
if self._match_keyword('supports'):
|
|
2525
|
+
if self._check(TokenType.AT):
|
|
2526
|
+
self._advance()
|
|
2527
|
+
if self._check(TokenType.IDENTIFIER):
|
|
2528
|
+
supports_language = '@' + self._advance().value
|
|
2529
|
+
else:
|
|
2530
|
+
raise CSSLSyntaxError("Expected language identifier after '@' in 'supports'")
|
|
2531
|
+
elif self._check(TokenType.IDENTIFIER):
|
|
2532
|
+
supports_language = self._advance().value
|
|
2533
|
+
else:
|
|
2534
|
+
raise CSSLSyntaxError("Expected language identifier after 'supports'")
|
|
2535
|
+
|
|
2536
|
+
self._expect(TokenType.BLOCK_START)
|
|
2537
|
+
|
|
2538
|
+
# v4.2.0: Extract raw body when supports_language is set for transformation
|
|
2539
|
+
raw_body = None
|
|
2540
|
+
children = []
|
|
2541
|
+
if supports_language:
|
|
2542
|
+
raw_body = self._extract_raw_block_body()
|
|
2543
|
+
# _extract_raw_block_body positions cursor at BLOCK_END
|
|
2544
|
+
else:
|
|
2545
|
+
while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
|
|
2546
|
+
stmt = self._parse_statement()
|
|
2547
|
+
if stmt:
|
|
2548
|
+
children.append(stmt)
|
|
2549
|
+
|
|
2550
|
+
self._expect(TokenType.BLOCK_END)
|
|
2551
|
+
|
|
2077
2552
|
node = ASTNode('function', value={
|
|
2078
2553
|
'name': name,
|
|
2079
2554
|
'is_global': is_global,
|
|
2555
|
+
'is_embedded': False, # v4.2.5: define uses delayed &target replacement
|
|
2080
2556
|
'params': params,
|
|
2081
2557
|
'non_null': non_null,
|
|
2082
2558
|
'exclude_type': exclude_type, # *[type] - must NOT return this type
|
|
@@ -2092,16 +2568,13 @@ class CSSLParser:
|
|
|
2092
2568
|
# New append mode fields
|
|
2093
2569
|
'append_mode': append_mode,
|
|
2094
2570
|
'append_ref_class': append_ref_class,
|
|
2095
|
-
'append_ref_member': append_ref_member
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
if stmt:
|
|
2102
|
-
node.children.append(stmt)
|
|
2571
|
+
'append_ref_member': append_ref_member,
|
|
2572
|
+
# v4.1.0: Multi-language support
|
|
2573
|
+
'supports_language': supports_language,
|
|
2574
|
+
# v4.2.0: Raw body for language transformation
|
|
2575
|
+
'raw_body': raw_body
|
|
2576
|
+
}, children=children)
|
|
2103
2577
|
|
|
2104
|
-
self._expect(TokenType.BLOCK_END)
|
|
2105
2578
|
return node
|
|
2106
2579
|
|
|
2107
2580
|
def _parse_statement(self) -> Optional[ASTNode]:
|
|
@@ -2127,6 +2600,9 @@ class CSSLParser:
|
|
|
2127
2600
|
return self._parse_try()
|
|
2128
2601
|
elif self._match_keyword('await'):
|
|
2129
2602
|
return self._parse_await()
|
|
2603
|
+
elif self._match_keyword('supports'):
|
|
2604
|
+
# v4.2.0: Standalone supports block for multi-language syntax
|
|
2605
|
+
return self._parse_supports_block()
|
|
2130
2606
|
elif self._match_keyword('define'):
|
|
2131
2607
|
# Nested define function
|
|
2132
2608
|
return self._parse_define()
|
|
@@ -2144,9 +2620,11 @@ class CSSLParser:
|
|
|
2144
2620
|
self._peek(1).type == TokenType.DOUBLE_COLON)):
|
|
2145
2621
|
# super() or super::method() call - calls parent constructor/method
|
|
2146
2622
|
return self._parse_super_call()
|
|
2623
|
+
# v4.2.1: Added LANG_INSTANCE_REF for lang$instance statements
|
|
2147
2624
|
elif (self._check(TokenType.IDENTIFIER) or self._check(TokenType.AT) or
|
|
2148
2625
|
self._check(TokenType.CAPTURED_REF) or self._check(TokenType.SHARED_REF) or
|
|
2149
2626
|
self._check(TokenType.GLOBAL_REF) or self._check(TokenType.SELF_REF) or
|
|
2627
|
+
self._check(TokenType.LANG_INSTANCE_REF) or
|
|
2150
2628
|
(self._check(TokenType.KEYWORD) and self._current().value in ('this', 'new')) or
|
|
2151
2629
|
self._looks_like_namespace_call()):
|
|
2152
2630
|
return self._parse_expression_statement()
|
|
@@ -2352,42 +2830,40 @@ class CSSLParser:
|
|
|
2352
2830
|
|
|
2353
2831
|
Supports: i = i + 1, i++, ++i, i += 1, i -= 1
|
|
2354
2832
|
"""
|
|
2355
|
-
# Check for prefix increment/decrement: ++i or --i
|
|
2356
|
-
if self._check(TokenType.
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2833
|
+
# Check for prefix increment/decrement: ++i or --i (as single PLUS_PLUS/MINUS_MINUS token)
|
|
2834
|
+
if self._check(TokenType.PLUS_PLUS):
|
|
2835
|
+
self._advance()
|
|
2836
|
+
var_name = self._advance().value
|
|
2837
|
+
return ASTNode('c_for_update', value={'var': var_name, 'op': 'increment'})
|
|
2838
|
+
elif self._check(TokenType.MINUS_MINUS):
|
|
2839
|
+
self._advance()
|
|
2840
|
+
var_name = self._advance().value
|
|
2841
|
+
return ASTNode('c_for_update', value={'var': var_name, 'op': 'decrement'})
|
|
2364
2842
|
|
|
2365
2843
|
# Regular variable assignment or postfix
|
|
2366
2844
|
var_name = self._advance().value
|
|
2367
2845
|
|
|
2368
|
-
# Check for postfix increment/decrement: i++ or i--
|
|
2369
|
-
if self._check(TokenType.
|
|
2846
|
+
# Check for postfix increment/decrement: i++ or i-- (as single PLUS_PLUS/MINUS_MINUS token)
|
|
2847
|
+
if self._check(TokenType.PLUS_PLUS):
|
|
2848
|
+
self._advance()
|
|
2849
|
+
return ASTNode('c_for_update', value={'var': var_name, 'op': 'increment'})
|
|
2850
|
+
elif self._check(TokenType.MINUS_MINUS):
|
|
2851
|
+
self._advance()
|
|
2852
|
+
return ASTNode('c_for_update', value={'var': var_name, 'op': 'decrement'})
|
|
2853
|
+
# i += value
|
|
2854
|
+
elif self._check(TokenType.PLUS):
|
|
2370
2855
|
self._advance()
|
|
2371
|
-
if self._check(TokenType.
|
|
2856
|
+
if self._check(TokenType.EQUALS):
|
|
2372
2857
|
self._advance()
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
if self._check(TokenType.EQUALS):
|
|
2377
|
-
self._advance()
|
|
2378
|
-
value = self._parse_expression()
|
|
2379
|
-
return ASTNode('c_for_update', value={'var': var_name, 'op': 'add', 'value': value})
|
|
2858
|
+
value = self._parse_expression()
|
|
2859
|
+
return ASTNode('c_for_update', value={'var': var_name, 'op': 'add', 'value': value})
|
|
2860
|
+
# i -= value
|
|
2380
2861
|
elif self._check(TokenType.MINUS):
|
|
2381
2862
|
self._advance()
|
|
2382
|
-
if self._check(TokenType.
|
|
2863
|
+
if self._check(TokenType.EQUALS):
|
|
2383
2864
|
self._advance()
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
# i -= value
|
|
2387
|
-
if self._check(TokenType.EQUALS):
|
|
2388
|
-
self._advance()
|
|
2389
|
-
value = self._parse_expression()
|
|
2390
|
-
return ASTNode('c_for_update', value={'var': var_name, 'op': 'subtract', 'value': value})
|
|
2865
|
+
value = self._parse_expression()
|
|
2866
|
+
return ASTNode('c_for_update', value={'var': var_name, 'op': 'subtract', 'value': value})
|
|
2391
2867
|
|
|
2392
2868
|
# Regular assignment: i = expression
|
|
2393
2869
|
if self._check(TokenType.EQUALS):
|
|
@@ -2512,6 +2988,14 @@ class CSSLParser:
|
|
|
2512
2988
|
value = self._parse_expression()
|
|
2513
2989
|
self._expect(TokenType.PAREN_END)
|
|
2514
2990
|
|
|
2991
|
+
# v4.2.5: Check if this is a param switch (switch on open params identifier)
|
|
2992
|
+
# Syntax: switch(Params): case name: ... except age: ... always: ... finally: ...
|
|
2993
|
+
is_param_switch = (value.type == 'identifier')
|
|
2994
|
+
|
|
2995
|
+
if is_param_switch:
|
|
2996
|
+
return self._parse_param_switch(value)
|
|
2997
|
+
|
|
2998
|
+
# Regular switch
|
|
2515
2999
|
node = ASTNode('switch', value={'value': value}, children=[])
|
|
2516
3000
|
self._expect(TokenType.BLOCK_START)
|
|
2517
3001
|
|
|
@@ -2545,6 +3029,139 @@ class CSSLParser:
|
|
|
2545
3029
|
self._expect(TokenType.BLOCK_END)
|
|
2546
3030
|
return node
|
|
2547
3031
|
|
|
3032
|
+
def _parse_param_switch(self, params_identifier: ASTNode) -> ASTNode:
|
|
3033
|
+
"""Parse param switch for open parameters.
|
|
3034
|
+
|
|
3035
|
+
v4.2.5: Switch on which parameters were provided.
|
|
3036
|
+
|
|
3037
|
+
Syntax:
|
|
3038
|
+
switch(Params): or switch(Params) {
|
|
3039
|
+
case name: // if 'name' param exists
|
|
3040
|
+
...
|
|
3041
|
+
break;
|
|
3042
|
+
case name & age: // if both 'name' AND 'age' exist
|
|
3043
|
+
...
|
|
3044
|
+
break;
|
|
3045
|
+
case name & not age: // if 'name' exists but 'age' doesn't
|
|
3046
|
+
...
|
|
3047
|
+
break;
|
|
3048
|
+
except name: // if 'name' does NOT exist (alias for 'case not name')
|
|
3049
|
+
...
|
|
3050
|
+
break;
|
|
3051
|
+
default: // fallback if no case matches
|
|
3052
|
+
...
|
|
3053
|
+
always: // always runs after a matching case
|
|
3054
|
+
...
|
|
3055
|
+
finally: // cleanup, runs last regardless
|
|
3056
|
+
...
|
|
3057
|
+
}
|
|
3058
|
+
"""
|
|
3059
|
+
# Allow both : and { as block start
|
|
3060
|
+
if self._match(TokenType.COLON):
|
|
3061
|
+
pass # Python-style with :
|
|
3062
|
+
self._expect(TokenType.BLOCK_START)
|
|
3063
|
+
|
|
3064
|
+
node = ASTNode('param_switch', value={
|
|
3065
|
+
'params': params_identifier.value # The open params variable name
|
|
3066
|
+
}, children=[])
|
|
3067
|
+
|
|
3068
|
+
while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
|
|
3069
|
+
if self._match_keyword('case'):
|
|
3070
|
+
# Parse param condition: name, name & age, name & not age
|
|
3071
|
+
condition = self._parse_param_condition()
|
|
3072
|
+
self._expect(TokenType.COLON)
|
|
3073
|
+
|
|
3074
|
+
case_node = ASTNode('param_case', value={'condition': condition}, children=[])
|
|
3075
|
+
self._parse_case_body(case_node)
|
|
3076
|
+
node.children.append(case_node)
|
|
3077
|
+
|
|
3078
|
+
elif self._match_keyword('except'):
|
|
3079
|
+
# except name: is alias for case not name:
|
|
3080
|
+
param_name = self._advance().value
|
|
3081
|
+
self._expect(TokenType.COLON)
|
|
3082
|
+
|
|
3083
|
+
condition = {'type': 'not', 'param': param_name}
|
|
3084
|
+
case_node = ASTNode('param_case', value={'condition': condition}, children=[])
|
|
3085
|
+
self._parse_case_body(case_node)
|
|
3086
|
+
node.children.append(case_node)
|
|
3087
|
+
|
|
3088
|
+
elif self._match_keyword('default'):
|
|
3089
|
+
self._expect(TokenType.COLON)
|
|
3090
|
+
default_node = ASTNode('param_default', children=[])
|
|
3091
|
+
self._parse_case_body(default_node)
|
|
3092
|
+
node.children.append(default_node)
|
|
3093
|
+
|
|
3094
|
+
elif self._match_keyword('always'):
|
|
3095
|
+
self._expect(TokenType.COLON)
|
|
3096
|
+
always_node = ASTNode('param_always', children=[])
|
|
3097
|
+
self._parse_case_body(always_node)
|
|
3098
|
+
node.children.append(always_node)
|
|
3099
|
+
|
|
3100
|
+
elif self._match_keyword('finally'):
|
|
3101
|
+
self._expect(TokenType.COLON)
|
|
3102
|
+
finally_node = ASTNode('param_finally', children=[])
|
|
3103
|
+
self._parse_case_body(finally_node)
|
|
3104
|
+
node.children.append(finally_node)
|
|
3105
|
+
|
|
3106
|
+
else:
|
|
3107
|
+
self._advance()
|
|
3108
|
+
|
|
3109
|
+
self._expect(TokenType.BLOCK_END)
|
|
3110
|
+
return node
|
|
3111
|
+
|
|
3112
|
+
def _parse_param_condition(self) -> dict:
|
|
3113
|
+
"""Parse param switch condition.
|
|
3114
|
+
|
|
3115
|
+
Syntax:
|
|
3116
|
+
name -> {'type': 'exists', 'param': 'name'}
|
|
3117
|
+
not name -> {'type': 'not', 'param': 'name'}
|
|
3118
|
+
name & age -> {'type': 'and', 'left': {...}, 'right': {...}}
|
|
3119
|
+
name & not age -> {'type': 'and', 'left': {...}, 'right': {'type': 'not', ...}}
|
|
3120
|
+
"""
|
|
3121
|
+
# Check for 'not' prefix
|
|
3122
|
+
negated = self._match_keyword('not')
|
|
3123
|
+
param_name = self._advance().value
|
|
3124
|
+
|
|
3125
|
+
if negated:
|
|
3126
|
+
condition = {'type': 'not', 'param': param_name}
|
|
3127
|
+
else:
|
|
3128
|
+
condition = {'type': 'exists', 'param': param_name}
|
|
3129
|
+
|
|
3130
|
+
# Check for & (AND) combinations
|
|
3131
|
+
while self._match(TokenType.AMPERSAND):
|
|
3132
|
+
# Parse right side
|
|
3133
|
+
right_negated = self._match_keyword('not')
|
|
3134
|
+
right_param = self._advance().value
|
|
3135
|
+
|
|
3136
|
+
if right_negated:
|
|
3137
|
+
right_condition = {'type': 'not', 'param': right_param}
|
|
3138
|
+
else:
|
|
3139
|
+
right_condition = {'type': 'exists', 'param': right_param}
|
|
3140
|
+
|
|
3141
|
+
condition = {'type': 'and', 'left': condition, 'right': right_condition}
|
|
3142
|
+
|
|
3143
|
+
return condition
|
|
3144
|
+
|
|
3145
|
+
def _parse_case_body(self, case_node: ASTNode):
|
|
3146
|
+
"""Parse the body of a case/except/default/always/finally block."""
|
|
3147
|
+
stop_keywords = ['case', 'except', 'default', 'always', 'finally']
|
|
3148
|
+
|
|
3149
|
+
while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
|
|
3150
|
+
# Check if we hit another case keyword
|
|
3151
|
+
if any(self._check_keyword(kw) for kw in stop_keywords):
|
|
3152
|
+
break
|
|
3153
|
+
|
|
3154
|
+
stmt = self._parse_statement()
|
|
3155
|
+
if stmt:
|
|
3156
|
+
case_node.children.append(stmt)
|
|
3157
|
+
|
|
3158
|
+
# Check for break
|
|
3159
|
+
if self._check_keyword('break'):
|
|
3160
|
+
break_stmt = self._parse_statement()
|
|
3161
|
+
if break_stmt:
|
|
3162
|
+
case_node.children.append(break_stmt)
|
|
3163
|
+
break
|
|
3164
|
+
|
|
2548
3165
|
def _parse_return(self) -> ASTNode:
|
|
2549
3166
|
"""Parse return statement, supporting multiple values for shuffled functions.
|
|
2550
3167
|
|
|
@@ -2609,6 +3226,9 @@ class CSSLParser:
|
|
|
2609
3226
|
self._expect(TokenType.BLOCK_END)
|
|
2610
3227
|
node.children.append(try_block)
|
|
2611
3228
|
|
|
3229
|
+
# v4.2.6: Skip optional semicolon between try block and catch
|
|
3230
|
+
self._match(TokenType.SEMICOLON)
|
|
3231
|
+
|
|
2612
3232
|
if self._match_keyword('catch'):
|
|
2613
3233
|
error_var = None
|
|
2614
3234
|
if self._match(TokenType.PAREN_START):
|
|
@@ -2624,6 +3244,20 @@ class CSSLParser:
|
|
|
2624
3244
|
self._expect(TokenType.BLOCK_END)
|
|
2625
3245
|
node.children.append(catch_block)
|
|
2626
3246
|
|
|
3247
|
+
# v4.2.6: Skip optional semicolon between catch and finally
|
|
3248
|
+
self._match(TokenType.SEMICOLON)
|
|
3249
|
+
|
|
3250
|
+
# v4.2.6: Add finally support
|
|
3251
|
+
if self._match_keyword('finally'):
|
|
3252
|
+
finally_block = ASTNode('finally-block', children=[])
|
|
3253
|
+
self._expect(TokenType.BLOCK_START)
|
|
3254
|
+
while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
|
|
3255
|
+
stmt = self._parse_statement()
|
|
3256
|
+
if stmt:
|
|
3257
|
+
finally_block.children.append(stmt)
|
|
3258
|
+
self._expect(TokenType.BLOCK_END)
|
|
3259
|
+
node.children.append(finally_block)
|
|
3260
|
+
|
|
2627
3261
|
return node
|
|
2628
3262
|
|
|
2629
3263
|
def _parse_await(self) -> ASTNode:
|
|
@@ -2632,6 +3266,163 @@ class CSSLParser:
|
|
|
2632
3266
|
self._match(TokenType.SEMICOLON)
|
|
2633
3267
|
return ASTNode('await', value=expr)
|
|
2634
3268
|
|
|
3269
|
+
def _parse_supports_block(self) -> ASTNode:
|
|
3270
|
+
"""Parse standalone supports block for multi-language syntax.
|
|
3271
|
+
|
|
3272
|
+
v4.2.0: Allows 'supports' to be used anywhere, not just in class/function.
|
|
3273
|
+
|
|
3274
|
+
Syntax:
|
|
3275
|
+
supports py { } // Python syntax block
|
|
3276
|
+
supports @py { } // With @ prefix
|
|
3277
|
+
supports python { } // Full language name
|
|
3278
|
+
supports cpp { } // C++ syntax block
|
|
3279
|
+
supports javascript { } // JavaScript syntax block
|
|
3280
|
+
|
|
3281
|
+
Example:
|
|
3282
|
+
supports py {
|
|
3283
|
+
for i in range(10):
|
|
3284
|
+
print(i)
|
|
3285
|
+
}
|
|
3286
|
+
|
|
3287
|
+
supports cpp {
|
|
3288
|
+
std::cout << "Hello" << std::endl;
|
|
3289
|
+
int x = 42;
|
|
3290
|
+
}
|
|
3291
|
+
"""
|
|
3292
|
+
# Parse language identifier
|
|
3293
|
+
language = None
|
|
3294
|
+
if self._check(TokenType.AT):
|
|
3295
|
+
self._advance()
|
|
3296
|
+
if self._check(TokenType.IDENTIFIER):
|
|
3297
|
+
language = '@' + self._advance().value
|
|
3298
|
+
else:
|
|
3299
|
+
raise CSSLSyntaxError("Expected language identifier after '@' in 'supports'")
|
|
3300
|
+
elif self._check(TokenType.IDENTIFIER):
|
|
3301
|
+
language = self._advance().value
|
|
3302
|
+
else:
|
|
3303
|
+
raise CSSLSyntaxError("Expected language identifier after 'supports'")
|
|
3304
|
+
|
|
3305
|
+
# Extract raw block source for language transformation (preserves indentation)
|
|
3306
|
+
raw_source = None
|
|
3307
|
+
if self._check(TokenType.BLOCK_START):
|
|
3308
|
+
raw_source = self._extract_raw_block_body()
|
|
3309
|
+
|
|
3310
|
+
# Skip parsing body if we have raw_source - runtime will transform and parse
|
|
3311
|
+
body = []
|
|
3312
|
+
self._expect(TokenType.BLOCK_START)
|
|
3313
|
+
if raw_source is None:
|
|
3314
|
+
# No raw source (e.g., already CSSL syntax), parse normally
|
|
3315
|
+
while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
|
|
3316
|
+
stmt = self._parse_statement()
|
|
3317
|
+
if stmt:
|
|
3318
|
+
body.append(stmt)
|
|
3319
|
+
else:
|
|
3320
|
+
# Skip the block - runtime will transform and parse
|
|
3321
|
+
brace_count = 1
|
|
3322
|
+
while brace_count > 0 and not self._is_at_end():
|
|
3323
|
+
if self._check(TokenType.BLOCK_START):
|
|
3324
|
+
brace_count += 1
|
|
3325
|
+
elif self._check(TokenType.BLOCK_END):
|
|
3326
|
+
brace_count -= 1
|
|
3327
|
+
if brace_count > 0:
|
|
3328
|
+
self._advance()
|
|
3329
|
+
self._expect(TokenType.BLOCK_END)
|
|
3330
|
+
|
|
3331
|
+
return ASTNode('supports_block', value={
|
|
3332
|
+
'language': language,
|
|
3333
|
+
'raw_source': raw_source
|
|
3334
|
+
}, children=body)
|
|
3335
|
+
|
|
3336
|
+
def _extract_raw_block_source(self) -> Optional[str]:
|
|
3337
|
+
"""Extract raw source code from a {} block before parsing.
|
|
3338
|
+
|
|
3339
|
+
Used for 'supports' blocks to allow language transformation.
|
|
3340
|
+
"""
|
|
3341
|
+
if not self._check(TokenType.BLOCK_START):
|
|
3342
|
+
return None
|
|
3343
|
+
|
|
3344
|
+
# Find the matching block end by counting braces
|
|
3345
|
+
start_pos = self.pos
|
|
3346
|
+
brace_count = 0
|
|
3347
|
+
found_start = False
|
|
3348
|
+
|
|
3349
|
+
# Walk through tokens to find matching close brace
|
|
3350
|
+
temp_pos = self.pos
|
|
3351
|
+
while temp_pos < len(self.tokens):
|
|
3352
|
+
token = self.tokens[temp_pos]
|
|
3353
|
+
if token.type == TokenType.BLOCK_START:
|
|
3354
|
+
if not found_start:
|
|
3355
|
+
found_start = True
|
|
3356
|
+
brace_count += 1
|
|
3357
|
+
elif token.type == TokenType.BLOCK_END:
|
|
3358
|
+
brace_count -= 1
|
|
3359
|
+
if brace_count == 0:
|
|
3360
|
+
break
|
|
3361
|
+
temp_pos += 1
|
|
3362
|
+
|
|
3363
|
+
# Build source from tokens between braces (excluding braces)
|
|
3364
|
+
source_parts = []
|
|
3365
|
+
for i in range(start_pos + 1, temp_pos):
|
|
3366
|
+
token = self.tokens[i]
|
|
3367
|
+
if token.type == TokenType.STRING:
|
|
3368
|
+
source_parts.append(f'"{token.value}"')
|
|
3369
|
+
elif token.type == TokenType.NEWLINE:
|
|
3370
|
+
source_parts.append('\n')
|
|
3371
|
+
else:
|
|
3372
|
+
source_parts.append(str(token.value))
|
|
3373
|
+
|
|
3374
|
+
return ' '.join(source_parts)
|
|
3375
|
+
|
|
3376
|
+
def _extract_raw_block_body(self) -> Optional[str]:
|
|
3377
|
+
"""Extract raw source code body from a {} block for language transformation.
|
|
3378
|
+
|
|
3379
|
+
v4.2.0: Used for 'supports' blocks to preserve original source (including indentation).
|
|
3380
|
+
This extracts the raw text between { and } from the original source string.
|
|
3381
|
+
|
|
3382
|
+
Returns the raw body content without the surrounding braces.
|
|
3383
|
+
"""
|
|
3384
|
+
if not self._check(TokenType.BLOCK_START):
|
|
3385
|
+
return None
|
|
3386
|
+
|
|
3387
|
+
# Get the { token's position
|
|
3388
|
+
start_token = self._current()
|
|
3389
|
+
start_line = start_token.line
|
|
3390
|
+
start_col = start_token.column
|
|
3391
|
+
|
|
3392
|
+
# Find the { character position in source
|
|
3393
|
+
# Line numbers are 1-indexed, columns are 1-indexed
|
|
3394
|
+
brace_start_pos = 0
|
|
3395
|
+
current_line = 1
|
|
3396
|
+
for i, char in enumerate(self.source):
|
|
3397
|
+
if current_line == start_line:
|
|
3398
|
+
# Found the right line, now find the column
|
|
3399
|
+
col_in_line = i - brace_start_pos + 1
|
|
3400
|
+
if col_in_line >= start_col:
|
|
3401
|
+
# Search for { from here
|
|
3402
|
+
for j in range(i, len(self.source)):
|
|
3403
|
+
if self.source[j] == '{':
|
|
3404
|
+
brace_start_pos = j
|
|
3405
|
+
break
|
|
3406
|
+
break
|
|
3407
|
+
if char == '\n':
|
|
3408
|
+
current_line += 1
|
|
3409
|
+
brace_start_pos = i + 1
|
|
3410
|
+
|
|
3411
|
+
# Now find the matching closing brace
|
|
3412
|
+
brace_count = 1
|
|
3413
|
+
pos = brace_start_pos + 1
|
|
3414
|
+
while pos < len(self.source) and brace_count > 0:
|
|
3415
|
+
char = self.source[pos]
|
|
3416
|
+
if char == '{':
|
|
3417
|
+
brace_count += 1
|
|
3418
|
+
elif char == '}':
|
|
3419
|
+
brace_count -= 1
|
|
3420
|
+
pos += 1
|
|
3421
|
+
|
|
3422
|
+
# Extract the body (everything between { and })
|
|
3423
|
+
body = self.source[brace_start_pos + 1:pos - 1]
|
|
3424
|
+
return body.strip()
|
|
3425
|
+
|
|
2635
3426
|
def _parse_action_block(self) -> ASTNode:
|
|
2636
3427
|
"""Parse an action block { ... } containing statements for createcmd"""
|
|
2637
3428
|
node = ASTNode('action_block', children=[])
|
|
@@ -2948,6 +3739,14 @@ class CSSLParser:
|
|
|
2948
3739
|
if self._match(TokenType.MINUS):
|
|
2949
3740
|
operand = self._parse_unary()
|
|
2950
3741
|
return ASTNode('unary', value={'op': '-', 'operand': operand})
|
|
3742
|
+
# Prefix increment: ++i
|
|
3743
|
+
if self._match(TokenType.PLUS_PLUS):
|
|
3744
|
+
operand = self._parse_unary()
|
|
3745
|
+
return ASTNode('increment', value={'op': 'prefix', 'operand': operand})
|
|
3746
|
+
# Prefix decrement: --i
|
|
3747
|
+
if self._match(TokenType.MINUS_MINUS):
|
|
3748
|
+
operand = self._parse_unary()
|
|
3749
|
+
return ASTNode('decrement', value={'op': 'prefix', 'operand': operand})
|
|
2951
3750
|
if self._match(TokenType.AMPERSAND):
|
|
2952
3751
|
# Reference operator: &variable or &@module
|
|
2953
3752
|
operand = self._parse_unary()
|
|
@@ -3009,7 +3808,7 @@ class CSSLParser:
|
|
|
3009
3808
|
# Just 'this' keyword alone - return as identifier for now
|
|
3010
3809
|
return ASTNode('identifier', value='this')
|
|
3011
3810
|
|
|
3012
|
-
# Handle 'new ClassName(args)' or 'new @ClassName(args)' instantiation
|
|
3811
|
+
# Handle 'new ClassName(args)' or 'new @ClassName(args)' or 'new Namespace::ClassName(args)' instantiation
|
|
3013
3812
|
if self._check(TokenType.KEYWORD) and self._current().value == 'new':
|
|
3014
3813
|
self._advance() # consume 'new'
|
|
3015
3814
|
# Check for @ prefix (global class reference)
|
|
@@ -3017,13 +3816,19 @@ class CSSLParser:
|
|
|
3017
3816
|
if self._check(TokenType.AT):
|
|
3018
3817
|
self._advance() # consume @
|
|
3019
3818
|
is_global_ref = True
|
|
3020
|
-
class_name = self._advance().value # get class name
|
|
3819
|
+
class_name = self._advance().value # get class name or namespace
|
|
3820
|
+
# v4.2.6: Handle Namespace::ClassName syntax
|
|
3821
|
+
namespace = None
|
|
3822
|
+
if self._check(TokenType.DOUBLE_COLON):
|
|
3823
|
+
self._advance() # consume ::
|
|
3824
|
+
namespace = class_name
|
|
3825
|
+
class_name = self._advance().value # get actual class name
|
|
3021
3826
|
args = []
|
|
3022
3827
|
kwargs = {}
|
|
3023
3828
|
if self._match(TokenType.PAREN_START):
|
|
3024
3829
|
args, kwargs = self._parse_call_arguments()
|
|
3025
3830
|
self._expect(TokenType.PAREN_END)
|
|
3026
|
-
node = ASTNode('new', value={'class': class_name, 'args': args, 'kwargs': kwargs, 'is_global_ref': is_global_ref})
|
|
3831
|
+
node = ASTNode('new', value={'class': class_name, 'namespace': namespace, 'args': args, 'kwargs': kwargs, 'is_global_ref': is_global_ref})
|
|
3027
3832
|
# Continue to check for member access, calls on the new object
|
|
3028
3833
|
while True:
|
|
3029
3834
|
if self._match(TokenType.DOT):
|
|
@@ -3090,12 +3895,13 @@ class CSSLParser:
|
|
|
3090
3895
|
token = self._advance()
|
|
3091
3896
|
node = ASTNode('global_ref', value=token.value, line=token.line, column=token.column)
|
|
3092
3897
|
# Check for member access, calls, indexing - with kwargs support
|
|
3898
|
+
# Support both . and -> for member access
|
|
3093
3899
|
while True:
|
|
3094
3900
|
if self._match(TokenType.PAREN_START):
|
|
3095
3901
|
args, kwargs = self._parse_call_arguments()
|
|
3096
3902
|
self._expect(TokenType.PAREN_END)
|
|
3097
3903
|
node = ASTNode('call', value={'callee': node, 'args': args, 'kwargs': kwargs})
|
|
3098
|
-
elif self._match(TokenType.DOT):
|
|
3904
|
+
elif self._match(TokenType.DOT) or self._match(TokenType.FLOW_RIGHT):
|
|
3099
3905
|
member = self._advance().value
|
|
3100
3906
|
node = ASTNode('member_access', value={'object': node, 'member': member})
|
|
3101
3907
|
elif self._match(TokenType.BRACKET_START):
|
|
@@ -3111,12 +3917,14 @@ class CSSLParser:
|
|
|
3111
3917
|
token = self._advance()
|
|
3112
3918
|
node = ASTNode('shared_ref', value=token.value, line=token.line, column=token.column)
|
|
3113
3919
|
# Check for member access, calls, indexing - with kwargs support
|
|
3920
|
+
# Support both . and -> for member access (like this->member)
|
|
3114
3921
|
while True:
|
|
3115
3922
|
if self._match(TokenType.PAREN_START):
|
|
3116
3923
|
args, kwargs = self._parse_call_arguments()
|
|
3117
3924
|
self._expect(TokenType.PAREN_END)
|
|
3118
3925
|
node = ASTNode('call', value={'callee': node, 'args': args, 'kwargs': kwargs})
|
|
3119
|
-
elif self._match(TokenType.DOT):
|
|
3926
|
+
elif self._match(TokenType.DOT) or self._match(TokenType.FLOW_RIGHT):
|
|
3927
|
+
# Support both $obj.member and $obj->member syntax
|
|
3120
3928
|
member = self._advance().value
|
|
3121
3929
|
node = ASTNode('member_access', value={'object': node, 'member': member})
|
|
3122
3930
|
elif self._match(TokenType.BRACKET_START):
|
|
@@ -3132,12 +3940,36 @@ class CSSLParser:
|
|
|
3132
3940
|
token = self._advance()
|
|
3133
3941
|
node = ASTNode('captured_ref', value=token.value, line=token.line, column=token.column)
|
|
3134
3942
|
# Check for member access, calls, indexing - with kwargs support
|
|
3943
|
+
# Support both . and -> for member access
|
|
3135
3944
|
while True:
|
|
3136
3945
|
if self._match(TokenType.PAREN_START):
|
|
3137
3946
|
args, kwargs = self._parse_call_arguments()
|
|
3138
3947
|
self._expect(TokenType.PAREN_END)
|
|
3139
3948
|
node = ASTNode('call', value={'callee': node, 'args': args, 'kwargs': kwargs})
|
|
3140
|
-
elif self._match(TokenType.DOT):
|
|
3949
|
+
elif self._match(TokenType.DOT) or self._match(TokenType.FLOW_RIGHT):
|
|
3950
|
+
member = self._advance().value
|
|
3951
|
+
node = ASTNode('member_access', value={'object': node, 'member': member})
|
|
3952
|
+
elif self._match(TokenType.BRACKET_START):
|
|
3953
|
+
index = self._parse_expression()
|
|
3954
|
+
self._expect(TokenType.BRACKET_END)
|
|
3955
|
+
node = ASTNode('index_access', value={'object': node, 'index': index})
|
|
3956
|
+
else:
|
|
3957
|
+
break
|
|
3958
|
+
return node
|
|
3959
|
+
|
|
3960
|
+
# v4.1.0: Cross-language instance reference: cpp$ClassName, py$Object
|
|
3961
|
+
if self._check(TokenType.LANG_INSTANCE_REF):
|
|
3962
|
+
token = self._advance()
|
|
3963
|
+
ref = token.value # {'lang': 'cpp', 'instance': 'ClassName'}
|
|
3964
|
+
node = ASTNode('lang_instance_ref', value=ref, line=token.line, column=token.column)
|
|
3965
|
+
# Check for member access, calls, indexing
|
|
3966
|
+
# Support both . and -> for member access
|
|
3967
|
+
while True:
|
|
3968
|
+
if self._match(TokenType.PAREN_START):
|
|
3969
|
+
args, kwargs = self._parse_call_arguments()
|
|
3970
|
+
self._expect(TokenType.PAREN_END)
|
|
3971
|
+
node = ASTNode('call', value={'callee': node, 'args': args, 'kwargs': kwargs})
|
|
3972
|
+
elif self._match(TokenType.DOT) or self._match(TokenType.FLOW_RIGHT):
|
|
3141
3973
|
member = self._advance().value
|
|
3142
3974
|
node = ASTNode('member_access', value={'object': node, 'member': member})
|
|
3143
3975
|
elif self._match(TokenType.BRACKET_START):
|
|
@@ -3366,23 +4198,24 @@ class CSSLParser:
|
|
|
3366
4198
|
|
|
3367
4199
|
self._expect(TokenType.COMPARE_GT) # consume >
|
|
3368
4200
|
|
|
3369
|
-
#
|
|
4201
|
+
# Optional () - for named parameter search, () is not required
|
|
4202
|
+
args = []
|
|
3370
4203
|
if self._check(TokenType.PAREN_START):
|
|
3371
4204
|
self._advance() # consume (
|
|
3372
|
-
args = []
|
|
3373
4205
|
while not self._check(TokenType.PAREN_END):
|
|
3374
4206
|
args.append(self._parse_expression())
|
|
3375
4207
|
if not self._check(TokenType.PAREN_END):
|
|
3376
4208
|
self._expect(TokenType.COMMA)
|
|
3377
4209
|
self._expect(TokenType.PAREN_END)
|
|
3378
4210
|
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
4211
|
+
# Return as typed function call (works with or without ())
|
|
4212
|
+
# v4.2.5: OpenFind<type, "name"> now works without ()
|
|
4213
|
+
return ASTNode('typed_call', value={
|
|
4214
|
+
'name': name,
|
|
4215
|
+
'type_param': type_param,
|
|
4216
|
+
'param_name': param_name, # Named parameter for OpenFind
|
|
4217
|
+
'args': args
|
|
4218
|
+
})
|
|
3386
4219
|
|
|
3387
4220
|
node = ASTNode('identifier', value=name)
|
|
3388
4221
|
|
|
@@ -3398,6 +4231,12 @@ class CSSLParser:
|
|
|
3398
4231
|
index = self._parse_expression()
|
|
3399
4232
|
self._expect(TokenType.BRACKET_END)
|
|
3400
4233
|
node = ASTNode('index_access', value={'object': node, 'index': index})
|
|
4234
|
+
# Postfix increment: i++
|
|
4235
|
+
elif self._match(TokenType.PLUS_PLUS):
|
|
4236
|
+
node = ASTNode('increment', value={'op': 'postfix', 'operand': node})
|
|
4237
|
+
# Postfix decrement: i--
|
|
4238
|
+
elif self._match(TokenType.MINUS_MINUS):
|
|
4239
|
+
node = ASTNode('decrement', value={'op': 'postfix', 'operand': node})
|
|
3401
4240
|
else:
|
|
3402
4241
|
break
|
|
3403
4242
|
|
|
@@ -3437,9 +4276,11 @@ class CSSLParser:
|
|
|
3437
4276
|
|
|
3438
4277
|
while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
|
|
3439
4278
|
# Parse statement or expression
|
|
4279
|
+
# v4.2.1: Added LANG_INSTANCE_REF for lang$instance statements
|
|
3440
4280
|
if (self._check(TokenType.IDENTIFIER) or self._check(TokenType.AT) or
|
|
3441
4281
|
self._check(TokenType.CAPTURED_REF) or self._check(TokenType.SHARED_REF) or
|
|
3442
4282
|
self._check(TokenType.GLOBAL_REF) or self._check(TokenType.SELF_REF) or
|
|
4283
|
+
self._check(TokenType.LANG_INSTANCE_REF) or
|
|
3443
4284
|
self._check(TokenType.STRING) or self._check(TokenType.NUMBER) or
|
|
3444
4285
|
self._check(TokenType.BOOLEAN) or self._check(TokenType.NULL) or
|
|
3445
4286
|
self._check(TokenType.PAREN_START)):
|
|
@@ -3508,7 +4349,7 @@ def parse_cssl(source: str) -> ASTNode:
|
|
|
3508
4349
|
"""Parse CSSL source code into an AST - auto-detects service vs program format"""
|
|
3509
4350
|
lexer = CSSLLexer(source)
|
|
3510
4351
|
tokens = lexer.tokenize()
|
|
3511
|
-
parser = CSSLParser(tokens, lexer.source_lines)
|
|
4352
|
+
parser = CSSLParser(tokens, lexer.source_lines, source) # v4.2.0: Pass source for raw extraction
|
|
3512
4353
|
|
|
3513
4354
|
# Auto-detect: if first token is '{', it's a service file
|
|
3514
4355
|
# Otherwise treat as standalone program (whitespace is already filtered by lexer)
|
|
@@ -3522,7 +4363,7 @@ def parse_cssl_program(source: str) -> ASTNode:
|
|
|
3522
4363
|
"""Parse standalone CSSL program (no service wrapper) into an AST"""
|
|
3523
4364
|
lexer = CSSLLexer(source)
|
|
3524
4365
|
tokens = lexer.tokenize()
|
|
3525
|
-
parser = CSSLParser(tokens, lexer.source_lines)
|
|
4366
|
+
parser = CSSLParser(tokens, lexer.source_lines, source) # v4.2.0: Pass source for raw extraction
|
|
3526
4367
|
return parser.parse_program()
|
|
3527
4368
|
|
|
3528
4369
|
|