IncludeCPP 4.2.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 +82 -115
- includecpp/DOCUMENTATION.md +208 -355
- includecpp/__init__.py +1 -1
- includecpp/cli/commands.py +2 -2
- includecpp/core/cssl/CSSL_DOCUMENTATION.md +1505 -1467
- includecpp/core/cssl/cssl_builtins.py +129 -19
- includecpp/core/cssl/cssl_parser.py +539 -29
- includecpp/core/cssl/cssl_runtime.py +467 -33
- includecpp/core/cssl/cssl_types.py +189 -0
- includecpp/vscode/cssl/syntaxes/cssl.tmLanguage.json +38 -2
- includecpp-4.3.0.dist-info/METADATA +277 -0
- {includecpp-4.2.2.dist-info → includecpp-4.3.0.dist-info}/RECORD +16 -16
- includecpp-4.2.2.dist-info/METADATA +0 -1008
- {includecpp-4.2.2.dist-info → includecpp-4.3.0.dist-info}/WHEEL +0 -0
- {includecpp-4.2.2.dist-info → includecpp-4.3.0.dist-info}/entry_points.txt +0 -0
- {includecpp-4.2.2.dist-info → includecpp-4.3.0.dist-info}/licenses/LICENSE +0 -0
- {includecpp-4.2.2.dist-info → includecpp-4.3.0.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,7 @@ 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)
|
|
158
161
|
# CSSL Include Keywords
|
|
159
162
|
'include', 'get',
|
|
160
163
|
# Multi-language support (v4.1.0)
|
|
@@ -164,7 +167,7 @@ KEYWORDS = {
|
|
|
164
167
|
# Function modifiers that can appear in any order before function name
|
|
165
168
|
FUNCTION_MODIFIERS = {
|
|
166
169
|
'undefined', 'open', 'meta', 'super', 'closed', 'private', 'virtual',
|
|
167
|
-
'sqlbased', 'const', 'public', 'static', 'global', 'shuffled'
|
|
170
|
+
'sqlbased', 'const', 'public', 'static', 'global', 'shuffled', 'embedded'
|
|
168
171
|
}
|
|
169
172
|
|
|
170
173
|
# Type literals that create empty instances
|
|
@@ -348,13 +351,16 @@ class CSSLLexer:
|
|
|
348
351
|
self._add_token(TokenType.MULTIPLY, '*')
|
|
349
352
|
self._advance()
|
|
350
353
|
elif char == '/':
|
|
351
|
-
# Check
|
|
352
|
-
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:
|
|
353
362
|
self._add_token(TokenType.DIVIDE, '/')
|
|
354
363
|
self._advance()
|
|
355
|
-
else:
|
|
356
|
-
# Already handled by // comment check above, but just in case
|
|
357
|
-
self._skip_comment()
|
|
358
364
|
elif char == '<':
|
|
359
365
|
self._read_less_than()
|
|
360
366
|
elif char == '>':
|
|
@@ -399,6 +405,26 @@ class CSSLLexer:
|
|
|
399
405
|
while self.pos < len(self.source) and self.source[self.pos] != '\n':
|
|
400
406
|
self._advance()
|
|
401
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
|
+
|
|
402
428
|
def _read_string(self, quote_char: str):
|
|
403
429
|
self._advance()
|
|
404
430
|
start = self.pos
|
|
@@ -453,6 +479,19 @@ class CSSLLexer:
|
|
|
453
479
|
start = self.pos
|
|
454
480
|
if self.source[self.pos] == '-':
|
|
455
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
|
+
|
|
456
495
|
while self.pos < len(self.source) and (self.source[self.pos].isdigit() or self.source[self.pos] == '.'):
|
|
457
496
|
self._advance()
|
|
458
497
|
value = self.source[start:self.pos]
|
|
@@ -766,6 +805,41 @@ class CSSLParser:
|
|
|
766
805
|
'list', 'dictionary', 'dict', 'instance', 'map', 'openquote', 'parameter',
|
|
767
806
|
'dynamic', 'datastruct', 'dataspace', 'shuffled', 'iterator', 'combo', 'structure')
|
|
768
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
|
+
|
|
769
843
|
def _looks_like_function_declaration(self) -> bool:
|
|
770
844
|
"""Check if current position looks like a C-style function declaration.
|
|
771
845
|
|
|
@@ -900,7 +974,7 @@ class CSSLParser:
|
|
|
900
974
|
self.pos = saved_pos
|
|
901
975
|
return False
|
|
902
976
|
|
|
903
|
-
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:
|
|
904
978
|
"""Parse C-style typed function declaration with flexible modifier ordering.
|
|
905
979
|
|
|
906
980
|
Supports any order of modifiers, types, non-null (*), and global (@):
|
|
@@ -930,6 +1004,8 @@ class CSSLParser:
|
|
|
930
1004
|
non_null = False
|
|
931
1005
|
exclude_type = None
|
|
932
1006
|
is_const = False
|
|
1007
|
+
# v4.2.5: embedded = immediate &target replacement (can come from param or modifier)
|
|
1008
|
+
_is_embedded = is_embedded
|
|
933
1009
|
|
|
934
1010
|
# Phase 1: Collect all modifiers, type, non-null, and global indicators
|
|
935
1011
|
# These can appear in any order before the function name
|
|
@@ -944,6 +1020,9 @@ class CSSLParser:
|
|
|
944
1020
|
elif mod == 'const':
|
|
945
1021
|
is_const = True
|
|
946
1022
|
modifiers.append(mod)
|
|
1023
|
+
elif mod == 'embedded':
|
|
1024
|
+
_is_embedded = True
|
|
1025
|
+
modifiers.append(mod)
|
|
947
1026
|
else:
|
|
948
1027
|
modifiers.append(mod)
|
|
949
1028
|
continue
|
|
@@ -1183,6 +1262,7 @@ class CSSLParser:
|
|
|
1183
1262
|
'name': name,
|
|
1184
1263
|
'is_global': is_global,
|
|
1185
1264
|
'is_const': is_const,
|
|
1265
|
+
'is_embedded': _is_embedded, # v4.2.5: immediate &target replacement
|
|
1186
1266
|
'params': params,
|
|
1187
1267
|
'return_type': return_type,
|
|
1188
1268
|
'generic_type': generic_type,
|
|
@@ -1280,19 +1360,16 @@ class CSSLParser:
|
|
|
1280
1360
|
|
|
1281
1361
|
The * prefix indicates a non-nullable variable (can never be None/null).
|
|
1282
1362
|
Example: vector<dynamic> *MyVector - can never contain None values.
|
|
1363
|
+
Supports nested generics: datastruct<map<string, dynamic>> zipped;
|
|
1283
1364
|
"""
|
|
1284
1365
|
# Get type name
|
|
1285
1366
|
type_name = self._advance().value # Consume type keyword
|
|
1286
1367
|
|
|
1287
|
-
# Check for generic type <T> or instance<"name">
|
|
1368
|
+
# Check for generic type <T> or instance<"name"> or nested <map<K,V>>
|
|
1288
1369
|
element_type = None
|
|
1289
1370
|
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)
|
|
1371
|
+
# Use helper to parse nested generic content
|
|
1372
|
+
element_type = self._parse_generic_type_content()
|
|
1296
1373
|
|
|
1297
1374
|
# Check for * prefix (non-nullable indicator)
|
|
1298
1375
|
non_null = False
|
|
@@ -1337,6 +1414,10 @@ class CSSLParser:
|
|
|
1337
1414
|
root.children.append(self._parse_struct())
|
|
1338
1415
|
elif self._match_keyword('class'):
|
|
1339
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())
|
|
1340
1421
|
elif self._match_keyword('define'):
|
|
1341
1422
|
root.children.append(self._parse_define())
|
|
1342
1423
|
# Check for C-style typed function declarations
|
|
@@ -1359,6 +1440,14 @@ class CSSLParser:
|
|
|
1359
1440
|
elif self._match_keyword('package-includes'):
|
|
1360
1441
|
root.children.append(self._parse_package_includes())
|
|
1361
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'")
|
|
1362
1451
|
elif self._match_keyword('global'):
|
|
1363
1452
|
# Check if followed by class or define (global class/function)
|
|
1364
1453
|
if self._match_keyword('class'):
|
|
@@ -1622,7 +1711,236 @@ class CSSLParser:
|
|
|
1622
1711
|
self._expect(TokenType.BLOCK_END)
|
|
1623
1712
|
return node
|
|
1624
1713
|
|
|
1625
|
-
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:
|
|
1626
1944
|
"""Parse class declaration with members and methods.
|
|
1627
1945
|
|
|
1628
1946
|
Syntax:
|
|
@@ -1630,6 +1948,7 @@ class CSSLParser:
|
|
|
1630
1948
|
global class ClassName { ... } // Global class
|
|
1631
1949
|
class @ClassName { ... } // Global class (alternative)
|
|
1632
1950
|
class *ClassName { ... } // Non-null class
|
|
1951
|
+
embedded class ClassName &$Target { ... } // Immediate replacement (v4.2.5)
|
|
1633
1952
|
|
|
1634
1953
|
Non-null class (all methods return non-null):
|
|
1635
1954
|
class *MyClass { ... }
|
|
@@ -1652,6 +1971,28 @@ class CSSLParser:
|
|
|
1652
1971
|
class_params = self._parse_parameter_list()
|
|
1653
1972
|
self._expect(TokenType.PAREN_END)
|
|
1654
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
|
+
|
|
1655
1996
|
# Check for inheritance and overwrites:
|
|
1656
1997
|
# class Child : extends Parent { ... }
|
|
1657
1998
|
# class Child : extends $PythonObject { ... }
|
|
@@ -1725,6 +2066,7 @@ class CSSLParser:
|
|
|
1725
2066
|
node = ASTNode('class', value={
|
|
1726
2067
|
'name': class_name,
|
|
1727
2068
|
'is_global': is_global,
|
|
2069
|
+
'is_embedded': is_embedded, # v4.2.5: immediate &target replacement
|
|
1728
2070
|
'non_null': non_null,
|
|
1729
2071
|
'class_params': class_params,
|
|
1730
2072
|
'extends': extends_class,
|
|
@@ -1734,7 +2076,9 @@ class CSSLParser:
|
|
|
1734
2076
|
'overwrites': overwrites_class,
|
|
1735
2077
|
'overwrites_is_python': overwrites_is_python,
|
|
1736
2078
|
'supports_language': supports_language, # v4.1.0
|
|
1737
|
-
'raw_body': raw_body # v4.2.0: Raw body for language transformation
|
|
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
|
|
1738
2082
|
}, children=[])
|
|
1739
2083
|
|
|
1740
2084
|
# v4.2.0: If we have raw_body for language transformation, skip regular parsing
|
|
@@ -2208,6 +2552,7 @@ class CSSLParser:
|
|
|
2208
2552
|
node = ASTNode('function', value={
|
|
2209
2553
|
'name': name,
|
|
2210
2554
|
'is_global': is_global,
|
|
2555
|
+
'is_embedded': False, # v4.2.5: define uses delayed &target replacement
|
|
2211
2556
|
'params': params,
|
|
2212
2557
|
'non_null': non_null,
|
|
2213
2558
|
'exclude_type': exclude_type, # *[type] - must NOT return this type
|
|
@@ -2643,6 +2988,14 @@ class CSSLParser:
|
|
|
2643
2988
|
value = self._parse_expression()
|
|
2644
2989
|
self._expect(TokenType.PAREN_END)
|
|
2645
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
|
|
2646
2999
|
node = ASTNode('switch', value={'value': value}, children=[])
|
|
2647
3000
|
self._expect(TokenType.BLOCK_START)
|
|
2648
3001
|
|
|
@@ -2676,6 +3029,139 @@ class CSSLParser:
|
|
|
2676
3029
|
self._expect(TokenType.BLOCK_END)
|
|
2677
3030
|
return node
|
|
2678
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
|
+
|
|
2679
3165
|
def _parse_return(self) -> ASTNode:
|
|
2680
3166
|
"""Parse return statement, supporting multiple values for shuffled functions.
|
|
2681
3167
|
|
|
@@ -2740,6 +3226,9 @@ class CSSLParser:
|
|
|
2740
3226
|
self._expect(TokenType.BLOCK_END)
|
|
2741
3227
|
node.children.append(try_block)
|
|
2742
3228
|
|
|
3229
|
+
# v4.2.6: Skip optional semicolon between try block and catch
|
|
3230
|
+
self._match(TokenType.SEMICOLON)
|
|
3231
|
+
|
|
2743
3232
|
if self._match_keyword('catch'):
|
|
2744
3233
|
error_var = None
|
|
2745
3234
|
if self._match(TokenType.PAREN_START):
|
|
@@ -2755,6 +3244,20 @@ class CSSLParser:
|
|
|
2755
3244
|
self._expect(TokenType.BLOCK_END)
|
|
2756
3245
|
node.children.append(catch_block)
|
|
2757
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
|
+
|
|
2758
3261
|
return node
|
|
2759
3262
|
|
|
2760
3263
|
def _parse_await(self) -> ASTNode:
|
|
@@ -3305,7 +3808,7 @@ class CSSLParser:
|
|
|
3305
3808
|
# Just 'this' keyword alone - return as identifier for now
|
|
3306
3809
|
return ASTNode('identifier', value='this')
|
|
3307
3810
|
|
|
3308
|
-
# Handle 'new ClassName(args)' or 'new @ClassName(args)' instantiation
|
|
3811
|
+
# Handle 'new ClassName(args)' or 'new @ClassName(args)' or 'new Namespace::ClassName(args)' instantiation
|
|
3309
3812
|
if self._check(TokenType.KEYWORD) and self._current().value == 'new':
|
|
3310
3813
|
self._advance() # consume 'new'
|
|
3311
3814
|
# Check for @ prefix (global class reference)
|
|
@@ -3313,13 +3816,19 @@ class CSSLParser:
|
|
|
3313
3816
|
if self._check(TokenType.AT):
|
|
3314
3817
|
self._advance() # consume @
|
|
3315
3818
|
is_global_ref = True
|
|
3316
|
-
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
|
|
3317
3826
|
args = []
|
|
3318
3827
|
kwargs = {}
|
|
3319
3828
|
if self._match(TokenType.PAREN_START):
|
|
3320
3829
|
args, kwargs = self._parse_call_arguments()
|
|
3321
3830
|
self._expect(TokenType.PAREN_END)
|
|
3322
|
-
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})
|
|
3323
3832
|
# Continue to check for member access, calls on the new object
|
|
3324
3833
|
while True:
|
|
3325
3834
|
if self._match(TokenType.DOT):
|
|
@@ -3689,23 +4198,24 @@ class CSSLParser:
|
|
|
3689
4198
|
|
|
3690
4199
|
self._expect(TokenType.COMPARE_GT) # consume >
|
|
3691
4200
|
|
|
3692
|
-
#
|
|
4201
|
+
# Optional () - for named parameter search, () is not required
|
|
4202
|
+
args = []
|
|
3693
4203
|
if self._check(TokenType.PAREN_START):
|
|
3694
4204
|
self._advance() # consume (
|
|
3695
|
-
args = []
|
|
3696
4205
|
while not self._check(TokenType.PAREN_END):
|
|
3697
4206
|
args.append(self._parse_expression())
|
|
3698
4207
|
if not self._check(TokenType.PAREN_END):
|
|
3699
4208
|
self._expect(TokenType.COMMA)
|
|
3700
4209
|
self._expect(TokenType.PAREN_END)
|
|
3701
4210
|
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
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
|
+
})
|
|
3709
4219
|
|
|
3710
4220
|
node = ASTNode('identifier', value=name)
|
|
3711
4221
|
|