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.
- includecpp/__init__.py +1 -1
- includecpp/cli/commands.py +418 -0
- includecpp/core/cssl/CSSL_DOCUMENTATION.md +613 -20
- includecpp/core/cssl/cssl_builtins.py +342 -2
- includecpp/core/cssl/cssl_builtins.pyi +1393 -0
- includecpp/core/cssl/cssl_parser.py +368 -64
- includecpp/core/cssl/cssl_runtime.py +637 -38
- includecpp/core/cssl/cssl_types.py +561 -2
- includecpp/core/cssl_bridge.py +100 -4
- includecpp/core/cssl_bridge.pyi +177 -0
- includecpp/vscode/cssl/package.json +24 -4
- includecpp/vscode/cssl/snippets/cssl.snippets.json +1080 -0
- includecpp/vscode/cssl/syntaxes/cssl.tmLanguage.json +127 -7
- {includecpp-3.5.0.dist-info → includecpp-3.7.1.dist-info}/METADATA +1 -1
- {includecpp-3.5.0.dist-info → includecpp-3.7.1.dist-info}/RECORD +19 -17
- {includecpp-3.5.0.dist-info → includecpp-3.7.1.dist-info}/WHEEL +0 -0
- {includecpp-3.5.0.dist-info → includecpp-3.7.1.dist-info}/entry_points.txt +0 -0
- {includecpp-3.5.0.dist-info → includecpp-3.7.1.dist-info}/licenses/LICENSE +0 -0
- {includecpp-3.5.0.dist-info → includecpp-3.7.1.dist-info}/top_level.txt +0 -0
|
@@ -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
|
|
544
|
-
elif self._peek(1) == '=' and self._peek(2) == '
|
|
545
|
-
self._add_token(TokenType.INJECT_MINUS_RIGHT, '
|
|
546
|
-
for _ in range(
|
|
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
|
-
#
|
|
913
|
-
if
|
|
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(
|
|
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
|
-
|
|
1522
|
-
self._expect(TokenType.COMMA)
|
|
1523
|
-
end = self._parse_expression()
|
|
1616
|
+
first_arg = self._parse_expression()
|
|
1524
1617
|
|
|
1525
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|