IncludeCPP 4.0.3__py3-none-any.whl → 4.2.1__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 +164 -0
- includecpp/DOCUMENTATION.md +593 -0
- includecpp/__init__.py +1 -1
- includecpp/__init__.pyi +4 -1
- includecpp/cli/commands.py +698 -84
- 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 +424 -98
- includecpp/core/cssl/cssl_runtime.py +480 -38
- includecpp/core/cssl/cssl_syntax.py +88 -4
- includecpp/core/cssl/cssl_types.py +31 -2
- includecpp/generator/parser.cpp +121 -4
- includecpp/generator/parser.h +6 -0
- {includecpp-4.0.3.dist-info → includecpp-4.2.1.dist-info}/METADATA +101 -1
- {includecpp-4.0.3.dist-info → includecpp-4.2.1.dist-info}/RECORD +22 -18
- {includecpp-4.0.3.dist-info → includecpp-4.2.1.dist-info}/WHEEL +0 -0
- {includecpp-4.0.3.dist-info → includecpp-4.2.1.dist-info}/entry_points.txt +0 -0
- {includecpp-4.0.3.dist-info → includecpp-4.2.1.dist-info}/licenses/LICENSE +0 -0
- {includecpp-4.0.3.dist-info → includecpp-4.2.1.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"""
|
|
@@ -1350,13 +1380,7 @@ class CSSLParser:
|
|
|
1350
1380
|
# Wrap in global_assignment to mark as global variable (same as 'global' keyword)
|
|
1351
1381
|
global_stmt = ASTNode('global_assignment', value=stmt)
|
|
1352
1382
|
root.children.append(global_stmt)
|
|
1353
|
-
#
|
|
1354
|
-
elif (self._check(TokenType.IDENTIFIER) or self._check(TokenType.AT) or
|
|
1355
|
-
self._check(TokenType.SELF_REF) or self._check(TokenType.SHARED_REF) or
|
|
1356
|
-
self._check(TokenType.KEYWORD)):
|
|
1357
|
-
stmt = self._parse_expression_statement()
|
|
1358
|
-
if stmt:
|
|
1359
|
-
root.children.append(stmt)
|
|
1383
|
+
# Control flow keywords must be checked BEFORE generic KEYWORD handling
|
|
1360
1384
|
elif self._match_keyword('if'):
|
|
1361
1385
|
root.children.append(self._parse_if())
|
|
1362
1386
|
elif self._match_keyword('while'):
|
|
@@ -1365,6 +1389,13 @@ class CSSLParser:
|
|
|
1365
1389
|
root.children.append(self._parse_for())
|
|
1366
1390
|
elif self._match_keyword('foreach'):
|
|
1367
1391
|
root.children.append(self._parse_foreach())
|
|
1392
|
+
# Handle statements - keywords like 'instance', 'list', 'map' can be variable names
|
|
1393
|
+
elif (self._check(TokenType.IDENTIFIER) or self._check(TokenType.AT) or
|
|
1394
|
+
self._check(TokenType.SELF_REF) or self._check(TokenType.SHARED_REF) or
|
|
1395
|
+
self._check(TokenType.KEYWORD)):
|
|
1396
|
+
stmt = self._parse_expression_statement()
|
|
1397
|
+
if stmt:
|
|
1398
|
+
root.children.append(stmt)
|
|
1368
1399
|
# Skip comments and newlines
|
|
1369
1400
|
elif self._check(TokenType.COMMENT) or self._check(TokenType.NEWLINE):
|
|
1370
1401
|
self._advance()
|
|
@@ -1627,15 +1658,22 @@ class CSSLParser:
|
|
|
1627
1658
|
# class Child : extends Parent (param1, param2) { ... } <- constructor args for parent
|
|
1628
1659
|
extends_class = None
|
|
1629
1660
|
extends_is_python = False
|
|
1661
|
+
extends_lang_ref = None # v4.1.0: Cross-language inheritance (cpp$ClassName)
|
|
1630
1662
|
extends_args = []
|
|
1631
1663
|
overwrites_class = None
|
|
1632
1664
|
overwrites_is_python = False
|
|
1665
|
+
supports_language = None # v4.1.0: Multi-language syntax support
|
|
1633
1666
|
|
|
1634
1667
|
if self._match(TokenType.DOUBLE_COLON) or self._match(TokenType.COLON):
|
|
1635
1668
|
# Parse extends and/or overwrites (can be chained with : or ::)
|
|
1636
1669
|
while True:
|
|
1637
1670
|
if self._match_keyword('extends'):
|
|
1638
|
-
|
|
1671
|
+
# v4.1.0: Check for cross-language inheritance: extends cpp$ClassName
|
|
1672
|
+
if self._check(TokenType.LANG_INSTANCE_REF):
|
|
1673
|
+
ref = self._advance().value
|
|
1674
|
+
extends_lang_ref = ref # {'lang': 'cpp', 'instance': 'ClassName'}
|
|
1675
|
+
extends_class = ref['instance']
|
|
1676
|
+
elif self._check(TokenType.IDENTIFIER):
|
|
1639
1677
|
extends_class = self._advance().value
|
|
1640
1678
|
elif self._check(TokenType.SHARED_REF):
|
|
1641
1679
|
extends_class = self._advance().value
|
|
@@ -1660,12 +1698,29 @@ class CSSLParser:
|
|
|
1660
1698
|
# Skip optional () after class name
|
|
1661
1699
|
if self._match(TokenType.PAREN_START):
|
|
1662
1700
|
self._expect(TokenType.PAREN_END)
|
|
1701
|
+
# v4.1.0: Parse 'supports' keyword for multi-language syntax
|
|
1702
|
+
elif self._match_keyword('supports'):
|
|
1703
|
+
if self._check(TokenType.AT):
|
|
1704
|
+
self._advance() # consume @
|
|
1705
|
+
if self._check(TokenType.IDENTIFIER):
|
|
1706
|
+
supports_language = '@' + self._advance().value
|
|
1707
|
+
else:
|
|
1708
|
+
raise CSSLSyntaxError("Expected language identifier after '@' in 'supports'")
|
|
1709
|
+
elif self._check(TokenType.IDENTIFIER):
|
|
1710
|
+
supports_language = self._advance().value
|
|
1711
|
+
else:
|
|
1712
|
+
raise CSSLSyntaxError("Expected language identifier after 'supports'")
|
|
1663
1713
|
else:
|
|
1664
|
-
raise CSSLSyntaxError("Expected 'extends' or '
|
|
1714
|
+
raise CSSLSyntaxError("Expected 'extends', 'overwrites', or 'supports' after ':' or '::' in class declaration")
|
|
1665
1715
|
# Check for another : or :: for chaining
|
|
1666
1716
|
if not (self._match(TokenType.DOUBLE_COLON) or self._match(TokenType.COLON)):
|
|
1667
1717
|
break
|
|
1668
1718
|
|
|
1719
|
+
# v4.2.0: If supports_language is set, capture raw body for runtime transformation
|
|
1720
|
+
raw_body = None
|
|
1721
|
+
if supports_language:
|
|
1722
|
+
raw_body = self._extract_raw_block_body()
|
|
1723
|
+
|
|
1669
1724
|
node = ASTNode('class', value={
|
|
1670
1725
|
'name': class_name,
|
|
1671
1726
|
'is_global': is_global,
|
|
@@ -1673,10 +1728,29 @@ class CSSLParser:
|
|
|
1673
1728
|
'class_params': class_params,
|
|
1674
1729
|
'extends': extends_class,
|
|
1675
1730
|
'extends_is_python': extends_is_python,
|
|
1731
|
+
'extends_lang_ref': extends_lang_ref, # v4.1.0
|
|
1676
1732
|
'extends_args': extends_args,
|
|
1677
1733
|
'overwrites': overwrites_class,
|
|
1678
|
-
'overwrites_is_python': overwrites_is_python
|
|
1734
|
+
'overwrites_is_python': overwrites_is_python,
|
|
1735
|
+
'supports_language': supports_language, # v4.1.0
|
|
1736
|
+
'raw_body': raw_body # v4.2.0: Raw body for language transformation
|
|
1679
1737
|
}, children=[])
|
|
1738
|
+
|
|
1739
|
+
# v4.2.0: If we have raw_body for language transformation, skip regular parsing
|
|
1740
|
+
if raw_body is not None:
|
|
1741
|
+
# Skip the block entirely - runtime will transform and parse
|
|
1742
|
+
self._expect(TokenType.BLOCK_START)
|
|
1743
|
+
brace_count = 1
|
|
1744
|
+
while brace_count > 0 and not self._is_at_end():
|
|
1745
|
+
if self._check(TokenType.BLOCK_START):
|
|
1746
|
+
brace_count += 1
|
|
1747
|
+
elif self._check(TokenType.BLOCK_END):
|
|
1748
|
+
brace_count -= 1
|
|
1749
|
+
if brace_count > 0:
|
|
1750
|
+
self._advance()
|
|
1751
|
+
self._expect(TokenType.BLOCK_END)
|
|
1752
|
+
return node
|
|
1753
|
+
|
|
1680
1754
|
self._expect(TokenType.BLOCK_START)
|
|
1681
1755
|
|
|
1682
1756
|
while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
|
|
@@ -1907,13 +1981,14 @@ class CSSLParser:
|
|
|
1907
1981
|
"""Parse define function declaration.
|
|
1908
1982
|
|
|
1909
1983
|
Syntax:
|
|
1910
|
-
define MyFunc(args) { }
|
|
1911
|
-
global define MyFunc(args) { }
|
|
1912
|
-
define @MyFunc(args) { }
|
|
1913
|
-
define *MyFunc(args) { }
|
|
1914
|
-
define MyFunc : extends OtherFunc
|
|
1915
|
-
define MyFunc : overwrites OtherFunc
|
|
1916
|
-
define MyFunc
|
|
1984
|
+
define MyFunc(args) { } // Local function
|
|
1985
|
+
global define MyFunc(args) { } // Global function
|
|
1986
|
+
define @MyFunc(args) { } // Global function (alternative)
|
|
1987
|
+
define *MyFunc(args) { } // Non-null: must never return None
|
|
1988
|
+
define MyFunc(args) : extends OtherFunc { } // Inherit local vars
|
|
1989
|
+
define MyFunc(args) : overwrites OtherFunc { } // Replace OtherFunc
|
|
1990
|
+
define MyFunc(args) : supports python { } // Multi-language syntax
|
|
1991
|
+
define MyFunc(args) :: extends Parent::Method { } // Method-level inheritance
|
|
1917
1992
|
"""
|
|
1918
1993
|
# Check for * prefix (non-null function - must return non-null)
|
|
1919
1994
|
# Also *[type] for type exclusion (must NOT return that type)
|
|
@@ -1935,8 +2010,56 @@ class CSSLParser:
|
|
|
1935
2010
|
|
|
1936
2011
|
name = self._advance().value
|
|
1937
2012
|
|
|
1938
|
-
#
|
|
1939
|
-
#
|
|
2013
|
+
# Parse parameters FIRST (before :extends/:overwrites/:supports)
|
|
2014
|
+
# Syntax: define funcName(params) : extends/overwrites/supports { }
|
|
2015
|
+
params = []
|
|
2016
|
+
|
|
2017
|
+
if self._match(TokenType.PAREN_START):
|
|
2018
|
+
while not self._check(TokenType.PAREN_END):
|
|
2019
|
+
param_info = {}
|
|
2020
|
+
# Handle 'open' keyword for open parameters
|
|
2021
|
+
if self._match_keyword('open'):
|
|
2022
|
+
param_info['open'] = True
|
|
2023
|
+
# Handle type annotations (e.g., string, int, dynamic, etc.)
|
|
2024
|
+
if self._check(TokenType.KEYWORD):
|
|
2025
|
+
param_info['type'] = self._advance().value
|
|
2026
|
+
# Handle reference operator &
|
|
2027
|
+
if self._match(TokenType.AMPERSAND):
|
|
2028
|
+
param_info['ref'] = True
|
|
2029
|
+
# Handle * prefix for non-null parameters
|
|
2030
|
+
if self._match(TokenType.MULTIPLY):
|
|
2031
|
+
param_info['non_null'] = True
|
|
2032
|
+
# Get parameter name
|
|
2033
|
+
if self._check(TokenType.IDENTIFIER):
|
|
2034
|
+
param_name = self._advance().value
|
|
2035
|
+
# v4.2.0: Handle default parameter values (param = value)
|
|
2036
|
+
if self._match(TokenType.EQUALS):
|
|
2037
|
+
default_value = self._parse_expression()
|
|
2038
|
+
param_info['default'] = default_value
|
|
2039
|
+
if param_info:
|
|
2040
|
+
params.append({'name': param_name, **param_info})
|
|
2041
|
+
else:
|
|
2042
|
+
params.append(param_name)
|
|
2043
|
+
self._match(TokenType.COMMA)
|
|
2044
|
+
elif self._check(TokenType.KEYWORD):
|
|
2045
|
+
# Parameter name could be a keyword like 'Params'
|
|
2046
|
+
param_name = self._advance().value
|
|
2047
|
+
# v4.2.0: Handle default parameter values (param = value)
|
|
2048
|
+
if self._match(TokenType.EQUALS):
|
|
2049
|
+
default_value = self._parse_expression()
|
|
2050
|
+
param_info['default'] = default_value
|
|
2051
|
+
if param_info:
|
|
2052
|
+
params.append({'name': param_name, **param_info})
|
|
2053
|
+
else:
|
|
2054
|
+
params.append(param_name)
|
|
2055
|
+
self._match(TokenType.COMMA)
|
|
2056
|
+
else:
|
|
2057
|
+
break
|
|
2058
|
+
self._expect(TokenType.PAREN_END)
|
|
2059
|
+
|
|
2060
|
+
# Check for extends/overwrites/supports AFTER parameters
|
|
2061
|
+
# Syntax: define func(params) : extends/overwrites target { }
|
|
2062
|
+
# Also supports method-level :: syntax: define func() :: extends Parent::method
|
|
1940
2063
|
extends_func = None
|
|
1941
2064
|
overwrites_func = None
|
|
1942
2065
|
extends_is_python = False
|
|
@@ -1945,6 +2068,7 @@ class CSSLParser:
|
|
|
1945
2068
|
extends_method_ref = None
|
|
1946
2069
|
overwrites_class_ref = None
|
|
1947
2070
|
overwrites_method_ref = None
|
|
2071
|
+
supports_language = None # v4.1.0: Multi-language syntax support
|
|
1948
2072
|
|
|
1949
2073
|
if self._match(TokenType.DOUBLE_COLON) or self._match(TokenType.COLON):
|
|
1950
2074
|
# Parse extends and/or overwrites (supports :: method-level syntax)
|
|
@@ -2005,49 +2129,24 @@ class CSSLParser:
|
|
|
2005
2129
|
# Skip optional () after function/method name
|
|
2006
2130
|
if self._match(TokenType.PAREN_START):
|
|
2007
2131
|
self._expect(TokenType.PAREN_END)
|
|
2132
|
+
# v4.1.0: Parse 'supports' keyword for multi-language syntax
|
|
2133
|
+
elif self._match_keyword('supports'):
|
|
2134
|
+
if self._check(TokenType.AT):
|
|
2135
|
+
self._advance() # consume @
|
|
2136
|
+
if self._check(TokenType.IDENTIFIER):
|
|
2137
|
+
supports_language = '@' + self._advance().value
|
|
2138
|
+
else:
|
|
2139
|
+
raise CSSLSyntaxError("Expected language identifier after '@' in 'supports'")
|
|
2140
|
+
elif self._check(TokenType.IDENTIFIER):
|
|
2141
|
+
supports_language = self._advance().value
|
|
2142
|
+
else:
|
|
2143
|
+
raise CSSLSyntaxError("Expected language identifier after 'supports'")
|
|
2008
2144
|
else:
|
|
2009
2145
|
break
|
|
2010
2146
|
# Check for another :: or : for chaining extends/overwrites
|
|
2011
2147
|
if not (self._match(TokenType.DOUBLE_COLON) or self._match(TokenType.COLON)):
|
|
2012
2148
|
break
|
|
2013
2149
|
|
|
2014
|
-
params = []
|
|
2015
|
-
|
|
2016
|
-
if self._match(TokenType.PAREN_START):
|
|
2017
|
-
while not self._check(TokenType.PAREN_END):
|
|
2018
|
-
param_info = {}
|
|
2019
|
-
# Handle 'open' keyword for open parameters
|
|
2020
|
-
if self._match_keyword('open'):
|
|
2021
|
-
param_info['open'] = True
|
|
2022
|
-
# Handle type annotations (e.g., string, int, dynamic, etc.)
|
|
2023
|
-
if self._check(TokenType.KEYWORD):
|
|
2024
|
-
param_info['type'] = self._advance().value
|
|
2025
|
-
# Handle reference operator &
|
|
2026
|
-
if self._match(TokenType.AMPERSAND):
|
|
2027
|
-
param_info['ref'] = True
|
|
2028
|
-
# Handle * prefix for non-null parameters
|
|
2029
|
-
if self._match(TokenType.MULTIPLY):
|
|
2030
|
-
param_info['non_null'] = True
|
|
2031
|
-
# Get parameter name
|
|
2032
|
-
if self._check(TokenType.IDENTIFIER):
|
|
2033
|
-
param_name = self._advance().value
|
|
2034
|
-
if param_info:
|
|
2035
|
-
params.append({'name': param_name, **param_info})
|
|
2036
|
-
else:
|
|
2037
|
-
params.append(param_name)
|
|
2038
|
-
self._match(TokenType.COMMA)
|
|
2039
|
-
elif self._check(TokenType.KEYWORD):
|
|
2040
|
-
# Parameter name could be a keyword like 'Params'
|
|
2041
|
-
param_name = self._advance().value
|
|
2042
|
-
if param_info:
|
|
2043
|
-
params.append({'name': param_name, **param_info})
|
|
2044
|
-
else:
|
|
2045
|
-
params.append(param_name)
|
|
2046
|
-
self._match(TokenType.COMMA)
|
|
2047
|
-
else:
|
|
2048
|
-
break
|
|
2049
|
-
self._expect(TokenType.PAREN_END)
|
|
2050
|
-
|
|
2051
2150
|
# New: Append mode and reference tracking for functions
|
|
2052
2151
|
# Syntax: define XYZ(int zahl) &overwrittenclass::functionyouwanttokeep ++ { ... }
|
|
2053
2152
|
append_mode = False
|
|
@@ -2074,6 +2173,37 @@ class CSSLParser:
|
|
|
2074
2173
|
if self._match(TokenType.PLUS_PLUS):
|
|
2075
2174
|
append_mode = True
|
|
2076
2175
|
|
|
2176
|
+
# v4.2.0: Allow 'supports' AFTER &Class::member reference
|
|
2177
|
+
# Syntax: define func() &$pyclass::method : supports python { }
|
|
2178
|
+
if self._match(TokenType.COLON) or self._match(TokenType.DOUBLE_COLON):
|
|
2179
|
+
if self._match_keyword('supports'):
|
|
2180
|
+
if self._check(TokenType.AT):
|
|
2181
|
+
self._advance()
|
|
2182
|
+
if self._check(TokenType.IDENTIFIER):
|
|
2183
|
+
supports_language = '@' + self._advance().value
|
|
2184
|
+
else:
|
|
2185
|
+
raise CSSLSyntaxError("Expected language identifier after '@' in 'supports'")
|
|
2186
|
+
elif self._check(TokenType.IDENTIFIER):
|
|
2187
|
+
supports_language = self._advance().value
|
|
2188
|
+
else:
|
|
2189
|
+
raise CSSLSyntaxError("Expected language identifier after 'supports'")
|
|
2190
|
+
|
|
2191
|
+
self._expect(TokenType.BLOCK_START)
|
|
2192
|
+
|
|
2193
|
+
# v4.2.0: Extract raw body when supports_language is set for transformation
|
|
2194
|
+
raw_body = None
|
|
2195
|
+
children = []
|
|
2196
|
+
if supports_language:
|
|
2197
|
+
raw_body = self._extract_raw_block_body()
|
|
2198
|
+
# _extract_raw_block_body positions cursor at BLOCK_END
|
|
2199
|
+
else:
|
|
2200
|
+
while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
|
|
2201
|
+
stmt = self._parse_statement()
|
|
2202
|
+
if stmt:
|
|
2203
|
+
children.append(stmt)
|
|
2204
|
+
|
|
2205
|
+
self._expect(TokenType.BLOCK_END)
|
|
2206
|
+
|
|
2077
2207
|
node = ASTNode('function', value={
|
|
2078
2208
|
'name': name,
|
|
2079
2209
|
'is_global': is_global,
|
|
@@ -2092,16 +2222,13 @@ class CSSLParser:
|
|
|
2092
2222
|
# New append mode fields
|
|
2093
2223
|
'append_mode': append_mode,
|
|
2094
2224
|
'append_ref_class': append_ref_class,
|
|
2095
|
-
'append_ref_member': append_ref_member
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
if stmt:
|
|
2102
|
-
node.children.append(stmt)
|
|
2225
|
+
'append_ref_member': append_ref_member,
|
|
2226
|
+
# v4.1.0: Multi-language support
|
|
2227
|
+
'supports_language': supports_language,
|
|
2228
|
+
# v4.2.0: Raw body for language transformation
|
|
2229
|
+
'raw_body': raw_body
|
|
2230
|
+
}, children=children)
|
|
2103
2231
|
|
|
2104
|
-
self._expect(TokenType.BLOCK_END)
|
|
2105
2232
|
return node
|
|
2106
2233
|
|
|
2107
2234
|
def _parse_statement(self) -> Optional[ASTNode]:
|
|
@@ -2127,6 +2254,9 @@ class CSSLParser:
|
|
|
2127
2254
|
return self._parse_try()
|
|
2128
2255
|
elif self._match_keyword('await'):
|
|
2129
2256
|
return self._parse_await()
|
|
2257
|
+
elif self._match_keyword('supports'):
|
|
2258
|
+
# v4.2.0: Standalone supports block for multi-language syntax
|
|
2259
|
+
return self._parse_supports_block()
|
|
2130
2260
|
elif self._match_keyword('define'):
|
|
2131
2261
|
# Nested define function
|
|
2132
2262
|
return self._parse_define()
|
|
@@ -2352,42 +2482,40 @@ class CSSLParser:
|
|
|
2352
2482
|
|
|
2353
2483
|
Supports: i = i + 1, i++, ++i, i += 1, i -= 1
|
|
2354
2484
|
"""
|
|
2355
|
-
# Check for prefix increment/decrement: ++i or --i
|
|
2356
|
-
if self._check(TokenType.
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2485
|
+
# Check for prefix increment/decrement: ++i or --i (as single PLUS_PLUS/MINUS_MINUS token)
|
|
2486
|
+
if self._check(TokenType.PLUS_PLUS):
|
|
2487
|
+
self._advance()
|
|
2488
|
+
var_name = self._advance().value
|
|
2489
|
+
return ASTNode('c_for_update', value={'var': var_name, 'op': 'increment'})
|
|
2490
|
+
elif self._check(TokenType.MINUS_MINUS):
|
|
2491
|
+
self._advance()
|
|
2492
|
+
var_name = self._advance().value
|
|
2493
|
+
return ASTNode('c_for_update', value={'var': var_name, 'op': 'decrement'})
|
|
2364
2494
|
|
|
2365
2495
|
# Regular variable assignment or postfix
|
|
2366
2496
|
var_name = self._advance().value
|
|
2367
2497
|
|
|
2368
|
-
# Check for postfix increment/decrement: i++ or i--
|
|
2369
|
-
if self._check(TokenType.
|
|
2498
|
+
# Check for postfix increment/decrement: i++ or i-- (as single PLUS_PLUS/MINUS_MINUS token)
|
|
2499
|
+
if self._check(TokenType.PLUS_PLUS):
|
|
2500
|
+
self._advance()
|
|
2501
|
+
return ASTNode('c_for_update', value={'var': var_name, 'op': 'increment'})
|
|
2502
|
+
elif self._check(TokenType.MINUS_MINUS):
|
|
2370
2503
|
self._advance()
|
|
2371
|
-
|
|
2504
|
+
return ASTNode('c_for_update', value={'var': var_name, 'op': 'decrement'})
|
|
2505
|
+
# i += value
|
|
2506
|
+
elif self._check(TokenType.PLUS):
|
|
2507
|
+
self._advance()
|
|
2508
|
+
if self._check(TokenType.EQUALS):
|
|
2372
2509
|
self._advance()
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
if self._check(TokenType.EQUALS):
|
|
2377
|
-
self._advance()
|
|
2378
|
-
value = self._parse_expression()
|
|
2379
|
-
return ASTNode('c_for_update', value={'var': var_name, 'op': 'add', 'value': value})
|
|
2510
|
+
value = self._parse_expression()
|
|
2511
|
+
return ASTNode('c_for_update', value={'var': var_name, 'op': 'add', 'value': value})
|
|
2512
|
+
# i -= value
|
|
2380
2513
|
elif self._check(TokenType.MINUS):
|
|
2381
2514
|
self._advance()
|
|
2382
|
-
if self._check(TokenType.
|
|
2515
|
+
if self._check(TokenType.EQUALS):
|
|
2383
2516
|
self._advance()
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
# i -= value
|
|
2387
|
-
if self._check(TokenType.EQUALS):
|
|
2388
|
-
self._advance()
|
|
2389
|
-
value = self._parse_expression()
|
|
2390
|
-
return ASTNode('c_for_update', value={'var': var_name, 'op': 'subtract', 'value': value})
|
|
2517
|
+
value = self._parse_expression()
|
|
2518
|
+
return ASTNode('c_for_update', value={'var': var_name, 'op': 'subtract', 'value': value})
|
|
2391
2519
|
|
|
2392
2520
|
# Regular assignment: i = expression
|
|
2393
2521
|
if self._check(TokenType.EQUALS):
|
|
@@ -2632,6 +2760,163 @@ class CSSLParser:
|
|
|
2632
2760
|
self._match(TokenType.SEMICOLON)
|
|
2633
2761
|
return ASTNode('await', value=expr)
|
|
2634
2762
|
|
|
2763
|
+
def _parse_supports_block(self) -> ASTNode:
|
|
2764
|
+
"""Parse standalone supports block for multi-language syntax.
|
|
2765
|
+
|
|
2766
|
+
v4.2.0: Allows 'supports' to be used anywhere, not just in class/function.
|
|
2767
|
+
|
|
2768
|
+
Syntax:
|
|
2769
|
+
supports py { } // Python syntax block
|
|
2770
|
+
supports @py { } // With @ prefix
|
|
2771
|
+
supports python { } // Full language name
|
|
2772
|
+
supports cpp { } // C++ syntax block
|
|
2773
|
+
supports javascript { } // JavaScript syntax block
|
|
2774
|
+
|
|
2775
|
+
Example:
|
|
2776
|
+
supports py {
|
|
2777
|
+
for i in range(10):
|
|
2778
|
+
print(i)
|
|
2779
|
+
}
|
|
2780
|
+
|
|
2781
|
+
supports cpp {
|
|
2782
|
+
std::cout << "Hello" << std::endl;
|
|
2783
|
+
int x = 42;
|
|
2784
|
+
}
|
|
2785
|
+
"""
|
|
2786
|
+
# Parse language identifier
|
|
2787
|
+
language = None
|
|
2788
|
+
if self._check(TokenType.AT):
|
|
2789
|
+
self._advance()
|
|
2790
|
+
if self._check(TokenType.IDENTIFIER):
|
|
2791
|
+
language = '@' + self._advance().value
|
|
2792
|
+
else:
|
|
2793
|
+
raise CSSLSyntaxError("Expected language identifier after '@' in 'supports'")
|
|
2794
|
+
elif self._check(TokenType.IDENTIFIER):
|
|
2795
|
+
language = self._advance().value
|
|
2796
|
+
else:
|
|
2797
|
+
raise CSSLSyntaxError("Expected language identifier after 'supports'")
|
|
2798
|
+
|
|
2799
|
+
# Extract raw block source for language transformation (preserves indentation)
|
|
2800
|
+
raw_source = None
|
|
2801
|
+
if self._check(TokenType.BLOCK_START):
|
|
2802
|
+
raw_source = self._extract_raw_block_body()
|
|
2803
|
+
|
|
2804
|
+
# Skip parsing body if we have raw_source - runtime will transform and parse
|
|
2805
|
+
body = []
|
|
2806
|
+
self._expect(TokenType.BLOCK_START)
|
|
2807
|
+
if raw_source is None:
|
|
2808
|
+
# No raw source (e.g., already CSSL syntax), parse normally
|
|
2809
|
+
while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
|
|
2810
|
+
stmt = self._parse_statement()
|
|
2811
|
+
if stmt:
|
|
2812
|
+
body.append(stmt)
|
|
2813
|
+
else:
|
|
2814
|
+
# Skip the block - runtime will transform and parse
|
|
2815
|
+
brace_count = 1
|
|
2816
|
+
while brace_count > 0 and not self._is_at_end():
|
|
2817
|
+
if self._check(TokenType.BLOCK_START):
|
|
2818
|
+
brace_count += 1
|
|
2819
|
+
elif self._check(TokenType.BLOCK_END):
|
|
2820
|
+
brace_count -= 1
|
|
2821
|
+
if brace_count > 0:
|
|
2822
|
+
self._advance()
|
|
2823
|
+
self._expect(TokenType.BLOCK_END)
|
|
2824
|
+
|
|
2825
|
+
return ASTNode('supports_block', value={
|
|
2826
|
+
'language': language,
|
|
2827
|
+
'raw_source': raw_source
|
|
2828
|
+
}, children=body)
|
|
2829
|
+
|
|
2830
|
+
def _extract_raw_block_source(self) -> Optional[str]:
|
|
2831
|
+
"""Extract raw source code from a {} block before parsing.
|
|
2832
|
+
|
|
2833
|
+
Used for 'supports' blocks to allow language transformation.
|
|
2834
|
+
"""
|
|
2835
|
+
if not self._check(TokenType.BLOCK_START):
|
|
2836
|
+
return None
|
|
2837
|
+
|
|
2838
|
+
# Find the matching block end by counting braces
|
|
2839
|
+
start_pos = self.pos
|
|
2840
|
+
brace_count = 0
|
|
2841
|
+
found_start = False
|
|
2842
|
+
|
|
2843
|
+
# Walk through tokens to find matching close brace
|
|
2844
|
+
temp_pos = self.pos
|
|
2845
|
+
while temp_pos < len(self.tokens):
|
|
2846
|
+
token = self.tokens[temp_pos]
|
|
2847
|
+
if token.type == TokenType.BLOCK_START:
|
|
2848
|
+
if not found_start:
|
|
2849
|
+
found_start = True
|
|
2850
|
+
brace_count += 1
|
|
2851
|
+
elif token.type == TokenType.BLOCK_END:
|
|
2852
|
+
brace_count -= 1
|
|
2853
|
+
if brace_count == 0:
|
|
2854
|
+
break
|
|
2855
|
+
temp_pos += 1
|
|
2856
|
+
|
|
2857
|
+
# Build source from tokens between braces (excluding braces)
|
|
2858
|
+
source_parts = []
|
|
2859
|
+
for i in range(start_pos + 1, temp_pos):
|
|
2860
|
+
token = self.tokens[i]
|
|
2861
|
+
if token.type == TokenType.STRING:
|
|
2862
|
+
source_parts.append(f'"{token.value}"')
|
|
2863
|
+
elif token.type == TokenType.NEWLINE:
|
|
2864
|
+
source_parts.append('\n')
|
|
2865
|
+
else:
|
|
2866
|
+
source_parts.append(str(token.value))
|
|
2867
|
+
|
|
2868
|
+
return ' '.join(source_parts)
|
|
2869
|
+
|
|
2870
|
+
def _extract_raw_block_body(self) -> Optional[str]:
|
|
2871
|
+
"""Extract raw source code body from a {} block for language transformation.
|
|
2872
|
+
|
|
2873
|
+
v4.2.0: Used for 'supports' blocks to preserve original source (including indentation).
|
|
2874
|
+
This extracts the raw text between { and } from the original source string.
|
|
2875
|
+
|
|
2876
|
+
Returns the raw body content without the surrounding braces.
|
|
2877
|
+
"""
|
|
2878
|
+
if not self._check(TokenType.BLOCK_START):
|
|
2879
|
+
return None
|
|
2880
|
+
|
|
2881
|
+
# Get the { token's position
|
|
2882
|
+
start_token = self._current()
|
|
2883
|
+
start_line = start_token.line
|
|
2884
|
+
start_col = start_token.column
|
|
2885
|
+
|
|
2886
|
+
# Find the { character position in source
|
|
2887
|
+
# Line numbers are 1-indexed, columns are 1-indexed
|
|
2888
|
+
brace_start_pos = 0
|
|
2889
|
+
current_line = 1
|
|
2890
|
+
for i, char in enumerate(self.source):
|
|
2891
|
+
if current_line == start_line:
|
|
2892
|
+
# Found the right line, now find the column
|
|
2893
|
+
col_in_line = i - brace_start_pos + 1
|
|
2894
|
+
if col_in_line >= start_col:
|
|
2895
|
+
# Search for { from here
|
|
2896
|
+
for j in range(i, len(self.source)):
|
|
2897
|
+
if self.source[j] == '{':
|
|
2898
|
+
brace_start_pos = j
|
|
2899
|
+
break
|
|
2900
|
+
break
|
|
2901
|
+
if char == '\n':
|
|
2902
|
+
current_line += 1
|
|
2903
|
+
brace_start_pos = i + 1
|
|
2904
|
+
|
|
2905
|
+
# Now find the matching closing brace
|
|
2906
|
+
brace_count = 1
|
|
2907
|
+
pos = brace_start_pos + 1
|
|
2908
|
+
while pos < len(self.source) and brace_count > 0:
|
|
2909
|
+
char = self.source[pos]
|
|
2910
|
+
if char == '{':
|
|
2911
|
+
brace_count += 1
|
|
2912
|
+
elif char == '}':
|
|
2913
|
+
brace_count -= 1
|
|
2914
|
+
pos += 1
|
|
2915
|
+
|
|
2916
|
+
# Extract the body (everything between { and })
|
|
2917
|
+
body = self.source[brace_start_pos + 1:pos - 1]
|
|
2918
|
+
return body.strip()
|
|
2919
|
+
|
|
2635
2920
|
def _parse_action_block(self) -> ASTNode:
|
|
2636
2921
|
"""Parse an action block { ... } containing statements for createcmd"""
|
|
2637
2922
|
node = ASTNode('action_block', children=[])
|
|
@@ -2948,6 +3233,14 @@ class CSSLParser:
|
|
|
2948
3233
|
if self._match(TokenType.MINUS):
|
|
2949
3234
|
operand = self._parse_unary()
|
|
2950
3235
|
return ASTNode('unary', value={'op': '-', 'operand': operand})
|
|
3236
|
+
# Prefix increment: ++i
|
|
3237
|
+
if self._match(TokenType.PLUS_PLUS):
|
|
3238
|
+
operand = self._parse_unary()
|
|
3239
|
+
return ASTNode('increment', value={'op': 'prefix', 'operand': operand})
|
|
3240
|
+
# Prefix decrement: --i
|
|
3241
|
+
if self._match(TokenType.MINUS_MINUS):
|
|
3242
|
+
operand = self._parse_unary()
|
|
3243
|
+
return ASTNode('decrement', value={'op': 'prefix', 'operand': operand})
|
|
2951
3244
|
if self._match(TokenType.AMPERSAND):
|
|
2952
3245
|
# Reference operator: &variable or &@module
|
|
2953
3246
|
operand = self._parse_unary()
|
|
@@ -3090,12 +3383,13 @@ class CSSLParser:
|
|
|
3090
3383
|
token = self._advance()
|
|
3091
3384
|
node = ASTNode('global_ref', value=token.value, line=token.line, column=token.column)
|
|
3092
3385
|
# Check for member access, calls, indexing - with kwargs support
|
|
3386
|
+
# Support both . and -> for member access
|
|
3093
3387
|
while True:
|
|
3094
3388
|
if self._match(TokenType.PAREN_START):
|
|
3095
3389
|
args, kwargs = self._parse_call_arguments()
|
|
3096
3390
|
self._expect(TokenType.PAREN_END)
|
|
3097
3391
|
node = ASTNode('call', value={'callee': node, 'args': args, 'kwargs': kwargs})
|
|
3098
|
-
elif self._match(TokenType.DOT):
|
|
3392
|
+
elif self._match(TokenType.DOT) or self._match(TokenType.FLOW_RIGHT):
|
|
3099
3393
|
member = self._advance().value
|
|
3100
3394
|
node = ASTNode('member_access', value={'object': node, 'member': member})
|
|
3101
3395
|
elif self._match(TokenType.BRACKET_START):
|
|
@@ -3111,12 +3405,14 @@ class CSSLParser:
|
|
|
3111
3405
|
token = self._advance()
|
|
3112
3406
|
node = ASTNode('shared_ref', value=token.value, line=token.line, column=token.column)
|
|
3113
3407
|
# Check for member access, calls, indexing - with kwargs support
|
|
3408
|
+
# Support both . and -> for member access (like this->member)
|
|
3114
3409
|
while True:
|
|
3115
3410
|
if self._match(TokenType.PAREN_START):
|
|
3116
3411
|
args, kwargs = self._parse_call_arguments()
|
|
3117
3412
|
self._expect(TokenType.PAREN_END)
|
|
3118
3413
|
node = ASTNode('call', value={'callee': node, 'args': args, 'kwargs': kwargs})
|
|
3119
|
-
elif self._match(TokenType.DOT):
|
|
3414
|
+
elif self._match(TokenType.DOT) or self._match(TokenType.FLOW_RIGHT):
|
|
3415
|
+
# Support both $obj.member and $obj->member syntax
|
|
3120
3416
|
member = self._advance().value
|
|
3121
3417
|
node = ASTNode('member_access', value={'object': node, 'member': member})
|
|
3122
3418
|
elif self._match(TokenType.BRACKET_START):
|
|
@@ -3132,12 +3428,36 @@ class CSSLParser:
|
|
|
3132
3428
|
token = self._advance()
|
|
3133
3429
|
node = ASTNode('captured_ref', value=token.value, line=token.line, column=token.column)
|
|
3134
3430
|
# Check for member access, calls, indexing - with kwargs support
|
|
3431
|
+
# Support both . and -> for member access
|
|
3135
3432
|
while True:
|
|
3136
3433
|
if self._match(TokenType.PAREN_START):
|
|
3137
3434
|
args, kwargs = self._parse_call_arguments()
|
|
3138
3435
|
self._expect(TokenType.PAREN_END)
|
|
3139
3436
|
node = ASTNode('call', value={'callee': node, 'args': args, 'kwargs': kwargs})
|
|
3140
|
-
elif self._match(TokenType.DOT):
|
|
3437
|
+
elif self._match(TokenType.DOT) or self._match(TokenType.FLOW_RIGHT):
|
|
3438
|
+
member = self._advance().value
|
|
3439
|
+
node = ASTNode('member_access', value={'object': node, 'member': member})
|
|
3440
|
+
elif self._match(TokenType.BRACKET_START):
|
|
3441
|
+
index = self._parse_expression()
|
|
3442
|
+
self._expect(TokenType.BRACKET_END)
|
|
3443
|
+
node = ASTNode('index_access', value={'object': node, 'index': index})
|
|
3444
|
+
else:
|
|
3445
|
+
break
|
|
3446
|
+
return node
|
|
3447
|
+
|
|
3448
|
+
# v4.1.0: Cross-language instance reference: cpp$ClassName, py$Object
|
|
3449
|
+
if self._check(TokenType.LANG_INSTANCE_REF):
|
|
3450
|
+
token = self._advance()
|
|
3451
|
+
ref = token.value # {'lang': 'cpp', 'instance': 'ClassName'}
|
|
3452
|
+
node = ASTNode('lang_instance_ref', value=ref, line=token.line, column=token.column)
|
|
3453
|
+
# Check for member access, calls, indexing
|
|
3454
|
+
# Support both . and -> for member access
|
|
3455
|
+
while True:
|
|
3456
|
+
if self._match(TokenType.PAREN_START):
|
|
3457
|
+
args, kwargs = self._parse_call_arguments()
|
|
3458
|
+
self._expect(TokenType.PAREN_END)
|
|
3459
|
+
node = ASTNode('call', value={'callee': node, 'args': args, 'kwargs': kwargs})
|
|
3460
|
+
elif self._match(TokenType.DOT) or self._match(TokenType.FLOW_RIGHT):
|
|
3141
3461
|
member = self._advance().value
|
|
3142
3462
|
node = ASTNode('member_access', value={'object': node, 'member': member})
|
|
3143
3463
|
elif self._match(TokenType.BRACKET_START):
|
|
@@ -3398,6 +3718,12 @@ class CSSLParser:
|
|
|
3398
3718
|
index = self._parse_expression()
|
|
3399
3719
|
self._expect(TokenType.BRACKET_END)
|
|
3400
3720
|
node = ASTNode('index_access', value={'object': node, 'index': index})
|
|
3721
|
+
# Postfix increment: i++
|
|
3722
|
+
elif self._match(TokenType.PLUS_PLUS):
|
|
3723
|
+
node = ASTNode('increment', value={'op': 'postfix', 'operand': node})
|
|
3724
|
+
# Postfix decrement: i--
|
|
3725
|
+
elif self._match(TokenType.MINUS_MINUS):
|
|
3726
|
+
node = ASTNode('decrement', value={'op': 'postfix', 'operand': node})
|
|
3401
3727
|
else:
|
|
3402
3728
|
break
|
|
3403
3729
|
|
|
@@ -3508,7 +3834,7 @@ def parse_cssl(source: str) -> ASTNode:
|
|
|
3508
3834
|
"""Parse CSSL source code into an AST - auto-detects service vs program format"""
|
|
3509
3835
|
lexer = CSSLLexer(source)
|
|
3510
3836
|
tokens = lexer.tokenize()
|
|
3511
|
-
parser = CSSLParser(tokens, lexer.source_lines)
|
|
3837
|
+
parser = CSSLParser(tokens, lexer.source_lines, source) # v4.2.0: Pass source for raw extraction
|
|
3512
3838
|
|
|
3513
3839
|
# Auto-detect: if first token is '{', it's a service file
|
|
3514
3840
|
# Otherwise treat as standalone program (whitespace is already filtered by lexer)
|
|
@@ -3522,7 +3848,7 @@ def parse_cssl_program(source: str) -> ASTNode:
|
|
|
3522
3848
|
"""Parse standalone CSSL program (no service wrapper) into an AST"""
|
|
3523
3849
|
lexer = CSSLLexer(source)
|
|
3524
3850
|
tokens = lexer.tokenize()
|
|
3525
|
-
parser = CSSLParser(tokens, lexer.source_lines)
|
|
3851
|
+
parser = CSSLParser(tokens, lexer.source_lines, source) # v4.2.0: Pass source for raw extraction
|
|
3526
3852
|
return parser.parse_program()
|
|
3527
3853
|
|
|
3528
3854
|
|