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.
@@ -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
- # Handle statements - keywords like 'instance', 'list', 'map' can be variable names
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
- if self._check(TokenType.IDENTIFIER):
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 'overwrites' after ':' or '::' in class declaration")
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) { } // Local function
1911
- global define MyFunc(args) { } // Global function
1912
- define @MyFunc(args) { } // Global function (alternative)
1913
- define *MyFunc(args) { } // Non-null: must never return None
1914
- define MyFunc : extends OtherFunc() { } // Inherit local vars
1915
- define MyFunc : overwrites OtherFunc() { } // Replace OtherFunc
1916
- define MyFunc :: extends Parent::Method :: overwrites Parent::Method() { } // Method-level inheritance
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
- # Check for extends/overwrites: define func : extends/overwrites target() { }
1939
- # Also supports method-level :: syntax: define func :: extends Parent::method
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
- }, children=[])
2097
- self._expect(TokenType.BLOCK_START)
2098
-
2099
- while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
2100
- stmt = self._parse_statement()
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.PLUS) or self._check(TokenType.MINUS):
2357
- op_token = self._advance()
2358
- # Check for double operator (++ or --)
2359
- if self._check(op_token.type):
2360
- self._advance()
2361
- var_name = self._advance().value
2362
- op = 'increment' if op_token.type == TokenType.PLUS else 'decrement'
2363
- return ASTNode('c_for_update', value={'var': var_name, 'op': op})
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.PLUS):
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
- if self._check(TokenType.PLUS):
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
- return ASTNode('c_for_update', value={'var': var_name, 'op': 'increment'})
2374
- else:
2375
- # i += value
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.MINUS):
2515
+ if self._check(TokenType.EQUALS):
2383
2516
  self._advance()
2384
- return ASTNode('c_for_update', value={'var': var_name, 'op': 'decrement'})
2385
- else:
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