IncludeCPP 3.5.0__py3-none-any.whl → 3.7.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.
@@ -100,6 +100,8 @@ class TokenType(Enum):
100
100
  GLOBAL_REF = auto() # r@<name> global variable declaration
101
101
  SELF_REF = auto() # s@<name> self-reference to global struct
102
102
  SHARED_REF = auto() # $<name> shared object reference
103
+ CAPTURED_REF = auto() # %<name> captured reference (for infusion)
104
+ THIS_REF = auto() # this-><name> class member reference
103
105
  PACKAGE = auto()
104
106
  PACKAGE_INCLUDES = auto()
105
107
  AS = auto()
@@ -110,7 +112,7 @@ class TokenType(Enum):
110
112
 
111
113
  KEYWORDS = {
112
114
  # Service structure
113
- 'service-init', 'service-run', 'service-include', 'struct', 'define', 'main',
115
+ 'service-init', 'service-run', 'service-include', 'struct', 'define', 'main', 'class', 'new', 'this',
114
116
  # Control flow
115
117
  'if', 'else', 'elif', 'while', 'for', 'foreach', 'in', 'range',
116
118
  'switch', 'case', 'default', 'break', 'continue', 'return',
@@ -125,6 +127,7 @@ KEYWORDS = {
125
127
  'package', 'package-includes', 'exec', 'as', 'global',
126
128
  # CSSL Type Keywords
127
129
  'int', 'string', 'float', 'bool', 'void', 'json', 'array', 'vector', 'stack',
130
+ 'list', 'dictionary', 'dict', 'instance', 'map', # Python-like types
128
131
  'dynamic', # No type declaration (slow but flexible)
129
132
  'undefined', # Function errors ignored
130
133
  'open', # Accept any parameter type
@@ -152,7 +155,7 @@ TYPE_LITERALS = {'list', 'dict'}
152
155
  # Generic type keywords that use <T> syntax
153
156
  TYPE_GENERICS = {
154
157
  'datastruct', 'dataspace', 'shuffled', 'iterator', 'combo',
155
- 'vector', 'stack', 'array', 'openquote'
158
+ 'vector', 'stack', 'array', 'openquote', 'list', 'dictionary', 'map'
156
159
  }
157
160
 
158
161
  # Functions that accept type parameters: FuncName<type>(args)
@@ -241,6 +244,9 @@ class CSSLLexer:
241
244
  elif char == '$':
242
245
  # $<name> shared object reference
243
246
  self._read_shared_ref()
247
+ elif char == '%':
248
+ # %<name> captured reference (for infusion)
249
+ self._read_captured_ref()
244
250
  elif char == '&':
245
251
  # & for references
246
252
  if self._peek(1) == '&':
@@ -493,6 +499,20 @@ class CSSLLexer:
493
499
  self.error("Expected identifier after '$'")
494
500
  self._add_token(TokenType.SHARED_REF, value)
495
501
 
502
+ def _read_captured_ref(self):
503
+ """Read %<name> captured reference (captures value at definition time for infusions)"""
504
+ self._advance() # skip '%'
505
+
506
+ # Read the identifier (captured reference name)
507
+ name_start = self.pos
508
+ while self.pos < len(self.source) and (self.source[self.pos].isalnum() or self.source[self.pos] == '_'):
509
+ self._advance()
510
+
511
+ value = self.source[name_start:self.pos]
512
+ if not value:
513
+ self.error("Expected identifier after '%'")
514
+ self._add_token(TokenType.CAPTURED_REF, value)
515
+
496
516
  def _read_less_than(self):
497
517
  # Check for <<== (code infusion left)
498
518
  if self._peek(1) == '<' and self._peek(2) == '=' and self._peek(3) == '=':
@@ -540,10 +560,10 @@ class CSSLLexer:
540
560
  elif self._peek(1) == '=' and self._peek(2) == '>' and self._peek(3) == '+':
541
561
  self._add_token(TokenType.INJECT_PLUS_RIGHT, '==>+')
542
562
  for _ in range(4): self._advance()
543
- # Check for ===>- (injection right minus - moves & removes)
544
- elif self._peek(1) == '=' and self._peek(2) == '=' and self._peek(3) == '>' and self._peek(4) == '-':
545
- self._add_token(TokenType.INJECT_MINUS_RIGHT, '===>')
546
- for _ in range(5): self._advance()
563
+ # Check for ==>- (injection right minus - moves & removes)
564
+ elif self._peek(1) == '=' and self._peek(2) == '>' and self._peek(3) == '-':
565
+ self._add_token(TokenType.INJECT_MINUS_RIGHT, '==>-')
566
+ for _ in range(4): self._advance()
547
567
  # Check for ==> (basic injection right)
548
568
  elif self._peek(1) == '=' and self._peek(2) == '>':
549
569
  self._add_token(TokenType.INJECT_RIGHT, '==>')
@@ -660,6 +680,7 @@ class CSSLParser:
660
680
  def _is_type_keyword(self, value: str) -> bool:
661
681
  """Check if a keyword is a type declaration"""
662
682
  return value in ('int', 'string', 'float', 'bool', 'void', 'json', 'array', 'vector', 'stack',
683
+ 'list', 'dictionary', 'dict', 'instance', 'map',
663
684
  'dynamic', 'datastruct', 'dataspace', 'shuffled', 'iterator', 'combo', 'structure')
664
685
 
665
686
  def _looks_like_function_declaration(self) -> bool:
@@ -877,7 +898,8 @@ class CSSLParser:
877
898
  # Skip known type keywords
878
899
  type_keywords = {'int', 'string', 'float', 'bool', 'dynamic', 'void',
879
900
  'stack', 'vector', 'datastruct', 'dataspace', 'shuffled',
880
- 'iterator', 'combo', 'array', 'openquote', 'json'}
901
+ 'iterator', 'combo', 'array', 'openquote', 'json',
902
+ 'list', 'dictionary', 'dict', 'instance', 'map'}
881
903
  if type_name not in type_keywords:
882
904
  return False
883
905
 
@@ -906,11 +928,13 @@ class CSSLParser:
906
928
  # Get type name
907
929
  type_name = self._advance().value # Consume type keyword
908
930
 
909
- # Check for generic type <T>
931
+ # Check for generic type <T> or instance<"name">
910
932
  element_type = None
911
933
  if self._match(TokenType.COMPARE_LT):
912
- # Get element type
913
- if self._check(TokenType.KEYWORD) or self._check(TokenType.IDENTIFIER):
934
+ # For instance<"name">, element_type can be a string literal
935
+ if type_name == 'instance' and self._check(TokenType.STRING):
936
+ element_type = self._advance().value
937
+ elif self._check(TokenType.KEYWORD) or self._check(TokenType.IDENTIFIER):
914
938
  element_type = self._advance().value
915
939
  self._expect(TokenType.COMPARE_GT)
916
940
 
@@ -926,6 +950,14 @@ class CSSLParser:
926
950
 
927
951
  self._match(TokenType.SEMICOLON)
928
952
 
953
+ # For instance<"name">, create a special node type
954
+ if type_name == 'instance':
955
+ return ASTNode('instance_declaration', value={
956
+ 'instance_name': element_type,
957
+ 'name': var_name,
958
+ 'value': value
959
+ })
960
+
929
961
  return ASTNode('typed_declaration', value={
930
962
  'type': type_name,
931
963
  'element_type': element_type,
@@ -940,6 +972,8 @@ class CSSLParser:
940
972
  while not self._is_at_end():
941
973
  if self._match_keyword('struct'):
942
974
  root.children.append(self._parse_struct())
975
+ elif self._match_keyword('class'):
976
+ root.children.append(self._parse_class())
943
977
  elif self._match_keyword('define'):
944
978
  root.children.append(self._parse_define())
945
979
  # Check for C-style typed function declarations
@@ -1212,6 +1246,58 @@ class CSSLParser:
1212
1246
  self._expect(TokenType.BLOCK_END)
1213
1247
  return node
1214
1248
 
1249
+ def _parse_class(self) -> ASTNode:
1250
+ """Parse class declaration with members and methods.
1251
+
1252
+ Syntax:
1253
+ class ClassName {
1254
+ string name;
1255
+ int age;
1256
+
1257
+ void ClassName(string n) { this->name = n; }
1258
+
1259
+ void sayHello() {
1260
+ printl("Hello " + this->name);
1261
+ }
1262
+ }
1263
+ """
1264
+ class_name = self._advance().value
1265
+
1266
+ node = ASTNode('class', value={'name': class_name}, children=[])
1267
+ self._expect(TokenType.BLOCK_START)
1268
+
1269
+ while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
1270
+ # Check for typed function (method) declaration
1271
+ if self._looks_like_function_declaration():
1272
+ method = self._parse_typed_function()
1273
+ method_info = method.value
1274
+ method_name = method_info.get('name')
1275
+
1276
+ # Mark constructor (same name as class or __init__)
1277
+ if method_name == class_name or method_name == '__init__':
1278
+ method.value['is_constructor'] = True
1279
+
1280
+ node.children.append(method)
1281
+
1282
+ # Check for typed member variable declaration
1283
+ elif self._looks_like_typed_variable():
1284
+ member = self._parse_typed_variable()
1285
+ if member:
1286
+ # Mark as class member
1287
+ member.value['is_member'] = True
1288
+ node.children.append(member)
1289
+
1290
+ # Check for define-style method
1291
+ elif self._match_keyword('define'):
1292
+ method = self._parse_define()
1293
+ node.children.append(method)
1294
+
1295
+ else:
1296
+ self._advance()
1297
+
1298
+ self._expect(TokenType.BLOCK_END)
1299
+ return node
1300
+
1215
1301
  def _parse_define(self) -> ASTNode:
1216
1302
  name = self._advance().value
1217
1303
  params = []
@@ -1291,7 +1377,10 @@ class CSSLParser:
1291
1377
  elif self._looks_like_function_declaration():
1292
1378
  # Nested typed function (e.g., void Level2() { ... })
1293
1379
  return self._parse_typed_function()
1294
- elif self._check(TokenType.IDENTIFIER) or self._check(TokenType.AT):
1380
+ elif (self._check(TokenType.IDENTIFIER) or self._check(TokenType.AT) or
1381
+ self._check(TokenType.CAPTURED_REF) or self._check(TokenType.SHARED_REF) or
1382
+ self._check(TokenType.GLOBAL_REF) or self._check(TokenType.SELF_REF) or
1383
+ (self._check(TokenType.KEYWORD) and self._current().value in ('this', 'new'))):
1295
1384
  return self._parse_expression_statement()
1296
1385
  else:
1297
1386
  self._advance()
@@ -1505,7 +1594,13 @@ class CSSLParser:
1505
1594
  return ASTNode('c_for_update', value={'var': var_name, 'op': 'none'})
1506
1595
 
1507
1596
  def _parse_python_style_for(self) -> ASTNode:
1508
- """Parse Python-style for loop: for (i in range(start, end)) { }"""
1597
+ """Parse Python-style for loop: for (i in range(...)) { }
1598
+
1599
+ Supports:
1600
+ for (i in range(n)) { } - 0 to n-1
1601
+ for (i in range(start, end)) { } - start to end-1
1602
+ for (i in range(start, end, step)) { }
1603
+ """
1509
1604
  var_name = self._advance().value
1510
1605
  self._expect(TokenType.KEYWORD) # 'in'
1511
1606
 
@@ -1518,15 +1613,27 @@ class CSSLParser:
1518
1613
  self.error(f"Expected 'range', got {self._peek().value}")
1519
1614
 
1520
1615
  self._expect(TokenType.PAREN_START)
1521
- start = self._parse_expression()
1522
- self._expect(TokenType.COMMA)
1523
- end = self._parse_expression()
1616
+ first_arg = self._parse_expression()
1524
1617
 
1525
- # Optional step parameter: range(start, end, step)
1618
+ # Check if there are more arguments
1619
+ start = None
1620
+ end = None
1526
1621
  step = None
1622
+
1527
1623
  if self._check(TokenType.COMMA):
1624
+ # range(start, end) or range(start, end, step)
1528
1625
  self._advance() # consume comma
1529
- step = self._parse_expression()
1626
+ start = first_arg
1627
+ end = self._parse_expression()
1628
+
1629
+ # Optional step parameter
1630
+ if self._check(TokenType.COMMA):
1631
+ self._advance() # consume comma
1632
+ step = self._parse_expression()
1633
+ else:
1634
+ # range(n) - single argument means 0 to n-1
1635
+ start = ASTNode('literal', value={'type': 'int', 'value': 0})
1636
+ end = first_arg
1530
1637
 
1531
1638
  self._expect(TokenType.PAREN_END)
1532
1639
  self._expect(TokenType.PAREN_END)
@@ -1734,12 +1841,26 @@ class CSSLParser:
1734
1841
  self._match(TokenType.SEMICOLON)
1735
1842
  return ASTNode('inject', value={'target': expr, 'source': source, 'mode': 'add', 'filter': filter_info})
1736
1843
 
1737
- # === MINUS INJECTION: -<== (move & remove from source) ===
1844
+ # === MINUS INJECTION: -<== or -<==[n] (move & remove from source) ===
1738
1845
  if self._match(TokenType.INJECT_MINUS_LEFT):
1846
+ # Check for indexed deletion: -<==[n] (only numbers, not filters)
1847
+ remove_index = None
1848
+ if self._check(TokenType.BRACKET_START):
1849
+ # Peek ahead to see if this is an index [n] or a filter [type::helper=...]
1850
+ # Only consume if it's a simple number index
1851
+ saved_pos = self.pos
1852
+ self._advance() # consume [
1853
+ if self._check(TokenType.NUMBER):
1854
+ remove_index = int(self._advance().value)
1855
+ self._expect(TokenType.BRACKET_END)
1856
+ else:
1857
+ # Not a number - restore position for filter parsing
1858
+ self.pos = saved_pos
1859
+
1739
1860
  filter_info = self._parse_injection_filter()
1740
1861
  source = self._parse_expression()
1741
1862
  self._match(TokenType.SEMICOLON)
1742
- return ASTNode('inject', value={'target': expr, 'source': source, 'mode': 'move', 'filter': filter_info})
1863
+ return ASTNode('inject', value={'target': expr, 'source': source, 'mode': 'move', 'filter': filter_info, 'index': remove_index})
1743
1864
 
1744
1865
  # === CODE INFUSION: <<== (inject code into function) ===
1745
1866
  if self._match(TokenType.INFUSE_LEFT):
@@ -1763,16 +1884,29 @@ class CSSLParser:
1763
1884
  self._match(TokenType.SEMICOLON)
1764
1885
  return ASTNode('infuse', value={'target': expr, 'source': source, 'mode': 'add'})
1765
1886
 
1766
- # === CODE INFUSION MINUS: -<<== (remove code from function) ===
1887
+ # === CODE INFUSION MINUS: -<<== or -<<==[n] (remove code from function) ===
1767
1888
  if self._match(TokenType.INFUSE_MINUS_LEFT):
1889
+ # Check for indexed deletion: -<<==[n] (only numbers)
1890
+ remove_index = None
1891
+ if self._check(TokenType.BRACKET_START):
1892
+ # Peek ahead to see if this is an index [n] or something else
1893
+ saved_pos = self.pos
1894
+ self._advance() # consume [
1895
+ if self._check(TokenType.NUMBER):
1896
+ remove_index = int(self._advance().value)
1897
+ self._expect(TokenType.BRACKET_END)
1898
+ else:
1899
+ # Not a number - restore position
1900
+ self.pos = saved_pos
1901
+
1768
1902
  if self._check(TokenType.BLOCK_START):
1769
1903
  code_block = self._parse_action_block()
1770
1904
  self._match(TokenType.SEMICOLON)
1771
- return ASTNode('infuse', value={'target': expr, 'code': code_block, 'mode': 'remove'})
1905
+ return ASTNode('infuse', value={'target': expr, 'code': code_block, 'mode': 'remove', 'index': remove_index})
1772
1906
  else:
1773
1907
  source = self._parse_expression()
1774
1908
  self._match(TokenType.SEMICOLON)
1775
- return ASTNode('infuse', value={'target': expr, 'source': source, 'mode': 'remove'})
1909
+ return ASTNode('infuse', value={'target': expr, 'source': source, 'mode': 'remove', 'index': remove_index})
1776
1910
 
1777
1911
  # === RIGHT-SIDE OPERATORS ===
1778
1912
 
@@ -1919,19 +2053,75 @@ class CSSLParser:
1919
2053
  return self._parse_primary()
1920
2054
 
1921
2055
  def _parse_primary(self) -> ASTNode:
2056
+ # Handle 'this->' member access
2057
+ if self._check(TokenType.KEYWORD) and self._current().value == 'this':
2058
+ self._advance() # consume 'this'
2059
+ if self._match(TokenType.FLOW_RIGHT): # ->
2060
+ member = self._advance().value
2061
+ node = ASTNode('this_access', value={'member': member})
2062
+ # Continue to check for calls, member access, indexing
2063
+ while True:
2064
+ if self._match(TokenType.PAREN_START):
2065
+ # Method call: this->method()
2066
+ args, kwargs = self._parse_call_arguments()
2067
+ self._expect(TokenType.PAREN_END)
2068
+ node = ASTNode('call', value={'callee': node, 'args': args, 'kwargs': kwargs})
2069
+ elif self._match(TokenType.DOT):
2070
+ # Chained access: this->obj.method
2071
+ member = self._advance().value
2072
+ node = ASTNode('member_access', value={'object': node, 'member': member})
2073
+ elif self._match(TokenType.BRACKET_START):
2074
+ # Index access: this->arr[0]
2075
+ index = self._parse_expression()
2076
+ self._expect(TokenType.BRACKET_END)
2077
+ node = ASTNode('index_access', value={'object': node, 'index': index})
2078
+ elif self._match(TokenType.FLOW_RIGHT):
2079
+ # Chained this->a->b style access
2080
+ member = self._advance().value
2081
+ node = ASTNode('this_access', value={'member': member, 'object': node})
2082
+ else:
2083
+ break
2084
+ return node
2085
+ else:
2086
+ # Just 'this' keyword alone - return as identifier for now
2087
+ return ASTNode('identifier', value='this')
2088
+
2089
+ # Handle 'new ClassName(args)' instantiation
2090
+ if self._check(TokenType.KEYWORD) and self._current().value == 'new':
2091
+ self._advance() # consume 'new'
2092
+ class_name = self._advance().value # get class name
2093
+ args = []
2094
+ kwargs = {}
2095
+ if self._match(TokenType.PAREN_START):
2096
+ args, kwargs = self._parse_call_arguments()
2097
+ self._expect(TokenType.PAREN_END)
2098
+ node = ASTNode('new', value={'class': class_name, 'args': args, 'kwargs': kwargs})
2099
+ # Continue to check for member access, calls on the new object
2100
+ while True:
2101
+ if self._match(TokenType.DOT):
2102
+ member = self._advance().value
2103
+ node = ASTNode('member_access', value={'object': node, 'member': member})
2104
+ if self._match(TokenType.PAREN_START):
2105
+ args, kwargs = self._parse_call_arguments()
2106
+ self._expect(TokenType.PAREN_END)
2107
+ node = ASTNode('call', value={'callee': node, 'args': args, 'kwargs': kwargs})
2108
+ elif self._match(TokenType.BRACKET_START):
2109
+ index = self._parse_expression()
2110
+ self._expect(TokenType.BRACKET_END)
2111
+ node = ASTNode('index_access', value={'object': node, 'index': index})
2112
+ else:
2113
+ break
2114
+ return node
2115
+
1922
2116
  if self._match(TokenType.AT):
1923
2117
  node = self._parse_module_reference()
1924
2118
  # Continue to check for calls, indexing, member access on module refs
1925
2119
  while True:
1926
2120
  if self._match(TokenType.PAREN_START):
1927
- # Function call on module ref: @Module.method()
1928
- args = []
1929
- while not self._check(TokenType.PAREN_END) and not self._is_at_end():
1930
- args.append(self._parse_expression())
1931
- if not self._check(TokenType.PAREN_END):
1932
- self._expect(TokenType.COMMA)
2121
+ # Function call on module ref: @Module.method() - with kwargs support
2122
+ args, kwargs = self._parse_call_arguments()
1933
2123
  self._expect(TokenType.PAREN_END)
1934
- node = ASTNode('call', value={'callee': node, 'args': args})
2124
+ node = ASTNode('call', value={'callee': node, 'args': args, 'kwargs': kwargs})
1935
2125
  elif self._match(TokenType.DOT):
1936
2126
  # Member access: @Module.property
1937
2127
  member = self._advance().value
@@ -1949,31 +2139,23 @@ class CSSLParser:
1949
2139
  # s@<name> self-reference to global struct
1950
2140
  token = self._advance()
1951
2141
  node = ASTNode('self_ref', value=token.value, line=token.line, column=token.column)
1952
- # Check for function call: s@Backend.Loop.Start()
2142
+ # Check for function call: s@Backend.Loop.Start() - with kwargs support
1953
2143
  if self._match(TokenType.PAREN_START):
1954
- args = []
1955
- while not self._check(TokenType.PAREN_END):
1956
- args.append(self._parse_expression())
1957
- if not self._check(TokenType.PAREN_END):
1958
- self._expect(TokenType.COMMA)
2144
+ args, kwargs = self._parse_call_arguments()
1959
2145
  self._expect(TokenType.PAREN_END)
1960
- node = ASTNode('call', value={'callee': node, 'args': args})
2146
+ node = ASTNode('call', value={'callee': node, 'args': args, 'kwargs': kwargs})
1961
2147
  return node
1962
2148
 
1963
2149
  if self._check(TokenType.GLOBAL_REF):
1964
2150
  # r@<name> global variable reference/declaration
1965
2151
  token = self._advance()
1966
2152
  node = ASTNode('global_ref', value=token.value, line=token.line, column=token.column)
1967
- # Check for member access, calls, indexing
2153
+ # Check for member access, calls, indexing - with kwargs support
1968
2154
  while True:
1969
2155
  if self._match(TokenType.PAREN_START):
1970
- args = []
1971
- while not self._check(TokenType.PAREN_END):
1972
- args.append(self._parse_expression())
1973
- if not self._check(TokenType.PAREN_END):
1974
- self._expect(TokenType.COMMA)
2156
+ args, kwargs = self._parse_call_arguments()
1975
2157
  self._expect(TokenType.PAREN_END)
1976
- node = ASTNode('call', value={'callee': node, 'args': args})
2158
+ node = ASTNode('call', value={'callee': node, 'args': args, 'kwargs': kwargs})
1977
2159
  elif self._match(TokenType.DOT):
1978
2160
  member = self._advance().value
1979
2161
  node = ASTNode('member_access', value={'object': node, 'member': member})
@@ -1989,16 +2171,33 @@ class CSSLParser:
1989
2171
  # $<name> shared object reference
1990
2172
  token = self._advance()
1991
2173
  node = ASTNode('shared_ref', value=token.value, line=token.line, column=token.column)
1992
- # Check for member access, calls, indexing
2174
+ # Check for member access, calls, indexing - with kwargs support
1993
2175
  while True:
1994
2176
  if self._match(TokenType.PAREN_START):
1995
- args = []
1996
- while not self._check(TokenType.PAREN_END):
1997
- args.append(self._parse_expression())
1998
- if not self._check(TokenType.PAREN_END):
1999
- self._expect(TokenType.COMMA)
2177
+ args, kwargs = self._parse_call_arguments()
2000
2178
  self._expect(TokenType.PAREN_END)
2001
- node = ASTNode('call', value={'callee': node, 'args': args})
2179
+ node = ASTNode('call', value={'callee': node, 'args': args, 'kwargs': kwargs})
2180
+ elif self._match(TokenType.DOT):
2181
+ member = self._advance().value
2182
+ node = ASTNode('member_access', value={'object': node, 'member': member})
2183
+ elif self._match(TokenType.BRACKET_START):
2184
+ index = self._parse_expression()
2185
+ self._expect(TokenType.BRACKET_END)
2186
+ node = ASTNode('index_access', value={'object': node, 'index': index})
2187
+ else:
2188
+ break
2189
+ return node
2190
+
2191
+ if self._check(TokenType.CAPTURED_REF):
2192
+ # %<name> captured reference (captures value at infusion registration time)
2193
+ token = self._advance()
2194
+ node = ASTNode('captured_ref', value=token.value, line=token.line, column=token.column)
2195
+ # Check for member access, calls, indexing - with kwargs support
2196
+ while True:
2197
+ if self._match(TokenType.PAREN_START):
2198
+ args, kwargs = self._parse_call_arguments()
2199
+ self._expect(TokenType.PAREN_END)
2200
+ node = ASTNode('call', value={'callee': node, 'args': args, 'kwargs': kwargs})
2002
2201
  elif self._match(TokenType.DOT):
2003
2202
  member = self._advance().value
2004
2203
  node = ASTNode('member_access', value={'object': node, 'member': member})
@@ -2034,7 +2233,13 @@ class CSSLParser:
2034
2233
  return expr
2035
2234
 
2036
2235
  if self._match(TokenType.BLOCK_START):
2037
- return self._parse_object()
2236
+ # Distinguish between object literal { key = value } and action block { expr; }
2237
+ # Object literal: starts with IDENTIFIER = or STRING =
2238
+ # Action block: starts with expression (captured_ref, call, literal, etc.)
2239
+ if self._is_object_literal():
2240
+ return self._parse_object()
2241
+ else:
2242
+ return self._parse_action_block_expression()
2038
2243
 
2039
2244
  if self._match(TokenType.BRACKET_START):
2040
2245
  return self._parse_array()
@@ -2061,14 +2266,10 @@ class CSSLParser:
2061
2266
  member = self._advance().value
2062
2267
  node = ASTNode('member_access', value={'object': node, 'member': member})
2063
2268
  elif self._match(TokenType.PAREN_START):
2064
- # Function call
2065
- args = []
2066
- while not self._check(TokenType.PAREN_END):
2067
- args.append(self._parse_expression())
2068
- if not self._check(TokenType.PAREN_END):
2069
- self._expect(TokenType.COMMA)
2269
+ # Function call - use _parse_call_arguments for kwargs support
2270
+ args, kwargs = self._parse_call_arguments()
2070
2271
  self._expect(TokenType.PAREN_END)
2071
- node = ASTNode('call', value={'callee': node, 'args': args})
2272
+ node = ASTNode('call', value={'callee': node, 'args': args, 'kwargs': kwargs})
2072
2273
  elif self._match(TokenType.BRACKET_START):
2073
2274
  # Index access
2074
2275
  index = self._parse_expression()
@@ -2079,9 +2280,61 @@ class CSSLParser:
2079
2280
 
2080
2281
  return node
2081
2282
 
2283
+ def _parse_call_arguments(self) -> tuple:
2284
+ """Parse function call arguments, supporting both positional and named (key=value).
2285
+
2286
+ Returns: (args, kwargs) where:
2287
+ args = list of positional argument expressions
2288
+ kwargs = dict of {name: expression} for named arguments
2289
+ """
2290
+ args = []
2291
+ kwargs = {}
2292
+
2293
+ while not self._check(TokenType.PAREN_END) and not self._is_at_end():
2294
+ # Check for named argument: identifier = expression
2295
+ if self._check(TokenType.IDENTIFIER):
2296
+ saved_pos = self.pos # Save token position
2297
+ name_token = self._advance()
2298
+
2299
+ if self._check(TokenType.EQUALS):
2300
+ # Named argument: name=value
2301
+ self._advance() # consume =
2302
+ value = self._parse_expression()
2303
+ kwargs[name_token.value] = value
2304
+ else:
2305
+ # Not named, restore and parse as expression
2306
+ self.pos = saved_pos # Restore token position
2307
+ args.append(self._parse_expression())
2308
+ else:
2309
+ args.append(self._parse_expression())
2310
+
2311
+ if not self._check(TokenType.PAREN_END):
2312
+ self._expect(TokenType.COMMA)
2313
+
2314
+ return args, kwargs
2315
+
2082
2316
  def _parse_identifier_or_call(self) -> ASTNode:
2083
2317
  name = self._advance().value
2084
2318
 
2319
+ # Check for namespace syntax: json::read, string::cut, etc.
2320
+ if self._match(TokenType.DOUBLE_COLON):
2321
+ if self._check(TokenType.IDENTIFIER) or self._check(TokenType.KEYWORD):
2322
+ namespace_member = self._advance().value
2323
+ name = f"{name}::{namespace_member}"
2324
+
2325
+ # Check for instance<"name"> syntax - gets/creates shared instance
2326
+ if name == 'instance' and self._check(TokenType.COMPARE_LT):
2327
+ self._advance() # consume <
2328
+ # Expect string literal for instance name
2329
+ if self._check(TokenType.STRING):
2330
+ instance_name = self._advance().value
2331
+ elif self._check(TokenType.IDENTIFIER):
2332
+ instance_name = self._advance().value
2333
+ else:
2334
+ raise CSSLParserError("Expected instance name (string or identifier)", self._current_line())
2335
+ self._expect(TokenType.COMPARE_GT) # consume >
2336
+ return ASTNode('instance_ref', value=instance_name)
2337
+
2085
2338
  # Check for type generic instantiation: stack<string>, vector<int>, etc.
2086
2339
  # This creates a new instance of the type with the specified element type
2087
2340
  if name in TYPE_GENERICS and self._check(TokenType.COMPARE_LT):
@@ -2127,13 +2380,9 @@ class CSSLParser:
2127
2380
  member = self._advance().value
2128
2381
  node = ASTNode('member_access', value={'object': node, 'member': member})
2129
2382
  elif self._match(TokenType.PAREN_START):
2130
- args = []
2131
- while not self._check(TokenType.PAREN_END):
2132
- args.append(self._parse_expression())
2133
- if not self._check(TokenType.PAREN_END):
2134
- self._expect(TokenType.COMMA)
2383
+ args, kwargs = self._parse_call_arguments()
2135
2384
  self._expect(TokenType.PAREN_END)
2136
- node = ASTNode('call', value={'callee': node, 'args': args})
2385
+ node = ASTNode('call', value={'callee': node, 'args': args, 'kwargs': kwargs})
2137
2386
  elif self._match(TokenType.BRACKET_START):
2138
2387
  index = self._parse_expression()
2139
2388
  self._expect(TokenType.BRACKET_END)
@@ -2143,6 +2392,61 @@ class CSSLParser:
2143
2392
 
2144
2393
  return node
2145
2394
 
2395
+ def _is_object_literal(self) -> bool:
2396
+ """Check if current position is an object literal { key = value } vs action block { expr; }
2397
+
2398
+ Object literal: { name = value; } or { "key" = value; }
2399
+ Action block: { %version; } or { "1.0.0" } or { call(); }
2400
+ """
2401
+ # Empty block is action block
2402
+ if self._check(TokenType.BLOCK_END):
2403
+ return False
2404
+
2405
+ # Save position for lookahead
2406
+ saved_pos = self.pos
2407
+
2408
+ # Check if it looks like key = value pattern
2409
+ is_object = False
2410
+ if self._check(TokenType.IDENTIFIER) or self._check(TokenType.STRING):
2411
+ self._advance() # skip key
2412
+ if self._check(TokenType.EQUALS):
2413
+ # Looks like object literal: { key = ...
2414
+ is_object = True
2415
+
2416
+ # Restore position
2417
+ self.pos = saved_pos
2418
+ return is_object
2419
+
2420
+ def _parse_action_block_expression(self) -> ASTNode:
2421
+ """Parse an action block expression: { expr; expr2; } returns last value
2422
+
2423
+ Used for: v <== { %version; } or v <== { "1.0.0" }
2424
+ """
2425
+ children = []
2426
+
2427
+ while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
2428
+ # Parse statement or expression
2429
+ if (self._check(TokenType.IDENTIFIER) or self._check(TokenType.AT) or
2430
+ self._check(TokenType.CAPTURED_REF) or self._check(TokenType.SHARED_REF) or
2431
+ self._check(TokenType.GLOBAL_REF) or self._check(TokenType.SELF_REF) or
2432
+ self._check(TokenType.STRING) or self._check(TokenType.NUMBER) or
2433
+ self._check(TokenType.BOOLEAN) or self._check(TokenType.NULL) or
2434
+ self._check(TokenType.PAREN_START)):
2435
+ # Parse as expression and wrap in expression node for _execute_node
2436
+ expr = self._parse_expression()
2437
+ self._match(TokenType.SEMICOLON)
2438
+ children.append(ASTNode('expression', value=expr))
2439
+ elif self._check(TokenType.KEYWORD):
2440
+ # Parse as statement
2441
+ stmt = self._parse_statement()
2442
+ if stmt:
2443
+ children.append(stmt)
2444
+ else:
2445
+ self._advance()
2446
+
2447
+ self._expect(TokenType.BLOCK_END)
2448
+ return ASTNode('action_block', children=children)
2449
+
2146
2450
  def _parse_object(self) -> ASTNode:
2147
2451
  properties = {}
2148
2452