IncludeCPP 4.2.2__py3-none-any.whl → 4.5.2__py3-none-any.whl
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/CHANGELOG.md +104 -115
- includecpp/DOCUMENTATION.md +208 -355
- includecpp/__init__.py +1 -1
- includecpp/__init__.pyi +1 -4
- includecpp/cli/commands.py +1220 -27
- includecpp/core/cpp_api_extensions.pyi +204 -200
- includecpp/core/cssl/CSSL_DOCUMENTATION.md +1505 -1467
- includecpp/core/cssl/__init__.py +317 -0
- includecpp/core/cssl/cpp/build/api.pyd +0 -0
- includecpp/core/cssl/cpp/build/cssl_core.pyi +323 -0
- includecpp/core/cssl/cpp/build/libgcc_s_seh-1.dll +0 -0
- includecpp/core/cssl/cpp/build/libstdc++-6.dll +0 -0
- includecpp/core/cssl/cpp/build/libwinpthread-1.dll +0 -0
- includecpp/core/cssl/cpp/cssl_core.cp +108 -0
- includecpp/core/cssl/cpp/cssl_lexer.hpp +280 -0
- includecpp/core/cssl/cssl_builtins.py +245 -20
- includecpp/core/cssl/cssl_compiler.py +448 -0
- includecpp/core/cssl/cssl_optimizer.py +833 -0
- includecpp/core/cssl/cssl_parser.py +945 -40
- includecpp/core/cssl/cssl_runtime.py +751 -38
- includecpp/core/cssl/cssl_syntax.py +17 -0
- includecpp/core/cssl/cssl_types.py +321 -0
- includecpp/core/cssl_bridge.py +36 -2
- includecpp/generator/parser.cpp +38 -14
- includecpp/vscode/cssl/package.json +15 -0
- includecpp/vscode/cssl/syntaxes/cssl.tmLanguage.json +134 -2
- includecpp-4.5.2.dist-info/METADATA +277 -0
- {includecpp-4.2.2.dist-info → includecpp-4.5.2.dist-info}/RECORD +32 -23
- includecpp-4.2.2.dist-info/METADATA +0 -1008
- {includecpp-4.2.2.dist-info → includecpp-4.5.2.dist-info}/WHEEL +0 -0
- {includecpp-4.2.2.dist-info → includecpp-4.5.2.dist-info}/entry_points.txt +0 -0
- {includecpp-4.2.2.dist-info → includecpp-4.5.2.dist-info}/licenses/LICENSE +0 -0
- {includecpp-4.2.2.dist-info → includecpp-4.5.2.dist-info}/top_level.txt +0 -0
|
@@ -15,7 +15,7 @@ from .cssl_modules import get_module_registry, get_standard_module
|
|
|
15
15
|
from .cssl_types import (
|
|
16
16
|
Parameter, DataStruct, Shuffled, Iterator, Combo,
|
|
17
17
|
Stack, Vector, Array, DataSpace, OpenQuote, List, Dictionary, Map,
|
|
18
|
-
CSSLClass, CSSLInstance
|
|
18
|
+
CSSLClass, CSSLInstance, ByteArrayed
|
|
19
19
|
)
|
|
20
20
|
|
|
21
21
|
|
|
@@ -199,6 +199,13 @@ class CSSLReturn(Exception):
|
|
|
199
199
|
super().__init__()
|
|
200
200
|
|
|
201
201
|
|
|
202
|
+
class CSSLThrow(Exception):
|
|
203
|
+
"""Throw statement - v4.5.1: User-thrown exceptions that propagate to catch blocks"""
|
|
204
|
+
def __init__(self, message: Any = None):
|
|
205
|
+
self.message = message
|
|
206
|
+
super().__init__(str(message) if message else "")
|
|
207
|
+
|
|
208
|
+
|
|
202
209
|
@dataclass
|
|
203
210
|
class Scope:
|
|
204
211
|
"""Variable scope"""
|
|
@@ -532,6 +539,10 @@ class CSSLRuntime:
|
|
|
532
539
|
self._exec_struct(child)
|
|
533
540
|
elif child.type == 'class':
|
|
534
541
|
self._exec_class(child)
|
|
542
|
+
elif child.type == 'enum':
|
|
543
|
+
self._exec_enum(child)
|
|
544
|
+
elif child.type == 'bytearrayed':
|
|
545
|
+
self._exec_bytearrayed(child)
|
|
535
546
|
elif child.type == 'function':
|
|
536
547
|
self._exec_function(child)
|
|
537
548
|
elif child.type == 'global_assignment':
|
|
@@ -813,6 +824,123 @@ class CSSLRuntime:
|
|
|
813
824
|
|
|
814
825
|
return struct_data
|
|
815
826
|
|
|
827
|
+
def _exec_enum(self, node: ASTNode) -> Dict[str, Any]:
|
|
828
|
+
"""Execute enum declaration - registers enum values in scope.
|
|
829
|
+
|
|
830
|
+
Creates a dictionary-like enum object accessible via EnumName::VALUE syntax.
|
|
831
|
+
|
|
832
|
+
Example:
|
|
833
|
+
enum Colors { RED, GREEN, BLUE }
|
|
834
|
+
Colors::RED // returns 0
|
|
835
|
+
Colors::GREEN // returns 1
|
|
836
|
+
|
|
837
|
+
v4.3.2: Support for embedded enum modification:
|
|
838
|
+
embedded __NewEnum &OldEnum { ... } // Replace OldEnum
|
|
839
|
+
embedded __NewEnum &OldEnum ++ { ... } // Add to OldEnum
|
|
840
|
+
embedded __NewEnum &OldEnum -- { ... } // Remove from OldEnum
|
|
841
|
+
"""
|
|
842
|
+
enum_info = node.value
|
|
843
|
+
enum_name = enum_info.get('name')
|
|
844
|
+
members = enum_info.get('members', [])
|
|
845
|
+
is_embedded = enum_info.get('is_embedded', False)
|
|
846
|
+
replace_target = enum_info.get('replace_target')
|
|
847
|
+
mode = enum_info.get('mode', 'replace')
|
|
848
|
+
|
|
849
|
+
# Create enum object as a dict-like object with members
|
|
850
|
+
new_values = {}
|
|
851
|
+
for member in members:
|
|
852
|
+
member_name = member['name']
|
|
853
|
+
member_value = member['value']
|
|
854
|
+
# Evaluate if value is an ASTNode
|
|
855
|
+
if isinstance(member_value, ASTNode):
|
|
856
|
+
member_value = self._evaluate(member_value)
|
|
857
|
+
new_values[member_name] = member_value
|
|
858
|
+
|
|
859
|
+
# v4.3.2: Handle embedded modification
|
|
860
|
+
if is_embedded and replace_target:
|
|
861
|
+
target_name = replace_target.lstrip('@') if replace_target.startswith('@') else replace_target
|
|
862
|
+
|
|
863
|
+
if mode == 'add':
|
|
864
|
+
# Get existing enum and add new values
|
|
865
|
+
existing = self.scope.get(target_name) or self.global_scope.get(target_name) or {}
|
|
866
|
+
if isinstance(existing, dict):
|
|
867
|
+
# For add mode, auto-increment from highest existing int value
|
|
868
|
+
max_val = -1
|
|
869
|
+
for v in existing.values():
|
|
870
|
+
if isinstance(v, int) and v > max_val:
|
|
871
|
+
max_val = v
|
|
872
|
+
# Update new values that don't have explicit values
|
|
873
|
+
for name, val in new_values.items():
|
|
874
|
+
if isinstance(val, int) and val <= max_val:
|
|
875
|
+
max_val += 1
|
|
876
|
+
new_values[name] = max_val
|
|
877
|
+
enum_obj = {**existing, **new_values}
|
|
878
|
+
else:
|
|
879
|
+
enum_obj = new_values
|
|
880
|
+
elif mode == 'remove':
|
|
881
|
+
# Get existing enum and remove specified keys
|
|
882
|
+
existing = self.scope.get(target_name) or self.global_scope.get(target_name) or {}
|
|
883
|
+
if isinstance(existing, dict):
|
|
884
|
+
enum_obj = {k: v for k, v in existing.items() if k not in new_values}
|
|
885
|
+
else:
|
|
886
|
+
enum_obj = {}
|
|
887
|
+
else:
|
|
888
|
+
# Replace mode - just use new values
|
|
889
|
+
enum_obj = new_values
|
|
890
|
+
|
|
891
|
+
self.scope.set(target_name, enum_obj)
|
|
892
|
+
self.global_scope.set(target_name, enum_obj)
|
|
893
|
+
if replace_target.startswith('@'):
|
|
894
|
+
self._promoted_globals[target_name] = enum_obj
|
|
895
|
+
else:
|
|
896
|
+
# Regular enum - just register it
|
|
897
|
+
enum_obj = new_values
|
|
898
|
+
self.scope.set(enum_name, enum_obj)
|
|
899
|
+
self.global_scope.set(enum_name, enum_obj)
|
|
900
|
+
|
|
901
|
+
return enum_obj
|
|
902
|
+
|
|
903
|
+
def _exec_bytearrayed(self, node: ASTNode) -> 'ByteArrayed':
|
|
904
|
+
"""Execute bytearrayed declaration - function-to-byte mapping with pattern matching.
|
|
905
|
+
|
|
906
|
+
Creates a ByteArrayed object that:
|
|
907
|
+
- Maps function references to byte positions (0x0, 0x1, etc.)
|
|
908
|
+
- Executes functions "invisibly" when called to get return values
|
|
909
|
+
- Matches case patterns based on return values
|
|
910
|
+
- Supports indexing: MyBytes["0x0"] or MyBytes[0]
|
|
911
|
+
|
|
912
|
+
Example:
|
|
913
|
+
bytearrayed MyBytes {
|
|
914
|
+
&func1; // 0x0
|
|
915
|
+
&func2; // 0x1
|
|
916
|
+
case {0, 1} { // func1=0, func2=1
|
|
917
|
+
printl("Match!");
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
MyBytes(); // Execute pattern matching
|
|
921
|
+
x = MyBytes["0x0"]; // Get value at position 0
|
|
922
|
+
"""
|
|
923
|
+
info = node.value
|
|
924
|
+
name = info.get('name')
|
|
925
|
+
func_refs = info.get('func_refs', [])
|
|
926
|
+
cases = info.get('cases', [])
|
|
927
|
+
default_block = info.get('default')
|
|
928
|
+
|
|
929
|
+
# Create ByteArrayed object
|
|
930
|
+
bytearrayed_obj = ByteArrayed(
|
|
931
|
+
name=name,
|
|
932
|
+
func_refs=func_refs,
|
|
933
|
+
cases=cases,
|
|
934
|
+
default_block=default_block,
|
|
935
|
+
runtime=self
|
|
936
|
+
)
|
|
937
|
+
|
|
938
|
+
# Register in scope
|
|
939
|
+
self.scope.set(name, bytearrayed_obj)
|
|
940
|
+
self.global_scope.set(name, bytearrayed_obj)
|
|
941
|
+
|
|
942
|
+
return bytearrayed_obj
|
|
943
|
+
|
|
816
944
|
def _exec_class(self, node: ASTNode) -> CSSLClass:
|
|
817
945
|
"""Execute class definition - registers class in scope.
|
|
818
946
|
|
|
@@ -942,6 +1070,22 @@ class CSSLRuntime:
|
|
|
942
1070
|
self.global_scope.set(class_name, class_def)
|
|
943
1071
|
self._promoted_globals[class_name] = class_def
|
|
944
1072
|
|
|
1073
|
+
# v4.2.5: Handle &target replacement for classes
|
|
1074
|
+
# embedded class MyClass &$Target { } - immediate replacement
|
|
1075
|
+
# class MyClass &$Target { } - deferred until instantiation
|
|
1076
|
+
append_ref_class = class_info.get('append_ref_class')
|
|
1077
|
+
is_embedded = class_info.get('is_embedded', False)
|
|
1078
|
+
if append_ref_class and is_embedded:
|
|
1079
|
+
append_ref_member = class_info.get('append_ref_member')
|
|
1080
|
+
self._overwrite_class_target(append_ref_class, append_ref_member, class_def)
|
|
1081
|
+
class_def._target_applied = True
|
|
1082
|
+
elif append_ref_class:
|
|
1083
|
+
# Store reference info for deferred replacement
|
|
1084
|
+
class_def._pending_target = {
|
|
1085
|
+
'append_ref_class': append_ref_class,
|
|
1086
|
+
'append_ref_member': class_info.get('append_ref_member')
|
|
1087
|
+
}
|
|
1088
|
+
|
|
945
1089
|
# Handle class overwrites - replace methods in target class
|
|
946
1090
|
if overwrites_class_name:
|
|
947
1091
|
self._apply_class_overwrites(
|
|
@@ -1164,13 +1308,18 @@ class CSSLRuntime:
|
|
|
1164
1308
|
# Handle &Class::method syntax
|
|
1165
1309
|
# Without ++ = full replacement
|
|
1166
1310
|
# With ++ = append (run original first, then new code)
|
|
1167
|
-
if
|
|
1311
|
+
# v4.2.5: Only do immediate replacement if is_embedded=True
|
|
1312
|
+
# For regular 'define', replacement is deferred until function is called
|
|
1313
|
+
is_embedded = func_info.get('is_embedded', False)
|
|
1314
|
+
if append_ref_class and is_embedded:
|
|
1168
1315
|
if append_mode:
|
|
1169
1316
|
# Append mode: wrap original to run original + new
|
|
1170
1317
|
self._append_to_target(append_ref_class, append_ref_member, node)
|
|
1171
1318
|
else:
|
|
1172
1319
|
# Full replacement
|
|
1173
1320
|
self._overwrite_target(append_ref_class, append_ref_member, node)
|
|
1321
|
+
# Mark as already applied so we don't apply again on call
|
|
1322
|
+
node.value['_target_applied'] = True
|
|
1174
1323
|
|
|
1175
1324
|
# Handle overwrites keyword - replace the target function
|
|
1176
1325
|
if overwrites_func:
|
|
@@ -1215,6 +1364,34 @@ class CSSLRuntime:
|
|
|
1215
1364
|
return self._call_function(func_node, list(args), kwargs)
|
|
1216
1365
|
return wrapper
|
|
1217
1366
|
|
|
1367
|
+
def _create_python_method_wrapper(self, func_node: ASTNode, python_obj):
|
|
1368
|
+
"""Create a Python-callable wrapper that passes python_obj as 'this'.
|
|
1369
|
+
|
|
1370
|
+
v4.3.0: Used when replacing Python object methods with CSSL functions.
|
|
1371
|
+
The wrapper ensures 'this->' in CSSL refers to the Python object.
|
|
1372
|
+
"""
|
|
1373
|
+
runtime = self
|
|
1374
|
+
_captured_obj = python_obj
|
|
1375
|
+
|
|
1376
|
+
def wrapper(*args, **kwargs):
|
|
1377
|
+
# Save current instance context
|
|
1378
|
+
old_instance = runtime._current_instance
|
|
1379
|
+
old_this = runtime.scope.get('this')
|
|
1380
|
+
try:
|
|
1381
|
+
# Set Python object as current instance for this-> access
|
|
1382
|
+
runtime._current_instance = _captured_obj
|
|
1383
|
+
runtime.scope.set('this', _captured_obj)
|
|
1384
|
+
return runtime._call_function(func_node, list(args), kwargs)
|
|
1385
|
+
finally:
|
|
1386
|
+
# Restore previous context
|
|
1387
|
+
runtime._current_instance = old_instance
|
|
1388
|
+
if old_this is not None:
|
|
1389
|
+
runtime.scope.set('this', old_this)
|
|
1390
|
+
elif 'this' in runtime.scope.variables:
|
|
1391
|
+
del runtime.scope.variables['this']
|
|
1392
|
+
|
|
1393
|
+
return wrapper
|
|
1394
|
+
|
|
1218
1395
|
def _overwrite_target(self, ref_class: str, ref_member: str, replacement_node: ASTNode):
|
|
1219
1396
|
"""Overwrite a class method or function with the replacement.
|
|
1220
1397
|
|
|
@@ -1222,9 +1399,50 @@ class CSSLRuntime:
|
|
|
1222
1399
|
- &ClassName::method - Overwrite method in CSSL class
|
|
1223
1400
|
- &$PyObject.method - Overwrite method in Python shared object
|
|
1224
1401
|
- &functionName - Overwrite standalone function
|
|
1402
|
+
|
|
1403
|
+
v4.2.6: In namespace mode (_payload_namespace_mode), track replacements
|
|
1404
|
+
instead of applying globally. The namespace handler will scope them.
|
|
1405
|
+
|
|
1406
|
+
v4.5.1: Respects 'private' modifier - private functions/methods cannot be overwritten.
|
|
1225
1407
|
"""
|
|
1226
1408
|
from ..cssl_bridge import _live_objects, SharedObjectProxy
|
|
1227
1409
|
|
|
1410
|
+
# v4.5.1: Check if target has 'private' modifier - prevent overwrite
|
|
1411
|
+
if not ref_class.startswith('$'):
|
|
1412
|
+
target_func = self.scope.get(ref_class) or self.global_scope.get(ref_class)
|
|
1413
|
+
if target_func is not None:
|
|
1414
|
+
# Check for function with private modifier
|
|
1415
|
+
if hasattr(target_func, 'value') and isinstance(target_func.value, dict):
|
|
1416
|
+
modifiers = target_func.value.get('modifiers', [])
|
|
1417
|
+
if 'private' in modifiers:
|
|
1418
|
+
raise CSSLRuntimeError(
|
|
1419
|
+
f"Cannot overwrite private function '{ref_class}' - "
|
|
1420
|
+
f"private functions are protected from embedded replacements"
|
|
1421
|
+
)
|
|
1422
|
+
# Check for class with private method
|
|
1423
|
+
if ref_member and isinstance(target_func, CSSLClass):
|
|
1424
|
+
method = target_func.methods.get(ref_member) if hasattr(target_func, 'methods') else None
|
|
1425
|
+
if method and hasattr(method, 'value') and isinstance(method.value, dict):
|
|
1426
|
+
modifiers = method.value.get('modifiers', [])
|
|
1427
|
+
if 'private' in modifiers:
|
|
1428
|
+
raise CSSLRuntimeError(
|
|
1429
|
+
f"Cannot overwrite private method '{ref_class}::{ref_member}' - "
|
|
1430
|
+
f"private methods are protected from embedded replacements"
|
|
1431
|
+
)
|
|
1432
|
+
|
|
1433
|
+
# v4.2.6: Check for namespace mode
|
|
1434
|
+
namespace_mode = getattr(self, '_payload_namespace_mode', None)
|
|
1435
|
+
if namespace_mode and ref_member is None and not ref_class.startswith('$'):
|
|
1436
|
+
# Standalone function replacement in namespace mode
|
|
1437
|
+
# Track it for the namespace handler
|
|
1438
|
+
if not hasattr(self, '_namespace_replacements'):
|
|
1439
|
+
self._namespace_replacements = {}
|
|
1440
|
+
self._namespace_replacements[ref_class] = replacement_node
|
|
1441
|
+
# Still register the function normally (namespace handler will move it)
|
|
1442
|
+
self.scope.set(ref_class, replacement_node)
|
|
1443
|
+
self.global_scope.set(ref_class, replacement_node)
|
|
1444
|
+
return
|
|
1445
|
+
|
|
1228
1446
|
# Handle Python shared objects
|
|
1229
1447
|
if ref_class.startswith('$'):
|
|
1230
1448
|
var_name = ref_class[1:]
|
|
@@ -1241,7 +1459,8 @@ class CSSLRuntime:
|
|
|
1241
1459
|
|
|
1242
1460
|
# Overwrite Python object method
|
|
1243
1461
|
if ref_member and hasattr(ref_obj, ref_member):
|
|
1244
|
-
wrapper
|
|
1462
|
+
# v4.3.0: Create wrapper that passes Python object as 'this' context
|
|
1463
|
+
wrapper = self._create_python_method_wrapper(replacement_node, ref_obj)
|
|
1245
1464
|
try:
|
|
1246
1465
|
setattr(ref_obj, ref_member, wrapper)
|
|
1247
1466
|
except (AttributeError, TypeError):
|
|
@@ -1250,12 +1469,49 @@ class CSSLRuntime:
|
|
|
1250
1469
|
|
|
1251
1470
|
# Handle CSSL class method overwrite
|
|
1252
1471
|
target_class = self.scope.get(ref_class) or self.global_scope.get(ref_class)
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1472
|
+
|
|
1473
|
+
# v4.2.3: Handle standalone function reference (&functionName) including builtins
|
|
1474
|
+
if ref_member is None:
|
|
1475
|
+
# Check if this is a builtin function
|
|
1476
|
+
is_builtin = hasattr(self, 'builtins') and self.builtins.has_function(ref_class)
|
|
1477
|
+
|
|
1478
|
+
# Check if target_class is NOT a CSSLClass (it's either a function/builtin or None)
|
|
1479
|
+
is_cssl_class = isinstance(target_class, CSSLClass)
|
|
1480
|
+
# v4.2.5: Also check if target is a CSSL function (ASTNode with type 'function')
|
|
1481
|
+
is_cssl_function = (hasattr(target_class, 'type') and target_class.type == 'function')
|
|
1482
|
+
|
|
1483
|
+
if is_builtin or (target_class is None) or (not is_cssl_class and callable(target_class)) or is_cssl_function:
|
|
1484
|
+
# v4.2.3: CRITICAL - Store original BEFORE overwriting for %name captures
|
|
1485
|
+
# This ensures %exit() refers to the ORIGINAL exit, not the replacement
|
|
1486
|
+
if ref_class not in self._original_functions:
|
|
1487
|
+
if is_builtin:
|
|
1488
|
+
# Store the original builtin from _functions dict
|
|
1489
|
+
# (handles aliases like printl -> builtin_println)
|
|
1490
|
+
original_builtin = self.builtins._functions.get(ref_class)
|
|
1491
|
+
if original_builtin is not None:
|
|
1492
|
+
self._original_functions[ref_class] = original_builtin
|
|
1493
|
+
elif is_cssl_function:
|
|
1494
|
+
# v4.2.5: Store original CSSL function (ASTNode)
|
|
1495
|
+
self._original_functions[ref_class] = target_class
|
|
1496
|
+
elif target_class is not None and callable(target_class):
|
|
1497
|
+
# Store the original function/callable
|
|
1498
|
+
self._original_functions[ref_class] = target_class
|
|
1499
|
+
|
|
1500
|
+
# &functionName - overwrite the function/builtin
|
|
1257
1501
|
self.scope.set(ref_class, replacement_node)
|
|
1258
1502
|
self.global_scope.set(ref_class, replacement_node)
|
|
1503
|
+
|
|
1504
|
+
# Also overwrite in builtins dict if it's a builtin
|
|
1505
|
+
if is_builtin:
|
|
1506
|
+
# Create wrapper that calls CSSL function instead of builtin
|
|
1507
|
+
def make_wrapper(node, runtime):
|
|
1508
|
+
def wrapper(*args, **kwargs):
|
|
1509
|
+
return runtime._call_function(node, list(args), kwargs)
|
|
1510
|
+
return wrapper
|
|
1511
|
+
self.builtins._functions[ref_class] = make_wrapper(replacement_node, self)
|
|
1512
|
+
return
|
|
1513
|
+
|
|
1514
|
+
if target_class is None:
|
|
1259
1515
|
return
|
|
1260
1516
|
|
|
1261
1517
|
if isinstance(target_class, CSSLClass) and ref_member:
|
|
@@ -1277,9 +1533,34 @@ class CSSLRuntime:
|
|
|
1277
1533
|
- &ClassName::method ++ - Append to method in CSSL class
|
|
1278
1534
|
- &$PyObject.method ++ - Append to method in Python shared object
|
|
1279
1535
|
- &functionName ++ - Append to standalone function
|
|
1536
|
+
|
|
1537
|
+
v4.5.1: Respects 'private' modifier - private functions/methods cannot be appended to.
|
|
1280
1538
|
"""
|
|
1281
1539
|
from ..cssl_bridge import _live_objects, SharedObjectProxy
|
|
1282
1540
|
|
|
1541
|
+
# v4.5.1: Check if target has 'private' modifier - prevent append
|
|
1542
|
+
if not ref_class.startswith('$'):
|
|
1543
|
+
target_func = self.scope.get(ref_class) or self.global_scope.get(ref_class)
|
|
1544
|
+
if target_func is not None:
|
|
1545
|
+
# Check for function with private modifier
|
|
1546
|
+
if hasattr(target_func, 'value') and isinstance(target_func.value, dict):
|
|
1547
|
+
modifiers = target_func.value.get('modifiers', [])
|
|
1548
|
+
if 'private' in modifiers:
|
|
1549
|
+
raise CSSLRuntimeError(
|
|
1550
|
+
f"Cannot append to private function '{ref_class}' - "
|
|
1551
|
+
f"private functions are protected from embedded modifications"
|
|
1552
|
+
)
|
|
1553
|
+
# Check for class with private method
|
|
1554
|
+
if ref_member and isinstance(target_func, CSSLClass):
|
|
1555
|
+
method = target_func.methods.get(ref_member) if hasattr(target_func, 'methods') else None
|
|
1556
|
+
if method and hasattr(method, 'value') and isinstance(method.value, dict):
|
|
1557
|
+
modifiers = method.value.get('modifiers', [])
|
|
1558
|
+
if 'private' in modifiers:
|
|
1559
|
+
raise CSSLRuntimeError(
|
|
1560
|
+
f"Cannot append to private method '{ref_class}::{ref_member}' - "
|
|
1561
|
+
f"private methods are protected from embedded modifications"
|
|
1562
|
+
)
|
|
1563
|
+
|
|
1283
1564
|
# Handle Python shared objects
|
|
1284
1565
|
if ref_class.startswith('$'):
|
|
1285
1566
|
var_name = ref_class[1:]
|
|
@@ -1357,6 +1638,58 @@ class CSSLRuntime:
|
|
|
1357
1638
|
target_class.members[i] = append_node
|
|
1358
1639
|
break
|
|
1359
1640
|
|
|
1641
|
+
def _overwrite_class_target(self, ref_class: str, ref_member: str, replacement_class: 'CSSLClass'):
|
|
1642
|
+
"""Overwrite a target class with the replacement class.
|
|
1643
|
+
|
|
1644
|
+
v4.2.5: Used by 'embedded class' with &target syntax.
|
|
1645
|
+
Handles:
|
|
1646
|
+
- &ClassName - Overwrite CSSL class
|
|
1647
|
+
- &$PyObject - Overwrite Python shared object
|
|
1648
|
+
|
|
1649
|
+
v4.2.6: In namespace mode, track replacements instead of applying globally.
|
|
1650
|
+
"""
|
|
1651
|
+
from ..cssl_bridge import _live_objects, SharedObjectProxy
|
|
1652
|
+
|
|
1653
|
+
# v4.2.6: Check for namespace mode
|
|
1654
|
+
namespace_mode = getattr(self, '_payload_namespace_mode', None)
|
|
1655
|
+
if namespace_mode and not ref_class.startswith('$') and not ref_class.startswith('@'):
|
|
1656
|
+
# Class replacement in namespace mode
|
|
1657
|
+
# Track it for the namespace handler
|
|
1658
|
+
if not hasattr(self, '_namespace_replacements'):
|
|
1659
|
+
self._namespace_replacements = {}
|
|
1660
|
+
self._namespace_replacements[ref_class] = replacement_class
|
|
1661
|
+
# Still register the class normally (namespace handler will move it)
|
|
1662
|
+
self.scope.set(ref_class, replacement_class)
|
|
1663
|
+
self.global_scope.set(ref_class, replacement_class)
|
|
1664
|
+
return
|
|
1665
|
+
|
|
1666
|
+
# Handle Python shared objects
|
|
1667
|
+
if ref_class.startswith('$'):
|
|
1668
|
+
var_name = ref_class[1:]
|
|
1669
|
+
# Replace the shared object with the new class
|
|
1670
|
+
_live_objects[var_name] = replacement_class
|
|
1671
|
+
self.scope.set(var_name, replacement_class)
|
|
1672
|
+
self.global_scope.set(var_name, replacement_class)
|
|
1673
|
+
return
|
|
1674
|
+
|
|
1675
|
+
# Handle @ prefix (global reference)
|
|
1676
|
+
if ref_class.startswith('@'):
|
|
1677
|
+
var_name = ref_class[1:]
|
|
1678
|
+
self.global_scope.set(var_name, replacement_class)
|
|
1679
|
+
self._promoted_globals[var_name] = replacement_class
|
|
1680
|
+
return
|
|
1681
|
+
|
|
1682
|
+
# Handle regular class reference
|
|
1683
|
+
target_class = self.scope.get(ref_class)
|
|
1684
|
+
if target_class is None:
|
|
1685
|
+
target_class = self.global_scope.get(ref_class)
|
|
1686
|
+
|
|
1687
|
+
# Replace the class definition
|
|
1688
|
+
self.scope.set(ref_class, replacement_class)
|
|
1689
|
+
self.global_scope.set(ref_class, replacement_class)
|
|
1690
|
+
if ref_class in self._promoted_globals:
|
|
1691
|
+
self._promoted_globals[ref_class] = replacement_class
|
|
1692
|
+
|
|
1360
1693
|
def _exec_typed_declaration(self, node: ASTNode) -> Any:
|
|
1361
1694
|
"""Execute typed variable declaration: type<T> varName = value;
|
|
1362
1695
|
|
|
@@ -1638,6 +1971,18 @@ class CSSLRuntime:
|
|
|
1638
1971
|
modifiers = func_info.get('modifiers', [])
|
|
1639
1972
|
kwargs = kwargs or {}
|
|
1640
1973
|
|
|
1974
|
+
# v4.2.5: Deferred &target replacement for non-embedded functions
|
|
1975
|
+
# If function has &target and hasn't been applied yet, apply now on first call
|
|
1976
|
+
append_ref_class = func_info.get('append_ref_class')
|
|
1977
|
+
if append_ref_class and not func_info.get('_target_applied', False):
|
|
1978
|
+
append_mode = func_info.get('append_mode', False)
|
|
1979
|
+
append_ref_member = func_info.get('append_ref_member')
|
|
1980
|
+
if append_mode:
|
|
1981
|
+
self._append_to_target(append_ref_class, append_ref_member, func_node)
|
|
1982
|
+
else:
|
|
1983
|
+
self._overwrite_target(append_ref_class, append_ref_member, func_node)
|
|
1984
|
+
func_node.value['_target_applied'] = True
|
|
1985
|
+
|
|
1641
1986
|
# Check for undefined modifier - suppress errors if present
|
|
1642
1987
|
is_undefined = 'undefined' in modifiers
|
|
1643
1988
|
|
|
@@ -2012,6 +2357,141 @@ class CSSLRuntime:
|
|
|
2012
2357
|
|
|
2013
2358
|
return None
|
|
2014
2359
|
|
|
2360
|
+
def _exec_param_switch(self, node: ASTNode) -> Any:
|
|
2361
|
+
"""Execute param switch statement for open parameters.
|
|
2362
|
+
|
|
2363
|
+
v4.2.5: Switch on which parameters were provided.
|
|
2364
|
+
|
|
2365
|
+
Syntax:
|
|
2366
|
+
switch(Params) {
|
|
2367
|
+
case name: // if 'name' param exists
|
|
2368
|
+
case name & age: // if both exist
|
|
2369
|
+
case name & not age: // if 'name' exists but 'age' doesn't
|
|
2370
|
+
except name: // if 'name' does NOT exist
|
|
2371
|
+
default: // fallback
|
|
2372
|
+
always: // always runs after match
|
|
2373
|
+
finally: // cleanup, always runs
|
|
2374
|
+
}
|
|
2375
|
+
"""
|
|
2376
|
+
params_name = node.value.get('params')
|
|
2377
|
+
# v4.3.2: Use the actual variable from switch(Variable), not just _OpenKwargs
|
|
2378
|
+
# This allows switch(Input) where Input is the open parameter
|
|
2379
|
+
if params_name == 'Params':
|
|
2380
|
+
open_kwargs = self.scope.get('_OpenKwargs') or {}
|
|
2381
|
+
else:
|
|
2382
|
+
# Try to get kwargs from the named variable
|
|
2383
|
+
open_kwargs = self.scope.get(params_name)
|
|
2384
|
+
if open_kwargs is None:
|
|
2385
|
+
open_kwargs = self.scope.get('_OpenKwargs') or {}
|
|
2386
|
+
elif not isinstance(open_kwargs, dict):
|
|
2387
|
+
# If it's not a dict, try _OpenKwargs as fallback
|
|
2388
|
+
open_kwargs = self.scope.get('_OpenKwargs') or {}
|
|
2389
|
+
|
|
2390
|
+
matched = False
|
|
2391
|
+
always_node = None
|
|
2392
|
+
finally_node = None
|
|
2393
|
+
default_node = None
|
|
2394
|
+
|
|
2395
|
+
# First pass: find always, finally, default nodes
|
|
2396
|
+
for child in node.children:
|
|
2397
|
+
if child.type == 'param_always':
|
|
2398
|
+
always_node = child
|
|
2399
|
+
elif child.type == 'param_finally':
|
|
2400
|
+
finally_node = child
|
|
2401
|
+
elif child.type == 'param_default':
|
|
2402
|
+
default_node = child
|
|
2403
|
+
|
|
2404
|
+
try:
|
|
2405
|
+
# Execute matching cases
|
|
2406
|
+
for child in node.children:
|
|
2407
|
+
if child.type == 'param_case':
|
|
2408
|
+
condition = child.value.get('condition')
|
|
2409
|
+
if self._eval_param_condition(condition, open_kwargs):
|
|
2410
|
+
matched = True
|
|
2411
|
+
try:
|
|
2412
|
+
for stmt in child.children:
|
|
2413
|
+
self._execute_node(stmt)
|
|
2414
|
+
except CSSLBreak:
|
|
2415
|
+
break
|
|
2416
|
+
|
|
2417
|
+
# Execute default if no match
|
|
2418
|
+
if not matched and default_node:
|
|
2419
|
+
try:
|
|
2420
|
+
for stmt in default_node.children:
|
|
2421
|
+
self._execute_node(stmt)
|
|
2422
|
+
except CSSLBreak:
|
|
2423
|
+
pass
|
|
2424
|
+
|
|
2425
|
+
# Execute always block (runs after a match, before finally)
|
|
2426
|
+
if matched and always_node:
|
|
2427
|
+
try:
|
|
2428
|
+
for stmt in always_node.children:
|
|
2429
|
+
self._execute_node(stmt)
|
|
2430
|
+
except CSSLBreak:
|
|
2431
|
+
pass
|
|
2432
|
+
|
|
2433
|
+
finally:
|
|
2434
|
+
# Execute finally block (always runs, even on break/exception)
|
|
2435
|
+
if finally_node:
|
|
2436
|
+
for stmt in finally_node.children:
|
|
2437
|
+
self._execute_node(stmt)
|
|
2438
|
+
|
|
2439
|
+
return None
|
|
2440
|
+
|
|
2441
|
+
def _eval_param_condition(self, condition: dict, kwargs: dict) -> bool:
|
|
2442
|
+
"""Evaluate a param switch condition.
|
|
2443
|
+
|
|
2444
|
+
Condition types:
|
|
2445
|
+
{'type': 'exists', 'param': 'name'} -> 'name' in kwargs OR variable 'name' is truthy
|
|
2446
|
+
{'type': 'not', 'param': 'name'} -> 'name' not in kwargs AND variable 'name' is falsy
|
|
2447
|
+
{'type': 'and', 'left': {...}, 'right': {...}} -> left AND right
|
|
2448
|
+
{'type': 'or', 'left': {...}, 'right': {...}} -> left OR right
|
|
2449
|
+
|
|
2450
|
+
v4.3.2: Added 'or' type for || operator support.
|
|
2451
|
+
v4.3.2: Enhanced to check both kwargs AND scope variables (from OpenFind).
|
|
2452
|
+
This allows positional args found via OpenFind<type>(index) to work
|
|
2453
|
+
with param_switch conditions like 'case text & !error:'.
|
|
2454
|
+
"""
|
|
2455
|
+
cond_type = condition.get('type')
|
|
2456
|
+
|
|
2457
|
+
if cond_type == 'exists':
|
|
2458
|
+
param = condition.get('param')
|
|
2459
|
+
# Check kwargs first
|
|
2460
|
+
if param in kwargs:
|
|
2461
|
+
return True
|
|
2462
|
+
# Also check if a variable with this name exists in scope and is truthy
|
|
2463
|
+
# This allows OpenFind results to work with param_switch
|
|
2464
|
+
var_value = self.scope.get(param)
|
|
2465
|
+
if var_value is not None and var_value != '' and var_value != 0 and var_value != False:
|
|
2466
|
+
# Make sure it's not a builtin function
|
|
2467
|
+
if not callable(var_value):
|
|
2468
|
+
return True
|
|
2469
|
+
return False
|
|
2470
|
+
|
|
2471
|
+
elif cond_type == 'not':
|
|
2472
|
+
param = condition.get('param')
|
|
2473
|
+
# Check kwargs first
|
|
2474
|
+
if param in kwargs:
|
|
2475
|
+
return False
|
|
2476
|
+
# Also check if variable is truthy (then it's "provided")
|
|
2477
|
+
var_value = self.scope.get(param)
|
|
2478
|
+
if var_value is not None and var_value != '' and var_value != 0 and var_value != False:
|
|
2479
|
+
if not callable(var_value):
|
|
2480
|
+
return False # Variable has value, so it IS provided
|
|
2481
|
+
return True # Not in kwargs and variable is null/empty
|
|
2482
|
+
|
|
2483
|
+
elif cond_type == 'and':
|
|
2484
|
+
left = self._eval_param_condition(condition.get('left'), kwargs)
|
|
2485
|
+
right = self._eval_param_condition(condition.get('right'), kwargs)
|
|
2486
|
+
return left and right
|
|
2487
|
+
|
|
2488
|
+
elif cond_type == 'or':
|
|
2489
|
+
left = self._eval_param_condition(condition.get('left'), kwargs)
|
|
2490
|
+
right = self._eval_param_condition(condition.get('right'), kwargs)
|
|
2491
|
+
return left or right
|
|
2492
|
+
|
|
2493
|
+
return False
|
|
2494
|
+
|
|
2015
2495
|
def _exec_return(self, node: ASTNode) -> Any:
|
|
2016
2496
|
"""Execute return statement.
|
|
2017
2497
|
|
|
@@ -2037,6 +2517,23 @@ class CSSLRuntime:
|
|
|
2037
2517
|
"""Execute continue statement"""
|
|
2038
2518
|
raise CSSLContinue()
|
|
2039
2519
|
|
|
2520
|
+
def _exec_throw(self, node: ASTNode) -> Any:
|
|
2521
|
+
"""Execute throw statement - v4.5.1
|
|
2522
|
+
|
|
2523
|
+
Throws a CSSLThrow exception that propagates to the nearest catch block.
|
|
2524
|
+
|
|
2525
|
+
Syntax:
|
|
2526
|
+
throw "Error message";
|
|
2527
|
+
throw errorVar;
|
|
2528
|
+
throw; // re-throw current exception
|
|
2529
|
+
"""
|
|
2530
|
+
if node.value is None:
|
|
2531
|
+
# throw; - re-throw current exception (should be in a catch block)
|
|
2532
|
+
raise CSSLThrow("Re-thrown exception")
|
|
2533
|
+
|
|
2534
|
+
message = self._evaluate(node.value)
|
|
2535
|
+
raise CSSLThrow(message)
|
|
2536
|
+
|
|
2040
2537
|
def _exec_constructor(self, node: ASTNode) -> Any:
|
|
2041
2538
|
"""Execute constructor node - only called when encountered directly.
|
|
2042
2539
|
|
|
@@ -2114,12 +2611,26 @@ class CSSLRuntime:
|
|
|
2114
2611
|
return None
|
|
2115
2612
|
|
|
2116
2613
|
def _exec_try(self, node: ASTNode) -> Any:
|
|
2117
|
-
"""Execute try/catch block"""
|
|
2614
|
+
"""Execute try/catch/finally block"""
|
|
2118
2615
|
try:
|
|
2119
2616
|
for child in node.children:
|
|
2120
2617
|
if child.type == 'try-block':
|
|
2121
2618
|
for stmt in child.children:
|
|
2122
2619
|
self._execute_node(stmt)
|
|
2620
|
+
except CSSLThrow as e:
|
|
2621
|
+
# v4.5.1: Handle user-thrown exceptions from throw statement
|
|
2622
|
+
for child in node.children:
|
|
2623
|
+
if child.type == 'catch-block':
|
|
2624
|
+
error_var = child.value.get('error_var') if child.value else None
|
|
2625
|
+
if error_var:
|
|
2626
|
+
# Store the thrown message in the error variable
|
|
2627
|
+
self.scope.set(error_var, e.message if e.message else str(e))
|
|
2628
|
+
for stmt in child.children:
|
|
2629
|
+
self._execute_node(stmt)
|
|
2630
|
+
break # Only execute first matching catch block
|
|
2631
|
+
else:
|
|
2632
|
+
# No catch block found - re-raise for outer try-catch
|
|
2633
|
+
raise
|
|
2123
2634
|
except CSSLRuntimeError as e:
|
|
2124
2635
|
for child in node.children:
|
|
2125
2636
|
if child.type == 'catch-block':
|
|
@@ -2128,6 +2639,27 @@ class CSSLRuntime:
|
|
|
2128
2639
|
self.scope.set(error_var, str(e))
|
|
2129
2640
|
for stmt in child.children:
|
|
2130
2641
|
self._execute_node(stmt)
|
|
2642
|
+
break
|
|
2643
|
+
else:
|
|
2644
|
+
raise # Re-raise if no catch block
|
|
2645
|
+
except Exception as e:
|
|
2646
|
+
# v4.2.6: Also catch Python exceptions
|
|
2647
|
+
for child in node.children:
|
|
2648
|
+
if child.type == 'catch-block':
|
|
2649
|
+
error_var = child.value.get('error_var') if child.value else None
|
|
2650
|
+
if error_var:
|
|
2651
|
+
self.scope.set(error_var, str(e))
|
|
2652
|
+
for stmt in child.children:
|
|
2653
|
+
self._execute_node(stmt)
|
|
2654
|
+
break
|
|
2655
|
+
else:
|
|
2656
|
+
raise # Re-raise if no catch block
|
|
2657
|
+
finally:
|
|
2658
|
+
# v4.2.6: Execute finally block if present
|
|
2659
|
+
for child in node.children:
|
|
2660
|
+
if child.type == 'finally-block':
|
|
2661
|
+
for stmt in child.children:
|
|
2662
|
+
self._execute_node(stmt)
|
|
2131
2663
|
|
|
2132
2664
|
return None
|
|
2133
2665
|
|
|
@@ -2831,6 +3363,15 @@ class CSSLRuntime:
|
|
|
2831
3363
|
final_value = {**current_value, **source}
|
|
2832
3364
|
elif isinstance(current_value, str) and isinstance(source, str):
|
|
2833
3365
|
final_value = current_value + source
|
|
3366
|
+
# Handle CSSL container types (DataStruct, Vector, Stack, etc.)
|
|
3367
|
+
elif hasattr(current_value, 'append') or hasattr(current_value, 'push') or hasattr(current_value, 'add'):
|
|
3368
|
+
if hasattr(current_value, 'append'):
|
|
3369
|
+
current_value.append(source)
|
|
3370
|
+
elif hasattr(current_value, 'push'):
|
|
3371
|
+
current_value.push(source)
|
|
3372
|
+
elif hasattr(current_value, 'add'):
|
|
3373
|
+
current_value.add(source)
|
|
3374
|
+
final_value = current_value
|
|
2834
3375
|
elif current_value is None:
|
|
2835
3376
|
final_value = [source] if not isinstance(source, list) else source
|
|
2836
3377
|
else:
|
|
@@ -3353,6 +3894,35 @@ class CSSLRuntime:
|
|
|
3353
3894
|
|
|
3354
3895
|
if node.type == 'identifier':
|
|
3355
3896
|
name = node.value
|
|
3897
|
+
|
|
3898
|
+
# Handle enum/namespace access: Colors::RED, MyNamespace::func
|
|
3899
|
+
if '::' in name:
|
|
3900
|
+
parts = name.split('::', 1)
|
|
3901
|
+
container_name = parts[0]
|
|
3902
|
+
member_name = parts[1]
|
|
3903
|
+
|
|
3904
|
+
# Look up the container (enum, class, or namespace)
|
|
3905
|
+
container = self.scope.get(container_name)
|
|
3906
|
+
if container is None:
|
|
3907
|
+
container = self.global_scope.get(container_name)
|
|
3908
|
+
if container is None:
|
|
3909
|
+
container = self._promoted_globals.get(container_name)
|
|
3910
|
+
|
|
3911
|
+
if container is not None:
|
|
3912
|
+
# If it's a dict-like object (enum or namespace), get the member
|
|
3913
|
+
if isinstance(container, dict):
|
|
3914
|
+
return container.get(member_name)
|
|
3915
|
+
# If it's an object with the member as an attribute
|
|
3916
|
+
elif hasattr(container, member_name):
|
|
3917
|
+
return getattr(container, member_name)
|
|
3918
|
+
|
|
3919
|
+
# v4.3.2: Check if full name exists as builtin function (json::write, string::cut, etc.)
|
|
3920
|
+
if self.builtins.has_function(name):
|
|
3921
|
+
return self.builtins.get_function(name)
|
|
3922
|
+
|
|
3923
|
+
# Fall through to normal lookup if container not found
|
|
3924
|
+
return None
|
|
3925
|
+
|
|
3356
3926
|
value = self.scope.get(name)
|
|
3357
3927
|
# Check if it's a class member in current instance context
|
|
3358
3928
|
# This allows accessing members without 'this->' inside methods
|
|
@@ -3426,14 +3996,22 @@ class CSSLRuntime:
|
|
|
3426
3996
|
|
|
3427
3997
|
if node.type == 'captured_ref':
|
|
3428
3998
|
# %<name> captured reference - use value captured at infusion registration time
|
|
3999
|
+
# Priority: The % prefix means "get the ORIGINAL value before any replacement"
|
|
3429
4000
|
name = node.value
|
|
3430
|
-
|
|
4001
|
+
|
|
4002
|
+
# 1. First check captured values from current injection context
|
|
3431
4003
|
if name in self._current_captured_values:
|
|
3432
4004
|
captured_value = self._current_captured_values[name]
|
|
3433
|
-
# Only use captured value if it's not None
|
|
3434
4005
|
if captured_value is not None:
|
|
3435
4006
|
return captured_value
|
|
3436
|
-
|
|
4007
|
+
|
|
4008
|
+
# 2. v4.2.3: Check _original_functions FIRST - this is the pre-replacement value
|
|
4009
|
+
# This ensures %exit() refers to the ORIGINAL exit when using &exit
|
|
4010
|
+
value = self._original_functions.get(name)
|
|
4011
|
+
if value is not None:
|
|
4012
|
+
return value
|
|
4013
|
+
|
|
4014
|
+
# 3. Fall back to scope/builtins if no original was captured
|
|
3437
4015
|
value = self.scope.get(name)
|
|
3438
4016
|
if value is None:
|
|
3439
4017
|
value = self.global_scope.get(name)
|
|
@@ -3444,9 +4022,6 @@ class CSSLRuntime:
|
|
|
3444
4022
|
value = lambda code=0, rt=runtime: rt.exit(code)
|
|
3445
4023
|
else:
|
|
3446
4024
|
value = getattr(self.builtins, f'builtin_{name}', None)
|
|
3447
|
-
if value is None:
|
|
3448
|
-
# Check original functions (for replaced functions)
|
|
3449
|
-
value = self._original_functions.get(name)
|
|
3450
4025
|
if value is not None:
|
|
3451
4026
|
return value
|
|
3452
4027
|
# Build helpful error for captured reference
|
|
@@ -3782,15 +4357,21 @@ class CSSLRuntime:
|
|
|
3782
4357
|
|
|
3783
4358
|
if op == '/':
|
|
3784
4359
|
r = self._to_number(right)
|
|
3785
|
-
|
|
4360
|
+
if r == 0:
|
|
4361
|
+
raise CSSLRuntimeError("Division by zero")
|
|
4362
|
+
return self._to_number(left) / r
|
|
3786
4363
|
|
|
3787
4364
|
if op == '//':
|
|
3788
4365
|
r = self._to_number(right)
|
|
3789
|
-
|
|
4366
|
+
if r == 0:
|
|
4367
|
+
raise CSSLRuntimeError("Integer division by zero")
|
|
4368
|
+
return self._to_number(left) // r
|
|
3790
4369
|
|
|
3791
4370
|
if op == '%':
|
|
3792
4371
|
r = self._to_number(right)
|
|
3793
|
-
|
|
4372
|
+
if r == 0:
|
|
4373
|
+
raise CSSLRuntimeError("Modulo by zero")
|
|
4374
|
+
return self._to_number(left) % r
|
|
3794
4375
|
|
|
3795
4376
|
if op == '**':
|
|
3796
4377
|
return self._to_number(left) ** self._to_number(right)
|
|
@@ -4144,30 +4725,44 @@ class CSSLRuntime:
|
|
|
4144
4725
|
)
|
|
4145
4726
|
|
|
4146
4727
|
def _eval_new(self, node: ASTNode) -> CSSLInstance:
|
|
4147
|
-
"""Evaluate 'new ClassName(args)' or 'new @ClassName(args)' expression.
|
|
4728
|
+
"""Evaluate 'new ClassName(args)' or 'new @ClassName(args)' or 'new Namespace::ClassName(args)' expression.
|
|
4148
4729
|
|
|
4149
4730
|
Creates a new instance of a CSSL class and calls its constructor.
|
|
4150
4731
|
Supports multiple constructors (constr keyword), class parameters,
|
|
4151
4732
|
and automatic parent constructor calling.
|
|
4152
4733
|
|
|
4153
4734
|
With '@' prefix (new @ClassName), looks only in global scope.
|
|
4735
|
+
With Namespace:: prefix, looks in the namespace dict first.
|
|
4154
4736
|
"""
|
|
4155
4737
|
class_name = node.value.get('class')
|
|
4738
|
+
namespace = node.value.get('namespace') # v4.2.6: Namespace::ClassName support
|
|
4156
4739
|
is_global_ref = node.value.get('is_global_ref', False)
|
|
4157
4740
|
args = [self._evaluate(arg) for arg in node.value.get('args', [])]
|
|
4158
4741
|
kwargs = {k: self._evaluate(v) for k, v in node.value.get('kwargs', {}).items()}
|
|
4159
4742
|
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4743
|
+
class_def = None
|
|
4744
|
+
|
|
4745
|
+
# v4.2.6: Handle Namespace::ClassName lookup
|
|
4746
|
+
if namespace:
|
|
4747
|
+
# Look up namespace dict first
|
|
4748
|
+
ns_dict = self.scope.get(namespace)
|
|
4749
|
+
if ns_dict is None:
|
|
4750
|
+
ns_dict = self.global_scope.get(namespace)
|
|
4751
|
+
if isinstance(ns_dict, dict) and class_name in ns_dict:
|
|
4752
|
+
class_def = ns_dict[class_name]
|
|
4753
|
+
|
|
4754
|
+
# Get class definition from scope (if not found in namespace)
|
|
4755
|
+
if class_def is None:
|
|
4756
|
+
if is_global_ref:
|
|
4757
|
+
# With @ prefix, only look in global scope
|
|
4758
|
+
class_def = self._promoted_globals.get(class_name)
|
|
4759
|
+
if class_def is None:
|
|
4760
|
+
class_def = self.global_scope.get(class_name)
|
|
4761
|
+
else:
|
|
4762
|
+
# Normal lookup: local scope first, then global
|
|
4763
|
+
class_def = self.scope.get(class_name)
|
|
4764
|
+
if class_def is None:
|
|
4765
|
+
class_def = self.global_scope.get(class_name)
|
|
4171
4766
|
|
|
4172
4767
|
if class_def is None:
|
|
4173
4768
|
# Build detailed error with suggestions
|
|
@@ -4216,6 +4811,18 @@ class CSSLRuntime:
|
|
|
4216
4811
|
hint=f"'{class_name}' is of type {type(class_def).__name__}"
|
|
4217
4812
|
)
|
|
4218
4813
|
|
|
4814
|
+
# v4.2.5: Deferred &target replacement for non-embedded classes
|
|
4815
|
+
# Apply on first instantiation if pending
|
|
4816
|
+
if hasattr(class_def, '_pending_target') and class_def._pending_target:
|
|
4817
|
+
pending = class_def._pending_target
|
|
4818
|
+
self._overwrite_class_target(
|
|
4819
|
+
pending['append_ref_class'],
|
|
4820
|
+
pending.get('append_ref_member'),
|
|
4821
|
+
class_def
|
|
4822
|
+
)
|
|
4823
|
+
class_def._target_applied = True
|
|
4824
|
+
class_def._pending_target = None # Clear pending
|
|
4825
|
+
|
|
4219
4826
|
# Create new instance
|
|
4220
4827
|
instance = CSSLInstance(class_def)
|
|
4221
4828
|
|
|
@@ -4646,6 +5253,33 @@ class CSSLRuntime:
|
|
|
4646
5253
|
if isinstance(obj, Parameter) and member == 'return':
|
|
4647
5254
|
member = 'return_'
|
|
4648
5255
|
|
|
5256
|
+
# === ServiceDefinition (from include()) ===
|
|
5257
|
+
if isinstance(obj, ServiceDefinition):
|
|
5258
|
+
# Check functions dict first
|
|
5259
|
+
if member in obj.functions:
|
|
5260
|
+
func_node = obj.functions[member]
|
|
5261
|
+
return lambda *args, **kwargs: self._call_function(func_node, list(args), kwargs)
|
|
5262
|
+
# Check structs dict
|
|
5263
|
+
if member in obj.structs:
|
|
5264
|
+
return obj.structs[member]
|
|
5265
|
+
# Check regular attributes
|
|
5266
|
+
if hasattr(obj, member):
|
|
5267
|
+
return getattr(obj, member)
|
|
5268
|
+
# Build helpful error
|
|
5269
|
+
available = list(obj.functions.keys()) + list(obj.structs.keys())
|
|
5270
|
+
similar = _find_similar_names(member, available)
|
|
5271
|
+
if similar:
|
|
5272
|
+
hint = f"Did you mean: {', '.join(similar)}?"
|
|
5273
|
+
elif available:
|
|
5274
|
+
hint = f"Available: {', '.join(available[:10])}"
|
|
5275
|
+
else:
|
|
5276
|
+
hint = "No functions or structs defined in this module."
|
|
5277
|
+
raise self._format_error(
|
|
5278
|
+
node.line if hasattr(node, 'line') else 0,
|
|
5279
|
+
f"Module has no function or struct '{member}'",
|
|
5280
|
+
hint
|
|
5281
|
+
)
|
|
5282
|
+
|
|
4649
5283
|
# === CSSL CLASS INSTANCE METHODS ===
|
|
4650
5284
|
if isinstance(obj, CSSLInstance):
|
|
4651
5285
|
# Check for member variable
|
|
@@ -4939,12 +5573,19 @@ class CSSLRuntime:
|
|
|
4939
5573
|
index = self._evaluate(node.value.get('index'))
|
|
4940
5574
|
|
|
4941
5575
|
if obj is None:
|
|
4942
|
-
|
|
5576
|
+
raise CSSLRuntimeError(f"Cannot index into None/null value")
|
|
4943
5577
|
|
|
4944
5578
|
try:
|
|
4945
5579
|
return obj[index]
|
|
4946
|
-
except
|
|
4947
|
-
|
|
5580
|
+
except IndexError:
|
|
5581
|
+
# v4.2.6: Throw error instead of returning None
|
|
5582
|
+
length = len(obj) if hasattr(obj, '__len__') else 'unknown'
|
|
5583
|
+
raise CSSLRuntimeError(f"Index {index} out of bounds (length: {length})")
|
|
5584
|
+
except KeyError:
|
|
5585
|
+
# v4.2.6: Throw error for missing dict keys
|
|
5586
|
+
raise CSSLRuntimeError(f"Key '{index}' not found in dictionary")
|
|
5587
|
+
except TypeError as e:
|
|
5588
|
+
raise CSSLRuntimeError(f"Cannot index: {e}")
|
|
4948
5589
|
|
|
4949
5590
|
def _set_member(self, node: ASTNode, value: Any):
|
|
4950
5591
|
"""Set member value"""
|
|
@@ -5238,10 +5879,17 @@ class CSSLRuntime:
|
|
|
5238
5879
|
|
|
5239
5880
|
def _emit_output(self, text: str, level: str = 'normal'):
|
|
5240
5881
|
"""Emit output through callback or print"""
|
|
5882
|
+
import sys
|
|
5241
5883
|
if self._output_callback:
|
|
5242
5884
|
self._output_callback(text, level)
|
|
5243
5885
|
else:
|
|
5244
|
-
|
|
5886
|
+
# Handle encoding issues on Windows console
|
|
5887
|
+
try:
|
|
5888
|
+
print(text, end='')
|
|
5889
|
+
except UnicodeEncodeError:
|
|
5890
|
+
# Fallback: encode with errors='replace' for unsupported chars
|
|
5891
|
+
encoded = text.encode(sys.stdout.encoding or 'utf-8', errors='replace')
|
|
5892
|
+
print(encoded.decode(sys.stdout.encoding or 'utf-8', errors='replace'), end='')
|
|
5245
5893
|
|
|
5246
5894
|
def output(self, text: str):
|
|
5247
5895
|
"""Output text"""
|
|
@@ -5369,14 +6017,79 @@ class CSSLServiceRunner:
|
|
|
5369
6017
|
return list(self.running_services.keys())
|
|
5370
6018
|
|
|
5371
6019
|
|
|
5372
|
-
#
|
|
5373
|
-
|
|
5374
|
-
|
|
6020
|
+
# C++ interpreter cache
|
|
6021
|
+
_cpp_interpreter = None
|
|
6022
|
+
|
|
6023
|
+
|
|
6024
|
+
def _get_cpp_interpreter():
|
|
6025
|
+
"""Get or create C++ interpreter instance."""
|
|
6026
|
+
global _cpp_interpreter
|
|
6027
|
+
if _cpp_interpreter is not None:
|
|
6028
|
+
return _cpp_interpreter
|
|
6029
|
+
|
|
6030
|
+
try:
|
|
6031
|
+
from . import _CPP_AVAILABLE, _cpp_module
|
|
6032
|
+
if _CPP_AVAILABLE and _cpp_module and hasattr(_cpp_module, 'Interpreter'):
|
|
6033
|
+
_cpp_interpreter = _cpp_module.Interpreter()
|
|
6034
|
+
return _cpp_interpreter
|
|
6035
|
+
except Exception:
|
|
6036
|
+
pass
|
|
6037
|
+
return None
|
|
6038
|
+
|
|
6039
|
+
|
|
6040
|
+
def run_cssl(source: str, service_engine=None, force_python: bool = False) -> Any:
|
|
6041
|
+
"""Run CSSL source code.
|
|
6042
|
+
|
|
6043
|
+
Uses C++ interpreter for maximum performance when available.
|
|
6044
|
+
Falls back to Python interpreter for unsupported features.
|
|
6045
|
+
|
|
6046
|
+
Args:
|
|
6047
|
+
source: CSSL source code
|
|
6048
|
+
service_engine: Optional service engine for external integrations
|
|
6049
|
+
force_python: Force Python interpreter (for debugging)
|
|
6050
|
+
|
|
6051
|
+
Returns:
|
|
6052
|
+
Execution result
|
|
6053
|
+
"""
|
|
6054
|
+
# Try C++ interpreter first (10-20x faster)
|
|
6055
|
+
if not force_python:
|
|
6056
|
+
cpp_interp = _get_cpp_interpreter()
|
|
6057
|
+
if cpp_interp:
|
|
6058
|
+
try:
|
|
6059
|
+
return cpp_interp.run_string(source)
|
|
6060
|
+
except Exception as e:
|
|
6061
|
+
# C++ doesn't support this feature, fall back to Python
|
|
6062
|
+
error_msg = str(e).lower()
|
|
6063
|
+
# Only fall back for unsupported features, not syntax errors
|
|
6064
|
+
if 'unsupported' in error_msg or 'not implemented' in error_msg:
|
|
6065
|
+
pass # Fall through to Python
|
|
6066
|
+
else:
|
|
6067
|
+
# Re-raise actual errors
|
|
6068
|
+
raise CSSLRuntimeError(str(e))
|
|
6069
|
+
|
|
6070
|
+
# Python fallback (full feature support)
|
|
5375
6071
|
runtime = CSSLRuntime(service_engine)
|
|
5376
6072
|
return runtime.execute(source)
|
|
5377
6073
|
|
|
5378
6074
|
|
|
5379
|
-
def run_cssl_file(filepath: str, service_engine=None) -> Any:
|
|
5380
|
-
"""Run a CSSL file
|
|
6075
|
+
def run_cssl_file(filepath: str, service_engine=None, force_python: bool = False) -> Any:
|
|
6076
|
+
"""Run a CSSL file.
|
|
6077
|
+
|
|
6078
|
+
Uses C++ interpreter for maximum performance when available.
|
|
6079
|
+
"""
|
|
6080
|
+
# Try C++ interpreter first
|
|
6081
|
+
if not force_python:
|
|
6082
|
+
cpp_interp = _get_cpp_interpreter()
|
|
6083
|
+
if cpp_interp:
|
|
6084
|
+
try:
|
|
6085
|
+
return cpp_interp.run(filepath)
|
|
6086
|
+
except Exception as e:
|
|
6087
|
+
error_msg = str(e).lower()
|
|
6088
|
+
if 'unsupported' in error_msg or 'not implemented' in error_msg:
|
|
6089
|
+
pass # Fall through to Python
|
|
6090
|
+
else:
|
|
6091
|
+
raise CSSLRuntimeError(str(e))
|
|
6092
|
+
|
|
6093
|
+
# Python fallback
|
|
5381
6094
|
runtime = CSSLRuntime(service_engine)
|
|
5382
6095
|
return runtime.execute_file(filepath)
|