IncludeCPP 3.4.10__py3-none-any.whl → 3.5.0__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 +224 -6
- 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 +780 -55
- includecpp/core/cssl/cssl_types.py +403 -2
- includecpp/core/cssl_bridge.py +369 -1
- includecpp/generator/parser.cpp +1 -1
- includecpp/vscode/__init__.py +1 -0
- includecpp/vscode/cssl/__init__.py +1 -0
- includecpp/vscode/cssl/language-configuration.json +38 -0
- includecpp/vscode/cssl/package.json +30 -0
- includecpp/vscode/cssl/syntaxes/cssl.tmLanguage.json +221 -0
- {includecpp-3.4.10.dist-info → includecpp-3.5.0.dist-info}/METADATA +270 -3
- {includecpp-3.4.10.dist-info → includecpp-3.5.0.dist-info}/RECORD +22 -16
- {includecpp-3.4.10.dist-info → includecpp-3.5.0.dist-info}/WHEEL +0 -0
- {includecpp-3.4.10.dist-info → includecpp-3.5.0.dist-info}/entry_points.txt +0 -0
- {includecpp-3.4.10.dist-info → includecpp-3.5.0.dist-info}/licenses/LICENSE +0 -0
- {includecpp-3.4.10.dist-info → includecpp-3.5.0.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,10 +140,13 @@ 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
|
|
112
147
|
self._output_callback = output_callback # Callback for console output (text, level)
|
|
148
|
+
self._source_lines: List[str] = [] # Store source code lines for error reporting
|
|
149
|
+
self._current_file: str = "<code>" # Current file being executed
|
|
113
150
|
|
|
114
151
|
self._setup_modules()
|
|
115
152
|
self._setup_builtins()
|
|
@@ -195,23 +232,68 @@ class CSSLRuntime:
|
|
|
195
232
|
|
|
196
233
|
return obj
|
|
197
234
|
|
|
235
|
+
def _format_error(self, line: int, message: str, hint: str = None) -> CSSLRuntimeError:
|
|
236
|
+
"""Format a detailed error with source context"""
|
|
237
|
+
error_parts = []
|
|
238
|
+
|
|
239
|
+
# Main error header
|
|
240
|
+
if line and line > 0:
|
|
241
|
+
error_parts.append(f"Error at line {line} in {self._current_file}:")
|
|
242
|
+
else:
|
|
243
|
+
error_parts.append(f"Error in {self._current_file}:")
|
|
244
|
+
|
|
245
|
+
# Extract message without existing line info
|
|
246
|
+
clean_msg = message
|
|
247
|
+
if "at line" in clean_msg.lower():
|
|
248
|
+
# Remove redundant line info from message
|
|
249
|
+
clean_msg = clean_msg.split(":", 1)[-1].strip() if ":" in clean_msg else clean_msg
|
|
250
|
+
|
|
251
|
+
error_parts.append(f" {clean_msg}")
|
|
252
|
+
|
|
253
|
+
# Show source context (3 lines before and after)
|
|
254
|
+
if self._source_lines and line and line > 0:
|
|
255
|
+
error_parts.append("")
|
|
256
|
+
start = max(0, line - 3)
|
|
257
|
+
end = min(len(self._source_lines), line + 2)
|
|
258
|
+
|
|
259
|
+
for i in range(start, end):
|
|
260
|
+
line_num = i + 1
|
|
261
|
+
source_line = self._source_lines[i] if i < len(self._source_lines) else ""
|
|
262
|
+
marker = ">>>" if line_num == line else " "
|
|
263
|
+
error_parts.append(f" {marker} {line_num:4d} | {source_line}")
|
|
264
|
+
|
|
265
|
+
# Add hint
|
|
266
|
+
if hint:
|
|
267
|
+
error_parts.append("")
|
|
268
|
+
error_parts.append(f" Hint: {hint}")
|
|
269
|
+
|
|
270
|
+
return CSSLRuntimeError("\n".join(error_parts), line)
|
|
271
|
+
|
|
272
|
+
def _get_source_line(self, line: int) -> str:
|
|
273
|
+
"""Get source line by number (1-indexed)"""
|
|
274
|
+
if self._source_lines and 0 < line <= len(self._source_lines):
|
|
275
|
+
return self._source_lines[line - 1]
|
|
276
|
+
return ""
|
|
277
|
+
|
|
198
278
|
def execute(self, source: str) -> Any:
|
|
199
279
|
"""Execute CSSL service source code"""
|
|
280
|
+
self._source_lines = source.splitlines()
|
|
200
281
|
try:
|
|
201
282
|
ast = parse_cssl(source)
|
|
202
283
|
return self._execute_node(ast)
|
|
203
284
|
except CSSLSyntaxError as e:
|
|
204
|
-
raise
|
|
285
|
+
raise self._format_error(e.line, str(e))
|
|
205
286
|
except SyntaxError as e:
|
|
206
287
|
raise CSSLRuntimeError(f"Syntax error: {e}")
|
|
207
288
|
|
|
208
289
|
def execute_program(self, source: str) -> Any:
|
|
209
290
|
"""Execute standalone CSSL program (no service wrapper)"""
|
|
291
|
+
self._source_lines = source.splitlines()
|
|
210
292
|
try:
|
|
211
293
|
ast = parse_cssl_program(source)
|
|
212
294
|
return self._exec_program(ast)
|
|
213
295
|
except CSSLSyntaxError as e:
|
|
214
|
-
raise
|
|
296
|
+
raise self._format_error(e.line, str(e))
|
|
215
297
|
except SyntaxError as e:
|
|
216
298
|
raise CSSLRuntimeError(f"Syntax error: {e}")
|
|
217
299
|
|
|
@@ -221,12 +303,16 @@ class CSSLRuntime:
|
|
|
221
303
|
|
|
222
304
|
def execute_file(self, filepath: str) -> Any:
|
|
223
305
|
"""Execute a CSSL service file"""
|
|
306
|
+
import os
|
|
307
|
+
self._current_file = os.path.basename(filepath)
|
|
224
308
|
with open(filepath, 'r', encoding='utf-8') as f:
|
|
225
309
|
source = f.read()
|
|
226
310
|
return self.execute(source)
|
|
227
311
|
|
|
228
312
|
def execute_program_file(self, filepath: str) -> Any:
|
|
229
313
|
"""Execute a standalone CSSL program file"""
|
|
314
|
+
import os
|
|
315
|
+
self._current_file = os.path.basename(filepath)
|
|
230
316
|
with open(filepath, 'r', encoding='utf-8') as f:
|
|
231
317
|
source = f.read()
|
|
232
318
|
return self.execute_program(source)
|
|
@@ -301,7 +387,7 @@ class CSSLRuntime:
|
|
|
301
387
|
# Handle typed variable declaration: type<T> varName = value;
|
|
302
388
|
result = self._exec_typed_declaration(child)
|
|
303
389
|
elif child.type in ('assignment', 'expression', 'inject', 'receive', 'flow',
|
|
304
|
-
'if', 'while', 'for', 'foreach', 'switch', 'try'):
|
|
390
|
+
'if', 'while', 'for', 'c_for', 'foreach', 'switch', 'try'):
|
|
305
391
|
result = self._execute_node(child)
|
|
306
392
|
elif child.type == 'call':
|
|
307
393
|
result = self._eval_call(child)
|
|
@@ -614,7 +700,7 @@ class CSSLRuntime:
|
|
|
614
700
|
elif type_name == 'json':
|
|
615
701
|
instance = {} if value_node is None else self._evaluate(value_node)
|
|
616
702
|
elif type_name == 'array':
|
|
617
|
-
instance =
|
|
703
|
+
instance = Array(element_type)
|
|
618
704
|
else:
|
|
619
705
|
# Default: evaluate the value or set to None
|
|
620
706
|
instance = self._evaluate(value_node) if value_node else None
|
|
@@ -649,8 +735,14 @@ class CSSLRuntime:
|
|
|
649
735
|
value = self._evaluate(inner.value.get('value'))
|
|
650
736
|
|
|
651
737
|
# Get variable name from target
|
|
652
|
-
if isinstance(target, ASTNode)
|
|
653
|
-
|
|
738
|
+
if isinstance(target, ASTNode):
|
|
739
|
+
if target.type == 'identifier':
|
|
740
|
+
var_name = target.value
|
|
741
|
+
elif target.type == 'global_ref':
|
|
742
|
+
# r@Name = value
|
|
743
|
+
var_name = target.value
|
|
744
|
+
else:
|
|
745
|
+
var_name = str(target.value) if hasattr(target, 'value') else str(target)
|
|
654
746
|
elif isinstance(target, str):
|
|
655
747
|
var_name = target
|
|
656
748
|
else:
|
|
@@ -744,12 +836,16 @@ class CSSLRuntime:
|
|
|
744
836
|
return None
|
|
745
837
|
|
|
746
838
|
def _exec_for(self, node: ASTNode) -> Any:
|
|
747
|
-
"""Execute for loop"""
|
|
839
|
+
"""Execute Python-style for loop: for (i in range(start, end, step)) { }"""
|
|
748
840
|
var_name = node.value.get('var')
|
|
749
841
|
start = int(self._evaluate(node.value.get('start')))
|
|
750
842
|
end = int(self._evaluate(node.value.get('end')))
|
|
751
843
|
|
|
752
|
-
|
|
844
|
+
# Optional step parameter (default is 1)
|
|
845
|
+
step_node = node.value.get('step')
|
|
846
|
+
step = int(self._evaluate(step_node)) if step_node else 1
|
|
847
|
+
|
|
848
|
+
for i in range(start, end, step):
|
|
753
849
|
self.scope.set(var_name, i)
|
|
754
850
|
try:
|
|
755
851
|
for child in node.children:
|
|
@@ -761,6 +857,73 @@ class CSSLRuntime:
|
|
|
761
857
|
|
|
762
858
|
return None
|
|
763
859
|
|
|
860
|
+
def _exec_c_for(self, node: ASTNode) -> Any:
|
|
861
|
+
"""Execute C-style for loop: for (init; condition; update) { }
|
|
862
|
+
|
|
863
|
+
Supports:
|
|
864
|
+
- for (int i = 0; i < n; i++) { }
|
|
865
|
+
- for (int i = 0; i < n; i = i + 1) { }
|
|
866
|
+
- for (i = 0; i < n; i += 1) { }
|
|
867
|
+
- for (; condition; ) { } (infinite loop with condition)
|
|
868
|
+
"""
|
|
869
|
+
init = node.value.get('init')
|
|
870
|
+
condition = node.value.get('condition')
|
|
871
|
+
update = node.value.get('update')
|
|
872
|
+
|
|
873
|
+
# Execute init statement
|
|
874
|
+
if init:
|
|
875
|
+
var_name = init.value.get('var')
|
|
876
|
+
init_value = self._evaluate(init.value.get('value'))
|
|
877
|
+
self.scope.set(var_name, init_value)
|
|
878
|
+
else:
|
|
879
|
+
var_name = None
|
|
880
|
+
|
|
881
|
+
# Main loop
|
|
882
|
+
while True:
|
|
883
|
+
# Check condition
|
|
884
|
+
if condition:
|
|
885
|
+
cond_result = self._evaluate(condition)
|
|
886
|
+
if not cond_result:
|
|
887
|
+
break
|
|
888
|
+
# If no condition, this would be infinite - we still need a way to break
|
|
889
|
+
|
|
890
|
+
# Execute body
|
|
891
|
+
try:
|
|
892
|
+
for child in node.children:
|
|
893
|
+
self._execute_node(child)
|
|
894
|
+
except CSSLBreak:
|
|
895
|
+
break
|
|
896
|
+
except CSSLContinue:
|
|
897
|
+
pass # Continue to update, then next iteration
|
|
898
|
+
|
|
899
|
+
# Execute update
|
|
900
|
+
if update:
|
|
901
|
+
self._exec_c_for_update(update)
|
|
902
|
+
|
|
903
|
+
return None
|
|
904
|
+
|
|
905
|
+
def _exec_c_for_update(self, update: 'ASTNode') -> None:
|
|
906
|
+
"""Execute the update part of a C-style for loop."""
|
|
907
|
+
var_name = update.value.get('var')
|
|
908
|
+
op = update.value.get('op')
|
|
909
|
+
value_node = update.value.get('value')
|
|
910
|
+
|
|
911
|
+
current = self.scope.get(var_name) or 0
|
|
912
|
+
|
|
913
|
+
if op == 'increment':
|
|
914
|
+
self.scope.set(var_name, current + 1)
|
|
915
|
+
elif op == 'decrement':
|
|
916
|
+
self.scope.set(var_name, current - 1)
|
|
917
|
+
elif op == 'add':
|
|
918
|
+
add_value = self._evaluate(value_node)
|
|
919
|
+
self.scope.set(var_name, current + add_value)
|
|
920
|
+
elif op == 'subtract':
|
|
921
|
+
sub_value = self._evaluate(value_node)
|
|
922
|
+
self.scope.set(var_name, current - sub_value)
|
|
923
|
+
elif op == 'assign':
|
|
924
|
+
new_value = self._evaluate(value_node)
|
|
925
|
+
self.scope.set(var_name, new_value)
|
|
926
|
+
|
|
764
927
|
def _exec_foreach(self, node: ASTNode) -> Any:
|
|
765
928
|
"""Execute foreach loop"""
|
|
766
929
|
var_name = node.value.get('var')
|
|
@@ -885,7 +1048,7 @@ class CSSLRuntime:
|
|
|
885
1048
|
def _apply_injection_filter(self, source: Any, filter_info: dict) -> Any:
|
|
886
1049
|
"""Apply injection filter to extract specific data from source.
|
|
887
1050
|
|
|
888
|
-
|
|
1051
|
+
All BruteInjector Helpers:
|
|
889
1052
|
- string::where=VALUE - Filter strings containing VALUE
|
|
890
1053
|
- string::length=LENGTH - Filter strings of specific length
|
|
891
1054
|
- integer::where=VALUE - Filter integers matching VALUE
|
|
@@ -894,9 +1057,12 @@ class CSSLRuntime:
|
|
|
894
1057
|
- array::index=INDEX - Get specific index from array
|
|
895
1058
|
- array::length=LENGTH - Filter arrays of specific length
|
|
896
1059
|
- vector::where=VALUE - Filter vectors containing VALUE
|
|
1060
|
+
- vector::index=INDEX - Get specific index from vector
|
|
1061
|
+
- vector::length=LENGTH - Filter vectors of specific length
|
|
897
1062
|
- combo::filterdb - Get filter database from combo
|
|
898
1063
|
- combo::blocked - Get blocked items from combo
|
|
899
|
-
- dynamic::VarName=VALUE - Filter by dynamic variable
|
|
1064
|
+
- dynamic::VarName=VALUE - Filter by dynamic variable value
|
|
1065
|
+
- sql::data - Return only SQL-compatible data
|
|
900
1066
|
"""
|
|
901
1067
|
if not filter_info:
|
|
902
1068
|
return source
|
|
@@ -908,25 +1074,51 @@ class CSSLRuntime:
|
|
|
908
1074
|
filter_type, helper = filter_key.split('::', 1)
|
|
909
1075
|
filter_val = self._evaluate(filter_value) if isinstance(filter_value, ASTNode) else filter_value
|
|
910
1076
|
|
|
1077
|
+
# === STRING HELPERS ===
|
|
911
1078
|
if filter_type == 'string':
|
|
912
1079
|
if helper == 'where':
|
|
913
|
-
|
|
914
|
-
|
|
1080
|
+
# Exact match
|
|
1081
|
+
if isinstance(result, str):
|
|
1082
|
+
result = result if result == filter_val else None
|
|
1083
|
+
elif isinstance(result, list):
|
|
1084
|
+
result = [item for item in result if isinstance(item, str) and item == filter_val]
|
|
1085
|
+
elif helper == 'contains':
|
|
1086
|
+
# Contains substring
|
|
1087
|
+
if isinstance(result, str):
|
|
1088
|
+
result = result if filter_val in result else None
|
|
915
1089
|
elif isinstance(result, list):
|
|
916
1090
|
result = [item for item in result if isinstance(item, str) and filter_val in item]
|
|
917
|
-
elif helper == '
|
|
1091
|
+
elif helper == 'not':
|
|
1092
|
+
# Exclude matching
|
|
1093
|
+
if isinstance(result, str):
|
|
1094
|
+
result = result if result != filter_val else None
|
|
1095
|
+
elif isinstance(result, list):
|
|
1096
|
+
result = [item for item in result if not (isinstance(item, str) and item == filter_val)]
|
|
1097
|
+
elif helper == 'startsWith':
|
|
1098
|
+
if isinstance(result, str):
|
|
1099
|
+
result = result if result.startswith(filter_val) else None
|
|
1100
|
+
elif isinstance(result, list):
|
|
1101
|
+
result = [item for item in result if isinstance(item, str) and item.startswith(filter_val)]
|
|
1102
|
+
elif helper == 'endsWith':
|
|
1103
|
+
if isinstance(result, str):
|
|
1104
|
+
result = result if result.endswith(filter_val) else None
|
|
1105
|
+
elif isinstance(result, list):
|
|
1106
|
+
result = [item for item in result if isinstance(item, str) and item.endswith(filter_val)]
|
|
1107
|
+
elif helper in ('length', 'lenght'): # Support common typo
|
|
918
1108
|
if isinstance(result, str):
|
|
919
1109
|
result = result if len(result) == filter_val else None
|
|
920
1110
|
elif isinstance(result, list):
|
|
921
1111
|
result = [item for item in result if isinstance(item, str) and len(item) == filter_val]
|
|
922
1112
|
|
|
1113
|
+
# === INTEGER HELPERS ===
|
|
923
1114
|
elif filter_type == 'integer':
|
|
924
1115
|
if helper == 'where':
|
|
925
|
-
if isinstance(result, int)
|
|
926
|
-
|
|
1116
|
+
if isinstance(result, int):
|
|
1117
|
+
result = result if result == filter_val else None
|
|
927
1118
|
elif isinstance(result, list):
|
|
928
1119
|
result = [item for item in result if isinstance(item, int) and item == filter_val]
|
|
929
1120
|
|
|
1121
|
+
# === JSON HELPERS ===
|
|
930
1122
|
elif filter_type == 'json':
|
|
931
1123
|
if helper == 'key':
|
|
932
1124
|
if isinstance(result, dict):
|
|
@@ -935,29 +1127,68 @@ class CSSLRuntime:
|
|
|
935
1127
|
result = [item.get(filter_val) for item in result if isinstance(item, dict) and filter_val in item]
|
|
936
1128
|
elif helper == 'value':
|
|
937
1129
|
if isinstance(result, dict):
|
|
938
|
-
|
|
1130
|
+
# Find key(s) with matching value
|
|
1131
|
+
matches = [k for k, v in result.items() if v == filter_val]
|
|
1132
|
+
result = matches[0] if len(matches) == 1 else matches
|
|
939
1133
|
elif isinstance(result, list):
|
|
940
1134
|
result = [item for item in result if (isinstance(item, dict) and filter_val in item.values())]
|
|
941
1135
|
|
|
942
|
-
|
|
1136
|
+
# === ARRAY HELPERS ===
|
|
1137
|
+
elif filter_type == 'array':
|
|
943
1138
|
if helper == 'index':
|
|
944
|
-
if isinstance(result, list
|
|
945
|
-
|
|
946
|
-
|
|
1139
|
+
if isinstance(result, (list, tuple)):
|
|
1140
|
+
idx = int(filter_val) if not isinstance(filter_val, int) else filter_val
|
|
1141
|
+
if 0 <= idx < len(result):
|
|
1142
|
+
result = result[idx]
|
|
1143
|
+
else:
|
|
1144
|
+
result = None
|
|
1145
|
+
elif helper in ('length', 'lenght'): # Support common typo
|
|
1146
|
+
if isinstance(result, (list, tuple)):
|
|
1147
|
+
result = result if len(result) == filter_val else []
|
|
1148
|
+
elif helper == 'where':
|
|
947
1149
|
if isinstance(result, list):
|
|
1150
|
+
result = [item for item in result if item == filter_val]
|
|
1151
|
+
|
|
1152
|
+
# === VECTOR HELPERS ===
|
|
1153
|
+
elif filter_type == 'vector':
|
|
1154
|
+
if helper == 'index':
|
|
1155
|
+
if isinstance(result, (list, tuple)):
|
|
1156
|
+
idx = int(filter_val) if not isinstance(filter_val, int) else filter_val
|
|
1157
|
+
if 0 <= idx < len(result):
|
|
1158
|
+
result = result[idx]
|
|
1159
|
+
else:
|
|
1160
|
+
result = None
|
|
1161
|
+
elif helper in ('length', 'lenght'): # Support common typo
|
|
1162
|
+
if isinstance(result, (list, tuple)):
|
|
948
1163
|
result = result if len(result) == filter_val else []
|
|
949
1164
|
elif helper == 'where':
|
|
950
1165
|
if isinstance(result, list):
|
|
951
1166
|
result = [item for item in result if item == filter_val]
|
|
952
1167
|
|
|
1168
|
+
# === COMBO HELPERS ===
|
|
953
1169
|
elif filter_type == 'combo':
|
|
954
1170
|
if helper == 'filterdb':
|
|
955
1171
|
if hasattr(result, '_filterdb'):
|
|
956
1172
|
result = result._filterdb
|
|
1173
|
+
elif hasattr(result, 'filterdb'):
|
|
1174
|
+
result = result.filterdb
|
|
957
1175
|
elif helper == 'blocked':
|
|
958
1176
|
if hasattr(result, '_blocked'):
|
|
959
1177
|
result = result._blocked
|
|
1178
|
+
elif hasattr(result, 'blocked'):
|
|
1179
|
+
result = result.blocked
|
|
1180
|
+
|
|
1181
|
+
# === DYNAMIC HELPERS ===
|
|
1182
|
+
elif filter_type == 'dynamic':
|
|
1183
|
+
# dynamic::VarName=VALUE - Match if variable equals value
|
|
1184
|
+
var_name = helper
|
|
1185
|
+
var_value = self.scope.get(var_name)
|
|
1186
|
+
if var_value == filter_val:
|
|
1187
|
+
pass # Keep result
|
|
1188
|
+
else:
|
|
1189
|
+
result = None
|
|
960
1190
|
|
|
1191
|
+
# === SQL HELPERS ===
|
|
961
1192
|
elif filter_type == 'sql':
|
|
962
1193
|
if helper == 'data':
|
|
963
1194
|
# Return only SQL-compatible data types
|
|
@@ -1037,6 +1268,12 @@ class CSSLRuntime:
|
|
|
1037
1268
|
self.scope.set(target.value, final_value)
|
|
1038
1269
|
elif target.type == 'module_ref':
|
|
1039
1270
|
self._set_module_value(target.value, final_value)
|
|
1271
|
+
elif target.type == 'shared_ref':
|
|
1272
|
+
# $Name <== value - 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))
|
|
1040
1277
|
elif target.type == 'member_access':
|
|
1041
1278
|
self._set_member(target, final_value)
|
|
1042
1279
|
elif target.type == 'call':
|
|
@@ -1105,6 +1342,12 @@ class CSSLRuntime:
|
|
|
1105
1342
|
self.scope.set(target.value, final_value)
|
|
1106
1343
|
elif target.type == 'module_ref':
|
|
1107
1344
|
self._set_module_value(target.value, final_value)
|
|
1345
|
+
elif target.type == 'shared_ref':
|
|
1346
|
+
# value ==> $Name - create/update shared object
|
|
1347
|
+
from ..cssl_bridge import _live_objects, SharedObjectProxy
|
|
1348
|
+
name = target.value
|
|
1349
|
+
_live_objects[name] = final_value
|
|
1350
|
+
self.global_scope.set(f'${name}', SharedObjectProxy(name, final_value))
|
|
1108
1351
|
elif target.type == 'member_access':
|
|
1109
1352
|
self._set_member(target, final_value)
|
|
1110
1353
|
|
|
@@ -1114,13 +1357,13 @@ class CSSLRuntime:
|
|
|
1114
1357
|
"""Execute code infusion (<<==, +<<==, -<<==)
|
|
1115
1358
|
|
|
1116
1359
|
Modes:
|
|
1117
|
-
- replace: func <<== { code }
|
|
1118
|
-
- add: func +<<== { code }
|
|
1119
|
-
- remove: func -<<== { code }
|
|
1360
|
+
- replace: func <<== { code } - REPLACES function body (original won't execute)
|
|
1361
|
+
- add: func +<<== { code } - ADDS code to function (both execute)
|
|
1362
|
+
- remove: func -<<== { code } - REMOVES matching code from function
|
|
1120
1363
|
"""
|
|
1121
1364
|
target = node.value.get('target')
|
|
1122
1365
|
code_block = node.value.get('code')
|
|
1123
|
-
mode = node.value.get('mode', '
|
|
1366
|
+
mode = node.value.get('mode', 'replace') # Default is REPLACE for <<==
|
|
1124
1367
|
|
|
1125
1368
|
# Get function name from target
|
|
1126
1369
|
func_name = None
|
|
@@ -1136,17 +1379,21 @@ class CSSLRuntime:
|
|
|
1136
1379
|
return None
|
|
1137
1380
|
|
|
1138
1381
|
if mode == 'add':
|
|
1139
|
-
# Add code to function (
|
|
1382
|
+
# +<<== : Add code to function (both injection + original execute)
|
|
1140
1383
|
self.register_function_injection(func_name, code_block)
|
|
1384
|
+
self._function_replaced[func_name] = False # Don't replace, just add
|
|
1141
1385
|
elif mode == 'replace':
|
|
1142
|
-
# Replace
|
|
1386
|
+
# <<== : Replace function body (only injection executes, original skipped)
|
|
1143
1387
|
self._function_injections[func_name] = [code_block]
|
|
1388
|
+
self._function_replaced[func_name] = True # Mark as replaced
|
|
1144
1389
|
elif mode == 'remove':
|
|
1145
|
-
# Remove matching code
|
|
1390
|
+
# -<<== : Remove matching code from function body
|
|
1391
|
+
# For now, this removes all injections for the function
|
|
1146
1392
|
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
1393
|
self._function_injections[func_name] = []
|
|
1394
|
+
self._function_replaced[func_name] = False
|
|
1395
|
+
# Note: Removing from actual function body would require AST manipulation
|
|
1396
|
+
# which is complex - for now we just clear injections
|
|
1150
1397
|
|
|
1151
1398
|
return None
|
|
1152
1399
|
|
|
@@ -1189,6 +1436,14 @@ class CSSLRuntime:
|
|
|
1189
1436
|
self.scope.set(target.value, source)
|
|
1190
1437
|
elif target.type == 'module_ref':
|
|
1191
1438
|
self._set_module_value(target.value, source)
|
|
1439
|
+
elif target.type == 'shared_ref':
|
|
1440
|
+
# $Name <== value - create/update shared object
|
|
1441
|
+
from ..cssl_bridge import _live_objects, SharedObjectProxy
|
|
1442
|
+
name = target.value
|
|
1443
|
+
_live_objects[name] = source
|
|
1444
|
+
self.global_scope.set(f'${name}', SharedObjectProxy(name, source))
|
|
1445
|
+
elif target.type == 'member_access':
|
|
1446
|
+
self._set_member(target, source)
|
|
1192
1447
|
|
|
1193
1448
|
return source
|
|
1194
1449
|
|
|
@@ -1204,6 +1459,16 @@ class CSSLRuntime:
|
|
|
1204
1459
|
# r@Name = value - store in promoted globals
|
|
1205
1460
|
self._promoted_globals[target.value] = value
|
|
1206
1461
|
self.global_scope.set(target.value, value)
|
|
1462
|
+
elif target.type == 'shared_ref':
|
|
1463
|
+
# $Name = value - create/update shared object
|
|
1464
|
+
from ..cssl_bridge import _live_objects, SharedObjectProxy
|
|
1465
|
+
name = target.value
|
|
1466
|
+
_live_objects[name] = value
|
|
1467
|
+
self.global_scope.set(f'${name}', SharedObjectProxy(name, value))
|
|
1468
|
+
elif target.type == 'module_ref':
|
|
1469
|
+
# @Name = value - store in promoted globals (like global keyword)
|
|
1470
|
+
self._promoted_globals[target.value] = value
|
|
1471
|
+
self.global_scope.set(target.value, value)
|
|
1207
1472
|
elif target.type == 'member_access':
|
|
1208
1473
|
self._set_member(target, value)
|
|
1209
1474
|
elif target.type == 'index_access':
|
|
@@ -1307,6 +1572,45 @@ class CSSLRuntime:
|
|
|
1307
1572
|
value = self.global_scope.get(node.value)
|
|
1308
1573
|
return value
|
|
1309
1574
|
|
|
1575
|
+
if node.type == 'shared_ref':
|
|
1576
|
+
# $<name> shared object reference
|
|
1577
|
+
# Returns the SharedObjectProxy for live access
|
|
1578
|
+
from ..cssl_bridge import _live_objects, SharedObjectProxy
|
|
1579
|
+
name = node.value
|
|
1580
|
+
if name in _live_objects:
|
|
1581
|
+
return SharedObjectProxy(name, _live_objects[name])
|
|
1582
|
+
# Check if stored in runtime's scope as $name
|
|
1583
|
+
scoped_val = self.global_scope.get(f'${name}')
|
|
1584
|
+
if scoped_val is not None:
|
|
1585
|
+
return scoped_val
|
|
1586
|
+
raise CSSLRuntimeError(f"Shared object '${name}' not found. Use share() to share objects.")
|
|
1587
|
+
|
|
1588
|
+
if node.type == 'type_instantiation':
|
|
1589
|
+
# Create new instance of a type: stack<string>, vector<int>, etc.
|
|
1590
|
+
type_name = node.value.get('type')
|
|
1591
|
+
element_type = node.value.get('element_type', 'dynamic')
|
|
1592
|
+
|
|
1593
|
+
if type_name == 'stack':
|
|
1594
|
+
return Stack(element_type)
|
|
1595
|
+
elif type_name == 'vector':
|
|
1596
|
+
return Vector(element_type)
|
|
1597
|
+
elif type_name == 'datastruct':
|
|
1598
|
+
return DataStruct(element_type)
|
|
1599
|
+
elif type_name == 'shuffled':
|
|
1600
|
+
return Shuffled(element_type)
|
|
1601
|
+
elif type_name == 'iterator':
|
|
1602
|
+
return Iterator(element_type)
|
|
1603
|
+
elif type_name == 'combo':
|
|
1604
|
+
return Combo(element_type)
|
|
1605
|
+
elif type_name == 'dataspace':
|
|
1606
|
+
return DataSpace(element_type)
|
|
1607
|
+
elif type_name == 'openquote':
|
|
1608
|
+
return OpenQuote()
|
|
1609
|
+
elif type_name == 'array':
|
|
1610
|
+
return Array(element_type)
|
|
1611
|
+
else:
|
|
1612
|
+
return None
|
|
1613
|
+
|
|
1310
1614
|
if node.type == 'binary':
|
|
1311
1615
|
return self._eval_binary(node)
|
|
1312
1616
|
|
|
@@ -1316,6 +1620,10 @@ class CSSLRuntime:
|
|
|
1316
1620
|
if node.type == 'call':
|
|
1317
1621
|
return self._eval_call(node)
|
|
1318
1622
|
|
|
1623
|
+
if node.type == 'typed_call':
|
|
1624
|
+
# Handle OpenFind<type>(args) style calls
|
|
1625
|
+
return self._eval_typed_call(node)
|
|
1626
|
+
|
|
1319
1627
|
if node.type == 'member_access':
|
|
1320
1628
|
return self._eval_member_access(node)
|
|
1321
1629
|
|
|
@@ -1342,32 +1650,161 @@ class CSSLRuntime:
|
|
|
1342
1650
|
return None
|
|
1343
1651
|
|
|
1344
1652
|
def _eval_binary(self, node: ASTNode) -> Any:
|
|
1345
|
-
"""Evaluate binary operation"""
|
|
1653
|
+
"""Evaluate binary operation with auto-casting support"""
|
|
1346
1654
|
op = node.value.get('op')
|
|
1347
1655
|
left = self._evaluate(node.value.get('left'))
|
|
1348
1656
|
right = self._evaluate(node.value.get('right'))
|
|
1349
1657
|
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1658
|
+
# === AUTO-CAST FOR STRING OPERATIONS ===
|
|
1659
|
+
if op == '+':
|
|
1660
|
+
# String concatenation with auto-cast
|
|
1661
|
+
if isinstance(left, str) or isinstance(right, str):
|
|
1662
|
+
return str(left if left is not None else '') + str(right if right is not None else '')
|
|
1663
|
+
# List concatenation
|
|
1664
|
+
if isinstance(left, list) and isinstance(right, list):
|
|
1665
|
+
result = type(left)(left._element_type) if hasattr(left, '_element_type') else []
|
|
1666
|
+
if hasattr(result, 'extend'):
|
|
1667
|
+
result.extend(left)
|
|
1668
|
+
result.extend(right)
|
|
1669
|
+
else:
|
|
1670
|
+
result = list(left) + list(right)
|
|
1671
|
+
return result
|
|
1672
|
+
# Numeric addition
|
|
1673
|
+
return (left or 0) + (right or 0)
|
|
1365
1674
|
|
|
1366
|
-
if op
|
|
1367
|
-
return
|
|
1675
|
+
if op == '-':
|
|
1676
|
+
return self._to_number(left) - self._to_number(right)
|
|
1677
|
+
|
|
1678
|
+
if op == '*':
|
|
1679
|
+
# String repeat: "abc" * 3 = "abcabcabc"
|
|
1680
|
+
if isinstance(left, str) and isinstance(right, (int, float)):
|
|
1681
|
+
return left * int(right)
|
|
1682
|
+
if isinstance(right, str) and isinstance(left, (int, float)):
|
|
1683
|
+
return right * int(left)
|
|
1684
|
+
return self._to_number(left) * self._to_number(right)
|
|
1685
|
+
|
|
1686
|
+
if op == '/':
|
|
1687
|
+
r = self._to_number(right)
|
|
1688
|
+
return self._to_number(left) / r if r != 0 else 0
|
|
1689
|
+
|
|
1690
|
+
if op == '//':
|
|
1691
|
+
r = self._to_number(right)
|
|
1692
|
+
return self._to_number(left) // r if r != 0 else 0
|
|
1693
|
+
|
|
1694
|
+
if op == '%':
|
|
1695
|
+
r = self._to_number(right)
|
|
1696
|
+
return self._to_number(left) % r if r != 0 else 0
|
|
1697
|
+
|
|
1698
|
+
if op == '**':
|
|
1699
|
+
return self._to_number(left) ** self._to_number(right)
|
|
1700
|
+
|
|
1701
|
+
# === COMPARISON OPERATIONS ===
|
|
1702
|
+
if op == '==':
|
|
1703
|
+
return left == right
|
|
1704
|
+
if op == '!=':
|
|
1705
|
+
return left != right
|
|
1706
|
+
if op == '<':
|
|
1707
|
+
return self._compare(left, right) < 0
|
|
1708
|
+
if op == '>':
|
|
1709
|
+
return self._compare(left, right) > 0
|
|
1710
|
+
if op == '<=':
|
|
1711
|
+
return self._compare(left, right) <= 0
|
|
1712
|
+
if op == '>=':
|
|
1713
|
+
return self._compare(left, right) >= 0
|
|
1714
|
+
|
|
1715
|
+
# === LOGICAL OPERATIONS ===
|
|
1716
|
+
if op == 'and' or op == '&&':
|
|
1717
|
+
return left and right
|
|
1718
|
+
if op == 'or' or op == '||':
|
|
1719
|
+
return left or right
|
|
1720
|
+
|
|
1721
|
+
# === BITWISE OPERATIONS ===
|
|
1722
|
+
if op == '&':
|
|
1723
|
+
return int(left or 0) & int(right or 0)
|
|
1724
|
+
if op == '|':
|
|
1725
|
+
return int(left or 0) | int(right or 0)
|
|
1726
|
+
if op == '^':
|
|
1727
|
+
return int(left or 0) ^ int(right or 0)
|
|
1728
|
+
if op == '<<':
|
|
1729
|
+
return int(left or 0) << int(right or 0)
|
|
1730
|
+
if op == '>>':
|
|
1731
|
+
return int(left or 0) >> int(right or 0)
|
|
1732
|
+
|
|
1733
|
+
# === IN OPERATOR ===
|
|
1734
|
+
if op == 'in':
|
|
1735
|
+
if right is None:
|
|
1736
|
+
return False
|
|
1737
|
+
return left in right
|
|
1368
1738
|
|
|
1369
1739
|
return None
|
|
1370
1740
|
|
|
1741
|
+
def _to_number(self, value: Any) -> Union[int, float]:
|
|
1742
|
+
"""Convert value to number with auto-casting"""
|
|
1743
|
+
if value is None:
|
|
1744
|
+
return 0
|
|
1745
|
+
if isinstance(value, (int, float)):
|
|
1746
|
+
return value
|
|
1747
|
+
if isinstance(value, str):
|
|
1748
|
+
value = value.strip()
|
|
1749
|
+
if not value:
|
|
1750
|
+
return 0
|
|
1751
|
+
try:
|
|
1752
|
+
if '.' in value:
|
|
1753
|
+
return float(value)
|
|
1754
|
+
return int(value)
|
|
1755
|
+
except ValueError:
|
|
1756
|
+
return 0
|
|
1757
|
+
if isinstance(value, bool):
|
|
1758
|
+
return 1 if value else 0
|
|
1759
|
+
if isinstance(value, (list, tuple)):
|
|
1760
|
+
return len(value)
|
|
1761
|
+
return 0
|
|
1762
|
+
|
|
1763
|
+
def _compare(self, left: Any, right: Any) -> int:
|
|
1764
|
+
"""Compare two values with auto-casting, returns -1, 0, or 1"""
|
|
1765
|
+
# Handle None
|
|
1766
|
+
if left is None and right is None:
|
|
1767
|
+
return 0
|
|
1768
|
+
if left is None:
|
|
1769
|
+
return -1
|
|
1770
|
+
if right is None:
|
|
1771
|
+
return 1
|
|
1772
|
+
|
|
1773
|
+
# Both strings - compare as strings
|
|
1774
|
+
if isinstance(left, str) and isinstance(right, str):
|
|
1775
|
+
if left < right:
|
|
1776
|
+
return -1
|
|
1777
|
+
elif left > right:
|
|
1778
|
+
return 1
|
|
1779
|
+
return 0
|
|
1780
|
+
|
|
1781
|
+
# Both numbers - compare as numbers
|
|
1782
|
+
if isinstance(left, (int, float)) and isinstance(right, (int, float)):
|
|
1783
|
+
if left < right:
|
|
1784
|
+
return -1
|
|
1785
|
+
elif left > right:
|
|
1786
|
+
return 1
|
|
1787
|
+
return 0
|
|
1788
|
+
|
|
1789
|
+
# Mixed types - try to convert to numbers
|
|
1790
|
+
try:
|
|
1791
|
+
l = self._to_number(left)
|
|
1792
|
+
r = self._to_number(right)
|
|
1793
|
+
if l < r:
|
|
1794
|
+
return -1
|
|
1795
|
+
elif l > r:
|
|
1796
|
+
return 1
|
|
1797
|
+
return 0
|
|
1798
|
+
except:
|
|
1799
|
+
# Fallback to string comparison
|
|
1800
|
+
l_str = str(left)
|
|
1801
|
+
r_str = str(right)
|
|
1802
|
+
if l_str < r_str:
|
|
1803
|
+
return -1
|
|
1804
|
+
elif l_str > r_str:
|
|
1805
|
+
return 1
|
|
1806
|
+
return 0
|
|
1807
|
+
|
|
1371
1808
|
def _eval_unary(self, node: ASTNode) -> Any:
|
|
1372
1809
|
"""Evaluate unary operation"""
|
|
1373
1810
|
op = node.value.get('op')
|
|
@@ -1386,7 +1823,7 @@ class CSSLRuntime:
|
|
|
1386
1823
|
callee = self._evaluate(callee_node)
|
|
1387
1824
|
args = [self._evaluate(a) for a in node.value.get('args', [])]
|
|
1388
1825
|
|
|
1389
|
-
#
|
|
1826
|
+
# Get function name for injection check
|
|
1390
1827
|
func_name = None
|
|
1391
1828
|
if isinstance(callee_node, ASTNode):
|
|
1392
1829
|
if callee_node.type == 'identifier':
|
|
@@ -1394,17 +1831,81 @@ class CSSLRuntime:
|
|
|
1394
1831
|
elif callee_node.type == 'member_access':
|
|
1395
1832
|
func_name = callee_node.value.get('member')
|
|
1396
1833
|
|
|
1397
|
-
#
|
|
1398
|
-
|
|
1834
|
+
# Check if function has injections
|
|
1835
|
+
has_injections = func_name and func_name in self._function_injections
|
|
1836
|
+
is_replaced = func_name and self._function_replaced.get(func_name, False)
|
|
1837
|
+
|
|
1838
|
+
# Execute injected code first (if any)
|
|
1839
|
+
if has_injections:
|
|
1399
1840
|
self._execute_function_injections(func_name)
|
|
1400
1841
|
|
|
1842
|
+
# If function is REPLACED (<<==), skip original body execution
|
|
1843
|
+
if is_replaced:
|
|
1844
|
+
return None # Injection already ran, don't run original
|
|
1845
|
+
|
|
1846
|
+
# Execute original function
|
|
1401
1847
|
if callable(callee):
|
|
1402
1848
|
return callee(*args)
|
|
1403
1849
|
|
|
1404
1850
|
if isinstance(callee, ASTNode) and callee.type == 'function':
|
|
1405
1851
|
return self._call_function(callee, args)
|
|
1406
1852
|
|
|
1407
|
-
|
|
1853
|
+
callee_name = callee_node.value if isinstance(callee_node, ASTNode) and hasattr(callee_node, 'value') else str(callee_node)
|
|
1854
|
+
raise CSSLRuntimeError(
|
|
1855
|
+
f"Cannot call '{callee_name}' - it is not a function",
|
|
1856
|
+
node.line,
|
|
1857
|
+
context=f"Type: {type(callee).__name__}",
|
|
1858
|
+
hint=ERROR_HINTS['undefined_function']
|
|
1859
|
+
)
|
|
1860
|
+
|
|
1861
|
+
def _eval_typed_call(self, node: ASTNode) -> Any:
|
|
1862
|
+
"""Evaluate typed function call like OpenFind<string>(0)"""
|
|
1863
|
+
name = node.value.get('name')
|
|
1864
|
+
type_param = node.value.get('type_param', 'dynamic')
|
|
1865
|
+
args = [self._evaluate(a) for a in node.value.get('args', [])]
|
|
1866
|
+
|
|
1867
|
+
# Handle OpenFind<type>(index)
|
|
1868
|
+
if name == 'OpenFind':
|
|
1869
|
+
# OpenFind searches for a value of the specified type
|
|
1870
|
+
# from the open parameters in scope
|
|
1871
|
+
open_params = self.scope.get('Params') or []
|
|
1872
|
+
index = args[0] if args else 0
|
|
1873
|
+
|
|
1874
|
+
# Search for value of matching type at or near the index
|
|
1875
|
+
type_map = {
|
|
1876
|
+
'string': str, 'str': str,
|
|
1877
|
+
'int': int, 'integer': int,
|
|
1878
|
+
'float': float, 'double': float,
|
|
1879
|
+
'bool': bool, 'boolean': bool,
|
|
1880
|
+
'list': list, 'array': list,
|
|
1881
|
+
'dict': dict, 'json': dict,
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
target_type = type_map.get(type_param.lower())
|
|
1885
|
+
|
|
1886
|
+
if isinstance(open_params, (list, tuple)):
|
|
1887
|
+
# Find first matching type starting from index
|
|
1888
|
+
for i in range(index, len(open_params)):
|
|
1889
|
+
if target_type is None or isinstance(open_params[i], target_type):
|
|
1890
|
+
return open_params[i]
|
|
1891
|
+
# Also search before index
|
|
1892
|
+
for i in range(0, min(index, len(open_params))):
|
|
1893
|
+
if target_type is None or isinstance(open_params[i], target_type):
|
|
1894
|
+
return open_params[i]
|
|
1895
|
+
|
|
1896
|
+
return None
|
|
1897
|
+
|
|
1898
|
+
# Fallback: call as regular function with type hint
|
|
1899
|
+
func = self.builtins.get_function(name)
|
|
1900
|
+
if func and callable(func):
|
|
1901
|
+
return func(type_param, *args)
|
|
1902
|
+
|
|
1903
|
+
raise CSSLRuntimeError(
|
|
1904
|
+
f"Unknown typed function: {name}<{type_param}>",
|
|
1905
|
+
node.line,
|
|
1906
|
+
context=f"Available typed functions: OpenFind<type>",
|
|
1907
|
+
hint="Typed functions use format: FunctionName<Type>(args)"
|
|
1908
|
+
)
|
|
1408
1909
|
|
|
1409
1910
|
def _eval_member_access(self, node: ASTNode) -> Any:
|
|
1410
1911
|
"""Evaluate member access"""
|
|
@@ -1419,6 +1920,18 @@ class CSSLRuntime:
|
|
|
1419
1920
|
if isinstance(obj, Parameter) and member == 'return':
|
|
1420
1921
|
member = 'return_'
|
|
1421
1922
|
|
|
1923
|
+
# === STRING METHODS ===
|
|
1924
|
+
if isinstance(obj, str):
|
|
1925
|
+
string_methods = self._get_string_method(obj, member)
|
|
1926
|
+
if string_methods is not None:
|
|
1927
|
+
return string_methods
|
|
1928
|
+
|
|
1929
|
+
# === LIST/ARRAY METHODS for plain lists ===
|
|
1930
|
+
if isinstance(obj, list) and not isinstance(obj, (Stack, Vector, Array)):
|
|
1931
|
+
list_methods = self._get_list_method(obj, member)
|
|
1932
|
+
if list_methods is not None:
|
|
1933
|
+
return list_methods
|
|
1934
|
+
|
|
1422
1935
|
if hasattr(obj, member):
|
|
1423
1936
|
return getattr(obj, member)
|
|
1424
1937
|
|
|
@@ -1427,6 +1940,198 @@ class CSSLRuntime:
|
|
|
1427
1940
|
|
|
1428
1941
|
return None
|
|
1429
1942
|
|
|
1943
|
+
def _get_string_method(self, s: str, method: str) -> Any:
|
|
1944
|
+
"""Get string method implementation for CSSL.
|
|
1945
|
+
|
|
1946
|
+
Provides C++/Java/JS style string methods that Python doesn't have.
|
|
1947
|
+
"""
|
|
1948
|
+
# === C++/Java/JS STRING METHODS ===
|
|
1949
|
+
if method == 'contains':
|
|
1950
|
+
return lambda substr: substr in s
|
|
1951
|
+
elif method == 'indexOf':
|
|
1952
|
+
return lambda substr, start=0: s.find(substr, start)
|
|
1953
|
+
elif method == 'lastIndexOf':
|
|
1954
|
+
return lambda substr: s.rfind(substr)
|
|
1955
|
+
elif method == 'charAt':
|
|
1956
|
+
return lambda index: s[index] if 0 <= index < len(s) else ''
|
|
1957
|
+
elif method == 'charCodeAt':
|
|
1958
|
+
return lambda index: ord(s[index]) if 0 <= index < len(s) else -1
|
|
1959
|
+
elif method == 'substring':
|
|
1960
|
+
return lambda start, end=None: s[start:end] if end else s[start:]
|
|
1961
|
+
elif method == 'substr':
|
|
1962
|
+
return lambda start, length=None: s[start:start+length] if length else s[start:]
|
|
1963
|
+
elif method == 'slice':
|
|
1964
|
+
return lambda start, end=None: s[start:end] if end else s[start:]
|
|
1965
|
+
|
|
1966
|
+
# === TRIM METHODS ===
|
|
1967
|
+
elif method == 'trim':
|
|
1968
|
+
return lambda: s.strip()
|
|
1969
|
+
elif method == 'trimStart' or method == 'trimLeft' or method == 'ltrim':
|
|
1970
|
+
return lambda: s.lstrip()
|
|
1971
|
+
elif method == 'trimEnd' or method == 'trimRight' or method == 'rtrim':
|
|
1972
|
+
return lambda: s.rstrip()
|
|
1973
|
+
|
|
1974
|
+
# === CASE METHODS ===
|
|
1975
|
+
elif method in ('toUpperCase', 'toUpper', 'upper'):
|
|
1976
|
+
return lambda: s.upper()
|
|
1977
|
+
elif method in ('toLowerCase', 'toLower', 'lower'):
|
|
1978
|
+
return lambda: s.lower()
|
|
1979
|
+
elif method == 'capitalize':
|
|
1980
|
+
return lambda: s.capitalize()
|
|
1981
|
+
elif method == 'title':
|
|
1982
|
+
return lambda: s.title()
|
|
1983
|
+
elif method == 'swapcase':
|
|
1984
|
+
return lambda: s.swapcase()
|
|
1985
|
+
|
|
1986
|
+
# === REPLACE METHODS ===
|
|
1987
|
+
elif method == 'replaceAll':
|
|
1988
|
+
return lambda old, new: s.replace(old, new)
|
|
1989
|
+
elif method == 'replaceFirst':
|
|
1990
|
+
return lambda old, new: s.replace(old, new, 1)
|
|
1991
|
+
|
|
1992
|
+
# === CHECK METHODS ===
|
|
1993
|
+
elif method == 'isEmpty':
|
|
1994
|
+
return lambda: len(s) == 0
|
|
1995
|
+
elif method == 'isBlank':
|
|
1996
|
+
return lambda: len(s.strip()) == 0
|
|
1997
|
+
elif method == 'isDigit' or method == 'isNumeric':
|
|
1998
|
+
return lambda: s.isdigit()
|
|
1999
|
+
elif method == 'isAlpha':
|
|
2000
|
+
return lambda: s.isalpha()
|
|
2001
|
+
elif method == 'isAlphaNumeric' or method == 'isAlnum':
|
|
2002
|
+
return lambda: s.isalnum()
|
|
2003
|
+
elif method == 'isSpace' or method == 'isWhitespace':
|
|
2004
|
+
return lambda: s.isspace()
|
|
2005
|
+
elif method == 'isUpper':
|
|
2006
|
+
return lambda: s.isupper()
|
|
2007
|
+
elif method == 'isLower':
|
|
2008
|
+
return lambda: s.islower()
|
|
2009
|
+
|
|
2010
|
+
# === STARTS/ENDS WITH ===
|
|
2011
|
+
elif method == 'startsWith' or method == 'startswith':
|
|
2012
|
+
return lambda prefix: s.startswith(prefix)
|
|
2013
|
+
elif method == 'endsWith' or method == 'endswith':
|
|
2014
|
+
return lambda suffix: s.endswith(suffix)
|
|
2015
|
+
|
|
2016
|
+
# === LENGTH/SIZE ===
|
|
2017
|
+
elif method == 'length' or method == 'size':
|
|
2018
|
+
return lambda: len(s)
|
|
2019
|
+
|
|
2020
|
+
# === SPLIT/JOIN ===
|
|
2021
|
+
elif method == 'toArray':
|
|
2022
|
+
return lambda sep=None: list(s.split(sep) if sep else list(s))
|
|
2023
|
+
elif method == 'lines':
|
|
2024
|
+
return lambda: s.splitlines()
|
|
2025
|
+
elif method == 'words':
|
|
2026
|
+
return lambda: s.split()
|
|
2027
|
+
|
|
2028
|
+
# === PADDING ===
|
|
2029
|
+
elif method == 'padStart' or method == 'padLeft' or method == 'lpad':
|
|
2030
|
+
return lambda width, char=' ': s.rjust(width, char[0] if char else ' ')
|
|
2031
|
+
elif method == 'padEnd' or method == 'padRight' or method == 'rpad':
|
|
2032
|
+
return lambda width, char=' ': s.ljust(width, char[0] if char else ' ')
|
|
2033
|
+
elif method == 'center':
|
|
2034
|
+
return lambda width, char=' ': s.center(width, char[0] if char else ' ')
|
|
2035
|
+
elif method == 'zfill':
|
|
2036
|
+
return lambda width: s.zfill(width)
|
|
2037
|
+
|
|
2038
|
+
# === REPEAT ===
|
|
2039
|
+
elif method == 'repeat':
|
|
2040
|
+
return lambda n: s * n
|
|
2041
|
+
|
|
2042
|
+
# === REVERSE ===
|
|
2043
|
+
elif method == 'reverse':
|
|
2044
|
+
return lambda: s[::-1]
|
|
2045
|
+
|
|
2046
|
+
# === FORMAT ===
|
|
2047
|
+
elif method == 'format':
|
|
2048
|
+
return lambda *args, **kwargs: s.format(*args, **kwargs)
|
|
2049
|
+
|
|
2050
|
+
# === ENCODING ===
|
|
2051
|
+
elif method == 'encode':
|
|
2052
|
+
return lambda encoding='utf-8': s.encode(encoding)
|
|
2053
|
+
elif method == 'bytes':
|
|
2054
|
+
return lambda encoding='utf-8': list(s.encode(encoding))
|
|
2055
|
+
|
|
2056
|
+
# === NUMERIC CONVERSION ===
|
|
2057
|
+
elif method == 'toInt' or method == 'toInteger':
|
|
2058
|
+
return lambda base=10: int(s, base) if s.lstrip('-').isdigit() else 0
|
|
2059
|
+
elif method == 'toFloat' or method == 'toDouble':
|
|
2060
|
+
try:
|
|
2061
|
+
return lambda: float(s)
|
|
2062
|
+
except:
|
|
2063
|
+
return lambda: 0.0
|
|
2064
|
+
elif method == 'toBool':
|
|
2065
|
+
return lambda: s.lower() in ('true', '1', 'yes', 'on')
|
|
2066
|
+
|
|
2067
|
+
# === C++ ITERATOR STYLE ===
|
|
2068
|
+
elif method == 'begin':
|
|
2069
|
+
return lambda: 0
|
|
2070
|
+
elif method == 'end':
|
|
2071
|
+
return lambda: len(s)
|
|
2072
|
+
|
|
2073
|
+
# Return None if not a string method
|
|
2074
|
+
return None
|
|
2075
|
+
|
|
2076
|
+
def _get_list_method(self, lst: list, method: str) -> Any:
|
|
2077
|
+
"""Get list method implementation for plain Python lists in CSSL."""
|
|
2078
|
+
if method == 'contains':
|
|
2079
|
+
return lambda item: item in lst
|
|
2080
|
+
elif method == 'indexOf':
|
|
2081
|
+
def index_of(item):
|
|
2082
|
+
try:
|
|
2083
|
+
return lst.index(item)
|
|
2084
|
+
except ValueError:
|
|
2085
|
+
return -1
|
|
2086
|
+
return index_of
|
|
2087
|
+
elif method == 'lastIndexOf':
|
|
2088
|
+
def last_index_of(item):
|
|
2089
|
+
for i in range(len(lst) - 1, -1, -1):
|
|
2090
|
+
if lst[i] == item:
|
|
2091
|
+
return i
|
|
2092
|
+
return -1
|
|
2093
|
+
return last_index_of
|
|
2094
|
+
elif method == 'length' or method == 'size':
|
|
2095
|
+
return lambda: len(lst)
|
|
2096
|
+
elif method == 'isEmpty':
|
|
2097
|
+
return lambda: len(lst) == 0
|
|
2098
|
+
elif method == 'first':
|
|
2099
|
+
return lambda: lst[0] if lst else None
|
|
2100
|
+
elif method == 'last':
|
|
2101
|
+
return lambda: lst[-1] if lst else None
|
|
2102
|
+
elif method == 'at':
|
|
2103
|
+
return lambda i: lst[i] if 0 <= i < len(lst) else None
|
|
2104
|
+
elif method == 'slice':
|
|
2105
|
+
return lambda start, end=None: lst[start:end] if end else lst[start:]
|
|
2106
|
+
elif method == 'join':
|
|
2107
|
+
return lambda sep=',': sep.join(str(x) for x in lst)
|
|
2108
|
+
elif method == 'find':
|
|
2109
|
+
def find_item(val):
|
|
2110
|
+
for item in lst:
|
|
2111
|
+
if item == val:
|
|
2112
|
+
return item
|
|
2113
|
+
return None
|
|
2114
|
+
return find_item
|
|
2115
|
+
elif method == 'push':
|
|
2116
|
+
def push_item(item):
|
|
2117
|
+
lst.append(item)
|
|
2118
|
+
return lst
|
|
2119
|
+
return push_item
|
|
2120
|
+
elif method == 'push_back':
|
|
2121
|
+
def push_back_item(item):
|
|
2122
|
+
lst.append(item)
|
|
2123
|
+
return lst
|
|
2124
|
+
return push_back_item
|
|
2125
|
+
elif method == 'toArray':
|
|
2126
|
+
return lambda: list(lst)
|
|
2127
|
+
# === C++ ITERATOR STYLE ===
|
|
2128
|
+
elif method == 'begin':
|
|
2129
|
+
return lambda: 0
|
|
2130
|
+
elif method == 'end':
|
|
2131
|
+
return lambda: len(lst)
|
|
2132
|
+
|
|
2133
|
+
return None
|
|
2134
|
+
|
|
1430
2135
|
def _eval_index_access(self, node: ASTNode) -> Any:
|
|
1431
2136
|
"""Evaluate index access"""
|
|
1432
2137
|
obj = self._evaluate(node.value.get('object'))
|
|
@@ -1448,10 +2153,30 @@ class CSSLRuntime:
|
|
|
1448
2153
|
if obj is None:
|
|
1449
2154
|
return
|
|
1450
2155
|
|
|
2156
|
+
# Check for SharedObjectProxy - directly access underlying object
|
|
2157
|
+
# This is more robust than relying on the proxy's __setattr__
|
|
2158
|
+
if hasattr(obj, '_direct_object') and hasattr(obj, '_name'):
|
|
2159
|
+
# This is a SharedObjectProxy - get the real object directly
|
|
2160
|
+
real_obj = object.__getattribute__(obj, '_direct_object')
|
|
2161
|
+
if real_obj is None:
|
|
2162
|
+
# Fallback to _live_objects registry
|
|
2163
|
+
name = object.__getattribute__(obj, '_name')
|
|
2164
|
+
from ..cssl_bridge import _live_objects
|
|
2165
|
+
real_obj = _live_objects.get(name)
|
|
2166
|
+
if real_obj is not None:
|
|
2167
|
+
setattr(real_obj, member, value)
|
|
2168
|
+
return
|
|
2169
|
+
|
|
1451
2170
|
if hasattr(obj, member):
|
|
1452
2171
|
setattr(obj, member, value)
|
|
1453
2172
|
elif isinstance(obj, dict):
|
|
1454
2173
|
obj[member] = value
|
|
2174
|
+
else:
|
|
2175
|
+
# Try setattr anyway for objects that support dynamic attributes
|
|
2176
|
+
try:
|
|
2177
|
+
setattr(obj, member, value)
|
|
2178
|
+
except (AttributeError, TypeError):
|
|
2179
|
+
pass
|
|
1455
2180
|
|
|
1456
2181
|
def _set_index(self, node: ASTNode, value: Any):
|
|
1457
2182
|
"""Set index value"""
|