IncludeCPP 3.5.2__tar.gz → 3.5.8__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.5.2 → includecpp-3.5.8}/IncludeCPP.egg-info/PKG-INFO +1 -1
- {includecpp-3.5.2 → includecpp-3.5.8}/PKG-INFO +1 -1
- {includecpp-3.5.2 → includecpp-3.5.8}/includecpp/__init__.py +1 -1
- {includecpp-3.5.2 → includecpp-3.5.8}/includecpp/core/cssl/cssl_builtins.py +23 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/includecpp/core/cssl/cssl_parser.py +112 -6
- {includecpp-3.5.2 → includecpp-3.5.8}/includecpp/core/cssl/cssl_runtime.py +250 -22
- {includecpp-3.5.2 → includecpp-3.5.8}/pyproject.toml +1 -1
- {includecpp-3.5.2 → includecpp-3.5.8}/IncludeCPP.egg-info/SOURCES.txt +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/IncludeCPP.egg-info/dependency_links.txt +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/IncludeCPP.egg-info/entry_points.txt +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/IncludeCPP.egg-info/requires.txt +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/IncludeCPP.egg-info/top_level.txt +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/LICENSE +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/MANIFEST.in +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/README.md +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/includecpp/__init__.pyi +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/includecpp/__main__.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/includecpp/cli/__init__.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/includecpp/cli/commands.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/includecpp/cli/config_parser.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/includecpp/core/__init__.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/includecpp/core/ai_integration.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/includecpp/core/build_manager.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/includecpp/core/cpp_api.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/includecpp/core/cpp_api.pyi +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/includecpp/core/cppy_converter.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/includecpp/core/cssl/CSSL_DOCUMENTATION.md +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/includecpp/core/cssl/__init__.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/includecpp/core/cssl/cssl_events.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/includecpp/core/cssl/cssl_modules.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/includecpp/core/cssl/cssl_syntax.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/includecpp/core/cssl/cssl_types.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/includecpp/core/cssl_bridge.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/includecpp/core/cssl_bridge.pyi +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/includecpp/core/error_catalog.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/includecpp/core/error_formatter.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/includecpp/core/exceptions.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/includecpp/core/path_discovery.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/includecpp/core/project_ui.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/includecpp/core/settings_ui.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/includecpp/generator/__init__.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/includecpp/generator/parser.cpp +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/includecpp/generator/parser.h +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/includecpp/generator/type_resolver.cpp +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/includecpp/generator/type_resolver.h +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/includecpp/py.typed +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/includecpp/templates/cpp.proj.template +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/includecpp/vscode/__init__.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/includecpp/vscode/cssl/__init__.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/includecpp/vscode/cssl/language-configuration.json +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/includecpp/vscode/cssl/package.json +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/includecpp/vscode/cssl/syntaxes/cssl.tmLanguage.json +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/requirements.txt +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/setup.cfg +0 -0
- {includecpp-3.5.2 → includecpp-3.5.8}/setup.py +0 -0
|
@@ -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
|
|
|
@@ -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()
|
|
@@ -242,6 +243,9 @@ class CSSLLexer:
|
|
|
242
243
|
elif char == '$':
|
|
243
244
|
# $<name> shared object reference
|
|
244
245
|
self._read_shared_ref()
|
|
246
|
+
elif char == '%':
|
|
247
|
+
# %<name> captured reference (for infusion)
|
|
248
|
+
self._read_captured_ref()
|
|
245
249
|
elif char == '&':
|
|
246
250
|
# & for references
|
|
247
251
|
if self._peek(1) == '&':
|
|
@@ -494,6 +498,20 @@ class CSSLLexer:
|
|
|
494
498
|
self.error("Expected identifier after '$'")
|
|
495
499
|
self._add_token(TokenType.SHARED_REF, value)
|
|
496
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
|
+
|
|
497
515
|
def _read_less_than(self):
|
|
498
516
|
# Check for <<== (code infusion left)
|
|
499
517
|
if self._peek(1) == '<' and self._peek(2) == '=' and self._peek(3) == '=':
|
|
@@ -541,10 +559,10 @@ class CSSLLexer:
|
|
|
541
559
|
elif self._peek(1) == '=' and self._peek(2) == '>' and self._peek(3) == '+':
|
|
542
560
|
self._add_token(TokenType.INJECT_PLUS_RIGHT, '==>+')
|
|
543
561
|
for _ in range(4): self._advance()
|
|
544
|
-
# Check for
|
|
545
|
-
elif self._peek(1) == '=' and self._peek(2) == '
|
|
546
|
-
self._add_token(TokenType.INJECT_MINUS_RIGHT, '
|
|
547
|
-
for _ in range(
|
|
562
|
+
# Check for ==>- (injection right minus - moves & removes)
|
|
563
|
+
elif self._peek(1) == '=' and self._peek(2) == '>' and self._peek(3) == '-':
|
|
564
|
+
self._add_token(TokenType.INJECT_MINUS_RIGHT, '==>-')
|
|
565
|
+
for _ in range(4): self._advance()
|
|
548
566
|
# Check for ==> (basic injection right)
|
|
549
567
|
elif self._peek(1) == '=' and self._peek(2) == '>':
|
|
550
568
|
self._add_token(TokenType.INJECT_RIGHT, '==>')
|
|
@@ -1294,7 +1312,9 @@ class CSSLParser:
|
|
|
1294
1312
|
elif self._looks_like_function_declaration():
|
|
1295
1313
|
# Nested typed function (e.g., void Level2() { ... })
|
|
1296
1314
|
return self._parse_typed_function()
|
|
1297
|
-
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)):
|
|
1298
1318
|
return self._parse_expression_statement()
|
|
1299
1319
|
else:
|
|
1300
1320
|
self._advance()
|
|
@@ -2058,6 +2078,31 @@ class CSSLParser:
|
|
|
2058
2078
|
break
|
|
2059
2079
|
return node
|
|
2060
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
|
+
|
|
2061
2106
|
if self._check(TokenType.NUMBER):
|
|
2062
2107
|
return ASTNode('literal', value=self._advance().value)
|
|
2063
2108
|
|
|
@@ -2082,7 +2127,13 @@ class CSSLParser:
|
|
|
2082
2127
|
return expr
|
|
2083
2128
|
|
|
2084
2129
|
if self._match(TokenType.BLOCK_START):
|
|
2085
|
-
|
|
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()
|
|
2086
2137
|
|
|
2087
2138
|
if self._match(TokenType.BRACKET_START):
|
|
2088
2139
|
return self._parse_array()
|
|
@@ -2191,6 +2242,61 @@ class CSSLParser:
|
|
|
2191
2242
|
|
|
2192
2243
|
return node
|
|
2193
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
|
+
|
|
2194
2300
|
def _parse_object(self) -> ASTNode:
|
|
2195
2301
|
properties = {}
|
|
2196
2302
|
|
|
@@ -139,8 +139,11 @@ class CSSLRuntime:
|
|
|
139
139
|
self.services: Dict[str, ServiceDefinition] = {}
|
|
140
140
|
self._modules: Dict[str, Any] = {}
|
|
141
141
|
self._global_structs: Dict[str, Any] = {} # Global structs for s@<name> references
|
|
142
|
-
self._function_injections: Dict[str, List[
|
|
142
|
+
self._function_injections: Dict[str, List[tuple]] = {} # List of (code_block, captured_values_dict)
|
|
143
143
|
self._function_replaced: Dict[str, bool] = {} # NEW: Track replaced functions (<<==)
|
|
144
|
+
self._original_functions: Dict[str, Any] = {} # Store originals before replacement
|
|
145
|
+
self._injection_captures: Dict[str, Dict[str, Any]] = {} # Captured %vars per injection
|
|
146
|
+
self._current_captured_values: Dict[str, Any] = {} # Current captured values during injection execution
|
|
144
147
|
self._promoted_globals: Dict[str, Any] = {} # NEW: Variables promoted via global()
|
|
145
148
|
self._running = False
|
|
146
149
|
self._exit_code = 0
|
|
@@ -799,6 +802,9 @@ class CSSLRuntime:
|
|
|
799
802
|
|
|
800
803
|
try:
|
|
801
804
|
for child in func_node.children:
|
|
805
|
+
# Check if exit() was called
|
|
806
|
+
if not self._running:
|
|
807
|
+
break
|
|
802
808
|
self._execute_node(child)
|
|
803
809
|
except CSSLReturn as ret:
|
|
804
810
|
return ret.value
|
|
@@ -1132,21 +1138,49 @@ class CSSLRuntime:
|
|
|
1132
1138
|
elif isinstance(result, list):
|
|
1133
1139
|
result = [item for item in result if isinstance(item, str) and len(item) == filter_val]
|
|
1134
1140
|
elif helper == 'cut':
|
|
1135
|
-
# Cut string
|
|
1141
|
+
# Cut string - returns the part BEFORE the index/substring
|
|
1136
1142
|
# x = <==[string::cut=2] "20:200-1" --> x = "20"
|
|
1143
|
+
# x = <==[string::cut="1.0"] "1.0.0" --> x = "" (before "1.0")
|
|
1137
1144
|
if isinstance(result, str):
|
|
1138
|
-
|
|
1139
|
-
|
|
1145
|
+
if isinstance(filter_val, str):
|
|
1146
|
+
# Cut at substring position
|
|
1147
|
+
idx = result.find(filter_val)
|
|
1148
|
+
result = result[:idx] if idx >= 0 else result
|
|
1149
|
+
else:
|
|
1150
|
+
# Cut at integer index
|
|
1151
|
+
idx = int(filter_val)
|
|
1152
|
+
result = result[:idx] if 0 <= idx <= len(result) else result
|
|
1140
1153
|
elif isinstance(result, list):
|
|
1141
|
-
|
|
1154
|
+
def cut_item(item):
|
|
1155
|
+
if not isinstance(item, str):
|
|
1156
|
+
return item
|
|
1157
|
+
if isinstance(filter_val, str):
|
|
1158
|
+
idx = item.find(filter_val)
|
|
1159
|
+
return item[:idx] if idx >= 0 else item
|
|
1160
|
+
return item[:int(filter_val)]
|
|
1161
|
+
result = [cut_item(item) for item in result]
|
|
1142
1162
|
elif helper == 'cutAfter':
|
|
1143
|
-
# Get the part AFTER the index
|
|
1163
|
+
# Get the part AFTER the index/substring
|
|
1144
1164
|
# x = <==[string::cutAfter=2] "20:200-1" --> x = ":200-1"
|
|
1165
|
+
# x = <==[string::cutAfter="1.0"] "1.0.0" --> x = ".0" (after "1.0")
|
|
1145
1166
|
if isinstance(result, str):
|
|
1146
|
-
|
|
1147
|
-
|
|
1167
|
+
if isinstance(filter_val, str):
|
|
1168
|
+
# Cut after substring
|
|
1169
|
+
idx = result.find(filter_val)
|
|
1170
|
+
result = result[idx + len(filter_val):] if idx >= 0 else result
|
|
1171
|
+
else:
|
|
1172
|
+
# Cut after integer index
|
|
1173
|
+
idx = int(filter_val)
|
|
1174
|
+
result = result[idx:] if 0 <= idx <= len(result) else result
|
|
1148
1175
|
elif isinstance(result, list):
|
|
1149
|
-
|
|
1176
|
+
def cut_after_item(item):
|
|
1177
|
+
if not isinstance(item, str):
|
|
1178
|
+
return item
|
|
1179
|
+
if isinstance(filter_val, str):
|
|
1180
|
+
idx = item.find(filter_val)
|
|
1181
|
+
return item[idx + len(filter_val):] if idx >= 0 else item
|
|
1182
|
+
return item[int(filter_val):]
|
|
1183
|
+
result = [cut_after_item(item) for item in result]
|
|
1150
1184
|
elif helper == 'slice':
|
|
1151
1185
|
# Slice string with start:end format (e.g., "2:5")
|
|
1152
1186
|
if isinstance(result, str) and isinstance(filter_val, str) and ':' in filter_val:
|
|
@@ -1284,8 +1318,21 @@ class CSSLRuntime:
|
|
|
1284
1318
|
self.register_function_injection(func_name, source_node)
|
|
1285
1319
|
return None
|
|
1286
1320
|
|
|
1287
|
-
#
|
|
1288
|
-
|
|
1321
|
+
# Check if source is an action_block with %<name> captures
|
|
1322
|
+
# If so, capture values NOW and evaluate the block with those captures
|
|
1323
|
+
if isinstance(source_node, ASTNode) and source_node.type == 'action_block':
|
|
1324
|
+
# Scan for %<name> captured references and capture their current values
|
|
1325
|
+
captured_values = self._scan_and_capture_refs(source_node)
|
|
1326
|
+
old_captured = self._current_captured_values.copy()
|
|
1327
|
+
self._current_captured_values = captured_values
|
|
1328
|
+
try:
|
|
1329
|
+
# Execute the action block and get the last expression's value
|
|
1330
|
+
source = self._evaluate_action_block(source_node)
|
|
1331
|
+
finally:
|
|
1332
|
+
self._current_captured_values = old_captured
|
|
1333
|
+
else:
|
|
1334
|
+
# Evaluate source normally
|
|
1335
|
+
source = self._evaluate(source_node)
|
|
1289
1336
|
|
|
1290
1337
|
# Apply filter if present
|
|
1291
1338
|
if filter_info:
|
|
@@ -1424,11 +1471,20 @@ class CSSLRuntime:
|
|
|
1424
1471
|
- replace: func <<== { code } - REPLACES function body (original won't execute)
|
|
1425
1472
|
- add: func +<<== { code } - ADDS code to function (both execute)
|
|
1426
1473
|
- remove: func -<<== { code } - REMOVES matching code from function
|
|
1474
|
+
|
|
1475
|
+
Also supports expression form: func <<== %exit() (wraps in action_block)
|
|
1427
1476
|
"""
|
|
1428
1477
|
target = node.value.get('target')
|
|
1429
1478
|
code_block = node.value.get('code')
|
|
1479
|
+
source_expr = node.value.get('source') # For expression form: func <<== expr
|
|
1430
1480
|
mode = node.value.get('mode', 'replace') # Default is REPLACE for <<==
|
|
1431
1481
|
|
|
1482
|
+
# If source expression is provided instead of code block, wrap it
|
|
1483
|
+
if code_block is None and source_expr is not None:
|
|
1484
|
+
# Wrap in expression node so _execute_node can handle it
|
|
1485
|
+
expr_node = ASTNode('expression', value=source_expr)
|
|
1486
|
+
code_block = ASTNode('action_block', children=[expr_node])
|
|
1487
|
+
|
|
1432
1488
|
# Get function name from target
|
|
1433
1489
|
func_name = None
|
|
1434
1490
|
if isinstance(target, ASTNode):
|
|
@@ -1439,7 +1495,7 @@ class CSSLRuntime:
|
|
|
1439
1495
|
if isinstance(callee, ASTNode) and callee.type == 'identifier':
|
|
1440
1496
|
func_name = callee.value
|
|
1441
1497
|
|
|
1442
|
-
if not func_name:
|
|
1498
|
+
if not func_name or code_block is None:
|
|
1443
1499
|
return None
|
|
1444
1500
|
|
|
1445
1501
|
if mode == 'add':
|
|
@@ -1448,7 +1504,17 @@ class CSSLRuntime:
|
|
|
1448
1504
|
self._function_replaced[func_name] = False # Don't replace, just add
|
|
1449
1505
|
elif mode == 'replace':
|
|
1450
1506
|
# <<== : Replace function body (only injection executes, original skipped)
|
|
1451
|
-
|
|
1507
|
+
# Save original function BEFORE replacing (for original() access)
|
|
1508
|
+
if func_name not in self._original_functions:
|
|
1509
|
+
# Try to find original in scope or builtins
|
|
1510
|
+
original = self.scope.get(func_name)
|
|
1511
|
+
if original is None:
|
|
1512
|
+
original = getattr(self.builtins, f'builtin_{func_name}', None)
|
|
1513
|
+
if original is not None:
|
|
1514
|
+
self._original_functions[func_name] = original
|
|
1515
|
+
# Capture %<name> references at registration time
|
|
1516
|
+
captured_values = self._scan_and_capture_refs(code_block)
|
|
1517
|
+
self._function_injections[func_name] = [(code_block, captured_values)]
|
|
1452
1518
|
self._function_replaced[func_name] = True # Mark as replaced
|
|
1453
1519
|
elif mode == 'remove':
|
|
1454
1520
|
# -<<== or -<<==[n] : Remove matching code from function body
|
|
@@ -1654,6 +1720,33 @@ class CSSLRuntime:
|
|
|
1654
1720
|
return scoped_val
|
|
1655
1721
|
raise CSSLRuntimeError(f"Shared object '${name}' not found. Use share() to share objects.")
|
|
1656
1722
|
|
|
1723
|
+
if node.type == 'captured_ref':
|
|
1724
|
+
# %<name> captured reference - use value captured at infusion registration time
|
|
1725
|
+
name = node.value
|
|
1726
|
+
# First check captured values from current injection context
|
|
1727
|
+
if name in self._current_captured_values:
|
|
1728
|
+
captured_value = self._current_captured_values[name]
|
|
1729
|
+
# Only use captured value if it's not None
|
|
1730
|
+
if captured_value is not None:
|
|
1731
|
+
return captured_value
|
|
1732
|
+
# Fall back to normal resolution if not captured or capture was None
|
|
1733
|
+
value = self.scope.get(name)
|
|
1734
|
+
if value is None:
|
|
1735
|
+
value = self.global_scope.get(name)
|
|
1736
|
+
if value is None:
|
|
1737
|
+
# For critical builtins like 'exit', create direct wrapper
|
|
1738
|
+
if name == 'exit':
|
|
1739
|
+
runtime = self
|
|
1740
|
+
value = lambda code=0, rt=runtime: rt.exit(code)
|
|
1741
|
+
else:
|
|
1742
|
+
value = getattr(self.builtins, f'builtin_{name}', None)
|
|
1743
|
+
if value is None:
|
|
1744
|
+
# Check original functions (for replaced functions)
|
|
1745
|
+
value = self._original_functions.get(name)
|
|
1746
|
+
if value is not None:
|
|
1747
|
+
return value
|
|
1748
|
+
raise CSSLRuntimeError(f"Captured reference '%{name}' not found.")
|
|
1749
|
+
|
|
1657
1750
|
if node.type == 'type_instantiation':
|
|
1658
1751
|
# Create new instance of a type: stack<string>, vector<int>, etc.
|
|
1659
1752
|
type_name = node.value.get('type')
|
|
@@ -1720,8 +1813,41 @@ class CSSLRuntime:
|
|
|
1720
1813
|
return {'__ref__': True, 'name': inner.value, 'value': self.get_module(inner.value)}
|
|
1721
1814
|
return {'__ref__': True, 'value': self._evaluate(inner)}
|
|
1722
1815
|
|
|
1816
|
+
# Handle action_block - execute and return last expression value
|
|
1817
|
+
if node.type == 'action_block':
|
|
1818
|
+
return self._evaluate_action_block(node)
|
|
1819
|
+
|
|
1723
1820
|
return None
|
|
1724
1821
|
|
|
1822
|
+
def _evaluate_action_block(self, node: ASTNode) -> Any:
|
|
1823
|
+
"""Evaluate an action block and return the last expression's value.
|
|
1824
|
+
|
|
1825
|
+
Used for: v <== { %version; } - captures %version at this moment
|
|
1826
|
+
|
|
1827
|
+
Returns the value of the last expression in the block.
|
|
1828
|
+
If the block contains a captured_ref (%name), that's what gets returned.
|
|
1829
|
+
"""
|
|
1830
|
+
last_value = None
|
|
1831
|
+
for child in node.children:
|
|
1832
|
+
if child.type == 'captured_ref':
|
|
1833
|
+
# Direct captured reference - return its value
|
|
1834
|
+
last_value = self._evaluate(child)
|
|
1835
|
+
elif child.type == 'expression':
|
|
1836
|
+
# Expression statement - evaluate and keep value
|
|
1837
|
+
last_value = self._evaluate(child.value if hasattr(child, 'value') else child)
|
|
1838
|
+
elif child.type == 'identifier':
|
|
1839
|
+
# Just an identifier - evaluate it
|
|
1840
|
+
last_value = self._evaluate(child)
|
|
1841
|
+
elif child.type in ('call', 'member_access', 'binary', 'unary'):
|
|
1842
|
+
# Expression types
|
|
1843
|
+
last_value = self._evaluate(child)
|
|
1844
|
+
else:
|
|
1845
|
+
# Execute other statements
|
|
1846
|
+
result = self._execute_node(child)
|
|
1847
|
+
if result is not None:
|
|
1848
|
+
last_value = result
|
|
1849
|
+
return last_value
|
|
1850
|
+
|
|
1725
1851
|
def _eval_binary(self, node: ASTNode) -> Any:
|
|
1726
1852
|
"""Evaluate binary operation with auto-casting support"""
|
|
1727
1853
|
op = node.value.get('op')
|
|
@@ -1893,10 +2019,9 @@ class CSSLRuntime:
|
|
|
1893
2019
|
def _eval_call(self, node: ASTNode) -> Any:
|
|
1894
2020
|
"""Evaluate function call"""
|
|
1895
2021
|
callee_node = node.value.get('callee')
|
|
1896
|
-
callee = self._evaluate(callee_node)
|
|
1897
2022
|
args = [self._evaluate(a) for a in node.value.get('args', [])]
|
|
1898
2023
|
|
|
1899
|
-
# Get function name for injection check
|
|
2024
|
+
# Get function name for injection check FIRST (before evaluating callee)
|
|
1900
2025
|
func_name = None
|
|
1901
2026
|
if isinstance(callee_node, ASTNode):
|
|
1902
2027
|
if callee_node.type == 'identifier':
|
|
@@ -1908,13 +2033,18 @@ class CSSLRuntime:
|
|
|
1908
2033
|
has_injections = func_name and func_name in self._function_injections
|
|
1909
2034
|
is_replaced = func_name and self._function_replaced.get(func_name, False)
|
|
1910
2035
|
|
|
1911
|
-
#
|
|
1912
|
-
|
|
2036
|
+
# If function is FULLY REPLACED (<<==), run injection and skip original
|
|
2037
|
+
# This allows creating new functions via infusion: new_func <<== { ... }
|
|
2038
|
+
if is_replaced:
|
|
1913
2039
|
self._execute_function_injections(func_name)
|
|
2040
|
+
return None # Injection ran, don't try to find original
|
|
1914
2041
|
|
|
1915
|
-
#
|
|
1916
|
-
|
|
1917
|
-
|
|
2042
|
+
# Now evaluate the callee (only if not replaced)
|
|
2043
|
+
callee = self._evaluate(callee_node)
|
|
2044
|
+
|
|
2045
|
+
# Execute added injections (+<<==) before original
|
|
2046
|
+
if has_injections and not is_replaced:
|
|
2047
|
+
self._execute_function_injections(func_name)
|
|
1918
2048
|
|
|
1919
2049
|
# Execute original function
|
|
1920
2050
|
if callable(callee):
|
|
@@ -2329,22 +2459,107 @@ class CSSLRuntime:
|
|
|
2329
2459
|
# Also store in promoted globals for string interpolation
|
|
2330
2460
|
self._promoted_globals[base_name] = value
|
|
2331
2461
|
|
|
2462
|
+
# NEW: Scan for captured_ref nodes and capture their current values
|
|
2463
|
+
def _scan_and_capture_refs(self, node: ASTNode) -> Dict[str, Any]:
|
|
2464
|
+
"""Scan AST for %<name> captured references and capture their current values.
|
|
2465
|
+
|
|
2466
|
+
This is called at infusion registration time to capture values.
|
|
2467
|
+
Example: old_exit <<== { %exit(); } captures 'exit' at definition time.
|
|
2468
|
+
"""
|
|
2469
|
+
captured = {}
|
|
2470
|
+
|
|
2471
|
+
def scan_node(n):
|
|
2472
|
+
if not isinstance(n, ASTNode):
|
|
2473
|
+
return
|
|
2474
|
+
|
|
2475
|
+
# Found a captured_ref - capture its current value
|
|
2476
|
+
if n.type == 'captured_ref':
|
|
2477
|
+
name = n.value
|
|
2478
|
+
if name not in captured:
|
|
2479
|
+
# Try to find value - check multiple sources
|
|
2480
|
+
value = None
|
|
2481
|
+
|
|
2482
|
+
# 1. Check _original_functions first (for functions that were JUST replaced)
|
|
2483
|
+
if value is None:
|
|
2484
|
+
value = self._original_functions.get(name)
|
|
2485
|
+
|
|
2486
|
+
# 2. Check scope
|
|
2487
|
+
if value is None:
|
|
2488
|
+
value = self.scope.get(name)
|
|
2489
|
+
|
|
2490
|
+
# 3. Check global_scope
|
|
2491
|
+
if value is None:
|
|
2492
|
+
value = self.global_scope.get(name)
|
|
2493
|
+
|
|
2494
|
+
# 4. Check builtins (most common case for exit, print, etc.)
|
|
2495
|
+
if value is None:
|
|
2496
|
+
# For critical builtins like 'exit', create a direct wrapper
|
|
2497
|
+
# that captures the runtime reference to ensure correct behavior
|
|
2498
|
+
if name == 'exit':
|
|
2499
|
+
runtime = self # Capture runtime in closure
|
|
2500
|
+
value = lambda code=0, rt=runtime: rt.exit(code)
|
|
2501
|
+
else:
|
|
2502
|
+
value = getattr(self.builtins, f'builtin_{name}', None)
|
|
2503
|
+
|
|
2504
|
+
# 5. Check if there's a user-defined function in scope
|
|
2505
|
+
if value is None:
|
|
2506
|
+
# Look for function definitions
|
|
2507
|
+
func_def = self.global_scope.get(f'__func_{name}')
|
|
2508
|
+
if func_def is not None:
|
|
2509
|
+
value = func_def
|
|
2510
|
+
|
|
2511
|
+
# Only capture if we found something
|
|
2512
|
+
if value is not None:
|
|
2513
|
+
captured[name] = value
|
|
2514
|
+
|
|
2515
|
+
# Check call node's callee
|
|
2516
|
+
if n.type == 'call':
|
|
2517
|
+
callee = n.value.get('callee')
|
|
2518
|
+
if callee:
|
|
2519
|
+
scan_node(callee)
|
|
2520
|
+
for arg in n.value.get('args', []):
|
|
2521
|
+
scan_node(arg)
|
|
2522
|
+
|
|
2523
|
+
# Recurse into children
|
|
2524
|
+
if hasattr(n, 'children') and n.children:
|
|
2525
|
+
for child in n.children:
|
|
2526
|
+
scan_node(child)
|
|
2527
|
+
|
|
2528
|
+
# Check value dict for nested nodes
|
|
2529
|
+
if hasattr(n, 'value') and isinstance(n.value, dict):
|
|
2530
|
+
for key, val in n.value.items():
|
|
2531
|
+
if isinstance(val, ASTNode):
|
|
2532
|
+
scan_node(val)
|
|
2533
|
+
elif isinstance(val, list):
|
|
2534
|
+
for item in val:
|
|
2535
|
+
if isinstance(item, ASTNode):
|
|
2536
|
+
scan_node(item)
|
|
2537
|
+
|
|
2538
|
+
scan_node(node)
|
|
2539
|
+
return captured
|
|
2540
|
+
|
|
2332
2541
|
# NEW: Register permanent function injection
|
|
2333
2542
|
def register_function_injection(self, func_name: str, code_block: ASTNode):
|
|
2334
2543
|
"""Register code to be permanently injected into a function - NEW
|
|
2335
2544
|
|
|
2336
2545
|
Example: exit() <== { println("Cleanup..."); }
|
|
2337
2546
|
Makes every call to exit() also execute the injected code
|
|
2547
|
+
|
|
2548
|
+
Captures %<name> references at registration time.
|
|
2338
2549
|
"""
|
|
2550
|
+
# Scan for %<name> captured references and capture their current values
|
|
2551
|
+
captured_values = self._scan_and_capture_refs(code_block)
|
|
2552
|
+
|
|
2339
2553
|
if func_name not in self._function_injections:
|
|
2340
2554
|
self._function_injections[func_name] = []
|
|
2341
|
-
self._function_injections[func_name].append(code_block)
|
|
2555
|
+
self._function_injections[func_name].append((code_block, captured_values))
|
|
2342
2556
|
|
|
2343
2557
|
# NEW: Execute injected code for a function
|
|
2344
2558
|
def _execute_function_injections(self, func_name: str):
|
|
2345
2559
|
"""Execute all injected code blocks for a function - NEW
|
|
2346
2560
|
|
|
2347
2561
|
Includes protection against recursive execution to prevent doubled output.
|
|
2562
|
+
Uses captured values for %<name> references.
|
|
2348
2563
|
"""
|
|
2349
2564
|
# Prevent recursive injection execution (fixes doubled output bug)
|
|
2350
2565
|
if getattr(self, '_injection_executing', False):
|
|
@@ -2352,16 +2567,29 @@ class CSSLRuntime:
|
|
|
2352
2567
|
|
|
2353
2568
|
if func_name in self._function_injections:
|
|
2354
2569
|
self._injection_executing = True
|
|
2570
|
+
old_captured = self._current_captured_values.copy()
|
|
2355
2571
|
try:
|
|
2356
|
-
for
|
|
2572
|
+
for injection in self._function_injections[func_name]:
|
|
2573
|
+
# Handle both tuple format (code_block, captured_values) and legacy ASTNode format
|
|
2574
|
+
if isinstance(injection, tuple):
|
|
2575
|
+
code_block, captured_values = injection
|
|
2576
|
+
self._current_captured_values = captured_values
|
|
2577
|
+
else:
|
|
2578
|
+
code_block = injection
|
|
2579
|
+
self._current_captured_values = {}
|
|
2580
|
+
|
|
2357
2581
|
if isinstance(code_block, ASTNode):
|
|
2358
2582
|
if code_block.type == 'action_block':
|
|
2359
2583
|
for child in code_block.children:
|
|
2584
|
+
# Check if exit() was called
|
|
2585
|
+
if not self._running:
|
|
2586
|
+
break
|
|
2360
2587
|
self._execute_node(child)
|
|
2361
2588
|
else:
|
|
2362
2589
|
self._execute_node(code_block)
|
|
2363
2590
|
finally:
|
|
2364
2591
|
self._injection_executing = False
|
|
2592
|
+
self._current_captured_values = old_captured
|
|
2365
2593
|
|
|
2366
2594
|
# Output functions for builtins
|
|
2367
2595
|
def set_output_callback(self, callback: Callable[[str, str], None]):
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "IncludeCPP"
|
|
7
|
-
version = "3.5.
|
|
7
|
+
version = "3.5.8"
|
|
8
8
|
description = "Professional C++ Python bindings with type-generic templates, pystubs and native threading"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|