IncludeCPP 3.4.22__tar.gz → 3.5.7__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 (55) hide show
  1. {includecpp-3.4.22 → includecpp-3.5.7}/IncludeCPP.egg-info/PKG-INFO +1 -1
  2. {includecpp-3.4.22 → includecpp-3.5.7}/PKG-INFO +1 -1
  3. {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/__init__.py +1 -1
  4. {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/cli/commands.py +32 -6
  5. {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/cssl/cssl_builtins.py +64 -2
  6. {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/cssl/cssl_parser.py +169 -15
  7. {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/cssl/cssl_runtime.py +302 -29
  8. {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/cssl/cssl_types.py +339 -2
  9. {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/cssl_bridge.py +100 -4
  10. {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/cssl_bridge.pyi +177 -0
  11. {includecpp-3.4.22 → includecpp-3.5.7}/pyproject.toml +1 -1
  12. {includecpp-3.4.22 → includecpp-3.5.7}/IncludeCPP.egg-info/SOURCES.txt +0 -0
  13. {includecpp-3.4.22 → includecpp-3.5.7}/IncludeCPP.egg-info/dependency_links.txt +0 -0
  14. {includecpp-3.4.22 → includecpp-3.5.7}/IncludeCPP.egg-info/entry_points.txt +0 -0
  15. {includecpp-3.4.22 → includecpp-3.5.7}/IncludeCPP.egg-info/requires.txt +0 -0
  16. {includecpp-3.4.22 → includecpp-3.5.7}/IncludeCPP.egg-info/top_level.txt +0 -0
  17. {includecpp-3.4.22 → includecpp-3.5.7}/LICENSE +0 -0
  18. {includecpp-3.4.22 → includecpp-3.5.7}/MANIFEST.in +0 -0
  19. {includecpp-3.4.22 → includecpp-3.5.7}/README.md +0 -0
  20. {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/__init__.pyi +0 -0
  21. {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/__main__.py +0 -0
  22. {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/cli/__init__.py +0 -0
  23. {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/cli/config_parser.py +0 -0
  24. {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/__init__.py +0 -0
  25. {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/ai_integration.py +0 -0
  26. {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/build_manager.py +0 -0
  27. {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/cpp_api.py +0 -0
  28. {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/cpp_api.pyi +0 -0
  29. {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/cppy_converter.py +0 -0
  30. {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/cssl/CSSL_DOCUMENTATION.md +0 -0
  31. {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/cssl/__init__.py +0 -0
  32. {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/cssl/cssl_events.py +0 -0
  33. {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/cssl/cssl_modules.py +0 -0
  34. {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/cssl/cssl_syntax.py +0 -0
  35. {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/error_catalog.py +0 -0
  36. {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/error_formatter.py +0 -0
  37. {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/exceptions.py +0 -0
  38. {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/path_discovery.py +0 -0
  39. {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/project_ui.py +0 -0
  40. {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/settings_ui.py +0 -0
  41. {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/generator/__init__.py +0 -0
  42. {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/generator/parser.cpp +0 -0
  43. {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/generator/parser.h +0 -0
  44. {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/generator/type_resolver.cpp +0 -0
  45. {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/generator/type_resolver.h +0 -0
  46. {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/py.typed +0 -0
  47. {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/templates/cpp.proj.template +0 -0
  48. {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/vscode/__init__.py +0 -0
  49. {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/vscode/cssl/__init__.py +0 -0
  50. {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/vscode/cssl/language-configuration.json +0 -0
  51. {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/vscode/cssl/package.json +0 -0
  52. {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/vscode/cssl/syntaxes/cssl.tmLanguage.json +0 -0
  53. {includecpp-3.4.22 → includecpp-3.5.7}/requirements.txt +0 -0
  54. {includecpp-3.4.22 → includecpp-3.5.7}/setup.cfg +0 -0
  55. {includecpp-3.4.22 → includecpp-3.5.7}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: IncludeCPP
3
- Version: 3.4.22
3
+ Version: 3.5.7
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.4.22
3
+ Version: 3.5.7
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.4.22"
5
+ __version__ = "3.5.7"
6
6
  __all__ = ["CppApi", "CSSL"]
7
7
 
8
8
  # Module-level cache for C++ modules
@@ -7139,20 +7139,21 @@ def cppy_types():
7139
7139
  # EXEC - Interactive Code Execution
7140
7140
  # ============================================================================
7141
7141
 
7142
- @cli.command()
7143
- @click.argument('lang', type=click.Choice(['py', 'cpp', 'python', 'c++']))
7142
+ @cli.command(name='exec')
7143
+ @click.argument('lang', type=click.Choice(['py', 'cpp', 'python', 'c++', 'cssl']))
7144
7144
  @click.argument('path', required=False, type=click.Path())
7145
7145
  @click.option('--all', 'import_all', is_flag=True, help='Import all available modules')
7146
- def exec(lang, path, import_all):
7146
+ def exec_repl(lang, path, import_all):
7147
7147
  """Execute code interactively for quick testing.
7148
7148
 
7149
- Run Python or C++ code snippets without creating files.
7149
+ Run Python, C++ or CSSL code snippets without creating files.
7150
7150
  Perfect for testing your IncludeCPP modules quickly.
7151
7151
 
7152
7152
  \b
7153
7153
  Usage:
7154
7154
  includecpp exec py # Interactive Python
7155
7155
  includecpp exec cpp # Interactive C++
7156
+ includecpp exec cssl # Interactive CSSL
7156
7157
  includecpp exec py mymodule # Auto-import mymodule
7157
7158
  includecpp exec py plugins/x.cp # Auto-import from plugin
7158
7159
  includecpp exec py --all # Import all modules
@@ -7178,7 +7179,8 @@ def exec(lang, path, import_all):
7178
7179
 
7179
7180
  # Normalize language
7180
7181
  is_python = lang in ('py', 'python')
7181
- lang_name = 'Python' if is_python else 'C++'
7182
+ is_cssl = lang == 'cssl'
7183
+ lang_name = 'Python' if is_python else ('CSSL' if is_cssl else 'C++')
7182
7184
 
7183
7185
  # Build imports/includes
7184
7186
  imports = []
@@ -7286,6 +7288,7 @@ def exec(lang, path, import_all):
7286
7288
 
7287
7289
  if is_python:
7288
7290
  # Execute Python code
7291
+ import builtins
7289
7292
  full_code = '\n'.join(code_lines)
7290
7293
  try:
7291
7294
  # Use exec with captured output
@@ -7299,7 +7302,7 @@ def exec(lang, path, import_all):
7299
7302
  exec_globals = {'__name__': '__main__'}
7300
7303
 
7301
7304
  with redirect_stdout(stdout_capture), redirect_stderr(stderr_capture):
7302
- exec(full_code, exec_globals)
7305
+ builtins.exec(full_code, exec_globals)
7303
7306
 
7304
7307
  stdout_val = stdout_capture.getvalue()
7305
7308
  stderr_val = stderr_capture.getvalue()
@@ -7315,6 +7318,29 @@ def exec(lang, path, import_all):
7315
7318
  except Exception as e:
7316
7319
  click.secho(f"Error: {e}", fg='red')
7317
7320
 
7321
+ elif is_cssl:
7322
+ # Execute CSSL code
7323
+ full_code = '\n'.join(lines)
7324
+ try:
7325
+ from ..core.cssl_bridge import CsslLang
7326
+ cssl_lang = CsslLang()
7327
+ result = cssl_lang.exec(full_code)
7328
+
7329
+ # Print any output from the execution
7330
+ output = cssl_lang.get_output()
7331
+ if output:
7332
+ for line in output:
7333
+ click.echo(line)
7334
+
7335
+ if result is not None:
7336
+ click.echo(result)
7337
+
7338
+ if not output and result is None:
7339
+ click.secho("(no output)", fg='bright_black')
7340
+
7341
+ except Exception as e:
7342
+ click.secho(f"Error: {e}", fg='red')
7343
+
7318
7344
  else:
7319
7345
  # Execute C++ code
7320
7346
  # Build a complete C++ program
@@ -913,6 +913,29 @@ class CSSLBuiltins:
913
913
  else:
914
914
  raise SystemExit(code)
915
915
 
916
+ def builtin_original(self, func_name: str, *args) -> Any:
917
+ """Call the original version of a replaced function.
918
+
919
+ Usage:
920
+ exit <<== { printl("custom exit"); }
921
+ original("exit"); // Calls the ORIGINAL exit, not the replacement
922
+
923
+ // In an injection that was defined BEFORE replacement:
924
+ old_exit <<== { original("exit"); } // Calls original exit
925
+ """
926
+ if self.runtime and hasattr(self.runtime, '_original_functions'):
927
+ original_func = self.runtime._original_functions.get(func_name)
928
+ if original_func is not None:
929
+ if callable(original_func):
930
+ return original_func(*args)
931
+ elif isinstance(original_func, type(lambda: None).__class__.__bases__[0]): # Check if bound method
932
+ return original_func(*args)
933
+ # Fallback: try to call builtin directly
934
+ builtin_method = getattr(self, f'builtin_{func_name}', None)
935
+ if builtin_method:
936
+ return builtin_method(*args)
937
+ raise CSSLBuiltinError(f"No original function '{func_name}' found")
938
+
916
939
  def builtin_env(self, name: str, default: str = None) -> Optional[str]:
917
940
  return os.environ.get(name, default)
918
941
 
@@ -1902,15 +1925,54 @@ class CSSLBuiltins:
1902
1925
  from .cssl_types import OpenQuote
1903
1926
  return OpenQuote(db_ref)
1904
1927
 
1905
- def builtin_openfind(self, combo_or_type: Any, index: int = 0) -> Any:
1928
+ def builtin_openfind(self, combo_or_type: Any, index: int = 0, params: list = None) -> Any:
1906
1929
  """Find open parameter by type or combo space.
1907
1930
 
1908
- Usage: OpenFind<string>(0) or OpenFind(&@comboSpace)
1931
+ Usage:
1932
+ OpenFind<string>(0) # Find first string at position 0
1933
+ OpenFind(&@comboSpace) # Find using combo filter
1934
+
1935
+ When using with open parameters:
1936
+ open define myFunc(open Params) {
1937
+ string name = OpenFind<string>(0); // Find nearest string at index 0
1938
+ int age = OpenFind<int>(1); // Find nearest int at index 1
1939
+ }
1909
1940
  """
1910
1941
  from .cssl_types import Combo
1911
1942
 
1912
1943
  if isinstance(combo_or_type, Combo):
1944
+ # Find by combo space
1945
+ if params:
1946
+ return combo_or_type.find_match(params)
1913
1947
  return combo_or_type.find_match([])
1948
+
1949
+ # Type-based search
1950
+ target_type = combo_or_type
1951
+ if params is None:
1952
+ params = []
1953
+
1954
+ # Map type names to Python types
1955
+ type_map = {
1956
+ 'string': str, 'str': str,
1957
+ 'int': int, 'integer': int,
1958
+ 'float': float, 'double': float,
1959
+ 'bool': bool, 'boolean': bool,
1960
+ 'list': list, 'array': list,
1961
+ 'dict': dict, 'dictionary': dict
1962
+ }
1963
+
1964
+ python_type = type_map.get(str(target_type).lower(), None)
1965
+ if python_type is None:
1966
+ return None
1967
+
1968
+ # Find the nearest matching type from index position
1969
+ matches_found = 0
1970
+ for i, param in enumerate(params):
1971
+ if isinstance(param, python_type):
1972
+ if matches_found == index:
1973
+ return param
1974
+ matches_found += 1
1975
+
1914
1976
  return None
1915
1977
 
1916
1978
 
@@ -100,6 +100,7 @@ class TokenType(Enum):
100
100
  GLOBAL_REF = auto() # r@<name> global variable declaration
101
101
  SELF_REF = auto() # s@<name> self-reference to global struct
102
102
  SHARED_REF = auto() # $<name> shared object reference
103
+ CAPTURED_REF = auto() # %<name> captured reference (for infusion)
103
104
  PACKAGE = auto()
104
105
  PACKAGE_INCLUDES = auto()
105
106
  AS = auto()
@@ -125,6 +126,7 @@ KEYWORDS = {
125
126
  'package', 'package-includes', 'exec', 'as', 'global',
126
127
  # CSSL Type Keywords
127
128
  'int', 'string', 'float', 'bool', 'void', 'json', 'array', 'vector', 'stack',
129
+ 'list', 'dictionary', 'dict', # Python-like types
128
130
  'dynamic', # No type declaration (slow but flexible)
129
131
  'undefined', # Function errors ignored
130
132
  'open', # Accept any parameter type
@@ -152,7 +154,7 @@ TYPE_LITERALS = {'list', 'dict'}
152
154
  # Generic type keywords that use <T> syntax
153
155
  TYPE_GENERICS = {
154
156
  'datastruct', 'dataspace', 'shuffled', 'iterator', 'combo',
155
- 'vector', 'stack', 'array', 'openquote'
157
+ 'vector', 'stack', 'array', 'openquote', 'list', 'dictionary'
156
158
  }
157
159
 
158
160
  # Functions that accept type parameters: FuncName<type>(args)
@@ -241,6 +243,9 @@ class CSSLLexer:
241
243
  elif char == '$':
242
244
  # $<name> shared object reference
243
245
  self._read_shared_ref()
246
+ elif char == '%':
247
+ # %<name> captured reference (for infusion)
248
+ self._read_captured_ref()
244
249
  elif char == '&':
245
250
  # & for references
246
251
  if self._peek(1) == '&':
@@ -493,6 +498,20 @@ class CSSLLexer:
493
498
  self.error("Expected identifier after '$'")
494
499
  self._add_token(TokenType.SHARED_REF, value)
495
500
 
501
+ def _read_captured_ref(self):
502
+ """Read %<name> captured reference (captures value at definition time for infusions)"""
503
+ self._advance() # skip '%'
504
+
505
+ # Read the identifier (captured reference name)
506
+ name_start = self.pos
507
+ while self.pos < len(self.source) and (self.source[self.pos].isalnum() or self.source[self.pos] == '_'):
508
+ self._advance()
509
+
510
+ value = self.source[name_start:self.pos]
511
+ if not value:
512
+ self.error("Expected identifier after '%'")
513
+ self._add_token(TokenType.CAPTURED_REF, value)
514
+
496
515
  def _read_less_than(self):
497
516
  # Check for <<== (code infusion left)
498
517
  if self._peek(1) == '<' and self._peek(2) == '=' and self._peek(3) == '=':
@@ -660,6 +679,7 @@ class CSSLParser:
660
679
  def _is_type_keyword(self, value: str) -> bool:
661
680
  """Check if a keyword is a type declaration"""
662
681
  return value in ('int', 'string', 'float', 'bool', 'void', 'json', 'array', 'vector', 'stack',
682
+ 'list', 'dictionary', 'dict',
663
683
  'dynamic', 'datastruct', 'dataspace', 'shuffled', 'iterator', 'combo', 'structure')
664
684
 
665
685
  def _looks_like_function_declaration(self) -> bool:
@@ -877,7 +897,8 @@ class CSSLParser:
877
897
  # Skip known type keywords
878
898
  type_keywords = {'int', 'string', 'float', 'bool', 'dynamic', 'void',
879
899
  'stack', 'vector', 'datastruct', 'dataspace', 'shuffled',
880
- 'iterator', 'combo', 'array', 'openquote', 'json'}
900
+ 'iterator', 'combo', 'array', 'openquote', 'json',
901
+ 'list', 'dictionary', 'dict'}
881
902
  if type_name not in type_keywords:
882
903
  return False
883
904
 
@@ -1291,7 +1312,9 @@ class CSSLParser:
1291
1312
  elif self._looks_like_function_declaration():
1292
1313
  # Nested typed function (e.g., void Level2() { ... })
1293
1314
  return self._parse_typed_function()
1294
- elif self._check(TokenType.IDENTIFIER) or self._check(TokenType.AT):
1315
+ elif (self._check(TokenType.IDENTIFIER) or self._check(TokenType.AT) or
1316
+ self._check(TokenType.CAPTURED_REF) or self._check(TokenType.SHARED_REF) or
1317
+ self._check(TokenType.GLOBAL_REF) or self._check(TokenType.SELF_REF)):
1295
1318
  return self._parse_expression_statement()
1296
1319
  else:
1297
1320
  self._advance()
@@ -1505,7 +1528,13 @@ class CSSLParser:
1505
1528
  return ASTNode('c_for_update', value={'var': var_name, 'op': 'none'})
1506
1529
 
1507
1530
  def _parse_python_style_for(self) -> ASTNode:
1508
- """Parse Python-style for loop: for (i in range(start, end)) { }"""
1531
+ """Parse Python-style for loop: for (i in range(...)) { }
1532
+
1533
+ Supports:
1534
+ for (i in range(n)) { } - 0 to n-1
1535
+ for (i in range(start, end)) { } - start to end-1
1536
+ for (i in range(start, end, step)) { }
1537
+ """
1509
1538
  var_name = self._advance().value
1510
1539
  self._expect(TokenType.KEYWORD) # 'in'
1511
1540
 
@@ -1518,15 +1547,27 @@ class CSSLParser:
1518
1547
  self.error(f"Expected 'range', got {self._peek().value}")
1519
1548
 
1520
1549
  self._expect(TokenType.PAREN_START)
1521
- start = self._parse_expression()
1522
- self._expect(TokenType.COMMA)
1523
- end = self._parse_expression()
1550
+ first_arg = self._parse_expression()
1524
1551
 
1525
- # Optional step parameter: range(start, end, step)
1552
+ # Check if there are more arguments
1553
+ start = None
1554
+ end = None
1526
1555
  step = None
1556
+
1527
1557
  if self._check(TokenType.COMMA):
1558
+ # range(start, end) or range(start, end, step)
1528
1559
  self._advance() # consume comma
1529
- step = self._parse_expression()
1560
+ start = first_arg
1561
+ end = self._parse_expression()
1562
+
1563
+ # Optional step parameter
1564
+ if self._check(TokenType.COMMA):
1565
+ self._advance() # consume comma
1566
+ step = self._parse_expression()
1567
+ else:
1568
+ # range(n) - single argument means 0 to n-1
1569
+ start = ASTNode('literal', value={'type': 'int', 'value': 0})
1570
+ end = first_arg
1530
1571
 
1531
1572
  self._expect(TokenType.PAREN_END)
1532
1573
  self._expect(TokenType.PAREN_END)
@@ -1734,12 +1775,26 @@ class CSSLParser:
1734
1775
  self._match(TokenType.SEMICOLON)
1735
1776
  return ASTNode('inject', value={'target': expr, 'source': source, 'mode': 'add', 'filter': filter_info})
1736
1777
 
1737
- # === MINUS INJECTION: -<== (move & remove from source) ===
1778
+ # === MINUS INJECTION: -<== or -<==[n] (move & remove from source) ===
1738
1779
  if self._match(TokenType.INJECT_MINUS_LEFT):
1780
+ # Check for indexed deletion: -<==[n] (only numbers, not filters)
1781
+ remove_index = None
1782
+ if self._check(TokenType.BRACKET_START):
1783
+ # Peek ahead to see if this is an index [n] or a filter [type::helper=...]
1784
+ # Only consume if it's a simple number index
1785
+ saved_pos = self._current
1786
+ self._advance() # consume [
1787
+ if self._check(TokenType.NUMBER):
1788
+ remove_index = int(self._advance().value)
1789
+ self._expect(TokenType.BRACKET_END)
1790
+ else:
1791
+ # Not a number - restore position for filter parsing
1792
+ self._current = saved_pos
1793
+
1739
1794
  filter_info = self._parse_injection_filter()
1740
1795
  source = self._parse_expression()
1741
1796
  self._match(TokenType.SEMICOLON)
1742
- return ASTNode('inject', value={'target': expr, 'source': source, 'mode': 'move', 'filter': filter_info})
1797
+ return ASTNode('inject', value={'target': expr, 'source': source, 'mode': 'move', 'filter': filter_info, 'index': remove_index})
1743
1798
 
1744
1799
  # === CODE INFUSION: <<== (inject code into function) ===
1745
1800
  if self._match(TokenType.INFUSE_LEFT):
@@ -1763,16 +1818,29 @@ class CSSLParser:
1763
1818
  self._match(TokenType.SEMICOLON)
1764
1819
  return ASTNode('infuse', value={'target': expr, 'source': source, 'mode': 'add'})
1765
1820
 
1766
- # === CODE INFUSION MINUS: -<<== (remove code from function) ===
1821
+ # === CODE INFUSION MINUS: -<<== or -<<==[n] (remove code from function) ===
1767
1822
  if self._match(TokenType.INFUSE_MINUS_LEFT):
1823
+ # Check for indexed deletion: -<<==[n] (only numbers)
1824
+ remove_index = None
1825
+ if self._check(TokenType.BRACKET_START):
1826
+ # Peek ahead to see if this is an index [n] or something else
1827
+ saved_pos = self._current
1828
+ self._advance() # consume [
1829
+ if self._check(TokenType.NUMBER):
1830
+ remove_index = int(self._advance().value)
1831
+ self._expect(TokenType.BRACKET_END)
1832
+ else:
1833
+ # Not a number - restore position
1834
+ self._current = saved_pos
1835
+
1768
1836
  if self._check(TokenType.BLOCK_START):
1769
1837
  code_block = self._parse_action_block()
1770
1838
  self._match(TokenType.SEMICOLON)
1771
- return ASTNode('infuse', value={'target': expr, 'code': code_block, 'mode': 'remove'})
1839
+ return ASTNode('infuse', value={'target': expr, 'code': code_block, 'mode': 'remove', 'index': remove_index})
1772
1840
  else:
1773
1841
  source = self._parse_expression()
1774
1842
  self._match(TokenType.SEMICOLON)
1775
- return ASTNode('infuse', value={'target': expr, 'source': source, 'mode': 'remove'})
1843
+ return ASTNode('infuse', value={'target': expr, 'source': source, 'mode': 'remove', 'index': remove_index})
1776
1844
 
1777
1845
  # === RIGHT-SIDE OPERATORS ===
1778
1846
 
@@ -2010,6 +2078,31 @@ class CSSLParser:
2010
2078
  break
2011
2079
  return node
2012
2080
 
2081
+ if self._check(TokenType.CAPTURED_REF):
2082
+ # %<name> captured reference (captures value at infusion registration time)
2083
+ token = self._advance()
2084
+ node = ASTNode('captured_ref', value=token.value, line=token.line, column=token.column)
2085
+ # Check for member access, calls, indexing
2086
+ while True:
2087
+ if self._match(TokenType.PAREN_START):
2088
+ args = []
2089
+ while not self._check(TokenType.PAREN_END):
2090
+ args.append(self._parse_expression())
2091
+ if not self._check(TokenType.PAREN_END):
2092
+ self._expect(TokenType.COMMA)
2093
+ self._expect(TokenType.PAREN_END)
2094
+ node = ASTNode('call', value={'callee': node, 'args': args})
2095
+ elif self._match(TokenType.DOT):
2096
+ member = self._advance().value
2097
+ node = ASTNode('member_access', value={'object': node, 'member': member})
2098
+ elif self._match(TokenType.BRACKET_START):
2099
+ index = self._parse_expression()
2100
+ self._expect(TokenType.BRACKET_END)
2101
+ node = ASTNode('index_access', value={'object': node, 'index': index})
2102
+ else:
2103
+ break
2104
+ return node
2105
+
2013
2106
  if self._check(TokenType.NUMBER):
2014
2107
  return ASTNode('literal', value=self._advance().value)
2015
2108
 
@@ -2034,7 +2127,13 @@ class CSSLParser:
2034
2127
  return expr
2035
2128
 
2036
2129
  if self._match(TokenType.BLOCK_START):
2037
- return self._parse_object()
2130
+ # Distinguish between object literal { key = value } and action block { expr; }
2131
+ # Object literal: starts with IDENTIFIER = or STRING =
2132
+ # Action block: starts with expression (captured_ref, call, literal, etc.)
2133
+ if self._is_object_literal():
2134
+ return self._parse_object()
2135
+ else:
2136
+ return self._parse_action_block_expression()
2038
2137
 
2039
2138
  if self._match(TokenType.BRACKET_START):
2040
2139
  return self._parse_array()
@@ -2143,6 +2242,61 @@ class CSSLParser:
2143
2242
 
2144
2243
  return node
2145
2244
 
2245
+ def _is_object_literal(self) -> bool:
2246
+ """Check if current position is an object literal { key = value } vs action block { expr; }
2247
+
2248
+ Object literal: { name = value; } or { "key" = value; }
2249
+ Action block: { %version; } or { "1.0.0" } or { call(); }
2250
+ """
2251
+ # Empty block is action block
2252
+ if self._check(TokenType.BLOCK_END):
2253
+ return False
2254
+
2255
+ # Save position for lookahead
2256
+ saved_pos = self._current
2257
+
2258
+ # Check if it looks like key = value pattern
2259
+ is_object = False
2260
+ if self._check(TokenType.IDENTIFIER) or self._check(TokenType.STRING):
2261
+ self._advance() # skip key
2262
+ if self._check(TokenType.EQUALS):
2263
+ # Looks like object literal: { key = ...
2264
+ is_object = True
2265
+
2266
+ # Restore position
2267
+ self._current = saved_pos
2268
+ return is_object
2269
+
2270
+ def _parse_action_block_expression(self) -> ASTNode:
2271
+ """Parse an action block expression: { expr; expr2; } returns last value
2272
+
2273
+ Used for: v <== { %version; } or v <== { "1.0.0" }
2274
+ """
2275
+ children = []
2276
+
2277
+ while not self._check(TokenType.BLOCK_END) and not self._is_at_end():
2278
+ # Parse statement or expression
2279
+ if (self._check(TokenType.IDENTIFIER) or self._check(TokenType.AT) or
2280
+ self._check(TokenType.CAPTURED_REF) or self._check(TokenType.SHARED_REF) or
2281
+ self._check(TokenType.GLOBAL_REF) or self._check(TokenType.SELF_REF) or
2282
+ self._check(TokenType.STRING) or self._check(TokenType.NUMBER) or
2283
+ self._check(TokenType.BOOLEAN) or self._check(TokenType.NULL) or
2284
+ self._check(TokenType.PAREN_START)):
2285
+ # Parse as expression and wrap in expression node for _execute_node
2286
+ expr = self._parse_expression()
2287
+ self._match(TokenType.SEMICOLON)
2288
+ children.append(ASTNode('expression', value=expr))
2289
+ elif self._check(TokenType.KEYWORD):
2290
+ # Parse as statement
2291
+ stmt = self._parse_statement()
2292
+ if stmt:
2293
+ children.append(stmt)
2294
+ else:
2295
+ self._advance()
2296
+
2297
+ self._expect(TokenType.BLOCK_END)
2298
+ return ASTNode('action_block', children=children)
2299
+
2146
2300
  def _parse_object(self) -> ASTNode:
2147
2301
  properties = {}
2148
2302