IncludeCPP 3.4.22__tar.gz → 3.5.2__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.2}/IncludeCPP.egg-info/PKG-INFO +1 -1
  2. {includecpp-3.4.22 → includecpp-3.5.2}/PKG-INFO +1 -1
  3. {includecpp-3.4.22 → includecpp-3.5.2}/includecpp/__init__.py +1 -1
  4. {includecpp-3.4.22 → includecpp-3.5.2}/includecpp/cli/commands.py +32 -6
  5. {includecpp-3.4.22 → includecpp-3.5.2}/includecpp/core/cssl/cssl_builtins.py +41 -2
  6. {includecpp-3.4.22 → includecpp-3.5.2}/includecpp/core/cssl/cssl_parser.py +61 -13
  7. {includecpp-3.4.22 → includecpp-3.5.2}/includecpp/core/cssl/cssl_runtime.py +88 -15
  8. {includecpp-3.4.22 → includecpp-3.5.2}/includecpp/core/cssl/cssl_types.py +339 -2
  9. {includecpp-3.4.22 → includecpp-3.5.2}/includecpp/core/cssl_bridge.py +100 -4
  10. {includecpp-3.4.22 → includecpp-3.5.2}/includecpp/core/cssl_bridge.pyi +177 -0
  11. {includecpp-3.4.22 → includecpp-3.5.2}/pyproject.toml +1 -1
  12. {includecpp-3.4.22 → includecpp-3.5.2}/IncludeCPP.egg-info/SOURCES.txt +0 -0
  13. {includecpp-3.4.22 → includecpp-3.5.2}/IncludeCPP.egg-info/dependency_links.txt +0 -0
  14. {includecpp-3.4.22 → includecpp-3.5.2}/IncludeCPP.egg-info/entry_points.txt +0 -0
  15. {includecpp-3.4.22 → includecpp-3.5.2}/IncludeCPP.egg-info/requires.txt +0 -0
  16. {includecpp-3.4.22 → includecpp-3.5.2}/IncludeCPP.egg-info/top_level.txt +0 -0
  17. {includecpp-3.4.22 → includecpp-3.5.2}/LICENSE +0 -0
  18. {includecpp-3.4.22 → includecpp-3.5.2}/MANIFEST.in +0 -0
  19. {includecpp-3.4.22 → includecpp-3.5.2}/README.md +0 -0
  20. {includecpp-3.4.22 → includecpp-3.5.2}/includecpp/__init__.pyi +0 -0
  21. {includecpp-3.4.22 → includecpp-3.5.2}/includecpp/__main__.py +0 -0
  22. {includecpp-3.4.22 → includecpp-3.5.2}/includecpp/cli/__init__.py +0 -0
  23. {includecpp-3.4.22 → includecpp-3.5.2}/includecpp/cli/config_parser.py +0 -0
  24. {includecpp-3.4.22 → includecpp-3.5.2}/includecpp/core/__init__.py +0 -0
  25. {includecpp-3.4.22 → includecpp-3.5.2}/includecpp/core/ai_integration.py +0 -0
  26. {includecpp-3.4.22 → includecpp-3.5.2}/includecpp/core/build_manager.py +0 -0
  27. {includecpp-3.4.22 → includecpp-3.5.2}/includecpp/core/cpp_api.py +0 -0
  28. {includecpp-3.4.22 → includecpp-3.5.2}/includecpp/core/cpp_api.pyi +0 -0
  29. {includecpp-3.4.22 → includecpp-3.5.2}/includecpp/core/cppy_converter.py +0 -0
  30. {includecpp-3.4.22 → includecpp-3.5.2}/includecpp/core/cssl/CSSL_DOCUMENTATION.md +0 -0
  31. {includecpp-3.4.22 → includecpp-3.5.2}/includecpp/core/cssl/__init__.py +0 -0
  32. {includecpp-3.4.22 → includecpp-3.5.2}/includecpp/core/cssl/cssl_events.py +0 -0
  33. {includecpp-3.4.22 → includecpp-3.5.2}/includecpp/core/cssl/cssl_modules.py +0 -0
  34. {includecpp-3.4.22 → includecpp-3.5.2}/includecpp/core/cssl/cssl_syntax.py +0 -0
  35. {includecpp-3.4.22 → includecpp-3.5.2}/includecpp/core/error_catalog.py +0 -0
  36. {includecpp-3.4.22 → includecpp-3.5.2}/includecpp/core/error_formatter.py +0 -0
  37. {includecpp-3.4.22 → includecpp-3.5.2}/includecpp/core/exceptions.py +0 -0
  38. {includecpp-3.4.22 → includecpp-3.5.2}/includecpp/core/path_discovery.py +0 -0
  39. {includecpp-3.4.22 → includecpp-3.5.2}/includecpp/core/project_ui.py +0 -0
  40. {includecpp-3.4.22 → includecpp-3.5.2}/includecpp/core/settings_ui.py +0 -0
  41. {includecpp-3.4.22 → includecpp-3.5.2}/includecpp/generator/__init__.py +0 -0
  42. {includecpp-3.4.22 → includecpp-3.5.2}/includecpp/generator/parser.cpp +0 -0
  43. {includecpp-3.4.22 → includecpp-3.5.2}/includecpp/generator/parser.h +0 -0
  44. {includecpp-3.4.22 → includecpp-3.5.2}/includecpp/generator/type_resolver.cpp +0 -0
  45. {includecpp-3.4.22 → includecpp-3.5.2}/includecpp/generator/type_resolver.h +0 -0
  46. {includecpp-3.4.22 → includecpp-3.5.2}/includecpp/py.typed +0 -0
  47. {includecpp-3.4.22 → includecpp-3.5.2}/includecpp/templates/cpp.proj.template +0 -0
  48. {includecpp-3.4.22 → includecpp-3.5.2}/includecpp/vscode/__init__.py +0 -0
  49. {includecpp-3.4.22 → includecpp-3.5.2}/includecpp/vscode/cssl/__init__.py +0 -0
  50. {includecpp-3.4.22 → includecpp-3.5.2}/includecpp/vscode/cssl/language-configuration.json +0 -0
  51. {includecpp-3.4.22 → includecpp-3.5.2}/includecpp/vscode/cssl/package.json +0 -0
  52. {includecpp-3.4.22 → includecpp-3.5.2}/includecpp/vscode/cssl/syntaxes/cssl.tmLanguage.json +0 -0
  53. {includecpp-3.4.22 → includecpp-3.5.2}/requirements.txt +0 -0
  54. {includecpp-3.4.22 → includecpp-3.5.2}/setup.cfg +0 -0
  55. {includecpp-3.4.22 → includecpp-3.5.2}/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.2
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.2
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.2"
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
@@ -1902,15 +1902,54 @@ class CSSLBuiltins:
1902
1902
  from .cssl_types import OpenQuote
1903
1903
  return OpenQuote(db_ref)
1904
1904
 
1905
- def builtin_openfind(self, combo_or_type: Any, index: int = 0) -> Any:
1905
+ def builtin_openfind(self, combo_or_type: Any, index: int = 0, params: list = None) -> Any:
1906
1906
  """Find open parameter by type or combo space.
1907
1907
 
1908
- Usage: OpenFind<string>(0) or OpenFind(&@comboSpace)
1908
+ Usage:
1909
+ OpenFind<string>(0) # Find first string at position 0
1910
+ OpenFind(&@comboSpace) # Find using combo filter
1911
+
1912
+ When using with open parameters:
1913
+ open define myFunc(open Params) {
1914
+ string name = OpenFind<string>(0); // Find nearest string at index 0
1915
+ int age = OpenFind<int>(1); // Find nearest int at index 1
1916
+ }
1909
1917
  """
1910
1918
  from .cssl_types import Combo
1911
1919
 
1912
1920
  if isinstance(combo_or_type, Combo):
1921
+ # Find by combo space
1922
+ if params:
1923
+ return combo_or_type.find_match(params)
1913
1924
  return combo_or_type.find_match([])
1925
+
1926
+ # Type-based search
1927
+ target_type = combo_or_type
1928
+ if params is None:
1929
+ params = []
1930
+
1931
+ # Map type names to Python types
1932
+ type_map = {
1933
+ 'string': str, 'str': str,
1934
+ 'int': int, 'integer': int,
1935
+ 'float': float, 'double': float,
1936
+ 'bool': bool, 'boolean': bool,
1937
+ 'list': list, 'array': list,
1938
+ 'dict': dict, 'dictionary': dict
1939
+ }
1940
+
1941
+ python_type = type_map.get(str(target_type).lower(), None)
1942
+ if python_type is None:
1943
+ return None
1944
+
1945
+ # Find the nearest matching type from index position
1946
+ matches_found = 0
1947
+ for i, param in enumerate(params):
1948
+ if isinstance(param, python_type):
1949
+ if matches_found == index:
1950
+ return param
1951
+ matches_found += 1
1952
+
1914
1953
  return None
1915
1954
 
1916
1955
 
@@ -125,6 +125,7 @@ KEYWORDS = {
125
125
  'package', 'package-includes', 'exec', 'as', 'global',
126
126
  # CSSL Type Keywords
127
127
  'int', 'string', 'float', 'bool', 'void', 'json', 'array', 'vector', 'stack',
128
+ 'list', 'dictionary', 'dict', # Python-like types
128
129
  'dynamic', # No type declaration (slow but flexible)
129
130
  'undefined', # Function errors ignored
130
131
  'open', # Accept any parameter type
@@ -152,7 +153,7 @@ TYPE_LITERALS = {'list', 'dict'}
152
153
  # Generic type keywords that use <T> syntax
153
154
  TYPE_GENERICS = {
154
155
  'datastruct', 'dataspace', 'shuffled', 'iterator', 'combo',
155
- 'vector', 'stack', 'array', 'openquote'
156
+ 'vector', 'stack', 'array', 'openquote', 'list', 'dictionary'
156
157
  }
157
158
 
158
159
  # Functions that accept type parameters: FuncName<type>(args)
@@ -660,6 +661,7 @@ class CSSLParser:
660
661
  def _is_type_keyword(self, value: str) -> bool:
661
662
  """Check if a keyword is a type declaration"""
662
663
  return value in ('int', 'string', 'float', 'bool', 'void', 'json', 'array', 'vector', 'stack',
664
+ 'list', 'dictionary', 'dict',
663
665
  'dynamic', 'datastruct', 'dataspace', 'shuffled', 'iterator', 'combo', 'structure')
664
666
 
665
667
  def _looks_like_function_declaration(self) -> bool:
@@ -877,7 +879,8 @@ class CSSLParser:
877
879
  # Skip known type keywords
878
880
  type_keywords = {'int', 'string', 'float', 'bool', 'dynamic', 'void',
879
881
  'stack', 'vector', 'datastruct', 'dataspace', 'shuffled',
880
- 'iterator', 'combo', 'array', 'openquote', 'json'}
882
+ 'iterator', 'combo', 'array', 'openquote', 'json',
883
+ 'list', 'dictionary', 'dict'}
881
884
  if type_name not in type_keywords:
882
885
  return False
883
886
 
@@ -1505,7 +1508,13 @@ class CSSLParser:
1505
1508
  return ASTNode('c_for_update', value={'var': var_name, 'op': 'none'})
1506
1509
 
1507
1510
  def _parse_python_style_for(self) -> ASTNode:
1508
- """Parse Python-style for loop: for (i in range(start, end)) { }"""
1511
+ """Parse Python-style for loop: for (i in range(...)) { }
1512
+
1513
+ Supports:
1514
+ for (i in range(n)) { } - 0 to n-1
1515
+ for (i in range(start, end)) { } - start to end-1
1516
+ for (i in range(start, end, step)) { }
1517
+ """
1509
1518
  var_name = self._advance().value
1510
1519
  self._expect(TokenType.KEYWORD) # 'in'
1511
1520
 
@@ -1518,15 +1527,27 @@ class CSSLParser:
1518
1527
  self.error(f"Expected 'range', got {self._peek().value}")
1519
1528
 
1520
1529
  self._expect(TokenType.PAREN_START)
1521
- start = self._parse_expression()
1522
- self._expect(TokenType.COMMA)
1523
- end = self._parse_expression()
1530
+ first_arg = self._parse_expression()
1524
1531
 
1525
- # Optional step parameter: range(start, end, step)
1532
+ # Check if there are more arguments
1533
+ start = None
1534
+ end = None
1526
1535
  step = None
1536
+
1527
1537
  if self._check(TokenType.COMMA):
1538
+ # range(start, end) or range(start, end, step)
1528
1539
  self._advance() # consume comma
1529
- step = self._parse_expression()
1540
+ start = first_arg
1541
+ end = self._parse_expression()
1542
+
1543
+ # Optional step parameter
1544
+ if self._check(TokenType.COMMA):
1545
+ self._advance() # consume comma
1546
+ step = self._parse_expression()
1547
+ else:
1548
+ # range(n) - single argument means 0 to n-1
1549
+ start = ASTNode('literal', value={'type': 'int', 'value': 0})
1550
+ end = first_arg
1530
1551
 
1531
1552
  self._expect(TokenType.PAREN_END)
1532
1553
  self._expect(TokenType.PAREN_END)
@@ -1734,12 +1755,26 @@ class CSSLParser:
1734
1755
  self._match(TokenType.SEMICOLON)
1735
1756
  return ASTNode('inject', value={'target': expr, 'source': source, 'mode': 'add', 'filter': filter_info})
1736
1757
 
1737
- # === MINUS INJECTION: -<== (move & remove from source) ===
1758
+ # === MINUS INJECTION: -<== or -<==[n] (move & remove from source) ===
1738
1759
  if self._match(TokenType.INJECT_MINUS_LEFT):
1760
+ # Check for indexed deletion: -<==[n] (only numbers, not filters)
1761
+ remove_index = None
1762
+ if self._check(TokenType.BRACKET_START):
1763
+ # Peek ahead to see if this is an index [n] or a filter [type::helper=...]
1764
+ # Only consume if it's a simple number index
1765
+ saved_pos = self._current
1766
+ self._advance() # consume [
1767
+ if self._check(TokenType.NUMBER):
1768
+ remove_index = int(self._advance().value)
1769
+ self._expect(TokenType.BRACKET_END)
1770
+ else:
1771
+ # Not a number - restore position for filter parsing
1772
+ self._current = saved_pos
1773
+
1739
1774
  filter_info = self._parse_injection_filter()
1740
1775
  source = self._parse_expression()
1741
1776
  self._match(TokenType.SEMICOLON)
1742
- return ASTNode('inject', value={'target': expr, 'source': source, 'mode': 'move', 'filter': filter_info})
1777
+ return ASTNode('inject', value={'target': expr, 'source': source, 'mode': 'move', 'filter': filter_info, 'index': remove_index})
1743
1778
 
1744
1779
  # === CODE INFUSION: <<== (inject code into function) ===
1745
1780
  if self._match(TokenType.INFUSE_LEFT):
@@ -1763,16 +1798,29 @@ class CSSLParser:
1763
1798
  self._match(TokenType.SEMICOLON)
1764
1799
  return ASTNode('infuse', value={'target': expr, 'source': source, 'mode': 'add'})
1765
1800
 
1766
- # === CODE INFUSION MINUS: -<<== (remove code from function) ===
1801
+ # === CODE INFUSION MINUS: -<<== or -<<==[n] (remove code from function) ===
1767
1802
  if self._match(TokenType.INFUSE_MINUS_LEFT):
1803
+ # Check for indexed deletion: -<<==[n] (only numbers)
1804
+ remove_index = None
1805
+ if self._check(TokenType.BRACKET_START):
1806
+ # Peek ahead to see if this is an index [n] or something else
1807
+ saved_pos = self._current
1808
+ self._advance() # consume [
1809
+ if self._check(TokenType.NUMBER):
1810
+ remove_index = int(self._advance().value)
1811
+ self._expect(TokenType.BRACKET_END)
1812
+ else:
1813
+ # Not a number - restore position
1814
+ self._current = saved_pos
1815
+
1768
1816
  if self._check(TokenType.BLOCK_START):
1769
1817
  code_block = self._parse_action_block()
1770
1818
  self._match(TokenType.SEMICOLON)
1771
- return ASTNode('infuse', value={'target': expr, 'code': code_block, 'mode': 'remove'})
1819
+ return ASTNode('infuse', value={'target': expr, 'code': code_block, 'mode': 'remove', 'index': remove_index})
1772
1820
  else:
1773
1821
  source = self._parse_expression()
1774
1822
  self._match(TokenType.SEMICOLON)
1775
- return ASTNode('infuse', value={'target': expr, 'source': source, 'mode': 'remove'})
1823
+ return ASTNode('infuse', value={'target': expr, 'source': source, 'mode': 'remove', 'index': remove_index})
1776
1824
 
1777
1825
  # === RIGHT-SIDE OPERATORS ===
1778
1826
 
@@ -14,7 +14,7 @@ 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
18
18
  )
19
19
 
20
20
 
@@ -374,8 +374,13 @@ class CSSLRuntime:
374
374
  - top-level statements (assignments, function calls, control flow)
375
375
  """
376
376
  result = None
377
+ self._running = True # Start running
377
378
 
378
379
  for child in node.children:
380
+ # Check if exit() was called
381
+ if not self._running:
382
+ break
383
+
379
384
  if child.type == 'struct':
380
385
  self._exec_struct(child)
381
386
  elif child.type == 'function':
@@ -398,13 +403,14 @@ class CSSLRuntime:
398
403
  except CSSLRuntimeError:
399
404
  pass # Ignore unknown nodes in program mode
400
405
 
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
406
+ # Look for and execute main() if defined (only if still running)
407
+ if self._running:
408
+ main_func = self.scope.get('main')
409
+ if main_func and isinstance(main_func, ASTNode) and main_func.type == 'function':
410
+ try:
411
+ result = self._call_function(main_func, [])
412
+ except CSSLReturn as ret:
413
+ result = ret.value
408
414
 
409
415
  return result
410
416
 
@@ -701,6 +707,10 @@ class CSSLRuntime:
701
707
  instance = {} if value_node is None else self._evaluate(value_node)
702
708
  elif type_name == 'array':
703
709
  instance = Array(element_type)
710
+ elif type_name == 'list':
711
+ instance = List(element_type)
712
+ elif type_name in ('dictionary', 'dict'):
713
+ instance = Dictionary(element_type)
704
714
  else:
705
715
  # Default: evaluate the value or set to None
706
716
  instance = self._evaluate(value_node) if value_node else None
@@ -824,9 +834,11 @@ class CSSLRuntime:
824
834
 
825
835
  def _exec_while(self, node: ASTNode) -> Any:
826
836
  """Execute while loop"""
827
- while self._evaluate(node.value.get('condition')):
837
+ while self._running and self._evaluate(node.value.get('condition')):
828
838
  try:
829
839
  for child in node.children:
840
+ if not self._running:
841
+ break
830
842
  self._execute_node(child)
831
843
  except CSSLBreak:
832
844
  break
@@ -846,9 +858,13 @@ class CSSLRuntime:
846
858
  step = int(self._evaluate(step_node)) if step_node else 1
847
859
 
848
860
  for i in range(start, end, step):
861
+ if not self._running:
862
+ break
849
863
  self.scope.set(var_name, i)
850
864
  try:
851
865
  for child in node.children:
866
+ if not self._running:
867
+ break
852
868
  self._execute_node(child)
853
869
  except CSSLBreak:
854
870
  break
@@ -879,7 +895,7 @@ class CSSLRuntime:
879
895
  var_name = None
880
896
 
881
897
  # Main loop
882
- while True:
898
+ while self._running:
883
899
  # Check condition
884
900
  if condition:
885
901
  cond_result = self._evaluate(condition)
@@ -890,6 +906,8 @@ class CSSLRuntime:
890
906
  # Execute body
891
907
  try:
892
908
  for child in node.children:
909
+ if not self._running:
910
+ break
893
911
  self._execute_node(child)
894
912
  except CSSLBreak:
895
913
  break
@@ -933,9 +951,13 @@ class CSSLRuntime:
933
951
  return None
934
952
 
935
953
  for item in iterable:
954
+ if not self._running:
955
+ break
936
956
  self.scope.set(var_name, item)
937
957
  try:
938
958
  for child in node.children:
959
+ if not self._running:
960
+ break
939
961
  self._execute_node(child)
940
962
  except CSSLBreak:
941
963
  break
@@ -1109,6 +1131,48 @@ class CSSLRuntime:
1109
1131
  result = result if len(result) == filter_val else None
1110
1132
  elif isinstance(result, list):
1111
1133
  result = [item for item in result if isinstance(item, str) and len(item) == filter_val]
1134
+ elif helper == 'cut':
1135
+ # Cut string at given index - returns the part BEFORE the index
1136
+ # x = <==[string::cut=2] "20:200-1" --> x = "20"
1137
+ if isinstance(result, str):
1138
+ idx = int(filter_val) if not isinstance(filter_val, int) else filter_val
1139
+ result = result[:idx] if 0 <= idx <= len(result) else result
1140
+ elif isinstance(result, list):
1141
+ result = [item[:int(filter_val)] if isinstance(item, str) else item for item in result]
1142
+ elif helper == 'cutAfter':
1143
+ # Get the part AFTER the index
1144
+ # x = <==[string::cutAfter=2] "20:200-1" --> x = ":200-1"
1145
+ if isinstance(result, str):
1146
+ idx = int(filter_val) if not isinstance(filter_val, int) else filter_val
1147
+ result = result[idx:] if 0 <= idx <= len(result) else result
1148
+ elif isinstance(result, list):
1149
+ result = [item[int(filter_val):] if isinstance(item, str) else item for item in result]
1150
+ elif helper == 'slice':
1151
+ # Slice string with start:end format (e.g., "2:5")
1152
+ if isinstance(result, str) and isinstance(filter_val, str) and ':' in filter_val:
1153
+ parts = filter_val.split(':')
1154
+ start = int(parts[0]) if parts[0] else 0
1155
+ end = int(parts[1]) if parts[1] else len(result)
1156
+ result = result[start:end]
1157
+ elif helper == 'split':
1158
+ # Split string by delimiter
1159
+ if isinstance(result, str):
1160
+ result = result.split(str(filter_val))
1161
+ elif helper == 'replace':
1162
+ # Replace in string (format: "old:new")
1163
+ if isinstance(result, str) and isinstance(filter_val, str) and ':' in filter_val:
1164
+ parts = filter_val.split(':', 1)
1165
+ if len(parts) == 2:
1166
+ result = result.replace(parts[0], parts[1])
1167
+ elif helper == 'upper':
1168
+ if isinstance(result, str):
1169
+ result = result.upper()
1170
+ elif helper == 'lower':
1171
+ if isinstance(result, str):
1172
+ result = result.lower()
1173
+ elif helper == 'trim':
1174
+ if isinstance(result, str):
1175
+ result = result.strip()
1112
1176
 
1113
1177
  # === INTEGER HELPERS ===
1114
1178
  elif filter_type == 'integer':
@@ -1387,13 +1451,18 @@ class CSSLRuntime:
1387
1451
  self._function_injections[func_name] = [code_block]
1388
1452
  self._function_replaced[func_name] = True # Mark as replaced
1389
1453
  elif mode == 'remove':
1390
- # -<<== : Remove matching code from function body
1391
- # For now, this removes all injections for the function
1454
+ # -<<== or -<<==[n] : Remove matching code from function body
1455
+ remove_index = node.value.get('index')
1456
+
1392
1457
  if func_name in self._function_injections:
1393
- self._function_injections[func_name] = []
1458
+ if remove_index is not None:
1459
+ # Indexed removal: -<<==[n] removes only the nth injection
1460
+ if 0 <= remove_index < len(self._function_injections[func_name]):
1461
+ self._function_injections[func_name].pop(remove_index)
1462
+ else:
1463
+ # No index: -<<== removes all injections
1464
+ self._function_injections[func_name] = []
1394
1465
  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
1466
 
1398
1467
  return None
1399
1468
 
@@ -1608,6 +1677,10 @@ class CSSLRuntime:
1608
1677
  return OpenQuote()
1609
1678
  elif type_name == 'array':
1610
1679
  return Array(element_type)
1680
+ elif type_name == 'list':
1681
+ return List(element_type)
1682
+ elif type_name in ('dictionary', 'dict'):
1683
+ return Dictionary(element_type)
1611
1684
  else:
1612
1685
  return None
1613
1686