IncludeCPP 3.5.0__py3-none-any.whl → 3.7.1__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/cli/commands.py +418 -0
- includecpp/core/cssl/CSSL_DOCUMENTATION.md +613 -20
- includecpp/core/cssl/cssl_builtins.py +342 -2
- includecpp/core/cssl/cssl_builtins.pyi +1393 -0
- includecpp/core/cssl/cssl_parser.py +368 -64
- includecpp/core/cssl/cssl_runtime.py +637 -38
- includecpp/core/cssl/cssl_types.py +561 -2
- includecpp/core/cssl_bridge.py +100 -4
- includecpp/core/cssl_bridge.pyi +177 -0
- includecpp/vscode/cssl/package.json +24 -4
- includecpp/vscode/cssl/snippets/cssl.snippets.json +1080 -0
- includecpp/vscode/cssl/syntaxes/cssl.tmLanguage.json +127 -7
- {includecpp-3.5.0.dist-info → includecpp-3.7.1.dist-info}/METADATA +1 -1
- {includecpp-3.5.0.dist-info → includecpp-3.7.1.dist-info}/RECORD +19 -17
- {includecpp-3.5.0.dist-info → includecpp-3.7.1.dist-info}/WHEEL +0 -0
- {includecpp-3.5.0.dist-info → includecpp-3.7.1.dist-info}/entry_points.txt +0 -0
- {includecpp-3.5.0.dist-info → includecpp-3.7.1.dist-info}/licenses/LICENSE +0 -0
- {includecpp-3.5.0.dist-info → includecpp-3.7.1.dist-info}/top_level.txt +0 -0
|
@@ -14,7 +14,8 @@ 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, Array, DataSpace, OpenQuote
|
|
17
|
+
Stack, Vector, Array, DataSpace, OpenQuote, List, Dictionary, Map,
|
|
18
|
+
CSSLClass, CSSLInstance
|
|
18
19
|
)
|
|
19
20
|
|
|
20
21
|
|
|
@@ -139,9 +140,13 @@ class CSSLRuntime:
|
|
|
139
140
|
self.services: Dict[str, ServiceDefinition] = {}
|
|
140
141
|
self._modules: Dict[str, Any] = {}
|
|
141
142
|
self._global_structs: Dict[str, Any] = {} # Global structs for s@<name> references
|
|
142
|
-
self._function_injections: Dict[str, List[
|
|
143
|
+
self._function_injections: Dict[str, List[tuple]] = {} # List of (code_block, captured_values_dict)
|
|
143
144
|
self._function_replaced: Dict[str, bool] = {} # NEW: Track replaced functions (<<==)
|
|
145
|
+
self._original_functions: Dict[str, Any] = {} # Store originals before replacement
|
|
146
|
+
self._injection_captures: Dict[str, Dict[str, Any]] = {} # Captured %vars per injection
|
|
147
|
+
self._current_captured_values: Dict[str, Any] = {} # Current captured values during injection execution
|
|
144
148
|
self._promoted_globals: Dict[str, Any] = {} # NEW: Variables promoted via global()
|
|
149
|
+
self._current_instance: Optional[CSSLInstance] = None # Current class instance for this-> access
|
|
145
150
|
self._running = False
|
|
146
151
|
self._exit_code = 0
|
|
147
152
|
self._output_callback = output_callback # Callback for console output (text, level)
|
|
@@ -374,10 +379,17 @@ class CSSLRuntime:
|
|
|
374
379
|
- top-level statements (assignments, function calls, control flow)
|
|
375
380
|
"""
|
|
376
381
|
result = None
|
|
382
|
+
self._running = True # Start running
|
|
377
383
|
|
|
378
384
|
for child in node.children:
|
|
385
|
+
# Check if exit() was called
|
|
386
|
+
if not self._running:
|
|
387
|
+
break
|
|
388
|
+
|
|
379
389
|
if child.type == 'struct':
|
|
380
390
|
self._exec_struct(child)
|
|
391
|
+
elif child.type == 'class':
|
|
392
|
+
self._exec_class(child)
|
|
381
393
|
elif child.type == 'function':
|
|
382
394
|
self._exec_function(child)
|
|
383
395
|
elif child.type == 'global_assignment':
|
|
@@ -386,6 +398,9 @@ class CSSLRuntime:
|
|
|
386
398
|
elif child.type == 'typed_declaration':
|
|
387
399
|
# Handle typed variable declaration: type<T> varName = value;
|
|
388
400
|
result = self._exec_typed_declaration(child)
|
|
401
|
+
elif child.type == 'instance_declaration':
|
|
402
|
+
# Handle instance declaration: instance<"name"> varName;
|
|
403
|
+
result = self._exec_instance_declaration(child)
|
|
389
404
|
elif child.type in ('assignment', 'expression', 'inject', 'receive', 'flow',
|
|
390
405
|
'if', 'while', 'for', 'c_for', 'foreach', 'switch', 'try'):
|
|
391
406
|
result = self._execute_node(child)
|
|
@@ -398,13 +413,14 @@ class CSSLRuntime:
|
|
|
398
413
|
except CSSLRuntimeError:
|
|
399
414
|
pass # Ignore unknown nodes in program mode
|
|
400
415
|
|
|
401
|
-
# Look for and execute main() if defined
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
416
|
+
# Look for and execute main() if defined (only if still running)
|
|
417
|
+
if self._running:
|
|
418
|
+
main_func = self.scope.get('main')
|
|
419
|
+
if main_func and isinstance(main_func, ASTNode) and main_func.type == 'function':
|
|
420
|
+
try:
|
|
421
|
+
result = self._call_function(main_func, [])
|
|
422
|
+
except CSSLReturn as ret:
|
|
423
|
+
result = ret.value
|
|
408
424
|
|
|
409
425
|
return result
|
|
410
426
|
|
|
@@ -652,6 +668,57 @@ class CSSLRuntime:
|
|
|
652
668
|
|
|
653
669
|
return struct_data
|
|
654
670
|
|
|
671
|
+
def _exec_class(self, node: ASTNode) -> CSSLClass:
|
|
672
|
+
"""Execute class definition - registers class in scope.
|
|
673
|
+
|
|
674
|
+
Parses class members and methods, creating a CSSLClass object
|
|
675
|
+
that can be instantiated with 'new'.
|
|
676
|
+
"""
|
|
677
|
+
class_info = node.value
|
|
678
|
+
class_name = class_info.get('name')
|
|
679
|
+
|
|
680
|
+
members = {} # Member variable defaults/types
|
|
681
|
+
methods = {} # Method AST nodes
|
|
682
|
+
constructor = None
|
|
683
|
+
|
|
684
|
+
for child in node.children:
|
|
685
|
+
if child.type == 'function':
|
|
686
|
+
# This is a method
|
|
687
|
+
func_info = child.value
|
|
688
|
+
method_name = func_info.get('name')
|
|
689
|
+
|
|
690
|
+
if func_info.get('is_constructor') or method_name == class_name or method_name == '__init__':
|
|
691
|
+
constructor = child
|
|
692
|
+
else:
|
|
693
|
+
methods[method_name] = child
|
|
694
|
+
|
|
695
|
+
elif child.type == 'typed_declaration':
|
|
696
|
+
# This is a member variable
|
|
697
|
+
decl = child.value
|
|
698
|
+
member_name = decl.get('name')
|
|
699
|
+
member_type = decl.get('type')
|
|
700
|
+
member_value = decl.get('value')
|
|
701
|
+
|
|
702
|
+
# Store member info with type and optional default
|
|
703
|
+
members[member_name] = {
|
|
704
|
+
'type': member_type,
|
|
705
|
+
'default': self._evaluate(member_value) if member_value else None
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
# Create class definition object
|
|
709
|
+
class_def = CSSLClass(
|
|
710
|
+
name=class_name,
|
|
711
|
+
members=members,
|
|
712
|
+
methods=methods,
|
|
713
|
+
constructor=constructor
|
|
714
|
+
)
|
|
715
|
+
|
|
716
|
+
# Register class in scope
|
|
717
|
+
self.scope.set(class_name, class_def)
|
|
718
|
+
self.global_scope.set(class_name, class_def)
|
|
719
|
+
|
|
720
|
+
return class_def
|
|
721
|
+
|
|
655
722
|
def _exec_function(self, node: ASTNode) -> Any:
|
|
656
723
|
"""Execute function definition - just registers it"""
|
|
657
724
|
func_info = node.value
|
|
@@ -701,6 +768,12 @@ class CSSLRuntime:
|
|
|
701
768
|
instance = {} if value_node is None else self._evaluate(value_node)
|
|
702
769
|
elif type_name == 'array':
|
|
703
770
|
instance = Array(element_type)
|
|
771
|
+
elif type_name == 'list':
|
|
772
|
+
instance = List(element_type)
|
|
773
|
+
elif type_name in ('dictionary', 'dict'):
|
|
774
|
+
instance = Dictionary(element_type)
|
|
775
|
+
elif type_name == 'map':
|
|
776
|
+
instance = Map(element_type)
|
|
704
777
|
else:
|
|
705
778
|
# Default: evaluate the value or set to None
|
|
706
779
|
instance = self._evaluate(value_node) if value_node else None
|
|
@@ -719,6 +792,34 @@ class CSSLRuntime:
|
|
|
719
792
|
self.scope.set(var_name, instance)
|
|
720
793
|
return instance
|
|
721
794
|
|
|
795
|
+
def _exec_instance_declaration(self, node: ASTNode) -> Any:
|
|
796
|
+
"""Execute instance declaration: instance<"name"> varName;
|
|
797
|
+
|
|
798
|
+
Gets or creates a shared instance by name.
|
|
799
|
+
"""
|
|
800
|
+
from ..cssl_bridge import _live_objects, SharedObjectProxy
|
|
801
|
+
decl = node.value
|
|
802
|
+
instance_name = decl.get('instance_name')
|
|
803
|
+
var_name = decl.get('name')
|
|
804
|
+
value_node = decl.get('value')
|
|
805
|
+
|
|
806
|
+
# Get existing shared instance
|
|
807
|
+
instance = None
|
|
808
|
+
if instance_name in _live_objects:
|
|
809
|
+
instance = SharedObjectProxy(instance_name, _live_objects[instance_name])
|
|
810
|
+
elif self.global_scope.has(f'${instance_name}'):
|
|
811
|
+
instance = self.global_scope.get(f'${instance_name}')
|
|
812
|
+
|
|
813
|
+
# If value is provided, use that and register as shared
|
|
814
|
+
if value_node:
|
|
815
|
+
instance = self._evaluate(value_node)
|
|
816
|
+
# Register in global scope for future access
|
|
817
|
+
self.global_scope.set(f'${instance_name}', instance)
|
|
818
|
+
|
|
819
|
+
# Store in scope
|
|
820
|
+
self.scope.set(var_name, instance)
|
|
821
|
+
return instance
|
|
822
|
+
|
|
722
823
|
def _exec_global_assignment(self, node: ASTNode) -> Any:
|
|
723
824
|
"""Execute global variable assignment: global Name = value
|
|
724
825
|
|
|
@@ -762,11 +863,18 @@ class CSSLRuntime:
|
|
|
762
863
|
# Fallback: execute normally
|
|
763
864
|
return self._execute_node(inner)
|
|
764
865
|
|
|
765
|
-
def _call_function(self, func_node: ASTNode, args: List[Any]) -> Any:
|
|
766
|
-
"""Call a function node with arguments
|
|
866
|
+
def _call_function(self, func_node: ASTNode, args: List[Any], kwargs: Dict[str, Any] = None) -> Any:
|
|
867
|
+
"""Call a function node with arguments (positional and named)
|
|
868
|
+
|
|
869
|
+
Args:
|
|
870
|
+
func_node: The function AST node
|
|
871
|
+
args: List of positional arguments
|
|
872
|
+
kwargs: Dict of named arguments (param_name -> value)
|
|
873
|
+
"""
|
|
767
874
|
func_info = func_node.value
|
|
768
875
|
params = func_info.get('params', [])
|
|
769
876
|
modifiers = func_info.get('modifiers', [])
|
|
877
|
+
kwargs = kwargs or {}
|
|
770
878
|
|
|
771
879
|
# Check for undefined modifier - suppress errors if present
|
|
772
880
|
is_undefined = 'undefined' in modifiers
|
|
@@ -774,11 +882,16 @@ class CSSLRuntime:
|
|
|
774
882
|
# Create new scope
|
|
775
883
|
new_scope = Scope(parent=self.scope)
|
|
776
884
|
|
|
777
|
-
# Bind parameters - handle both
|
|
885
|
+
# Bind parameters - handle both positional and named arguments
|
|
778
886
|
for i, param in enumerate(params):
|
|
779
887
|
# Extract param name from dict format: {'name': 'a', 'type': 'int'}
|
|
780
888
|
param_name = param['name'] if isinstance(param, dict) else param
|
|
781
|
-
|
|
889
|
+
|
|
890
|
+
if param_name in kwargs:
|
|
891
|
+
# Named argument takes priority
|
|
892
|
+
new_scope.set(param_name, kwargs[param_name])
|
|
893
|
+
elif i < len(args):
|
|
894
|
+
# Positional argument
|
|
782
895
|
new_scope.set(param_name, args[i])
|
|
783
896
|
else:
|
|
784
897
|
new_scope.set(param_name, None)
|
|
@@ -789,6 +902,9 @@ class CSSLRuntime:
|
|
|
789
902
|
|
|
790
903
|
try:
|
|
791
904
|
for child in func_node.children:
|
|
905
|
+
# Check if exit() was called
|
|
906
|
+
if not self._running:
|
|
907
|
+
break
|
|
792
908
|
self._execute_node(child)
|
|
793
909
|
except CSSLReturn as ret:
|
|
794
910
|
return ret.value
|
|
@@ -824,9 +940,11 @@ class CSSLRuntime:
|
|
|
824
940
|
|
|
825
941
|
def _exec_while(self, node: ASTNode) -> Any:
|
|
826
942
|
"""Execute while loop"""
|
|
827
|
-
while self._evaluate(node.value.get('condition')):
|
|
943
|
+
while self._running and self._evaluate(node.value.get('condition')):
|
|
828
944
|
try:
|
|
829
945
|
for child in node.children:
|
|
946
|
+
if not self._running:
|
|
947
|
+
break
|
|
830
948
|
self._execute_node(child)
|
|
831
949
|
except CSSLBreak:
|
|
832
950
|
break
|
|
@@ -846,9 +964,13 @@ class CSSLRuntime:
|
|
|
846
964
|
step = int(self._evaluate(step_node)) if step_node else 1
|
|
847
965
|
|
|
848
966
|
for i in range(start, end, step):
|
|
967
|
+
if not self._running:
|
|
968
|
+
break
|
|
849
969
|
self.scope.set(var_name, i)
|
|
850
970
|
try:
|
|
851
971
|
for child in node.children:
|
|
972
|
+
if not self._running:
|
|
973
|
+
break
|
|
852
974
|
self._execute_node(child)
|
|
853
975
|
except CSSLBreak:
|
|
854
976
|
break
|
|
@@ -879,7 +1001,7 @@ class CSSLRuntime:
|
|
|
879
1001
|
var_name = None
|
|
880
1002
|
|
|
881
1003
|
# Main loop
|
|
882
|
-
while
|
|
1004
|
+
while self._running:
|
|
883
1005
|
# Check condition
|
|
884
1006
|
if condition:
|
|
885
1007
|
cond_result = self._evaluate(condition)
|
|
@@ -890,6 +1012,8 @@ class CSSLRuntime:
|
|
|
890
1012
|
# Execute body
|
|
891
1013
|
try:
|
|
892
1014
|
for child in node.children:
|
|
1015
|
+
if not self._running:
|
|
1016
|
+
break
|
|
893
1017
|
self._execute_node(child)
|
|
894
1018
|
except CSSLBreak:
|
|
895
1019
|
break
|
|
@@ -933,9 +1057,13 @@ class CSSLRuntime:
|
|
|
933
1057
|
return None
|
|
934
1058
|
|
|
935
1059
|
for item in iterable:
|
|
1060
|
+
if not self._running:
|
|
1061
|
+
break
|
|
936
1062
|
self.scope.set(var_name, item)
|
|
937
1063
|
try:
|
|
938
1064
|
for child in node.children:
|
|
1065
|
+
if not self._running:
|
|
1066
|
+
break
|
|
939
1067
|
self._execute_node(child)
|
|
940
1068
|
except CSSLBreak:
|
|
941
1069
|
break
|
|
@@ -1109,6 +1237,76 @@ class CSSLRuntime:
|
|
|
1109
1237
|
result = result if len(result) == filter_val else None
|
|
1110
1238
|
elif isinstance(result, list):
|
|
1111
1239
|
result = [item for item in result if isinstance(item, str) and len(item) == filter_val]
|
|
1240
|
+
elif helper == 'cut':
|
|
1241
|
+
# Cut string - returns the part BEFORE the index/substring
|
|
1242
|
+
# x = <==[string::cut=2] "20:200-1" --> x = "20"
|
|
1243
|
+
# x = <==[string::cut="1.0"] "1.0.0" --> x = "" (before "1.0")
|
|
1244
|
+
if isinstance(result, str):
|
|
1245
|
+
if isinstance(filter_val, str):
|
|
1246
|
+
# Cut at substring position
|
|
1247
|
+
idx = result.find(filter_val)
|
|
1248
|
+
result = result[:idx] if idx >= 0 else result
|
|
1249
|
+
else:
|
|
1250
|
+
# Cut at integer index
|
|
1251
|
+
idx = int(filter_val)
|
|
1252
|
+
result = result[:idx] if 0 <= idx <= len(result) else result
|
|
1253
|
+
elif isinstance(result, list):
|
|
1254
|
+
def cut_item(item):
|
|
1255
|
+
if not isinstance(item, str):
|
|
1256
|
+
return item
|
|
1257
|
+
if isinstance(filter_val, str):
|
|
1258
|
+
idx = item.find(filter_val)
|
|
1259
|
+
return item[:idx] if idx >= 0 else item
|
|
1260
|
+
return item[:int(filter_val)]
|
|
1261
|
+
result = [cut_item(item) for item in result]
|
|
1262
|
+
elif helper == 'cutAfter':
|
|
1263
|
+
# Get the part AFTER the index/substring
|
|
1264
|
+
# x = <==[string::cutAfter=2] "20:200-1" --> x = ":200-1"
|
|
1265
|
+
# x = <==[string::cutAfter="1.0"] "1.0.0" --> x = ".0" (after "1.0")
|
|
1266
|
+
if isinstance(result, str):
|
|
1267
|
+
if isinstance(filter_val, str):
|
|
1268
|
+
# Cut after substring
|
|
1269
|
+
idx = result.find(filter_val)
|
|
1270
|
+
result = result[idx + len(filter_val):] if idx >= 0 else result
|
|
1271
|
+
else:
|
|
1272
|
+
# Cut after integer index
|
|
1273
|
+
idx = int(filter_val)
|
|
1274
|
+
result = result[idx:] if 0 <= idx <= len(result) else result
|
|
1275
|
+
elif isinstance(result, list):
|
|
1276
|
+
def cut_after_item(item):
|
|
1277
|
+
if not isinstance(item, str):
|
|
1278
|
+
return item
|
|
1279
|
+
if isinstance(filter_val, str):
|
|
1280
|
+
idx = item.find(filter_val)
|
|
1281
|
+
return item[idx + len(filter_val):] if idx >= 0 else item
|
|
1282
|
+
return item[int(filter_val):]
|
|
1283
|
+
result = [cut_after_item(item) for item in result]
|
|
1284
|
+
elif helper == 'slice':
|
|
1285
|
+
# Slice string with start:end format (e.g., "2:5")
|
|
1286
|
+
if isinstance(result, str) and isinstance(filter_val, str) and ':' in filter_val:
|
|
1287
|
+
parts = filter_val.split(':')
|
|
1288
|
+
start = int(parts[0]) if parts[0] else 0
|
|
1289
|
+
end = int(parts[1]) if parts[1] else len(result)
|
|
1290
|
+
result = result[start:end]
|
|
1291
|
+
elif helper == 'split':
|
|
1292
|
+
# Split string by delimiter
|
|
1293
|
+
if isinstance(result, str):
|
|
1294
|
+
result = result.split(str(filter_val))
|
|
1295
|
+
elif helper == 'replace':
|
|
1296
|
+
# Replace in string (format: "old:new")
|
|
1297
|
+
if isinstance(result, str) and isinstance(filter_val, str) and ':' in filter_val:
|
|
1298
|
+
parts = filter_val.split(':', 1)
|
|
1299
|
+
if len(parts) == 2:
|
|
1300
|
+
result = result.replace(parts[0], parts[1])
|
|
1301
|
+
elif helper == 'upper':
|
|
1302
|
+
if isinstance(result, str):
|
|
1303
|
+
result = result.upper()
|
|
1304
|
+
elif helper == 'lower':
|
|
1305
|
+
if isinstance(result, str):
|
|
1306
|
+
result = result.lower()
|
|
1307
|
+
elif helper == 'trim':
|
|
1308
|
+
if isinstance(result, str):
|
|
1309
|
+
result = result.strip()
|
|
1112
1310
|
|
|
1113
1311
|
# === INTEGER HELPERS ===
|
|
1114
1312
|
elif filter_type == 'integer':
|
|
@@ -1220,8 +1418,21 @@ class CSSLRuntime:
|
|
|
1220
1418
|
self.register_function_injection(func_name, source_node)
|
|
1221
1419
|
return None
|
|
1222
1420
|
|
|
1223
|
-
#
|
|
1224
|
-
|
|
1421
|
+
# Check if source is an action_block with %<name> captures
|
|
1422
|
+
# If so, capture values NOW and evaluate the block with those captures
|
|
1423
|
+
if isinstance(source_node, ASTNode) and source_node.type == 'action_block':
|
|
1424
|
+
# Scan for %<name> captured references and capture their current values
|
|
1425
|
+
captured_values = self._scan_and_capture_refs(source_node)
|
|
1426
|
+
old_captured = self._current_captured_values.copy()
|
|
1427
|
+
self._current_captured_values = captured_values
|
|
1428
|
+
try:
|
|
1429
|
+
# Execute the action block and get the last expression's value
|
|
1430
|
+
source = self._evaluate_action_block(source_node)
|
|
1431
|
+
finally:
|
|
1432
|
+
self._current_captured_values = old_captured
|
|
1433
|
+
else:
|
|
1434
|
+
# Evaluate source normally
|
|
1435
|
+
source = self._evaluate(source_node)
|
|
1225
1436
|
|
|
1226
1437
|
# Apply filter if present
|
|
1227
1438
|
if filter_info:
|
|
@@ -1348,6 +1559,12 @@ class CSSLRuntime:
|
|
|
1348
1559
|
name = target.value
|
|
1349
1560
|
_live_objects[name] = final_value
|
|
1350
1561
|
self.global_scope.set(f'${name}', SharedObjectProxy(name, final_value))
|
|
1562
|
+
elif target.type == 'instance_ref':
|
|
1563
|
+
# value ==> instance<"name"> - create/update shared object
|
|
1564
|
+
from ..cssl_bridge import _live_objects, SharedObjectProxy
|
|
1565
|
+
name = target.value
|
|
1566
|
+
_live_objects[name] = final_value
|
|
1567
|
+
self.global_scope.set(f'${name}', SharedObjectProxy(name, final_value))
|
|
1351
1568
|
elif target.type == 'member_access':
|
|
1352
1569
|
self._set_member(target, final_value)
|
|
1353
1570
|
|
|
@@ -1360,11 +1577,20 @@ class CSSLRuntime:
|
|
|
1360
1577
|
- replace: func <<== { code } - REPLACES function body (original won't execute)
|
|
1361
1578
|
- add: func +<<== { code } - ADDS code to function (both execute)
|
|
1362
1579
|
- remove: func -<<== { code } - REMOVES matching code from function
|
|
1580
|
+
|
|
1581
|
+
Also supports expression form: func <<== %exit() (wraps in action_block)
|
|
1363
1582
|
"""
|
|
1364
1583
|
target = node.value.get('target')
|
|
1365
1584
|
code_block = node.value.get('code')
|
|
1585
|
+
source_expr = node.value.get('source') # For expression form: func <<== expr
|
|
1366
1586
|
mode = node.value.get('mode', 'replace') # Default is REPLACE for <<==
|
|
1367
1587
|
|
|
1588
|
+
# If source expression is provided instead of code block, wrap it
|
|
1589
|
+
if code_block is None and source_expr is not None:
|
|
1590
|
+
# Wrap in expression node so _execute_node can handle it
|
|
1591
|
+
expr_node = ASTNode('expression', value=source_expr)
|
|
1592
|
+
code_block = ASTNode('action_block', children=[expr_node])
|
|
1593
|
+
|
|
1368
1594
|
# Get function name from target
|
|
1369
1595
|
func_name = None
|
|
1370
1596
|
if isinstance(target, ASTNode):
|
|
@@ -1375,7 +1601,7 @@ class CSSLRuntime:
|
|
|
1375
1601
|
if isinstance(callee, ASTNode) and callee.type == 'identifier':
|
|
1376
1602
|
func_name = callee.value
|
|
1377
1603
|
|
|
1378
|
-
if not func_name:
|
|
1604
|
+
if not func_name or code_block is None:
|
|
1379
1605
|
return None
|
|
1380
1606
|
|
|
1381
1607
|
if mode == 'add':
|
|
@@ -1384,16 +1610,31 @@ class CSSLRuntime:
|
|
|
1384
1610
|
self._function_replaced[func_name] = False # Don't replace, just add
|
|
1385
1611
|
elif mode == 'replace':
|
|
1386
1612
|
# <<== : Replace function body (only injection executes, original skipped)
|
|
1387
|
-
|
|
1613
|
+
# Save original function BEFORE replacing (for original() access)
|
|
1614
|
+
if func_name not in self._original_functions:
|
|
1615
|
+
# Try to find original in scope or builtins
|
|
1616
|
+
original = self.scope.get(func_name)
|
|
1617
|
+
if original is None:
|
|
1618
|
+
original = getattr(self.builtins, f'builtin_{func_name}', None)
|
|
1619
|
+
if original is not None:
|
|
1620
|
+
self._original_functions[func_name] = original
|
|
1621
|
+
# Capture %<name> references at registration time
|
|
1622
|
+
captured_values = self._scan_and_capture_refs(code_block)
|
|
1623
|
+
self._function_injections[func_name] = [(code_block, captured_values)]
|
|
1388
1624
|
self._function_replaced[func_name] = True # Mark as replaced
|
|
1389
1625
|
elif mode == 'remove':
|
|
1390
|
-
# -<<== : Remove matching code from function body
|
|
1391
|
-
|
|
1626
|
+
# -<<== or -<<==[n] : Remove matching code from function body
|
|
1627
|
+
remove_index = node.value.get('index')
|
|
1628
|
+
|
|
1392
1629
|
if func_name in self._function_injections:
|
|
1393
|
-
|
|
1630
|
+
if remove_index is not None:
|
|
1631
|
+
# Indexed removal: -<<==[n] removes only the nth injection
|
|
1632
|
+
if 0 <= remove_index < len(self._function_injections[func_name]):
|
|
1633
|
+
self._function_injections[func_name].pop(remove_index)
|
|
1634
|
+
else:
|
|
1635
|
+
# No index: -<<== removes all injections
|
|
1636
|
+
self._function_injections[func_name] = []
|
|
1394
1637
|
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
|
|
1397
1638
|
|
|
1398
1639
|
return None
|
|
1399
1640
|
|
|
@@ -1473,6 +1714,12 @@ class CSSLRuntime:
|
|
|
1473
1714
|
self._set_member(target, value)
|
|
1474
1715
|
elif target.type == 'index_access':
|
|
1475
1716
|
self._set_index(target, value)
|
|
1717
|
+
elif target.type == 'this_access':
|
|
1718
|
+
# this->member = value
|
|
1719
|
+
if self._current_instance is None:
|
|
1720
|
+
raise CSSLRuntimeError("'this' used outside of class method context")
|
|
1721
|
+
member = target.value.get('member')
|
|
1722
|
+
self._current_instance.set_member(member, value)
|
|
1476
1723
|
elif isinstance(target, str):
|
|
1477
1724
|
self.scope.set(target, value)
|
|
1478
1725
|
|
|
@@ -1546,9 +1793,17 @@ class CSSLRuntime:
|
|
|
1546
1793
|
return None
|
|
1547
1794
|
|
|
1548
1795
|
if node.type == 'identifier':
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1796
|
+
name = node.value
|
|
1797
|
+
value = self.scope.get(name)
|
|
1798
|
+
# Fallback to global scope
|
|
1799
|
+
if value is None:
|
|
1800
|
+
value = self.global_scope.get(name)
|
|
1801
|
+
# Fallback to promoted globals (from 'global' keyword)
|
|
1802
|
+
if value is None:
|
|
1803
|
+
value = self._promoted_globals.get(name)
|
|
1804
|
+
# Fallback to builtins
|
|
1805
|
+
if value is None and self.builtins.has_function(name):
|
|
1806
|
+
return self.builtins.get_function(name)
|
|
1552
1807
|
return value
|
|
1553
1808
|
|
|
1554
1809
|
if node.type == 'module_ref':
|
|
@@ -1585,6 +1840,55 @@ class CSSLRuntime:
|
|
|
1585
1840
|
return scoped_val
|
|
1586
1841
|
raise CSSLRuntimeError(f"Shared object '${name}' not found. Use share() to share objects.")
|
|
1587
1842
|
|
|
1843
|
+
if node.type == 'captured_ref':
|
|
1844
|
+
# %<name> captured reference - use value captured at infusion registration time
|
|
1845
|
+
name = node.value
|
|
1846
|
+
# First check captured values from current injection context
|
|
1847
|
+
if name in self._current_captured_values:
|
|
1848
|
+
captured_value = self._current_captured_values[name]
|
|
1849
|
+
# Only use captured value if it's not None
|
|
1850
|
+
if captured_value is not None:
|
|
1851
|
+
return captured_value
|
|
1852
|
+
# Fall back to normal resolution if not captured or capture was None
|
|
1853
|
+
value = self.scope.get(name)
|
|
1854
|
+
if value is None:
|
|
1855
|
+
value = self.global_scope.get(name)
|
|
1856
|
+
if value is None:
|
|
1857
|
+
# For critical builtins like 'exit', create direct wrapper
|
|
1858
|
+
if name == 'exit':
|
|
1859
|
+
runtime = self
|
|
1860
|
+
value = lambda code=0, rt=runtime: rt.exit(code)
|
|
1861
|
+
else:
|
|
1862
|
+
value = getattr(self.builtins, f'builtin_{name}', None)
|
|
1863
|
+
if value is None:
|
|
1864
|
+
# Check original functions (for replaced functions)
|
|
1865
|
+
value = self._original_functions.get(name)
|
|
1866
|
+
if value is not None:
|
|
1867
|
+
return value
|
|
1868
|
+
raise CSSLRuntimeError(f"Captured reference '%{name}' not found.")
|
|
1869
|
+
|
|
1870
|
+
if node.type == 'instance_ref':
|
|
1871
|
+
# instance<"name"> - get shared instance by name
|
|
1872
|
+
# Works like $name but with explicit syntax
|
|
1873
|
+
from ..cssl_bridge import _live_objects, SharedObjectProxy
|
|
1874
|
+
name = node.value
|
|
1875
|
+
if name in _live_objects:
|
|
1876
|
+
return SharedObjectProxy(name, _live_objects[name])
|
|
1877
|
+
# Check if stored in runtime's scope
|
|
1878
|
+
scoped_val = self.global_scope.get(f'${name}')
|
|
1879
|
+
if scoped_val is not None:
|
|
1880
|
+
return scoped_val
|
|
1881
|
+
# Return None if instance doesn't exist (can be created via ==>)
|
|
1882
|
+
return None
|
|
1883
|
+
|
|
1884
|
+
if node.type == 'new':
|
|
1885
|
+
# Create new instance of a class: new ClassName(args)
|
|
1886
|
+
return self._eval_new(node)
|
|
1887
|
+
|
|
1888
|
+
if node.type == 'this_access':
|
|
1889
|
+
# this->member access
|
|
1890
|
+
return self._eval_this_access(node)
|
|
1891
|
+
|
|
1588
1892
|
if node.type == 'type_instantiation':
|
|
1589
1893
|
# Create new instance of a type: stack<string>, vector<int>, etc.
|
|
1590
1894
|
type_name = node.value.get('type')
|
|
@@ -1608,6 +1912,10 @@ class CSSLRuntime:
|
|
|
1608
1912
|
return OpenQuote()
|
|
1609
1913
|
elif type_name == 'array':
|
|
1610
1914
|
return Array(element_type)
|
|
1915
|
+
elif type_name == 'list':
|
|
1916
|
+
return List(element_type)
|
|
1917
|
+
elif type_name in ('dictionary', 'dict'):
|
|
1918
|
+
return Dictionary(element_type)
|
|
1611
1919
|
else:
|
|
1612
1920
|
return None
|
|
1613
1921
|
|
|
@@ -1647,8 +1955,41 @@ class CSSLRuntime:
|
|
|
1647
1955
|
return {'__ref__': True, 'name': inner.value, 'value': self.get_module(inner.value)}
|
|
1648
1956
|
return {'__ref__': True, 'value': self._evaluate(inner)}
|
|
1649
1957
|
|
|
1958
|
+
# Handle action_block - execute and return last expression value
|
|
1959
|
+
if node.type == 'action_block':
|
|
1960
|
+
return self._evaluate_action_block(node)
|
|
1961
|
+
|
|
1650
1962
|
return None
|
|
1651
1963
|
|
|
1964
|
+
def _evaluate_action_block(self, node: ASTNode) -> Any:
|
|
1965
|
+
"""Evaluate an action block and return the last expression's value.
|
|
1966
|
+
|
|
1967
|
+
Used for: v <== { %version; } - captures %version at this moment
|
|
1968
|
+
|
|
1969
|
+
Returns the value of the last expression in the block.
|
|
1970
|
+
If the block contains a captured_ref (%name), that's what gets returned.
|
|
1971
|
+
"""
|
|
1972
|
+
last_value = None
|
|
1973
|
+
for child in node.children:
|
|
1974
|
+
if child.type == 'captured_ref':
|
|
1975
|
+
# Direct captured reference - return its value
|
|
1976
|
+
last_value = self._evaluate(child)
|
|
1977
|
+
elif child.type == 'expression':
|
|
1978
|
+
# Expression statement - evaluate and keep value
|
|
1979
|
+
last_value = self._evaluate(child.value if hasattr(child, 'value') else child)
|
|
1980
|
+
elif child.type == 'identifier':
|
|
1981
|
+
# Just an identifier - evaluate it
|
|
1982
|
+
last_value = self._evaluate(child)
|
|
1983
|
+
elif child.type in ('call', 'member_access', 'binary', 'unary'):
|
|
1984
|
+
# Expression types
|
|
1985
|
+
last_value = self._evaluate(child)
|
|
1986
|
+
else:
|
|
1987
|
+
# Execute other statements
|
|
1988
|
+
result = self._execute_node(child)
|
|
1989
|
+
if result is not None:
|
|
1990
|
+
last_value = result
|
|
1991
|
+
return last_value
|
|
1992
|
+
|
|
1652
1993
|
def _eval_binary(self, node: ASTNode) -> Any:
|
|
1653
1994
|
"""Evaluate binary operation with auto-casting support"""
|
|
1654
1995
|
op = node.value.get('op')
|
|
@@ -1818,12 +2159,15 @@ class CSSLRuntime:
|
|
|
1818
2159
|
return None
|
|
1819
2160
|
|
|
1820
2161
|
def _eval_call(self, node: ASTNode) -> Any:
|
|
1821
|
-
"""Evaluate function call"""
|
|
2162
|
+
"""Evaluate function call with optional named arguments"""
|
|
1822
2163
|
callee_node = node.value.get('callee')
|
|
1823
|
-
callee = self._evaluate(callee_node)
|
|
1824
2164
|
args = [self._evaluate(a) for a in node.value.get('args', [])]
|
|
1825
2165
|
|
|
1826
|
-
#
|
|
2166
|
+
# Evaluate named arguments (kwargs)
|
|
2167
|
+
kwargs_raw = node.value.get('kwargs', {})
|
|
2168
|
+
kwargs = {k: self._evaluate(v) for k, v in kwargs_raw.items()} if kwargs_raw else {}
|
|
2169
|
+
|
|
2170
|
+
# Get function name for injection check FIRST (before evaluating callee)
|
|
1827
2171
|
func_name = None
|
|
1828
2172
|
if isinstance(callee_node, ASTNode):
|
|
1829
2173
|
if callee_node.type == 'identifier':
|
|
@@ -1835,20 +2179,27 @@ class CSSLRuntime:
|
|
|
1835
2179
|
has_injections = func_name and func_name in self._function_injections
|
|
1836
2180
|
is_replaced = func_name and self._function_replaced.get(func_name, False)
|
|
1837
2181
|
|
|
1838
|
-
#
|
|
1839
|
-
|
|
2182
|
+
# If function is FULLY REPLACED (<<==), run injection and skip original
|
|
2183
|
+
# This allows creating new functions via infusion: new_func <<== { ... }
|
|
2184
|
+
if is_replaced:
|
|
1840
2185
|
self._execute_function_injections(func_name)
|
|
2186
|
+
return None # Injection ran, don't try to find original
|
|
1841
2187
|
|
|
1842
|
-
#
|
|
1843
|
-
|
|
1844
|
-
|
|
2188
|
+
# Now evaluate the callee (only if not replaced)
|
|
2189
|
+
callee = self._evaluate(callee_node)
|
|
2190
|
+
|
|
2191
|
+
# Execute added injections (+<<==) before original
|
|
2192
|
+
if has_injections and not is_replaced:
|
|
2193
|
+
self._execute_function_injections(func_name)
|
|
1845
2194
|
|
|
1846
2195
|
# Execute original function
|
|
1847
2196
|
if callable(callee):
|
|
2197
|
+
if kwargs:
|
|
2198
|
+
return callee(*args, **kwargs)
|
|
1848
2199
|
return callee(*args)
|
|
1849
2200
|
|
|
1850
2201
|
if isinstance(callee, ASTNode) and callee.type == 'function':
|
|
1851
|
-
return self._call_function(callee, args)
|
|
2202
|
+
return self._call_function(callee, args, kwargs)
|
|
1852
2203
|
|
|
1853
2204
|
callee_name = callee_node.value if isinstance(callee_node, ASTNode) and hasattr(callee_node, 'value') else str(callee_node)
|
|
1854
2205
|
raise CSSLRuntimeError(
|
|
@@ -1907,6 +2258,140 @@ class CSSLRuntime:
|
|
|
1907
2258
|
hint="Typed functions use format: FunctionName<Type>(args)"
|
|
1908
2259
|
)
|
|
1909
2260
|
|
|
2261
|
+
def _eval_new(self, node: ASTNode) -> CSSLInstance:
|
|
2262
|
+
"""Evaluate 'new ClassName(args)' expression.
|
|
2263
|
+
|
|
2264
|
+
Creates a new instance of a CSSL class and calls its constructor.
|
|
2265
|
+
"""
|
|
2266
|
+
class_name = node.value.get('class')
|
|
2267
|
+
args = [self._evaluate(arg) for arg in node.value.get('args', [])]
|
|
2268
|
+
kwargs = {k: self._evaluate(v) for k, v in node.value.get('kwargs', {}).items()}
|
|
2269
|
+
|
|
2270
|
+
# Get class definition from scope
|
|
2271
|
+
class_def = self.scope.get(class_name)
|
|
2272
|
+
if class_def is None:
|
|
2273
|
+
class_def = self.global_scope.get(class_name)
|
|
2274
|
+
|
|
2275
|
+
if class_def is None:
|
|
2276
|
+
raise CSSLRuntimeError(
|
|
2277
|
+
f"Class '{class_name}' not found",
|
|
2278
|
+
node.line,
|
|
2279
|
+
hint="Make sure the class is defined before instantiation"
|
|
2280
|
+
)
|
|
2281
|
+
|
|
2282
|
+
if not isinstance(class_def, CSSLClass):
|
|
2283
|
+
raise CSSLRuntimeError(
|
|
2284
|
+
f"'{class_name}' is not a class",
|
|
2285
|
+
node.line,
|
|
2286
|
+
hint=f"'{class_name}' is of type {type(class_def).__name__}"
|
|
2287
|
+
)
|
|
2288
|
+
|
|
2289
|
+
# Create new instance
|
|
2290
|
+
instance = CSSLInstance(class_def)
|
|
2291
|
+
|
|
2292
|
+
# Call constructor if defined
|
|
2293
|
+
if class_def.constructor:
|
|
2294
|
+
self._call_method(instance, class_def.constructor, args, kwargs)
|
|
2295
|
+
|
|
2296
|
+
return instance
|
|
2297
|
+
|
|
2298
|
+
def _eval_this_access(self, node: ASTNode) -> Any:
|
|
2299
|
+
"""Evaluate 'this->member' access.
|
|
2300
|
+
|
|
2301
|
+
Returns the value of a member from the current class instance.
|
|
2302
|
+
"""
|
|
2303
|
+
if self._current_instance is None:
|
|
2304
|
+
raise CSSLRuntimeError(
|
|
2305
|
+
"'this' used outside of class method context",
|
|
2306
|
+
node.line if hasattr(node, 'line') else 0,
|
|
2307
|
+
hint="'this->' can only be used inside class methods"
|
|
2308
|
+
)
|
|
2309
|
+
|
|
2310
|
+
member = node.value.get('member')
|
|
2311
|
+
|
|
2312
|
+
# Check if it's a chained access (this->a->b)
|
|
2313
|
+
if 'object' in node.value:
|
|
2314
|
+
# First evaluate the object part
|
|
2315
|
+
obj = self._evaluate(node.value.get('object'))
|
|
2316
|
+
if obj is None:
|
|
2317
|
+
return None
|
|
2318
|
+
if hasattr(obj, member):
|
|
2319
|
+
return getattr(obj, member)
|
|
2320
|
+
if isinstance(obj, dict):
|
|
2321
|
+
return obj.get(member)
|
|
2322
|
+
return None
|
|
2323
|
+
|
|
2324
|
+
# Direct this->member access
|
|
2325
|
+
instance = self._current_instance
|
|
2326
|
+
|
|
2327
|
+
# Check if it's a member variable
|
|
2328
|
+
if instance.has_member(member):
|
|
2329
|
+
return instance.get_member(member)
|
|
2330
|
+
|
|
2331
|
+
# Check if it's a method
|
|
2332
|
+
if instance.has_method(member):
|
|
2333
|
+
# Return a callable that will invoke the method with instance context
|
|
2334
|
+
method_node = instance.get_method(member)
|
|
2335
|
+
return lambda *args, **kwargs: self._call_method(instance, method_node, list(args), kwargs)
|
|
2336
|
+
|
|
2337
|
+
raise CSSLRuntimeError(
|
|
2338
|
+
f"'{instance._class.name}' has no member or method '{member}'",
|
|
2339
|
+
node.line if hasattr(node, 'line') else 0
|
|
2340
|
+
)
|
|
2341
|
+
|
|
2342
|
+
def _call_method(self, instance: CSSLInstance, method_node: ASTNode, args: list, kwargs: dict = None) -> Any:
|
|
2343
|
+
"""Call a method on an instance with 'this' context.
|
|
2344
|
+
|
|
2345
|
+
Sets up the instance as the current 'this' context and executes the method.
|
|
2346
|
+
"""
|
|
2347
|
+
kwargs = kwargs or {}
|
|
2348
|
+
func_info = method_node.value
|
|
2349
|
+
params = func_info.get('params', [])
|
|
2350
|
+
modifiers = func_info.get('modifiers', [])
|
|
2351
|
+
|
|
2352
|
+
# Check for undefined modifier
|
|
2353
|
+
is_undefined = 'undefined' in modifiers
|
|
2354
|
+
|
|
2355
|
+
# Create new scope for method
|
|
2356
|
+
new_scope = Scope(parent=self.scope)
|
|
2357
|
+
|
|
2358
|
+
# Bind parameters
|
|
2359
|
+
for i, param in enumerate(params):
|
|
2360
|
+
param_name = param['name'] if isinstance(param, dict) else param
|
|
2361
|
+
|
|
2362
|
+
if param_name in kwargs:
|
|
2363
|
+
new_scope.set(param_name, kwargs[param_name])
|
|
2364
|
+
elif i < len(args):
|
|
2365
|
+
new_scope.set(param_name, args[i])
|
|
2366
|
+
else:
|
|
2367
|
+
new_scope.set(param_name, None)
|
|
2368
|
+
|
|
2369
|
+
# Save current state
|
|
2370
|
+
old_scope = self.scope
|
|
2371
|
+
old_instance = self._current_instance
|
|
2372
|
+
|
|
2373
|
+
# Set up method context
|
|
2374
|
+
self.scope = new_scope
|
|
2375
|
+
self._current_instance = instance
|
|
2376
|
+
|
|
2377
|
+
try:
|
|
2378
|
+
for child in method_node.children:
|
|
2379
|
+
if not self._running:
|
|
2380
|
+
break
|
|
2381
|
+
self._execute_node(child)
|
|
2382
|
+
except CSSLReturn as ret:
|
|
2383
|
+
return ret.value
|
|
2384
|
+
except Exception as e:
|
|
2385
|
+
if is_undefined:
|
|
2386
|
+
return None
|
|
2387
|
+
raise
|
|
2388
|
+
finally:
|
|
2389
|
+
# Restore previous state
|
|
2390
|
+
self.scope = old_scope
|
|
2391
|
+
self._current_instance = old_instance
|
|
2392
|
+
|
|
2393
|
+
return None
|
|
2394
|
+
|
|
1910
2395
|
def _eval_member_access(self, node: ASTNode) -> Any:
|
|
1911
2396
|
"""Evaluate member access"""
|
|
1912
2397
|
obj = self._evaluate(node.value.get('object'))
|
|
@@ -1920,6 +2405,17 @@ class CSSLRuntime:
|
|
|
1920
2405
|
if isinstance(obj, Parameter) and member == 'return':
|
|
1921
2406
|
member = 'return_'
|
|
1922
2407
|
|
|
2408
|
+
# === CSSL CLASS INSTANCE METHODS ===
|
|
2409
|
+
if isinstance(obj, CSSLInstance):
|
|
2410
|
+
# Check for member variable
|
|
2411
|
+
if obj.has_member(member):
|
|
2412
|
+
return obj.get_member(member)
|
|
2413
|
+
# Check for method
|
|
2414
|
+
if obj.has_method(member):
|
|
2415
|
+
method_node = obj.get_method(member)
|
|
2416
|
+
return lambda *args, **kwargs: self._call_method(obj, method_node, list(args), kwargs)
|
|
2417
|
+
raise CSSLRuntimeError(f"'{obj._class.name}' has no member or method '{member}'")
|
|
2418
|
+
|
|
1923
2419
|
# === STRING METHODS ===
|
|
1924
2420
|
if isinstance(obj, str):
|
|
1925
2421
|
string_methods = self._get_string_method(obj, member)
|
|
@@ -2153,6 +2649,11 @@ class CSSLRuntime:
|
|
|
2153
2649
|
if obj is None:
|
|
2154
2650
|
return
|
|
2155
2651
|
|
|
2652
|
+
# Check for CSSLInstance - use set_member method
|
|
2653
|
+
if isinstance(obj, CSSLInstance):
|
|
2654
|
+
obj.set_member(member, value)
|
|
2655
|
+
return
|
|
2656
|
+
|
|
2156
2657
|
# Check for SharedObjectProxy - directly access underlying object
|
|
2157
2658
|
# This is more robust than relying on the proxy's __setattr__
|
|
2158
2659
|
if hasattr(obj, '_direct_object') and hasattr(obj, '_name'):
|
|
@@ -2256,22 +2757,107 @@ class CSSLRuntime:
|
|
|
2256
2757
|
# Also store in promoted globals for string interpolation
|
|
2257
2758
|
self._promoted_globals[base_name] = value
|
|
2258
2759
|
|
|
2760
|
+
# NEW: Scan for captured_ref nodes and capture their current values
|
|
2761
|
+
def _scan_and_capture_refs(self, node: ASTNode) -> Dict[str, Any]:
|
|
2762
|
+
"""Scan AST for %<name> captured references and capture their current values.
|
|
2763
|
+
|
|
2764
|
+
This is called at infusion registration time to capture values.
|
|
2765
|
+
Example: old_exit <<== { %exit(); } captures 'exit' at definition time.
|
|
2766
|
+
"""
|
|
2767
|
+
captured = {}
|
|
2768
|
+
|
|
2769
|
+
def scan_node(n):
|
|
2770
|
+
if not isinstance(n, ASTNode):
|
|
2771
|
+
return
|
|
2772
|
+
|
|
2773
|
+
# Found a captured_ref - capture its current value
|
|
2774
|
+
if n.type == 'captured_ref':
|
|
2775
|
+
name = n.value
|
|
2776
|
+
if name not in captured:
|
|
2777
|
+
# Try to find value - check multiple sources
|
|
2778
|
+
value = None
|
|
2779
|
+
|
|
2780
|
+
# 1. Check _original_functions first (for functions that were JUST replaced)
|
|
2781
|
+
if value is None:
|
|
2782
|
+
value = self._original_functions.get(name)
|
|
2783
|
+
|
|
2784
|
+
# 2. Check scope
|
|
2785
|
+
if value is None:
|
|
2786
|
+
value = self.scope.get(name)
|
|
2787
|
+
|
|
2788
|
+
# 3. Check global_scope
|
|
2789
|
+
if value is None:
|
|
2790
|
+
value = self.global_scope.get(name)
|
|
2791
|
+
|
|
2792
|
+
# 4. Check builtins (most common case for exit, print, etc.)
|
|
2793
|
+
if value is None:
|
|
2794
|
+
# For critical builtins like 'exit', create a direct wrapper
|
|
2795
|
+
# that captures the runtime reference to ensure correct behavior
|
|
2796
|
+
if name == 'exit':
|
|
2797
|
+
runtime = self # Capture runtime in closure
|
|
2798
|
+
value = lambda code=0, rt=runtime: rt.exit(code)
|
|
2799
|
+
else:
|
|
2800
|
+
value = getattr(self.builtins, f'builtin_{name}', None)
|
|
2801
|
+
|
|
2802
|
+
# 5. Check if there's a user-defined function in scope
|
|
2803
|
+
if value is None:
|
|
2804
|
+
# Look for function definitions
|
|
2805
|
+
func_def = self.global_scope.get(f'__func_{name}')
|
|
2806
|
+
if func_def is not None:
|
|
2807
|
+
value = func_def
|
|
2808
|
+
|
|
2809
|
+
# Only capture if we found something
|
|
2810
|
+
if value is not None:
|
|
2811
|
+
captured[name] = value
|
|
2812
|
+
|
|
2813
|
+
# Check call node's callee
|
|
2814
|
+
if n.type == 'call':
|
|
2815
|
+
callee = n.value.get('callee')
|
|
2816
|
+
if callee:
|
|
2817
|
+
scan_node(callee)
|
|
2818
|
+
for arg in n.value.get('args', []):
|
|
2819
|
+
scan_node(arg)
|
|
2820
|
+
|
|
2821
|
+
# Recurse into children
|
|
2822
|
+
if hasattr(n, 'children') and n.children:
|
|
2823
|
+
for child in n.children:
|
|
2824
|
+
scan_node(child)
|
|
2825
|
+
|
|
2826
|
+
# Check value dict for nested nodes
|
|
2827
|
+
if hasattr(n, 'value') and isinstance(n.value, dict):
|
|
2828
|
+
for key, val in n.value.items():
|
|
2829
|
+
if isinstance(val, ASTNode):
|
|
2830
|
+
scan_node(val)
|
|
2831
|
+
elif isinstance(val, list):
|
|
2832
|
+
for item in val:
|
|
2833
|
+
if isinstance(item, ASTNode):
|
|
2834
|
+
scan_node(item)
|
|
2835
|
+
|
|
2836
|
+
scan_node(node)
|
|
2837
|
+
return captured
|
|
2838
|
+
|
|
2259
2839
|
# NEW: Register permanent function injection
|
|
2260
2840
|
def register_function_injection(self, func_name: str, code_block: ASTNode):
|
|
2261
2841
|
"""Register code to be permanently injected into a function - NEW
|
|
2262
2842
|
|
|
2263
2843
|
Example: exit() <== { println("Cleanup..."); }
|
|
2264
2844
|
Makes every call to exit() also execute the injected code
|
|
2845
|
+
|
|
2846
|
+
Captures %<name> references at registration time.
|
|
2265
2847
|
"""
|
|
2848
|
+
# Scan for %<name> captured references and capture their current values
|
|
2849
|
+
captured_values = self._scan_and_capture_refs(code_block)
|
|
2850
|
+
|
|
2266
2851
|
if func_name not in self._function_injections:
|
|
2267
2852
|
self._function_injections[func_name] = []
|
|
2268
|
-
self._function_injections[func_name].append(code_block)
|
|
2853
|
+
self._function_injections[func_name].append((code_block, captured_values))
|
|
2269
2854
|
|
|
2270
2855
|
# NEW: Execute injected code for a function
|
|
2271
2856
|
def _execute_function_injections(self, func_name: str):
|
|
2272
2857
|
"""Execute all injected code blocks for a function - NEW
|
|
2273
2858
|
|
|
2274
2859
|
Includes protection against recursive execution to prevent doubled output.
|
|
2860
|
+
Uses captured values for %<name> references.
|
|
2275
2861
|
"""
|
|
2276
2862
|
# Prevent recursive injection execution (fixes doubled output bug)
|
|
2277
2863
|
if getattr(self, '_injection_executing', False):
|
|
@@ -2279,16 +2865,29 @@ class CSSLRuntime:
|
|
|
2279
2865
|
|
|
2280
2866
|
if func_name in self._function_injections:
|
|
2281
2867
|
self._injection_executing = True
|
|
2868
|
+
old_captured = self._current_captured_values.copy()
|
|
2282
2869
|
try:
|
|
2283
|
-
for
|
|
2870
|
+
for injection in self._function_injections[func_name]:
|
|
2871
|
+
# Handle both tuple format (code_block, captured_values) and legacy ASTNode format
|
|
2872
|
+
if isinstance(injection, tuple):
|
|
2873
|
+
code_block, captured_values = injection
|
|
2874
|
+
self._current_captured_values = captured_values
|
|
2875
|
+
else:
|
|
2876
|
+
code_block = injection
|
|
2877
|
+
self._current_captured_values = {}
|
|
2878
|
+
|
|
2284
2879
|
if isinstance(code_block, ASTNode):
|
|
2285
2880
|
if code_block.type == 'action_block':
|
|
2286
2881
|
for child in code_block.children:
|
|
2882
|
+
# Check if exit() was called
|
|
2883
|
+
if not self._running:
|
|
2884
|
+
break
|
|
2287
2885
|
self._execute_node(child)
|
|
2288
2886
|
else:
|
|
2289
2887
|
self._execute_node(code_block)
|
|
2290
2888
|
finally:
|
|
2291
2889
|
self._injection_executing = False
|
|
2890
|
+
self._current_captured_values = old_captured
|
|
2292
2891
|
|
|
2293
2892
|
# Output functions for builtins
|
|
2294
2893
|
def set_output_callback(self, callback: Callable[[str, str], None]):
|