IncludeCPP 4.0.0__py3-none-any.whl → 4.2.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 +175 -0
- includecpp/DOCUMENTATION.md +593 -0
- includecpp/__init__.py +1 -1
- includecpp/__init__.pyi +4 -1
- includecpp/cli/commands.py +705 -98
- includecpp/core/ai_integration.py +46 -13
- includecpp/core/cpp_api_extensions.pyi +350 -0
- includecpp/core/cssl/CSSL_DOCUMENTATION.md +186 -5
- includecpp/core/cssl/cssl_builtins.py +101 -4
- includecpp/core/cssl/cssl_languages.py +1757 -0
- includecpp/core/cssl/cssl_parser.py +437 -104
- includecpp/core/cssl/cssl_runtime.py +865 -62
- includecpp/core/cssl/cssl_syntax.py +88 -4
- includecpp/core/cssl/cssl_types.py +172 -1
- includecpp/core/cssl_bridge.py +198 -12
- includecpp/core/cssl_bridge.pyi +717 -186
- includecpp/generator/parser.cpp +121 -4
- includecpp/generator/parser.h +6 -0
- includecpp/vscode/cssl/package.json +43 -1
- includecpp/vscode/cssl/syntaxes/cssl.tmLanguage.json +140 -17
- {includecpp-4.0.0.dist-info → includecpp-4.2.2.dist-info}/METADATA +102 -1
- {includecpp-4.0.0.dist-info → includecpp-4.2.2.dist-info}/RECORD +26 -22
- {includecpp-4.0.0.dist-info → includecpp-4.2.2.dist-info}/WHEEL +0 -0
- {includecpp-4.0.0.dist-info → includecpp-4.2.2.dist-info}/entry_points.txt +0 -0
- {includecpp-4.0.0.dist-info → includecpp-4.2.2.dist-info}/licenses/LICENSE +0 -0
- {includecpp-4.0.0.dist-info → includecpp-4.2.2.dist-info}/top_level.txt +0 -0
|
@@ -113,6 +113,8 @@ class TokenType(Enum):
|
|
|
113
113
|
# Append operator for constructor/function extension
|
|
114
114
|
PLUS_PLUS = auto() # ++ for constructor/function append (keeps old + adds new)
|
|
115
115
|
MINUS_MINUS = auto() # -- for potential future use
|
|
116
|
+
# Multi-language support (v4.1.0)
|
|
117
|
+
LANG_INSTANCE_REF = auto() # cpp$InstanceName, py$Object - cross-language instance access
|
|
116
118
|
|
|
117
119
|
|
|
118
120
|
KEYWORDS = {
|
|
@@ -155,6 +157,8 @@ KEYWORDS = {
|
|
|
155
157
|
'static', # Static method/function
|
|
156
158
|
# CSSL Include Keywords
|
|
157
159
|
'include', 'get',
|
|
160
|
+
# Multi-language support (v4.1.0)
|
|
161
|
+
'supports', 'libinclude',
|
|
158
162
|
}
|
|
159
163
|
|
|
160
164
|
# Function modifiers that can appear in any order before function name
|
|
@@ -182,6 +186,12 @@ INJECTION_HELPERS = {
|
|
|
182
186
|
'string', 'integer', 'json', 'array', 'vector', 'combo', 'dynamic', 'sql'
|
|
183
187
|
}
|
|
184
188
|
|
|
189
|
+
# Language identifiers for multi-language support (v4.1.0)
|
|
190
|
+
# Used in lang$instance patterns like cpp$MyClass, py$Object
|
|
191
|
+
LANGUAGE_IDS = {
|
|
192
|
+
'cpp', 'py', 'python', 'java', 'csharp', 'js', 'javascript'
|
|
193
|
+
}
|
|
194
|
+
|
|
185
195
|
|
|
186
196
|
@dataclass
|
|
187
197
|
class Token:
|
|
@@ -457,6 +467,25 @@ class CSSLLexer:
|
|
|
457
467
|
self._advance()
|
|
458
468
|
value = self.source[start:self.pos]
|
|
459
469
|
|
|
470
|
+
# Check for language$instance pattern (v4.1.0)
|
|
471
|
+
# e.g., cpp$MyClass, py$Object, java$Service
|
|
472
|
+
if value.lower() in LANGUAGE_IDS and self.pos < len(self.source) and self.source[self.pos] == '$':
|
|
473
|
+
lang_id = value
|
|
474
|
+
self._advance() # skip '$'
|
|
475
|
+
# Read instance name
|
|
476
|
+
instance_start = self.pos
|
|
477
|
+
while self.pos < len(self.source) and (self.source[self.pos].isalnum() or self.source[self.pos] == '_'):
|
|
478
|
+
self._advance()
|
|
479
|
+
instance_name = self.source[instance_start:self.pos]
|
|
480
|
+
if instance_name:
|
|
481
|
+
self._add_token(TokenType.LANG_INSTANCE_REF, {'lang': lang_id, 'instance': instance_name})
|
|
482
|
+
return
|
|
483
|
+
# If no instance name, revert and treat as normal identifier
|
|
484
|
+
self.pos = start
|
|
485
|
+
while self.pos < len(self.source) and (self.source[self.pos].isalnum() or self.source[self.pos] == '_'):
|
|
486
|
+
self._advance()
|
|
487
|
+
value = self.source[start:self.pos]
|
|
488
|
+
|
|
460
489
|
if value in ('True', 'true'):
|
|
461
490
|
self._add_token(TokenType.BOOLEAN, True)
|
|
462
491
|
elif value in ('False', 'false'):
|
|
@@ -672,10 +701,11 @@ class ASTNode:
|
|
|
672
701
|
class CSSLParser:
|
|
673
702
|
"""Parses CSSL tokens into an Abstract Syntax Tree."""
|
|
674
703
|
|
|
675
|
-
def __init__(self, tokens: List[Token], source_lines: List[str] = None):
|
|
704
|
+
def __init__(self, tokens: List[Token], source_lines: List[str] = None, source: str = None):
|
|
676
705
|
self.tokens = [t for t in tokens if t.type != TokenType.NEWLINE]
|
|
677
706
|
self.pos = 0
|
|
678
707
|
self.source_lines = source_lines or []
|
|
708
|
+
self.source = source or '\n'.join(self.source_lines) # v4.2.0: Full source for raw extraction
|
|
679
709
|
|
|
680
710
|
def get_source_line(self, line_num: int) -> str:
|
|
681
711
|
"""Get a specific source line for error reporting"""
|
|
@@ -1084,7 +1114,7 @@ class CSSLParser:
|
|
|
1084
1114
|
append_ref_class = None
|
|
1085
1115
|
append_ref_member = None
|
|
1086
1116
|
|
|
1087
|
-
# Check for &ClassName::member reference
|
|
1117
|
+
# Check for &ClassName::member or &ClassName.member or &function reference
|
|
1088
1118
|
if self._match(TokenType.AMPERSAND):
|
|
1089
1119
|
if self._check(TokenType.IDENTIFIER):
|
|
1090
1120
|
append_ref_class = self._advance().value
|
|
@@ -1095,8 +1125,8 @@ class CSSLParser:
|
|
|
1095
1125
|
elif self._check(TokenType.SHARED_REF):
|
|
1096
1126
|
append_ref_class = f'${self._advance().value}'
|
|
1097
1127
|
|
|
1098
|
-
# Check for ::member
|
|
1099
|
-
if self._match(TokenType.DOUBLE_COLON):
|
|
1128
|
+
# Check for ::member or .member (support both syntaxes)
|
|
1129
|
+
if self._match(TokenType.DOUBLE_COLON) or self._match(TokenType.DOT):
|
|
1100
1130
|
if self._check(TokenType.IDENTIFIER) or self._check(TokenType.KEYWORD):
|
|
1101
1131
|
append_ref_member = self._advance().value
|
|
1102
1132
|
|
|
@@ -1108,7 +1138,7 @@ class CSSLParser:
|
|
|
1108
1138
|
if self._match(TokenType.DOUBLE_COLON) or self._match(TokenType.COLON):
|
|
1109
1139
|
while True:
|
|
1110
1140
|
if self._match_keyword('extends'):
|
|
1111
|
-
# Parse target: @ModuleName, $PythonObject, Parent::method
|
|
1141
|
+
# Parse target: @ModuleName, $PythonObject, Parent::method, Parent.method
|
|
1112
1142
|
if self._check(TokenType.AT):
|
|
1113
1143
|
self._advance()
|
|
1114
1144
|
extends_func = '@' + self._advance().value
|
|
@@ -1117,7 +1147,8 @@ class CSSLParser:
|
|
|
1117
1147
|
extends_func = self._advance().value
|
|
1118
1148
|
elif self._check(TokenType.IDENTIFIER):
|
|
1119
1149
|
first_part = self._advance().value
|
|
1120
|
-
|
|
1150
|
+
# Support both :: and . for class method access
|
|
1151
|
+
if self._match(TokenType.DOUBLE_COLON) or self._match(TokenType.DOT):
|
|
1121
1152
|
extends_class_ref = first_part
|
|
1122
1153
|
extends_method_ref = self._advance().value
|
|
1123
1154
|
else:
|
|
@@ -1134,7 +1165,8 @@ class CSSLParser:
|
|
|
1134
1165
|
overwrites_func = self._advance().value
|
|
1135
1166
|
elif self._check(TokenType.IDENTIFIER):
|
|
1136
1167
|
first_part = self._advance().value
|
|
1137
|
-
|
|
1168
|
+
# Support both :: and . for class method access
|
|
1169
|
+
if self._match(TokenType.DOUBLE_COLON) or self._match(TokenType.DOT):
|
|
1138
1170
|
overwrites_class_ref = first_part
|
|
1139
1171
|
overwrites_method_ref = self._advance().value
|
|
1140
1172
|
else:
|
|
@@ -1348,13 +1380,7 @@ class CSSLParser:
|
|
|
1348
1380
|
# Wrap in global_assignment to mark as global variable (same as 'global' keyword)
|
|
1349
1381
|
global_stmt = ASTNode('global_assignment', value=stmt)
|
|
1350
1382
|
root.children.append(global_stmt)
|
|
1351
|
-
#
|
|
1352
|
-
elif (self._check(TokenType.IDENTIFIER) or self._check(TokenType.AT) or
|
|
1353
|
-
self._check(TokenType.SELF_REF) or self._check(TokenType.SHARED_REF) or
|
|
1354
|
-
self._check(TokenType.KEYWORD)):
|
|
1355
|
-
stmt = self._parse_expression_statement()
|
|
1356
|
-
if stmt:
|
|
1357
|
-
root.children.append(stmt)
|
|
1383
|
+
# Control flow keywords must be checked BEFORE generic KEYWORD handling
|
|
1358
1384
|
elif self._match_keyword('if'):
|
|
1359
1385
|
root.children.append(self._parse_if())
|
|
1360
1386
|
elif self._match_keyword('while'):
|
|
@@ -1363,6 +1389,14 @@ class CSSLParser:
|
|
|
1363
1389
|
root.children.append(self._parse_for())
|
|
1364
1390
|
elif self._match_keyword('foreach'):
|
|
1365
1391
|
root.children.append(self._parse_foreach())
|
|
1392
|
+
# Handle statements - keywords like 'instance', 'list', 'map' can be variable names
|
|
1393
|
+
# v4.2.1: Added LANG_INSTANCE_REF for lang$instance statements (js$GameData.score = 1337)
|
|
1394
|
+
elif (self._check(TokenType.IDENTIFIER) or self._check(TokenType.AT) or
|
|
1395
|
+
self._check(TokenType.SELF_REF) or self._check(TokenType.SHARED_REF) or
|
|
1396
|
+
self._check(TokenType.KEYWORD) or self._check(TokenType.LANG_INSTANCE_REF)):
|
|
1397
|
+
stmt = self._parse_expression_statement()
|
|
1398
|
+
if stmt:
|
|
1399
|
+
root.children.append(stmt)
|
|
1366
1400
|
# Skip comments and newlines
|
|
1367
1401
|
elif self._check(TokenType.COMMENT) or self._check(TokenType.NEWLINE):
|
|
1368
1402
|
self._advance()
|
|
@@ -1625,15 +1659,22 @@ class CSSLParser:
|
|
|
1625
1659
|
# class Child : extends Parent (param1, param2) { ... } <- constructor args for parent
|
|
1626
1660
|
extends_class = None
|
|
1627
1661
|
extends_is_python = False
|
|
1662
|
+
extends_lang_ref = None # v4.1.0: Cross-language inheritance (cpp$ClassName)
|
|
1628
1663
|
extends_args = []
|
|
1629
1664
|
overwrites_class = None
|
|
1630
1665
|
overwrites_is_python = False
|
|
1666
|
+
supports_language = None # v4.1.0: Multi-language syntax support
|
|
1631
1667
|
|
|
1632
1668
|
if self._match(TokenType.DOUBLE_COLON) or self._match(TokenType.COLON):
|
|
1633
1669
|
# Parse extends and/or overwrites (can be chained with : or ::)
|
|
1634
1670
|
while True:
|
|
1635
1671
|
if self._match_keyword('extends'):
|
|
1636
|
-
|
|
1672
|
+
# v4.1.0: Check for cross-language inheritance: extends cpp$ClassName
|
|
1673
|
+
if self._check(TokenType.LANG_INSTANCE_REF):
|
|
1674
|
+
ref = self._advance().value
|
|
1675
|
+
extends_lang_ref = ref # {'lang': 'cpp', 'instance': 'ClassName'}
|
|
1676
|
+
extends_class = ref['instance']
|
|
1677
|
+
elif self._check(TokenType.IDENTIFIER):
|
|
1637
1678
|
extends_class = self._advance().value
|
|
1638
1679
|
elif self._check(TokenType.SHARED_REF):
|
|
1639
1680
|
extends_class = self._advance().value
|
|
@@ -1658,12 +1699,29 @@ class CSSLParser:
|
|
|
1658
1699
|
# Skip optional () after class name
|
|
1659
1700
|
if self._match(TokenType.PAREN_START):
|
|
1660
1701
|
self._expect(TokenType.PAREN_END)
|
|
1702
|
+
# v4.1.0: Parse 'supports' keyword for multi-language syntax
|
|
1703
|
+
elif self._match_keyword('supports'):
|
|
1704
|
+
if self._check(TokenType.AT):
|
|
1705
|
+
self._advance() # consume @
|
|
1706
|
+
if self._check(TokenType.IDENTIFIER):
|
|
1707
|
+
supports_language = '@' + self._advance().value
|
|
1708
|
+
else:
|
|
1709
|
+
raise CSSLSyntaxError("Expected language identifier after '@' in 'supports'")
|
|
1710
|
+
elif self._check(TokenType.IDENTIFIER):
|
|
1711
|
+
supports_language = self._advance().value
|
|
1712
|
+
else:
|
|
1713
|
+
raise CSSLSyntaxError("Expected language identifier after 'supports'")
|
|
1661
1714
|
else:
|
|
1662
|
-
raise CSSLSyntaxError("Expected 'extends' or '
|
|
1715
|
+
raise CSSLSyntaxError("Expected 'extends', 'overwrites', or 'supports' after ':' or '::' in class declaration")
|
|
1663
1716
|
# Check for another : or :: for chaining
|
|
1664
1717
|
if not (self._match(TokenType.DOUBLE_COLON) or self._match(TokenType.COLON)):
|
|
1665
1718
|
break
|
|
1666
1719
|
|
|
1720
|
+
# v4.2.0: If supports_language is set, capture raw body for runtime transformation
|
|
1721
|
+
raw_body = None
|
|
1722
|
+
if supports_language:
|
|
1723
|
+
raw_body = self._extract_raw_block_body()
|
|
1724
|
+
|
|
1667
1725
|
node = ASTNode('class', value={
|
|
1668
1726
|
'name': class_name,
|
|
1669
1727
|
'is_global': is_global,
|
|
@@ -1671,10 +1729,29 @@ class CSSLParser:
|
|
|
1671
1729
|
'class_params': class_params,
|
|
1672
1730
|
'extends': extends_class,
|
|
1673
1731
|
'extends_is_python': extends_is_python,
|
|
1732
|
+
'extends_lang_ref': extends_lang_ref, # v4.1.0
|
|
1674
1733
|
'extends_args': extends_args,
|
|
1675
1734
|
'overwrites': overwrites_class,
|
|
1676
|
-
'overwrites_is_python': overwrites_is_python
|
|
1735
|
+
'overwrites_is_python': overwrites_is_python,
|
|
1736
|
+
'supports_language': supports_language, # v4.1.0
|
|
1737
|
+
'raw_body': raw_body # v4.2.0: Raw body for language transformation
|
|
1677
1738
|
}, children=[])
|
|
1739
|
+
|
|
1740
|
+
# v4.2.0: If we have raw_body for language transformation, skip regular parsing
|
|
1741
|
+
if raw_body is not None:
|
|
1742
|
+
# Skip the block entirely - runtime will transform and parse
|
|
1743
|
+
self._expect(TokenType.BLOCK_START)
|
|
1744
|
+
brace_count = 1
|
|
1745
|
+
while brace_count > 0 and not self._is_at_end():
|
|
1746
|
+
if self._check(TokenType.BLOCK_START):
|
|
1747
|
+
brace_count += 1
|
|
1748
|
+
elif self._check(TokenType.BLOCK_END):
|
|
1749
|
+
brace_count -= 1
|
|
1750
|
+
if brace_count > 0:
|
|
1751
|
+
self._advance()
|
|
1752
|
+
self._expect(TokenType.BLOCK_END)
|
|
1753
|
+
return node
|
|
1754
|
+
|
|
1678
1755
|
self._expect(TokenType.BLOCK_START)
|
|
1679
1756
|
|
|
1680
1757
|
while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
|
|
@@ -1905,13 +1982,14 @@ class CSSLParser:
|
|
|
1905
1982
|
"""Parse define function declaration.
|
|
1906
1983
|
|
|
1907
1984
|
Syntax:
|
|
1908
|
-
define MyFunc(args) { }
|
|
1909
|
-
global define MyFunc(args) { }
|
|
1910
|
-
define @MyFunc(args) { }
|
|
1911
|
-
define *MyFunc(args) { }
|
|
1912
|
-
define MyFunc : extends OtherFunc
|
|
1913
|
-
define MyFunc : overwrites OtherFunc
|
|
1914
|
-
define MyFunc
|
|
1985
|
+
define MyFunc(args) { } // Local function
|
|
1986
|
+
global define MyFunc(args) { } // Global function
|
|
1987
|
+
define @MyFunc(args) { } // Global function (alternative)
|
|
1988
|
+
define *MyFunc(args) { } // Non-null: must never return None
|
|
1989
|
+
define MyFunc(args) : extends OtherFunc { } // Inherit local vars
|
|
1990
|
+
define MyFunc(args) : overwrites OtherFunc { } // Replace OtherFunc
|
|
1991
|
+
define MyFunc(args) : supports python { } // Multi-language syntax
|
|
1992
|
+
define MyFunc(args) :: extends Parent::Method { } // Method-level inheritance
|
|
1915
1993
|
"""
|
|
1916
1994
|
# Check for * prefix (non-null function - must return non-null)
|
|
1917
1995
|
# Also *[type] for type exclusion (must NOT return that type)
|
|
@@ -1933,8 +2011,56 @@ class CSSLParser:
|
|
|
1933
2011
|
|
|
1934
2012
|
name = self._advance().value
|
|
1935
2013
|
|
|
1936
|
-
#
|
|
1937
|
-
#
|
|
2014
|
+
# Parse parameters FIRST (before :extends/:overwrites/:supports)
|
|
2015
|
+
# Syntax: define funcName(params) : extends/overwrites/supports { }
|
|
2016
|
+
params = []
|
|
2017
|
+
|
|
2018
|
+
if self._match(TokenType.PAREN_START):
|
|
2019
|
+
while not self._check(TokenType.PAREN_END):
|
|
2020
|
+
param_info = {}
|
|
2021
|
+
# Handle 'open' keyword for open parameters
|
|
2022
|
+
if self._match_keyword('open'):
|
|
2023
|
+
param_info['open'] = True
|
|
2024
|
+
# Handle type annotations (e.g., string, int, dynamic, etc.)
|
|
2025
|
+
if self._check(TokenType.KEYWORD):
|
|
2026
|
+
param_info['type'] = self._advance().value
|
|
2027
|
+
# Handle reference operator &
|
|
2028
|
+
if self._match(TokenType.AMPERSAND):
|
|
2029
|
+
param_info['ref'] = True
|
|
2030
|
+
# Handle * prefix for non-null parameters
|
|
2031
|
+
if self._match(TokenType.MULTIPLY):
|
|
2032
|
+
param_info['non_null'] = True
|
|
2033
|
+
# Get parameter name
|
|
2034
|
+
if self._check(TokenType.IDENTIFIER):
|
|
2035
|
+
param_name = self._advance().value
|
|
2036
|
+
# v4.2.0: Handle default parameter values (param = value)
|
|
2037
|
+
if self._match(TokenType.EQUALS):
|
|
2038
|
+
default_value = self._parse_expression()
|
|
2039
|
+
param_info['default'] = default_value
|
|
2040
|
+
if param_info:
|
|
2041
|
+
params.append({'name': param_name, **param_info})
|
|
2042
|
+
else:
|
|
2043
|
+
params.append(param_name)
|
|
2044
|
+
self._match(TokenType.COMMA)
|
|
2045
|
+
elif self._check(TokenType.KEYWORD):
|
|
2046
|
+
# Parameter name could be a keyword like 'Params'
|
|
2047
|
+
param_name = self._advance().value
|
|
2048
|
+
# v4.2.0: Handle default parameter values (param = value)
|
|
2049
|
+
if self._match(TokenType.EQUALS):
|
|
2050
|
+
default_value = self._parse_expression()
|
|
2051
|
+
param_info['default'] = default_value
|
|
2052
|
+
if param_info:
|
|
2053
|
+
params.append({'name': param_name, **param_info})
|
|
2054
|
+
else:
|
|
2055
|
+
params.append(param_name)
|
|
2056
|
+
self._match(TokenType.COMMA)
|
|
2057
|
+
else:
|
|
2058
|
+
break
|
|
2059
|
+
self._expect(TokenType.PAREN_END)
|
|
2060
|
+
|
|
2061
|
+
# Check for extends/overwrites/supports AFTER parameters
|
|
2062
|
+
# Syntax: define func(params) : extends/overwrites target { }
|
|
2063
|
+
# Also supports method-level :: syntax: define func() :: extends Parent::method
|
|
1938
2064
|
extends_func = None
|
|
1939
2065
|
overwrites_func = None
|
|
1940
2066
|
extends_is_python = False
|
|
@@ -1943,6 +2069,7 @@ class CSSLParser:
|
|
|
1943
2069
|
extends_method_ref = None
|
|
1944
2070
|
overwrites_class_ref = None
|
|
1945
2071
|
overwrites_method_ref = None
|
|
2072
|
+
supports_language = None # v4.1.0: Multi-language syntax support
|
|
1946
2073
|
|
|
1947
2074
|
if self._match(TokenType.DOUBLE_COLON) or self._match(TokenType.COLON):
|
|
1948
2075
|
# Parse extends and/or overwrites (supports :: method-level syntax)
|
|
@@ -2003,49 +2130,24 @@ class CSSLParser:
|
|
|
2003
2130
|
# Skip optional () after function/method name
|
|
2004
2131
|
if self._match(TokenType.PAREN_START):
|
|
2005
2132
|
self._expect(TokenType.PAREN_END)
|
|
2133
|
+
# v4.1.0: Parse 'supports' keyword for multi-language syntax
|
|
2134
|
+
elif self._match_keyword('supports'):
|
|
2135
|
+
if self._check(TokenType.AT):
|
|
2136
|
+
self._advance() # consume @
|
|
2137
|
+
if self._check(TokenType.IDENTIFIER):
|
|
2138
|
+
supports_language = '@' + self._advance().value
|
|
2139
|
+
else:
|
|
2140
|
+
raise CSSLSyntaxError("Expected language identifier after '@' in 'supports'")
|
|
2141
|
+
elif self._check(TokenType.IDENTIFIER):
|
|
2142
|
+
supports_language = self._advance().value
|
|
2143
|
+
else:
|
|
2144
|
+
raise CSSLSyntaxError("Expected language identifier after 'supports'")
|
|
2006
2145
|
else:
|
|
2007
2146
|
break
|
|
2008
2147
|
# Check for another :: or : for chaining extends/overwrites
|
|
2009
2148
|
if not (self._match(TokenType.DOUBLE_COLON) or self._match(TokenType.COLON)):
|
|
2010
2149
|
break
|
|
2011
2150
|
|
|
2012
|
-
params = []
|
|
2013
|
-
|
|
2014
|
-
if self._match(TokenType.PAREN_START):
|
|
2015
|
-
while not self._check(TokenType.PAREN_END):
|
|
2016
|
-
param_info = {}
|
|
2017
|
-
# Handle 'open' keyword for open parameters
|
|
2018
|
-
if self._match_keyword('open'):
|
|
2019
|
-
param_info['open'] = True
|
|
2020
|
-
# Handle type annotations (e.g., string, int, dynamic, etc.)
|
|
2021
|
-
if self._check(TokenType.KEYWORD):
|
|
2022
|
-
param_info['type'] = self._advance().value
|
|
2023
|
-
# Handle reference operator &
|
|
2024
|
-
if self._match(TokenType.AMPERSAND):
|
|
2025
|
-
param_info['ref'] = True
|
|
2026
|
-
# Handle * prefix for non-null parameters
|
|
2027
|
-
if self._match(TokenType.MULTIPLY):
|
|
2028
|
-
param_info['non_null'] = True
|
|
2029
|
-
# Get parameter name
|
|
2030
|
-
if self._check(TokenType.IDENTIFIER):
|
|
2031
|
-
param_name = self._advance().value
|
|
2032
|
-
if param_info:
|
|
2033
|
-
params.append({'name': param_name, **param_info})
|
|
2034
|
-
else:
|
|
2035
|
-
params.append(param_name)
|
|
2036
|
-
self._match(TokenType.COMMA)
|
|
2037
|
-
elif self._check(TokenType.KEYWORD):
|
|
2038
|
-
# Parameter name could be a keyword like 'Params'
|
|
2039
|
-
param_name = self._advance().value
|
|
2040
|
-
if param_info:
|
|
2041
|
-
params.append({'name': param_name, **param_info})
|
|
2042
|
-
else:
|
|
2043
|
-
params.append(param_name)
|
|
2044
|
-
self._match(TokenType.COMMA)
|
|
2045
|
-
else:
|
|
2046
|
-
break
|
|
2047
|
-
self._expect(TokenType.PAREN_END)
|
|
2048
|
-
|
|
2049
2151
|
# New: Append mode and reference tracking for functions
|
|
2050
2152
|
# Syntax: define XYZ(int zahl) &overwrittenclass::functionyouwanttokeep ++ { ... }
|
|
2051
2153
|
append_mode = False
|
|
@@ -2072,6 +2174,37 @@ class CSSLParser:
|
|
|
2072
2174
|
if self._match(TokenType.PLUS_PLUS):
|
|
2073
2175
|
append_mode = True
|
|
2074
2176
|
|
|
2177
|
+
# v4.2.0: Allow 'supports' AFTER &Class::member reference
|
|
2178
|
+
# Syntax: define func() &$pyclass::method : supports python { }
|
|
2179
|
+
if self._match(TokenType.COLON) or self._match(TokenType.DOUBLE_COLON):
|
|
2180
|
+
if self._match_keyword('supports'):
|
|
2181
|
+
if self._check(TokenType.AT):
|
|
2182
|
+
self._advance()
|
|
2183
|
+
if self._check(TokenType.IDENTIFIER):
|
|
2184
|
+
supports_language = '@' + self._advance().value
|
|
2185
|
+
else:
|
|
2186
|
+
raise CSSLSyntaxError("Expected language identifier after '@' in 'supports'")
|
|
2187
|
+
elif self._check(TokenType.IDENTIFIER):
|
|
2188
|
+
supports_language = self._advance().value
|
|
2189
|
+
else:
|
|
2190
|
+
raise CSSLSyntaxError("Expected language identifier after 'supports'")
|
|
2191
|
+
|
|
2192
|
+
self._expect(TokenType.BLOCK_START)
|
|
2193
|
+
|
|
2194
|
+
# v4.2.0: Extract raw body when supports_language is set for transformation
|
|
2195
|
+
raw_body = None
|
|
2196
|
+
children = []
|
|
2197
|
+
if supports_language:
|
|
2198
|
+
raw_body = self._extract_raw_block_body()
|
|
2199
|
+
# _extract_raw_block_body positions cursor at BLOCK_END
|
|
2200
|
+
else:
|
|
2201
|
+
while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
|
|
2202
|
+
stmt = self._parse_statement()
|
|
2203
|
+
if stmt:
|
|
2204
|
+
children.append(stmt)
|
|
2205
|
+
|
|
2206
|
+
self._expect(TokenType.BLOCK_END)
|
|
2207
|
+
|
|
2075
2208
|
node = ASTNode('function', value={
|
|
2076
2209
|
'name': name,
|
|
2077
2210
|
'is_global': is_global,
|
|
@@ -2090,16 +2223,13 @@ class CSSLParser:
|
|
|
2090
2223
|
# New append mode fields
|
|
2091
2224
|
'append_mode': append_mode,
|
|
2092
2225
|
'append_ref_class': append_ref_class,
|
|
2093
|
-
'append_ref_member': append_ref_member
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
if stmt:
|
|
2100
|
-
node.children.append(stmt)
|
|
2226
|
+
'append_ref_member': append_ref_member,
|
|
2227
|
+
# v4.1.0: Multi-language support
|
|
2228
|
+
'supports_language': supports_language,
|
|
2229
|
+
# v4.2.0: Raw body for language transformation
|
|
2230
|
+
'raw_body': raw_body
|
|
2231
|
+
}, children=children)
|
|
2101
2232
|
|
|
2102
|
-
self._expect(TokenType.BLOCK_END)
|
|
2103
2233
|
return node
|
|
2104
2234
|
|
|
2105
2235
|
def _parse_statement(self) -> Optional[ASTNode]:
|
|
@@ -2125,6 +2255,9 @@ class CSSLParser:
|
|
|
2125
2255
|
return self._parse_try()
|
|
2126
2256
|
elif self._match_keyword('await'):
|
|
2127
2257
|
return self._parse_await()
|
|
2258
|
+
elif self._match_keyword('supports'):
|
|
2259
|
+
# v4.2.0: Standalone supports block for multi-language syntax
|
|
2260
|
+
return self._parse_supports_block()
|
|
2128
2261
|
elif self._match_keyword('define'):
|
|
2129
2262
|
# Nested define function
|
|
2130
2263
|
return self._parse_define()
|
|
@@ -2142,9 +2275,11 @@ class CSSLParser:
|
|
|
2142
2275
|
self._peek(1).type == TokenType.DOUBLE_COLON)):
|
|
2143
2276
|
# super() or super::method() call - calls parent constructor/method
|
|
2144
2277
|
return self._parse_super_call()
|
|
2278
|
+
# v4.2.1: Added LANG_INSTANCE_REF for lang$instance statements
|
|
2145
2279
|
elif (self._check(TokenType.IDENTIFIER) or self._check(TokenType.AT) or
|
|
2146
2280
|
self._check(TokenType.CAPTURED_REF) or self._check(TokenType.SHARED_REF) or
|
|
2147
2281
|
self._check(TokenType.GLOBAL_REF) or self._check(TokenType.SELF_REF) or
|
|
2282
|
+
self._check(TokenType.LANG_INSTANCE_REF) or
|
|
2148
2283
|
(self._check(TokenType.KEYWORD) and self._current().value in ('this', 'new')) or
|
|
2149
2284
|
self._looks_like_namespace_call()):
|
|
2150
2285
|
return self._parse_expression_statement()
|
|
@@ -2350,42 +2485,40 @@ class CSSLParser:
|
|
|
2350
2485
|
|
|
2351
2486
|
Supports: i = i + 1, i++, ++i, i += 1, i -= 1
|
|
2352
2487
|
"""
|
|
2353
|
-
# Check for prefix increment/decrement: ++i or --i
|
|
2354
|
-
if self._check(TokenType.
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2488
|
+
# Check for prefix increment/decrement: ++i or --i (as single PLUS_PLUS/MINUS_MINUS token)
|
|
2489
|
+
if self._check(TokenType.PLUS_PLUS):
|
|
2490
|
+
self._advance()
|
|
2491
|
+
var_name = self._advance().value
|
|
2492
|
+
return ASTNode('c_for_update', value={'var': var_name, 'op': 'increment'})
|
|
2493
|
+
elif self._check(TokenType.MINUS_MINUS):
|
|
2494
|
+
self._advance()
|
|
2495
|
+
var_name = self._advance().value
|
|
2496
|
+
return ASTNode('c_for_update', value={'var': var_name, 'op': 'decrement'})
|
|
2362
2497
|
|
|
2363
2498
|
# Regular variable assignment or postfix
|
|
2364
2499
|
var_name = self._advance().value
|
|
2365
2500
|
|
|
2366
|
-
# Check for postfix increment/decrement: i++ or i--
|
|
2367
|
-
if self._check(TokenType.
|
|
2501
|
+
# Check for postfix increment/decrement: i++ or i-- (as single PLUS_PLUS/MINUS_MINUS token)
|
|
2502
|
+
if self._check(TokenType.PLUS_PLUS):
|
|
2368
2503
|
self._advance()
|
|
2369
|
-
|
|
2504
|
+
return ASTNode('c_for_update', value={'var': var_name, 'op': 'increment'})
|
|
2505
|
+
elif self._check(TokenType.MINUS_MINUS):
|
|
2506
|
+
self._advance()
|
|
2507
|
+
return ASTNode('c_for_update', value={'var': var_name, 'op': 'decrement'})
|
|
2508
|
+
# i += value
|
|
2509
|
+
elif self._check(TokenType.PLUS):
|
|
2510
|
+
self._advance()
|
|
2511
|
+
if self._check(TokenType.EQUALS):
|
|
2370
2512
|
self._advance()
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
if self._check(TokenType.EQUALS):
|
|
2375
|
-
self._advance()
|
|
2376
|
-
value = self._parse_expression()
|
|
2377
|
-
return ASTNode('c_for_update', value={'var': var_name, 'op': 'add', 'value': value})
|
|
2513
|
+
value = self._parse_expression()
|
|
2514
|
+
return ASTNode('c_for_update', value={'var': var_name, 'op': 'add', 'value': value})
|
|
2515
|
+
# i -= value
|
|
2378
2516
|
elif self._check(TokenType.MINUS):
|
|
2379
2517
|
self._advance()
|
|
2380
|
-
if self._check(TokenType.
|
|
2518
|
+
if self._check(TokenType.EQUALS):
|
|
2381
2519
|
self._advance()
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
# i -= value
|
|
2385
|
-
if self._check(TokenType.EQUALS):
|
|
2386
|
-
self._advance()
|
|
2387
|
-
value = self._parse_expression()
|
|
2388
|
-
return ASTNode('c_for_update', value={'var': var_name, 'op': 'subtract', 'value': value})
|
|
2520
|
+
value = self._parse_expression()
|
|
2521
|
+
return ASTNode('c_for_update', value={'var': var_name, 'op': 'subtract', 'value': value})
|
|
2389
2522
|
|
|
2390
2523
|
# Regular assignment: i = expression
|
|
2391
2524
|
if self._check(TokenType.EQUALS):
|
|
@@ -2630,6 +2763,163 @@ class CSSLParser:
|
|
|
2630
2763
|
self._match(TokenType.SEMICOLON)
|
|
2631
2764
|
return ASTNode('await', value=expr)
|
|
2632
2765
|
|
|
2766
|
+
def _parse_supports_block(self) -> ASTNode:
|
|
2767
|
+
"""Parse standalone supports block for multi-language syntax.
|
|
2768
|
+
|
|
2769
|
+
v4.2.0: Allows 'supports' to be used anywhere, not just in class/function.
|
|
2770
|
+
|
|
2771
|
+
Syntax:
|
|
2772
|
+
supports py { } // Python syntax block
|
|
2773
|
+
supports @py { } // With @ prefix
|
|
2774
|
+
supports python { } // Full language name
|
|
2775
|
+
supports cpp { } // C++ syntax block
|
|
2776
|
+
supports javascript { } // JavaScript syntax block
|
|
2777
|
+
|
|
2778
|
+
Example:
|
|
2779
|
+
supports py {
|
|
2780
|
+
for i in range(10):
|
|
2781
|
+
print(i)
|
|
2782
|
+
}
|
|
2783
|
+
|
|
2784
|
+
supports cpp {
|
|
2785
|
+
std::cout << "Hello" << std::endl;
|
|
2786
|
+
int x = 42;
|
|
2787
|
+
}
|
|
2788
|
+
"""
|
|
2789
|
+
# Parse language identifier
|
|
2790
|
+
language = None
|
|
2791
|
+
if self._check(TokenType.AT):
|
|
2792
|
+
self._advance()
|
|
2793
|
+
if self._check(TokenType.IDENTIFIER):
|
|
2794
|
+
language = '@' + self._advance().value
|
|
2795
|
+
else:
|
|
2796
|
+
raise CSSLSyntaxError("Expected language identifier after '@' in 'supports'")
|
|
2797
|
+
elif self._check(TokenType.IDENTIFIER):
|
|
2798
|
+
language = self._advance().value
|
|
2799
|
+
else:
|
|
2800
|
+
raise CSSLSyntaxError("Expected language identifier after 'supports'")
|
|
2801
|
+
|
|
2802
|
+
# Extract raw block source for language transformation (preserves indentation)
|
|
2803
|
+
raw_source = None
|
|
2804
|
+
if self._check(TokenType.BLOCK_START):
|
|
2805
|
+
raw_source = self._extract_raw_block_body()
|
|
2806
|
+
|
|
2807
|
+
# Skip parsing body if we have raw_source - runtime will transform and parse
|
|
2808
|
+
body = []
|
|
2809
|
+
self._expect(TokenType.BLOCK_START)
|
|
2810
|
+
if raw_source is None:
|
|
2811
|
+
# No raw source (e.g., already CSSL syntax), parse normally
|
|
2812
|
+
while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
|
|
2813
|
+
stmt = self._parse_statement()
|
|
2814
|
+
if stmt:
|
|
2815
|
+
body.append(stmt)
|
|
2816
|
+
else:
|
|
2817
|
+
# Skip the block - runtime will transform and parse
|
|
2818
|
+
brace_count = 1
|
|
2819
|
+
while brace_count > 0 and not self._is_at_end():
|
|
2820
|
+
if self._check(TokenType.BLOCK_START):
|
|
2821
|
+
brace_count += 1
|
|
2822
|
+
elif self._check(TokenType.BLOCK_END):
|
|
2823
|
+
brace_count -= 1
|
|
2824
|
+
if brace_count > 0:
|
|
2825
|
+
self._advance()
|
|
2826
|
+
self._expect(TokenType.BLOCK_END)
|
|
2827
|
+
|
|
2828
|
+
return ASTNode('supports_block', value={
|
|
2829
|
+
'language': language,
|
|
2830
|
+
'raw_source': raw_source
|
|
2831
|
+
}, children=body)
|
|
2832
|
+
|
|
2833
|
+
def _extract_raw_block_source(self) -> Optional[str]:
|
|
2834
|
+
"""Extract raw source code from a {} block before parsing.
|
|
2835
|
+
|
|
2836
|
+
Used for 'supports' blocks to allow language transformation.
|
|
2837
|
+
"""
|
|
2838
|
+
if not self._check(TokenType.BLOCK_START):
|
|
2839
|
+
return None
|
|
2840
|
+
|
|
2841
|
+
# Find the matching block end by counting braces
|
|
2842
|
+
start_pos = self.pos
|
|
2843
|
+
brace_count = 0
|
|
2844
|
+
found_start = False
|
|
2845
|
+
|
|
2846
|
+
# Walk through tokens to find matching close brace
|
|
2847
|
+
temp_pos = self.pos
|
|
2848
|
+
while temp_pos < len(self.tokens):
|
|
2849
|
+
token = self.tokens[temp_pos]
|
|
2850
|
+
if token.type == TokenType.BLOCK_START:
|
|
2851
|
+
if not found_start:
|
|
2852
|
+
found_start = True
|
|
2853
|
+
brace_count += 1
|
|
2854
|
+
elif token.type == TokenType.BLOCK_END:
|
|
2855
|
+
brace_count -= 1
|
|
2856
|
+
if brace_count == 0:
|
|
2857
|
+
break
|
|
2858
|
+
temp_pos += 1
|
|
2859
|
+
|
|
2860
|
+
# Build source from tokens between braces (excluding braces)
|
|
2861
|
+
source_parts = []
|
|
2862
|
+
for i in range(start_pos + 1, temp_pos):
|
|
2863
|
+
token = self.tokens[i]
|
|
2864
|
+
if token.type == TokenType.STRING:
|
|
2865
|
+
source_parts.append(f'"{token.value}"')
|
|
2866
|
+
elif token.type == TokenType.NEWLINE:
|
|
2867
|
+
source_parts.append('\n')
|
|
2868
|
+
else:
|
|
2869
|
+
source_parts.append(str(token.value))
|
|
2870
|
+
|
|
2871
|
+
return ' '.join(source_parts)
|
|
2872
|
+
|
|
2873
|
+
def _extract_raw_block_body(self) -> Optional[str]:
|
|
2874
|
+
"""Extract raw source code body from a {} block for language transformation.
|
|
2875
|
+
|
|
2876
|
+
v4.2.0: Used for 'supports' blocks to preserve original source (including indentation).
|
|
2877
|
+
This extracts the raw text between { and } from the original source string.
|
|
2878
|
+
|
|
2879
|
+
Returns the raw body content without the surrounding braces.
|
|
2880
|
+
"""
|
|
2881
|
+
if not self._check(TokenType.BLOCK_START):
|
|
2882
|
+
return None
|
|
2883
|
+
|
|
2884
|
+
# Get the { token's position
|
|
2885
|
+
start_token = self._current()
|
|
2886
|
+
start_line = start_token.line
|
|
2887
|
+
start_col = start_token.column
|
|
2888
|
+
|
|
2889
|
+
# Find the { character position in source
|
|
2890
|
+
# Line numbers are 1-indexed, columns are 1-indexed
|
|
2891
|
+
brace_start_pos = 0
|
|
2892
|
+
current_line = 1
|
|
2893
|
+
for i, char in enumerate(self.source):
|
|
2894
|
+
if current_line == start_line:
|
|
2895
|
+
# Found the right line, now find the column
|
|
2896
|
+
col_in_line = i - brace_start_pos + 1
|
|
2897
|
+
if col_in_line >= start_col:
|
|
2898
|
+
# Search for { from here
|
|
2899
|
+
for j in range(i, len(self.source)):
|
|
2900
|
+
if self.source[j] == '{':
|
|
2901
|
+
brace_start_pos = j
|
|
2902
|
+
break
|
|
2903
|
+
break
|
|
2904
|
+
if char == '\n':
|
|
2905
|
+
current_line += 1
|
|
2906
|
+
brace_start_pos = i + 1
|
|
2907
|
+
|
|
2908
|
+
# Now find the matching closing brace
|
|
2909
|
+
brace_count = 1
|
|
2910
|
+
pos = brace_start_pos + 1
|
|
2911
|
+
while pos < len(self.source) and brace_count > 0:
|
|
2912
|
+
char = self.source[pos]
|
|
2913
|
+
if char == '{':
|
|
2914
|
+
brace_count += 1
|
|
2915
|
+
elif char == '}':
|
|
2916
|
+
brace_count -= 1
|
|
2917
|
+
pos += 1
|
|
2918
|
+
|
|
2919
|
+
# Extract the body (everything between { and })
|
|
2920
|
+
body = self.source[brace_start_pos + 1:pos - 1]
|
|
2921
|
+
return body.strip()
|
|
2922
|
+
|
|
2633
2923
|
def _parse_action_block(self) -> ASTNode:
|
|
2634
2924
|
"""Parse an action block { ... } containing statements for createcmd"""
|
|
2635
2925
|
node = ASTNode('action_block', children=[])
|
|
@@ -2946,6 +3236,14 @@ class CSSLParser:
|
|
|
2946
3236
|
if self._match(TokenType.MINUS):
|
|
2947
3237
|
operand = self._parse_unary()
|
|
2948
3238
|
return ASTNode('unary', value={'op': '-', 'operand': operand})
|
|
3239
|
+
# Prefix increment: ++i
|
|
3240
|
+
if self._match(TokenType.PLUS_PLUS):
|
|
3241
|
+
operand = self._parse_unary()
|
|
3242
|
+
return ASTNode('increment', value={'op': 'prefix', 'operand': operand})
|
|
3243
|
+
# Prefix decrement: --i
|
|
3244
|
+
if self._match(TokenType.MINUS_MINUS):
|
|
3245
|
+
operand = self._parse_unary()
|
|
3246
|
+
return ASTNode('decrement', value={'op': 'prefix', 'operand': operand})
|
|
2949
3247
|
if self._match(TokenType.AMPERSAND):
|
|
2950
3248
|
# Reference operator: &variable or &@module
|
|
2951
3249
|
operand = self._parse_unary()
|
|
@@ -3088,12 +3386,13 @@ class CSSLParser:
|
|
|
3088
3386
|
token = self._advance()
|
|
3089
3387
|
node = ASTNode('global_ref', value=token.value, line=token.line, column=token.column)
|
|
3090
3388
|
# Check for member access, calls, indexing - with kwargs support
|
|
3389
|
+
# Support both . and -> for member access
|
|
3091
3390
|
while True:
|
|
3092
3391
|
if self._match(TokenType.PAREN_START):
|
|
3093
3392
|
args, kwargs = self._parse_call_arguments()
|
|
3094
3393
|
self._expect(TokenType.PAREN_END)
|
|
3095
3394
|
node = ASTNode('call', value={'callee': node, 'args': args, 'kwargs': kwargs})
|
|
3096
|
-
elif self._match(TokenType.DOT):
|
|
3395
|
+
elif self._match(TokenType.DOT) or self._match(TokenType.FLOW_RIGHT):
|
|
3097
3396
|
member = self._advance().value
|
|
3098
3397
|
node = ASTNode('member_access', value={'object': node, 'member': member})
|
|
3099
3398
|
elif self._match(TokenType.BRACKET_START):
|
|
@@ -3109,12 +3408,14 @@ class CSSLParser:
|
|
|
3109
3408
|
token = self._advance()
|
|
3110
3409
|
node = ASTNode('shared_ref', value=token.value, line=token.line, column=token.column)
|
|
3111
3410
|
# Check for member access, calls, indexing - with kwargs support
|
|
3411
|
+
# Support both . and -> for member access (like this->member)
|
|
3112
3412
|
while True:
|
|
3113
3413
|
if self._match(TokenType.PAREN_START):
|
|
3114
3414
|
args, kwargs = self._parse_call_arguments()
|
|
3115
3415
|
self._expect(TokenType.PAREN_END)
|
|
3116
3416
|
node = ASTNode('call', value={'callee': node, 'args': args, 'kwargs': kwargs})
|
|
3117
|
-
elif self._match(TokenType.DOT):
|
|
3417
|
+
elif self._match(TokenType.DOT) or self._match(TokenType.FLOW_RIGHT):
|
|
3418
|
+
# Support both $obj.member and $obj->member syntax
|
|
3118
3419
|
member = self._advance().value
|
|
3119
3420
|
node = ASTNode('member_access', value={'object': node, 'member': member})
|
|
3120
3421
|
elif self._match(TokenType.BRACKET_START):
|
|
@@ -3130,12 +3431,36 @@ class CSSLParser:
|
|
|
3130
3431
|
token = self._advance()
|
|
3131
3432
|
node = ASTNode('captured_ref', value=token.value, line=token.line, column=token.column)
|
|
3132
3433
|
# Check for member access, calls, indexing - with kwargs support
|
|
3434
|
+
# Support both . and -> for member access
|
|
3133
3435
|
while True:
|
|
3134
3436
|
if self._match(TokenType.PAREN_START):
|
|
3135
3437
|
args, kwargs = self._parse_call_arguments()
|
|
3136
3438
|
self._expect(TokenType.PAREN_END)
|
|
3137
3439
|
node = ASTNode('call', value={'callee': node, 'args': args, 'kwargs': kwargs})
|
|
3138
|
-
elif self._match(TokenType.DOT):
|
|
3440
|
+
elif self._match(TokenType.DOT) or self._match(TokenType.FLOW_RIGHT):
|
|
3441
|
+
member = self._advance().value
|
|
3442
|
+
node = ASTNode('member_access', value={'object': node, 'member': member})
|
|
3443
|
+
elif self._match(TokenType.BRACKET_START):
|
|
3444
|
+
index = self._parse_expression()
|
|
3445
|
+
self._expect(TokenType.BRACKET_END)
|
|
3446
|
+
node = ASTNode('index_access', value={'object': node, 'index': index})
|
|
3447
|
+
else:
|
|
3448
|
+
break
|
|
3449
|
+
return node
|
|
3450
|
+
|
|
3451
|
+
# v4.1.0: Cross-language instance reference: cpp$ClassName, py$Object
|
|
3452
|
+
if self._check(TokenType.LANG_INSTANCE_REF):
|
|
3453
|
+
token = self._advance()
|
|
3454
|
+
ref = token.value # {'lang': 'cpp', 'instance': 'ClassName'}
|
|
3455
|
+
node = ASTNode('lang_instance_ref', value=ref, line=token.line, column=token.column)
|
|
3456
|
+
# Check for member access, calls, indexing
|
|
3457
|
+
# Support both . and -> for member access
|
|
3458
|
+
while True:
|
|
3459
|
+
if self._match(TokenType.PAREN_START):
|
|
3460
|
+
args, kwargs = self._parse_call_arguments()
|
|
3461
|
+
self._expect(TokenType.PAREN_END)
|
|
3462
|
+
node = ASTNode('call', value={'callee': node, 'args': args, 'kwargs': kwargs})
|
|
3463
|
+
elif self._match(TokenType.DOT) or self._match(TokenType.FLOW_RIGHT):
|
|
3139
3464
|
member = self._advance().value
|
|
3140
3465
|
node = ASTNode('member_access', value={'object': node, 'member': member})
|
|
3141
3466
|
elif self._match(TokenType.BRACKET_START):
|
|
@@ -3396,6 +3721,12 @@ class CSSLParser:
|
|
|
3396
3721
|
index = self._parse_expression()
|
|
3397
3722
|
self._expect(TokenType.BRACKET_END)
|
|
3398
3723
|
node = ASTNode('index_access', value={'object': node, 'index': index})
|
|
3724
|
+
# Postfix increment: i++
|
|
3725
|
+
elif self._match(TokenType.PLUS_PLUS):
|
|
3726
|
+
node = ASTNode('increment', value={'op': 'postfix', 'operand': node})
|
|
3727
|
+
# Postfix decrement: i--
|
|
3728
|
+
elif self._match(TokenType.MINUS_MINUS):
|
|
3729
|
+
node = ASTNode('decrement', value={'op': 'postfix', 'operand': node})
|
|
3399
3730
|
else:
|
|
3400
3731
|
break
|
|
3401
3732
|
|
|
@@ -3435,9 +3766,11 @@ class CSSLParser:
|
|
|
3435
3766
|
|
|
3436
3767
|
while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
|
|
3437
3768
|
# Parse statement or expression
|
|
3769
|
+
# v4.2.1: Added LANG_INSTANCE_REF for lang$instance statements
|
|
3438
3770
|
if (self._check(TokenType.IDENTIFIER) or self._check(TokenType.AT) or
|
|
3439
3771
|
self._check(TokenType.CAPTURED_REF) or self._check(TokenType.SHARED_REF) or
|
|
3440
3772
|
self._check(TokenType.GLOBAL_REF) or self._check(TokenType.SELF_REF) or
|
|
3773
|
+
self._check(TokenType.LANG_INSTANCE_REF) or
|
|
3441
3774
|
self._check(TokenType.STRING) or self._check(TokenType.NUMBER) or
|
|
3442
3775
|
self._check(TokenType.BOOLEAN) or self._check(TokenType.NULL) or
|
|
3443
3776
|
self._check(TokenType.PAREN_START)):
|
|
@@ -3506,7 +3839,7 @@ def parse_cssl(source: str) -> ASTNode:
|
|
|
3506
3839
|
"""Parse CSSL source code into an AST - auto-detects service vs program format"""
|
|
3507
3840
|
lexer = CSSLLexer(source)
|
|
3508
3841
|
tokens = lexer.tokenize()
|
|
3509
|
-
parser = CSSLParser(tokens, lexer.source_lines)
|
|
3842
|
+
parser = CSSLParser(tokens, lexer.source_lines, source) # v4.2.0: Pass source for raw extraction
|
|
3510
3843
|
|
|
3511
3844
|
# Auto-detect: if first token is '{', it's a service file
|
|
3512
3845
|
# Otherwise treat as standalone program (whitespace is already filtered by lexer)
|
|
@@ -3520,7 +3853,7 @@ def parse_cssl_program(source: str) -> ASTNode:
|
|
|
3520
3853
|
"""Parse standalone CSSL program (no service wrapper) into an AST"""
|
|
3521
3854
|
lexer = CSSLLexer(source)
|
|
3522
3855
|
tokens = lexer.tokenize()
|
|
3523
|
-
parser = CSSLParser(tokens, lexer.source_lines)
|
|
3856
|
+
parser = CSSLParser(tokens, lexer.source_lines, source) # v4.2.0: Pass source for raw extraction
|
|
3524
3857
|
return parser.parse_program()
|
|
3525
3858
|
|
|
3526
3859
|
|