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.
- {includecpp-3.4.22 → includecpp-3.5.7}/IncludeCPP.egg-info/PKG-INFO +1 -1
- {includecpp-3.4.22 → includecpp-3.5.7}/PKG-INFO +1 -1
- {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/__init__.py +1 -1
- {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/cli/commands.py +32 -6
- {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/cssl/cssl_builtins.py +64 -2
- {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/cssl/cssl_parser.py +169 -15
- {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/cssl/cssl_runtime.py +302 -29
- {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/cssl/cssl_types.py +339 -2
- {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/cssl_bridge.py +100 -4
- {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/cssl_bridge.pyi +177 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/pyproject.toml +1 -1
- {includecpp-3.4.22 → includecpp-3.5.7}/IncludeCPP.egg-info/SOURCES.txt +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/IncludeCPP.egg-info/dependency_links.txt +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/IncludeCPP.egg-info/entry_points.txt +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/IncludeCPP.egg-info/requires.txt +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/IncludeCPP.egg-info/top_level.txt +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/LICENSE +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/MANIFEST.in +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/README.md +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/__init__.pyi +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/__main__.py +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/cli/__init__.py +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/cli/config_parser.py +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/__init__.py +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/ai_integration.py +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/build_manager.py +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/cpp_api.py +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/cpp_api.pyi +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/cppy_converter.py +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/cssl/CSSL_DOCUMENTATION.md +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/cssl/__init__.py +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/cssl/cssl_events.py +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/cssl/cssl_modules.py +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/cssl/cssl_syntax.py +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/error_catalog.py +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/error_formatter.py +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/exceptions.py +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/path_discovery.py +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/project_ui.py +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/core/settings_ui.py +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/generator/__init__.py +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/generator/parser.cpp +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/generator/parser.h +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/generator/type_resolver.cpp +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/generator/type_resolver.h +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/py.typed +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/templates/cpp.proj.template +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/vscode/__init__.py +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/vscode/cssl/__init__.py +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/vscode/cssl/language-configuration.json +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/vscode/cssl/package.json +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/includecpp/vscode/cssl/syntaxes/cssl.tmLanguage.json +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/requirements.txt +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/setup.cfg +0 -0
- {includecpp-3.4.22 → includecpp-3.5.7}/setup.py +0 -0
|
@@ -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
|
|
7146
|
+
def exec_repl(lang, path, import_all):
|
|
7147
7147
|
"""Execute code interactively for quick testing.
|
|
7148
7148
|
|
|
7149
|
-
Run Python
|
|
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
|
-
|
|
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:
|
|
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(
|
|
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
|
-
|
|
1522
|
-
self._expect(TokenType.COMMA)
|
|
1523
|
-
end = self._parse_expression()
|
|
1550
|
+
first_arg = self._parse_expression()
|
|
1524
1551
|
|
|
1525
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|