IncludeCPP 3.4.8__py3-none-any.whl → 3.4.21__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- includecpp/__init__.py +1 -1
- includecpp/__init__.pyi +133 -2
- includecpp/cli/commands.py +126 -3
- includecpp/core/cssl/CSSL_DOCUMENTATION.md +1482 -0
- includecpp/core/cssl/__init__.py +8 -6
- includecpp/core/cssl/cssl_builtins.py +243 -5
- includecpp/core/cssl/cssl_parser.py +447 -12
- includecpp/core/cssl/cssl_runtime.py +831 -51
- includecpp/core/cssl/cssl_types.py +522 -7
- includecpp/core/cssl_bridge.py +374 -2
- includecpp/generator/parser.cpp +1 -1
- {includecpp-3.4.8.dist-info → includecpp-3.4.21.dist-info}/METADATA +270 -3
- {includecpp-3.4.8.dist-info → includecpp-3.4.21.dist-info}/RECORD +17 -16
- {includecpp-3.4.8.dist-info → includecpp-3.4.21.dist-info}/WHEEL +0 -0
- {includecpp-3.4.8.dist-info → includecpp-3.4.21.dist-info}/entry_points.txt +0 -0
- {includecpp-3.4.8.dist-info → includecpp-3.4.21.dist-info}/licenses/LICENSE +0 -0
- {includecpp-3.4.8.dist-info → includecpp-3.4.21.dist-info}/top_level.txt +0 -0
|
@@ -12,14 +12,51 @@ from .cssl_parser import ASTNode, parse_cssl, parse_cssl_program, CSSLSyntaxErro
|
|
|
12
12
|
from .cssl_events import CSSLEventManager, EventType, EventData, get_event_manager
|
|
13
13
|
from .cssl_builtins import CSSLBuiltins
|
|
14
14
|
from .cssl_modules import get_module_registry, get_standard_module
|
|
15
|
-
from .cssl_types import
|
|
15
|
+
from .cssl_types import (
|
|
16
|
+
Parameter, DataStruct, Shuffled, Iterator, Combo,
|
|
17
|
+
Stack, Vector, Array, DataSpace, OpenQuote
|
|
18
|
+
)
|
|
16
19
|
|
|
17
20
|
|
|
18
21
|
class CSSLRuntimeError(Exception):
|
|
19
|
-
"""Runtime error during CSSL execution"""
|
|
20
|
-
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):
|
|
21
24
|
self.line = line
|
|
22
|
-
|
|
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
|
+
}
|
|
23
60
|
|
|
24
61
|
|
|
25
62
|
class CSSLBreak(Exception):
|
|
@@ -103,6 +140,7 @@ class CSSLRuntime:
|
|
|
103
140
|
self._modules: Dict[str, Any] = {}
|
|
104
141
|
self._global_structs: Dict[str, Any] = {} # Global structs for s@<name> references
|
|
105
142
|
self._function_injections: Dict[str, List[ASTNode]] = {} # NEW: Permanent function injections
|
|
143
|
+
self._function_replaced: Dict[str, bool] = {} # NEW: Track replaced functions (<<==)
|
|
106
144
|
self._promoted_globals: Dict[str, Any] = {} # NEW: Variables promoted via global()
|
|
107
145
|
self._running = False
|
|
108
146
|
self._exit_code = 0
|
|
@@ -291,8 +329,14 @@ class CSSLRuntime:
|
|
|
291
329
|
self._exec_struct(child)
|
|
292
330
|
elif child.type == 'function':
|
|
293
331
|
self._exec_function(child)
|
|
332
|
+
elif child.type == 'global_assignment':
|
|
333
|
+
# Handle global variable declaration: global Name = value
|
|
334
|
+
result = self._exec_global_assignment(child)
|
|
335
|
+
elif child.type == 'typed_declaration':
|
|
336
|
+
# Handle typed variable declaration: type<T> varName = value;
|
|
337
|
+
result = self._exec_typed_declaration(child)
|
|
294
338
|
elif child.type in ('assignment', 'expression', 'inject', 'receive', 'flow',
|
|
295
|
-
'if', 'while', 'for', 'foreach', 'switch', 'try'):
|
|
339
|
+
'if', 'while', 'for', 'c_for', 'foreach', 'switch', 'try'):
|
|
296
340
|
result = self._execute_node(child)
|
|
297
341
|
elif child.type == 'call':
|
|
298
342
|
result = self._eval_call(child)
|
|
@@ -564,6 +608,109 @@ class CSSLRuntime:
|
|
|
564
608
|
self.scope.set(func_name, node)
|
|
565
609
|
return None
|
|
566
610
|
|
|
611
|
+
def _exec_typed_declaration(self, node: ASTNode) -> Any:
|
|
612
|
+
"""Execute typed variable declaration: type<T> varName = value;
|
|
613
|
+
|
|
614
|
+
Creates appropriate type instances for stack, vector, datastruct, etc.
|
|
615
|
+
"""
|
|
616
|
+
decl = node.value
|
|
617
|
+
type_name = decl.get('type')
|
|
618
|
+
element_type = decl.get('element_type', 'dynamic')
|
|
619
|
+
var_name = decl.get('name')
|
|
620
|
+
value_node = decl.get('value')
|
|
621
|
+
|
|
622
|
+
# Create the appropriate type instance
|
|
623
|
+
if type_name == 'stack':
|
|
624
|
+
instance = Stack(element_type)
|
|
625
|
+
elif type_name == 'vector':
|
|
626
|
+
instance = Vector(element_type)
|
|
627
|
+
elif type_name == 'datastruct':
|
|
628
|
+
instance = DataStruct(element_type)
|
|
629
|
+
elif type_name == 'shuffled':
|
|
630
|
+
instance = Shuffled(element_type)
|
|
631
|
+
elif type_name == 'iterator':
|
|
632
|
+
instance = Iterator(element_type)
|
|
633
|
+
elif type_name == 'combo':
|
|
634
|
+
instance = Combo(element_type)
|
|
635
|
+
elif type_name == 'dataspace':
|
|
636
|
+
instance = DataSpace(element_type)
|
|
637
|
+
elif type_name == 'openquote':
|
|
638
|
+
instance = OpenQuote()
|
|
639
|
+
elif type_name in ('int', 'integer'):
|
|
640
|
+
instance = 0 if value_node is None else self._evaluate(value_node)
|
|
641
|
+
elif type_name in ('string', 'str'):
|
|
642
|
+
instance = "" if value_node is None else self._evaluate(value_node)
|
|
643
|
+
elif type_name in ('float', 'double'):
|
|
644
|
+
instance = 0.0 if value_node is None else self._evaluate(value_node)
|
|
645
|
+
elif type_name == 'bool':
|
|
646
|
+
instance = False if value_node is None else self._evaluate(value_node)
|
|
647
|
+
elif type_name == 'dynamic':
|
|
648
|
+
instance = None if value_node is None else self._evaluate(value_node)
|
|
649
|
+
elif type_name == 'json':
|
|
650
|
+
instance = {} if value_node is None else self._evaluate(value_node)
|
|
651
|
+
elif type_name == 'array':
|
|
652
|
+
instance = Array(element_type)
|
|
653
|
+
else:
|
|
654
|
+
# Default: evaluate the value or set to None
|
|
655
|
+
instance = self._evaluate(value_node) if value_node else None
|
|
656
|
+
|
|
657
|
+
# If there's an explicit value, use it instead
|
|
658
|
+
if value_node and type_name not in ('int', 'integer', 'string', 'str', 'float', 'double', 'bool', 'dynamic', 'json', 'array'):
|
|
659
|
+
# For container types, the value might be initialization data
|
|
660
|
+
init_value = self._evaluate(value_node)
|
|
661
|
+
if isinstance(init_value, (list, tuple)):
|
|
662
|
+
instance.extend(init_value)
|
|
663
|
+
elif init_value is not None:
|
|
664
|
+
if hasattr(instance, 'append'):
|
|
665
|
+
instance.append(init_value)
|
|
666
|
+
|
|
667
|
+
# Store in scope
|
|
668
|
+
self.scope.set(var_name, instance)
|
|
669
|
+
return instance
|
|
670
|
+
|
|
671
|
+
def _exec_global_assignment(self, node: ASTNode) -> Any:
|
|
672
|
+
"""Execute global variable assignment: global Name = value
|
|
673
|
+
|
|
674
|
+
Stores the value in _promoted_globals so it can be accessed via @Name
|
|
675
|
+
"""
|
|
676
|
+
inner = node.value # The wrapped assignment/expression node
|
|
677
|
+
|
|
678
|
+
if inner is None:
|
|
679
|
+
return None
|
|
680
|
+
|
|
681
|
+
# Handle assignment node: global Name = value
|
|
682
|
+
if inner.type == 'assignment':
|
|
683
|
+
target = inner.value.get('target')
|
|
684
|
+
value = self._evaluate(inner.value.get('value'))
|
|
685
|
+
|
|
686
|
+
# Get variable name from target
|
|
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)
|
|
695
|
+
elif isinstance(target, str):
|
|
696
|
+
var_name = target
|
|
697
|
+
else:
|
|
698
|
+
var_name = str(target)
|
|
699
|
+
|
|
700
|
+
# Store in promoted globals for @Name access
|
|
701
|
+
self._promoted_globals[var_name] = value
|
|
702
|
+
# Also store in global scope for regular access
|
|
703
|
+
self.global_scope.set(var_name, value)
|
|
704
|
+
return value
|
|
705
|
+
|
|
706
|
+
# Handle expression that results in assignment
|
|
707
|
+
elif inner.type == 'expression':
|
|
708
|
+
result = self._evaluate(inner.value)
|
|
709
|
+
return result
|
|
710
|
+
|
|
711
|
+
# Fallback: execute normally
|
|
712
|
+
return self._execute_node(inner)
|
|
713
|
+
|
|
567
714
|
def _call_function(self, func_node: ASTNode, args: List[Any]) -> Any:
|
|
568
715
|
"""Call a function node with arguments"""
|
|
569
716
|
func_info = func_node.value
|
|
@@ -638,12 +785,16 @@ class CSSLRuntime:
|
|
|
638
785
|
return None
|
|
639
786
|
|
|
640
787
|
def _exec_for(self, node: ASTNode) -> Any:
|
|
641
|
-
"""Execute for loop"""
|
|
788
|
+
"""Execute Python-style for loop: for (i in range(start, end, step)) { }"""
|
|
642
789
|
var_name = node.value.get('var')
|
|
643
790
|
start = int(self._evaluate(node.value.get('start')))
|
|
644
791
|
end = int(self._evaluate(node.value.get('end')))
|
|
645
792
|
|
|
646
|
-
|
|
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):
|
|
647
798
|
self.scope.set(var_name, i)
|
|
648
799
|
try:
|
|
649
800
|
for child in node.children:
|
|
@@ -655,6 +806,73 @@ class CSSLRuntime:
|
|
|
655
806
|
|
|
656
807
|
return None
|
|
657
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
|
+
|
|
658
876
|
def _exec_foreach(self, node: ASTNode) -> Any:
|
|
659
877
|
"""Execute foreach loop"""
|
|
660
878
|
var_name = node.value.get('var')
|
|
@@ -779,7 +997,7 @@ class CSSLRuntime:
|
|
|
779
997
|
def _apply_injection_filter(self, source: Any, filter_info: dict) -> Any:
|
|
780
998
|
"""Apply injection filter to extract specific data from source.
|
|
781
999
|
|
|
782
|
-
|
|
1000
|
+
All BruteInjector Helpers:
|
|
783
1001
|
- string::where=VALUE - Filter strings containing VALUE
|
|
784
1002
|
- string::length=LENGTH - Filter strings of specific length
|
|
785
1003
|
- integer::where=VALUE - Filter integers matching VALUE
|
|
@@ -788,9 +1006,12 @@ class CSSLRuntime:
|
|
|
788
1006
|
- array::index=INDEX - Get specific index from array
|
|
789
1007
|
- array::length=LENGTH - Filter arrays of specific length
|
|
790
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
|
|
791
1011
|
- combo::filterdb - Get filter database from combo
|
|
792
1012
|
- combo::blocked - Get blocked items from combo
|
|
793
|
-
- dynamic::VarName=VALUE - Filter by dynamic variable
|
|
1013
|
+
- dynamic::VarName=VALUE - Filter by dynamic variable value
|
|
1014
|
+
- sql::data - Return only SQL-compatible data
|
|
794
1015
|
"""
|
|
795
1016
|
if not filter_info:
|
|
796
1017
|
return source
|
|
@@ -802,25 +1023,28 @@ class CSSLRuntime:
|
|
|
802
1023
|
filter_type, helper = filter_key.split('::', 1)
|
|
803
1024
|
filter_val = self._evaluate(filter_value) if isinstance(filter_value, ASTNode) else filter_value
|
|
804
1025
|
|
|
1026
|
+
# === STRING HELPERS ===
|
|
805
1027
|
if filter_type == 'string':
|
|
806
1028
|
if helper == 'where':
|
|
807
|
-
if isinstance(result, str)
|
|
808
|
-
|
|
1029
|
+
if isinstance(result, str):
|
|
1030
|
+
result = result if filter_val in result else None
|
|
809
1031
|
elif isinstance(result, list):
|
|
810
1032
|
result = [item for item in result if isinstance(item, str) and filter_val in item]
|
|
811
|
-
elif helper
|
|
1033
|
+
elif helper in ('length', 'lenght'): # Support common typo
|
|
812
1034
|
if isinstance(result, str):
|
|
813
1035
|
result = result if len(result) == filter_val else None
|
|
814
1036
|
elif isinstance(result, list):
|
|
815
1037
|
result = [item for item in result if isinstance(item, str) and len(item) == filter_val]
|
|
816
1038
|
|
|
1039
|
+
# === INTEGER HELPERS ===
|
|
817
1040
|
elif filter_type == 'integer':
|
|
818
1041
|
if helper == 'where':
|
|
819
|
-
if isinstance(result, int)
|
|
820
|
-
|
|
1042
|
+
if isinstance(result, int):
|
|
1043
|
+
result = result if result == filter_val else None
|
|
821
1044
|
elif isinstance(result, list):
|
|
822
1045
|
result = [item for item in result if isinstance(item, int) and item == filter_val]
|
|
823
1046
|
|
|
1047
|
+
# === JSON HELPERS ===
|
|
824
1048
|
elif filter_type == 'json':
|
|
825
1049
|
if helper == 'key':
|
|
826
1050
|
if isinstance(result, dict):
|
|
@@ -829,29 +1053,68 @@ class CSSLRuntime:
|
|
|
829
1053
|
result = [item.get(filter_val) for item in result if isinstance(item, dict) and filter_val in item]
|
|
830
1054
|
elif helper == 'value':
|
|
831
1055
|
if isinstance(result, dict):
|
|
832
|
-
|
|
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
|
|
833
1059
|
elif isinstance(result, list):
|
|
834
1060
|
result = [item for item in result if (isinstance(item, dict) and filter_val in item.values())]
|
|
835
1061
|
|
|
836
|
-
|
|
1062
|
+
# === ARRAY HELPERS ===
|
|
1063
|
+
elif filter_type == 'array':
|
|
837
1064
|
if helper == 'index':
|
|
838
|
-
if isinstance(result, list
|
|
839
|
-
|
|
840
|
-
|
|
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':
|
|
841
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)):
|
|
842
1089
|
result = result if len(result) == filter_val else []
|
|
843
1090
|
elif helper == 'where':
|
|
844
1091
|
if isinstance(result, list):
|
|
845
1092
|
result = [item for item in result if item == filter_val]
|
|
846
1093
|
|
|
1094
|
+
# === COMBO HELPERS ===
|
|
847
1095
|
elif filter_type == 'combo':
|
|
848
1096
|
if helper == 'filterdb':
|
|
849
1097
|
if hasattr(result, '_filterdb'):
|
|
850
1098
|
result = result._filterdb
|
|
1099
|
+
elif hasattr(result, 'filterdb'):
|
|
1100
|
+
result = result.filterdb
|
|
851
1101
|
elif helper == 'blocked':
|
|
852
1102
|
if hasattr(result, '_blocked'):
|
|
853
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
|
|
854
1116
|
|
|
1117
|
+
# === SQL HELPERS ===
|
|
855
1118
|
elif filter_type == 'sql':
|
|
856
1119
|
if helper == 'data':
|
|
857
1120
|
# Return only SQL-compatible data types
|
|
@@ -931,6 +1194,12 @@ class CSSLRuntime:
|
|
|
931
1194
|
self.scope.set(target.value, final_value)
|
|
932
1195
|
elif target.type == 'module_ref':
|
|
933
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))
|
|
934
1203
|
elif target.type == 'member_access':
|
|
935
1204
|
self._set_member(target, final_value)
|
|
936
1205
|
elif target.type == 'call':
|
|
@@ -999,6 +1268,12 @@ class CSSLRuntime:
|
|
|
999
1268
|
self.scope.set(target.value, final_value)
|
|
1000
1269
|
elif target.type == 'module_ref':
|
|
1001
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))
|
|
1002
1277
|
elif target.type == 'member_access':
|
|
1003
1278
|
self._set_member(target, final_value)
|
|
1004
1279
|
|
|
@@ -1008,13 +1283,13 @@ class CSSLRuntime:
|
|
|
1008
1283
|
"""Execute code infusion (<<==, +<<==, -<<==)
|
|
1009
1284
|
|
|
1010
1285
|
Modes:
|
|
1011
|
-
- replace: func <<== { code }
|
|
1012
|
-
- add: func +<<== { code }
|
|
1013
|
-
- 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
|
|
1014
1289
|
"""
|
|
1015
1290
|
target = node.value.get('target')
|
|
1016
1291
|
code_block = node.value.get('code')
|
|
1017
|
-
mode = node.value.get('mode', '
|
|
1292
|
+
mode = node.value.get('mode', 'replace') # Default is REPLACE for <<==
|
|
1018
1293
|
|
|
1019
1294
|
# Get function name from target
|
|
1020
1295
|
func_name = None
|
|
@@ -1030,17 +1305,21 @@ class CSSLRuntime:
|
|
|
1030
1305
|
return None
|
|
1031
1306
|
|
|
1032
1307
|
if mode == 'add':
|
|
1033
|
-
# Add code to function (
|
|
1308
|
+
# +<<== : Add code to function (both injection + original execute)
|
|
1034
1309
|
self.register_function_injection(func_name, code_block)
|
|
1310
|
+
self._function_replaced[func_name] = False # Don't replace, just add
|
|
1035
1311
|
elif mode == 'replace':
|
|
1036
|
-
# Replace
|
|
1312
|
+
# <<== : Replace function body (only injection executes, original skipped)
|
|
1037
1313
|
self._function_injections[func_name] = [code_block]
|
|
1314
|
+
self._function_replaced[func_name] = True # Mark as replaced
|
|
1038
1315
|
elif mode == 'remove':
|
|
1039
|
-
# Remove matching code
|
|
1316
|
+
# -<<== : Remove matching code from function body
|
|
1317
|
+
# For now, this removes all injections for the function
|
|
1040
1318
|
if func_name in self._function_injections:
|
|
1041
|
-
# For simplicity, clear all injections for this function
|
|
1042
|
-
# A more sophisticated implementation would compare code blocks
|
|
1043
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
|
|
1044
1323
|
|
|
1045
1324
|
return None
|
|
1046
1325
|
|
|
@@ -1083,6 +1362,14 @@ class CSSLRuntime:
|
|
|
1083
1362
|
self.scope.set(target.value, source)
|
|
1084
1363
|
elif target.type == 'module_ref':
|
|
1085
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)
|
|
1086
1373
|
|
|
1087
1374
|
return source
|
|
1088
1375
|
|
|
@@ -1094,6 +1381,20 @@ class CSSLRuntime:
|
|
|
1094
1381
|
if isinstance(target, ASTNode):
|
|
1095
1382
|
if target.type == 'identifier':
|
|
1096
1383
|
self.scope.set(target.value, value)
|
|
1384
|
+
elif target.type == 'global_ref':
|
|
1385
|
+
# r@Name = value - store in promoted globals
|
|
1386
|
+
self._promoted_globals[target.value] = value
|
|
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)
|
|
1097
1398
|
elif target.type == 'member_access':
|
|
1098
1399
|
self._set_member(target, value)
|
|
1099
1400
|
elif target.type == 'index_access':
|
|
@@ -1177,12 +1478,65 @@ class CSSLRuntime:
|
|
|
1177
1478
|
return value
|
|
1178
1479
|
|
|
1179
1480
|
if node.type == 'module_ref':
|
|
1180
|
-
|
|
1481
|
+
# Check modules first, then promoted globals, then scope
|
|
1482
|
+
value = self.get_module(node.value)
|
|
1483
|
+
if value is None:
|
|
1484
|
+
value = self._promoted_globals.get(node.value)
|
|
1485
|
+
if value is None:
|
|
1486
|
+
value = self.global_scope.get(node.value)
|
|
1487
|
+
return value
|
|
1181
1488
|
|
|
1182
1489
|
if node.type == 'self_ref':
|
|
1183
1490
|
# s@<name> reference to global struct
|
|
1184
1491
|
return self.get_global_struct(node.value)
|
|
1185
1492
|
|
|
1493
|
+
if node.type == 'global_ref':
|
|
1494
|
+
# r@<name> global variable reference
|
|
1495
|
+
# Check promoted globals first, then global scope
|
|
1496
|
+
value = self._promoted_globals.get(node.value)
|
|
1497
|
+
if value is None:
|
|
1498
|
+
value = self.global_scope.get(node.value)
|
|
1499
|
+
return value
|
|
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
|
+
|
|
1186
1540
|
if node.type == 'binary':
|
|
1187
1541
|
return self._eval_binary(node)
|
|
1188
1542
|
|
|
@@ -1192,6 +1546,10 @@ class CSSLRuntime:
|
|
|
1192
1546
|
if node.type == 'call':
|
|
1193
1547
|
return self._eval_call(node)
|
|
1194
1548
|
|
|
1549
|
+
if node.type == 'typed_call':
|
|
1550
|
+
# Handle OpenFind<type>(args) style calls
|
|
1551
|
+
return self._eval_typed_call(node)
|
|
1552
|
+
|
|
1195
1553
|
if node.type == 'member_access':
|
|
1196
1554
|
return self._eval_member_access(node)
|
|
1197
1555
|
|
|
@@ -1218,32 +1576,161 @@ class CSSLRuntime:
|
|
|
1218
1576
|
return None
|
|
1219
1577
|
|
|
1220
1578
|
def _eval_binary(self, node: ASTNode) -> Any:
|
|
1221
|
-
"""Evaluate binary operation"""
|
|
1579
|
+
"""Evaluate binary operation with auto-casting support"""
|
|
1222
1580
|
op = node.value.get('op')
|
|
1223
1581
|
left = self._evaluate(node.value.get('left'))
|
|
1224
1582
|
right = self._evaluate(node.value.get('right'))
|
|
1225
1583
|
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
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)
|
|
1241
1600
|
|
|
1242
|
-
if op
|
|
1243
|
-
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
|
|
1244
1664
|
|
|
1245
1665
|
return None
|
|
1246
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
|
+
|
|
1247
1734
|
def _eval_unary(self, node: ASTNode) -> Any:
|
|
1248
1735
|
"""Evaluate unary operation"""
|
|
1249
1736
|
op = node.value.get('op')
|
|
@@ -1262,7 +1749,7 @@ class CSSLRuntime:
|
|
|
1262
1749
|
callee = self._evaluate(callee_node)
|
|
1263
1750
|
args = [self._evaluate(a) for a in node.value.get('args', [])]
|
|
1264
1751
|
|
|
1265
|
-
#
|
|
1752
|
+
# Get function name for injection check
|
|
1266
1753
|
func_name = None
|
|
1267
1754
|
if isinstance(callee_node, ASTNode):
|
|
1268
1755
|
if callee_node.type == 'identifier':
|
|
@@ -1270,17 +1757,81 @@ class CSSLRuntime:
|
|
|
1270
1757
|
elif callee_node.type == 'member_access':
|
|
1271
1758
|
func_name = callee_node.value.get('member')
|
|
1272
1759
|
|
|
1273
|
-
#
|
|
1274
|
-
|
|
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:
|
|
1275
1766
|
self._execute_function_injections(func_name)
|
|
1276
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
|
|
1277
1773
|
if callable(callee):
|
|
1278
1774
|
return callee(*args)
|
|
1279
1775
|
|
|
1280
1776
|
if isinstance(callee, ASTNode) and callee.type == 'function':
|
|
1281
1777
|
return self._call_function(callee, args)
|
|
1282
1778
|
|
|
1283
|
-
|
|
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
|
+
)
|
|
1284
1835
|
|
|
1285
1836
|
def _eval_member_access(self, node: ASTNode) -> Any:
|
|
1286
1837
|
"""Evaluate member access"""
|
|
@@ -1290,6 +1841,23 @@ class CSSLRuntime:
|
|
|
1290
1841
|
if obj is None:
|
|
1291
1842
|
return None
|
|
1292
1843
|
|
|
1844
|
+
# Special handling for Parameter.return() -> Parameter.return_()
|
|
1845
|
+
# since 'return' is a Python keyword
|
|
1846
|
+
if isinstance(obj, Parameter) and member == 'return':
|
|
1847
|
+
member = 'return_'
|
|
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
|
+
|
|
1293
1861
|
if hasattr(obj, member):
|
|
1294
1862
|
return getattr(obj, member)
|
|
1295
1863
|
|
|
@@ -1298,6 +1866,198 @@ class CSSLRuntime:
|
|
|
1298
1866
|
|
|
1299
1867
|
return None
|
|
1300
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
|
+
|
|
1301
2061
|
def _eval_index_access(self, node: ASTNode) -> Any:
|
|
1302
2062
|
"""Evaluate index access"""
|
|
1303
2063
|
obj = self._evaluate(node.value.get('object'))
|
|
@@ -1319,10 +2079,30 @@ class CSSLRuntime:
|
|
|
1319
2079
|
if obj is None:
|
|
1320
2080
|
return
|
|
1321
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
|
+
|
|
1322
2096
|
if hasattr(obj, member):
|
|
1323
2097
|
setattr(obj, member, value)
|
|
1324
2098
|
elif isinstance(obj, dict):
|
|
1325
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
|
|
1326
2106
|
|
|
1327
2107
|
def _set_index(self, node: ASTNode, value: Any):
|
|
1328
2108
|
"""Set index value"""
|