IncludeCPP 4.2.2__py3-none-any.whl → 4.5.2__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 +104 -115
- includecpp/DOCUMENTATION.md +208 -355
- includecpp/__init__.py +1 -1
- includecpp/__init__.pyi +1 -4
- includecpp/cli/commands.py +1220 -27
- includecpp/core/cpp_api_extensions.pyi +204 -200
- includecpp/core/cssl/CSSL_DOCUMENTATION.md +1505 -1467
- includecpp/core/cssl/__init__.py +317 -0
- includecpp/core/cssl/cpp/build/api.pyd +0 -0
- includecpp/core/cssl/cpp/build/cssl_core.pyi +323 -0
- includecpp/core/cssl/cpp/build/libgcc_s_seh-1.dll +0 -0
- includecpp/core/cssl/cpp/build/libstdc++-6.dll +0 -0
- includecpp/core/cssl/cpp/build/libwinpthread-1.dll +0 -0
- includecpp/core/cssl/cpp/cssl_core.cp +108 -0
- includecpp/core/cssl/cpp/cssl_lexer.hpp +280 -0
- includecpp/core/cssl/cssl_builtins.py +245 -20
- includecpp/core/cssl/cssl_compiler.py +448 -0
- includecpp/core/cssl/cssl_optimizer.py +833 -0
- includecpp/core/cssl/cssl_parser.py +945 -40
- includecpp/core/cssl/cssl_runtime.py +751 -38
- includecpp/core/cssl/cssl_syntax.py +17 -0
- includecpp/core/cssl/cssl_types.py +321 -0
- includecpp/core/cssl_bridge.py +36 -2
- includecpp/generator/parser.cpp +38 -14
- includecpp/vscode/cssl/package.json +15 -0
- includecpp/vscode/cssl/syntaxes/cssl.tmLanguage.json +134 -2
- includecpp-4.5.2.dist-info/METADATA +277 -0
- {includecpp-4.2.2.dist-info → includecpp-4.5.2.dist-info}/RECORD +32 -23
- includecpp-4.2.2.dist-info/METADATA +0 -1008
- {includecpp-4.2.2.dist-info → includecpp-4.5.2.dist-info}/WHEEL +0 -0
- {includecpp-4.2.2.dist-info → includecpp-4.5.2.dist-info}/entry_points.txt +0 -0
- {includecpp-4.2.2.dist-info → includecpp-4.5.2.dist-info}/licenses/LICENSE +0 -0
- {includecpp-4.2.2.dist-info → includecpp-4.5.2.dist-info}/top_level.txt +0 -0
|
@@ -119,11 +119,12 @@ class TokenType(Enum):
|
|
|
119
119
|
|
|
120
120
|
KEYWORDS = {
|
|
121
121
|
# Service structure
|
|
122
|
-
'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',
|
|
123
123
|
# Control flow
|
|
124
124
|
'if', 'else', 'elif', 'while', 'for', 'foreach', 'in', 'range',
|
|
125
125
|
'switch', 'case', 'default', 'break', 'continue', 'return',
|
|
126
126
|
'try', 'catch', 'finally', 'throw',
|
|
127
|
+
'except', 'always', # v4.2.5: param switch keywords
|
|
127
128
|
# Literals
|
|
128
129
|
'True', 'False', 'null', 'None', 'true', 'false',
|
|
129
130
|
# Logical operators
|
|
@@ -141,6 +142,7 @@ KEYWORDS = {
|
|
|
141
142
|
'datastruct', # Universal container (lazy declarator)
|
|
142
143
|
'dataspace', # SQL/data storage container
|
|
143
144
|
'shuffled', # Unorganized fast storage (multiple returns)
|
|
145
|
+
'bytearrayed', # Function-to-byte mapping with pattern matching (v4.2.5)
|
|
144
146
|
'iterator', # Advanced iterator with tasks
|
|
145
147
|
'combo', # Filter/search spaces
|
|
146
148
|
'structure', # Advanced C++/Py Class
|
|
@@ -155,6 +157,8 @@ KEYWORDS = {
|
|
|
155
157
|
'sqlbased', # SQL-based function
|
|
156
158
|
'public', # Explicitly public (default)
|
|
157
159
|
'static', # Static method/function
|
|
160
|
+
'embedded', # Immediate &target replacement at registration (v4.2.5)
|
|
161
|
+
'native', # Force C++ execution (no Python fallback)
|
|
158
162
|
# CSSL Include Keywords
|
|
159
163
|
'include', 'get',
|
|
160
164
|
# Multi-language support (v4.1.0)
|
|
@@ -164,7 +168,8 @@ KEYWORDS = {
|
|
|
164
168
|
# Function modifiers that can appear in any order before function name
|
|
165
169
|
FUNCTION_MODIFIERS = {
|
|
166
170
|
'undefined', 'open', 'meta', 'super', 'closed', 'private', 'virtual',
|
|
167
|
-
'sqlbased', 'const', 'public', 'static', 'global', 'shuffled'
|
|
171
|
+
'sqlbased', 'const', 'public', 'static', 'global', 'shuffled', 'embedded',
|
|
172
|
+
'native' # Force C++ execution
|
|
168
173
|
}
|
|
169
174
|
|
|
170
175
|
# Type literals that create empty instances
|
|
@@ -348,13 +353,16 @@ class CSSLLexer:
|
|
|
348
353
|
self._add_token(TokenType.MULTIPLY, '*')
|
|
349
354
|
self._advance()
|
|
350
355
|
elif char == '/':
|
|
351
|
-
# Check
|
|
352
|
-
if self._peek(1)
|
|
356
|
+
# Check for // comment, /* block comment */, or division
|
|
357
|
+
if self._peek(1) == '/':
|
|
358
|
+
# Single-line comment
|
|
359
|
+
self._skip_comment()
|
|
360
|
+
elif self._peek(1) == '*':
|
|
361
|
+
# Block comment /* ... */
|
|
362
|
+
self._skip_block_comment()
|
|
363
|
+
else:
|
|
353
364
|
self._add_token(TokenType.DIVIDE, '/')
|
|
354
365
|
self._advance()
|
|
355
|
-
else:
|
|
356
|
-
# Already handled by // comment check above, but just in case
|
|
357
|
-
self._skip_comment()
|
|
358
366
|
elif char == '<':
|
|
359
367
|
self._read_less_than()
|
|
360
368
|
elif char == '>':
|
|
@@ -399,6 +407,26 @@ class CSSLLexer:
|
|
|
399
407
|
while self.pos < len(self.source) and self.source[self.pos] != '\n':
|
|
400
408
|
self._advance()
|
|
401
409
|
|
|
410
|
+
def _skip_block_comment(self):
|
|
411
|
+
"""Skip block comment /* ... */ including nested comments"""
|
|
412
|
+
self._advance() # skip /
|
|
413
|
+
self._advance() # skip *
|
|
414
|
+
depth = 1
|
|
415
|
+
while self.pos < len(self.source) and depth > 0:
|
|
416
|
+
if self.source[self.pos] == '/' and self._peek(1) == '*':
|
|
417
|
+
depth += 1
|
|
418
|
+
self._advance()
|
|
419
|
+
self._advance()
|
|
420
|
+
elif self.source[self.pos] == '*' and self._peek(1) == '/':
|
|
421
|
+
depth -= 1
|
|
422
|
+
self._advance()
|
|
423
|
+
self._advance()
|
|
424
|
+
else:
|
|
425
|
+
if self.source[self.pos] == '\n':
|
|
426
|
+
self.line += 1
|
|
427
|
+
self.column = 0
|
|
428
|
+
self._advance()
|
|
429
|
+
|
|
402
430
|
def _read_string(self, quote_char: str):
|
|
403
431
|
self._advance()
|
|
404
432
|
start = self.pos
|
|
@@ -453,6 +481,19 @@ class CSSLLexer:
|
|
|
453
481
|
start = self.pos
|
|
454
482
|
if self.source[self.pos] == '-':
|
|
455
483
|
self._advance()
|
|
484
|
+
|
|
485
|
+
# Check for hex number: 0x... or 0X...
|
|
486
|
+
if (self.pos < len(self.source) and self.source[self.pos] == '0' and
|
|
487
|
+
self.pos + 1 < len(self.source) and self.source[self.pos + 1] in 'xX'):
|
|
488
|
+
self._advance() # skip '0'
|
|
489
|
+
self._advance() # skip 'x' or 'X'
|
|
490
|
+
# Read hex digits
|
|
491
|
+
while self.pos < len(self.source) and self.source[self.pos] in '0123456789abcdefABCDEF':
|
|
492
|
+
self._advance()
|
|
493
|
+
value = self.source[start:self.pos]
|
|
494
|
+
self._add_token(TokenType.NUMBER, int(value, 16))
|
|
495
|
+
return
|
|
496
|
+
|
|
456
497
|
while self.pos < len(self.source) and (self.source[self.pos].isdigit() or self.source[self.pos] == '.'):
|
|
457
498
|
self._advance()
|
|
458
499
|
value = self.source[start:self.pos]
|
|
@@ -766,6 +807,41 @@ class CSSLParser:
|
|
|
766
807
|
'list', 'dictionary', 'dict', 'instance', 'map', 'openquote', 'parameter',
|
|
767
808
|
'dynamic', 'datastruct', 'dataspace', 'shuffled', 'iterator', 'combo', 'structure')
|
|
768
809
|
|
|
810
|
+
def _parse_generic_type_content(self) -> str:
|
|
811
|
+
"""Parse generic type content including nested generics.
|
|
812
|
+
|
|
813
|
+
Handles: <int>, <string, dynamic>, <map<string, dynamic>>, etc.
|
|
814
|
+
Returns the full type string including nested generics.
|
|
815
|
+
|
|
816
|
+
Called AFTER consuming the opening '<'.
|
|
817
|
+
"""
|
|
818
|
+
parts = []
|
|
819
|
+
depth = 1 # Already inside first <
|
|
820
|
+
|
|
821
|
+
while depth > 0 and not self._is_at_end():
|
|
822
|
+
if self._check(TokenType.COMPARE_LT):
|
|
823
|
+
parts.append('<')
|
|
824
|
+
depth += 1
|
|
825
|
+
self._advance()
|
|
826
|
+
elif self._check(TokenType.COMPARE_GT):
|
|
827
|
+
depth -= 1
|
|
828
|
+
if depth > 0: # Only add > if not the final closing >
|
|
829
|
+
parts.append('>')
|
|
830
|
+
self._advance()
|
|
831
|
+
elif self._check(TokenType.COMMA):
|
|
832
|
+
parts.append(', ')
|
|
833
|
+
self._advance()
|
|
834
|
+
elif self._check(TokenType.KEYWORD) or self._check(TokenType.IDENTIFIER):
|
|
835
|
+
parts.append(self._advance().value)
|
|
836
|
+
elif self._check(TokenType.STRING):
|
|
837
|
+
# For instance<"name">
|
|
838
|
+
parts.append(f'"{self._advance().value}"')
|
|
839
|
+
else:
|
|
840
|
+
# Skip whitespace/other tokens
|
|
841
|
+
self._advance()
|
|
842
|
+
|
|
843
|
+
return ''.join(parts)
|
|
844
|
+
|
|
769
845
|
def _looks_like_function_declaration(self) -> bool:
|
|
770
846
|
"""Check if current position looks like a C-style function declaration.
|
|
771
847
|
|
|
@@ -900,7 +976,7 @@ class CSSLParser:
|
|
|
900
976
|
self.pos = saved_pos
|
|
901
977
|
return False
|
|
902
978
|
|
|
903
|
-
def _parse_typed_function(self, is_global: bool = False) -> ASTNode:
|
|
979
|
+
def _parse_typed_function(self, is_global: bool = False, is_embedded: bool = False) -> ASTNode:
|
|
904
980
|
"""Parse C-style typed function declaration with flexible modifier ordering.
|
|
905
981
|
|
|
906
982
|
Supports any order of modifiers, types, non-null (*), and global (@):
|
|
@@ -930,6 +1006,8 @@ class CSSLParser:
|
|
|
930
1006
|
non_null = False
|
|
931
1007
|
exclude_type = None
|
|
932
1008
|
is_const = False
|
|
1009
|
+
# v4.2.5: embedded = immediate &target replacement (can come from param or modifier)
|
|
1010
|
+
_is_embedded = is_embedded
|
|
933
1011
|
|
|
934
1012
|
# Phase 1: Collect all modifiers, type, non-null, and global indicators
|
|
935
1013
|
# These can appear in any order before the function name
|
|
@@ -944,6 +1022,9 @@ class CSSLParser:
|
|
|
944
1022
|
elif mod == 'const':
|
|
945
1023
|
is_const = True
|
|
946
1024
|
modifiers.append(mod)
|
|
1025
|
+
elif mod == 'embedded':
|
|
1026
|
+
_is_embedded = True
|
|
1027
|
+
modifiers.append(mod)
|
|
947
1028
|
else:
|
|
948
1029
|
modifiers.append(mod)
|
|
949
1030
|
continue
|
|
@@ -1183,6 +1264,7 @@ class CSSLParser:
|
|
|
1183
1264
|
'name': name,
|
|
1184
1265
|
'is_global': is_global,
|
|
1185
1266
|
'is_const': is_const,
|
|
1267
|
+
'is_embedded': _is_embedded, # v4.2.5: immediate &target replacement
|
|
1186
1268
|
'params': params,
|
|
1187
1269
|
'return_type': return_type,
|
|
1188
1270
|
'generic_type': generic_type,
|
|
@@ -1200,7 +1282,8 @@ class CSSLParser:
|
|
|
1200
1282
|
'append_mode': append_mode,
|
|
1201
1283
|
'append_ref_class': append_ref_class,
|
|
1202
1284
|
'append_ref_member': append_ref_member,
|
|
1203
|
-
|
|
1285
|
+
# v4.3.2: Also disable strict return type enforcement for 'shuffled' modifier (returns tuple)
|
|
1286
|
+
'enforce_return_type': return_type is not None and 'meta' not in modifiers and 'shuffled' not in modifiers and return_type != 'shuffled'
|
|
1204
1287
|
}, children=[])
|
|
1205
1288
|
|
|
1206
1289
|
self._expect(TokenType.BLOCK_START)
|
|
@@ -1280,19 +1363,16 @@ class CSSLParser:
|
|
|
1280
1363
|
|
|
1281
1364
|
The * prefix indicates a non-nullable variable (can never be None/null).
|
|
1282
1365
|
Example: vector<dynamic> *MyVector - can never contain None values.
|
|
1366
|
+
Supports nested generics: datastruct<map<string, dynamic>> zipped;
|
|
1283
1367
|
"""
|
|
1284
1368
|
# Get type name
|
|
1285
1369
|
type_name = self._advance().value # Consume type keyword
|
|
1286
1370
|
|
|
1287
|
-
# Check for generic type <T> or instance<"name">
|
|
1371
|
+
# Check for generic type <T> or instance<"name"> or nested <map<K,V>>
|
|
1288
1372
|
element_type = None
|
|
1289
1373
|
if self._match(TokenType.COMPARE_LT):
|
|
1290
|
-
#
|
|
1291
|
-
|
|
1292
|
-
element_type = self._advance().value
|
|
1293
|
-
elif self._check(TokenType.KEYWORD) or self._check(TokenType.IDENTIFIER):
|
|
1294
|
-
element_type = self._advance().value
|
|
1295
|
-
self._expect(TokenType.COMPARE_GT)
|
|
1374
|
+
# Use helper to parse nested generic content
|
|
1375
|
+
element_type = self._parse_generic_type_content()
|
|
1296
1376
|
|
|
1297
1377
|
# Check for * prefix (non-nullable indicator)
|
|
1298
1378
|
non_null = False
|
|
@@ -1337,8 +1417,37 @@ class CSSLParser:
|
|
|
1337
1417
|
root.children.append(self._parse_struct())
|
|
1338
1418
|
elif self._match_keyword('class'):
|
|
1339
1419
|
root.children.append(self._parse_class())
|
|
1420
|
+
elif self._match_keyword('enum'):
|
|
1421
|
+
root.children.append(self._parse_enum())
|
|
1422
|
+
elif self._match_keyword('bytearrayed'):
|
|
1423
|
+
root.children.append(self._parse_bytearrayed())
|
|
1340
1424
|
elif self._match_keyword('define'):
|
|
1341
1425
|
root.children.append(self._parse_define())
|
|
1426
|
+
# v4.5.1: Handle function modifiers (private, const, static, etc.) before define
|
|
1427
|
+
elif self._check(TokenType.KEYWORD) and self._is_function_modifier(self._current().value):
|
|
1428
|
+
modifiers = []
|
|
1429
|
+
is_embedded = False
|
|
1430
|
+
is_global = False
|
|
1431
|
+
has_open_params = False
|
|
1432
|
+
while self._check(TokenType.KEYWORD) and self._is_function_modifier(self._current().value):
|
|
1433
|
+
mod = self._advance().value
|
|
1434
|
+
modifiers.append(mod)
|
|
1435
|
+
if mod == 'embedded':
|
|
1436
|
+
is_embedded = True
|
|
1437
|
+
elif mod == 'global':
|
|
1438
|
+
is_global = True
|
|
1439
|
+
elif mod == 'open':
|
|
1440
|
+
has_open_params = True
|
|
1441
|
+
# Now check what follows
|
|
1442
|
+
if self._match_keyword('define'):
|
|
1443
|
+
root.children.append(self._parse_define(
|
|
1444
|
+
is_global=is_global, is_embedded=is_embedded,
|
|
1445
|
+
has_open_params=has_open_params, modifiers=modifiers
|
|
1446
|
+
))
|
|
1447
|
+
elif self._looks_like_function_declaration():
|
|
1448
|
+
root.children.append(self._parse_typed_function(modifiers=modifiers))
|
|
1449
|
+
else:
|
|
1450
|
+
self.error(f"Expected 'define' or function declaration after modifiers: {modifiers}")
|
|
1342
1451
|
# Check for C-style typed function declarations
|
|
1343
1452
|
elif self._looks_like_function_declaration():
|
|
1344
1453
|
root.children.append(self._parse_typed_function())
|
|
@@ -1359,6 +1468,45 @@ class CSSLParser:
|
|
|
1359
1468
|
elif self._match_keyword('package-includes'):
|
|
1360
1469
|
root.children.append(self._parse_package_includes())
|
|
1361
1470
|
# Handle global declarations
|
|
1471
|
+
# v4.2.5: Handle 'embedded' keyword for immediate &target replacement
|
|
1472
|
+
# v4.3.2: Extended to support enums: embedded EnumName &TargetEnum { ... }
|
|
1473
|
+
# v4.3.2: Support 'open embedded define' syntax
|
|
1474
|
+
elif self._match_keyword('open'):
|
|
1475
|
+
# open can be followed by embedded or define
|
|
1476
|
+
if self._match_keyword('embedded'):
|
|
1477
|
+
if self._match_keyword('define'):
|
|
1478
|
+
root.children.append(self._parse_define(is_embedded=True, has_open_params=True))
|
|
1479
|
+
elif self._looks_like_function_declaration():
|
|
1480
|
+
root.children.append(self._parse_typed_function(is_embedded=True, has_open_params=True))
|
|
1481
|
+
else:
|
|
1482
|
+
self.error("Expected 'define' or function declaration after 'open embedded'")
|
|
1483
|
+
elif self._match_keyword('define'):
|
|
1484
|
+
root.children.append(self._parse_define(has_open_params=True))
|
|
1485
|
+
else:
|
|
1486
|
+
self.error("Expected 'embedded' or 'define' after 'open'")
|
|
1487
|
+
elif self._match_keyword('embedded'):
|
|
1488
|
+
# v4.3.2: Support both 'embedded open define' and 'embedded define'
|
|
1489
|
+
if self._match_keyword('open'):
|
|
1490
|
+
# embedded open define ...
|
|
1491
|
+
if self._match_keyword('define'):
|
|
1492
|
+
root.children.append(self._parse_define(is_embedded=True, has_open_params=True))
|
|
1493
|
+
elif self._looks_like_function_declaration():
|
|
1494
|
+
root.children.append(self._parse_typed_function(is_embedded=True, has_open_params=True))
|
|
1495
|
+
else:
|
|
1496
|
+
self.error("Expected 'define' or function declaration after 'embedded open'")
|
|
1497
|
+
elif self._match_keyword('class'):
|
|
1498
|
+
root.children.append(self._parse_class(is_embedded=True))
|
|
1499
|
+
elif self._match_keyword('enum'):
|
|
1500
|
+
root.children.append(self._parse_enum(is_embedded=True))
|
|
1501
|
+
elif self._match_keyword('define'):
|
|
1502
|
+
root.children.append(self._parse_define(is_embedded=True))
|
|
1503
|
+
elif self._looks_like_function_declaration():
|
|
1504
|
+
root.children.append(self._parse_typed_function(is_embedded=True))
|
|
1505
|
+
elif self._check(TokenType.IDENTIFIER):
|
|
1506
|
+
# embedded Name &Target { ... } - could be enum override
|
|
1507
|
+
root.children.append(self._parse_embedded_override())
|
|
1508
|
+
else:
|
|
1509
|
+
self.error("Expected 'class', 'enum', 'open', 'define', function declaration, or identifier after 'embedded'")
|
|
1362
1510
|
elif self._match_keyword('global'):
|
|
1363
1511
|
# Check if followed by class or define (global class/function)
|
|
1364
1512
|
if self._match_keyword('class'):
|
|
@@ -1389,6 +1537,11 @@ class CSSLParser:
|
|
|
1389
1537
|
root.children.append(self._parse_for())
|
|
1390
1538
|
elif self._match_keyword('foreach'):
|
|
1391
1539
|
root.children.append(self._parse_foreach())
|
|
1540
|
+
# v4.3.2: Add try/catch and switch at top-level
|
|
1541
|
+
elif self._match_keyword('try'):
|
|
1542
|
+
root.children.append(self._parse_try())
|
|
1543
|
+
elif self._match_keyword('switch'):
|
|
1544
|
+
root.children.append(self._parse_switch())
|
|
1392
1545
|
# Handle statements - keywords like 'instance', 'list', 'map' can be variable names
|
|
1393
1546
|
# v4.2.1: Added LANG_INSTANCE_REF for lang$instance statements (js$GameData.score = 1337)
|
|
1394
1547
|
elif (self._check(TokenType.IDENTIFIER) or self._check(TokenType.AT) or
|
|
@@ -1622,7 +1775,368 @@ class CSSLParser:
|
|
|
1622
1775
|
self._expect(TokenType.BLOCK_END)
|
|
1623
1776
|
return node
|
|
1624
1777
|
|
|
1625
|
-
def
|
|
1778
|
+
def _parse_enum(self, is_embedded: bool = False) -> ASTNode:
|
|
1779
|
+
"""Parse enum declaration.
|
|
1780
|
+
|
|
1781
|
+
Syntax:
|
|
1782
|
+
enum EnumName {
|
|
1783
|
+
VALUE1, // Auto value 0
|
|
1784
|
+
VALUE2, // Auto value 1
|
|
1785
|
+
VALUE3 = 10, // Explicit value 10
|
|
1786
|
+
VALUE4 // Auto value 11
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
embedded enum NewEnum &OldEnum { ... } // Replace OldEnum with NewEnum values
|
|
1790
|
+
|
|
1791
|
+
Access values via EnumName::VALUE1
|
|
1792
|
+
"""
|
|
1793
|
+
enum_name = self._advance().value
|
|
1794
|
+
|
|
1795
|
+
# v4.3.2: Check for &Target reference (enum replacement)
|
|
1796
|
+
replace_target = None
|
|
1797
|
+
if self._match(TokenType.AMPERSAND):
|
|
1798
|
+
if self._check(TokenType.IDENTIFIER):
|
|
1799
|
+
replace_target = self._advance().value
|
|
1800
|
+
elif self._check(TokenType.AT):
|
|
1801
|
+
self._advance()
|
|
1802
|
+
if self._check(TokenType.IDENTIFIER):
|
|
1803
|
+
replace_target = '@' + self._advance().value
|
|
1804
|
+
|
|
1805
|
+
self._expect(TokenType.BLOCK_START)
|
|
1806
|
+
|
|
1807
|
+
members = []
|
|
1808
|
+
current_value = 0
|
|
1809
|
+
|
|
1810
|
+
while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
|
|
1811
|
+
# Skip newlines/whitespace between enum values
|
|
1812
|
+
if self._check(TokenType.NEWLINE):
|
|
1813
|
+
self._advance()
|
|
1814
|
+
continue
|
|
1815
|
+
|
|
1816
|
+
# Get member name
|
|
1817
|
+
if self._check(TokenType.IDENTIFIER):
|
|
1818
|
+
member_name = self._advance().value
|
|
1819
|
+
|
|
1820
|
+
# Check for explicit value: VALUE = 10
|
|
1821
|
+
if self._match(TokenType.EQUALS):
|
|
1822
|
+
value_node = self._parse_expression()
|
|
1823
|
+
if isinstance(value_node, ASTNode) and value_node.type == 'literal':
|
|
1824
|
+
val = value_node.value
|
|
1825
|
+
if isinstance(val, dict) and 'value' in val:
|
|
1826
|
+
current_value = val['value']
|
|
1827
|
+
else:
|
|
1828
|
+
current_value = val
|
|
1829
|
+
else:
|
|
1830
|
+
current_value = value_node
|
|
1831
|
+
|
|
1832
|
+
members.append({'name': member_name, 'value': current_value})
|
|
1833
|
+
current_value = current_value + 1 if isinstance(current_value, int) else current_value
|
|
1834
|
+
|
|
1835
|
+
# Skip comma if present
|
|
1836
|
+
self._match(TokenType.COMMA)
|
|
1837
|
+
else:
|
|
1838
|
+
self._advance()
|
|
1839
|
+
|
|
1840
|
+
self._expect(TokenType.BLOCK_END)
|
|
1841
|
+
|
|
1842
|
+
return ASTNode('enum', value={
|
|
1843
|
+
'name': enum_name,
|
|
1844
|
+
'members': members,
|
|
1845
|
+
'is_embedded': is_embedded,
|
|
1846
|
+
'replace_target': replace_target
|
|
1847
|
+
})
|
|
1848
|
+
|
|
1849
|
+
def _parse_embedded_override(self) -> ASTNode:
|
|
1850
|
+
"""Parse embedded override for enums/structs without explicit type keyword.
|
|
1851
|
+
|
|
1852
|
+
Syntax:
|
|
1853
|
+
embedded __NewName &OldEnum { ... } // Replace OldEnum
|
|
1854
|
+
embedded __NewName &OldEnum ++ { ... } // Add to OldEnum
|
|
1855
|
+
embedded __NewName &OldEnum -- { ... } // Remove from OldEnum
|
|
1856
|
+
|
|
1857
|
+
This creates a new definition that modifies the target.
|
|
1858
|
+
"""
|
|
1859
|
+
# Get the new name
|
|
1860
|
+
new_name = self._advance().value
|
|
1861
|
+
|
|
1862
|
+
# Expect &Target
|
|
1863
|
+
if not self._match(TokenType.AMPERSAND):
|
|
1864
|
+
self.error("Expected '&' followed by target name after embedded identifier")
|
|
1865
|
+
|
|
1866
|
+
# Get target name
|
|
1867
|
+
target_name = None
|
|
1868
|
+
if self._check(TokenType.IDENTIFIER):
|
|
1869
|
+
target_name = self._advance().value
|
|
1870
|
+
elif self._check(TokenType.AT):
|
|
1871
|
+
self._advance()
|
|
1872
|
+
if self._check(TokenType.IDENTIFIER):
|
|
1873
|
+
target_name = '@' + self._advance().value
|
|
1874
|
+
else:
|
|
1875
|
+
self.error("Expected target name after '&'")
|
|
1876
|
+
|
|
1877
|
+
# Check for mode modifier: ++ (add) or -- (remove)
|
|
1878
|
+
mode = 'replace'
|
|
1879
|
+
if self._match(TokenType.PLUS_PLUS):
|
|
1880
|
+
mode = 'add'
|
|
1881
|
+
elif self._match(TokenType.MINUS_MINUS):
|
|
1882
|
+
mode = 'remove'
|
|
1883
|
+
|
|
1884
|
+
self._expect(TokenType.BLOCK_START)
|
|
1885
|
+
|
|
1886
|
+
# Parse members (enum-style: NAME = value, NAME, etc.)
|
|
1887
|
+
members = []
|
|
1888
|
+
current_value = 0
|
|
1889
|
+
|
|
1890
|
+
while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
|
|
1891
|
+
if self._check(TokenType.NEWLINE):
|
|
1892
|
+
self._advance()
|
|
1893
|
+
continue
|
|
1894
|
+
|
|
1895
|
+
if self._check(TokenType.IDENTIFIER):
|
|
1896
|
+
member_name = self._advance().value
|
|
1897
|
+
|
|
1898
|
+
# Check for explicit value
|
|
1899
|
+
if self._match(TokenType.EQUALS):
|
|
1900
|
+
value_node = self._parse_expression()
|
|
1901
|
+
if isinstance(value_node, ASTNode) and value_node.type == 'literal':
|
|
1902
|
+
val = value_node.value
|
|
1903
|
+
if isinstance(val, dict) and 'value' in val:
|
|
1904
|
+
current_value = val['value']
|
|
1905
|
+
else:
|
|
1906
|
+
current_value = val
|
|
1907
|
+
else:
|
|
1908
|
+
current_value = value_node
|
|
1909
|
+
|
|
1910
|
+
members.append({'name': member_name, 'value': current_value})
|
|
1911
|
+
if isinstance(current_value, int):
|
|
1912
|
+
current_value = current_value + 1
|
|
1913
|
+
|
|
1914
|
+
self._match(TokenType.COMMA)
|
|
1915
|
+
else:
|
|
1916
|
+
self._advance()
|
|
1917
|
+
|
|
1918
|
+
self._expect(TokenType.BLOCK_END)
|
|
1919
|
+
|
|
1920
|
+
return ASTNode('enum', value={
|
|
1921
|
+
'name': new_name,
|
|
1922
|
+
'members': members,
|
|
1923
|
+
'is_embedded': True,
|
|
1924
|
+
'replace_target': target_name,
|
|
1925
|
+
'mode': mode # 'replace', 'add', or 'remove'
|
|
1926
|
+
})
|
|
1927
|
+
|
|
1928
|
+
def _parse_bytearrayed(self) -> ASTNode:
|
|
1929
|
+
"""Parse bytearrayed declaration - function-to-byte mapping with pattern matching.
|
|
1930
|
+
|
|
1931
|
+
Syntax:
|
|
1932
|
+
bytearrayed MyBytes {
|
|
1933
|
+
&func1; // Position 0x0
|
|
1934
|
+
&func2; // Position 0x1
|
|
1935
|
+
&func3; // Position 0x2
|
|
1936
|
+
case {0, 1, 0} { // Pattern match on return values
|
|
1937
|
+
// Execute if func1=0, func2=1, func3=0
|
|
1938
|
+
}
|
|
1939
|
+
case {1, _, _} { // Wildcards with _
|
|
1940
|
+
// Execute if func1=1, others any value
|
|
1941
|
+
}
|
|
1942
|
+
default {
|
|
1943
|
+
// Execute if no case matches
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
Access:
|
|
1948
|
+
MyBytes() // Execute pattern matching
|
|
1949
|
+
MyBytes["0x0"] // Get value at position 0
|
|
1950
|
+
MyBytes[0] // Get value at position 0
|
|
1951
|
+
"""
|
|
1952
|
+
bytearrayed_name = self._advance().value
|
|
1953
|
+
self._expect(TokenType.BLOCK_START)
|
|
1954
|
+
|
|
1955
|
+
func_refs = [] # List of function references at each byte position
|
|
1956
|
+
cases = [] # List of case blocks with patterns
|
|
1957
|
+
default_block = None # Default block if no case matches
|
|
1958
|
+
|
|
1959
|
+
while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
|
|
1960
|
+
# Skip newlines
|
|
1961
|
+
if self._check(TokenType.NEWLINE):
|
|
1962
|
+
self._advance()
|
|
1963
|
+
continue
|
|
1964
|
+
|
|
1965
|
+
# Parse case block
|
|
1966
|
+
if self._match_keyword('case'):
|
|
1967
|
+
pattern = []
|
|
1968
|
+
self._expect(TokenType.BLOCK_START)
|
|
1969
|
+
|
|
1970
|
+
# Parse pattern: {value, value, value, ...}
|
|
1971
|
+
while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
|
|
1972
|
+
if self._check(TokenType.COMMA):
|
|
1973
|
+
self._advance()
|
|
1974
|
+
continue
|
|
1975
|
+
if self._check(TokenType.NEWLINE):
|
|
1976
|
+
self._advance()
|
|
1977
|
+
continue
|
|
1978
|
+
|
|
1979
|
+
# Parse pattern element
|
|
1980
|
+
if self._check(TokenType.IDENTIFIER) and self._current().value == '_':
|
|
1981
|
+
# Wildcard - matches any value
|
|
1982
|
+
pattern.append({'type': 'wildcard'})
|
|
1983
|
+
self._advance()
|
|
1984
|
+
elif self._check(TokenType.BRACKET_START):
|
|
1985
|
+
# v4.3.2: List pattern - matches a list value: ["read", "write"]
|
|
1986
|
+
self._advance() # consume [
|
|
1987
|
+
list_elements = []
|
|
1988
|
+
while not self._check(TokenType.BRACKET_END) and not self._is_at_end():
|
|
1989
|
+
if self._check(TokenType.COMMA):
|
|
1990
|
+
self._advance()
|
|
1991
|
+
continue
|
|
1992
|
+
if self._check(TokenType.STRING):
|
|
1993
|
+
list_elements.append(self._advance().value)
|
|
1994
|
+
elif self._check(TokenType.NUMBER):
|
|
1995
|
+
list_elements.append(self._advance().value)
|
|
1996
|
+
elif self._check(TokenType.KEYWORD):
|
|
1997
|
+
kw = self._current().value
|
|
1998
|
+
if kw in ('true', 'True'):
|
|
1999
|
+
list_elements.append(True)
|
|
2000
|
+
elif kw in ('false', 'False'):
|
|
2001
|
+
list_elements.append(False)
|
|
2002
|
+
self._advance()
|
|
2003
|
+
else:
|
|
2004
|
+
self._advance()
|
|
2005
|
+
self._expect(TokenType.BRACKET_END)
|
|
2006
|
+
pattern.append({'type': 'list', 'values': list_elements})
|
|
2007
|
+
elif self._check(TokenType.NUMBER):
|
|
2008
|
+
# Check for hex format like 0x28
|
|
2009
|
+
token = self._current()
|
|
2010
|
+
value = token.value
|
|
2011
|
+
if isinstance(value, dict) and 'value' in value:
|
|
2012
|
+
value = value['value']
|
|
2013
|
+
# Check for position=value syntax: 0x28="Gut"
|
|
2014
|
+
self._advance()
|
|
2015
|
+
if self._match(TokenType.EQUALS):
|
|
2016
|
+
match_value = self._parse_expression()
|
|
2017
|
+
pattern.append({'type': 'indexed', 'index': value, 'value': match_value})
|
|
2018
|
+
else:
|
|
2019
|
+
pattern.append({'type': 'value', 'value': value})
|
|
2020
|
+
elif self._check(TokenType.STRING):
|
|
2021
|
+
value = self._advance().value
|
|
2022
|
+
pattern.append({'type': 'value', 'value': value})
|
|
2023
|
+
elif self._check(TokenType.BOOLEAN):
|
|
2024
|
+
# v4.3.2: Handle true/false which are tokenized as BOOLEAN
|
|
2025
|
+
value = self._advance().value
|
|
2026
|
+
pattern.append({'type': 'value', 'value': value})
|
|
2027
|
+
elif self._check(TokenType.KEYWORD):
|
|
2028
|
+
kw = self._current().value
|
|
2029
|
+
if kw in ('true', 'True'):
|
|
2030
|
+
pattern.append({'type': 'value', 'value': True})
|
|
2031
|
+
self._advance()
|
|
2032
|
+
elif kw in ('false', 'False'):
|
|
2033
|
+
pattern.append({'type': 'value', 'value': False})
|
|
2034
|
+
self._advance()
|
|
2035
|
+
elif kw in TYPE_GENERICS or kw in ('int', 'string', 'float', 'bool', 'dynamic'):
|
|
2036
|
+
# Type pattern: vector<string>, int, etc.
|
|
2037
|
+
type_name = self._advance().value
|
|
2038
|
+
if self._check(TokenType.COMPARE_LT):
|
|
2039
|
+
# Generic type
|
|
2040
|
+
self._advance()
|
|
2041
|
+
inner = self._parse_generic_type_content()
|
|
2042
|
+
type_name = f"{type_name}<{inner}>"
|
|
2043
|
+
pattern.append({'type': 'type_match', 'type_name': type_name})
|
|
2044
|
+
else:
|
|
2045
|
+
# Skip unknown keywords
|
|
2046
|
+
self._advance()
|
|
2047
|
+
elif self._check(TokenType.IDENTIFIER):
|
|
2048
|
+
# Could be a variable reference or type
|
|
2049
|
+
ident = self._advance().value
|
|
2050
|
+
# Check for indexed syntax: 2x35="value"
|
|
2051
|
+
if self._check(TokenType.IDENTIFIER) and ident.isdigit():
|
|
2052
|
+
# Pattern like 2x35 (position 2, repeat 35)
|
|
2053
|
+
second = self._advance().value
|
|
2054
|
+
if self._match(TokenType.EQUALS):
|
|
2055
|
+
match_value = self._parse_expression()
|
|
2056
|
+
pattern.append({'type': 'repeat', 'pos': int(ident), 'repeat': second, 'value': match_value})
|
|
2057
|
+
else:
|
|
2058
|
+
pattern.append({'type': 'repeat', 'pos': int(ident), 'repeat': second, 'value': None})
|
|
2059
|
+
else:
|
|
2060
|
+
pattern.append({'type': 'variable', 'name': ident})
|
|
2061
|
+
else:
|
|
2062
|
+
self._advance()
|
|
2063
|
+
|
|
2064
|
+
self._expect(TokenType.BLOCK_END)
|
|
2065
|
+
|
|
2066
|
+
# Parse case body - supports both:
|
|
2067
|
+
# case {pattern}: statement; (colon syntax)
|
|
2068
|
+
# case {pattern} { statements } (block syntax)
|
|
2069
|
+
body_children = []
|
|
2070
|
+
if self._match(TokenType.COLON):
|
|
2071
|
+
# Colon syntax: single statement or until next case/default/}
|
|
2072
|
+
stmt = self._parse_statement()
|
|
2073
|
+
if stmt:
|
|
2074
|
+
body_children.append(stmt)
|
|
2075
|
+
elif self._check(TokenType.BLOCK_START):
|
|
2076
|
+
self._advance() # consume {
|
|
2077
|
+
while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
|
|
2078
|
+
stmt = self._parse_statement()
|
|
2079
|
+
if stmt:
|
|
2080
|
+
body_children.append(stmt)
|
|
2081
|
+
self._expect(TokenType.BLOCK_END)
|
|
2082
|
+
|
|
2083
|
+
cases.append({'pattern': pattern, 'body': body_children})
|
|
2084
|
+
|
|
2085
|
+
# Parse default block - supports both:
|
|
2086
|
+
# default: statement; (colon syntax)
|
|
2087
|
+
# default { statements } (block syntax)
|
|
2088
|
+
elif self._match_keyword('default'):
|
|
2089
|
+
body_children = []
|
|
2090
|
+
if self._match(TokenType.COLON):
|
|
2091
|
+
# Colon syntax: single statement
|
|
2092
|
+
stmt = self._parse_statement()
|
|
2093
|
+
if stmt:
|
|
2094
|
+
body_children.append(stmt)
|
|
2095
|
+
elif self._check(TokenType.BLOCK_START):
|
|
2096
|
+
self._advance() # consume {
|
|
2097
|
+
while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
|
|
2098
|
+
stmt = self._parse_statement()
|
|
2099
|
+
if stmt:
|
|
2100
|
+
body_children.append(stmt)
|
|
2101
|
+
self._expect(TokenType.BLOCK_END)
|
|
2102
|
+
default_block = body_children
|
|
2103
|
+
|
|
2104
|
+
# Parse function reference: &funcName; or &funcName(arg1, arg2);
|
|
2105
|
+
elif self._check(TokenType.AMPERSAND):
|
|
2106
|
+
self._advance() # consume &
|
|
2107
|
+
if self._check(TokenType.IDENTIFIER):
|
|
2108
|
+
func_name = self._advance().value
|
|
2109
|
+
# v4.3.2: Support function references with parameters: &testfunc(1, 2)
|
|
2110
|
+
func_args = []
|
|
2111
|
+
if self._check(TokenType.PAREN_START):
|
|
2112
|
+
self._advance() # consume (
|
|
2113
|
+
while not self._check(TokenType.PAREN_END) and not self._is_at_end():
|
|
2114
|
+
arg = self._parse_expression()
|
|
2115
|
+
func_args.append(arg)
|
|
2116
|
+
if not self._check(TokenType.PAREN_END):
|
|
2117
|
+
self._expect(TokenType.COMMA)
|
|
2118
|
+
self._expect(TokenType.PAREN_END)
|
|
2119
|
+
func_refs.append({
|
|
2120
|
+
'position': len(func_refs),
|
|
2121
|
+
'hex_pos': f"0x{len(func_refs):x}",
|
|
2122
|
+
'func_ref': func_name,
|
|
2123
|
+
'args': func_args # v4.3.2: Store arguments for simulation
|
|
2124
|
+
})
|
|
2125
|
+
self._match(TokenType.SEMICOLON)
|
|
2126
|
+
|
|
2127
|
+
else:
|
|
2128
|
+
self._advance()
|
|
2129
|
+
|
|
2130
|
+
self._expect(TokenType.BLOCK_END)
|
|
2131
|
+
|
|
2132
|
+
return ASTNode('bytearrayed', value={
|
|
2133
|
+
'name': bytearrayed_name,
|
|
2134
|
+
'func_refs': func_refs,
|
|
2135
|
+
'cases': cases,
|
|
2136
|
+
'default': default_block
|
|
2137
|
+
})
|
|
2138
|
+
|
|
2139
|
+
def _parse_class(self, is_global: bool = False, is_embedded: bool = False) -> ASTNode:
|
|
1626
2140
|
"""Parse class declaration with members and methods.
|
|
1627
2141
|
|
|
1628
2142
|
Syntax:
|
|
@@ -1630,6 +2144,7 @@ class CSSLParser:
|
|
|
1630
2144
|
global class ClassName { ... } // Global class
|
|
1631
2145
|
class @ClassName { ... } // Global class (alternative)
|
|
1632
2146
|
class *ClassName { ... } // Non-null class
|
|
2147
|
+
embedded class ClassName &$Target { ... } // Immediate replacement (v4.2.5)
|
|
1633
2148
|
|
|
1634
2149
|
Non-null class (all methods return non-null):
|
|
1635
2150
|
class *MyClass { ... }
|
|
@@ -1652,6 +2167,28 @@ class CSSLParser:
|
|
|
1652
2167
|
class_params = self._parse_parameter_list()
|
|
1653
2168
|
self._expect(TokenType.PAREN_END)
|
|
1654
2169
|
|
|
2170
|
+
# v4.2.5: Check for &target reference for class replacement
|
|
2171
|
+
# Syntax: embedded class BetterGame() &$Game { ... }
|
|
2172
|
+
# class NewClass &OldClass { ... }
|
|
2173
|
+
append_ref_class = None
|
|
2174
|
+
append_ref_member = None
|
|
2175
|
+
if self._match(TokenType.AMPERSAND):
|
|
2176
|
+
if self._check(TokenType.IDENTIFIER):
|
|
2177
|
+
append_ref_class = self._advance().value
|
|
2178
|
+
elif self._check(TokenType.AT):
|
|
2179
|
+
self._advance()
|
|
2180
|
+
if self._check(TokenType.IDENTIFIER):
|
|
2181
|
+
append_ref_class = '@' + self._advance().value
|
|
2182
|
+
elif self._check(TokenType.SHARED_REF):
|
|
2183
|
+
append_ref_class = f'${self._advance().value}'
|
|
2184
|
+
else:
|
|
2185
|
+
raise CSSLSyntaxError("Expected class name after '&' in class reference")
|
|
2186
|
+
|
|
2187
|
+
# Check for ::member or .member (for targeting specific members)
|
|
2188
|
+
if self._match(TokenType.DOUBLE_COLON) or self._match(TokenType.DOT):
|
|
2189
|
+
if self._check(TokenType.IDENTIFIER) or self._check(TokenType.KEYWORD):
|
|
2190
|
+
append_ref_member = self._advance().value
|
|
2191
|
+
|
|
1655
2192
|
# Check for inheritance and overwrites:
|
|
1656
2193
|
# class Child : extends Parent { ... }
|
|
1657
2194
|
# class Child : extends $PythonObject { ... }
|
|
@@ -1725,6 +2262,7 @@ class CSSLParser:
|
|
|
1725
2262
|
node = ASTNode('class', value={
|
|
1726
2263
|
'name': class_name,
|
|
1727
2264
|
'is_global': is_global,
|
|
2265
|
+
'is_embedded': is_embedded, # v4.2.5: immediate &target replacement
|
|
1728
2266
|
'non_null': non_null,
|
|
1729
2267
|
'class_params': class_params,
|
|
1730
2268
|
'extends': extends_class,
|
|
@@ -1734,7 +2272,9 @@ class CSSLParser:
|
|
|
1734
2272
|
'overwrites': overwrites_class,
|
|
1735
2273
|
'overwrites_is_python': overwrites_is_python,
|
|
1736
2274
|
'supports_language': supports_language, # v4.1.0
|
|
1737
|
-
'raw_body': raw_body # v4.2.0: Raw body for language transformation
|
|
2275
|
+
'raw_body': raw_body, # v4.2.0: Raw body for language transformation
|
|
2276
|
+
'append_ref_class': append_ref_class, # v4.2.5: &target class reference
|
|
2277
|
+
'append_ref_member': append_ref_member # v4.2.5: &target member reference
|
|
1738
2278
|
}, children=[])
|
|
1739
2279
|
|
|
1740
2280
|
# v4.2.0: If we have raw_body for language transformation, skip regular parsing
|
|
@@ -1978,18 +2518,21 @@ class CSSLParser:
|
|
|
1978
2518
|
|
|
1979
2519
|
return params
|
|
1980
2520
|
|
|
1981
|
-
def _parse_define(self, is_global: bool = False) -> ASTNode:
|
|
2521
|
+
def _parse_define(self, is_global: bool = False, is_embedded: bool = False, has_open_params: bool = False, modifiers: list = None) -> ASTNode:
|
|
1982
2522
|
"""Parse define function declaration.
|
|
1983
2523
|
|
|
1984
2524
|
Syntax:
|
|
1985
2525
|
define MyFunc(args) { } // Local function
|
|
1986
2526
|
global define MyFunc(args) { } // Global function
|
|
2527
|
+
private define MyFunc(args) { } // Private function (v4.5.1)
|
|
1987
2528
|
define @MyFunc(args) { } // Global function (alternative)
|
|
1988
2529
|
define *MyFunc(args) { } // Non-null: must never return None
|
|
1989
2530
|
define MyFunc(args) : extends OtherFunc { } // Inherit local vars
|
|
1990
2531
|
define MyFunc(args) : overwrites OtherFunc { } // Replace OtherFunc
|
|
1991
2532
|
define MyFunc(args) : supports python { } // Multi-language syntax
|
|
1992
2533
|
define MyFunc(args) :: extends Parent::Method { } // Method-level inheritance
|
|
2534
|
+
embedded define MyFunc(args) &target { } // Immediate &target replacement
|
|
2535
|
+
open embedded define MyFunc(open Input) &target { } // Open params + embedded
|
|
1993
2536
|
"""
|
|
1994
2537
|
# Check for * prefix (non-null function - must return non-null)
|
|
1995
2538
|
# Also *[type] for type exclusion (must NOT return that type)
|
|
@@ -2208,6 +2751,8 @@ class CSSLParser:
|
|
|
2208
2751
|
node = ASTNode('function', value={
|
|
2209
2752
|
'name': name,
|
|
2210
2753
|
'is_global': is_global,
|
|
2754
|
+
'is_embedded': is_embedded, # v4.2.5: immediate &target replacement (v4.3.2: use param)
|
|
2755
|
+
'has_open_params': has_open_params, # v4.3.2: open embedded define support
|
|
2211
2756
|
'params': params,
|
|
2212
2757
|
'non_null': non_null,
|
|
2213
2758
|
'exclude_type': exclude_type, # *[type] - must NOT return this type
|
|
@@ -2227,7 +2772,9 @@ class CSSLParser:
|
|
|
2227
2772
|
# v4.1.0: Multi-language support
|
|
2228
2773
|
'supports_language': supports_language,
|
|
2229
2774
|
# v4.2.0: Raw body for language transformation
|
|
2230
|
-
'raw_body': raw_body
|
|
2775
|
+
'raw_body': raw_body,
|
|
2776
|
+
# v4.5.1: Function modifiers (private, const, static, etc.)
|
|
2777
|
+
'modifiers': modifiers or []
|
|
2231
2778
|
}, children=children)
|
|
2232
2779
|
|
|
2233
2780
|
return node
|
|
@@ -2251,6 +2798,9 @@ class CSSLParser:
|
|
|
2251
2798
|
elif self._match_keyword('continue'):
|
|
2252
2799
|
self._match(TokenType.SEMICOLON)
|
|
2253
2800
|
return ASTNode('continue')
|
|
2801
|
+
# v4.5.1: Add throw statement parsing
|
|
2802
|
+
elif self._match_keyword('throw'):
|
|
2803
|
+
return self._parse_throw()
|
|
2254
2804
|
elif self._match_keyword('try'):
|
|
2255
2805
|
return self._parse_try()
|
|
2256
2806
|
elif self._match_keyword('await'):
|
|
@@ -2643,6 +3193,44 @@ class CSSLParser:
|
|
|
2643
3193
|
value = self._parse_expression()
|
|
2644
3194
|
self._expect(TokenType.PAREN_END)
|
|
2645
3195
|
|
|
3196
|
+
# v4.2.5: Check if this is a param switch (switch on open params)
|
|
3197
|
+
# Syntax: switch(Params): case name: ... except age: ... always: ... finally: ...
|
|
3198
|
+
# v4.3.2: Detect param_switch by:
|
|
3199
|
+
# 1. Explicit "Params" identifier, OR
|
|
3200
|
+
# 2. Any identifier if case uses param-style conditions (& / not)
|
|
3201
|
+
is_param_switch = (value.type == 'identifier' and value.value == 'Params')
|
|
3202
|
+
|
|
3203
|
+
# v4.3.2: Also check if case syntax uses param-style conditions
|
|
3204
|
+
# Look ahead to see if first case uses & or 'not' operators
|
|
3205
|
+
if not is_param_switch and value.type == 'identifier':
|
|
3206
|
+
saved_pos = self.pos
|
|
3207
|
+
# Skip optional : and {
|
|
3208
|
+
if self._check(TokenType.COLON):
|
|
3209
|
+
self._advance()
|
|
3210
|
+
if self._check(TokenType.BLOCK_START):
|
|
3211
|
+
self._advance()
|
|
3212
|
+
# Check for 'case' keyword
|
|
3213
|
+
if self._check_keyword('case'):
|
|
3214
|
+
self._advance() # skip 'case'
|
|
3215
|
+
# Look for & or 'not' before : (indicates param switch)
|
|
3216
|
+
depth = 0
|
|
3217
|
+
while not self._is_at_end():
|
|
3218
|
+
if self._check(TokenType.COLON) and depth == 0:
|
|
3219
|
+
break
|
|
3220
|
+
if self._check(TokenType.PAREN_START):
|
|
3221
|
+
depth += 1
|
|
3222
|
+
if self._check(TokenType.PAREN_END):
|
|
3223
|
+
depth -= 1
|
|
3224
|
+
if self._check(TokenType.AMPERSAND) or self._check_keyword('not') or self._check(TokenType.NOT):
|
|
3225
|
+
is_param_switch = True
|
|
3226
|
+
break
|
|
3227
|
+
self._advance()
|
|
3228
|
+
self.pos = saved_pos # Restore position
|
|
3229
|
+
|
|
3230
|
+
if is_param_switch:
|
|
3231
|
+
return self._parse_param_switch(value)
|
|
3232
|
+
|
|
3233
|
+
# Regular switch
|
|
2646
3234
|
node = ASTNode('switch', value={'value': value}, children=[])
|
|
2647
3235
|
self._expect(TokenType.BLOCK_START)
|
|
2648
3236
|
|
|
@@ -2676,6 +3264,157 @@ class CSSLParser:
|
|
|
2676
3264
|
self._expect(TokenType.BLOCK_END)
|
|
2677
3265
|
return node
|
|
2678
3266
|
|
|
3267
|
+
def _parse_param_switch(self, params_identifier: ASTNode) -> ASTNode:
|
|
3268
|
+
"""Parse param switch for open parameters.
|
|
3269
|
+
|
|
3270
|
+
v4.2.5: Switch on which parameters were provided.
|
|
3271
|
+
|
|
3272
|
+
Syntax:
|
|
3273
|
+
switch(Params): or switch(Params) {
|
|
3274
|
+
case name: // if 'name' param exists
|
|
3275
|
+
...
|
|
3276
|
+
break;
|
|
3277
|
+
case name & age: // if both 'name' AND 'age' exist
|
|
3278
|
+
...
|
|
3279
|
+
break;
|
|
3280
|
+
case name & not age: // if 'name' exists but 'age' doesn't
|
|
3281
|
+
...
|
|
3282
|
+
break;
|
|
3283
|
+
except name: // if 'name' does NOT exist (alias for 'case not name')
|
|
3284
|
+
...
|
|
3285
|
+
break;
|
|
3286
|
+
default: // fallback if no case matches
|
|
3287
|
+
...
|
|
3288
|
+
always: // always runs after a matching case
|
|
3289
|
+
...
|
|
3290
|
+
finally: // cleanup, runs last regardless
|
|
3291
|
+
...
|
|
3292
|
+
}
|
|
3293
|
+
"""
|
|
3294
|
+
# Allow both : and { as block start
|
|
3295
|
+
if self._match(TokenType.COLON):
|
|
3296
|
+
pass # Python-style with :
|
|
3297
|
+
self._expect(TokenType.BLOCK_START)
|
|
3298
|
+
|
|
3299
|
+
node = ASTNode('param_switch', value={
|
|
3300
|
+
'params': params_identifier.value # The open params variable name
|
|
3301
|
+
}, children=[])
|
|
3302
|
+
|
|
3303
|
+
while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
|
|
3304
|
+
if self._match_keyword('case'):
|
|
3305
|
+
# Parse param condition: name, name & age, name & not age
|
|
3306
|
+
condition = self._parse_param_condition()
|
|
3307
|
+
self._expect(TokenType.COLON)
|
|
3308
|
+
|
|
3309
|
+
case_node = ASTNode('param_case', value={'condition': condition}, children=[])
|
|
3310
|
+
self._parse_case_body(case_node)
|
|
3311
|
+
node.children.append(case_node)
|
|
3312
|
+
|
|
3313
|
+
elif self._match_keyword('except'):
|
|
3314
|
+
# except name: is alias for case not name:
|
|
3315
|
+
param_name = self._advance().value
|
|
3316
|
+
self._expect(TokenType.COLON)
|
|
3317
|
+
|
|
3318
|
+
condition = {'type': 'not', 'param': param_name}
|
|
3319
|
+
case_node = ASTNode('param_case', value={'condition': condition}, children=[])
|
|
3320
|
+
self._parse_case_body(case_node)
|
|
3321
|
+
node.children.append(case_node)
|
|
3322
|
+
|
|
3323
|
+
elif self._match_keyword('default'):
|
|
3324
|
+
self._expect(TokenType.COLON)
|
|
3325
|
+
default_node = ASTNode('param_default', children=[])
|
|
3326
|
+
self._parse_case_body(default_node)
|
|
3327
|
+
node.children.append(default_node)
|
|
3328
|
+
|
|
3329
|
+
elif self._match_keyword('always'):
|
|
3330
|
+
self._expect(TokenType.COLON)
|
|
3331
|
+
always_node = ASTNode('param_always', children=[])
|
|
3332
|
+
self._parse_case_body(always_node)
|
|
3333
|
+
node.children.append(always_node)
|
|
3334
|
+
|
|
3335
|
+
elif self._match_keyword('finally'):
|
|
3336
|
+
self._expect(TokenType.COLON)
|
|
3337
|
+
finally_node = ASTNode('param_finally', children=[])
|
|
3338
|
+
self._parse_case_body(finally_node)
|
|
3339
|
+
node.children.append(finally_node)
|
|
3340
|
+
|
|
3341
|
+
else:
|
|
3342
|
+
self._advance()
|
|
3343
|
+
|
|
3344
|
+
self._expect(TokenType.BLOCK_END)
|
|
3345
|
+
return node
|
|
3346
|
+
|
|
3347
|
+
def _parse_param_condition(self) -> dict:
|
|
3348
|
+
"""Parse param switch condition.
|
|
3349
|
+
|
|
3350
|
+
v4.3.2: Enhanced to support multiple styles:
|
|
3351
|
+
name -> {'type': 'exists', 'param': 'name'}
|
|
3352
|
+
not name -> {'type': 'not', 'param': 'name'}
|
|
3353
|
+
!name -> {'type': 'not', 'param': 'name'}
|
|
3354
|
+
name & age -> {'type': 'and', 'left': {...}, 'right': {...}}
|
|
3355
|
+
name & not age -> {'type': 'and', 'left': {...}, 'right': {'type': 'not', ...}}
|
|
3356
|
+
name & !age -> {'type': 'and', 'left': {...}, 'right': {'type': 'not', ...}}
|
|
3357
|
+
name || age -> {'type': 'or', 'left': {...}, 'right': {...}}
|
|
3358
|
+
"""
|
|
3359
|
+
# Check for 'not' or '!' prefix
|
|
3360
|
+
negated = self._match_keyword('not') or self._match(TokenType.NOT)
|
|
3361
|
+
param_name = self._advance().value
|
|
3362
|
+
|
|
3363
|
+
if negated:
|
|
3364
|
+
condition = {'type': 'not', 'param': param_name}
|
|
3365
|
+
else:
|
|
3366
|
+
condition = {'type': 'exists', 'param': param_name}
|
|
3367
|
+
|
|
3368
|
+
# Check for & (AND) or || (OR) combinations
|
|
3369
|
+
while True:
|
|
3370
|
+
if self._match(TokenType.AMPERSAND):
|
|
3371
|
+
# AND operator - check for 'not' or '!' prefix on right side
|
|
3372
|
+
right_negated = self._match_keyword('not') or self._match(TokenType.NOT)
|
|
3373
|
+
right_param = self._advance().value
|
|
3374
|
+
|
|
3375
|
+
if right_negated:
|
|
3376
|
+
right_condition = {'type': 'not', 'param': right_param}
|
|
3377
|
+
else:
|
|
3378
|
+
right_condition = {'type': 'exists', 'param': right_param}
|
|
3379
|
+
|
|
3380
|
+
condition = {'type': 'and', 'left': condition, 'right': right_condition}
|
|
3381
|
+
|
|
3382
|
+
elif self._match(TokenType.OR):
|
|
3383
|
+
# OR operator (||) - check for 'not' or '!' prefix on right side
|
|
3384
|
+
right_negated = self._match_keyword('not') or self._match(TokenType.NOT)
|
|
3385
|
+
right_param = self._advance().value
|
|
3386
|
+
|
|
3387
|
+
if right_negated:
|
|
3388
|
+
right_condition = {'type': 'not', 'param': right_param}
|
|
3389
|
+
else:
|
|
3390
|
+
right_condition = {'type': 'exists', 'param': right_param}
|
|
3391
|
+
|
|
3392
|
+
condition = {'type': 'or', 'left': condition, 'right': right_condition}
|
|
3393
|
+
else:
|
|
3394
|
+
break
|
|
3395
|
+
|
|
3396
|
+
return condition
|
|
3397
|
+
|
|
3398
|
+
def _parse_case_body(self, case_node: ASTNode):
|
|
3399
|
+
"""Parse the body of a case/except/default/always/finally block."""
|
|
3400
|
+
stop_keywords = ['case', 'except', 'default', 'always', 'finally']
|
|
3401
|
+
|
|
3402
|
+
while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
|
|
3403
|
+
# Check if we hit another case keyword
|
|
3404
|
+
if any(self._check_keyword(kw) for kw in stop_keywords):
|
|
3405
|
+
break
|
|
3406
|
+
|
|
3407
|
+
stmt = self._parse_statement()
|
|
3408
|
+
if stmt:
|
|
3409
|
+
case_node.children.append(stmt)
|
|
3410
|
+
|
|
3411
|
+
# Check for break
|
|
3412
|
+
if self._check_keyword('break'):
|
|
3413
|
+
break_stmt = self._parse_statement()
|
|
3414
|
+
if break_stmt:
|
|
3415
|
+
case_node.children.append(break_stmt)
|
|
3416
|
+
break
|
|
3417
|
+
|
|
2679
3418
|
def _parse_return(self) -> ASTNode:
|
|
2680
3419
|
"""Parse return statement, supporting multiple values for shuffled functions.
|
|
2681
3420
|
|
|
@@ -2740,6 +3479,12 @@ class CSSLParser:
|
|
|
2740
3479
|
self._expect(TokenType.BLOCK_END)
|
|
2741
3480
|
node.children.append(try_block)
|
|
2742
3481
|
|
|
3482
|
+
# v4.2.6: Skip optional semicolons between try block and catch
|
|
3483
|
+
# v4.5.1: Also skip comments for better .cssl-pl file support
|
|
3484
|
+
while self._match(TokenType.SEMICOLON) or self._check(TokenType.COMMENT):
|
|
3485
|
+
if self._check(TokenType.COMMENT):
|
|
3486
|
+
self._advance()
|
|
3487
|
+
|
|
2743
3488
|
if self._match_keyword('catch'):
|
|
2744
3489
|
error_var = None
|
|
2745
3490
|
if self._match(TokenType.PAREN_START):
|
|
@@ -2755,8 +3500,45 @@ class CSSLParser:
|
|
|
2755
3500
|
self._expect(TokenType.BLOCK_END)
|
|
2756
3501
|
node.children.append(catch_block)
|
|
2757
3502
|
|
|
3503
|
+
# v4.2.6: Skip optional semicolons between catch and finally
|
|
3504
|
+
# v4.5.1: Also skip comments for better .cssl-pl file support
|
|
3505
|
+
while self._match(TokenType.SEMICOLON) or self._check(TokenType.COMMENT):
|
|
3506
|
+
if self._check(TokenType.COMMENT):
|
|
3507
|
+
self._advance()
|
|
3508
|
+
|
|
3509
|
+
# v4.2.6: Add finally support
|
|
3510
|
+
if self._match_keyword('finally'):
|
|
3511
|
+
finally_block = ASTNode('finally-block', children=[])
|
|
3512
|
+
self._expect(TokenType.BLOCK_START)
|
|
3513
|
+
while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
|
|
3514
|
+
stmt = self._parse_statement()
|
|
3515
|
+
if stmt:
|
|
3516
|
+
finally_block.children.append(stmt)
|
|
3517
|
+
self._expect(TokenType.BLOCK_END)
|
|
3518
|
+
node.children.append(finally_block)
|
|
3519
|
+
|
|
2758
3520
|
return node
|
|
2759
3521
|
|
|
3522
|
+
def _parse_throw(self) -> ASTNode:
|
|
3523
|
+
"""Parse throw statement: throw expression;
|
|
3524
|
+
|
|
3525
|
+
v4.5.1: Added throw statement for exception handling.
|
|
3526
|
+
|
|
3527
|
+
Syntax:
|
|
3528
|
+
throw "Error message";
|
|
3529
|
+
throw error_variable;
|
|
3530
|
+
throw MyException("details");
|
|
3531
|
+
"""
|
|
3532
|
+
# Parse the expression to throw (can be string, variable, or function call)
|
|
3533
|
+
if self._check(TokenType.SEMICOLON):
|
|
3534
|
+
# throw; - re-throw current exception
|
|
3535
|
+
self._advance()
|
|
3536
|
+
return ASTNode('throw', value=None)
|
|
3537
|
+
|
|
3538
|
+
expr = self._parse_expression()
|
|
3539
|
+
self._match(TokenType.SEMICOLON)
|
|
3540
|
+
return ASTNode('throw', value=expr)
|
|
3541
|
+
|
|
2760
3542
|
def _parse_await(self) -> ASTNode:
|
|
2761
3543
|
"""Parse await statement: await expression;"""
|
|
2762
3544
|
expr = self._parse_expression()
|
|
@@ -2955,7 +3737,8 @@ class CSSLParser:
|
|
|
2955
3737
|
filter_info = {}
|
|
2956
3738
|
# Parse type::helper=value patterns within this bracket
|
|
2957
3739
|
while not self._check(TokenType.BRACKET_END) and not self._is_at_end():
|
|
2958
|
-
|
|
3740
|
+
# Accept IDENTIFIER, KEYWORD, or TYPE_LITERAL (dict, list) as filter type
|
|
3741
|
+
if self._check(TokenType.IDENTIFIER) or self._check(TokenType.KEYWORD) or self._check(TokenType.TYPE_LITERAL):
|
|
2959
3742
|
filter_type = self._advance().value
|
|
2960
3743
|
if self._match(TokenType.DOUBLE_COLON):
|
|
2961
3744
|
helper = self._advance().value
|
|
@@ -3305,7 +4088,7 @@ class CSSLParser:
|
|
|
3305
4088
|
# Just 'this' keyword alone - return as identifier for now
|
|
3306
4089
|
return ASTNode('identifier', value='this')
|
|
3307
4090
|
|
|
3308
|
-
# Handle 'new ClassName(args)' or 'new @ClassName(args)' instantiation
|
|
4091
|
+
# Handle 'new ClassName(args)' or 'new @ClassName(args)' or 'new Namespace::ClassName(args)' instantiation
|
|
3309
4092
|
if self._check(TokenType.KEYWORD) and self._current().value == 'new':
|
|
3310
4093
|
self._advance() # consume 'new'
|
|
3311
4094
|
# Check for @ prefix (global class reference)
|
|
@@ -3313,13 +4096,19 @@ class CSSLParser:
|
|
|
3313
4096
|
if self._check(TokenType.AT):
|
|
3314
4097
|
self._advance() # consume @
|
|
3315
4098
|
is_global_ref = True
|
|
3316
|
-
class_name = self._advance().value # get class name
|
|
4099
|
+
class_name = self._advance().value # get class name or namespace
|
|
4100
|
+
# v4.2.6: Handle Namespace::ClassName syntax
|
|
4101
|
+
namespace = None
|
|
4102
|
+
if self._check(TokenType.DOUBLE_COLON):
|
|
4103
|
+
self._advance() # consume ::
|
|
4104
|
+
namespace = class_name
|
|
4105
|
+
class_name = self._advance().value # get actual class name
|
|
3317
4106
|
args = []
|
|
3318
4107
|
kwargs = {}
|
|
3319
4108
|
if self._match(TokenType.PAREN_START):
|
|
3320
4109
|
args, kwargs = self._parse_call_arguments()
|
|
3321
4110
|
self._expect(TokenType.PAREN_END)
|
|
3322
|
-
node = ASTNode('new', value={'class': class_name, 'args': args, 'kwargs': kwargs, 'is_global_ref': is_global_ref})
|
|
4111
|
+
node = ASTNode('new', value={'class': class_name, 'namespace': namespace, 'args': args, 'kwargs': kwargs, 'is_global_ref': is_global_ref})
|
|
3323
4112
|
# Continue to check for member access, calls on the new object
|
|
3324
4113
|
while True:
|
|
3325
4114
|
if self._match(TokenType.DOT):
|
|
@@ -3689,23 +4478,24 @@ class CSSLParser:
|
|
|
3689
4478
|
|
|
3690
4479
|
self._expect(TokenType.COMPARE_GT) # consume >
|
|
3691
4480
|
|
|
3692
|
-
#
|
|
4481
|
+
# Optional () - for named parameter search, () is not required
|
|
4482
|
+
args = []
|
|
3693
4483
|
if self._check(TokenType.PAREN_START):
|
|
3694
4484
|
self._advance() # consume (
|
|
3695
|
-
args = []
|
|
3696
4485
|
while not self._check(TokenType.PAREN_END):
|
|
3697
4486
|
args.append(self._parse_expression())
|
|
3698
4487
|
if not self._check(TokenType.PAREN_END):
|
|
3699
4488
|
self._expect(TokenType.COMMA)
|
|
3700
4489
|
self._expect(TokenType.PAREN_END)
|
|
3701
4490
|
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
4491
|
+
# Return as typed function call (works with or without ())
|
|
4492
|
+
# v4.2.5: OpenFind<type, "name"> now works without ()
|
|
4493
|
+
return ASTNode('typed_call', value={
|
|
4494
|
+
'name': name,
|
|
4495
|
+
'type_param': type_param,
|
|
4496
|
+
'param_name': param_name, # Named parameter for OpenFind
|
|
4497
|
+
'args': args
|
|
4498
|
+
})
|
|
3709
4499
|
|
|
3710
4500
|
node = ASTNode('identifier', value=name)
|
|
3711
4501
|
|
|
@@ -3735,7 +4525,7 @@ class CSSLParser:
|
|
|
3735
4525
|
def _is_object_literal(self) -> bool:
|
|
3736
4526
|
"""Check if current position is an object literal { key = value } vs action block { expr; }
|
|
3737
4527
|
|
|
3738
|
-
Object literal: { name = value
|
|
4528
|
+
Object literal: { name = value } or { "key" = value } or { "key": value } (Python-style)
|
|
3739
4529
|
Action block: { %version; } or { "1.0.0" } or { call(); }
|
|
3740
4530
|
"""
|
|
3741
4531
|
# Empty block is action block
|
|
@@ -3745,12 +4535,12 @@ class CSSLParser:
|
|
|
3745
4535
|
# Save position for lookahead
|
|
3746
4536
|
saved_pos = self.pos
|
|
3747
4537
|
|
|
3748
|
-
# Check if it looks like key = value pattern
|
|
4538
|
+
# Check if it looks like key = value or key: value pattern
|
|
3749
4539
|
is_object = False
|
|
3750
4540
|
if self._check(TokenType.IDENTIFIER) or self._check(TokenType.STRING):
|
|
3751
4541
|
self._advance() # skip key
|
|
3752
|
-
if self._check(TokenType.EQUALS):
|
|
3753
|
-
# Looks like object literal: { key = ...
|
|
4542
|
+
if self._check(TokenType.EQUALS) or self._check(TokenType.COLON):
|
|
4543
|
+
# Looks like object literal: { key = ... } or { "key": ... }
|
|
3754
4544
|
is_object = True
|
|
3755
4545
|
|
|
3756
4546
|
# Restore position
|
|
@@ -3790,12 +4580,22 @@ class CSSLParser:
|
|
|
3790
4580
|
return ASTNode('action_block', children=children)
|
|
3791
4581
|
|
|
3792
4582
|
def _parse_object(self) -> ASTNode:
|
|
4583
|
+
"""Parse object/dict literal.
|
|
4584
|
+
|
|
4585
|
+
Supports both CSSL-style and Python-style syntax:
|
|
4586
|
+
{ key = value } // CSSL style
|
|
4587
|
+
{ "key" = value } // CSSL style with string key
|
|
4588
|
+
{ "key": value } // Python style
|
|
4589
|
+
{ key: value } // Python style with identifier key
|
|
4590
|
+
"""
|
|
3793
4591
|
properties = {}
|
|
3794
4592
|
|
|
3795
4593
|
while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
|
|
3796
4594
|
if self._check(TokenType.IDENTIFIER) or self._check(TokenType.STRING):
|
|
3797
4595
|
key = self._advance().value
|
|
3798
|
-
|
|
4596
|
+
# Accept both = (CSSL) and : (Python) as key-value separator
|
|
4597
|
+
if not self._match(TokenType.EQUALS) and not self._match(TokenType.COLON):
|
|
4598
|
+
self.error(f"Expected '=' or ':' after key '{key}' in object literal")
|
|
3799
4599
|
value = self._parse_expression()
|
|
3800
4600
|
properties[key] = value
|
|
3801
4601
|
self._match(TokenType.SEMICOLON)
|
|
@@ -3857,12 +4657,117 @@ def parse_cssl_program(source: str) -> ASTNode:
|
|
|
3857
4657
|
return parser.parse_program()
|
|
3858
4658
|
|
|
3859
4659
|
|
|
3860
|
-
def tokenize_cssl(source: str) -> List[Token]:
|
|
3861
|
-
"""
|
|
4660
|
+
def tokenize_cssl(source: str, use_cpp: bool = True) -> List[Token]:
|
|
4661
|
+
"""
|
|
4662
|
+
Tokenize CSSL source code (useful for syntax highlighting).
|
|
4663
|
+
|
|
4664
|
+
Args:
|
|
4665
|
+
source: CSSL source code
|
|
4666
|
+
use_cpp: If True, use C++ acceleration when available (default True)
|
|
4667
|
+
|
|
4668
|
+
Returns:
|
|
4669
|
+
List of Token objects
|
|
4670
|
+
"""
|
|
4671
|
+
# Try C++ tokenization if available and requested
|
|
4672
|
+
if use_cpp:
|
|
4673
|
+
try:
|
|
4674
|
+
from . import _CPP_AVAILABLE, _cpp_module
|
|
4675
|
+
if _CPP_AVAILABLE and _cpp_module and hasattr(_cpp_module, 'Lexer'):
|
|
4676
|
+
lexer = _cpp_module.Lexer(source)
|
|
4677
|
+
cpp_tokens = lexer.tokenize()
|
|
4678
|
+
# Convert C++ tokens to Python Token objects
|
|
4679
|
+
return _convert_cpp_tokens(cpp_tokens)
|
|
4680
|
+
except (ImportError, Exception):
|
|
4681
|
+
pass
|
|
4682
|
+
|
|
4683
|
+
# Fall back to Python lexer
|
|
3862
4684
|
lexer = CSSLLexer(source)
|
|
3863
4685
|
return lexer.tokenize()
|
|
3864
4686
|
|
|
3865
4687
|
|
|
4688
|
+
def _convert_cpp_tokens(cpp_tokens: list) -> List[Token]:
|
|
4689
|
+
"""Convert C++ Token objects to Python Token objects."""
|
|
4690
|
+
result = []
|
|
4691
|
+
for ct in cpp_tokens:
|
|
4692
|
+
# Map C++ token type to Python TokenType
|
|
4693
|
+
token_type = _map_cpp_token_type(ct.type)
|
|
4694
|
+
|
|
4695
|
+
# Get value based on value_type
|
|
4696
|
+
if ct.value_type == 'string':
|
|
4697
|
+
value = ct.str_value
|
|
4698
|
+
elif ct.value_type == 'number':
|
|
4699
|
+
value = ct.num_value
|
|
4700
|
+
elif ct.value_type == 'bool':
|
|
4701
|
+
value = ct.bool_value
|
|
4702
|
+
else:
|
|
4703
|
+
value = ct.str_value
|
|
4704
|
+
|
|
4705
|
+
result.append(Token(token_type, value, ct.line, ct.column))
|
|
4706
|
+
|
|
4707
|
+
return result
|
|
4708
|
+
|
|
4709
|
+
|
|
4710
|
+
def _map_cpp_token_type(cpp_type: int) -> TokenType:
|
|
4711
|
+
"""Map C++ token type integer to Python TokenType enum."""
|
|
4712
|
+
# This mapping must match the C++ TokenType enum in cssl_core.cpp
|
|
4713
|
+
type_map = {
|
|
4714
|
+
0: TokenType.KEYWORD,
|
|
4715
|
+
1: TokenType.IDENTIFIER,
|
|
4716
|
+
2: TokenType.STRING,
|
|
4717
|
+
3: TokenType.NUMBER,
|
|
4718
|
+
4: TokenType.BOOLEAN,
|
|
4719
|
+
5: TokenType.NULL,
|
|
4720
|
+
6: TokenType.OPERATOR,
|
|
4721
|
+
7: TokenType.INJECT_LEFT,
|
|
4722
|
+
8: TokenType.INJECT_RIGHT,
|
|
4723
|
+
9: TokenType.INJECT_PLUS_LEFT,
|
|
4724
|
+
10: TokenType.INJECT_PLUS_RIGHT,
|
|
4725
|
+
11: TokenType.INJECT_MINUS_LEFT,
|
|
4726
|
+
12: TokenType.INJECT_MINUS_RIGHT,
|
|
4727
|
+
13: TokenType.INFUSE_LEFT,
|
|
4728
|
+
14: TokenType.INFUSE_RIGHT,
|
|
4729
|
+
15: TokenType.FLOW_RIGHT,
|
|
4730
|
+
16: TokenType.FLOW_LEFT,
|
|
4731
|
+
17: TokenType.EQUALS,
|
|
4732
|
+
18: TokenType.COMPARE_EQ,
|
|
4733
|
+
19: TokenType.COMPARE_NE,
|
|
4734
|
+
20: TokenType.COMPARE_LT,
|
|
4735
|
+
21: TokenType.COMPARE_GT,
|
|
4736
|
+
22: TokenType.COMPARE_LE,
|
|
4737
|
+
23: TokenType.COMPARE_GE,
|
|
4738
|
+
24: TokenType.PLUS,
|
|
4739
|
+
25: TokenType.MINUS,
|
|
4740
|
+
26: TokenType.MULTIPLY,
|
|
4741
|
+
27: TokenType.DIVIDE,
|
|
4742
|
+
28: TokenType.MODULO,
|
|
4743
|
+
29: TokenType.AND,
|
|
4744
|
+
30: TokenType.OR,
|
|
4745
|
+
31: TokenType.NOT,
|
|
4746
|
+
32: TokenType.AMPERSAND,
|
|
4747
|
+
33: TokenType.BLOCK_START,
|
|
4748
|
+
34: TokenType.BLOCK_END,
|
|
4749
|
+
35: TokenType.PAREN_START,
|
|
4750
|
+
36: TokenType.PAREN_END,
|
|
4751
|
+
37: TokenType.BRACKET_START,
|
|
4752
|
+
38: TokenType.BRACKET_END,
|
|
4753
|
+
39: TokenType.SEMICOLON,
|
|
4754
|
+
40: TokenType.COLON,
|
|
4755
|
+
41: TokenType.DOUBLE_COLON,
|
|
4756
|
+
42: TokenType.COMMA,
|
|
4757
|
+
43: TokenType.DOT,
|
|
4758
|
+
44: TokenType.AT,
|
|
4759
|
+
45: TokenType.GLOBAL_REF,
|
|
4760
|
+
46: TokenType.SNAPSHOT_REF,
|
|
4761
|
+
47: TokenType.ARROW,
|
|
4762
|
+
48: TokenType.LAMBDA,
|
|
4763
|
+
49: TokenType.TERNARY,
|
|
4764
|
+
50: TokenType.INCREMENT,
|
|
4765
|
+
51: TokenType.DECREMENT,
|
|
4766
|
+
52: TokenType.EOF,
|
|
4767
|
+
}
|
|
4768
|
+
return type_map.get(cpp_type, TokenType.IDENTIFIER)
|
|
4769
|
+
|
|
4770
|
+
|
|
3866
4771
|
# Export public API
|
|
3867
4772
|
__all__ = [
|
|
3868
4773
|
'TokenType', 'Token', 'ASTNode',
|