IncludeCPP 3.6.0__py3-none-any.whl → 3.7.9__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, List, Dictionary
17
+ Stack, Vector, Array, DataSpace, OpenQuote, List, Dictionary, Map,
18
+ CSSLClass, CSSLInstance
18
19
  )
19
20
 
20
21
 
@@ -145,6 +146,7 @@ class CSSLRuntime:
145
146
  self._injection_captures: Dict[str, Dict[str, Any]] = {} # Captured %vars per injection
146
147
  self._current_captured_values: Dict[str, Any] = {} # Current captured values during injection execution
147
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
148
150
  self._running = False
149
151
  self._exit_code = 0
150
152
  self._output_callback = output_callback # Callback for console output (text, level)
@@ -386,6 +388,8 @@ class CSSLRuntime:
386
388
 
387
389
  if child.type == 'struct':
388
390
  self._exec_struct(child)
391
+ elif child.type == 'class':
392
+ self._exec_class(child)
389
393
  elif child.type == 'function':
390
394
  self._exec_function(child)
391
395
  elif child.type == 'global_assignment':
@@ -394,6 +398,12 @@ class CSSLRuntime:
394
398
  elif child.type == 'typed_declaration':
395
399
  # Handle typed variable declaration: type<T> varName = value;
396
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)
404
+ elif child.type == 'super_func':
405
+ # Super-function for .cssl-pl payload files (#$run, #$exec, #$printl)
406
+ result = self._exec_super_func(child)
397
407
  elif child.type in ('assignment', 'expression', 'inject', 'receive', 'flow',
398
408
  'if', 'while', 'for', 'c_for', 'foreach', 'switch', 'try'):
399
409
  result = self._execute_node(child)
@@ -661,6 +671,57 @@ class CSSLRuntime:
661
671
 
662
672
  return struct_data
663
673
 
674
+ def _exec_class(self, node: ASTNode) -> CSSLClass:
675
+ """Execute class definition - registers class in scope.
676
+
677
+ Parses class members and methods, creating a CSSLClass object
678
+ that can be instantiated with 'new'.
679
+ """
680
+ class_info = node.value
681
+ class_name = class_info.get('name')
682
+
683
+ members = {} # Member variable defaults/types
684
+ methods = {} # Method AST nodes
685
+ constructor = None
686
+
687
+ for child in node.children:
688
+ if child.type == 'function':
689
+ # This is a method
690
+ func_info = child.value
691
+ method_name = func_info.get('name')
692
+
693
+ if func_info.get('is_constructor') or method_name == class_name or method_name == '__init__':
694
+ constructor = child
695
+ else:
696
+ methods[method_name] = child
697
+
698
+ elif child.type == 'typed_declaration':
699
+ # This is a member variable
700
+ decl = child.value
701
+ member_name = decl.get('name')
702
+ member_type = decl.get('type')
703
+ member_value = decl.get('value')
704
+
705
+ # Store member info with type and optional default
706
+ members[member_name] = {
707
+ 'type': member_type,
708
+ 'default': self._evaluate(member_value) if member_value else None
709
+ }
710
+
711
+ # Create class definition object
712
+ class_def = CSSLClass(
713
+ name=class_name,
714
+ members=members,
715
+ methods=methods,
716
+ constructor=constructor
717
+ )
718
+
719
+ # Register class in scope
720
+ self.scope.set(class_name, class_def)
721
+ self.global_scope.set(class_name, class_def)
722
+
723
+ return class_def
724
+
664
725
  def _exec_function(self, node: ASTNode) -> Any:
665
726
  """Execute function definition - just registers it"""
666
727
  func_info = node.value
@@ -714,6 +775,8 @@ class CSSLRuntime:
714
775
  instance = List(element_type)
715
776
  elif type_name in ('dictionary', 'dict'):
716
777
  instance = Dictionary(element_type)
778
+ elif type_name == 'map':
779
+ instance = Map(element_type)
717
780
  else:
718
781
  # Default: evaluate the value or set to None
719
782
  instance = self._evaluate(value_node) if value_node else None
@@ -732,6 +795,110 @@ class CSSLRuntime:
732
795
  self.scope.set(var_name, instance)
733
796
  return instance
734
797
 
798
+ def _exec_instance_declaration(self, node: ASTNode) -> Any:
799
+ """Execute instance declaration: instance<"name"> varName;
800
+
801
+ Gets or creates a shared instance by name.
802
+ """
803
+ from ..cssl_bridge import _live_objects, SharedObjectProxy
804
+ decl = node.value
805
+ instance_name = decl.get('instance_name')
806
+ var_name = decl.get('name')
807
+ value_node = decl.get('value')
808
+
809
+ # Get existing shared instance
810
+ instance = None
811
+ if instance_name in _live_objects:
812
+ instance = SharedObjectProxy(instance_name, _live_objects[instance_name])
813
+ elif self.global_scope.has(f'${instance_name}'):
814
+ instance = self.global_scope.get(f'${instance_name}')
815
+
816
+ # If value is provided, use that and register as shared
817
+ if value_node:
818
+ instance = self._evaluate(value_node)
819
+ # Register in global scope for future access
820
+ self.global_scope.set(f'${instance_name}', instance)
821
+
822
+ # Store in scope
823
+ self.scope.set(var_name, instance)
824
+ return instance
825
+
826
+ def _exec_super_func(self, node: ASTNode) -> Any:
827
+ """Execute super-function for .cssl-pl payload files.
828
+
829
+ Super-functions are pre-execution hooks that run when payload() loads a file.
830
+
831
+ Supported super-functions:
832
+ #$run(funcName) - Call a function defined in the payload
833
+ #$exec(expression) - Execute an expression immediately
834
+ #$printl(message) - Print a message during load
835
+
836
+ Example .cssl-pl file:
837
+ void initDatabase() {
838
+ printl("DB initialized");
839
+ }
840
+
841
+ #$run(initDatabase); // Calls initDatabase when payload loads
842
+ #$printl("Payload loaded"); // Prints during load
843
+ """
844
+ super_info = node.value
845
+ super_name = super_info.get('name', '') # e.g., "#$run", "#$exec", "#$printl"
846
+ args = super_info.get('args', [])
847
+
848
+ # Extract the function name part (after #$)
849
+ if super_name.startswith('#$'):
850
+ func_type = super_name[2:] # "run", "exec", "printl"
851
+ else:
852
+ func_type = super_name
853
+
854
+ if func_type == 'run':
855
+ # #$run(funcName) - Call a function by name
856
+ if args:
857
+ func_ref = args[0]
858
+ if isinstance(func_ref, ASTNode):
859
+ if func_ref.type == 'identifier':
860
+ func_name = func_ref.value
861
+ elif func_ref.type == 'call':
862
+ # Direct call like #$run(setup())
863
+ return self._eval_call(func_ref)
864
+ else:
865
+ func_name = self._evaluate(func_ref)
866
+ else:
867
+ func_name = str(func_ref)
868
+
869
+ # Look up and call the function
870
+ func_node = self.scope.get(func_name)
871
+ if func_node and isinstance(func_node, ASTNode) and func_node.type == 'function':
872
+ return self._call_function(func_node, [])
873
+ else:
874
+ raise CSSLRuntimeError(f"#$run: Function '{func_name}' not found", node.line)
875
+
876
+ elif func_type == 'exec':
877
+ # #$exec(expression) - Execute an expression
878
+ if args:
879
+ return self._evaluate(args[0])
880
+
881
+ elif func_type == 'printl':
882
+ # #$printl(message) - Print a message
883
+ if args:
884
+ msg = self._evaluate(args[0])
885
+ print(str(msg))
886
+ self.output_buffer.append(str(msg))
887
+ return None
888
+
889
+ elif func_type == 'print':
890
+ # #$print(message) - Print without newline
891
+ if args:
892
+ msg = self._evaluate(args[0])
893
+ print(str(msg), end='')
894
+ self.output_buffer.append(str(msg))
895
+ return None
896
+
897
+ else:
898
+ raise CSSLRuntimeError(f"Unknown super-function: {super_name}", node.line)
899
+
900
+ return None
901
+
735
902
  def _exec_global_assignment(self, node: ASTNode) -> Any:
736
903
  """Execute global variable assignment: global Name = value
737
904
 
@@ -796,10 +963,20 @@ class CSSLRuntime:
796
963
 
797
964
  # Bind parameters - handle both positional and named arguments
798
965
  for i, param in enumerate(params):
799
- # Extract param name from dict format: {'name': 'a', 'type': 'int'}
800
- param_name = param['name'] if isinstance(param, dict) else param
801
-
802
- if param_name in kwargs:
966
+ # Extract param name and type from dict format: {'name': 'a', 'type': 'int'}
967
+ if isinstance(param, dict):
968
+ param_name = param['name']
969
+ param_type = param.get('type', '')
970
+ else:
971
+ param_name = param
972
+ param_type = ''
973
+
974
+ # Check if this is an 'open' parameter - receives all args as a list
975
+ if param_type == 'open' or param_name == 'Params':
976
+ # 'open Params' receives all arguments as a list
977
+ new_scope.set(param_name, list(args))
978
+ new_scope.set('Params', list(args)) # Also set 'Params' for OpenFind
979
+ elif param_name in kwargs:
803
980
  # Named argument takes priority
804
981
  new_scope.set(param_name, kwargs[param_name])
805
982
  elif i < len(args):
@@ -1009,8 +1186,20 @@ class CSSLRuntime:
1009
1186
  return None
1010
1187
 
1011
1188
  def _exec_return(self, node: ASTNode) -> Any:
1012
- """Execute return statement"""
1013
- value = self._evaluate(node.value) if node.value else None
1189
+ """Execute return statement.
1190
+
1191
+ Supports multiple return values for shuffled functions:
1192
+ return a, b, c; // Returns tuple (a, b, c)
1193
+ """
1194
+ if node.value is None:
1195
+ raise CSSLReturn(None)
1196
+
1197
+ # Check if this is a multiple return value
1198
+ if isinstance(node.value, dict) and node.value.get('multiple'):
1199
+ values = [self._evaluate(v) for v in node.value.get('values', [])]
1200
+ raise CSSLReturn(tuple(values))
1201
+
1202
+ value = self._evaluate(node.value)
1014
1203
  raise CSSLReturn(value)
1015
1204
 
1016
1205
  def _exec_break(self, node: ASTNode) -> Any:
@@ -1471,6 +1660,12 @@ class CSSLRuntime:
1471
1660
  name = target.value
1472
1661
  _live_objects[name] = final_value
1473
1662
  self.global_scope.set(f'${name}', SharedObjectProxy(name, final_value))
1663
+ elif target.type == 'instance_ref':
1664
+ # value ==> instance<"name"> - create/update shared object
1665
+ from ..cssl_bridge import _live_objects, SharedObjectProxy
1666
+ name = target.value
1667
+ _live_objects[name] = final_value
1668
+ self.global_scope.set(f'${name}', SharedObjectProxy(name, final_value))
1474
1669
  elif target.type == 'member_access':
1475
1670
  self._set_member(target, final_value)
1476
1671
 
@@ -1620,11 +1815,47 @@ class CSSLRuntime:
1620
1815
  self._set_member(target, value)
1621
1816
  elif target.type == 'index_access':
1622
1817
  self._set_index(target, value)
1818
+ elif target.type == 'this_access':
1819
+ # this->member = value
1820
+ if self._current_instance is None:
1821
+ raise CSSLRuntimeError("'this' used outside of class method context")
1822
+ member = target.value.get('member')
1823
+ self._current_instance.set_member(member, value)
1623
1824
  elif isinstance(target, str):
1624
1825
  self.scope.set(target, value)
1625
1826
 
1626
1827
  return value
1627
1828
 
1829
+ def _exec_tuple_assignment(self, node: ASTNode) -> Any:
1830
+ """Execute tuple unpacking assignment: a, b, c = shuffled_func()
1831
+
1832
+ Used with shuffled functions that return multiple values.
1833
+ """
1834
+ targets = node.value.get('targets', [])
1835
+ value = self._evaluate(node.value.get('value'))
1836
+
1837
+ # Convert value to list if it's a tuple or iterable
1838
+ if isinstance(value, (list, tuple)):
1839
+ values = list(value)
1840
+ elif hasattr(value, '__iter__') and not isinstance(value, (str, dict)):
1841
+ values = list(value)
1842
+ else:
1843
+ # Single value - assign to first target only
1844
+ values = [value]
1845
+
1846
+ # Assign values to targets
1847
+ for i, target in enumerate(targets):
1848
+ if i < len(values):
1849
+ var_name = target.value if isinstance(target, ASTNode) else target
1850
+ self.scope.set(var_name, values[i])
1851
+ else:
1852
+ # More targets than values - set to None
1853
+ var_name = target.value if isinstance(target, ASTNode) else target
1854
+ self.scope.set(var_name, None)
1855
+
1856
+ # Assignment statements don't produce a visible result
1857
+ return None
1858
+
1628
1859
  def _exec_expression(self, node: ASTNode) -> Any:
1629
1860
  """Execute expression statement"""
1630
1861
  return self._evaluate(node.value)
@@ -1678,9 +1909,12 @@ class CSSLRuntime:
1678
1909
 
1679
1910
  if node.type == 'literal':
1680
1911
  value = node.value
1681
- # NEW: String interpolation - replace <variable> with scope values
1682
- if isinstance(value, str) and '<' in value and '>' in value:
1683
- value = self._interpolate_string(value)
1912
+ # String interpolation - replace {var} or <var> with scope values
1913
+ if isinstance(value, str):
1914
+ has_fstring = '{' in value and '}' in value
1915
+ has_legacy = '<' in value and '>' in value
1916
+ if has_fstring or has_legacy:
1917
+ value = self._interpolate_string(value)
1684
1918
  return value
1685
1919
 
1686
1920
  # NEW: Type literals (list, dict) - create empty instances
@@ -1767,10 +2001,34 @@ class CSSLRuntime:
1767
2001
  return value
1768
2002
  raise CSSLRuntimeError(f"Captured reference '%{name}' not found.")
1769
2003
 
2004
+ if node.type == 'instance_ref':
2005
+ # instance<"name"> - get shared instance by name
2006
+ # Works like $name but with explicit syntax
2007
+ from ..cssl_bridge import _live_objects, SharedObjectProxy
2008
+ name = node.value
2009
+ if name in _live_objects:
2010
+ return SharedObjectProxy(name, _live_objects[name])
2011
+ # Check if stored in runtime's scope
2012
+ scoped_val = self.global_scope.get(f'${name}')
2013
+ if scoped_val is not None:
2014
+ return scoped_val
2015
+ # Return None if instance doesn't exist (can be created via ==>)
2016
+ return None
2017
+
2018
+ if node.type == 'new':
2019
+ # Create new instance of a class: new ClassName(args)
2020
+ return self._eval_new(node)
2021
+
2022
+ if node.type == 'this_access':
2023
+ # this->member access
2024
+ return self._eval_this_access(node)
2025
+
1770
2026
  if node.type == 'type_instantiation':
1771
- # Create new instance of a type: stack<string>, vector<int>, etc.
2027
+ # Create new instance of a type: stack<string>, vector<int>, map<K,V>, etc.
1772
2028
  type_name = node.value.get('type')
1773
2029
  element_type = node.value.get('element_type', 'dynamic')
2030
+ value_type = node.value.get('value_type') # For map<K, V>
2031
+ init_values = node.value.get('init_values') # For inline init: map<K,V>{...}
1774
2032
 
1775
2033
  if type_name == 'stack':
1776
2034
  return Stack(element_type)
@@ -1794,6 +2052,15 @@ class CSSLRuntime:
1794
2052
  return List(element_type)
1795
2053
  elif type_name in ('dictionary', 'dict'):
1796
2054
  return Dictionary(element_type)
2055
+ elif type_name == 'map':
2056
+ # Create Map with key_type and value_type
2057
+ m = Map(element_type, value_type or 'dynamic')
2058
+ # If inline initialization provided, populate the map
2059
+ if init_values:
2060
+ for key, value_node in init_values.items():
2061
+ value = self._evaluate(value_node)
2062
+ m.insert(key, value)
2063
+ return m
1797
2064
  else:
1798
2065
  return None
1799
2066
 
@@ -2136,6 +2403,140 @@ class CSSLRuntime:
2136
2403
  hint="Typed functions use format: FunctionName<Type>(args)"
2137
2404
  )
2138
2405
 
2406
+ def _eval_new(self, node: ASTNode) -> CSSLInstance:
2407
+ """Evaluate 'new ClassName(args)' expression.
2408
+
2409
+ Creates a new instance of a CSSL class and calls its constructor.
2410
+ """
2411
+ class_name = node.value.get('class')
2412
+ args = [self._evaluate(arg) for arg in node.value.get('args', [])]
2413
+ kwargs = {k: self._evaluate(v) for k, v in node.value.get('kwargs', {}).items()}
2414
+
2415
+ # Get class definition from scope
2416
+ class_def = self.scope.get(class_name)
2417
+ if class_def is None:
2418
+ class_def = self.global_scope.get(class_name)
2419
+
2420
+ if class_def is None:
2421
+ raise CSSLRuntimeError(
2422
+ f"Class '{class_name}' not found",
2423
+ node.line,
2424
+ hint="Make sure the class is defined before instantiation"
2425
+ )
2426
+
2427
+ if not isinstance(class_def, CSSLClass):
2428
+ raise CSSLRuntimeError(
2429
+ f"'{class_name}' is not a class",
2430
+ node.line,
2431
+ hint=f"'{class_name}' is of type {type(class_def).__name__}"
2432
+ )
2433
+
2434
+ # Create new instance
2435
+ instance = CSSLInstance(class_def)
2436
+
2437
+ # Call constructor if defined
2438
+ if class_def.constructor:
2439
+ self._call_method(instance, class_def.constructor, args, kwargs)
2440
+
2441
+ return instance
2442
+
2443
+ def _eval_this_access(self, node: ASTNode) -> Any:
2444
+ """Evaluate 'this->member' access.
2445
+
2446
+ Returns the value of a member from the current class instance.
2447
+ """
2448
+ if self._current_instance is None:
2449
+ raise CSSLRuntimeError(
2450
+ "'this' used outside of class method context",
2451
+ node.line if hasattr(node, 'line') else 0,
2452
+ hint="'this->' can only be used inside class methods"
2453
+ )
2454
+
2455
+ member = node.value.get('member')
2456
+
2457
+ # Check if it's a chained access (this->a->b)
2458
+ if 'object' in node.value:
2459
+ # First evaluate the object part
2460
+ obj = self._evaluate(node.value.get('object'))
2461
+ if obj is None:
2462
+ return None
2463
+ if hasattr(obj, member):
2464
+ return getattr(obj, member)
2465
+ if isinstance(obj, dict):
2466
+ return obj.get(member)
2467
+ return None
2468
+
2469
+ # Direct this->member access
2470
+ instance = self._current_instance
2471
+
2472
+ # Check if it's a member variable
2473
+ if instance.has_member(member):
2474
+ return instance.get_member(member)
2475
+
2476
+ # Check if it's a method
2477
+ if instance.has_method(member):
2478
+ # Return a callable that will invoke the method with instance context
2479
+ method_node = instance.get_method(member)
2480
+ return lambda *args, **kwargs: self._call_method(instance, method_node, list(args), kwargs)
2481
+
2482
+ raise CSSLRuntimeError(
2483
+ f"'{instance._class.name}' has no member or method '{member}'",
2484
+ node.line if hasattr(node, 'line') else 0
2485
+ )
2486
+
2487
+ def _call_method(self, instance: CSSLInstance, method_node: ASTNode, args: list, kwargs: dict = None) -> Any:
2488
+ """Call a method on an instance with 'this' context.
2489
+
2490
+ Sets up the instance as the current 'this' context and executes the method.
2491
+ """
2492
+ kwargs = kwargs or {}
2493
+ func_info = method_node.value
2494
+ params = func_info.get('params', [])
2495
+ modifiers = func_info.get('modifiers', [])
2496
+
2497
+ # Check for undefined modifier
2498
+ is_undefined = 'undefined' in modifiers
2499
+
2500
+ # Create new scope for method
2501
+ new_scope = Scope(parent=self.scope)
2502
+
2503
+ # Bind parameters
2504
+ for i, param in enumerate(params):
2505
+ param_name = param['name'] if isinstance(param, dict) else param
2506
+
2507
+ if param_name in kwargs:
2508
+ new_scope.set(param_name, kwargs[param_name])
2509
+ elif i < len(args):
2510
+ new_scope.set(param_name, args[i])
2511
+ else:
2512
+ new_scope.set(param_name, None)
2513
+
2514
+ # Save current state
2515
+ old_scope = self.scope
2516
+ old_instance = self._current_instance
2517
+
2518
+ # Set up method context
2519
+ self.scope = new_scope
2520
+ self._current_instance = instance
2521
+
2522
+ try:
2523
+ for child in method_node.children:
2524
+ if not self._running:
2525
+ break
2526
+ self._execute_node(child)
2527
+ except CSSLReturn as ret:
2528
+ return ret.value
2529
+ except Exception as e:
2530
+ if is_undefined:
2531
+ return None
2532
+ raise
2533
+ finally:
2534
+ # Restore previous state
2535
+ self.scope = old_scope
2536
+ self._current_instance = old_instance
2537
+
2538
+ return None
2539
+
2139
2540
  def _eval_member_access(self, node: ASTNode) -> Any:
2140
2541
  """Evaluate member access"""
2141
2542
  obj = self._evaluate(node.value.get('object'))
@@ -2149,6 +2550,17 @@ class CSSLRuntime:
2149
2550
  if isinstance(obj, Parameter) and member == 'return':
2150
2551
  member = 'return_'
2151
2552
 
2553
+ # === CSSL CLASS INSTANCE METHODS ===
2554
+ if isinstance(obj, CSSLInstance):
2555
+ # Check for member variable
2556
+ if obj.has_member(member):
2557
+ return obj.get_member(member)
2558
+ # Check for method
2559
+ if obj.has_method(member):
2560
+ method_node = obj.get_method(member)
2561
+ return lambda *args, **kwargs: self._call_method(obj, method_node, list(args), kwargs)
2562
+ raise CSSLRuntimeError(f"'{obj._class.name}' has no member or method '{member}'")
2563
+
2152
2564
  # === STRING METHODS ===
2153
2565
  if isinstance(obj, str):
2154
2566
  string_methods = self._get_string_method(obj, member)
@@ -2382,6 +2794,11 @@ class CSSLRuntime:
2382
2794
  if obj is None:
2383
2795
  return
2384
2796
 
2797
+ # Check for CSSLInstance - use set_member method
2798
+ if isinstance(obj, CSSLInstance):
2799
+ obj.set_member(member, value)
2800
+ return
2801
+
2385
2802
  # Check for SharedObjectProxy - directly access underlying object
2386
2803
  # This is more robust than relying on the proxy's __setattr__
2387
2804
  if hasattr(obj, '_direct_object') and hasattr(obj, '_name'):
@@ -2442,14 +2859,24 @@ class CSSLRuntime:
2442
2859
  elif isinstance(obj, dict):
2443
2860
  obj[final_attr] = value
2444
2861
 
2445
- # NEW: String interpolation
2862
+ # String interpolation (supports both <var> and {var} syntax)
2446
2863
  def _interpolate_string(self, string: str) -> str:
2447
- """Replace <variable> placeholders with values from scope - NEW
2864
+ """Replace {variable} and <variable> placeholders with values from scope.
2865
+
2866
+ Both syntaxes are supported (variables only, not expressions):
2867
+ "Hello {name}!" -> "Hello John!" (f-string style)
2868
+ "Hello <name>!" -> "Hello John!" (legacy CSSL style)
2448
2869
 
2449
- Example: "Hello <name>!" becomes "Hello John!" if name = "John"
2870
+ Examples:
2871
+ string name = "Alice";
2872
+ int age = 30;
2873
+ printl("Hello {name}, you are {age} years old!");
2874
+ printl("Hello <name>, you are <age> years old!");
2875
+
2876
+ Note: Only simple variable names are supported, not expressions.
2877
+ Use string concatenation for complex expressions.
2450
2878
  """
2451
2879
  import re
2452
- pattern = r'<([A-Za-z_][A-Za-z0-9_]*)>'
2453
2880
 
2454
2881
  def replacer(match):
2455
2882
  var_name = match.group(1)
@@ -2461,10 +2888,23 @@ class CSSLRuntime:
2461
2888
  # Try modules
2462
2889
  if value is None:
2463
2890
  value = self._modules.get(var_name)
2891
+ # Try global scope
2892
+ if value is None:
2893
+ value = self.global_scope.get(var_name)
2464
2894
  # Return string representation or empty string if None
2465
2895
  return str(value) if value is not None else ''
2466
2896
 
2467
- return re.sub(pattern, replacer, string)
2897
+ # Support both {var} and <var> syntax - simple variable names only
2898
+ # Pattern: {identifier} or <identifier>
2899
+ patterns = [
2900
+ r'\{([A-Za-z_][A-Za-z0-9_]*)\}', # {name} f-string style
2901
+ r'<([A-Za-z_][A-Za-z0-9_]*)>', # <name> legacy CSSL style
2902
+ ]
2903
+
2904
+ result = string
2905
+ for pattern in patterns:
2906
+ result = re.sub(pattern, replacer, result)
2907
+ return result
2468
2908
 
2469
2909
  # NEW: Promote variable to global scope via global()
2470
2910
  def promote_to_global(self, s_ref_name: str):