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.
@@ -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[ASTNode]] = {} # NEW: Permanent function injections
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
- main_func = self.scope.get('main')
403
- if main_func and isinstance(main_func, ASTNode) and main_func.type == 'function':
404
- try:
405
- result = self._call_function(main_func, [])
406
- except CSSLReturn as ret:
407
- result = ret.value
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 string and dict formats
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
- if i < len(args):
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 True:
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
- # Evaluate source
1224
- source = self._evaluate(source_node)
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
- self._function_injections[func_name] = [code_block]
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
- # For now, this removes all injections for the function
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
- self._function_injections[func_name] = []
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
- value = self.scope.get(node.value)
1550
- if value is None and self.builtins.has_function(node.value):
1551
- return self.builtins.get_function(node.value)
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
- # Get function name for injection check
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
- # Execute injected code first (if any)
1839
- if has_injections:
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
- # If function is REPLACED (<<==), skip original body execution
1843
- if is_replaced:
1844
- return None # Injection already ran, don't run original
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 code_block in self._function_injections[func_name]:
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]):