IncludeCPP 4.3.0__py3-none-any.whl → 4.6.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 +22 -0
- includecpp/__init__.py +1 -1
- includecpp/__init__.pyi +1 -4
- includecpp/cli/commands.py +1218 -25
- includecpp/core/cpp_api_extensions.pyi +204 -200
- 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 +142 -27
- includecpp/core/cssl/cssl_compiler.py +448 -0
- includecpp/core/cssl/cssl_optimizer.py +833 -0
- includecpp/core/cssl/cssl_parser.py +433 -38
- includecpp/core/cssl/cssl_runtime.py +294 -15
- includecpp/core/cssl/cssl_syntax.py +17 -0
- includecpp/core/cssl/cssl_types.py +143 -11
- includecpp/core/cssl_bridge.py +39 -2
- includecpp/generator/parser.cpp +38 -14
- includecpp/vscode/cssl/package.json +15 -0
- includecpp/vscode/cssl/syntaxes/cssl.tmLanguage.json +96 -0
- {includecpp-4.3.0.dist-info → includecpp-4.6.0.dist-info}/METADATA +1 -1
- {includecpp-4.3.0.dist-info → includecpp-4.6.0.dist-info}/RECORD +30 -21
- {includecpp-4.3.0.dist-info → includecpp-4.6.0.dist-info}/WHEEL +0 -0
- {includecpp-4.3.0.dist-info → includecpp-4.6.0.dist-info}/entry_points.txt +0 -0
- {includecpp-4.3.0.dist-info → includecpp-4.6.0.dist-info}/licenses/LICENSE +0 -0
- {includecpp-4.3.0.dist-info → includecpp-4.6.0.dist-info}/top_level.txt +0 -0
|
@@ -158,6 +158,7 @@ KEYWORDS = {
|
|
|
158
158
|
'public', # Explicitly public (default)
|
|
159
159
|
'static', # Static method/function
|
|
160
160
|
'embedded', # Immediate &target replacement at registration (v4.2.5)
|
|
161
|
+
'native', # Force C++ execution (no Python fallback)
|
|
161
162
|
# CSSL Include Keywords
|
|
162
163
|
'include', 'get',
|
|
163
164
|
# Multi-language support (v4.1.0)
|
|
@@ -167,7 +168,8 @@ KEYWORDS = {
|
|
|
167
168
|
# Function modifiers that can appear in any order before function name
|
|
168
169
|
FUNCTION_MODIFIERS = {
|
|
169
170
|
'undefined', 'open', 'meta', 'super', 'closed', 'private', 'virtual',
|
|
170
|
-
'sqlbased', 'const', 'public', 'static', 'global', 'shuffled', 'embedded'
|
|
171
|
+
'sqlbased', 'const', 'public', 'static', 'global', 'shuffled', 'embedded',
|
|
172
|
+
'native' # Force C++ execution
|
|
171
173
|
}
|
|
172
174
|
|
|
173
175
|
# Type literals that create empty instances
|
|
@@ -1280,7 +1282,8 @@ class CSSLParser:
|
|
|
1280
1282
|
'append_mode': append_mode,
|
|
1281
1283
|
'append_ref_class': append_ref_class,
|
|
1282
1284
|
'append_ref_member': append_ref_member,
|
|
1283
|
-
|
|
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'
|
|
1284
1287
|
}, children=[])
|
|
1285
1288
|
|
|
1286
1289
|
self._expect(TokenType.BLOCK_START)
|
|
@@ -1420,6 +1423,31 @@ class CSSLParser:
|
|
|
1420
1423
|
root.children.append(self._parse_bytearrayed())
|
|
1421
1424
|
elif self._match_keyword('define'):
|
|
1422
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}")
|
|
1423
1451
|
# Check for C-style typed function declarations
|
|
1424
1452
|
elif self._looks_like_function_declaration():
|
|
1425
1453
|
root.children.append(self._parse_typed_function())
|
|
@@ -1441,13 +1469,44 @@ class CSSLParser:
|
|
|
1441
1469
|
root.children.append(self._parse_package_includes())
|
|
1442
1470
|
# Handle global declarations
|
|
1443
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'")
|
|
1444
1487
|
elif self._match_keyword('embedded'):
|
|
1445
|
-
|
|
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'):
|
|
1446
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))
|
|
1447
1503
|
elif self._looks_like_function_declaration():
|
|
1448
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())
|
|
1449
1508
|
else:
|
|
1450
|
-
self.error("Expected 'class'
|
|
1509
|
+
self.error("Expected 'class', 'enum', 'open', 'define', function declaration, or identifier after 'embedded'")
|
|
1451
1510
|
elif self._match_keyword('global'):
|
|
1452
1511
|
# Check if followed by class or define (global class/function)
|
|
1453
1512
|
if self._match_keyword('class'):
|
|
@@ -1478,6 +1537,11 @@ class CSSLParser:
|
|
|
1478
1537
|
root.children.append(self._parse_for())
|
|
1479
1538
|
elif self._match_keyword('foreach'):
|
|
1480
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())
|
|
1481
1545
|
# Handle statements - keywords like 'instance', 'list', 'map' can be variable names
|
|
1482
1546
|
# v4.2.1: Added LANG_INSTANCE_REF for lang$instance statements (js$GameData.score = 1337)
|
|
1483
1547
|
elif (self._check(TokenType.IDENTIFIER) or self._check(TokenType.AT) or
|
|
@@ -1711,7 +1775,7 @@ class CSSLParser:
|
|
|
1711
1775
|
self._expect(TokenType.BLOCK_END)
|
|
1712
1776
|
return node
|
|
1713
1777
|
|
|
1714
|
-
def _parse_enum(self) -> ASTNode:
|
|
1778
|
+
def _parse_enum(self, is_embedded: bool = False) -> ASTNode:
|
|
1715
1779
|
"""Parse enum declaration.
|
|
1716
1780
|
|
|
1717
1781
|
Syntax:
|
|
@@ -1722,9 +1786,22 @@ class CSSLParser:
|
|
|
1722
1786
|
VALUE4 // Auto value 11
|
|
1723
1787
|
}
|
|
1724
1788
|
|
|
1789
|
+
embedded enum NewEnum &OldEnum { ... } // Replace OldEnum with NewEnum values
|
|
1790
|
+
|
|
1725
1791
|
Access values via EnumName::VALUE1
|
|
1726
1792
|
"""
|
|
1727
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
|
+
|
|
1728
1805
|
self._expect(TokenType.BLOCK_START)
|
|
1729
1806
|
|
|
1730
1807
|
members = []
|
|
@@ -1764,7 +1841,88 @@ class CSSLParser:
|
|
|
1764
1841
|
|
|
1765
1842
|
return ASTNode('enum', value={
|
|
1766
1843
|
'name': enum_name,
|
|
1767
|
-
'members': members
|
|
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'
|
|
1768
1926
|
})
|
|
1769
1927
|
|
|
1770
1928
|
def _parse_bytearrayed(self) -> ASTNode:
|
|
@@ -1823,6 +1981,29 @@ class CSSLParser:
|
|
|
1823
1981
|
# Wildcard - matches any value
|
|
1824
1982
|
pattern.append({'type': 'wildcard'})
|
|
1825
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})
|
|
1826
2007
|
elif self._check(TokenType.NUMBER):
|
|
1827
2008
|
# Check for hex format like 0x28
|
|
1828
2009
|
token = self._current()
|
|
@@ -1839,6 +2020,10 @@ class CSSLParser:
|
|
|
1839
2020
|
elif self._check(TokenType.STRING):
|
|
1840
2021
|
value = self._advance().value
|
|
1841
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})
|
|
1842
2027
|
elif self._check(TokenType.KEYWORD):
|
|
1843
2028
|
kw = self._current().value
|
|
1844
2029
|
if kw in ('true', 'True'):
|
|
@@ -1916,15 +2101,26 @@ class CSSLParser:
|
|
|
1916
2101
|
self._expect(TokenType.BLOCK_END)
|
|
1917
2102
|
default_block = body_children
|
|
1918
2103
|
|
|
1919
|
-
# Parse function reference: &funcName;
|
|
2104
|
+
# Parse function reference: &funcName; or &funcName(arg1, arg2);
|
|
1920
2105
|
elif self._check(TokenType.AMPERSAND):
|
|
1921
2106
|
self._advance() # consume &
|
|
1922
2107
|
if self._check(TokenType.IDENTIFIER):
|
|
1923
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)
|
|
1924
2119
|
func_refs.append({
|
|
1925
2120
|
'position': len(func_refs),
|
|
1926
2121
|
'hex_pos': f"0x{len(func_refs):x}",
|
|
1927
|
-
'func_ref': func_name
|
|
2122
|
+
'func_ref': func_name,
|
|
2123
|
+
'args': func_args # v4.3.2: Store arguments for simulation
|
|
1928
2124
|
})
|
|
1929
2125
|
self._match(TokenType.SEMICOLON)
|
|
1930
2126
|
|
|
@@ -2322,18 +2518,21 @@ class CSSLParser:
|
|
|
2322
2518
|
|
|
2323
2519
|
return params
|
|
2324
2520
|
|
|
2325
|
-
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:
|
|
2326
2522
|
"""Parse define function declaration.
|
|
2327
2523
|
|
|
2328
2524
|
Syntax:
|
|
2329
2525
|
define MyFunc(args) { } // Local function
|
|
2330
2526
|
global define MyFunc(args) { } // Global function
|
|
2527
|
+
private define MyFunc(args) { } // Private function (v4.5.1)
|
|
2331
2528
|
define @MyFunc(args) { } // Global function (alternative)
|
|
2332
2529
|
define *MyFunc(args) { } // Non-null: must never return None
|
|
2333
2530
|
define MyFunc(args) : extends OtherFunc { } // Inherit local vars
|
|
2334
2531
|
define MyFunc(args) : overwrites OtherFunc { } // Replace OtherFunc
|
|
2335
2532
|
define MyFunc(args) : supports python { } // Multi-language syntax
|
|
2336
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
|
|
2337
2536
|
"""
|
|
2338
2537
|
# Check for * prefix (non-null function - must return non-null)
|
|
2339
2538
|
# Also *[type] for type exclusion (must NOT return that type)
|
|
@@ -2552,7 +2751,8 @@ class CSSLParser:
|
|
|
2552
2751
|
node = ASTNode('function', value={
|
|
2553
2752
|
'name': name,
|
|
2554
2753
|
'is_global': is_global,
|
|
2555
|
-
'is_embedded':
|
|
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
|
|
2556
2756
|
'params': params,
|
|
2557
2757
|
'non_null': non_null,
|
|
2558
2758
|
'exclude_type': exclude_type, # *[type] - must NOT return this type
|
|
@@ -2572,7 +2772,9 @@ class CSSLParser:
|
|
|
2572
2772
|
# v4.1.0: Multi-language support
|
|
2573
2773
|
'supports_language': supports_language,
|
|
2574
2774
|
# v4.2.0: Raw body for language transformation
|
|
2575
|
-
'raw_body': raw_body
|
|
2775
|
+
'raw_body': raw_body,
|
|
2776
|
+
# v4.5.1: Function modifiers (private, const, static, etc.)
|
|
2777
|
+
'modifiers': modifiers or []
|
|
2576
2778
|
}, children=children)
|
|
2577
2779
|
|
|
2578
2780
|
return node
|
|
@@ -2596,6 +2798,9 @@ class CSSLParser:
|
|
|
2596
2798
|
elif self._match_keyword('continue'):
|
|
2597
2799
|
self._match(TokenType.SEMICOLON)
|
|
2598
2800
|
return ASTNode('continue')
|
|
2801
|
+
# v4.5.1: Add throw statement parsing
|
|
2802
|
+
elif self._match_keyword('throw'):
|
|
2803
|
+
return self._parse_throw()
|
|
2599
2804
|
elif self._match_keyword('try'):
|
|
2600
2805
|
return self._parse_try()
|
|
2601
2806
|
elif self._match_keyword('await'):
|
|
@@ -2988,9 +3193,39 @@ class CSSLParser:
|
|
|
2988
3193
|
value = self._parse_expression()
|
|
2989
3194
|
self._expect(TokenType.PAREN_END)
|
|
2990
3195
|
|
|
2991
|
-
# v4.2.5: Check if this is a param switch (switch on open params
|
|
3196
|
+
# v4.2.5: Check if this is a param switch (switch on open params)
|
|
2992
3197
|
# Syntax: switch(Params): case name: ... except age: ... always: ... finally: ...
|
|
2993
|
-
|
|
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
|
|
2994
3229
|
|
|
2995
3230
|
if is_param_switch:
|
|
2996
3231
|
return self._parse_param_switch(value)
|
|
@@ -3112,14 +3347,17 @@ class CSSLParser:
|
|
|
3112
3347
|
def _parse_param_condition(self) -> dict:
|
|
3113
3348
|
"""Parse param switch condition.
|
|
3114
3349
|
|
|
3115
|
-
|
|
3350
|
+
v4.3.2: Enhanced to support multiple styles:
|
|
3116
3351
|
name -> {'type': 'exists', 'param': 'name'}
|
|
3117
3352
|
not name -> {'type': 'not', 'param': 'name'}
|
|
3353
|
+
!name -> {'type': 'not', 'param': 'name'}
|
|
3118
3354
|
name & age -> {'type': 'and', 'left': {...}, 'right': {...}}
|
|
3119
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': {...}}
|
|
3120
3358
|
"""
|
|
3121
|
-
# Check for 'not' prefix
|
|
3122
|
-
negated = self._match_keyword('not')
|
|
3359
|
+
# Check for 'not' or '!' prefix
|
|
3360
|
+
negated = self._match_keyword('not') or self._match(TokenType.NOT)
|
|
3123
3361
|
param_name = self._advance().value
|
|
3124
3362
|
|
|
3125
3363
|
if negated:
|
|
@@ -3127,18 +3365,33 @@ class CSSLParser:
|
|
|
3127
3365
|
else:
|
|
3128
3366
|
condition = {'type': 'exists', 'param': param_name}
|
|
3129
3367
|
|
|
3130
|
-
# Check for & (AND) combinations
|
|
3131
|
-
while
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
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
|
|
3135
3374
|
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
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}
|
|
3140
3381
|
|
|
3141
|
-
|
|
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
|
|
3142
3395
|
|
|
3143
3396
|
return condition
|
|
3144
3397
|
|
|
@@ -3226,8 +3479,11 @@ class CSSLParser:
|
|
|
3226
3479
|
self._expect(TokenType.BLOCK_END)
|
|
3227
3480
|
node.children.append(try_block)
|
|
3228
3481
|
|
|
3229
|
-
# v4.2.6: Skip optional
|
|
3230
|
-
|
|
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()
|
|
3231
3487
|
|
|
3232
3488
|
if self._match_keyword('catch'):
|
|
3233
3489
|
error_var = None
|
|
@@ -3244,8 +3500,11 @@ class CSSLParser:
|
|
|
3244
3500
|
self._expect(TokenType.BLOCK_END)
|
|
3245
3501
|
node.children.append(catch_block)
|
|
3246
3502
|
|
|
3247
|
-
# v4.2.6: Skip optional
|
|
3248
|
-
|
|
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()
|
|
3249
3508
|
|
|
3250
3509
|
# v4.2.6: Add finally support
|
|
3251
3510
|
if self._match_keyword('finally'):
|
|
@@ -3260,6 +3519,26 @@ class CSSLParser:
|
|
|
3260
3519
|
|
|
3261
3520
|
return node
|
|
3262
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
|
+
|
|
3263
3542
|
def _parse_await(self) -> ASTNode:
|
|
3264
3543
|
"""Parse await statement: await expression;"""
|
|
3265
3544
|
expr = self._parse_expression()
|
|
@@ -3458,7 +3737,8 @@ class CSSLParser:
|
|
|
3458
3737
|
filter_info = {}
|
|
3459
3738
|
# Parse type::helper=value patterns within this bracket
|
|
3460
3739
|
while not self._check(TokenType.BRACKET_END) and not self._is_at_end():
|
|
3461
|
-
|
|
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):
|
|
3462
3742
|
filter_type = self._advance().value
|
|
3463
3743
|
if self._match(TokenType.DOUBLE_COLON):
|
|
3464
3744
|
helper = self._advance().value
|
|
@@ -4245,7 +4525,7 @@ class CSSLParser:
|
|
|
4245
4525
|
def _is_object_literal(self) -> bool:
|
|
4246
4526
|
"""Check if current position is an object literal { key = value } vs action block { expr; }
|
|
4247
4527
|
|
|
4248
|
-
Object literal: { name = value
|
|
4528
|
+
Object literal: { name = value } or { "key" = value } or { "key": value } (Python-style)
|
|
4249
4529
|
Action block: { %version; } or { "1.0.0" } or { call(); }
|
|
4250
4530
|
"""
|
|
4251
4531
|
# Empty block is action block
|
|
@@ -4255,12 +4535,12 @@ class CSSLParser:
|
|
|
4255
4535
|
# Save position for lookahead
|
|
4256
4536
|
saved_pos = self.pos
|
|
4257
4537
|
|
|
4258
|
-
# Check if it looks like key = value pattern
|
|
4538
|
+
# Check if it looks like key = value or key: value pattern
|
|
4259
4539
|
is_object = False
|
|
4260
4540
|
if self._check(TokenType.IDENTIFIER) or self._check(TokenType.STRING):
|
|
4261
4541
|
self._advance() # skip key
|
|
4262
|
-
if self._check(TokenType.EQUALS):
|
|
4263
|
-
# Looks like object literal: { key = ...
|
|
4542
|
+
if self._check(TokenType.EQUALS) or self._check(TokenType.COLON):
|
|
4543
|
+
# Looks like object literal: { key = ... } or { "key": ... }
|
|
4264
4544
|
is_object = True
|
|
4265
4545
|
|
|
4266
4546
|
# Restore position
|
|
@@ -4300,12 +4580,22 @@ class CSSLParser:
|
|
|
4300
4580
|
return ASTNode('action_block', children=children)
|
|
4301
4581
|
|
|
4302
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
|
+
"""
|
|
4303
4591
|
properties = {}
|
|
4304
4592
|
|
|
4305
4593
|
while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
|
|
4306
4594
|
if self._check(TokenType.IDENTIFIER) or self._check(TokenType.STRING):
|
|
4307
4595
|
key = self._advance().value
|
|
4308
|
-
|
|
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")
|
|
4309
4599
|
value = self._parse_expression()
|
|
4310
4600
|
properties[key] = value
|
|
4311
4601
|
self._match(TokenType.SEMICOLON)
|
|
@@ -4367,12 +4657,117 @@ def parse_cssl_program(source: str) -> ASTNode:
|
|
|
4367
4657
|
return parser.parse_program()
|
|
4368
4658
|
|
|
4369
4659
|
|
|
4370
|
-
def tokenize_cssl(source: str) -> List[Token]:
|
|
4371
|
-
"""
|
|
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
|
|
4372
4684
|
lexer = CSSLLexer(source)
|
|
4373
4685
|
return lexer.tokenize()
|
|
4374
4686
|
|
|
4375
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
|
+
|
|
4376
4771
|
# Export public API
|
|
4377
4772
|
__all__ = [
|
|
4378
4773
|
'TokenType', 'Token', 'ASTNode',
|