IncludeCPP 3.4.8__py3-none-any.whl → 3.4.21__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/__init__.pyi +133 -2
- includecpp/cli/commands.py +126 -3
- includecpp/core/cssl/CSSL_DOCUMENTATION.md +1482 -0
- includecpp/core/cssl/__init__.py +8 -6
- includecpp/core/cssl/cssl_builtins.py +243 -5
- includecpp/core/cssl/cssl_parser.py +447 -12
- includecpp/core/cssl/cssl_runtime.py +831 -51
- includecpp/core/cssl/cssl_types.py +522 -7
- includecpp/core/cssl_bridge.py +374 -2
- includecpp/generator/parser.cpp +1 -1
- {includecpp-3.4.8.dist-info → includecpp-3.4.21.dist-info}/METADATA +270 -3
- {includecpp-3.4.8.dist-info → includecpp-3.4.21.dist-info}/RECORD +17 -16
- {includecpp-3.4.8.dist-info → includecpp-3.4.21.dist-info}/WHEEL +0 -0
- {includecpp-3.4.8.dist-info → includecpp-3.4.21.dist-info}/entry_points.txt +0 -0
- {includecpp-3.4.8.dist-info → includecpp-3.4.21.dist-info}/licenses/LICENSE +0 -0
- {includecpp-3.4.8.dist-info → includecpp-3.4.21.dist-info}/top_level.txt +0 -0
|
@@ -99,6 +99,7 @@ class TokenType(Enum):
|
|
|
99
99
|
AT = auto()
|
|
100
100
|
GLOBAL_REF = auto() # r@<name> global variable declaration
|
|
101
101
|
SELF_REF = auto() # s@<name> self-reference to global struct
|
|
102
|
+
SHARED_REF = auto() # $<name> shared object reference
|
|
102
103
|
PACKAGE = auto()
|
|
103
104
|
PACKAGE_INCLUDES = auto()
|
|
104
105
|
AS = auto()
|
|
@@ -154,6 +155,11 @@ TYPE_GENERICS = {
|
|
|
154
155
|
'vector', 'stack', 'array', 'openquote'
|
|
155
156
|
}
|
|
156
157
|
|
|
158
|
+
# Functions that accept type parameters: FuncName<type>(args)
|
|
159
|
+
TYPE_PARAM_FUNCTIONS = {
|
|
160
|
+
'OpenFind' # OpenFind<string>(0)
|
|
161
|
+
}
|
|
162
|
+
|
|
157
163
|
# Injection helper prefixes (type::helper=value)
|
|
158
164
|
INJECTION_HELPERS = {
|
|
159
165
|
'string', 'integer', 'json', 'array', 'vector', 'combo', 'dynamic', 'sql'
|
|
@@ -232,6 +238,9 @@ class CSSLLexer:
|
|
|
232
238
|
elif char == '@':
|
|
233
239
|
self._add_token(TokenType.AT, '@')
|
|
234
240
|
self._advance()
|
|
241
|
+
elif char == '$':
|
|
242
|
+
# $<name> shared object reference
|
|
243
|
+
self._read_shared_ref()
|
|
235
244
|
elif char == '&':
|
|
236
245
|
# & for references
|
|
237
246
|
if self._peek(1) == '&':
|
|
@@ -470,6 +479,20 @@ class CSSLLexer:
|
|
|
470
479
|
value = self.source[name_start:self.pos]
|
|
471
480
|
self._add_token(TokenType.GLOBAL_REF, value)
|
|
472
481
|
|
|
482
|
+
def _read_shared_ref(self):
|
|
483
|
+
"""Read $<name> shared object reference"""
|
|
484
|
+
self._advance() # skip '$'
|
|
485
|
+
|
|
486
|
+
# Read the identifier (shared object name)
|
|
487
|
+
name_start = self.pos
|
|
488
|
+
while self.pos < len(self.source) and (self.source[self.pos].isalnum() or self.source[self.pos] == '_'):
|
|
489
|
+
self._advance()
|
|
490
|
+
|
|
491
|
+
value = self.source[name_start:self.pos]
|
|
492
|
+
if not value:
|
|
493
|
+
self.error("Expected identifier after '$'")
|
|
494
|
+
self._add_token(TokenType.SHARED_REF, value)
|
|
495
|
+
|
|
473
496
|
def _read_less_than(self):
|
|
474
497
|
# Check for <<== (code infusion left)
|
|
475
498
|
if self._peek(1) == '<' and self._peek(2) == '=' and self._peek(3) == '=':
|
|
@@ -679,6 +702,44 @@ class CSSLParser:
|
|
|
679
702
|
self.pos = saved_pos
|
|
680
703
|
return False
|
|
681
704
|
|
|
705
|
+
def _looks_like_typed_variable(self) -> bool:
|
|
706
|
+
"""Check if current position looks like a typed variable declaration.
|
|
707
|
+
|
|
708
|
+
Patterns:
|
|
709
|
+
- int x;
|
|
710
|
+
- stack<string> myStack;
|
|
711
|
+
- vector<int> nums = [1,2,3];
|
|
712
|
+
|
|
713
|
+
Distinguishes from function declarations by checking for '(' after identifier.
|
|
714
|
+
"""
|
|
715
|
+
saved_pos = self.pos
|
|
716
|
+
|
|
717
|
+
# Check for type keyword
|
|
718
|
+
if self._check(TokenType.KEYWORD) and self._is_type_keyword(self._current().value):
|
|
719
|
+
self._advance()
|
|
720
|
+
|
|
721
|
+
# Skip generic type parameters <T>
|
|
722
|
+
if self._check(TokenType.COMPARE_LT):
|
|
723
|
+
depth = 1
|
|
724
|
+
self._advance()
|
|
725
|
+
while depth > 0 and not self._is_at_end():
|
|
726
|
+
if self._check(TokenType.COMPARE_LT):
|
|
727
|
+
depth += 1
|
|
728
|
+
elif self._check(TokenType.COMPARE_GT):
|
|
729
|
+
depth -= 1
|
|
730
|
+
self._advance()
|
|
731
|
+
|
|
732
|
+
# Check for identifier NOT followed by ( (that would be a function)
|
|
733
|
+
if self._check(TokenType.IDENTIFIER):
|
|
734
|
+
self._advance()
|
|
735
|
+
# If followed by '(' it's a function, not a variable
|
|
736
|
+
is_var = not self._check(TokenType.PAREN_START)
|
|
737
|
+
self.pos = saved_pos
|
|
738
|
+
return is_var
|
|
739
|
+
|
|
740
|
+
self.pos = saved_pos
|
|
741
|
+
return False
|
|
742
|
+
|
|
682
743
|
def _parse_typed_function(self) -> ASTNode:
|
|
683
744
|
"""Parse C-style typed function declaration.
|
|
684
745
|
|
|
@@ -800,6 +861,78 @@ class CSSLParser:
|
|
|
800
861
|
self._expect(TokenType.BLOCK_END)
|
|
801
862
|
return node
|
|
802
863
|
|
|
864
|
+
def _looks_like_typed_variable(self) -> bool:
|
|
865
|
+
"""Check if current position looks like a typed variable declaration:
|
|
866
|
+
type_name varName; or type_name<T> varName; or type_name varName = value;
|
|
867
|
+
"""
|
|
868
|
+
# Save position
|
|
869
|
+
saved_pos = self.pos
|
|
870
|
+
|
|
871
|
+
# Must start with a type keyword (int, string, stack, vector, etc.)
|
|
872
|
+
if not self._check(TokenType.KEYWORD):
|
|
873
|
+
return False
|
|
874
|
+
|
|
875
|
+
type_name = self._current().value
|
|
876
|
+
|
|
877
|
+
# Skip known type keywords
|
|
878
|
+
type_keywords = {'int', 'string', 'float', 'bool', 'dynamic', 'void',
|
|
879
|
+
'stack', 'vector', 'datastruct', 'dataspace', 'shuffled',
|
|
880
|
+
'iterator', 'combo', 'array', 'openquote', 'json'}
|
|
881
|
+
if type_name not in type_keywords:
|
|
882
|
+
return False
|
|
883
|
+
|
|
884
|
+
self._advance()
|
|
885
|
+
|
|
886
|
+
# Check for optional generic <T>
|
|
887
|
+
if self._match(TokenType.COMPARE_LT):
|
|
888
|
+
# Skip until >
|
|
889
|
+
depth = 1
|
|
890
|
+
while depth > 0 and not self._is_at_end():
|
|
891
|
+
if self._check(TokenType.COMPARE_LT):
|
|
892
|
+
depth += 1
|
|
893
|
+
elif self._check(TokenType.COMPARE_GT):
|
|
894
|
+
depth -= 1
|
|
895
|
+
self._advance()
|
|
896
|
+
|
|
897
|
+
# Next should be an identifier (variable name), not '(' (function) or ';'
|
|
898
|
+
result = self._check(TokenType.IDENTIFIER)
|
|
899
|
+
|
|
900
|
+
# Restore position
|
|
901
|
+
self.pos = saved_pos
|
|
902
|
+
return result
|
|
903
|
+
|
|
904
|
+
def _parse_typed_variable(self) -> Optional[ASTNode]:
|
|
905
|
+
"""Parse a typed variable declaration: type varName; or type<T> varName = value;"""
|
|
906
|
+
# Get type name
|
|
907
|
+
type_name = self._advance().value # Consume type keyword
|
|
908
|
+
|
|
909
|
+
# Check for generic type <T>
|
|
910
|
+
element_type = None
|
|
911
|
+
if self._match(TokenType.COMPARE_LT):
|
|
912
|
+
# Get element type
|
|
913
|
+
if self._check(TokenType.KEYWORD) or self._check(TokenType.IDENTIFIER):
|
|
914
|
+
element_type = self._advance().value
|
|
915
|
+
self._expect(TokenType.COMPARE_GT)
|
|
916
|
+
|
|
917
|
+
# Get variable name
|
|
918
|
+
if not self._check(TokenType.IDENTIFIER):
|
|
919
|
+
return None
|
|
920
|
+
var_name = self._advance().value
|
|
921
|
+
|
|
922
|
+
# Check for assignment or just declaration
|
|
923
|
+
value = None
|
|
924
|
+
if self._match(TokenType.EQUALS):
|
|
925
|
+
value = self._parse_expression()
|
|
926
|
+
|
|
927
|
+
self._match(TokenType.SEMICOLON)
|
|
928
|
+
|
|
929
|
+
return ASTNode('typed_declaration', value={
|
|
930
|
+
'type': type_name,
|
|
931
|
+
'element_type': element_type,
|
|
932
|
+
'name': var_name,
|
|
933
|
+
'value': value
|
|
934
|
+
})
|
|
935
|
+
|
|
803
936
|
def parse_program(self) -> ASTNode:
|
|
804
937
|
"""Parse a standalone program (no service wrapper)"""
|
|
805
938
|
root = ASTNode('program', children=[])
|
|
@@ -812,6 +945,11 @@ class CSSLParser:
|
|
|
812
945
|
# Check for C-style typed function declarations
|
|
813
946
|
elif self._looks_like_function_declaration():
|
|
814
947
|
root.children.append(self._parse_typed_function())
|
|
948
|
+
# Check for typed variable declarations (int x;, stack<string> s;)
|
|
949
|
+
elif self._looks_like_typed_variable():
|
|
950
|
+
decl = self._parse_typed_variable()
|
|
951
|
+
if decl:
|
|
952
|
+
root.children.append(decl)
|
|
815
953
|
# Handle service blocks
|
|
816
954
|
elif self._match_keyword('service-init'):
|
|
817
955
|
root.children.append(self._parse_service_init())
|
|
@@ -827,13 +965,17 @@ class CSSLParser:
|
|
|
827
965
|
elif self._match_keyword('global'):
|
|
828
966
|
stmt = self._parse_expression_statement()
|
|
829
967
|
if stmt:
|
|
830
|
-
|
|
968
|
+
# Wrap in global_assignment to mark as global variable
|
|
969
|
+
global_stmt = ASTNode('global_assignment', value=stmt)
|
|
970
|
+
root.children.append(global_stmt)
|
|
831
971
|
elif self._check(TokenType.GLOBAL_REF):
|
|
832
972
|
stmt = self._parse_expression_statement()
|
|
833
973
|
if stmt:
|
|
834
|
-
|
|
974
|
+
# Wrap in global_assignment to mark as global variable (same as 'global' keyword)
|
|
975
|
+
global_stmt = ASTNode('global_assignment', value=stmt)
|
|
976
|
+
root.children.append(global_stmt)
|
|
835
977
|
# Handle statements
|
|
836
|
-
elif self._check(TokenType.IDENTIFIER) or self._check(TokenType.AT) or self._check(TokenType.SELF_REF):
|
|
978
|
+
elif self._check(TokenType.IDENTIFIER) or self._check(TokenType.AT) or self._check(TokenType.SELF_REF) or self._check(TokenType.SHARED_REF):
|
|
837
979
|
stmt = self._parse_expression_statement()
|
|
838
980
|
if stmt:
|
|
839
981
|
root.children.append(stmt)
|
|
@@ -1143,6 +1285,9 @@ class CSSLParser:
|
|
|
1143
1285
|
elif self._match_keyword('define'):
|
|
1144
1286
|
# Nested define function
|
|
1145
1287
|
return self._parse_define()
|
|
1288
|
+
elif self._looks_like_typed_variable():
|
|
1289
|
+
# Typed variable declaration (e.g., stack<string> myStack;)
|
|
1290
|
+
return self._parse_typed_variable()
|
|
1146
1291
|
elif self._looks_like_function_declaration():
|
|
1147
1292
|
# Nested typed function (e.g., void Level2() { ... })
|
|
1148
1293
|
return self._parse_typed_function()
|
|
@@ -1153,6 +1298,7 @@ class CSSLParser:
|
|
|
1153
1298
|
return None
|
|
1154
1299
|
|
|
1155
1300
|
def _parse_if(self) -> ASTNode:
|
|
1301
|
+
"""Parse if statement with support for else if AND elif syntax."""
|
|
1156
1302
|
self._expect(TokenType.PAREN_START)
|
|
1157
1303
|
condition = self._parse_expression()
|
|
1158
1304
|
self._expect(TokenType.PAREN_END)
|
|
@@ -1168,7 +1314,13 @@ class CSSLParser:
|
|
|
1168
1314
|
self._expect(TokenType.BLOCK_END)
|
|
1169
1315
|
node.children.append(then_block)
|
|
1170
1316
|
|
|
1171
|
-
|
|
1317
|
+
# Support both 'else if' AND 'elif' syntax
|
|
1318
|
+
if self._match_keyword('elif'):
|
|
1319
|
+
# elif is shorthand for else if
|
|
1320
|
+
else_block = ASTNode('else', children=[])
|
|
1321
|
+
else_block.children.append(self._parse_if())
|
|
1322
|
+
node.children.append(else_block)
|
|
1323
|
+
elif self._match_keyword('else'):
|
|
1172
1324
|
else_block = ASTNode('else', children=[])
|
|
1173
1325
|
if self._match_keyword('if'):
|
|
1174
1326
|
else_block.children.append(self._parse_if())
|
|
@@ -1200,18 +1352,186 @@ class CSSLParser:
|
|
|
1200
1352
|
return node
|
|
1201
1353
|
|
|
1202
1354
|
def _parse_for(self) -> ASTNode:
|
|
1355
|
+
"""Parse for loop - supports both syntaxes:
|
|
1356
|
+
|
|
1357
|
+
Python-style: for (i in range(0, n)) { }
|
|
1358
|
+
C-style: for (int i = 0; i < n; i = i + 1) { }
|
|
1359
|
+
for (i = 0; i < n; i++) { }
|
|
1360
|
+
"""
|
|
1203
1361
|
self._expect(TokenType.PAREN_START)
|
|
1362
|
+
|
|
1363
|
+
# Detect C-style by checking for semicolons in the for header
|
|
1364
|
+
# Look ahead without consuming tokens
|
|
1365
|
+
is_c_style = self._detect_c_style_for()
|
|
1366
|
+
|
|
1367
|
+
if is_c_style:
|
|
1368
|
+
# C-style: for (init; condition; update) { }
|
|
1369
|
+
return self._parse_c_style_for()
|
|
1370
|
+
else:
|
|
1371
|
+
# Python-style: for (var in range(start, end)) { }
|
|
1372
|
+
return self._parse_python_style_for()
|
|
1373
|
+
|
|
1374
|
+
def _detect_c_style_for(self) -> bool:
|
|
1375
|
+
"""Detect if this is a C-style for loop by looking for semicolons."""
|
|
1376
|
+
# Scan the tokens list directly without modifying self.pos
|
|
1377
|
+
pos = self.pos
|
|
1378
|
+
paren_depth = 1
|
|
1379
|
+
|
|
1380
|
+
while pos < len(self.tokens) and paren_depth > 0:
|
|
1381
|
+
token = self.tokens[pos]
|
|
1382
|
+
if token.type == TokenType.PAREN_START:
|
|
1383
|
+
paren_depth += 1
|
|
1384
|
+
elif token.type == TokenType.PAREN_END:
|
|
1385
|
+
paren_depth -= 1
|
|
1386
|
+
elif token.type == TokenType.SEMICOLON and paren_depth == 1:
|
|
1387
|
+
# Found semicolon at top level - C-style
|
|
1388
|
+
return True
|
|
1389
|
+
elif token.type == TokenType.KEYWORD and token.value == 'in':
|
|
1390
|
+
# Found 'in' keyword - Python-style
|
|
1391
|
+
return False
|
|
1392
|
+
pos += 1
|
|
1393
|
+
|
|
1394
|
+
return False # Default to Python-style
|
|
1395
|
+
|
|
1396
|
+
def _parse_c_style_for(self) -> ASTNode:
|
|
1397
|
+
"""Parse C-style for loop: for (init; condition; update) { }"""
|
|
1398
|
+
# Parse init statement
|
|
1399
|
+
init = None
|
|
1400
|
+
if not self._check(TokenType.SEMICOLON):
|
|
1401
|
+
# Check if it's a typed declaration: int i = 0
|
|
1402
|
+
if self._check(TokenType.KEYWORD) and self._peek().value in ('int', 'float', 'string', 'bool', 'dynamic'):
|
|
1403
|
+
type_name = self._advance().value
|
|
1404
|
+
var_name = self._advance().value
|
|
1405
|
+
self._expect(TokenType.EQUALS)
|
|
1406
|
+
value = self._parse_expression()
|
|
1407
|
+
init = ASTNode('c_for_init', value={
|
|
1408
|
+
'type': type_name,
|
|
1409
|
+
'var': var_name,
|
|
1410
|
+
'value': value
|
|
1411
|
+
})
|
|
1412
|
+
else:
|
|
1413
|
+
# Simple assignment: i = 0
|
|
1414
|
+
var_name = self._advance().value
|
|
1415
|
+
self._expect(TokenType.EQUALS)
|
|
1416
|
+
value = self._parse_expression()
|
|
1417
|
+
init = ASTNode('c_for_init', value={
|
|
1418
|
+
'type': None,
|
|
1419
|
+
'var': var_name,
|
|
1420
|
+
'value': value
|
|
1421
|
+
})
|
|
1422
|
+
|
|
1423
|
+
self._expect(TokenType.SEMICOLON)
|
|
1424
|
+
|
|
1425
|
+
# Parse condition
|
|
1426
|
+
condition = None
|
|
1427
|
+
if not self._check(TokenType.SEMICOLON):
|
|
1428
|
+
condition = self._parse_expression()
|
|
1429
|
+
|
|
1430
|
+
self._expect(TokenType.SEMICOLON)
|
|
1431
|
+
|
|
1432
|
+
# Parse update statement
|
|
1433
|
+
update = None
|
|
1434
|
+
if not self._check(TokenType.PAREN_END):
|
|
1435
|
+
# Could be: i = i + 1, i++, ++i, i += 1
|
|
1436
|
+
update = self._parse_c_for_update()
|
|
1437
|
+
|
|
1438
|
+
self._expect(TokenType.PAREN_END)
|
|
1439
|
+
|
|
1440
|
+
node = ASTNode('c_for', value={
|
|
1441
|
+
'init': init,
|
|
1442
|
+
'condition': condition,
|
|
1443
|
+
'update': update
|
|
1444
|
+
}, children=[])
|
|
1445
|
+
|
|
1446
|
+
self._expect(TokenType.BLOCK_START)
|
|
1447
|
+
|
|
1448
|
+
while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
|
|
1449
|
+
stmt = self._parse_statement()
|
|
1450
|
+
if stmt:
|
|
1451
|
+
node.children.append(stmt)
|
|
1452
|
+
|
|
1453
|
+
self._expect(TokenType.BLOCK_END)
|
|
1454
|
+
return node
|
|
1455
|
+
|
|
1456
|
+
def _parse_c_for_update(self) -> ASTNode:
|
|
1457
|
+
"""Parse the update part of a C-style for loop.
|
|
1458
|
+
|
|
1459
|
+
Supports: i = i + 1, i++, ++i, i += 1, i -= 1
|
|
1460
|
+
"""
|
|
1461
|
+
# Check for prefix increment/decrement: ++i or --i
|
|
1462
|
+
if self._check(TokenType.PLUS) or self._check(TokenType.MINUS):
|
|
1463
|
+
op_token = self._advance()
|
|
1464
|
+
# Check for double operator (++ or --)
|
|
1465
|
+
if self._check(op_token.type):
|
|
1466
|
+
self._advance()
|
|
1467
|
+
var_name = self._advance().value
|
|
1468
|
+
op = 'increment' if op_token.type == TokenType.PLUS else 'decrement'
|
|
1469
|
+
return ASTNode('c_for_update', value={'var': var_name, 'op': op})
|
|
1470
|
+
|
|
1471
|
+
# Regular variable assignment or postfix
|
|
1472
|
+
var_name = self._advance().value
|
|
1473
|
+
|
|
1474
|
+
# Check for postfix increment/decrement: i++ or i--
|
|
1475
|
+
if self._check(TokenType.PLUS):
|
|
1476
|
+
self._advance()
|
|
1477
|
+
if self._check(TokenType.PLUS):
|
|
1478
|
+
self._advance()
|
|
1479
|
+
return ASTNode('c_for_update', value={'var': var_name, 'op': 'increment'})
|
|
1480
|
+
else:
|
|
1481
|
+
# i += value
|
|
1482
|
+
if self._check(TokenType.EQUALS):
|
|
1483
|
+
self._advance()
|
|
1484
|
+
value = self._parse_expression()
|
|
1485
|
+
return ASTNode('c_for_update', value={'var': var_name, 'op': 'add', 'value': value})
|
|
1486
|
+
elif self._check(TokenType.MINUS):
|
|
1487
|
+
self._advance()
|
|
1488
|
+
if self._check(TokenType.MINUS):
|
|
1489
|
+
self._advance()
|
|
1490
|
+
return ASTNode('c_for_update', value={'var': var_name, 'op': 'decrement'})
|
|
1491
|
+
else:
|
|
1492
|
+
# i -= value
|
|
1493
|
+
if self._check(TokenType.EQUALS):
|
|
1494
|
+
self._advance()
|
|
1495
|
+
value = self._parse_expression()
|
|
1496
|
+
return ASTNode('c_for_update', value={'var': var_name, 'op': 'subtract', 'value': value})
|
|
1497
|
+
|
|
1498
|
+
# Regular assignment: i = expression
|
|
1499
|
+
if self._check(TokenType.EQUALS):
|
|
1500
|
+
self._advance()
|
|
1501
|
+
value = self._parse_expression()
|
|
1502
|
+
return ASTNode('c_for_update', value={'var': var_name, 'op': 'assign', 'value': value})
|
|
1503
|
+
|
|
1504
|
+
# Just the variable (shouldn't happen but handle it)
|
|
1505
|
+
return ASTNode('c_for_update', value={'var': var_name, 'op': 'none'})
|
|
1506
|
+
|
|
1507
|
+
def _parse_python_style_for(self) -> ASTNode:
|
|
1508
|
+
"""Parse Python-style for loop: for (i in range(start, end)) { }"""
|
|
1204
1509
|
var_name = self._advance().value
|
|
1205
|
-
self._expect(TokenType.KEYWORD)
|
|
1206
|
-
|
|
1510
|
+
self._expect(TokenType.KEYWORD) # 'in'
|
|
1511
|
+
|
|
1512
|
+
# 'range' can be keyword or identifier
|
|
1513
|
+
if self._check(TokenType.KEYWORD) and self._peek().value == 'range':
|
|
1514
|
+
self._advance() # consume 'range' keyword
|
|
1515
|
+
elif self._check(TokenType.IDENTIFIER) and self._peek().value == 'range':
|
|
1516
|
+
self._advance() # consume 'range' identifier
|
|
1517
|
+
else:
|
|
1518
|
+
self.error(f"Expected 'range', got {self._peek().value}")
|
|
1519
|
+
|
|
1207
1520
|
self._expect(TokenType.PAREN_START)
|
|
1208
1521
|
start = self._parse_expression()
|
|
1209
1522
|
self._expect(TokenType.COMMA)
|
|
1210
1523
|
end = self._parse_expression()
|
|
1524
|
+
|
|
1525
|
+
# Optional step parameter: range(start, end, step)
|
|
1526
|
+
step = None
|
|
1527
|
+
if self._check(TokenType.COMMA):
|
|
1528
|
+
self._advance() # consume comma
|
|
1529
|
+
step = self._parse_expression()
|
|
1530
|
+
|
|
1211
1531
|
self._expect(TokenType.PAREN_END)
|
|
1212
1532
|
self._expect(TokenType.PAREN_END)
|
|
1213
1533
|
|
|
1214
|
-
node = ASTNode('for', value={'var': var_name, 'start': start, 'end': end}, children=[])
|
|
1534
|
+
node = ASTNode('for', value={'var': var_name, 'start': start, 'end': end, 'step': step}, children=[])
|
|
1215
1535
|
self._expect(TokenType.BLOCK_START)
|
|
1216
1536
|
|
|
1217
1537
|
while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
|
|
@@ -1640,6 +1960,56 @@ class CSSLParser:
|
|
|
1640
1960
|
node = ASTNode('call', value={'callee': node, 'args': args})
|
|
1641
1961
|
return node
|
|
1642
1962
|
|
|
1963
|
+
if self._check(TokenType.GLOBAL_REF):
|
|
1964
|
+
# r@<name> global variable reference/declaration
|
|
1965
|
+
token = self._advance()
|
|
1966
|
+
node = ASTNode('global_ref', value=token.value, line=token.line, column=token.column)
|
|
1967
|
+
# Check for member access, calls, indexing
|
|
1968
|
+
while True:
|
|
1969
|
+
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)
|
|
1975
|
+
self._expect(TokenType.PAREN_END)
|
|
1976
|
+
node = ASTNode('call', value={'callee': node, 'args': args})
|
|
1977
|
+
elif self._match(TokenType.DOT):
|
|
1978
|
+
member = self._advance().value
|
|
1979
|
+
node = ASTNode('member_access', value={'object': node, 'member': member})
|
|
1980
|
+
elif self._match(TokenType.BRACKET_START):
|
|
1981
|
+
index = self._parse_expression()
|
|
1982
|
+
self._expect(TokenType.BRACKET_END)
|
|
1983
|
+
node = ASTNode('index_access', value={'object': node, 'index': index})
|
|
1984
|
+
else:
|
|
1985
|
+
break
|
|
1986
|
+
return node
|
|
1987
|
+
|
|
1988
|
+
if self._check(TokenType.SHARED_REF):
|
|
1989
|
+
# $<name> shared object reference
|
|
1990
|
+
token = self._advance()
|
|
1991
|
+
node = ASTNode('shared_ref', value=token.value, line=token.line, column=token.column)
|
|
1992
|
+
# Check for member access, calls, indexing
|
|
1993
|
+
while True:
|
|
1994
|
+
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)
|
|
2000
|
+
self._expect(TokenType.PAREN_END)
|
|
2001
|
+
node = ASTNode('call', value={'callee': node, 'args': args})
|
|
2002
|
+
elif self._match(TokenType.DOT):
|
|
2003
|
+
member = self._advance().value
|
|
2004
|
+
node = ASTNode('member_access', value={'object': node, 'member': member})
|
|
2005
|
+
elif self._match(TokenType.BRACKET_START):
|
|
2006
|
+
index = self._parse_expression()
|
|
2007
|
+
self._expect(TokenType.BRACKET_END)
|
|
2008
|
+
node = ASTNode('index_access', value={'object': node, 'index': index})
|
|
2009
|
+
else:
|
|
2010
|
+
break
|
|
2011
|
+
return node
|
|
2012
|
+
|
|
1643
2013
|
if self._check(TokenType.NUMBER):
|
|
1644
2014
|
return ASTNode('literal', value=self._advance().value)
|
|
1645
2015
|
|
|
@@ -1675,16 +2045,81 @@ class CSSLParser:
|
|
|
1675
2045
|
return ASTNode('literal', value=None)
|
|
1676
2046
|
|
|
1677
2047
|
def _parse_module_reference(self) -> ASTNode:
|
|
1678
|
-
|
|
1679
|
-
|
|
2048
|
+
"""Parse @name, handling method calls and property access.
|
|
2049
|
+
|
|
2050
|
+
@name alone -> module_ref
|
|
2051
|
+
@name.method() -> call with member_access
|
|
2052
|
+
@name.property -> member_access
|
|
2053
|
+
"""
|
|
2054
|
+
# Get base name
|
|
2055
|
+
name = self._advance().value
|
|
2056
|
+
node = ASTNode('module_ref', value=name)
|
|
1680
2057
|
|
|
1681
|
-
|
|
1682
|
-
|
|
2058
|
+
# Continue to handle member access, calls, and indexing
|
|
2059
|
+
while True:
|
|
2060
|
+
if self._match(TokenType.DOT):
|
|
2061
|
+
member = self._advance().value
|
|
2062
|
+
node = ASTNode('member_access', value={'object': node, 'member': member})
|
|
2063
|
+
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)
|
|
2070
|
+
self._expect(TokenType.PAREN_END)
|
|
2071
|
+
node = ASTNode('call', value={'callee': node, 'args': args})
|
|
2072
|
+
elif self._match(TokenType.BRACKET_START):
|
|
2073
|
+
# Index access
|
|
2074
|
+
index = self._parse_expression()
|
|
2075
|
+
self._expect(TokenType.BRACKET_END)
|
|
2076
|
+
node = ASTNode('index_access', value={'object': node, 'index': index})
|
|
2077
|
+
else:
|
|
2078
|
+
break
|
|
1683
2079
|
|
|
1684
|
-
return
|
|
2080
|
+
return node
|
|
1685
2081
|
|
|
1686
2082
|
def _parse_identifier_or_call(self) -> ASTNode:
|
|
1687
2083
|
name = self._advance().value
|
|
2084
|
+
|
|
2085
|
+
# Check for type generic instantiation: stack<string>, vector<int>, etc.
|
|
2086
|
+
# This creates a new instance of the type with the specified element type
|
|
2087
|
+
if name in TYPE_GENERICS and self._check(TokenType.COMPARE_LT):
|
|
2088
|
+
self._advance() # consume <
|
|
2089
|
+
element_type = 'dynamic'
|
|
2090
|
+
if self._check(TokenType.KEYWORD) or self._check(TokenType.IDENTIFIER):
|
|
2091
|
+
element_type = self._advance().value
|
|
2092
|
+
self._expect(TokenType.COMPARE_GT) # consume >
|
|
2093
|
+
return ASTNode('type_instantiation', value={
|
|
2094
|
+
'type': name,
|
|
2095
|
+
'element_type': element_type
|
|
2096
|
+
})
|
|
2097
|
+
|
|
2098
|
+
# Check for type-parameterized function call: OpenFind<string>(0)
|
|
2099
|
+
if name in TYPE_PARAM_FUNCTIONS and self._check(TokenType.COMPARE_LT):
|
|
2100
|
+
self._advance() # consume <
|
|
2101
|
+
type_param = 'dynamic'
|
|
2102
|
+
if self._check(TokenType.KEYWORD) or self._check(TokenType.IDENTIFIER):
|
|
2103
|
+
type_param = self._advance().value
|
|
2104
|
+
self._expect(TokenType.COMPARE_GT) # consume >
|
|
2105
|
+
|
|
2106
|
+
# Must be followed by ()
|
|
2107
|
+
if self._check(TokenType.PAREN_START):
|
|
2108
|
+
self._advance() # consume (
|
|
2109
|
+
args = []
|
|
2110
|
+
while not self._check(TokenType.PAREN_END):
|
|
2111
|
+
args.append(self._parse_expression())
|
|
2112
|
+
if not self._check(TokenType.PAREN_END):
|
|
2113
|
+
self._expect(TokenType.COMMA)
|
|
2114
|
+
self._expect(TokenType.PAREN_END)
|
|
2115
|
+
|
|
2116
|
+
# Return as typed function call
|
|
2117
|
+
return ASTNode('typed_call', value={
|
|
2118
|
+
'name': name,
|
|
2119
|
+
'type_param': type_param,
|
|
2120
|
+
'args': args
|
|
2121
|
+
})
|
|
2122
|
+
|
|
1688
2123
|
node = ASTNode('identifier', value=name)
|
|
1689
2124
|
|
|
1690
2125
|
while True:
|