IncludeCPP 3.7.2__tar.gz → 3.7.5__tar.gz

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.
Files changed (57) hide show
  1. {includecpp-3.7.2 → includecpp-3.7.5}/IncludeCPP.egg-info/PKG-INFO +1 -1
  2. {includecpp-3.7.2 → includecpp-3.7.5}/PKG-INFO +1 -1
  3. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/__init__.py +1 -1
  4. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/core/cssl/cssl_parser.py +132 -8
  5. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/core/cssl/cssl_runtime.py +139 -13
  6. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/core/cssl_bridge.py +477 -48
  7. {includecpp-3.7.2 → includecpp-3.7.5}/pyproject.toml +1 -1
  8. {includecpp-3.7.2 → includecpp-3.7.5}/setup.py +1 -1
  9. {includecpp-3.7.2 → includecpp-3.7.5}/IncludeCPP.egg-info/SOURCES.txt +0 -0
  10. {includecpp-3.7.2 → includecpp-3.7.5}/IncludeCPP.egg-info/dependency_links.txt +0 -0
  11. {includecpp-3.7.2 → includecpp-3.7.5}/IncludeCPP.egg-info/entry_points.txt +0 -0
  12. {includecpp-3.7.2 → includecpp-3.7.5}/IncludeCPP.egg-info/requires.txt +0 -0
  13. {includecpp-3.7.2 → includecpp-3.7.5}/IncludeCPP.egg-info/top_level.txt +0 -0
  14. {includecpp-3.7.2 → includecpp-3.7.5}/LICENSE +0 -0
  15. {includecpp-3.7.2 → includecpp-3.7.5}/MANIFEST.in +0 -0
  16. {includecpp-3.7.2 → includecpp-3.7.5}/README.md +0 -0
  17. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/__init__.pyi +0 -0
  18. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/__main__.py +0 -0
  19. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/cli/__init__.py +0 -0
  20. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/cli/commands.py +0 -0
  21. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/cli/config_parser.py +0 -0
  22. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/core/__init__.py +0 -0
  23. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/core/ai_integration.py +0 -0
  24. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/core/build_manager.py +0 -0
  25. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/core/cpp_api.py +0 -0
  26. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/core/cpp_api.pyi +0 -0
  27. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/core/cppy_converter.py +0 -0
  28. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/core/cssl/CSSL_DOCUMENTATION.md +0 -0
  29. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/core/cssl/__init__.py +0 -0
  30. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/core/cssl/cssl_builtins.py +0 -0
  31. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/core/cssl/cssl_builtins.pyi +0 -0
  32. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/core/cssl/cssl_events.py +0 -0
  33. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/core/cssl/cssl_modules.py +0 -0
  34. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/core/cssl/cssl_syntax.py +0 -0
  35. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/core/cssl/cssl_types.py +0 -0
  36. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/core/cssl_bridge.pyi +0 -0
  37. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/core/error_catalog.py +0 -0
  38. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/core/error_formatter.py +0 -0
  39. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/core/exceptions.py +0 -0
  40. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/core/path_discovery.py +0 -0
  41. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/core/project_ui.py +0 -0
  42. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/core/settings_ui.py +0 -0
  43. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/generator/__init__.py +0 -0
  44. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/generator/parser.cpp +0 -0
  45. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/generator/parser.h +0 -0
  46. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/generator/type_resolver.cpp +0 -0
  47. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/generator/type_resolver.h +0 -0
  48. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/py.typed +0 -0
  49. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/templates/cpp.proj.template +0 -0
  50. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/vscode/__init__.py +0 -0
  51. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/vscode/cssl/__init__.py +0 -0
  52. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/vscode/cssl/language-configuration.json +0 -0
  53. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/vscode/cssl/package.json +0 -0
  54. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/vscode/cssl/snippets/cssl.snippets.json +0 -0
  55. {includecpp-3.7.2 → includecpp-3.7.5}/includecpp/vscode/cssl/syntaxes/cssl.tmLanguage.json +0 -0
  56. {includecpp-3.7.2 → includecpp-3.7.5}/requirements.txt +0 -0
  57. {includecpp-3.7.2 → includecpp-3.7.5}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: IncludeCPP
3
- Version: 3.7.2
3
+ Version: 3.7.5
4
4
  Summary: Professional C++ Python bindings with type-generic templates, pystubs and native threading
5
5
  Home-page: https://github.com/liliassg/IncludeCPP
6
6
  Author: Lilias Hatterscheidt
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: IncludeCPP
3
- Version: 3.7.2
3
+ Version: 3.7.5
4
4
  Summary: Professional C++ Python bindings with type-generic templates, pystubs and native threading
5
5
  Home-page: https://github.com/liliassg/IncludeCPP
6
6
  Author: Lilias Hatterscheidt
@@ -2,7 +2,7 @@ from .core.cpp_api import CppApi
2
2
  from .core import cssl_bridge as CSSL
3
3
  import warnings
4
4
 
5
- __version__ = "3.7.2"
5
+ __version__ = "3.7.5"
6
6
  __all__ = ["CppApi", "CSSL"]
7
7
 
8
8
  # Module-level cache for C++ modules
@@ -108,6 +108,8 @@ class TokenType(Enum):
108
108
  COMMENT = auto()
109
109
  NEWLINE = auto()
110
110
  EOF = auto()
111
+ # Super-functions for .cssl-pl payload files (v3.8.0)
112
+ SUPER_FUNC = auto() # #$run(), #$exec(), #$printl() - pre-execution hooks
111
113
 
112
114
 
113
115
  KEYWORDS = {
@@ -212,9 +214,14 @@ class CSSLLexer:
212
214
 
213
215
  char = self.source[self.pos]
214
216
 
215
- # Comments: both # and // style
217
+ # Super-functions (#$) or Comments (# and // style)
216
218
  if char == '#':
217
- self._skip_comment()
219
+ if self._peek(1) == '$':
220
+ # Super-function: #$run(), #$exec(), #$printl()
221
+ self._read_super_function()
222
+ else:
223
+ # Regular comment
224
+ self._skip_comment()
218
225
  elif char == '/' and self._peek(1) == '/':
219
226
  # C-style // comment - NEW
220
227
  self._skip_comment()
@@ -446,6 +453,31 @@ class CSSLLexer:
446
453
  else:
447
454
  self._add_token(TokenType.IDENTIFIER, value)
448
455
 
456
+ def _read_super_function(self):
457
+ """Read #$<name>(...) super-function call for .cssl-pl payloads.
458
+
459
+ Super-functions are pre-execution hooks that run when a payload is loaded.
460
+ Valid super-functions: #$run(), #$exec(), #$printl()
461
+
462
+ Syntax:
463
+ #$run(initFunction); // Call a function at load time
464
+ #$exec(setup()); // Execute expression at load time
465
+ #$printl("Payload loaded"); // Print at load time
466
+ """
467
+ start = self.pos
468
+ self._advance() # skip '#'
469
+ self._advance() # skip '$'
470
+
471
+ # Read the super-function name (run, exec, printl, etc.)
472
+ name_start = self.pos
473
+ while self.pos < len(self.source) and (self.source[self.pos].isalnum() or self.source[self.pos] == '_'):
474
+ self._advance()
475
+ func_name = self.source[name_start:self.pos]
476
+
477
+ # Store as #$<name> token value
478
+ value = f'#${func_name}'
479
+ self._add_token(TokenType.SUPER_FUNC, value)
480
+
449
481
  def _read_self_ref(self):
450
482
  """Read s@<name> or s@<name>.<member>... self-reference"""
451
483
  start = self.pos
@@ -1377,6 +1409,9 @@ class CSSLParser:
1377
1409
  elif self._looks_like_function_declaration():
1378
1410
  # Nested typed function (e.g., void Level2() { ... })
1379
1411
  return self._parse_typed_function()
1412
+ elif self._check(TokenType.SUPER_FUNC):
1413
+ # Super-function for .cssl-pl payload files
1414
+ return self._parse_super_function()
1380
1415
  elif (self._check(TokenType.IDENTIFIER) or self._check(TokenType.AT) or
1381
1416
  self._check(TokenType.CAPTURED_REF) or self._check(TokenType.SHARED_REF) or
1382
1417
  self._check(TokenType.GLOBAL_REF) or self._check(TokenType.SELF_REF) or
@@ -1594,23 +1629,42 @@ class CSSLParser:
1594
1629
  return ASTNode('c_for_update', value={'var': var_name, 'op': 'none'})
1595
1630
 
1596
1631
  def _parse_python_style_for(self) -> ASTNode:
1597
- """Parse Python-style for loop: for (i in range(...)) { }
1632
+ """Parse Python-style for loop: for (i in range(...)) { } or for (item in collection) { }
1598
1633
 
1599
1634
  Supports:
1600
1635
  for (i in range(n)) { } - 0 to n-1
1601
1636
  for (i in range(start, end)) { } - start to end-1
1602
1637
  for (i in range(start, end, step)) { }
1638
+ for (item in collection) { } - iterate over list/vector
1639
+ for (item in @global_collection) { } - iterate over global
1603
1640
  """
1604
1641
  var_name = self._advance().value
1605
1642
  self._expect(TokenType.KEYWORD) # 'in'
1606
1643
 
1607
- # 'range' can be keyword or identifier
1644
+ # Check if this is range() or collection iteration
1645
+ is_range = False
1608
1646
  if self._check(TokenType.KEYWORD) and self._peek().value == 'range':
1609
1647
  self._advance() # consume 'range' keyword
1648
+ is_range = True
1610
1649
  elif self._check(TokenType.IDENTIFIER) and self._peek().value == 'range':
1611
1650
  self._advance() # consume 'range' identifier
1612
- else:
1613
- self.error(f"Expected 'range', got {self._peek().value}")
1651
+ is_range = True
1652
+
1653
+ # If not range, parse as collection iteration
1654
+ if not is_range:
1655
+ iterable = self._parse_expression()
1656
+ self._expect(TokenType.PAREN_END)
1657
+
1658
+ node = ASTNode('foreach', value={'var': var_name, 'iterable': iterable}, children=[])
1659
+ self._expect(TokenType.BLOCK_START)
1660
+
1661
+ while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
1662
+ stmt = self._parse_statement()
1663
+ if stmt:
1664
+ node.children.append(stmt)
1665
+
1666
+ self._expect(TokenType.BLOCK_END)
1667
+ return node
1614
1668
 
1615
1669
  self._expect(TokenType.PAREN_START)
1616
1670
  first_arg = self._parse_expression()
@@ -1728,6 +1782,31 @@ class CSSLParser:
1728
1782
  self._match(TokenType.SEMICOLON)
1729
1783
  return ASTNode('return', value=value)
1730
1784
 
1785
+ def _parse_super_function(self) -> ASTNode:
1786
+ """Parse super-function for .cssl-pl payload files.
1787
+
1788
+ Syntax:
1789
+ #$run(initFunction); // Call function at load time
1790
+ #$exec(setup()); // Execute expression at load time
1791
+ #$printl("Payload loaded"); // Print at load time
1792
+
1793
+ These are pre-execution hooks that run when payload() loads the file.
1794
+ """
1795
+ token = self._advance() # Get the SUPER_FUNC token
1796
+ super_name = token.value # e.g., "#$run", "#$exec", "#$printl"
1797
+
1798
+ # Parse the arguments
1799
+ self._expect(TokenType.PAREN_START)
1800
+ args = []
1801
+ if not self._check(TokenType.PAREN_END):
1802
+ args.append(self._parse_expression())
1803
+ while self._match(TokenType.COMMA):
1804
+ args.append(self._parse_expression())
1805
+ self._expect(TokenType.PAREN_END)
1806
+ self._match(TokenType.SEMICOLON)
1807
+
1808
+ return ASTNode('super_func', value={'name': super_name, 'args': args})
1809
+
1731
1810
  def _parse_try(self) -> ASTNode:
1732
1811
  node = ASTNode('try', children=[])
1733
1812
 
@@ -2335,17 +2414,62 @@ class CSSLParser:
2335
2414
  self._expect(TokenType.COMPARE_GT) # consume >
2336
2415
  return ASTNode('instance_ref', value=instance_name)
2337
2416
 
2338
- # Check for type generic instantiation: stack<string>, vector<int>, etc.
2417
+ # Check for type generic instantiation: stack<string>, vector<int>, map<string, int>, etc.
2339
2418
  # This creates a new instance of the type with the specified element type
2340
2419
  if name in TYPE_GENERICS and self._check(TokenType.COMPARE_LT):
2341
2420
  self._advance() # consume <
2342
2421
  element_type = 'dynamic'
2422
+ value_type = None # For map<K, V>
2423
+
2343
2424
  if self._check(TokenType.KEYWORD) or self._check(TokenType.IDENTIFIER):
2344
2425
  element_type = self._advance().value
2426
+
2427
+ # Check for second type parameter (map<K, V>)
2428
+ if name == 'map' and self._check(TokenType.COMMA):
2429
+ self._advance() # consume ,
2430
+ if self._check(TokenType.KEYWORD) or self._check(TokenType.IDENTIFIER):
2431
+ value_type = self._advance().value
2432
+ else:
2433
+ value_type = 'dynamic'
2434
+
2345
2435
  self._expect(TokenType.COMPARE_GT) # consume >
2436
+
2437
+ # Check for inline initialization: map<K,V>{"key": "value", ...}
2438
+ init_values = None
2439
+ if self._check(TokenType.BLOCK_START):
2440
+ self._advance() # consume {
2441
+ init_values = {}
2442
+
2443
+ while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
2444
+ # Parse key
2445
+ if self._check(TokenType.STRING):
2446
+ key = self._advance().value
2447
+ elif self._check(TokenType.IDENTIFIER):
2448
+ key = self._advance().value
2449
+ else:
2450
+ key = str(self._parse_expression().value) if hasattr(self._parse_expression(), 'value') else 'key'
2451
+
2452
+ # Expect : or =
2453
+ if self._check(TokenType.COLON):
2454
+ self._advance()
2455
+ elif self._check(TokenType.EQUALS):
2456
+ self._advance()
2457
+
2458
+ # Parse value
2459
+ value = self._parse_expression()
2460
+ init_values[key] = value
2461
+
2462
+ # Optional comma
2463
+ if self._check(TokenType.COMMA):
2464
+ self._advance()
2465
+
2466
+ self._expect(TokenType.BLOCK_END) # consume }
2467
+
2346
2468
  return ASTNode('type_instantiation', value={
2347
2469
  'type': name,
2348
- 'element_type': element_type
2470
+ 'element_type': element_type,
2471
+ 'value_type': value_type,
2472
+ 'init_values': init_values
2349
2473
  })
2350
2474
 
2351
2475
  # Check for type-parameterized function call: OpenFind<string>(0)
@@ -401,6 +401,9 @@ class CSSLRuntime:
401
401
  elif child.type == 'instance_declaration':
402
402
  # Handle instance declaration: instance<"name"> varName;
403
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)
404
407
  elif child.type in ('assignment', 'expression', 'inject', 'receive', 'flow',
405
408
  'if', 'while', 'for', 'c_for', 'foreach', 'switch', 'try'):
406
409
  result = self._execute_node(child)
@@ -820,6 +823,82 @@ class CSSLRuntime:
820
823
  self.scope.set(var_name, instance)
821
824
  return instance
822
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
+
823
902
  def _exec_global_assignment(self, node: ASTNode) -> Any:
824
903
  """Execute global variable assignment: global Name = value
825
904
 
@@ -884,10 +963,20 @@ class CSSLRuntime:
884
963
 
885
964
  # Bind parameters - handle both positional and named arguments
886
965
  for i, param in enumerate(params):
887
- # Extract param name from dict format: {'name': 'a', 'type': 'int'}
888
- param_name = param['name'] if isinstance(param, dict) else param
889
-
890
- 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:
891
980
  # Named argument takes priority
892
981
  new_scope.set(param_name, kwargs[param_name])
893
982
  elif i < len(args):
@@ -1778,9 +1867,12 @@ class CSSLRuntime:
1778
1867
 
1779
1868
  if node.type == 'literal':
1780
1869
  value = node.value
1781
- # NEW: String interpolation - replace <variable> with scope values
1782
- if isinstance(value, str) and '<' in value and '>' in value:
1783
- value = self._interpolate_string(value)
1870
+ # String interpolation - replace {var} or <var> with scope values
1871
+ if isinstance(value, str):
1872
+ has_fstring = '{' in value and '}' in value
1873
+ has_legacy = '<' in value and '>' in value
1874
+ if has_fstring or has_legacy:
1875
+ value = self._interpolate_string(value)
1784
1876
  return value
1785
1877
 
1786
1878
  # NEW: Type literals (list, dict) - create empty instances
@@ -1890,9 +1982,11 @@ class CSSLRuntime:
1890
1982
  return self._eval_this_access(node)
1891
1983
 
1892
1984
  if node.type == 'type_instantiation':
1893
- # Create new instance of a type: stack<string>, vector<int>, etc.
1985
+ # Create new instance of a type: stack<string>, vector<int>, map<K,V>, etc.
1894
1986
  type_name = node.value.get('type')
1895
1987
  element_type = node.value.get('element_type', 'dynamic')
1988
+ value_type = node.value.get('value_type') # For map<K, V>
1989
+ init_values = node.value.get('init_values') # For inline init: map<K,V>{...}
1896
1990
 
1897
1991
  if type_name == 'stack':
1898
1992
  return Stack(element_type)
@@ -1916,6 +2010,15 @@ class CSSLRuntime:
1916
2010
  return List(element_type)
1917
2011
  elif type_name in ('dictionary', 'dict'):
1918
2012
  return Dictionary(element_type)
2013
+ elif type_name == 'map':
2014
+ # Create Map with key_type and value_type
2015
+ m = Map(element_type, value_type or 'dynamic')
2016
+ # If inline initialization provided, populate the map
2017
+ if init_values:
2018
+ for key, value_node in init_values.items():
2019
+ value = self._evaluate(value_node)
2020
+ m.insert(key, value)
2021
+ return m
1919
2022
  else:
1920
2023
  return None
1921
2024
 
@@ -2714,14 +2817,24 @@ class CSSLRuntime:
2714
2817
  elif isinstance(obj, dict):
2715
2818
  obj[final_attr] = value
2716
2819
 
2717
- # NEW: String interpolation
2820
+ # String interpolation (supports both <var> and {var} syntax)
2718
2821
  def _interpolate_string(self, string: str) -> str:
2719
- """Replace <variable> placeholders with values from scope - NEW
2822
+ """Replace {variable} and <variable> placeholders with values from scope.
2823
+
2824
+ Both syntaxes are supported (variables only, not expressions):
2825
+ "Hello {name}!" -> "Hello John!" (f-string style)
2826
+ "Hello <name>!" -> "Hello John!" (legacy CSSL style)
2827
+
2828
+ Examples:
2829
+ string name = "Alice";
2830
+ int age = 30;
2831
+ printl("Hello {name}, you are {age} years old!");
2832
+ printl("Hello <name>, you are <age> years old!");
2720
2833
 
2721
- Example: "Hello <name>!" becomes "Hello John!" if name = "John"
2834
+ Note: Only simple variable names are supported, not expressions.
2835
+ Use string concatenation for complex expressions.
2722
2836
  """
2723
2837
  import re
2724
- pattern = r'<([A-Za-z_][A-Za-z0-9_]*)>'
2725
2838
 
2726
2839
  def replacer(match):
2727
2840
  var_name = match.group(1)
@@ -2733,10 +2846,23 @@ class CSSLRuntime:
2733
2846
  # Try modules
2734
2847
  if value is None:
2735
2848
  value = self._modules.get(var_name)
2849
+ # Try global scope
2850
+ if value is None:
2851
+ value = self.global_scope.get(var_name)
2736
2852
  # Return string representation or empty string if None
2737
2853
  return str(value) if value is not None else ''
2738
2854
 
2739
- return re.sub(pattern, replacer, string)
2855
+ # Support both {var} and <var> syntax - simple variable names only
2856
+ # Pattern: {identifier} or <identifier>
2857
+ patterns = [
2858
+ r'\{([A-Za-z_][A-Za-z0-9_]*)\}', # {name} f-string style
2859
+ r'<([A-Za-z_][A-Za-z0-9_]*)>', # <name> legacy CSSL style
2860
+ ]
2861
+
2862
+ result = string
2863
+ for pattern in patterns:
2864
+ result = re.sub(pattern, replacer, result)
2865
+ return result
2740
2866
 
2741
2867
  # NEW: Promote variable to global scope via global()
2742
2868
  def promote_to_global(self, s_ref_name: str):