IncludeCPP 3.4.10__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 +131 -3
- includecpp/cli/commands.py +124 -0
- includecpp/core/cssl/CSSL_DOCUMENTATION.md +1482 -0
- includecpp/core/cssl/__init__.py +6 -6
- includecpp/core/cssl/cssl_builtins.py +243 -5
- includecpp/core/cssl/cssl_parser.py +298 -10
- includecpp/core/cssl/cssl_runtime.py +704 -53
- includecpp/core/cssl/cssl_types.py +403 -2
- includecpp/core/cssl_bridge.py +363 -0
- includecpp/generator/parser.cpp +1 -1
- {includecpp-3.4.10.dist-info → includecpp-3.4.21.dist-info}/METADATA +270 -3
- {includecpp-3.4.10.dist-info → includecpp-3.4.21.dist-info}/RECORD +17 -16
- {includecpp-3.4.10.dist-info → includecpp-3.4.21.dist-info}/WHEEL +0 -0
- {includecpp-3.4.10.dist-info → includecpp-3.4.21.dist-info}/entry_points.txt +0 -0
- {includecpp-3.4.10.dist-info → includecpp-3.4.21.dist-info}/licenses/LICENSE +0 -0
- {includecpp-3.4.10.dist-info → includecpp-3.4.21.dist-info}/top_level.txt +0 -0
|
@@ -14,15 +14,49 @@ from .cssl_builtins import CSSLBuiltins
|
|
|
14
14
|
from .cssl_modules import get_module_registry, get_standard_module
|
|
15
15
|
from .cssl_types import (
|
|
16
16
|
Parameter, DataStruct, Shuffled, Iterator, Combo,
|
|
17
|
-
Stack, Vector, DataSpace, OpenQuote
|
|
17
|
+
Stack, Vector, Array, DataSpace, OpenQuote
|
|
18
18
|
)
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
class CSSLRuntimeError(Exception):
|
|
22
|
-
"""Runtime error during CSSL execution"""
|
|
23
|
-
def __init__(self, message: str, line: int = 0):
|
|
22
|
+
"""Runtime error during CSSL execution with detailed context"""
|
|
23
|
+
def __init__(self, message: str, line: int = 0, context: str = None, hint: str = None):
|
|
24
24
|
self.line = line
|
|
25
|
-
|
|
25
|
+
self.context = context
|
|
26
|
+
self.hint = hint
|
|
27
|
+
|
|
28
|
+
# Build detailed error message
|
|
29
|
+
error_parts = []
|
|
30
|
+
|
|
31
|
+
# Main error message
|
|
32
|
+
if line:
|
|
33
|
+
error_parts.append(f"Error at line {line}: {message}")
|
|
34
|
+
else:
|
|
35
|
+
error_parts.append(f"Error: {message}")
|
|
36
|
+
|
|
37
|
+
# Add context if available
|
|
38
|
+
if context:
|
|
39
|
+
error_parts.append(f" Context: {context}")
|
|
40
|
+
|
|
41
|
+
# Add hint if available
|
|
42
|
+
if hint:
|
|
43
|
+
error_parts.append(f" Hint: {hint}")
|
|
44
|
+
|
|
45
|
+
super().__init__("\n".join(error_parts))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# Common error hints for better user experience
|
|
49
|
+
ERROR_HINTS = {
|
|
50
|
+
'undefined_variable': "Did you forget to declare the variable? Use 'string x = ...' or 'int x = ...'",
|
|
51
|
+
'undefined_function': "Check function name spelling. Functions are case-sensitive.",
|
|
52
|
+
'type_mismatch': "Try using explicit type conversion: toInt(), toFloat(), toString()",
|
|
53
|
+
'null_reference': "Variable is null. Check if it was properly initialized.",
|
|
54
|
+
'index_out_of_bounds': "Array index must be >= 0 and < array.length()",
|
|
55
|
+
'division_by_zero': "Cannot divide by zero. Add a check: if (divisor != 0) { ... }",
|
|
56
|
+
'invalid_operation': "This operation is not supported for this type.",
|
|
57
|
+
'missing_semicolon': "Statement might be missing a semicolon (;)",
|
|
58
|
+
'missing_brace': "Check for matching opening and closing braces { }",
|
|
59
|
+
}
|
|
26
60
|
|
|
27
61
|
|
|
28
62
|
class CSSLBreak(Exception):
|
|
@@ -106,6 +140,7 @@ class CSSLRuntime:
|
|
|
106
140
|
self._modules: Dict[str, Any] = {}
|
|
107
141
|
self._global_structs: Dict[str, Any] = {} # Global structs for s@<name> references
|
|
108
142
|
self._function_injections: Dict[str, List[ASTNode]] = {} # NEW: Permanent function injections
|
|
143
|
+
self._function_replaced: Dict[str, bool] = {} # NEW: Track replaced functions (<<==)
|
|
109
144
|
self._promoted_globals: Dict[str, Any] = {} # NEW: Variables promoted via global()
|
|
110
145
|
self._running = False
|
|
111
146
|
self._exit_code = 0
|
|
@@ -301,7 +336,7 @@ class CSSLRuntime:
|
|
|
301
336
|
# Handle typed variable declaration: type<T> varName = value;
|
|
302
337
|
result = self._exec_typed_declaration(child)
|
|
303
338
|
elif child.type in ('assignment', 'expression', 'inject', 'receive', 'flow',
|
|
304
|
-
'if', 'while', 'for', 'foreach', 'switch', 'try'):
|
|
339
|
+
'if', 'while', 'for', 'c_for', 'foreach', 'switch', 'try'):
|
|
305
340
|
result = self._execute_node(child)
|
|
306
341
|
elif child.type == 'call':
|
|
307
342
|
result = self._eval_call(child)
|
|
@@ -614,7 +649,7 @@ class CSSLRuntime:
|
|
|
614
649
|
elif type_name == 'json':
|
|
615
650
|
instance = {} if value_node is None else self._evaluate(value_node)
|
|
616
651
|
elif type_name == 'array':
|
|
617
|
-
instance =
|
|
652
|
+
instance = Array(element_type)
|
|
618
653
|
else:
|
|
619
654
|
# Default: evaluate the value or set to None
|
|
620
655
|
instance = self._evaluate(value_node) if value_node else None
|
|
@@ -649,8 +684,14 @@ class CSSLRuntime:
|
|
|
649
684
|
value = self._evaluate(inner.value.get('value'))
|
|
650
685
|
|
|
651
686
|
# Get variable name from target
|
|
652
|
-
if isinstance(target, ASTNode)
|
|
653
|
-
|
|
687
|
+
if isinstance(target, ASTNode):
|
|
688
|
+
if target.type == 'identifier':
|
|
689
|
+
var_name = target.value
|
|
690
|
+
elif target.type == 'global_ref':
|
|
691
|
+
# r@Name = value
|
|
692
|
+
var_name = target.value
|
|
693
|
+
else:
|
|
694
|
+
var_name = str(target.value) if hasattr(target, 'value') else str(target)
|
|
654
695
|
elif isinstance(target, str):
|
|
655
696
|
var_name = target
|
|
656
697
|
else:
|
|
@@ -744,12 +785,16 @@ class CSSLRuntime:
|
|
|
744
785
|
return None
|
|
745
786
|
|
|
746
787
|
def _exec_for(self, node: ASTNode) -> Any:
|
|
747
|
-
"""Execute for loop"""
|
|
788
|
+
"""Execute Python-style for loop: for (i in range(start, end, step)) { }"""
|
|
748
789
|
var_name = node.value.get('var')
|
|
749
790
|
start = int(self._evaluate(node.value.get('start')))
|
|
750
791
|
end = int(self._evaluate(node.value.get('end')))
|
|
751
792
|
|
|
752
|
-
|
|
793
|
+
# Optional step parameter (default is 1)
|
|
794
|
+
step_node = node.value.get('step')
|
|
795
|
+
step = int(self._evaluate(step_node)) if step_node else 1
|
|
796
|
+
|
|
797
|
+
for i in range(start, end, step):
|
|
753
798
|
self.scope.set(var_name, i)
|
|
754
799
|
try:
|
|
755
800
|
for child in node.children:
|
|
@@ -761,6 +806,73 @@ class CSSLRuntime:
|
|
|
761
806
|
|
|
762
807
|
return None
|
|
763
808
|
|
|
809
|
+
def _exec_c_for(self, node: ASTNode) -> Any:
|
|
810
|
+
"""Execute C-style for loop: for (init; condition; update) { }
|
|
811
|
+
|
|
812
|
+
Supports:
|
|
813
|
+
- for (int i = 0; i < n; i++) { }
|
|
814
|
+
- for (int i = 0; i < n; i = i + 1) { }
|
|
815
|
+
- for (i = 0; i < n; i += 1) { }
|
|
816
|
+
- for (; condition; ) { } (infinite loop with condition)
|
|
817
|
+
"""
|
|
818
|
+
init = node.value.get('init')
|
|
819
|
+
condition = node.value.get('condition')
|
|
820
|
+
update = node.value.get('update')
|
|
821
|
+
|
|
822
|
+
# Execute init statement
|
|
823
|
+
if init:
|
|
824
|
+
var_name = init.value.get('var')
|
|
825
|
+
init_value = self._evaluate(init.value.get('value'))
|
|
826
|
+
self.scope.set(var_name, init_value)
|
|
827
|
+
else:
|
|
828
|
+
var_name = None
|
|
829
|
+
|
|
830
|
+
# Main loop
|
|
831
|
+
while True:
|
|
832
|
+
# Check condition
|
|
833
|
+
if condition:
|
|
834
|
+
cond_result = self._evaluate(condition)
|
|
835
|
+
if not cond_result:
|
|
836
|
+
break
|
|
837
|
+
# If no condition, this would be infinite - we still need a way to break
|
|
838
|
+
|
|
839
|
+
# Execute body
|
|
840
|
+
try:
|
|
841
|
+
for child in node.children:
|
|
842
|
+
self._execute_node(child)
|
|
843
|
+
except CSSLBreak:
|
|
844
|
+
break
|
|
845
|
+
except CSSLContinue:
|
|
846
|
+
pass # Continue to update, then next iteration
|
|
847
|
+
|
|
848
|
+
# Execute update
|
|
849
|
+
if update:
|
|
850
|
+
self._exec_c_for_update(update)
|
|
851
|
+
|
|
852
|
+
return None
|
|
853
|
+
|
|
854
|
+
def _exec_c_for_update(self, update: 'ASTNode') -> None:
|
|
855
|
+
"""Execute the update part of a C-style for loop."""
|
|
856
|
+
var_name = update.value.get('var')
|
|
857
|
+
op = update.value.get('op')
|
|
858
|
+
value_node = update.value.get('value')
|
|
859
|
+
|
|
860
|
+
current = self.scope.get(var_name) or 0
|
|
861
|
+
|
|
862
|
+
if op == 'increment':
|
|
863
|
+
self.scope.set(var_name, current + 1)
|
|
864
|
+
elif op == 'decrement':
|
|
865
|
+
self.scope.set(var_name, current - 1)
|
|
866
|
+
elif op == 'add':
|
|
867
|
+
add_value = self._evaluate(value_node)
|
|
868
|
+
self.scope.set(var_name, current + add_value)
|
|
869
|
+
elif op == 'subtract':
|
|
870
|
+
sub_value = self._evaluate(value_node)
|
|
871
|
+
self.scope.set(var_name, current - sub_value)
|
|
872
|
+
elif op == 'assign':
|
|
873
|
+
new_value = self._evaluate(value_node)
|
|
874
|
+
self.scope.set(var_name, new_value)
|
|
875
|
+
|
|
764
876
|
def _exec_foreach(self, node: ASTNode) -> Any:
|
|
765
877
|
"""Execute foreach loop"""
|
|
766
878
|
var_name = node.value.get('var')
|
|
@@ -885,7 +997,7 @@ class CSSLRuntime:
|
|
|
885
997
|
def _apply_injection_filter(self, source: Any, filter_info: dict) -> Any:
|
|
886
998
|
"""Apply injection filter to extract specific data from source.
|
|
887
999
|
|
|
888
|
-
|
|
1000
|
+
All BruteInjector Helpers:
|
|
889
1001
|
- string::where=VALUE - Filter strings containing VALUE
|
|
890
1002
|
- string::length=LENGTH - Filter strings of specific length
|
|
891
1003
|
- integer::where=VALUE - Filter integers matching VALUE
|
|
@@ -894,9 +1006,12 @@ class CSSLRuntime:
|
|
|
894
1006
|
- array::index=INDEX - Get specific index from array
|
|
895
1007
|
- array::length=LENGTH - Filter arrays of specific length
|
|
896
1008
|
- vector::where=VALUE - Filter vectors containing VALUE
|
|
1009
|
+
- vector::index=INDEX - Get specific index from vector
|
|
1010
|
+
- vector::length=LENGTH - Filter vectors of specific length
|
|
897
1011
|
- combo::filterdb - Get filter database from combo
|
|
898
1012
|
- combo::blocked - Get blocked items from combo
|
|
899
|
-
- dynamic::VarName=VALUE - Filter by dynamic variable
|
|
1013
|
+
- dynamic::VarName=VALUE - Filter by dynamic variable value
|
|
1014
|
+
- sql::data - Return only SQL-compatible data
|
|
900
1015
|
"""
|
|
901
1016
|
if not filter_info:
|
|
902
1017
|
return source
|
|
@@ -908,25 +1023,28 @@ class CSSLRuntime:
|
|
|
908
1023
|
filter_type, helper = filter_key.split('::', 1)
|
|
909
1024
|
filter_val = self._evaluate(filter_value) if isinstance(filter_value, ASTNode) else filter_value
|
|
910
1025
|
|
|
1026
|
+
# === STRING HELPERS ===
|
|
911
1027
|
if filter_type == 'string':
|
|
912
1028
|
if helper == 'where':
|
|
913
|
-
if isinstance(result, str)
|
|
914
|
-
|
|
1029
|
+
if isinstance(result, str):
|
|
1030
|
+
result = result if filter_val in result else None
|
|
915
1031
|
elif isinstance(result, list):
|
|
916
1032
|
result = [item for item in result if isinstance(item, str) and filter_val in item]
|
|
917
|
-
elif helper
|
|
1033
|
+
elif helper in ('length', 'lenght'): # Support common typo
|
|
918
1034
|
if isinstance(result, str):
|
|
919
1035
|
result = result if len(result) == filter_val else None
|
|
920
1036
|
elif isinstance(result, list):
|
|
921
1037
|
result = [item for item in result if isinstance(item, str) and len(item) == filter_val]
|
|
922
1038
|
|
|
1039
|
+
# === INTEGER HELPERS ===
|
|
923
1040
|
elif filter_type == 'integer':
|
|
924
1041
|
if helper == 'where':
|
|
925
|
-
if isinstance(result, int)
|
|
926
|
-
|
|
1042
|
+
if isinstance(result, int):
|
|
1043
|
+
result = result if result == filter_val else None
|
|
927
1044
|
elif isinstance(result, list):
|
|
928
1045
|
result = [item for item in result if isinstance(item, int) and item == filter_val]
|
|
929
1046
|
|
|
1047
|
+
# === JSON HELPERS ===
|
|
930
1048
|
elif filter_type == 'json':
|
|
931
1049
|
if helper == 'key':
|
|
932
1050
|
if isinstance(result, dict):
|
|
@@ -935,29 +1053,68 @@ class CSSLRuntime:
|
|
|
935
1053
|
result = [item.get(filter_val) for item in result if isinstance(item, dict) and filter_val in item]
|
|
936
1054
|
elif helper == 'value':
|
|
937
1055
|
if isinstance(result, dict):
|
|
938
|
-
|
|
1056
|
+
# Find key(s) with matching value
|
|
1057
|
+
matches = [k for k, v in result.items() if v == filter_val]
|
|
1058
|
+
result = matches[0] if len(matches) == 1 else matches
|
|
939
1059
|
elif isinstance(result, list):
|
|
940
1060
|
result = [item for item in result if (isinstance(item, dict) and filter_val in item.values())]
|
|
941
1061
|
|
|
942
|
-
|
|
1062
|
+
# === ARRAY HELPERS ===
|
|
1063
|
+
elif filter_type == 'array':
|
|
943
1064
|
if helper == 'index':
|
|
944
|
-
if isinstance(result, list
|
|
945
|
-
|
|
946
|
-
|
|
1065
|
+
if isinstance(result, (list, tuple)):
|
|
1066
|
+
idx = int(filter_val) if not isinstance(filter_val, int) else filter_val
|
|
1067
|
+
if 0 <= idx < len(result):
|
|
1068
|
+
result = result[idx]
|
|
1069
|
+
else:
|
|
1070
|
+
result = None
|
|
1071
|
+
elif helper in ('length', 'lenght'): # Support common typo
|
|
1072
|
+
if isinstance(result, (list, tuple)):
|
|
1073
|
+
result = result if len(result) == filter_val else []
|
|
1074
|
+
elif helper == 'where':
|
|
947
1075
|
if isinstance(result, list):
|
|
1076
|
+
result = [item for item in result if item == filter_val]
|
|
1077
|
+
|
|
1078
|
+
# === VECTOR HELPERS ===
|
|
1079
|
+
elif filter_type == 'vector':
|
|
1080
|
+
if helper == 'index':
|
|
1081
|
+
if isinstance(result, (list, tuple)):
|
|
1082
|
+
idx = int(filter_val) if not isinstance(filter_val, int) else filter_val
|
|
1083
|
+
if 0 <= idx < len(result):
|
|
1084
|
+
result = result[idx]
|
|
1085
|
+
else:
|
|
1086
|
+
result = None
|
|
1087
|
+
elif helper in ('length', 'lenght'): # Support common typo
|
|
1088
|
+
if isinstance(result, (list, tuple)):
|
|
948
1089
|
result = result if len(result) == filter_val else []
|
|
949
1090
|
elif helper == 'where':
|
|
950
1091
|
if isinstance(result, list):
|
|
951
1092
|
result = [item for item in result if item == filter_val]
|
|
952
1093
|
|
|
1094
|
+
# === COMBO HELPERS ===
|
|
953
1095
|
elif filter_type == 'combo':
|
|
954
1096
|
if helper == 'filterdb':
|
|
955
1097
|
if hasattr(result, '_filterdb'):
|
|
956
1098
|
result = result._filterdb
|
|
1099
|
+
elif hasattr(result, 'filterdb'):
|
|
1100
|
+
result = result.filterdb
|
|
957
1101
|
elif helper == 'blocked':
|
|
958
1102
|
if hasattr(result, '_blocked'):
|
|
959
1103
|
result = result._blocked
|
|
1104
|
+
elif hasattr(result, 'blocked'):
|
|
1105
|
+
result = result.blocked
|
|
1106
|
+
|
|
1107
|
+
# === DYNAMIC HELPERS ===
|
|
1108
|
+
elif filter_type == 'dynamic':
|
|
1109
|
+
# dynamic::VarName=VALUE - Match if variable equals value
|
|
1110
|
+
var_name = helper
|
|
1111
|
+
var_value = self.scope.get(var_name)
|
|
1112
|
+
if var_value == filter_val:
|
|
1113
|
+
pass # Keep result
|
|
1114
|
+
else:
|
|
1115
|
+
result = None
|
|
960
1116
|
|
|
1117
|
+
# === SQL HELPERS ===
|
|
961
1118
|
elif filter_type == 'sql':
|
|
962
1119
|
if helper == 'data':
|
|
963
1120
|
# Return only SQL-compatible data types
|
|
@@ -1037,6 +1194,12 @@ class CSSLRuntime:
|
|
|
1037
1194
|
self.scope.set(target.value, final_value)
|
|
1038
1195
|
elif target.type == 'module_ref':
|
|
1039
1196
|
self._set_module_value(target.value, final_value)
|
|
1197
|
+
elif target.type == 'shared_ref':
|
|
1198
|
+
# $Name <== value - create/update shared object
|
|
1199
|
+
from ..cssl_bridge import _live_objects, SharedObjectProxy
|
|
1200
|
+
name = target.value
|
|
1201
|
+
_live_objects[name] = final_value
|
|
1202
|
+
self.global_scope.set(f'${name}', SharedObjectProxy(name, final_value))
|
|
1040
1203
|
elif target.type == 'member_access':
|
|
1041
1204
|
self._set_member(target, final_value)
|
|
1042
1205
|
elif target.type == 'call':
|
|
@@ -1105,6 +1268,12 @@ class CSSLRuntime:
|
|
|
1105
1268
|
self.scope.set(target.value, final_value)
|
|
1106
1269
|
elif target.type == 'module_ref':
|
|
1107
1270
|
self._set_module_value(target.value, final_value)
|
|
1271
|
+
elif target.type == 'shared_ref':
|
|
1272
|
+
# value ==> $Name - create/update shared object
|
|
1273
|
+
from ..cssl_bridge import _live_objects, SharedObjectProxy
|
|
1274
|
+
name = target.value
|
|
1275
|
+
_live_objects[name] = final_value
|
|
1276
|
+
self.global_scope.set(f'${name}', SharedObjectProxy(name, final_value))
|
|
1108
1277
|
elif target.type == 'member_access':
|
|
1109
1278
|
self._set_member(target, final_value)
|
|
1110
1279
|
|
|
@@ -1114,13 +1283,13 @@ class CSSLRuntime:
|
|
|
1114
1283
|
"""Execute code infusion (<<==, +<<==, -<<==)
|
|
1115
1284
|
|
|
1116
1285
|
Modes:
|
|
1117
|
-
- replace: func <<== { code }
|
|
1118
|
-
- add: func +<<== { code }
|
|
1119
|
-
- remove: func -<<== { code }
|
|
1286
|
+
- replace: func <<== { code } - REPLACES function body (original won't execute)
|
|
1287
|
+
- add: func +<<== { code } - ADDS code to function (both execute)
|
|
1288
|
+
- remove: func -<<== { code } - REMOVES matching code from function
|
|
1120
1289
|
"""
|
|
1121
1290
|
target = node.value.get('target')
|
|
1122
1291
|
code_block = node.value.get('code')
|
|
1123
|
-
mode = node.value.get('mode', '
|
|
1292
|
+
mode = node.value.get('mode', 'replace') # Default is REPLACE for <<==
|
|
1124
1293
|
|
|
1125
1294
|
# Get function name from target
|
|
1126
1295
|
func_name = None
|
|
@@ -1136,17 +1305,21 @@ class CSSLRuntime:
|
|
|
1136
1305
|
return None
|
|
1137
1306
|
|
|
1138
1307
|
if mode == 'add':
|
|
1139
|
-
# Add code to function (
|
|
1308
|
+
# +<<== : Add code to function (both injection + original execute)
|
|
1140
1309
|
self.register_function_injection(func_name, code_block)
|
|
1310
|
+
self._function_replaced[func_name] = False # Don't replace, just add
|
|
1141
1311
|
elif mode == 'replace':
|
|
1142
|
-
# Replace
|
|
1312
|
+
# <<== : Replace function body (only injection executes, original skipped)
|
|
1143
1313
|
self._function_injections[func_name] = [code_block]
|
|
1314
|
+
self._function_replaced[func_name] = True # Mark as replaced
|
|
1144
1315
|
elif mode == 'remove':
|
|
1145
|
-
# Remove matching code
|
|
1316
|
+
# -<<== : Remove matching code from function body
|
|
1317
|
+
# For now, this removes all injections for the function
|
|
1146
1318
|
if func_name in self._function_injections:
|
|
1147
|
-
# For simplicity, clear all injections for this function
|
|
1148
|
-
# A more sophisticated implementation would compare code blocks
|
|
1149
1319
|
self._function_injections[func_name] = []
|
|
1320
|
+
self._function_replaced[func_name] = False
|
|
1321
|
+
# Note: Removing from actual function body would require AST manipulation
|
|
1322
|
+
# which is complex - for now we just clear injections
|
|
1150
1323
|
|
|
1151
1324
|
return None
|
|
1152
1325
|
|
|
@@ -1189,6 +1362,14 @@ class CSSLRuntime:
|
|
|
1189
1362
|
self.scope.set(target.value, source)
|
|
1190
1363
|
elif target.type == 'module_ref':
|
|
1191
1364
|
self._set_module_value(target.value, source)
|
|
1365
|
+
elif target.type == 'shared_ref':
|
|
1366
|
+
# $Name <== value - create/update shared object
|
|
1367
|
+
from ..cssl_bridge import _live_objects, SharedObjectProxy
|
|
1368
|
+
name = target.value
|
|
1369
|
+
_live_objects[name] = source
|
|
1370
|
+
self.global_scope.set(f'${name}', SharedObjectProxy(name, source))
|
|
1371
|
+
elif target.type == 'member_access':
|
|
1372
|
+
self._set_member(target, source)
|
|
1192
1373
|
|
|
1193
1374
|
return source
|
|
1194
1375
|
|
|
@@ -1204,6 +1385,16 @@ class CSSLRuntime:
|
|
|
1204
1385
|
# r@Name = value - store in promoted globals
|
|
1205
1386
|
self._promoted_globals[target.value] = value
|
|
1206
1387
|
self.global_scope.set(target.value, value)
|
|
1388
|
+
elif target.type == 'shared_ref':
|
|
1389
|
+
# $Name = value - create/update shared object
|
|
1390
|
+
from ..cssl_bridge import _live_objects, SharedObjectProxy
|
|
1391
|
+
name = target.value
|
|
1392
|
+
_live_objects[name] = value
|
|
1393
|
+
self.global_scope.set(f'${name}', SharedObjectProxy(name, value))
|
|
1394
|
+
elif target.type == 'module_ref':
|
|
1395
|
+
# @Name = value - store in promoted globals (like global keyword)
|
|
1396
|
+
self._promoted_globals[target.value] = value
|
|
1397
|
+
self.global_scope.set(target.value, value)
|
|
1207
1398
|
elif target.type == 'member_access':
|
|
1208
1399
|
self._set_member(target, value)
|
|
1209
1400
|
elif target.type == 'index_access':
|
|
@@ -1307,6 +1498,45 @@ class CSSLRuntime:
|
|
|
1307
1498
|
value = self.global_scope.get(node.value)
|
|
1308
1499
|
return value
|
|
1309
1500
|
|
|
1501
|
+
if node.type == 'shared_ref':
|
|
1502
|
+
# $<name> shared object reference
|
|
1503
|
+
# Returns the SharedObjectProxy for live access
|
|
1504
|
+
from ..cssl_bridge import _live_objects, SharedObjectProxy
|
|
1505
|
+
name = node.value
|
|
1506
|
+
if name in _live_objects:
|
|
1507
|
+
return SharedObjectProxy(name, _live_objects[name])
|
|
1508
|
+
# Check if stored in runtime's scope as $name
|
|
1509
|
+
scoped_val = self.global_scope.get(f'${name}')
|
|
1510
|
+
if scoped_val is not None:
|
|
1511
|
+
return scoped_val
|
|
1512
|
+
raise CSSLRuntimeError(f"Shared object '${name}' not found. Use share() to share objects.")
|
|
1513
|
+
|
|
1514
|
+
if node.type == 'type_instantiation':
|
|
1515
|
+
# Create new instance of a type: stack<string>, vector<int>, etc.
|
|
1516
|
+
type_name = node.value.get('type')
|
|
1517
|
+
element_type = node.value.get('element_type', 'dynamic')
|
|
1518
|
+
|
|
1519
|
+
if type_name == 'stack':
|
|
1520
|
+
return Stack(element_type)
|
|
1521
|
+
elif type_name == 'vector':
|
|
1522
|
+
return Vector(element_type)
|
|
1523
|
+
elif type_name == 'datastruct':
|
|
1524
|
+
return DataStruct(element_type)
|
|
1525
|
+
elif type_name == 'shuffled':
|
|
1526
|
+
return Shuffled(element_type)
|
|
1527
|
+
elif type_name == 'iterator':
|
|
1528
|
+
return Iterator(element_type)
|
|
1529
|
+
elif type_name == 'combo':
|
|
1530
|
+
return Combo(element_type)
|
|
1531
|
+
elif type_name == 'dataspace':
|
|
1532
|
+
return DataSpace(element_type)
|
|
1533
|
+
elif type_name == 'openquote':
|
|
1534
|
+
return OpenQuote()
|
|
1535
|
+
elif type_name == 'array':
|
|
1536
|
+
return Array(element_type)
|
|
1537
|
+
else:
|
|
1538
|
+
return None
|
|
1539
|
+
|
|
1310
1540
|
if node.type == 'binary':
|
|
1311
1541
|
return self._eval_binary(node)
|
|
1312
1542
|
|
|
@@ -1316,6 +1546,10 @@ class CSSLRuntime:
|
|
|
1316
1546
|
if node.type == 'call':
|
|
1317
1547
|
return self._eval_call(node)
|
|
1318
1548
|
|
|
1549
|
+
if node.type == 'typed_call':
|
|
1550
|
+
# Handle OpenFind<type>(args) style calls
|
|
1551
|
+
return self._eval_typed_call(node)
|
|
1552
|
+
|
|
1319
1553
|
if node.type == 'member_access':
|
|
1320
1554
|
return self._eval_member_access(node)
|
|
1321
1555
|
|
|
@@ -1342,32 +1576,161 @@ class CSSLRuntime:
|
|
|
1342
1576
|
return None
|
|
1343
1577
|
|
|
1344
1578
|
def _eval_binary(self, node: ASTNode) -> Any:
|
|
1345
|
-
"""Evaluate binary operation"""
|
|
1579
|
+
"""Evaluate binary operation with auto-casting support"""
|
|
1346
1580
|
op = node.value.get('op')
|
|
1347
1581
|
left = self._evaluate(node.value.get('left'))
|
|
1348
1582
|
right = self._evaluate(node.value.get('right'))
|
|
1349
1583
|
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1584
|
+
# === AUTO-CAST FOR STRING OPERATIONS ===
|
|
1585
|
+
if op == '+':
|
|
1586
|
+
# String concatenation with auto-cast
|
|
1587
|
+
if isinstance(left, str) or isinstance(right, str):
|
|
1588
|
+
return str(left if left is not None else '') + str(right if right is not None else '')
|
|
1589
|
+
# List concatenation
|
|
1590
|
+
if isinstance(left, list) and isinstance(right, list):
|
|
1591
|
+
result = type(left)(left._element_type) if hasattr(left, '_element_type') else []
|
|
1592
|
+
if hasattr(result, 'extend'):
|
|
1593
|
+
result.extend(left)
|
|
1594
|
+
result.extend(right)
|
|
1595
|
+
else:
|
|
1596
|
+
result = list(left) + list(right)
|
|
1597
|
+
return result
|
|
1598
|
+
# Numeric addition
|
|
1599
|
+
return (left or 0) + (right or 0)
|
|
1365
1600
|
|
|
1366
|
-
if op
|
|
1367
|
-
return
|
|
1601
|
+
if op == '-':
|
|
1602
|
+
return self._to_number(left) - self._to_number(right)
|
|
1603
|
+
|
|
1604
|
+
if op == '*':
|
|
1605
|
+
# String repeat: "abc" * 3 = "abcabcabc"
|
|
1606
|
+
if isinstance(left, str) and isinstance(right, (int, float)):
|
|
1607
|
+
return left * int(right)
|
|
1608
|
+
if isinstance(right, str) and isinstance(left, (int, float)):
|
|
1609
|
+
return right * int(left)
|
|
1610
|
+
return self._to_number(left) * self._to_number(right)
|
|
1611
|
+
|
|
1612
|
+
if op == '/':
|
|
1613
|
+
r = self._to_number(right)
|
|
1614
|
+
return self._to_number(left) / r if r != 0 else 0
|
|
1615
|
+
|
|
1616
|
+
if op == '//':
|
|
1617
|
+
r = self._to_number(right)
|
|
1618
|
+
return self._to_number(left) // r if r != 0 else 0
|
|
1619
|
+
|
|
1620
|
+
if op == '%':
|
|
1621
|
+
r = self._to_number(right)
|
|
1622
|
+
return self._to_number(left) % r if r != 0 else 0
|
|
1623
|
+
|
|
1624
|
+
if op == '**':
|
|
1625
|
+
return self._to_number(left) ** self._to_number(right)
|
|
1626
|
+
|
|
1627
|
+
# === COMPARISON OPERATIONS ===
|
|
1628
|
+
if op == '==':
|
|
1629
|
+
return left == right
|
|
1630
|
+
if op == '!=':
|
|
1631
|
+
return left != right
|
|
1632
|
+
if op == '<':
|
|
1633
|
+
return self._compare(left, right) < 0
|
|
1634
|
+
if op == '>':
|
|
1635
|
+
return self._compare(left, right) > 0
|
|
1636
|
+
if op == '<=':
|
|
1637
|
+
return self._compare(left, right) <= 0
|
|
1638
|
+
if op == '>=':
|
|
1639
|
+
return self._compare(left, right) >= 0
|
|
1640
|
+
|
|
1641
|
+
# === LOGICAL OPERATIONS ===
|
|
1642
|
+
if op == 'and' or op == '&&':
|
|
1643
|
+
return left and right
|
|
1644
|
+
if op == 'or' or op == '||':
|
|
1645
|
+
return left or right
|
|
1646
|
+
|
|
1647
|
+
# === BITWISE OPERATIONS ===
|
|
1648
|
+
if op == '&':
|
|
1649
|
+
return int(left or 0) & int(right or 0)
|
|
1650
|
+
if op == '|':
|
|
1651
|
+
return int(left or 0) | int(right or 0)
|
|
1652
|
+
if op == '^':
|
|
1653
|
+
return int(left or 0) ^ int(right or 0)
|
|
1654
|
+
if op == '<<':
|
|
1655
|
+
return int(left or 0) << int(right or 0)
|
|
1656
|
+
if op == '>>':
|
|
1657
|
+
return int(left or 0) >> int(right or 0)
|
|
1658
|
+
|
|
1659
|
+
# === IN OPERATOR ===
|
|
1660
|
+
if op == 'in':
|
|
1661
|
+
if right is None:
|
|
1662
|
+
return False
|
|
1663
|
+
return left in right
|
|
1368
1664
|
|
|
1369
1665
|
return None
|
|
1370
1666
|
|
|
1667
|
+
def _to_number(self, value: Any) -> Union[int, float]:
|
|
1668
|
+
"""Convert value to number with auto-casting"""
|
|
1669
|
+
if value is None:
|
|
1670
|
+
return 0
|
|
1671
|
+
if isinstance(value, (int, float)):
|
|
1672
|
+
return value
|
|
1673
|
+
if isinstance(value, str):
|
|
1674
|
+
value = value.strip()
|
|
1675
|
+
if not value:
|
|
1676
|
+
return 0
|
|
1677
|
+
try:
|
|
1678
|
+
if '.' in value:
|
|
1679
|
+
return float(value)
|
|
1680
|
+
return int(value)
|
|
1681
|
+
except ValueError:
|
|
1682
|
+
return 0
|
|
1683
|
+
if isinstance(value, bool):
|
|
1684
|
+
return 1 if value else 0
|
|
1685
|
+
if isinstance(value, (list, tuple)):
|
|
1686
|
+
return len(value)
|
|
1687
|
+
return 0
|
|
1688
|
+
|
|
1689
|
+
def _compare(self, left: Any, right: Any) -> int:
|
|
1690
|
+
"""Compare two values with auto-casting, returns -1, 0, or 1"""
|
|
1691
|
+
# Handle None
|
|
1692
|
+
if left is None and right is None:
|
|
1693
|
+
return 0
|
|
1694
|
+
if left is None:
|
|
1695
|
+
return -1
|
|
1696
|
+
if right is None:
|
|
1697
|
+
return 1
|
|
1698
|
+
|
|
1699
|
+
# Both strings - compare as strings
|
|
1700
|
+
if isinstance(left, str) and isinstance(right, str):
|
|
1701
|
+
if left < right:
|
|
1702
|
+
return -1
|
|
1703
|
+
elif left > right:
|
|
1704
|
+
return 1
|
|
1705
|
+
return 0
|
|
1706
|
+
|
|
1707
|
+
# Both numbers - compare as numbers
|
|
1708
|
+
if isinstance(left, (int, float)) and isinstance(right, (int, float)):
|
|
1709
|
+
if left < right:
|
|
1710
|
+
return -1
|
|
1711
|
+
elif left > right:
|
|
1712
|
+
return 1
|
|
1713
|
+
return 0
|
|
1714
|
+
|
|
1715
|
+
# Mixed types - try to convert to numbers
|
|
1716
|
+
try:
|
|
1717
|
+
l = self._to_number(left)
|
|
1718
|
+
r = self._to_number(right)
|
|
1719
|
+
if l < r:
|
|
1720
|
+
return -1
|
|
1721
|
+
elif l > r:
|
|
1722
|
+
return 1
|
|
1723
|
+
return 0
|
|
1724
|
+
except:
|
|
1725
|
+
# Fallback to string comparison
|
|
1726
|
+
l_str = str(left)
|
|
1727
|
+
r_str = str(right)
|
|
1728
|
+
if l_str < r_str:
|
|
1729
|
+
return -1
|
|
1730
|
+
elif l_str > r_str:
|
|
1731
|
+
return 1
|
|
1732
|
+
return 0
|
|
1733
|
+
|
|
1371
1734
|
def _eval_unary(self, node: ASTNode) -> Any:
|
|
1372
1735
|
"""Evaluate unary operation"""
|
|
1373
1736
|
op = node.value.get('op')
|
|
@@ -1386,7 +1749,7 @@ class CSSLRuntime:
|
|
|
1386
1749
|
callee = self._evaluate(callee_node)
|
|
1387
1750
|
args = [self._evaluate(a) for a in node.value.get('args', [])]
|
|
1388
1751
|
|
|
1389
|
-
#
|
|
1752
|
+
# Get function name for injection check
|
|
1390
1753
|
func_name = None
|
|
1391
1754
|
if isinstance(callee_node, ASTNode):
|
|
1392
1755
|
if callee_node.type == 'identifier':
|
|
@@ -1394,17 +1757,81 @@ class CSSLRuntime:
|
|
|
1394
1757
|
elif callee_node.type == 'member_access':
|
|
1395
1758
|
func_name = callee_node.value.get('member')
|
|
1396
1759
|
|
|
1397
|
-
#
|
|
1398
|
-
|
|
1760
|
+
# Check if function has injections
|
|
1761
|
+
has_injections = func_name and func_name in self._function_injections
|
|
1762
|
+
is_replaced = func_name and self._function_replaced.get(func_name, False)
|
|
1763
|
+
|
|
1764
|
+
# Execute injected code first (if any)
|
|
1765
|
+
if has_injections:
|
|
1399
1766
|
self._execute_function_injections(func_name)
|
|
1400
1767
|
|
|
1768
|
+
# If function is REPLACED (<<==), skip original body execution
|
|
1769
|
+
if is_replaced:
|
|
1770
|
+
return None # Injection already ran, don't run original
|
|
1771
|
+
|
|
1772
|
+
# Execute original function
|
|
1401
1773
|
if callable(callee):
|
|
1402
1774
|
return callee(*args)
|
|
1403
1775
|
|
|
1404
1776
|
if isinstance(callee, ASTNode) and callee.type == 'function':
|
|
1405
1777
|
return self._call_function(callee, args)
|
|
1406
1778
|
|
|
1407
|
-
|
|
1779
|
+
callee_name = callee_node.value if isinstance(callee_node, ASTNode) and hasattr(callee_node, 'value') else str(callee_node)
|
|
1780
|
+
raise CSSLRuntimeError(
|
|
1781
|
+
f"Cannot call '{callee_name}' - it is not a function",
|
|
1782
|
+
node.line,
|
|
1783
|
+
context=f"Type: {type(callee).__name__}",
|
|
1784
|
+
hint=ERROR_HINTS['undefined_function']
|
|
1785
|
+
)
|
|
1786
|
+
|
|
1787
|
+
def _eval_typed_call(self, node: ASTNode) -> Any:
|
|
1788
|
+
"""Evaluate typed function call like OpenFind<string>(0)"""
|
|
1789
|
+
name = node.value.get('name')
|
|
1790
|
+
type_param = node.value.get('type_param', 'dynamic')
|
|
1791
|
+
args = [self._evaluate(a) for a in node.value.get('args', [])]
|
|
1792
|
+
|
|
1793
|
+
# Handle OpenFind<type>(index)
|
|
1794
|
+
if name == 'OpenFind':
|
|
1795
|
+
# OpenFind searches for a value of the specified type
|
|
1796
|
+
# from the open parameters in scope
|
|
1797
|
+
open_params = self.scope.get('Params') or []
|
|
1798
|
+
index = args[0] if args else 0
|
|
1799
|
+
|
|
1800
|
+
# Search for value of matching type at or near the index
|
|
1801
|
+
type_map = {
|
|
1802
|
+
'string': str, 'str': str,
|
|
1803
|
+
'int': int, 'integer': int,
|
|
1804
|
+
'float': float, 'double': float,
|
|
1805
|
+
'bool': bool, 'boolean': bool,
|
|
1806
|
+
'list': list, 'array': list,
|
|
1807
|
+
'dict': dict, 'json': dict,
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
target_type = type_map.get(type_param.lower())
|
|
1811
|
+
|
|
1812
|
+
if isinstance(open_params, (list, tuple)):
|
|
1813
|
+
# Find first matching type starting from index
|
|
1814
|
+
for i in range(index, len(open_params)):
|
|
1815
|
+
if target_type is None or isinstance(open_params[i], target_type):
|
|
1816
|
+
return open_params[i]
|
|
1817
|
+
# Also search before index
|
|
1818
|
+
for i in range(0, min(index, len(open_params))):
|
|
1819
|
+
if target_type is None or isinstance(open_params[i], target_type):
|
|
1820
|
+
return open_params[i]
|
|
1821
|
+
|
|
1822
|
+
return None
|
|
1823
|
+
|
|
1824
|
+
# Fallback: call as regular function with type hint
|
|
1825
|
+
func = self.builtins.get_function(name)
|
|
1826
|
+
if func and callable(func):
|
|
1827
|
+
return func(type_param, *args)
|
|
1828
|
+
|
|
1829
|
+
raise CSSLRuntimeError(
|
|
1830
|
+
f"Unknown typed function: {name}<{type_param}>",
|
|
1831
|
+
node.line,
|
|
1832
|
+
context=f"Available typed functions: OpenFind<type>",
|
|
1833
|
+
hint="Typed functions use format: FunctionName<Type>(args)"
|
|
1834
|
+
)
|
|
1408
1835
|
|
|
1409
1836
|
def _eval_member_access(self, node: ASTNode) -> Any:
|
|
1410
1837
|
"""Evaluate member access"""
|
|
@@ -1419,6 +1846,18 @@ class CSSLRuntime:
|
|
|
1419
1846
|
if isinstance(obj, Parameter) and member == 'return':
|
|
1420
1847
|
member = 'return_'
|
|
1421
1848
|
|
|
1849
|
+
# === STRING METHODS ===
|
|
1850
|
+
if isinstance(obj, str):
|
|
1851
|
+
string_methods = self._get_string_method(obj, member)
|
|
1852
|
+
if string_methods is not None:
|
|
1853
|
+
return string_methods
|
|
1854
|
+
|
|
1855
|
+
# === LIST/ARRAY METHODS for plain lists ===
|
|
1856
|
+
if isinstance(obj, list) and not isinstance(obj, (Stack, Vector, Array)):
|
|
1857
|
+
list_methods = self._get_list_method(obj, member)
|
|
1858
|
+
if list_methods is not None:
|
|
1859
|
+
return list_methods
|
|
1860
|
+
|
|
1422
1861
|
if hasattr(obj, member):
|
|
1423
1862
|
return getattr(obj, member)
|
|
1424
1863
|
|
|
@@ -1427,6 +1866,198 @@ class CSSLRuntime:
|
|
|
1427
1866
|
|
|
1428
1867
|
return None
|
|
1429
1868
|
|
|
1869
|
+
def _get_string_method(self, s: str, method: str) -> Any:
|
|
1870
|
+
"""Get string method implementation for CSSL.
|
|
1871
|
+
|
|
1872
|
+
Provides C++/Java/JS style string methods that Python doesn't have.
|
|
1873
|
+
"""
|
|
1874
|
+
# === C++/Java/JS STRING METHODS ===
|
|
1875
|
+
if method == 'contains':
|
|
1876
|
+
return lambda substr: substr in s
|
|
1877
|
+
elif method == 'indexOf':
|
|
1878
|
+
return lambda substr, start=0: s.find(substr, start)
|
|
1879
|
+
elif method == 'lastIndexOf':
|
|
1880
|
+
return lambda substr: s.rfind(substr)
|
|
1881
|
+
elif method == 'charAt':
|
|
1882
|
+
return lambda index: s[index] if 0 <= index < len(s) else ''
|
|
1883
|
+
elif method == 'charCodeAt':
|
|
1884
|
+
return lambda index: ord(s[index]) if 0 <= index < len(s) else -1
|
|
1885
|
+
elif method == 'substring':
|
|
1886
|
+
return lambda start, end=None: s[start:end] if end else s[start:]
|
|
1887
|
+
elif method == 'substr':
|
|
1888
|
+
return lambda start, length=None: s[start:start+length] if length else s[start:]
|
|
1889
|
+
elif method == 'slice':
|
|
1890
|
+
return lambda start, end=None: s[start:end] if end else s[start:]
|
|
1891
|
+
|
|
1892
|
+
# === TRIM METHODS ===
|
|
1893
|
+
elif method == 'trim':
|
|
1894
|
+
return lambda: s.strip()
|
|
1895
|
+
elif method == 'trimStart' or method == 'trimLeft' or method == 'ltrim':
|
|
1896
|
+
return lambda: s.lstrip()
|
|
1897
|
+
elif method == 'trimEnd' or method == 'trimRight' or method == 'rtrim':
|
|
1898
|
+
return lambda: s.rstrip()
|
|
1899
|
+
|
|
1900
|
+
# === CASE METHODS ===
|
|
1901
|
+
elif method in ('toUpperCase', 'toUpper', 'upper'):
|
|
1902
|
+
return lambda: s.upper()
|
|
1903
|
+
elif method in ('toLowerCase', 'toLower', 'lower'):
|
|
1904
|
+
return lambda: s.lower()
|
|
1905
|
+
elif method == 'capitalize':
|
|
1906
|
+
return lambda: s.capitalize()
|
|
1907
|
+
elif method == 'title':
|
|
1908
|
+
return lambda: s.title()
|
|
1909
|
+
elif method == 'swapcase':
|
|
1910
|
+
return lambda: s.swapcase()
|
|
1911
|
+
|
|
1912
|
+
# === REPLACE METHODS ===
|
|
1913
|
+
elif method == 'replaceAll':
|
|
1914
|
+
return lambda old, new: s.replace(old, new)
|
|
1915
|
+
elif method == 'replaceFirst':
|
|
1916
|
+
return lambda old, new: s.replace(old, new, 1)
|
|
1917
|
+
|
|
1918
|
+
# === CHECK METHODS ===
|
|
1919
|
+
elif method == 'isEmpty':
|
|
1920
|
+
return lambda: len(s) == 0
|
|
1921
|
+
elif method == 'isBlank':
|
|
1922
|
+
return lambda: len(s.strip()) == 0
|
|
1923
|
+
elif method == 'isDigit' or method == 'isNumeric':
|
|
1924
|
+
return lambda: s.isdigit()
|
|
1925
|
+
elif method == 'isAlpha':
|
|
1926
|
+
return lambda: s.isalpha()
|
|
1927
|
+
elif method == 'isAlphaNumeric' or method == 'isAlnum':
|
|
1928
|
+
return lambda: s.isalnum()
|
|
1929
|
+
elif method == 'isSpace' or method == 'isWhitespace':
|
|
1930
|
+
return lambda: s.isspace()
|
|
1931
|
+
elif method == 'isUpper':
|
|
1932
|
+
return lambda: s.isupper()
|
|
1933
|
+
elif method == 'isLower':
|
|
1934
|
+
return lambda: s.islower()
|
|
1935
|
+
|
|
1936
|
+
# === STARTS/ENDS WITH ===
|
|
1937
|
+
elif method == 'startsWith' or method == 'startswith':
|
|
1938
|
+
return lambda prefix: s.startswith(prefix)
|
|
1939
|
+
elif method == 'endsWith' or method == 'endswith':
|
|
1940
|
+
return lambda suffix: s.endswith(suffix)
|
|
1941
|
+
|
|
1942
|
+
# === LENGTH/SIZE ===
|
|
1943
|
+
elif method == 'length' or method == 'size':
|
|
1944
|
+
return lambda: len(s)
|
|
1945
|
+
|
|
1946
|
+
# === SPLIT/JOIN ===
|
|
1947
|
+
elif method == 'toArray':
|
|
1948
|
+
return lambda sep=None: list(s.split(sep) if sep else list(s))
|
|
1949
|
+
elif method == 'lines':
|
|
1950
|
+
return lambda: s.splitlines()
|
|
1951
|
+
elif method == 'words':
|
|
1952
|
+
return lambda: s.split()
|
|
1953
|
+
|
|
1954
|
+
# === PADDING ===
|
|
1955
|
+
elif method == 'padStart' or method == 'padLeft' or method == 'lpad':
|
|
1956
|
+
return lambda width, char=' ': s.rjust(width, char[0] if char else ' ')
|
|
1957
|
+
elif method == 'padEnd' or method == 'padRight' or method == 'rpad':
|
|
1958
|
+
return lambda width, char=' ': s.ljust(width, char[0] if char else ' ')
|
|
1959
|
+
elif method == 'center':
|
|
1960
|
+
return lambda width, char=' ': s.center(width, char[0] if char else ' ')
|
|
1961
|
+
elif method == 'zfill':
|
|
1962
|
+
return lambda width: s.zfill(width)
|
|
1963
|
+
|
|
1964
|
+
# === REPEAT ===
|
|
1965
|
+
elif method == 'repeat':
|
|
1966
|
+
return lambda n: s * n
|
|
1967
|
+
|
|
1968
|
+
# === REVERSE ===
|
|
1969
|
+
elif method == 'reverse':
|
|
1970
|
+
return lambda: s[::-1]
|
|
1971
|
+
|
|
1972
|
+
# === FORMAT ===
|
|
1973
|
+
elif method == 'format':
|
|
1974
|
+
return lambda *args, **kwargs: s.format(*args, **kwargs)
|
|
1975
|
+
|
|
1976
|
+
# === ENCODING ===
|
|
1977
|
+
elif method == 'encode':
|
|
1978
|
+
return lambda encoding='utf-8': s.encode(encoding)
|
|
1979
|
+
elif method == 'bytes':
|
|
1980
|
+
return lambda encoding='utf-8': list(s.encode(encoding))
|
|
1981
|
+
|
|
1982
|
+
# === NUMERIC CONVERSION ===
|
|
1983
|
+
elif method == 'toInt' or method == 'toInteger':
|
|
1984
|
+
return lambda base=10: int(s, base) if s.lstrip('-').isdigit() else 0
|
|
1985
|
+
elif method == 'toFloat' or method == 'toDouble':
|
|
1986
|
+
try:
|
|
1987
|
+
return lambda: float(s)
|
|
1988
|
+
except:
|
|
1989
|
+
return lambda: 0.0
|
|
1990
|
+
elif method == 'toBool':
|
|
1991
|
+
return lambda: s.lower() in ('true', '1', 'yes', 'on')
|
|
1992
|
+
|
|
1993
|
+
# === C++ ITERATOR STYLE ===
|
|
1994
|
+
elif method == 'begin':
|
|
1995
|
+
return lambda: 0
|
|
1996
|
+
elif method == 'end':
|
|
1997
|
+
return lambda: len(s)
|
|
1998
|
+
|
|
1999
|
+
# Return None if not a string method
|
|
2000
|
+
return None
|
|
2001
|
+
|
|
2002
|
+
def _get_list_method(self, lst: list, method: str) -> Any:
|
|
2003
|
+
"""Get list method implementation for plain Python lists in CSSL."""
|
|
2004
|
+
if method == 'contains':
|
|
2005
|
+
return lambda item: item in lst
|
|
2006
|
+
elif method == 'indexOf':
|
|
2007
|
+
def index_of(item):
|
|
2008
|
+
try:
|
|
2009
|
+
return lst.index(item)
|
|
2010
|
+
except ValueError:
|
|
2011
|
+
return -1
|
|
2012
|
+
return index_of
|
|
2013
|
+
elif method == 'lastIndexOf':
|
|
2014
|
+
def last_index_of(item):
|
|
2015
|
+
for i in range(len(lst) - 1, -1, -1):
|
|
2016
|
+
if lst[i] == item:
|
|
2017
|
+
return i
|
|
2018
|
+
return -1
|
|
2019
|
+
return last_index_of
|
|
2020
|
+
elif method == 'length' or method == 'size':
|
|
2021
|
+
return lambda: len(lst)
|
|
2022
|
+
elif method == 'isEmpty':
|
|
2023
|
+
return lambda: len(lst) == 0
|
|
2024
|
+
elif method == 'first':
|
|
2025
|
+
return lambda: lst[0] if lst else None
|
|
2026
|
+
elif method == 'last':
|
|
2027
|
+
return lambda: lst[-1] if lst else None
|
|
2028
|
+
elif method == 'at':
|
|
2029
|
+
return lambda i: lst[i] if 0 <= i < len(lst) else None
|
|
2030
|
+
elif method == 'slice':
|
|
2031
|
+
return lambda start, end=None: lst[start:end] if end else lst[start:]
|
|
2032
|
+
elif method == 'join':
|
|
2033
|
+
return lambda sep=',': sep.join(str(x) for x in lst)
|
|
2034
|
+
elif method == 'find':
|
|
2035
|
+
def find_item(val):
|
|
2036
|
+
for item in lst:
|
|
2037
|
+
if item == val:
|
|
2038
|
+
return item
|
|
2039
|
+
return None
|
|
2040
|
+
return find_item
|
|
2041
|
+
elif method == 'push':
|
|
2042
|
+
def push_item(item):
|
|
2043
|
+
lst.append(item)
|
|
2044
|
+
return lst
|
|
2045
|
+
return push_item
|
|
2046
|
+
elif method == 'push_back':
|
|
2047
|
+
def push_back_item(item):
|
|
2048
|
+
lst.append(item)
|
|
2049
|
+
return lst
|
|
2050
|
+
return push_back_item
|
|
2051
|
+
elif method == 'toArray':
|
|
2052
|
+
return lambda: list(lst)
|
|
2053
|
+
# === C++ ITERATOR STYLE ===
|
|
2054
|
+
elif method == 'begin':
|
|
2055
|
+
return lambda: 0
|
|
2056
|
+
elif method == 'end':
|
|
2057
|
+
return lambda: len(lst)
|
|
2058
|
+
|
|
2059
|
+
return None
|
|
2060
|
+
|
|
1430
2061
|
def _eval_index_access(self, node: ASTNode) -> Any:
|
|
1431
2062
|
"""Evaluate index access"""
|
|
1432
2063
|
obj = self._evaluate(node.value.get('object'))
|
|
@@ -1448,10 +2079,30 @@ class CSSLRuntime:
|
|
|
1448
2079
|
if obj is None:
|
|
1449
2080
|
return
|
|
1450
2081
|
|
|
2082
|
+
# Check for SharedObjectProxy - directly access underlying object
|
|
2083
|
+
# This is more robust than relying on the proxy's __setattr__
|
|
2084
|
+
if hasattr(obj, '_direct_object') and hasattr(obj, '_name'):
|
|
2085
|
+
# This is a SharedObjectProxy - get the real object directly
|
|
2086
|
+
real_obj = object.__getattribute__(obj, '_direct_object')
|
|
2087
|
+
if real_obj is None:
|
|
2088
|
+
# Fallback to _live_objects registry
|
|
2089
|
+
name = object.__getattribute__(obj, '_name')
|
|
2090
|
+
from ..cssl_bridge import _live_objects
|
|
2091
|
+
real_obj = _live_objects.get(name)
|
|
2092
|
+
if real_obj is not None:
|
|
2093
|
+
setattr(real_obj, member, value)
|
|
2094
|
+
return
|
|
2095
|
+
|
|
1451
2096
|
if hasattr(obj, member):
|
|
1452
2097
|
setattr(obj, member, value)
|
|
1453
2098
|
elif isinstance(obj, dict):
|
|
1454
2099
|
obj[member] = value
|
|
2100
|
+
else:
|
|
2101
|
+
# Try setattr anyway for objects that support dynamic attributes
|
|
2102
|
+
try:
|
|
2103
|
+
setattr(obj, member, value)
|
|
2104
|
+
except (AttributeError, TypeError):
|
|
2105
|
+
pass
|
|
1455
2106
|
|
|
1456
2107
|
def _set_index(self, node: ASTNode, value: Any):
|
|
1457
2108
|
"""Set index value"""
|