IncludeCPP 3.7.1__py3-none-any.whl → 3.7.25__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 +2 -2
- includecpp/cli/commands.py +278 -75
- includecpp/core/cssl/CSSL_DOCUMENTATION.md +27 -8
- includecpp/core/cssl/__init__.py +7 -2
- includecpp/core/cssl/cssl_builtins.py +201 -9
- includecpp/core/cssl/cssl_builtins.pyi +3682 -401
- includecpp/core/cssl/cssl_parser.py +291 -40
- includecpp/core/cssl/cssl_runtime.py +629 -40
- includecpp/core/cssl/cssl_syntax.py +7 -7
- includecpp/core/cssl/cssl_types.py +75 -2
- includecpp/core/cssl_bridge.py +540 -53
- includecpp/vscode/cssl/extension.js +133 -0
- includecpp/vscode/cssl/images/cssl.png +0 -0
- includecpp/vscode/cssl/images/cssl_pl.png +0 -0
- includecpp/vscode/cssl/language-configuration.json +1 -4
- includecpp/vscode/cssl/package.json +117 -11
- includecpp/vscode/cssl/syntaxes/cssl.tmLanguage.json +213 -29
- {includecpp-3.7.1.dist-info → includecpp-3.7.25.dist-info}/METADATA +2 -2
- {includecpp-3.7.1.dist-info → includecpp-3.7.25.dist-info}/RECORD +24 -21
- {includecpp-3.7.1.dist-info → includecpp-3.7.25.dist-info}/WHEEL +0 -0
- {includecpp-3.7.1.dist-info → includecpp-3.7.25.dist-info}/entry_points.txt +0 -0
- {includecpp-3.7.1.dist-info → includecpp-3.7.25.dist-info}/licenses/LICENSE +0 -0
- {includecpp-3.7.1.dist-info → includecpp-3.7.25.dist-info}/top_level.txt +0 -0
|
@@ -19,6 +19,45 @@ from .cssl_types import (
|
|
|
19
19
|
)
|
|
20
20
|
|
|
21
21
|
|
|
22
|
+
# Global custom filter registry
|
|
23
|
+
# Structure: { "type::helper": callback_function }
|
|
24
|
+
# Callback signature: (source, filter_value, runtime) -> Any
|
|
25
|
+
_custom_filters: Dict[str, Callable[[Any, Any, Any], Any]] = {}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def register_filter(filter_type: str, helper: str, callback: Callable[[Any, Any, Any], Any]) -> None:
|
|
29
|
+
"""Register a custom filter.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
filter_type: The filter type (e.g., "mytype")
|
|
33
|
+
helper: The helper name (e.g., "where", "index", or "*" for catch-all)
|
|
34
|
+
callback: Function(source, filter_value, runtime) -> filtered_result
|
|
35
|
+
|
|
36
|
+
Usage in CSSL:
|
|
37
|
+
result <==[mytype::where="value"] source;
|
|
38
|
+
|
|
39
|
+
Usage from Python:
|
|
40
|
+
from includecpp.core.cssl.cssl_runtime import register_filter
|
|
41
|
+
register_filter("mytype", "where", lambda src, val, rt: ...)
|
|
42
|
+
"""
|
|
43
|
+
key = f"{filter_type}::{helper}"
|
|
44
|
+
_custom_filters[key] = callback
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def unregister_filter(filter_type: str, helper: str) -> bool:
|
|
48
|
+
"""Unregister a custom filter."""
|
|
49
|
+
key = f"{filter_type}::{helper}"
|
|
50
|
+
if key in _custom_filters:
|
|
51
|
+
del _custom_filters[key]
|
|
52
|
+
return True
|
|
53
|
+
return False
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def get_custom_filters() -> Dict[str, Callable]:
|
|
57
|
+
"""Get all registered custom filters."""
|
|
58
|
+
return _custom_filters.copy()
|
|
59
|
+
|
|
60
|
+
|
|
22
61
|
class CSSLRuntimeError(Exception):
|
|
23
62
|
"""Runtime error during CSSL execution with detailed context"""
|
|
24
63
|
def __init__(self, message: str, line: int = 0, context: str = None, hint: str = None):
|
|
@@ -401,6 +440,9 @@ class CSSLRuntime:
|
|
|
401
440
|
elif child.type == 'instance_declaration':
|
|
402
441
|
# Handle instance declaration: instance<"name"> varName;
|
|
403
442
|
result = self._exec_instance_declaration(child)
|
|
443
|
+
elif child.type == 'super_func':
|
|
444
|
+
# Super-function for .cssl-pl payload files (#$run, #$exec, #$printl)
|
|
445
|
+
result = self._exec_super_func(child)
|
|
404
446
|
elif child.type in ('assignment', 'expression', 'inject', 'receive', 'flow',
|
|
405
447
|
'if', 'while', 'for', 'c_for', 'foreach', 'switch', 'try'):
|
|
406
448
|
result = self._execute_node(child)
|
|
@@ -788,8 +830,18 @@ class CSSLRuntime:
|
|
|
788
830
|
if hasattr(instance, 'append'):
|
|
789
831
|
instance.append(init_value)
|
|
790
832
|
|
|
833
|
+
# Check for global modifier
|
|
834
|
+
modifiers = decl.get('modifiers', [])
|
|
835
|
+
is_global = 'global' in modifiers
|
|
836
|
+
|
|
791
837
|
# Store in scope
|
|
792
838
|
self.scope.set(var_name, instance)
|
|
839
|
+
|
|
840
|
+
# If global, also store in promoted_globals and global_scope
|
|
841
|
+
if is_global:
|
|
842
|
+
self._promoted_globals[var_name] = instance
|
|
843
|
+
self.global_scope.set(var_name, instance)
|
|
844
|
+
|
|
793
845
|
return instance
|
|
794
846
|
|
|
795
847
|
def _exec_instance_declaration(self, node: ASTNode) -> Any:
|
|
@@ -820,6 +872,82 @@ class CSSLRuntime:
|
|
|
820
872
|
self.scope.set(var_name, instance)
|
|
821
873
|
return instance
|
|
822
874
|
|
|
875
|
+
def _exec_super_func(self, node: ASTNode) -> Any:
|
|
876
|
+
"""Execute super-function for .cssl-pl payload files.
|
|
877
|
+
|
|
878
|
+
Super-functions are pre-execution hooks that run when payload() loads a file.
|
|
879
|
+
|
|
880
|
+
Supported super-functions:
|
|
881
|
+
#$run(funcName) - Call a function defined in the payload
|
|
882
|
+
#$exec(expression) - Execute an expression immediately
|
|
883
|
+
#$printl(message) - Print a message during load
|
|
884
|
+
|
|
885
|
+
Example .cssl-pl file:
|
|
886
|
+
void initDatabase() {
|
|
887
|
+
printl("DB initialized");
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
#$run(initDatabase); // Calls initDatabase when payload loads
|
|
891
|
+
#$printl("Payload loaded"); // Prints during load
|
|
892
|
+
"""
|
|
893
|
+
super_info = node.value
|
|
894
|
+
super_name = super_info.get('name', '') # e.g., "#$run", "#$exec", "#$printl"
|
|
895
|
+
args = super_info.get('args', [])
|
|
896
|
+
|
|
897
|
+
# Extract the function name part (after #$)
|
|
898
|
+
if super_name.startswith('#$'):
|
|
899
|
+
func_type = super_name[2:] # "run", "exec", "printl"
|
|
900
|
+
else:
|
|
901
|
+
func_type = super_name
|
|
902
|
+
|
|
903
|
+
if func_type == 'run':
|
|
904
|
+
# #$run(funcName) - Call a function by name
|
|
905
|
+
if args:
|
|
906
|
+
func_ref = args[0]
|
|
907
|
+
if isinstance(func_ref, ASTNode):
|
|
908
|
+
if func_ref.type == 'identifier':
|
|
909
|
+
func_name = func_ref.value
|
|
910
|
+
elif func_ref.type == 'call':
|
|
911
|
+
# Direct call like #$run(setup())
|
|
912
|
+
return self._eval_call(func_ref)
|
|
913
|
+
else:
|
|
914
|
+
func_name = self._evaluate(func_ref)
|
|
915
|
+
else:
|
|
916
|
+
func_name = str(func_ref)
|
|
917
|
+
|
|
918
|
+
# Look up and call the function
|
|
919
|
+
func_node = self.scope.get(func_name)
|
|
920
|
+
if func_node and isinstance(func_node, ASTNode) and func_node.type == 'function':
|
|
921
|
+
return self._call_function(func_node, [])
|
|
922
|
+
else:
|
|
923
|
+
raise CSSLRuntimeError(f"#$run: Function '{func_name}' not found", node.line)
|
|
924
|
+
|
|
925
|
+
elif func_type == 'exec':
|
|
926
|
+
# #$exec(expression) - Execute an expression
|
|
927
|
+
if args:
|
|
928
|
+
return self._evaluate(args[0])
|
|
929
|
+
|
|
930
|
+
elif func_type == 'printl':
|
|
931
|
+
# #$printl(message) - Print a message
|
|
932
|
+
if args:
|
|
933
|
+
msg = self._evaluate(args[0])
|
|
934
|
+
print(str(msg))
|
|
935
|
+
self.output_buffer.append(str(msg))
|
|
936
|
+
return None
|
|
937
|
+
|
|
938
|
+
elif func_type == 'print':
|
|
939
|
+
# #$print(message) - Print without newline
|
|
940
|
+
if args:
|
|
941
|
+
msg = self._evaluate(args[0])
|
|
942
|
+
print(str(msg), end='')
|
|
943
|
+
self.output_buffer.append(str(msg))
|
|
944
|
+
return None
|
|
945
|
+
|
|
946
|
+
else:
|
|
947
|
+
raise CSSLRuntimeError(f"Unknown super-function: {super_name}", node.line)
|
|
948
|
+
|
|
949
|
+
return None
|
|
950
|
+
|
|
823
951
|
def _exec_global_assignment(self, node: ASTNode) -> Any:
|
|
824
952
|
"""Execute global variable assignment: global Name = value
|
|
825
953
|
|
|
@@ -860,6 +988,14 @@ class CSSLRuntime:
|
|
|
860
988
|
result = self._evaluate(inner.value)
|
|
861
989
|
return result
|
|
862
990
|
|
|
991
|
+
# Handle typed declaration: global datastruct<int> data;
|
|
992
|
+
elif inner.type == 'typed_declaration':
|
|
993
|
+
# Add global modifier to the declaration
|
|
994
|
+
if isinstance(inner.value, dict):
|
|
995
|
+
inner.value['modifiers'] = inner.value.get('modifiers', []) + ['global']
|
|
996
|
+
result = self._exec_typed_declaration(inner)
|
|
997
|
+
return result
|
|
998
|
+
|
|
863
999
|
# Fallback: execute normally
|
|
864
1000
|
return self._execute_node(inner)
|
|
865
1001
|
|
|
@@ -884,10 +1020,20 @@ class CSSLRuntime:
|
|
|
884
1020
|
|
|
885
1021
|
# Bind parameters - handle both positional and named arguments
|
|
886
1022
|
for i, param in enumerate(params):
|
|
887
|
-
# Extract param name from dict format: {'name': 'a', 'type': 'int'}
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
1023
|
+
# Extract param name and type from dict format: {'name': 'a', 'type': 'int'}
|
|
1024
|
+
if isinstance(param, dict):
|
|
1025
|
+
param_name = param['name']
|
|
1026
|
+
param_type = param.get('type', '')
|
|
1027
|
+
else:
|
|
1028
|
+
param_name = param
|
|
1029
|
+
param_type = ''
|
|
1030
|
+
|
|
1031
|
+
# Check if this is an 'open' parameter - receives all args as a list
|
|
1032
|
+
if param_type == 'open' or param_name == 'Params':
|
|
1033
|
+
# 'open Params' receives all arguments as a list
|
|
1034
|
+
new_scope.set(param_name, list(args))
|
|
1035
|
+
new_scope.set('Params', list(args)) # Also set 'Params' for OpenFind
|
|
1036
|
+
elif param_name in kwargs:
|
|
891
1037
|
# Named argument takes priority
|
|
892
1038
|
new_scope.set(param_name, kwargs[param_name])
|
|
893
1039
|
elif i < len(args):
|
|
@@ -1097,8 +1243,20 @@ class CSSLRuntime:
|
|
|
1097
1243
|
return None
|
|
1098
1244
|
|
|
1099
1245
|
def _exec_return(self, node: ASTNode) -> Any:
|
|
1100
|
-
"""Execute return statement
|
|
1101
|
-
|
|
1246
|
+
"""Execute return statement.
|
|
1247
|
+
|
|
1248
|
+
Supports multiple return values for shuffled functions:
|
|
1249
|
+
return a, b, c; // Returns tuple (a, b, c)
|
|
1250
|
+
"""
|
|
1251
|
+
if node.value is None:
|
|
1252
|
+
raise CSSLReturn(None)
|
|
1253
|
+
|
|
1254
|
+
# Check if this is a multiple return value
|
|
1255
|
+
if isinstance(node.value, dict) and node.value.get('multiple'):
|
|
1256
|
+
values = [self._evaluate(v) for v in node.value.get('values', [])]
|
|
1257
|
+
raise CSSLReturn(tuple(values))
|
|
1258
|
+
|
|
1259
|
+
value = self._evaluate(node.value)
|
|
1102
1260
|
raise CSSLReturn(value)
|
|
1103
1261
|
|
|
1104
1262
|
def _exec_break(self, node: ASTNode) -> Any:
|
|
@@ -1173,8 +1331,11 @@ class CSSLRuntime:
|
|
|
1173
1331
|
|
|
1174
1332
|
return command_name
|
|
1175
1333
|
|
|
1176
|
-
def _apply_injection_filter(self, source: Any, filter_info
|
|
1177
|
-
"""Apply injection filter to extract specific data from source.
|
|
1334
|
+
def _apply_injection_filter(self, source: Any, filter_info) -> Any:
|
|
1335
|
+
"""Apply injection filter(s) to extract specific data from source.
|
|
1336
|
+
|
|
1337
|
+
Supports both single filter dict and list of filter dicts for chained filters.
|
|
1338
|
+
Example: [dynamic::content=10][dynamic::content=100] applies both filters.
|
|
1178
1339
|
|
|
1179
1340
|
All BruteInjector Helpers:
|
|
1180
1341
|
- string::where=VALUE - Filter strings containing VALUE
|
|
@@ -1191,10 +1352,31 @@ class CSSLRuntime:
|
|
|
1191
1352
|
- combo::blocked - Get blocked items from combo
|
|
1192
1353
|
- dynamic::VarName=VALUE - Filter by dynamic variable value
|
|
1193
1354
|
- sql::data - Return only SQL-compatible data
|
|
1355
|
+
- instance::class - Get classes from object
|
|
1356
|
+
- instance::method - Get methods from object
|
|
1357
|
+
- instance::var - Get variables from object
|
|
1358
|
+
- instance::all - Get all categorized (methods, classes, vars)
|
|
1359
|
+
- instance::"ClassName" - Get specific class by name
|
|
1360
|
+
- name::"Name" - Filter by name (class, dict key, attribute)
|
|
1194
1361
|
"""
|
|
1195
1362
|
if not filter_info:
|
|
1196
1363
|
return source
|
|
1197
1364
|
|
|
1365
|
+
# Handle list of filters (chained filters)
|
|
1366
|
+
if isinstance(filter_info, list):
|
|
1367
|
+
result = source
|
|
1368
|
+
for single_filter in filter_info:
|
|
1369
|
+
result = self._apply_single_filter(result, single_filter)
|
|
1370
|
+
return result
|
|
1371
|
+
|
|
1372
|
+
# Single filter (dict)
|
|
1373
|
+
return self._apply_single_filter(source, filter_info)
|
|
1374
|
+
|
|
1375
|
+
def _apply_single_filter(self, source: Any, filter_info: dict) -> Any:
|
|
1376
|
+
"""Apply a single injection filter to extract specific data from source."""
|
|
1377
|
+
if not filter_info:
|
|
1378
|
+
return source
|
|
1379
|
+
|
|
1198
1380
|
result = source
|
|
1199
1381
|
|
|
1200
1382
|
for filter_key, filter_value in filter_info.items():
|
|
@@ -1202,6 +1384,18 @@ class CSSLRuntime:
|
|
|
1202
1384
|
filter_type, helper = filter_key.split('::', 1)
|
|
1203
1385
|
filter_val = self._evaluate(filter_value) if isinstance(filter_value, ASTNode) else filter_value
|
|
1204
1386
|
|
|
1387
|
+
# === CHECK CUSTOM FILTERS FIRST ===
|
|
1388
|
+
custom_key = f"{filter_type}::{helper}"
|
|
1389
|
+
if custom_key in _custom_filters:
|
|
1390
|
+
result = _custom_filters[custom_key](result, filter_val, self)
|
|
1391
|
+
continue
|
|
1392
|
+
|
|
1393
|
+
# Check for catch-all custom filter (type::*)
|
|
1394
|
+
catchall_key = f"{filter_type}::*"
|
|
1395
|
+
if catchall_key in _custom_filters:
|
|
1396
|
+
result = _custom_filters[catchall_key](result, filter_val, self)
|
|
1397
|
+
continue
|
|
1398
|
+
|
|
1205
1399
|
# === STRING HELPERS ===
|
|
1206
1400
|
if filter_type == 'string':
|
|
1207
1401
|
if helper == 'where':
|
|
@@ -1378,13 +1572,112 @@ class CSSLRuntime:
|
|
|
1378
1572
|
|
|
1379
1573
|
# === DYNAMIC HELPERS ===
|
|
1380
1574
|
elif filter_type == 'dynamic':
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1575
|
+
if helper == 'content':
|
|
1576
|
+
# dynamic::content=VALUE - General content filter for any type
|
|
1577
|
+
# Filters elements where content equals VALUE
|
|
1578
|
+
if isinstance(result, (list, tuple)):
|
|
1579
|
+
# Filter list/tuple elements by content value
|
|
1580
|
+
result = [item for item in result if item == filter_val]
|
|
1581
|
+
elif isinstance(result, dict):
|
|
1582
|
+
# Filter dict by values matching filter_val
|
|
1583
|
+
result = {k: v for k, v in result.items() if v == filter_val}
|
|
1584
|
+
elif result == filter_val:
|
|
1585
|
+
pass # Keep result if it matches
|
|
1586
|
+
else:
|
|
1587
|
+
result = None
|
|
1588
|
+
elif helper == 'not':
|
|
1589
|
+
# dynamic::not=VALUE - Exclude elements equal to VALUE
|
|
1590
|
+
if isinstance(result, (list, tuple)):
|
|
1591
|
+
result = [item for item in result if item != filter_val]
|
|
1592
|
+
elif isinstance(result, dict):
|
|
1593
|
+
result = {k: v for k, v in result.items() if v != filter_val}
|
|
1594
|
+
elif result != filter_val:
|
|
1595
|
+
pass # Keep result if it doesn't match
|
|
1596
|
+
else:
|
|
1597
|
+
result = None
|
|
1598
|
+
elif helper == 'gt':
|
|
1599
|
+
# dynamic::gt=VALUE - Greater than
|
|
1600
|
+
if isinstance(result, (list, tuple)):
|
|
1601
|
+
result = [item for item in result if item > filter_val]
|
|
1602
|
+
elif result > filter_val:
|
|
1603
|
+
pass
|
|
1604
|
+
else:
|
|
1605
|
+
result = None
|
|
1606
|
+
elif helper == 'lt':
|
|
1607
|
+
# dynamic::lt=VALUE - Less than
|
|
1608
|
+
if isinstance(result, (list, tuple)):
|
|
1609
|
+
result = [item for item in result if item < filter_val]
|
|
1610
|
+
elif result < filter_val:
|
|
1611
|
+
pass
|
|
1612
|
+
else:
|
|
1613
|
+
result = None
|
|
1614
|
+
elif helper == 'gte':
|
|
1615
|
+
# dynamic::gte=VALUE - Greater than or equal
|
|
1616
|
+
if isinstance(result, (list, tuple)):
|
|
1617
|
+
result = [item for item in result if item >= filter_val]
|
|
1618
|
+
elif result >= filter_val:
|
|
1619
|
+
pass
|
|
1620
|
+
else:
|
|
1621
|
+
result = None
|
|
1622
|
+
elif helper == 'lte':
|
|
1623
|
+
# dynamic::lte=VALUE - Less than or equal
|
|
1624
|
+
if isinstance(result, (list, tuple)):
|
|
1625
|
+
result = [item for item in result if item <= filter_val]
|
|
1626
|
+
elif result <= filter_val:
|
|
1627
|
+
pass
|
|
1628
|
+
else:
|
|
1629
|
+
result = None
|
|
1630
|
+
elif helper == 'mod':
|
|
1631
|
+
# dynamic::mod=VALUE - Modulo filter (item % VALUE == 0)
|
|
1632
|
+
if isinstance(result, (list, tuple)):
|
|
1633
|
+
result = [item for item in result if isinstance(item, (int, float)) and item % filter_val == 0]
|
|
1634
|
+
elif isinstance(result, (int, float)) and result % filter_val == 0:
|
|
1635
|
+
pass
|
|
1636
|
+
else:
|
|
1637
|
+
result = None
|
|
1638
|
+
elif helper == 'range':
|
|
1639
|
+
# dynamic::range="min:max" - Filter values in range
|
|
1640
|
+
if isinstance(filter_val, str) and ':' in filter_val:
|
|
1641
|
+
parts = filter_val.split(':')
|
|
1642
|
+
min_val = int(parts[0]) if parts[0] else None
|
|
1643
|
+
max_val = int(parts[1]) if parts[1] else None
|
|
1644
|
+
if isinstance(result, (list, tuple)):
|
|
1645
|
+
def in_range(x):
|
|
1646
|
+
if min_val is not None and x < min_val:
|
|
1647
|
+
return False
|
|
1648
|
+
if max_val is not None and x > max_val:
|
|
1649
|
+
return False
|
|
1650
|
+
return True
|
|
1651
|
+
result = [item for item in result if in_range(item)]
|
|
1652
|
+
elif isinstance(result, (int, float)):
|
|
1653
|
+
if min_val is not None and result < min_val:
|
|
1654
|
+
result = None
|
|
1655
|
+
elif max_val is not None and result > max_val:
|
|
1656
|
+
result = None
|
|
1657
|
+
elif helper == 'even':
|
|
1658
|
+
# dynamic::even - Filter even numbers
|
|
1659
|
+
if isinstance(result, (list, tuple)):
|
|
1660
|
+
result = [item for item in result if isinstance(item, int) and item % 2 == 0]
|
|
1661
|
+
elif isinstance(result, int) and result % 2 == 0:
|
|
1662
|
+
pass
|
|
1663
|
+
else:
|
|
1664
|
+
result = None
|
|
1665
|
+
elif helper == 'odd':
|
|
1666
|
+
# dynamic::odd - Filter odd numbers
|
|
1667
|
+
if isinstance(result, (list, tuple)):
|
|
1668
|
+
result = [item for item in result if isinstance(item, int) and item % 2 != 0]
|
|
1669
|
+
elif isinstance(result, int) and result % 2 != 0:
|
|
1670
|
+
pass
|
|
1671
|
+
else:
|
|
1672
|
+
result = None
|
|
1386
1673
|
else:
|
|
1387
|
-
|
|
1674
|
+
# dynamic::VarName=VALUE - Match if variable equals value
|
|
1675
|
+
var_name = helper
|
|
1676
|
+
var_value = self.scope.get(var_name)
|
|
1677
|
+
if var_value == filter_val:
|
|
1678
|
+
pass # Keep result
|
|
1679
|
+
else:
|
|
1680
|
+
result = None
|
|
1388
1681
|
|
|
1389
1682
|
# === SQL HELPERS ===
|
|
1390
1683
|
elif filter_type == 'sql':
|
|
@@ -1395,6 +1688,128 @@ class CSSLRuntime:
|
|
|
1395
1688
|
else:
|
|
1396
1689
|
result = str(result) # Convert to string
|
|
1397
1690
|
|
|
1691
|
+
# === INSTANCE HELPERS ===
|
|
1692
|
+
# Works on CSSLInstance, Python objects, dicts, modules
|
|
1693
|
+
elif filter_type == 'instance':
|
|
1694
|
+
from .cssl_types import CSSLInstance
|
|
1695
|
+
import inspect
|
|
1696
|
+
|
|
1697
|
+
if isinstance(result, CSSLInstance):
|
|
1698
|
+
# Filter CSSL instances
|
|
1699
|
+
if helper in ('class', 'classes'):
|
|
1700
|
+
classes = {}
|
|
1701
|
+
for name, member in result._members.items():
|
|
1702
|
+
if isinstance(member, CSSLInstance):
|
|
1703
|
+
classes[name] = member
|
|
1704
|
+
result = classes if classes else None
|
|
1705
|
+
elif helper in ('method', 'methods'):
|
|
1706
|
+
result = dict(result._class.methods)
|
|
1707
|
+
elif helper in ('var', 'vars', 'variable', 'variables'):
|
|
1708
|
+
vars_dict = {}
|
|
1709
|
+
for name, member in result._members.items():
|
|
1710
|
+
if not isinstance(member, CSSLInstance):
|
|
1711
|
+
vars_dict[name] = member
|
|
1712
|
+
result = vars_dict
|
|
1713
|
+
elif helper in ('all',):
|
|
1714
|
+
result = {
|
|
1715
|
+
'methods': list(result._class.methods.keys()),
|
|
1716
|
+
'classes': [result._class.name] + [m._class.name for m in result._members.values() if isinstance(m, CSSLInstance)],
|
|
1717
|
+
'vars': [n for n, m in result._members.items() if not isinstance(m, CSSLInstance)]
|
|
1718
|
+
}
|
|
1719
|
+
else:
|
|
1720
|
+
# Filter by class name
|
|
1721
|
+
class_name = filter_val if filter_val else helper
|
|
1722
|
+
found = None
|
|
1723
|
+
if result._class.name == class_name:
|
|
1724
|
+
found = result
|
|
1725
|
+
else:
|
|
1726
|
+
for name, member in result._members.items():
|
|
1727
|
+
if isinstance(member, CSSLInstance) and member._class.name == class_name:
|
|
1728
|
+
found = member
|
|
1729
|
+
break
|
|
1730
|
+
result = found
|
|
1731
|
+
elif isinstance(result, dict):
|
|
1732
|
+
# Filter dicts
|
|
1733
|
+
if helper in ('class', 'classes'):
|
|
1734
|
+
result = {k: v for k, v in result.items() if inspect.isclass(v)}
|
|
1735
|
+
elif helper in ('method', 'methods', 'func', 'function', 'functions'):
|
|
1736
|
+
result = {k: v for k, v in result.items() if callable(v)}
|
|
1737
|
+
elif helper in ('var', 'vars', 'variable', 'variables'):
|
|
1738
|
+
result = {k: v for k, v in result.items() if not callable(v) and not inspect.isclass(v)}
|
|
1739
|
+
else:
|
|
1740
|
+
# Get by key
|
|
1741
|
+
key_name = filter_val if filter_val else helper
|
|
1742
|
+
result = result.get(key_name)
|
|
1743
|
+
elif hasattr(result, '__dict__') or hasattr(result, '__class__'):
|
|
1744
|
+
# Filter Python objects/modules
|
|
1745
|
+
if helper in ('class', 'classes'):
|
|
1746
|
+
classes = {}
|
|
1747
|
+
for name in dir(result):
|
|
1748
|
+
if not name.startswith('_'):
|
|
1749
|
+
attr = getattr(result, name, None)
|
|
1750
|
+
if inspect.isclass(attr):
|
|
1751
|
+
classes[name] = attr
|
|
1752
|
+
result = classes if classes else None
|
|
1753
|
+
elif helper in ('method', 'methods', 'func', 'function', 'functions'):
|
|
1754
|
+
methods = {}
|
|
1755
|
+
for name in dir(result):
|
|
1756
|
+
if not name.startswith('_'):
|
|
1757
|
+
attr = getattr(result, name, None)
|
|
1758
|
+
if callable(attr):
|
|
1759
|
+
methods[name] = attr
|
|
1760
|
+
result = methods if methods else None
|
|
1761
|
+
elif helper in ('var', 'vars', 'variable', 'variables'):
|
|
1762
|
+
vars_dict = {}
|
|
1763
|
+
for name in dir(result):
|
|
1764
|
+
if not name.startswith('_'):
|
|
1765
|
+
attr = getattr(result, name, None)
|
|
1766
|
+
if not callable(attr) and not inspect.isclass(attr):
|
|
1767
|
+
vars_dict[name] = attr
|
|
1768
|
+
result = vars_dict if vars_dict else None
|
|
1769
|
+
elif helper in ('all',):
|
|
1770
|
+
all_info = {'methods': [], 'classes': [], 'vars': []}
|
|
1771
|
+
for name in dir(result):
|
|
1772
|
+
if not name.startswith('_'):
|
|
1773
|
+
attr = getattr(result, name, None)
|
|
1774
|
+
if inspect.isclass(attr):
|
|
1775
|
+
all_info['classes'].append(name)
|
|
1776
|
+
elif callable(attr):
|
|
1777
|
+
all_info['methods'].append(name)
|
|
1778
|
+
else:
|
|
1779
|
+
all_info['vars'].append(name)
|
|
1780
|
+
result = all_info
|
|
1781
|
+
else:
|
|
1782
|
+
# Get attribute by name
|
|
1783
|
+
attr_name = filter_val if filter_val else helper
|
|
1784
|
+
result = getattr(result, attr_name, None)
|
|
1785
|
+
|
|
1786
|
+
# === NAME HELPERS ===
|
|
1787
|
+
# General name filter for any object type
|
|
1788
|
+
elif filter_type == 'name':
|
|
1789
|
+
from .cssl_types import CSSLInstance
|
|
1790
|
+
target_name = filter_val if filter_val else helper
|
|
1791
|
+
|
|
1792
|
+
if isinstance(result, CSSLInstance):
|
|
1793
|
+
if result._class.name == target_name:
|
|
1794
|
+
pass # Keep result
|
|
1795
|
+
else:
|
|
1796
|
+
found = None
|
|
1797
|
+
for name, member in result._members.items():
|
|
1798
|
+
if isinstance(member, CSSLInstance) and member._class.name == target_name:
|
|
1799
|
+
found = member
|
|
1800
|
+
break
|
|
1801
|
+
result = found
|
|
1802
|
+
elif isinstance(result, dict):
|
|
1803
|
+
result = result.get(target_name)
|
|
1804
|
+
elif isinstance(result, list):
|
|
1805
|
+
result = [item for item in result if str(item) == target_name or (hasattr(item, 'name') and item.name == target_name)]
|
|
1806
|
+
elif hasattr(result, target_name):
|
|
1807
|
+
result = getattr(result, target_name)
|
|
1808
|
+
elif hasattr(result, '__class__') and result.__class__.__name__ == target_name:
|
|
1809
|
+
pass # Keep result if class name matches
|
|
1810
|
+
else:
|
|
1811
|
+
result = None
|
|
1812
|
+
|
|
1398
1813
|
return result
|
|
1399
1814
|
|
|
1400
1815
|
def _exec_inject(self, node: ASTNode) -> Any:
|
|
@@ -1452,7 +1867,15 @@ class CSSLRuntime:
|
|
|
1452
1867
|
final_value = source
|
|
1453
1868
|
elif mode == 'add':
|
|
1454
1869
|
# Copy & add - preserve target and add source
|
|
1455
|
-
|
|
1870
|
+
from .cssl_types import CSSLInstance
|
|
1871
|
+
|
|
1872
|
+
# Special handling for CSSLInstance - merge classes
|
|
1873
|
+
if isinstance(current_value, CSSLInstance) and isinstance(source, CSSLInstance):
|
|
1874
|
+
# Add the new class instance as a member with class name as key
|
|
1875
|
+
class_name = source._class.name
|
|
1876
|
+
current_value._members[class_name] = source
|
|
1877
|
+
final_value = current_value
|
|
1878
|
+
elif isinstance(current_value, list):
|
|
1456
1879
|
if isinstance(source, list):
|
|
1457
1880
|
final_value = current_value + source
|
|
1458
1881
|
else:
|
|
@@ -1468,9 +1891,14 @@ class CSSLRuntime:
|
|
|
1468
1891
|
elif mode == 'move':
|
|
1469
1892
|
# Move & remove from source
|
|
1470
1893
|
final_value = source
|
|
1471
|
-
# Clear the source
|
|
1472
|
-
if isinstance(source_node, ASTNode)
|
|
1473
|
-
|
|
1894
|
+
# Clear the source - handle all node types
|
|
1895
|
+
if isinstance(source_node, ASTNode):
|
|
1896
|
+
if source_node.type == 'identifier':
|
|
1897
|
+
self.scope.set(source_node.value, None)
|
|
1898
|
+
elif source_node.type == 'module_ref':
|
|
1899
|
+
self._set_module_value(source_node.value, None)
|
|
1900
|
+
elif source_node.type == 'member_access':
|
|
1901
|
+
self._set_member(source_node, None)
|
|
1474
1902
|
else:
|
|
1475
1903
|
final_value = source
|
|
1476
1904
|
|
|
@@ -1542,9 +1970,36 @@ class CSSLRuntime:
|
|
|
1542
1970
|
final_value = [current_value, source]
|
|
1543
1971
|
elif mode == 'move':
|
|
1544
1972
|
final_value = source
|
|
1545
|
-
#
|
|
1546
|
-
if isinstance(source_node, ASTNode)
|
|
1547
|
-
|
|
1973
|
+
# Remove filtered elements from source (not clear entirely)
|
|
1974
|
+
if isinstance(source_node, ASTNode):
|
|
1975
|
+
if filter_info:
|
|
1976
|
+
# Get original source value
|
|
1977
|
+
original_source = self._evaluate(source_node)
|
|
1978
|
+
if isinstance(original_source, list) and isinstance(final_value, list):
|
|
1979
|
+
# Remove filtered items from original list
|
|
1980
|
+
remaining = [item for item in original_source if item not in final_value]
|
|
1981
|
+
if source_node.type == 'identifier':
|
|
1982
|
+
self.scope.set(source_node.value, remaining)
|
|
1983
|
+
elif source_node.type == 'module_ref':
|
|
1984
|
+
self._set_module_value(source_node.value, remaining)
|
|
1985
|
+
elif source_node.type == 'member_access':
|
|
1986
|
+
self._set_member(source_node, remaining)
|
|
1987
|
+
else:
|
|
1988
|
+
# Single value filter - set source to None
|
|
1989
|
+
if source_node.type == 'identifier':
|
|
1990
|
+
self.scope.set(source_node.value, None)
|
|
1991
|
+
elif source_node.type == 'module_ref':
|
|
1992
|
+
self._set_module_value(source_node.value, None)
|
|
1993
|
+
elif source_node.type == 'member_access':
|
|
1994
|
+
self._set_member(source_node, None)
|
|
1995
|
+
else:
|
|
1996
|
+
# No filter - clear entire source
|
|
1997
|
+
if source_node.type == 'identifier':
|
|
1998
|
+
self.scope.set(source_node.value, None)
|
|
1999
|
+
elif source_node.type == 'module_ref':
|
|
2000
|
+
self._set_module_value(source_node.value, None)
|
|
2001
|
+
elif source_node.type == 'member_access':
|
|
2002
|
+
self._set_member(source_node, None)
|
|
1548
2003
|
else:
|
|
1549
2004
|
final_value = source
|
|
1550
2005
|
|
|
@@ -1567,6 +2022,42 @@ class CSSLRuntime:
|
|
|
1567
2022
|
self.global_scope.set(f'${name}', SharedObjectProxy(name, final_value))
|
|
1568
2023
|
elif target.type == 'member_access':
|
|
1569
2024
|
self._set_member(target, final_value)
|
|
2025
|
+
elif target.type == 'typed_declaration':
|
|
2026
|
+
# Handle typed target: source ==> datastruct<dynamic> Output
|
|
2027
|
+
var_name = target.value.get('name')
|
|
2028
|
+
type_name = target.value.get('type_name')
|
|
2029
|
+
element_type = target.value.get('element_type', 'dynamic')
|
|
2030
|
+
|
|
2031
|
+
# Create appropriate container with final_value
|
|
2032
|
+
from .cssl_types import (create_datastruct, create_vector, create_array,
|
|
2033
|
+
create_stack, create_list, create_dictionary, create_map)
|
|
2034
|
+
|
|
2035
|
+
container = None
|
|
2036
|
+
if type_name == 'datastruct':
|
|
2037
|
+
container = create_datastruct(element_type)
|
|
2038
|
+
elif type_name == 'vector':
|
|
2039
|
+
container = create_vector(element_type)
|
|
2040
|
+
elif type_name == 'array':
|
|
2041
|
+
container = create_array(element_type)
|
|
2042
|
+
elif type_name == 'stack':
|
|
2043
|
+
container = create_stack(element_type)
|
|
2044
|
+
elif type_name == 'list':
|
|
2045
|
+
container = create_list(element_type)
|
|
2046
|
+
elif type_name == 'dictionary':
|
|
2047
|
+
container = create_dictionary()
|
|
2048
|
+
elif type_name == 'map':
|
|
2049
|
+
container = create_map()
|
|
2050
|
+
|
|
2051
|
+
if container is not None:
|
|
2052
|
+
# Add final_value to container
|
|
2053
|
+
if isinstance(final_value, (list, tuple)):
|
|
2054
|
+
container.extend(final_value)
|
|
2055
|
+
elif final_value is not None:
|
|
2056
|
+
container.append(final_value)
|
|
2057
|
+
self.scope.set(var_name, container)
|
|
2058
|
+
else:
|
|
2059
|
+
# Unknown type, just set the value directly
|
|
2060
|
+
self.scope.set(var_name, final_value)
|
|
1570
2061
|
|
|
1571
2062
|
return final_value
|
|
1572
2063
|
|
|
@@ -1725,10 +2216,44 @@ class CSSLRuntime:
|
|
|
1725
2216
|
|
|
1726
2217
|
return value
|
|
1727
2218
|
|
|
2219
|
+
def _exec_tuple_assignment(self, node: ASTNode) -> Any:
|
|
2220
|
+
"""Execute tuple unpacking assignment: a, b, c = shuffled_func()
|
|
2221
|
+
|
|
2222
|
+
Used with shuffled functions that return multiple values.
|
|
2223
|
+
"""
|
|
2224
|
+
targets = node.value.get('targets', [])
|
|
2225
|
+
value = self._evaluate(node.value.get('value'))
|
|
2226
|
+
|
|
2227
|
+
# Convert value to list if it's a tuple or iterable
|
|
2228
|
+
if isinstance(value, (list, tuple)):
|
|
2229
|
+
values = list(value)
|
|
2230
|
+
elif hasattr(value, '__iter__') and not isinstance(value, (str, dict)):
|
|
2231
|
+
values = list(value)
|
|
2232
|
+
else:
|
|
2233
|
+
# Single value - assign to first target only
|
|
2234
|
+
values = [value]
|
|
2235
|
+
|
|
2236
|
+
# Assign values to targets
|
|
2237
|
+
for i, target in enumerate(targets):
|
|
2238
|
+
if i < len(values):
|
|
2239
|
+
var_name = target.value if isinstance(target, ASTNode) else target
|
|
2240
|
+
self.scope.set(var_name, values[i])
|
|
2241
|
+
else:
|
|
2242
|
+
# More targets than values - set to None
|
|
2243
|
+
var_name = target.value if isinstance(target, ASTNode) else target
|
|
2244
|
+
self.scope.set(var_name, None)
|
|
2245
|
+
|
|
2246
|
+
# Assignment statements don't produce a visible result
|
|
2247
|
+
return None
|
|
2248
|
+
|
|
1728
2249
|
def _exec_expression(self, node: ASTNode) -> Any:
|
|
1729
2250
|
"""Execute expression statement"""
|
|
1730
2251
|
return self._evaluate(node.value)
|
|
1731
2252
|
|
|
2253
|
+
def _exec_type_instantiation(self, node: ASTNode) -> Any:
|
|
2254
|
+
"""Execute type instantiation as statement (e.g., vector<int>)"""
|
|
2255
|
+
return self._evaluate(node)
|
|
2256
|
+
|
|
1732
2257
|
def _exec_await(self, node: ASTNode) -> Any:
|
|
1733
2258
|
"""Execute await statement - waits for expression to complete"""
|
|
1734
2259
|
# Evaluate the awaited expression
|
|
@@ -1778,9 +2303,12 @@ class CSSLRuntime:
|
|
|
1778
2303
|
|
|
1779
2304
|
if node.type == 'literal':
|
|
1780
2305
|
value = node.value
|
|
1781
|
-
#
|
|
1782
|
-
if isinstance(value, str)
|
|
1783
|
-
|
|
2306
|
+
# String interpolation - replace {var} or <var> with scope values
|
|
2307
|
+
if isinstance(value, str):
|
|
2308
|
+
has_fstring = '{' in value and '}' in value
|
|
2309
|
+
has_legacy = '<' in value and '>' in value
|
|
2310
|
+
if has_fstring or has_legacy:
|
|
2311
|
+
value = self._interpolate_string(value)
|
|
1784
2312
|
return value
|
|
1785
2313
|
|
|
1786
2314
|
# NEW: Type literals (list, dict) - create empty instances
|
|
@@ -1807,12 +2335,13 @@ class CSSLRuntime:
|
|
|
1807
2335
|
return value
|
|
1808
2336
|
|
|
1809
2337
|
if node.type == 'module_ref':
|
|
1810
|
-
#
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
value = self._promoted_globals.get(node.value)
|
|
2338
|
+
# User-defined globals have priority over SDK modules
|
|
2339
|
+
# Check promoted globals first, then global scope, then SDK modules
|
|
2340
|
+
value = self._promoted_globals.get(node.value)
|
|
1814
2341
|
if value is None:
|
|
1815
2342
|
value = self.global_scope.get(node.value)
|
|
2343
|
+
if value is None:
|
|
2344
|
+
value = self.get_module(node.value) # SDK modules as fallback
|
|
1816
2345
|
return value
|
|
1817
2346
|
|
|
1818
2347
|
if node.type == 'self_ref':
|
|
@@ -1890,16 +2419,34 @@ class CSSLRuntime:
|
|
|
1890
2419
|
return self._eval_this_access(node)
|
|
1891
2420
|
|
|
1892
2421
|
if node.type == 'type_instantiation':
|
|
1893
|
-
# Create new instance of a type: stack<string>, vector<int>, etc.
|
|
2422
|
+
# Create new instance of a type: stack<string>, vector<int>, map<K,V>, etc.
|
|
1894
2423
|
type_name = node.value.get('type')
|
|
1895
2424
|
element_type = node.value.get('element_type', 'dynamic')
|
|
2425
|
+
value_type = node.value.get('value_type') # For map<K, V>
|
|
2426
|
+
init_values = node.value.get('init_values') # For inline init: map<K,V>{...}
|
|
2427
|
+
|
|
2428
|
+
# Helper to populate container with init values
|
|
2429
|
+
def _populate_container(container, init_vals):
|
|
2430
|
+
if init_vals and isinstance(init_vals, list):
|
|
2431
|
+
for val_node in init_vals:
|
|
2432
|
+
val = self._evaluate(val_node) if isinstance(val_node, ASTNode) else val_node
|
|
2433
|
+
if hasattr(container, 'push'):
|
|
2434
|
+
container.push(val)
|
|
2435
|
+
elif hasattr(container, 'add'):
|
|
2436
|
+
container.add(val)
|
|
2437
|
+
elif hasattr(container, 'append'):
|
|
2438
|
+
container.append(val)
|
|
2439
|
+
return container
|
|
1896
2440
|
|
|
1897
2441
|
if type_name == 'stack':
|
|
1898
|
-
|
|
2442
|
+
s = Stack(element_type)
|
|
2443
|
+
return _populate_container(s, init_values)
|
|
1899
2444
|
elif type_name == 'vector':
|
|
1900
|
-
|
|
2445
|
+
v = Vector(element_type)
|
|
2446
|
+
return _populate_container(v, init_values)
|
|
1901
2447
|
elif type_name == 'datastruct':
|
|
1902
|
-
|
|
2448
|
+
d = DataStruct(element_type)
|
|
2449
|
+
return _populate_container(d, init_values)
|
|
1903
2450
|
elif type_name == 'shuffled':
|
|
1904
2451
|
return Shuffled(element_type)
|
|
1905
2452
|
elif type_name == 'iterator':
|
|
@@ -1911,11 +2458,22 @@ class CSSLRuntime:
|
|
|
1911
2458
|
elif type_name == 'openquote':
|
|
1912
2459
|
return OpenQuote()
|
|
1913
2460
|
elif type_name == 'array':
|
|
1914
|
-
|
|
2461
|
+
a = Array(element_type)
|
|
2462
|
+
return _populate_container(a, init_values)
|
|
1915
2463
|
elif type_name == 'list':
|
|
1916
|
-
|
|
2464
|
+
l = List(element_type)
|
|
2465
|
+
return _populate_container(l, init_values)
|
|
1917
2466
|
elif type_name in ('dictionary', 'dict'):
|
|
1918
2467
|
return Dictionary(element_type)
|
|
2468
|
+
elif type_name == 'map':
|
|
2469
|
+
# Create Map with key_type and value_type
|
|
2470
|
+
m = Map(element_type, value_type or 'dynamic')
|
|
2471
|
+
# If inline initialization provided, populate the map
|
|
2472
|
+
if init_values:
|
|
2473
|
+
for key, value_node in init_values.items():
|
|
2474
|
+
value = self._evaluate(value_node)
|
|
2475
|
+
m.insert(key, value)
|
|
2476
|
+
return m
|
|
1919
2477
|
else:
|
|
1920
2478
|
return None
|
|
1921
2479
|
|
|
@@ -2691,12 +3249,20 @@ class CSSLRuntime:
|
|
|
2691
3249
|
pass
|
|
2692
3250
|
|
|
2693
3251
|
def _set_module_value(self, path: str, value: Any):
|
|
2694
|
-
"""Set a value on a module path"""
|
|
3252
|
+
"""Set a value on a module path or promoted global"""
|
|
2695
3253
|
parts = path.split('.')
|
|
2696
3254
|
if len(parts) < 2:
|
|
3255
|
+
# Single name (no dots) - set in promoted_globals and global_scope
|
|
3256
|
+
self._promoted_globals[path] = value
|
|
3257
|
+
self.global_scope.set(path, value)
|
|
2697
3258
|
return
|
|
2698
3259
|
|
|
2699
3260
|
obj = self._modules.get(parts[0])
|
|
3261
|
+
# Also check promoted_globals for the base object
|
|
3262
|
+
if obj is None:
|
|
3263
|
+
obj = self._promoted_globals.get(parts[0])
|
|
3264
|
+
if obj is None:
|
|
3265
|
+
obj = self.global_scope.get(parts[0])
|
|
2700
3266
|
if obj is None:
|
|
2701
3267
|
return
|
|
2702
3268
|
|
|
@@ -2714,14 +3280,24 @@ class CSSLRuntime:
|
|
|
2714
3280
|
elif isinstance(obj, dict):
|
|
2715
3281
|
obj[final_attr] = value
|
|
2716
3282
|
|
|
2717
|
-
#
|
|
3283
|
+
# String interpolation (supports both <var> and {var} syntax)
|
|
2718
3284
|
def _interpolate_string(self, string: str) -> str:
|
|
2719
|
-
"""Replace <variable> placeholders with values from scope
|
|
3285
|
+
"""Replace {variable} and <variable> placeholders with values from scope.
|
|
3286
|
+
|
|
3287
|
+
Both syntaxes are supported (variables only, not expressions):
|
|
3288
|
+
"Hello {name}!" -> "Hello John!" (f-string style)
|
|
3289
|
+
"Hello <name>!" -> "Hello John!" (legacy CSSL style)
|
|
3290
|
+
|
|
3291
|
+
Examples:
|
|
3292
|
+
string name = "Alice";
|
|
3293
|
+
int age = 30;
|
|
3294
|
+
printl("Hello {name}, you are {age} years old!");
|
|
3295
|
+
printl("Hello <name>, you are <age> years old!");
|
|
2720
3296
|
|
|
2721
|
-
|
|
3297
|
+
Note: Only simple variable names are supported, not expressions.
|
|
3298
|
+
Use string concatenation for complex expressions.
|
|
2722
3299
|
"""
|
|
2723
3300
|
import re
|
|
2724
|
-
pattern = r'<([A-Za-z_][A-Za-z0-9_]*)>'
|
|
2725
3301
|
|
|
2726
3302
|
def replacer(match):
|
|
2727
3303
|
var_name = match.group(1)
|
|
@@ -2733,10 +3309,23 @@ class CSSLRuntime:
|
|
|
2733
3309
|
# Try modules
|
|
2734
3310
|
if value is None:
|
|
2735
3311
|
value = self._modules.get(var_name)
|
|
3312
|
+
# Try global scope
|
|
3313
|
+
if value is None:
|
|
3314
|
+
value = self.global_scope.get(var_name)
|
|
2736
3315
|
# Return string representation or empty string if None
|
|
2737
3316
|
return str(value) if value is not None else ''
|
|
2738
3317
|
|
|
2739
|
-
|
|
3318
|
+
# Support both {var} and <var> syntax - simple variable names only
|
|
3319
|
+
# Pattern: {identifier} or <identifier>
|
|
3320
|
+
patterns = [
|
|
3321
|
+
r'\{([A-Za-z_][A-Za-z0-9_]*)\}', # {name} f-string style
|
|
3322
|
+
r'<([A-Za-z_][A-Za-z0-9_]*)>', # <name> legacy CSSL style
|
|
3323
|
+
]
|
|
3324
|
+
|
|
3325
|
+
result = string
|
|
3326
|
+
for pattern in patterns:
|
|
3327
|
+
result = re.sub(pattern, replacer, result)
|
|
3328
|
+
return result
|
|
2740
3329
|
|
|
2741
3330
|
# NEW: Promote variable to global scope via global()
|
|
2742
3331
|
def promote_to_global(self, s_ref_name: str):
|