IncludeCPP 3.5.2__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.5.2 → includecpp-3.5.7}/IncludeCPP.egg-info/PKG-INFO +1 -1
- {includecpp-3.5.2 → includecpp-3.5.7}/PKG-INFO +1 -1
- {includecpp-3.5.2 → includecpp-3.5.7}/includecpp/__init__.py +1 -1
- {includecpp-3.5.2 → includecpp-3.5.7}/includecpp/core/cssl/cssl_builtins.py +23 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/includecpp/core/cssl/cssl_parser.py +108 -2
- {includecpp-3.5.2 → includecpp-3.5.7}/includecpp/core/cssl/cssl_runtime.py +214 -14
- {includecpp-3.5.2 → includecpp-3.5.7}/pyproject.toml +1 -1
- {includecpp-3.5.2 → includecpp-3.5.7}/IncludeCPP.egg-info/SOURCES.txt +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/IncludeCPP.egg-info/dependency_links.txt +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/IncludeCPP.egg-info/entry_points.txt +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/IncludeCPP.egg-info/requires.txt +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/IncludeCPP.egg-info/top_level.txt +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/LICENSE +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/MANIFEST.in +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/README.md +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/includecpp/__init__.pyi +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/includecpp/__main__.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/includecpp/cli/__init__.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/includecpp/cli/commands.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/includecpp/cli/config_parser.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/includecpp/core/__init__.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/includecpp/core/ai_integration.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/includecpp/core/build_manager.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/includecpp/core/cpp_api.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/includecpp/core/cpp_api.pyi +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/includecpp/core/cppy_converter.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/includecpp/core/cssl/CSSL_DOCUMENTATION.md +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/includecpp/core/cssl/__init__.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/includecpp/core/cssl/cssl_events.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/includecpp/core/cssl/cssl_modules.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/includecpp/core/cssl/cssl_syntax.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/includecpp/core/cssl/cssl_types.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/includecpp/core/cssl_bridge.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/includecpp/core/cssl_bridge.pyi +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/includecpp/core/error_catalog.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/includecpp/core/error_formatter.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/includecpp/core/exceptions.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/includecpp/core/path_discovery.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/includecpp/core/project_ui.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/includecpp/core/settings_ui.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/includecpp/generator/__init__.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/includecpp/generator/parser.cpp +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/includecpp/generator/parser.h +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/includecpp/generator/type_resolver.cpp +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/includecpp/generator/type_resolver.h +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/includecpp/py.typed +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/includecpp/templates/cpp.proj.template +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/includecpp/vscode/__init__.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/includecpp/vscode/cssl/__init__.py +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/includecpp/vscode/cssl/language-configuration.json +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/includecpp/vscode/cssl/package.json +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/includecpp/vscode/cssl/syntaxes/cssl.tmLanguage.json +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/requirements.txt +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/setup.cfg +0 -0
- {includecpp-3.5.2 → includecpp-3.5.7}/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) == '=':
|
|
@@ -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
|
|
@@ -1284,8 +1290,21 @@ class CSSLRuntime:
|
|
|
1284
1290
|
self.register_function_injection(func_name, source_node)
|
|
1285
1291
|
return None
|
|
1286
1292
|
|
|
1287
|
-
#
|
|
1288
|
-
|
|
1293
|
+
# Check if source is an action_block with %<name> captures
|
|
1294
|
+
# If so, capture values NOW and evaluate the block with those captures
|
|
1295
|
+
if isinstance(source_node, ASTNode) and source_node.type == 'action_block':
|
|
1296
|
+
# Scan for %<name> captured references and capture their current values
|
|
1297
|
+
captured_values = self._scan_and_capture_refs(source_node)
|
|
1298
|
+
old_captured = self._current_captured_values.copy()
|
|
1299
|
+
self._current_captured_values = captured_values
|
|
1300
|
+
try:
|
|
1301
|
+
# Execute the action block and get the last expression's value
|
|
1302
|
+
source = self._evaluate_action_block(source_node)
|
|
1303
|
+
finally:
|
|
1304
|
+
self._current_captured_values = old_captured
|
|
1305
|
+
else:
|
|
1306
|
+
# Evaluate source normally
|
|
1307
|
+
source = self._evaluate(source_node)
|
|
1289
1308
|
|
|
1290
1309
|
# Apply filter if present
|
|
1291
1310
|
if filter_info:
|
|
@@ -1424,11 +1443,20 @@ class CSSLRuntime:
|
|
|
1424
1443
|
- replace: func <<== { code } - REPLACES function body (original won't execute)
|
|
1425
1444
|
- add: func +<<== { code } - ADDS code to function (both execute)
|
|
1426
1445
|
- remove: func -<<== { code } - REMOVES matching code from function
|
|
1446
|
+
|
|
1447
|
+
Also supports expression form: func <<== %exit() (wraps in action_block)
|
|
1427
1448
|
"""
|
|
1428
1449
|
target = node.value.get('target')
|
|
1429
1450
|
code_block = node.value.get('code')
|
|
1451
|
+
source_expr = node.value.get('source') # For expression form: func <<== expr
|
|
1430
1452
|
mode = node.value.get('mode', 'replace') # Default is REPLACE for <<==
|
|
1431
1453
|
|
|
1454
|
+
# If source expression is provided instead of code block, wrap it
|
|
1455
|
+
if code_block is None and source_expr is not None:
|
|
1456
|
+
# Wrap in expression node so _execute_node can handle it
|
|
1457
|
+
expr_node = ASTNode('expression', value=source_expr)
|
|
1458
|
+
code_block = ASTNode('action_block', children=[expr_node])
|
|
1459
|
+
|
|
1432
1460
|
# Get function name from target
|
|
1433
1461
|
func_name = None
|
|
1434
1462
|
if isinstance(target, ASTNode):
|
|
@@ -1439,7 +1467,7 @@ class CSSLRuntime:
|
|
|
1439
1467
|
if isinstance(callee, ASTNode) and callee.type == 'identifier':
|
|
1440
1468
|
func_name = callee.value
|
|
1441
1469
|
|
|
1442
|
-
if not func_name:
|
|
1470
|
+
if not func_name or code_block is None:
|
|
1443
1471
|
return None
|
|
1444
1472
|
|
|
1445
1473
|
if mode == 'add':
|
|
@@ -1448,7 +1476,17 @@ class CSSLRuntime:
|
|
|
1448
1476
|
self._function_replaced[func_name] = False # Don't replace, just add
|
|
1449
1477
|
elif mode == 'replace':
|
|
1450
1478
|
# <<== : Replace function body (only injection executes, original skipped)
|
|
1451
|
-
|
|
1479
|
+
# Save original function BEFORE replacing (for original() access)
|
|
1480
|
+
if func_name not in self._original_functions:
|
|
1481
|
+
# Try to find original in scope or builtins
|
|
1482
|
+
original = self.scope.get(func_name)
|
|
1483
|
+
if original is None:
|
|
1484
|
+
original = getattr(self.builtins, f'builtin_{func_name}', None)
|
|
1485
|
+
if original is not None:
|
|
1486
|
+
self._original_functions[func_name] = original
|
|
1487
|
+
# Capture %<name> references at registration time
|
|
1488
|
+
captured_values = self._scan_and_capture_refs(code_block)
|
|
1489
|
+
self._function_injections[func_name] = [(code_block, captured_values)]
|
|
1452
1490
|
self._function_replaced[func_name] = True # Mark as replaced
|
|
1453
1491
|
elif mode == 'remove':
|
|
1454
1492
|
# -<<== or -<<==[n] : Remove matching code from function body
|
|
@@ -1654,6 +1692,33 @@ class CSSLRuntime:
|
|
|
1654
1692
|
return scoped_val
|
|
1655
1693
|
raise CSSLRuntimeError(f"Shared object '${name}' not found. Use share() to share objects.")
|
|
1656
1694
|
|
|
1695
|
+
if node.type == 'captured_ref':
|
|
1696
|
+
# %<name> captured reference - use value captured at infusion registration time
|
|
1697
|
+
name = node.value
|
|
1698
|
+
# First check captured values from current injection context
|
|
1699
|
+
if name in self._current_captured_values:
|
|
1700
|
+
captured_value = self._current_captured_values[name]
|
|
1701
|
+
# Only use captured value if it's not None
|
|
1702
|
+
if captured_value is not None:
|
|
1703
|
+
return captured_value
|
|
1704
|
+
# Fall back to normal resolution if not captured or capture was None
|
|
1705
|
+
value = self.scope.get(name)
|
|
1706
|
+
if value is None:
|
|
1707
|
+
value = self.global_scope.get(name)
|
|
1708
|
+
if value is None:
|
|
1709
|
+
# For critical builtins like 'exit', create direct wrapper
|
|
1710
|
+
if name == 'exit':
|
|
1711
|
+
runtime = self
|
|
1712
|
+
value = lambda code=0, rt=runtime: rt.exit(code)
|
|
1713
|
+
else:
|
|
1714
|
+
value = getattr(self.builtins, f'builtin_{name}', None)
|
|
1715
|
+
if value is None:
|
|
1716
|
+
# Check original functions (for replaced functions)
|
|
1717
|
+
value = self._original_functions.get(name)
|
|
1718
|
+
if value is not None:
|
|
1719
|
+
return value
|
|
1720
|
+
raise CSSLRuntimeError(f"Captured reference '%{name}' not found.")
|
|
1721
|
+
|
|
1657
1722
|
if node.type == 'type_instantiation':
|
|
1658
1723
|
# Create new instance of a type: stack<string>, vector<int>, etc.
|
|
1659
1724
|
type_name = node.value.get('type')
|
|
@@ -1720,8 +1785,41 @@ class CSSLRuntime:
|
|
|
1720
1785
|
return {'__ref__': True, 'name': inner.value, 'value': self.get_module(inner.value)}
|
|
1721
1786
|
return {'__ref__': True, 'value': self._evaluate(inner)}
|
|
1722
1787
|
|
|
1788
|
+
# Handle action_block - execute and return last expression value
|
|
1789
|
+
if node.type == 'action_block':
|
|
1790
|
+
return self._evaluate_action_block(node)
|
|
1791
|
+
|
|
1723
1792
|
return None
|
|
1724
1793
|
|
|
1794
|
+
def _evaluate_action_block(self, node: ASTNode) -> Any:
|
|
1795
|
+
"""Evaluate an action block and return the last expression's value.
|
|
1796
|
+
|
|
1797
|
+
Used for: v <== { %version; } - captures %version at this moment
|
|
1798
|
+
|
|
1799
|
+
Returns the value of the last expression in the block.
|
|
1800
|
+
If the block contains a captured_ref (%name), that's what gets returned.
|
|
1801
|
+
"""
|
|
1802
|
+
last_value = None
|
|
1803
|
+
for child in node.children:
|
|
1804
|
+
if child.type == 'captured_ref':
|
|
1805
|
+
# Direct captured reference - return its value
|
|
1806
|
+
last_value = self._evaluate(child)
|
|
1807
|
+
elif child.type == 'expression':
|
|
1808
|
+
# Expression statement - evaluate and keep value
|
|
1809
|
+
last_value = self._evaluate(child.value if hasattr(child, 'value') else child)
|
|
1810
|
+
elif child.type == 'identifier':
|
|
1811
|
+
# Just an identifier - evaluate it
|
|
1812
|
+
last_value = self._evaluate(child)
|
|
1813
|
+
elif child.type in ('call', 'member_access', 'binary', 'unary'):
|
|
1814
|
+
# Expression types
|
|
1815
|
+
last_value = self._evaluate(child)
|
|
1816
|
+
else:
|
|
1817
|
+
# Execute other statements
|
|
1818
|
+
result = self._execute_node(child)
|
|
1819
|
+
if result is not None:
|
|
1820
|
+
last_value = result
|
|
1821
|
+
return last_value
|
|
1822
|
+
|
|
1725
1823
|
def _eval_binary(self, node: ASTNode) -> Any:
|
|
1726
1824
|
"""Evaluate binary operation with auto-casting support"""
|
|
1727
1825
|
op = node.value.get('op')
|
|
@@ -1893,10 +1991,9 @@ class CSSLRuntime:
|
|
|
1893
1991
|
def _eval_call(self, node: ASTNode) -> Any:
|
|
1894
1992
|
"""Evaluate function call"""
|
|
1895
1993
|
callee_node = node.value.get('callee')
|
|
1896
|
-
callee = self._evaluate(callee_node)
|
|
1897
1994
|
args = [self._evaluate(a) for a in node.value.get('args', [])]
|
|
1898
1995
|
|
|
1899
|
-
# Get function name for injection check
|
|
1996
|
+
# Get function name for injection check FIRST (before evaluating callee)
|
|
1900
1997
|
func_name = None
|
|
1901
1998
|
if isinstance(callee_node, ASTNode):
|
|
1902
1999
|
if callee_node.type == 'identifier':
|
|
@@ -1908,13 +2005,18 @@ class CSSLRuntime:
|
|
|
1908
2005
|
has_injections = func_name and func_name in self._function_injections
|
|
1909
2006
|
is_replaced = func_name and self._function_replaced.get(func_name, False)
|
|
1910
2007
|
|
|
1911
|
-
#
|
|
1912
|
-
|
|
2008
|
+
# If function is FULLY REPLACED (<<==), run injection and skip original
|
|
2009
|
+
# This allows creating new functions via infusion: new_func <<== { ... }
|
|
2010
|
+
if is_replaced:
|
|
1913
2011
|
self._execute_function_injections(func_name)
|
|
2012
|
+
return None # Injection ran, don't try to find original
|
|
1914
2013
|
|
|
1915
|
-
#
|
|
1916
|
-
|
|
1917
|
-
|
|
2014
|
+
# Now evaluate the callee (only if not replaced)
|
|
2015
|
+
callee = self._evaluate(callee_node)
|
|
2016
|
+
|
|
2017
|
+
# Execute added injections (+<<==) before original
|
|
2018
|
+
if has_injections and not is_replaced:
|
|
2019
|
+
self._execute_function_injections(func_name)
|
|
1918
2020
|
|
|
1919
2021
|
# Execute original function
|
|
1920
2022
|
if callable(callee):
|
|
@@ -2329,22 +2431,107 @@ class CSSLRuntime:
|
|
|
2329
2431
|
# Also store in promoted globals for string interpolation
|
|
2330
2432
|
self._promoted_globals[base_name] = value
|
|
2331
2433
|
|
|
2434
|
+
# NEW: Scan for captured_ref nodes and capture their current values
|
|
2435
|
+
def _scan_and_capture_refs(self, node: ASTNode) -> Dict[str, Any]:
|
|
2436
|
+
"""Scan AST for %<name> captured references and capture their current values.
|
|
2437
|
+
|
|
2438
|
+
This is called at infusion registration time to capture values.
|
|
2439
|
+
Example: old_exit <<== { %exit(); } captures 'exit' at definition time.
|
|
2440
|
+
"""
|
|
2441
|
+
captured = {}
|
|
2442
|
+
|
|
2443
|
+
def scan_node(n):
|
|
2444
|
+
if not isinstance(n, ASTNode):
|
|
2445
|
+
return
|
|
2446
|
+
|
|
2447
|
+
# Found a captured_ref - capture its current value
|
|
2448
|
+
if n.type == 'captured_ref':
|
|
2449
|
+
name = n.value
|
|
2450
|
+
if name not in captured:
|
|
2451
|
+
# Try to find value - check multiple sources
|
|
2452
|
+
value = None
|
|
2453
|
+
|
|
2454
|
+
# 1. Check _original_functions first (for functions that were JUST replaced)
|
|
2455
|
+
if value is None:
|
|
2456
|
+
value = self._original_functions.get(name)
|
|
2457
|
+
|
|
2458
|
+
# 2. Check scope
|
|
2459
|
+
if value is None:
|
|
2460
|
+
value = self.scope.get(name)
|
|
2461
|
+
|
|
2462
|
+
# 3. Check global_scope
|
|
2463
|
+
if value is None:
|
|
2464
|
+
value = self.global_scope.get(name)
|
|
2465
|
+
|
|
2466
|
+
# 4. Check builtins (most common case for exit, print, etc.)
|
|
2467
|
+
if value is None:
|
|
2468
|
+
# For critical builtins like 'exit', create a direct wrapper
|
|
2469
|
+
# that captures the runtime reference to ensure correct behavior
|
|
2470
|
+
if name == 'exit':
|
|
2471
|
+
runtime = self # Capture runtime in closure
|
|
2472
|
+
value = lambda code=0, rt=runtime: rt.exit(code)
|
|
2473
|
+
else:
|
|
2474
|
+
value = getattr(self.builtins, f'builtin_{name}', None)
|
|
2475
|
+
|
|
2476
|
+
# 5. Check if there's a user-defined function in scope
|
|
2477
|
+
if value is None:
|
|
2478
|
+
# Look for function definitions
|
|
2479
|
+
func_def = self.global_scope.get(f'__func_{name}')
|
|
2480
|
+
if func_def is not None:
|
|
2481
|
+
value = func_def
|
|
2482
|
+
|
|
2483
|
+
# Only capture if we found something
|
|
2484
|
+
if value is not None:
|
|
2485
|
+
captured[name] = value
|
|
2486
|
+
|
|
2487
|
+
# Check call node's callee
|
|
2488
|
+
if n.type == 'call':
|
|
2489
|
+
callee = n.value.get('callee')
|
|
2490
|
+
if callee:
|
|
2491
|
+
scan_node(callee)
|
|
2492
|
+
for arg in n.value.get('args', []):
|
|
2493
|
+
scan_node(arg)
|
|
2494
|
+
|
|
2495
|
+
# Recurse into children
|
|
2496
|
+
if hasattr(n, 'children') and n.children:
|
|
2497
|
+
for child in n.children:
|
|
2498
|
+
scan_node(child)
|
|
2499
|
+
|
|
2500
|
+
# Check value dict for nested nodes
|
|
2501
|
+
if hasattr(n, 'value') and isinstance(n.value, dict):
|
|
2502
|
+
for key, val in n.value.items():
|
|
2503
|
+
if isinstance(val, ASTNode):
|
|
2504
|
+
scan_node(val)
|
|
2505
|
+
elif isinstance(val, list):
|
|
2506
|
+
for item in val:
|
|
2507
|
+
if isinstance(item, ASTNode):
|
|
2508
|
+
scan_node(item)
|
|
2509
|
+
|
|
2510
|
+
scan_node(node)
|
|
2511
|
+
return captured
|
|
2512
|
+
|
|
2332
2513
|
# NEW: Register permanent function injection
|
|
2333
2514
|
def register_function_injection(self, func_name: str, code_block: ASTNode):
|
|
2334
2515
|
"""Register code to be permanently injected into a function - NEW
|
|
2335
2516
|
|
|
2336
2517
|
Example: exit() <== { println("Cleanup..."); }
|
|
2337
2518
|
Makes every call to exit() also execute the injected code
|
|
2519
|
+
|
|
2520
|
+
Captures %<name> references at registration time.
|
|
2338
2521
|
"""
|
|
2522
|
+
# Scan for %<name> captured references and capture their current values
|
|
2523
|
+
captured_values = self._scan_and_capture_refs(code_block)
|
|
2524
|
+
|
|
2339
2525
|
if func_name not in self._function_injections:
|
|
2340
2526
|
self._function_injections[func_name] = []
|
|
2341
|
-
self._function_injections[func_name].append(code_block)
|
|
2527
|
+
self._function_injections[func_name].append((code_block, captured_values))
|
|
2342
2528
|
|
|
2343
2529
|
# NEW: Execute injected code for a function
|
|
2344
2530
|
def _execute_function_injections(self, func_name: str):
|
|
2345
2531
|
"""Execute all injected code blocks for a function - NEW
|
|
2346
2532
|
|
|
2347
2533
|
Includes protection against recursive execution to prevent doubled output.
|
|
2534
|
+
Uses captured values for %<name> references.
|
|
2348
2535
|
"""
|
|
2349
2536
|
# Prevent recursive injection execution (fixes doubled output bug)
|
|
2350
2537
|
if getattr(self, '_injection_executing', False):
|
|
@@ -2352,16 +2539,29 @@ class CSSLRuntime:
|
|
|
2352
2539
|
|
|
2353
2540
|
if func_name in self._function_injections:
|
|
2354
2541
|
self._injection_executing = True
|
|
2542
|
+
old_captured = self._current_captured_values.copy()
|
|
2355
2543
|
try:
|
|
2356
|
-
for
|
|
2544
|
+
for injection in self._function_injections[func_name]:
|
|
2545
|
+
# Handle both tuple format (code_block, captured_values) and legacy ASTNode format
|
|
2546
|
+
if isinstance(injection, tuple):
|
|
2547
|
+
code_block, captured_values = injection
|
|
2548
|
+
self._current_captured_values = captured_values
|
|
2549
|
+
else:
|
|
2550
|
+
code_block = injection
|
|
2551
|
+
self._current_captured_values = {}
|
|
2552
|
+
|
|
2357
2553
|
if isinstance(code_block, ASTNode):
|
|
2358
2554
|
if code_block.type == 'action_block':
|
|
2359
2555
|
for child in code_block.children:
|
|
2556
|
+
# Check if exit() was called
|
|
2557
|
+
if not self._running:
|
|
2558
|
+
break
|
|
2360
2559
|
self._execute_node(child)
|
|
2361
2560
|
else:
|
|
2362
2561
|
self._execute_node(code_block)
|
|
2363
2562
|
finally:
|
|
2364
2563
|
self._injection_executing = False
|
|
2564
|
+
self._current_captured_values = old_captured
|
|
2365
2565
|
|
|
2366
2566
|
# Output functions for builtins
|
|
2367
2567
|
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.7"
|
|
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
|