IncludeCPP 3.8.8__py3-none-any.whl → 4.0.0__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/__init__.py +1 -1
- includecpp/core/cssl/CSSL_DOCUMENTATION.md +877 -1781
- includecpp/core/cssl/CSSL_DOCUMENTATION_NEW.md +1348 -0
- includecpp/core/cssl/cssl_builtins.pyi +231 -0
- includecpp/core/cssl/cssl_parser.py +439 -94
- includecpp/core/cssl/cssl_runtime.py +498 -27
- includecpp/vscode/cssl/syntaxes/cssl.tmLanguage.json +25 -0
- {includecpp-3.8.8.dist-info → includecpp-4.0.0.dist-info}/METADATA +1 -1
- {includecpp-3.8.8.dist-info → includecpp-4.0.0.dist-info}/RECORD +13 -12
- {includecpp-3.8.8.dist-info → includecpp-4.0.0.dist-info}/WHEEL +0 -0
- {includecpp-3.8.8.dist-info → includecpp-4.0.0.dist-info}/entry_points.txt +0 -0
- {includecpp-3.8.8.dist-info → includecpp-4.0.0.dist-info}/licenses/LICENSE +0 -0
- {includecpp-3.8.8.dist-info → includecpp-4.0.0.dist-info}/top_level.txt +0 -0
|
@@ -99,6 +99,89 @@ ERROR_HINTS = {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
|
|
102
|
+
def _find_similar_names(name: str, candidates: list, max_distance: int = 2) -> list:
|
|
103
|
+
"""Find similar names for 'did you mean' suggestions using Levenshtein distance."""
|
|
104
|
+
if not candidates:
|
|
105
|
+
return []
|
|
106
|
+
|
|
107
|
+
def levenshtein(s1: str, s2: str) -> int:
|
|
108
|
+
if len(s1) < len(s2):
|
|
109
|
+
s1, s2 = s2, s1
|
|
110
|
+
if len(s2) == 0:
|
|
111
|
+
return len(s1)
|
|
112
|
+
prev_row = range(len(s2) + 1)
|
|
113
|
+
for i, c1 in enumerate(s1):
|
|
114
|
+
curr_row = [i + 1]
|
|
115
|
+
for j, c2 in enumerate(s2):
|
|
116
|
+
insertions = prev_row[j + 1] + 1
|
|
117
|
+
deletions = curr_row[j] + 1
|
|
118
|
+
substitutions = prev_row[j] + (c1.lower() != c2.lower())
|
|
119
|
+
curr_row.append(min(insertions, deletions, substitutions))
|
|
120
|
+
prev_row = curr_row
|
|
121
|
+
return prev_row[-1]
|
|
122
|
+
|
|
123
|
+
similar = []
|
|
124
|
+
name_lower = name.lower()
|
|
125
|
+
for candidate in candidates:
|
|
126
|
+
if candidate.startswith('_'):
|
|
127
|
+
continue
|
|
128
|
+
dist = levenshtein(name, candidate)
|
|
129
|
+
# Also check case-insensitive exact match
|
|
130
|
+
if name_lower == candidate.lower() and name != candidate:
|
|
131
|
+
similar.insert(0, candidate) # Exact case mismatch goes first
|
|
132
|
+
elif dist <= max_distance:
|
|
133
|
+
similar.append(candidate)
|
|
134
|
+
|
|
135
|
+
return similar[:3] # Return top 3 suggestions
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _get_available_classes(scope: 'Scope', global_scope: 'Scope', promoted_globals: dict) -> list:
|
|
139
|
+
"""Get list of all available class names."""
|
|
140
|
+
classes = []
|
|
141
|
+
# Check current scope chain
|
|
142
|
+
current = scope
|
|
143
|
+
while current:
|
|
144
|
+
for name, val in current.variables.items():
|
|
145
|
+
if isinstance(val, CSSLClass) and name not in classes:
|
|
146
|
+
classes.append(name)
|
|
147
|
+
current = current.parent
|
|
148
|
+
# Check global scope
|
|
149
|
+
for name, val in global_scope.variables.items():
|
|
150
|
+
if isinstance(val, CSSLClass) and name not in classes:
|
|
151
|
+
classes.append(name)
|
|
152
|
+
# Check promoted globals
|
|
153
|
+
for name, val in promoted_globals.items():
|
|
154
|
+
if isinstance(val, CSSLClass) and name not in classes:
|
|
155
|
+
classes.append(name)
|
|
156
|
+
return classes
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _get_available_functions(scope: 'Scope', global_scope: 'Scope', builtins) -> list:
|
|
160
|
+
"""Get list of all available function names."""
|
|
161
|
+
functions = []
|
|
162
|
+
# Check current scope chain
|
|
163
|
+
current = scope
|
|
164
|
+
while current:
|
|
165
|
+
for name, val in current.variables.items():
|
|
166
|
+
if callable(val) or (isinstance(val, ASTNode) and val.type == 'function'):
|
|
167
|
+
if name not in functions:
|
|
168
|
+
functions.append(name)
|
|
169
|
+
current = current.parent
|
|
170
|
+
# Check global scope
|
|
171
|
+
for name, val in global_scope.variables.items():
|
|
172
|
+
if callable(val) or (isinstance(val, ASTNode) and val.type == 'function'):
|
|
173
|
+
if name not in functions:
|
|
174
|
+
functions.append(name)
|
|
175
|
+
# Check builtins
|
|
176
|
+
if builtins:
|
|
177
|
+
for name in dir(builtins):
|
|
178
|
+
if name.startswith('builtin_'):
|
|
179
|
+
func_name = name[8:] # Remove 'builtin_' prefix
|
|
180
|
+
if func_name not in functions:
|
|
181
|
+
functions.append(func_name)
|
|
182
|
+
return functions
|
|
183
|
+
|
|
184
|
+
|
|
102
185
|
class CSSLBreak(Exception):
|
|
103
186
|
"""Break statement"""
|
|
104
187
|
pass
|
|
@@ -716,9 +799,12 @@ class CSSLRuntime:
|
|
|
716
799
|
Parses class members and methods, creating a CSSLClass object
|
|
717
800
|
that can be instantiated with 'new'.
|
|
718
801
|
Supports inheritance via 'extends' keyword and method overwriting via 'overwrites'.
|
|
802
|
+
|
|
803
|
+
Classes are local by default. Use 'global class' or 'class @Name' for global classes.
|
|
719
804
|
"""
|
|
720
805
|
class_info = node.value
|
|
721
806
|
class_name = class_info.get('name')
|
|
807
|
+
is_global = class_info.get('is_global', False)
|
|
722
808
|
extends_class_name = class_info.get('extends')
|
|
723
809
|
extends_is_python = class_info.get('extends_is_python', False)
|
|
724
810
|
overwrites_class_name = class_info.get('overwrites')
|
|
@@ -745,7 +831,22 @@ class CSSLRuntime:
|
|
|
745
831
|
parent_class = self.global_scope.get(extends_class_name)
|
|
746
832
|
|
|
747
833
|
if parent_class is None:
|
|
748
|
-
|
|
834
|
+
# Build detailed error for extends
|
|
835
|
+
available_classes = _get_available_classes(self.scope, self.global_scope, self._promoted_globals)
|
|
836
|
+
similar = _find_similar_names(extends_class_name, available_classes)
|
|
837
|
+
|
|
838
|
+
if similar:
|
|
839
|
+
hint = f"Did you mean: {', '.join(similar)}?"
|
|
840
|
+
elif extends_is_python:
|
|
841
|
+
hint = f"Python object '${extends_class_name}' not found. Use share() to share Python objects first."
|
|
842
|
+
else:
|
|
843
|
+
hint = f"Define class '{extends_class_name}' before this class, or use 'extends $PyObject' for Python objects"
|
|
844
|
+
|
|
845
|
+
raise self._format_error(
|
|
846
|
+
node.line,
|
|
847
|
+
f"Cannot extend unknown class '{extends_class_name}'",
|
|
848
|
+
hint
|
|
849
|
+
)
|
|
749
850
|
|
|
750
851
|
# Auto-wrap Python objects for inheritance
|
|
751
852
|
from .cssl_builtins import CSSLizedPythonObject
|
|
@@ -803,9 +904,11 @@ class CSSLRuntime:
|
|
|
803
904
|
class_def.class_params = class_params # Class-level constructor parameters
|
|
804
905
|
class_def.extends_args = extends_args # Arguments to pass to parent constructor
|
|
805
906
|
|
|
806
|
-
# Register class in scope
|
|
907
|
+
# Register class in scope (local by default, global if marked)
|
|
807
908
|
self.scope.set(class_name, class_def)
|
|
808
|
-
|
|
909
|
+
if is_global:
|
|
910
|
+
self.global_scope.set(class_name, class_def)
|
|
911
|
+
self._promoted_globals[class_name] = class_def
|
|
809
912
|
|
|
810
913
|
# Handle class overwrites - replace methods in target class
|
|
811
914
|
if overwrites_class_name:
|
|
@@ -888,12 +991,15 @@ class CSSLRuntime:
|
|
|
888
991
|
"""Execute function definition - registers it and handles extends/overwrites.
|
|
889
992
|
|
|
890
993
|
Syntax:
|
|
891
|
-
define func() { ... }
|
|
892
|
-
define func
|
|
893
|
-
define func
|
|
994
|
+
define func() { ... } - Local function
|
|
995
|
+
global define func() { ... } - Global function
|
|
996
|
+
define @func() { ... } - Global function (alternative)
|
|
997
|
+
define func : extends otherFunc() { ... } - Inherit local vars
|
|
998
|
+
define func : overwrites otherFunc() { ... } - Replace otherFunc
|
|
894
999
|
"""
|
|
895
1000
|
func_info = node.value
|
|
896
1001
|
func_name = func_info.get('name')
|
|
1002
|
+
is_global = func_info.get('is_global', False)
|
|
897
1003
|
extends_func = func_info.get('extends')
|
|
898
1004
|
extends_is_python = func_info.get('extends_is_python', False)
|
|
899
1005
|
overwrites_func = func_info.get('overwrites')
|
|
@@ -922,8 +1028,11 @@ class CSSLRuntime:
|
|
|
922
1028
|
self.scope.set(overwrites_func, node)
|
|
923
1029
|
self.global_scope.set(overwrites_func, node)
|
|
924
1030
|
|
|
925
|
-
# Register the function
|
|
1031
|
+
# Register the function (local by default, global if marked)
|
|
926
1032
|
self.scope.set(func_name, node)
|
|
1033
|
+
if is_global:
|
|
1034
|
+
self.global_scope.set(func_name, node)
|
|
1035
|
+
self._promoted_globals[func_name] = node
|
|
927
1036
|
return None
|
|
928
1037
|
|
|
929
1038
|
def _resolve_function_target(self, name: str, is_python: bool) -> Any:
|
|
@@ -1290,13 +1399,97 @@ class CSSLRuntime:
|
|
|
1290
1399
|
self.scope = new_scope
|
|
1291
1400
|
|
|
1292
1401
|
try:
|
|
1402
|
+
# Handle append mode (++) - execute referenced function first
|
|
1403
|
+
append_mode = func_info.get('append_mode', False)
|
|
1404
|
+
append_ref_class = func_info.get('append_ref_class')
|
|
1405
|
+
append_ref_member = func_info.get('append_ref_member')
|
|
1406
|
+
|
|
1407
|
+
if append_mode and append_ref_class:
|
|
1408
|
+
self._execute_append_reference(
|
|
1409
|
+
None, append_ref_class, append_ref_member,
|
|
1410
|
+
args, kwargs, {}, is_constructor=False
|
|
1411
|
+
)
|
|
1412
|
+
|
|
1293
1413
|
for child in func_node.children:
|
|
1294
1414
|
# Check if exit() was called
|
|
1295
1415
|
if not self._running:
|
|
1296
1416
|
break
|
|
1297
1417
|
self._execute_node(child)
|
|
1298
1418
|
except CSSLReturn as ret:
|
|
1299
|
-
|
|
1419
|
+
return_value = ret.value
|
|
1420
|
+
|
|
1421
|
+
# Check exclude_type: *[type] - must NOT return excluded type
|
|
1422
|
+
exclude_type = func_info.get('exclude_type')
|
|
1423
|
+
if exclude_type:
|
|
1424
|
+
type_map = {
|
|
1425
|
+
'string': str, 'int': int, 'float': float, 'bool': bool,
|
|
1426
|
+
'null': type(None), 'none': type(None),
|
|
1427
|
+
'list': list, 'array': list, 'dict': dict, 'json': dict,
|
|
1428
|
+
}
|
|
1429
|
+
excluded_py_type = type_map.get(exclude_type.lower())
|
|
1430
|
+
# For shuffled returns (tuples), check each element
|
|
1431
|
+
if isinstance(return_value, tuple):
|
|
1432
|
+
for val in return_value:
|
|
1433
|
+
if excluded_py_type and isinstance(val, excluded_py_type):
|
|
1434
|
+
raise CSSLRuntimeError(f"Type exclusion: function must NOT return '{exclude_type}' values")
|
|
1435
|
+
elif excluded_py_type and isinstance(return_value, excluded_py_type):
|
|
1436
|
+
raise CSSLRuntimeError(f"Type exclusion: function must NOT return '{exclude_type}'")
|
|
1437
|
+
|
|
1438
|
+
# Enforce return type for typed functions (like C++)
|
|
1439
|
+
# Typed functions MUST return the declared type
|
|
1440
|
+
# Exception: 'meta' modifier allows any return type
|
|
1441
|
+
enforce_return_type = func_info.get('enforce_return_type', False)
|
|
1442
|
+
return_type = func_info.get('return_type')
|
|
1443
|
+
|
|
1444
|
+
if enforce_return_type and return_type and return_type != 'void':
|
|
1445
|
+
# Type mapping from CSSL types to Python types
|
|
1446
|
+
type_validate_map = {
|
|
1447
|
+
'string': str, 'int': int, 'float': (int, float), 'bool': bool,
|
|
1448
|
+
'list': list, 'array': list, 'dict': dict, 'json': dict,
|
|
1449
|
+
'dynamic': object, # Any type
|
|
1450
|
+
'void': type(None),
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
# Generic container types - accept lists/tuples
|
|
1454
|
+
container_types = {
|
|
1455
|
+
'vector', 'stack', 'datastruct', 'dataspace',
|
|
1456
|
+
'shuffled', 'iterator', 'combo', 'openquote', 'map'
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
if return_type in container_types:
|
|
1460
|
+
# Container types accept list, tuple, dict depending on type
|
|
1461
|
+
if return_type == 'map':
|
|
1462
|
+
expected = dict
|
|
1463
|
+
elif return_type == 'shuffled':
|
|
1464
|
+
expected = (list, tuple)
|
|
1465
|
+
else:
|
|
1466
|
+
expected = (list, tuple, object)
|
|
1467
|
+
|
|
1468
|
+
if not isinstance(return_value, expected):
|
|
1469
|
+
func_name = func_info.get('name', 'unknown')
|
|
1470
|
+
actual_type = type(return_value).__name__
|
|
1471
|
+
raise CSSLRuntimeError(
|
|
1472
|
+
f"Type error in '{func_name}': declared return type '{return_type}' "
|
|
1473
|
+
f"but returned '{actual_type}'. Typed functions must return declared type."
|
|
1474
|
+
)
|
|
1475
|
+
elif return_type in type_validate_map:
|
|
1476
|
+
expected = type_validate_map[return_type]
|
|
1477
|
+
if expected != object and return_value is not None:
|
|
1478
|
+
if not isinstance(return_value, expected):
|
|
1479
|
+
func_name = func_info.get('name', 'unknown')
|
|
1480
|
+
actual_type = type(return_value).__name__
|
|
1481
|
+
raise CSSLRuntimeError(
|
|
1482
|
+
f"Type error in '{func_name}': declared return type '{return_type}' "
|
|
1483
|
+
f"but returned '{actual_type}'. Typed functions must return declared type."
|
|
1484
|
+
)
|
|
1485
|
+
|
|
1486
|
+
# Check non_null: function must return a value (not None)
|
|
1487
|
+
non_null = func_info.get('non_null', False)
|
|
1488
|
+
if non_null and return_value is None:
|
|
1489
|
+
func_name = func_info.get('name', 'unknown')
|
|
1490
|
+
raise CSSLRuntimeError(f"Non-null function '{func_name}' returned null/None")
|
|
1491
|
+
|
|
1492
|
+
return return_value
|
|
1300
1493
|
except Exception as e:
|
|
1301
1494
|
# If undefined modifier, suppress all errors
|
|
1302
1495
|
if is_undefined:
|
|
@@ -2704,7 +2897,21 @@ class CSSLRuntime:
|
|
|
2704
2897
|
scoped_val = self.global_scope.get(f'${name}')
|
|
2705
2898
|
if scoped_val is not None:
|
|
2706
2899
|
return scoped_val
|
|
2707
|
-
|
|
2900
|
+
# List available shared objects for helpful error
|
|
2901
|
+
available_shared = list(_live_objects.keys())
|
|
2902
|
+
similar = _find_similar_names(name, available_shared)
|
|
2903
|
+
if similar:
|
|
2904
|
+
hint = f"Did you mean: ${', $'.join(similar)}?"
|
|
2905
|
+
elif available_shared:
|
|
2906
|
+
hint = f"Available shared objects: ${', $'.join(available_shared[:5])}"
|
|
2907
|
+
else:
|
|
2908
|
+
hint = "Use share(name, object) from Python to share objects first."
|
|
2909
|
+
|
|
2910
|
+
raise self._format_error(
|
|
2911
|
+
node.line if hasattr(node, 'line') else 0,
|
|
2912
|
+
f"Shared object '${name}' not found",
|
|
2913
|
+
hint
|
|
2914
|
+
)
|
|
2708
2915
|
|
|
2709
2916
|
if node.type == 'captured_ref':
|
|
2710
2917
|
# %<name> captured reference - use value captured at infusion registration time
|
|
@@ -2731,7 +2938,13 @@ class CSSLRuntime:
|
|
|
2731
2938
|
value = self._original_functions.get(name)
|
|
2732
2939
|
if value is not None:
|
|
2733
2940
|
return value
|
|
2734
|
-
|
|
2941
|
+
# Build helpful error for captured reference
|
|
2942
|
+
hint = f"Variable '{name}' must exist when the infusion is registered. Check that '%{name}' is defined before the <<== operator."
|
|
2943
|
+
raise self._format_error(
|
|
2944
|
+
node.line if hasattr(node, 'line') else 0,
|
|
2945
|
+
f"Captured reference '%{name}' not found",
|
|
2946
|
+
hint
|
|
2947
|
+
)
|
|
2735
2948
|
|
|
2736
2949
|
if node.type == 'instance_ref':
|
|
2737
2950
|
# instance<"name"> - get shared instance by name
|
|
@@ -2820,6 +3033,58 @@ class CSSLRuntime:
|
|
|
2820
3033
|
if node.type == 'unary':
|
|
2821
3034
|
return self._eval_unary(node)
|
|
2822
3035
|
|
|
3036
|
+
if node.type == 'non_null_assert':
|
|
3037
|
+
# *$var, *@module, *identifier - assert value is not null/None
|
|
3038
|
+
operand = node.value.get('operand')
|
|
3039
|
+
value = self._evaluate(operand)
|
|
3040
|
+
if value is None:
|
|
3041
|
+
# Get name of the operand for better error message
|
|
3042
|
+
operand_name = "unknown"
|
|
3043
|
+
if isinstance(operand, ASTNode):
|
|
3044
|
+
if operand.type == 'identifier':
|
|
3045
|
+
operand_name = operand.value
|
|
3046
|
+
elif operand.type == 'shared_ref':
|
|
3047
|
+
operand_name = f"${operand.value}"
|
|
3048
|
+
elif operand.type == 'module_ref':
|
|
3049
|
+
operand_name = f"@{operand.value}"
|
|
3050
|
+
elif operand.type == 'global_ref':
|
|
3051
|
+
operand_name = f"r@{operand.value}"
|
|
3052
|
+
raise self._format_error(
|
|
3053
|
+
node.line if hasattr(node, 'line') else 0,
|
|
3054
|
+
f"Non-null assertion failed: '{operand_name}' is null/None",
|
|
3055
|
+
f"The value accessed via '*{operand_name}' must not be null. Check that it is defined and initialized."
|
|
3056
|
+
)
|
|
3057
|
+
return value
|
|
3058
|
+
|
|
3059
|
+
if node.type == 'type_exclude_assert':
|
|
3060
|
+
# *[type]expr - assert value is NOT of excluded type
|
|
3061
|
+
exclude_type = node.value.get('exclude_type')
|
|
3062
|
+
operand = node.value.get('operand')
|
|
3063
|
+
value = self._evaluate(operand)
|
|
3064
|
+
|
|
3065
|
+
# Map CSSL types to Python types
|
|
3066
|
+
type_map = {
|
|
3067
|
+
'string': str,
|
|
3068
|
+
'int': int,
|
|
3069
|
+
'float': float,
|
|
3070
|
+
'bool': bool,
|
|
3071
|
+
'null': type(None),
|
|
3072
|
+
'none': type(None),
|
|
3073
|
+
'list': list,
|
|
3074
|
+
'array': list,
|
|
3075
|
+
'dict': dict,
|
|
3076
|
+
'json': dict,
|
|
3077
|
+
}
|
|
3078
|
+
|
|
3079
|
+
excluded_py_type = type_map.get(exclude_type.lower())
|
|
3080
|
+
if excluded_py_type and isinstance(value, excluded_py_type):
|
|
3081
|
+
raise self._format_error(
|
|
3082
|
+
node.line if hasattr(node, 'line') else 0,
|
|
3083
|
+
f"Type exclusion assertion failed: value is of excluded type '{exclude_type}'",
|
|
3084
|
+
f"The expression was marked *[{exclude_type}] meaning it must NOT return {exclude_type}, but it did."
|
|
3085
|
+
)
|
|
3086
|
+
return value
|
|
3087
|
+
|
|
2823
3088
|
if node.type == 'call':
|
|
2824
3089
|
return self._eval_call(node)
|
|
2825
3090
|
|
|
@@ -3097,12 +3362,33 @@ class CSSLRuntime:
|
|
|
3097
3362
|
return self._call_function(callee, args, kwargs)
|
|
3098
3363
|
|
|
3099
3364
|
callee_name = callee_node.value if isinstance(callee_node, ASTNode) and hasattr(callee_node, 'value') else str(callee_node)
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3365
|
+
|
|
3366
|
+
# Build detailed error with suggestions
|
|
3367
|
+
available_funcs = _get_available_functions(self.scope, self.global_scope, self.builtins)
|
|
3368
|
+
similar = _find_similar_names(callee_name, available_funcs)
|
|
3369
|
+
|
|
3370
|
+
if callee is None:
|
|
3371
|
+
# Function not found at all
|
|
3372
|
+
if similar:
|
|
3373
|
+
hint = f"Did you mean: {', '.join(similar)}?"
|
|
3374
|
+
else:
|
|
3375
|
+
hint = f"Function '{callee_name}' is not defined. Define it with: define {callee_name}() {{ }}"
|
|
3376
|
+
raise self._format_error(
|
|
3377
|
+
node.line,
|
|
3378
|
+
f"Function '{callee_name}' not found",
|
|
3379
|
+
hint
|
|
3380
|
+
)
|
|
3381
|
+
else:
|
|
3382
|
+
# Found something but it's not callable
|
|
3383
|
+
if similar:
|
|
3384
|
+
hint = f"'{callee_name}' is a {type(callee).__name__}, not a function. Did you mean: {', '.join(similar)}?"
|
|
3385
|
+
else:
|
|
3386
|
+
hint = f"'{callee_name}' is a {type(callee).__name__}. Functions must be defined with 'define' keyword."
|
|
3387
|
+
raise self._format_error(
|
|
3388
|
+
node.line,
|
|
3389
|
+
f"Cannot call '{callee_name}' - it is not a function",
|
|
3390
|
+
hint
|
|
3391
|
+
)
|
|
3106
3392
|
|
|
3107
3393
|
def _eval_typed_call(self, node: ASTNode) -> Any:
|
|
3108
3394
|
"""Evaluate typed function call like OpenFind<string>(0) or OpenFind<dynamic, "name">"""
|
|
@@ -3168,26 +3454,53 @@ class CSSLRuntime:
|
|
|
3168
3454
|
)
|
|
3169
3455
|
|
|
3170
3456
|
def _eval_new(self, node: ASTNode) -> CSSLInstance:
|
|
3171
|
-
"""Evaluate 'new ClassName(args)' expression.
|
|
3457
|
+
"""Evaluate 'new ClassName(args)' or 'new @ClassName(args)' expression.
|
|
3172
3458
|
|
|
3173
3459
|
Creates a new instance of a CSSL class and calls its constructor.
|
|
3174
3460
|
Supports multiple constructors (constr keyword), class parameters,
|
|
3175
3461
|
and automatic parent constructor calling.
|
|
3462
|
+
|
|
3463
|
+
With '@' prefix (new @ClassName), looks only in global scope.
|
|
3176
3464
|
"""
|
|
3177
3465
|
class_name = node.value.get('class')
|
|
3466
|
+
is_global_ref = node.value.get('is_global_ref', False)
|
|
3178
3467
|
args = [self._evaluate(arg) for arg in node.value.get('args', [])]
|
|
3179
3468
|
kwargs = {k: self._evaluate(v) for k, v in node.value.get('kwargs', {}).items()}
|
|
3180
3469
|
|
|
3181
3470
|
# Get class definition from scope
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
class_def = self.
|
|
3471
|
+
if is_global_ref:
|
|
3472
|
+
# With @ prefix, only look in global scope
|
|
3473
|
+
class_def = self._promoted_globals.get(class_name)
|
|
3474
|
+
if class_def is None:
|
|
3475
|
+
class_def = self.global_scope.get(class_name)
|
|
3476
|
+
else:
|
|
3477
|
+
# Normal lookup: local scope first, then global
|
|
3478
|
+
class_def = self.scope.get(class_name)
|
|
3479
|
+
if class_def is None:
|
|
3480
|
+
class_def = self.global_scope.get(class_name)
|
|
3185
3481
|
|
|
3186
3482
|
if class_def is None:
|
|
3187
|
-
|
|
3188
|
-
|
|
3483
|
+
# Build detailed error with suggestions
|
|
3484
|
+
source_line = self._get_source_line(node.line)
|
|
3485
|
+
available_classes = _get_available_classes(self.scope, self.global_scope, self._promoted_globals)
|
|
3486
|
+
similar = _find_similar_names(class_name, available_classes)
|
|
3487
|
+
|
|
3488
|
+
# Check if class exists in global scope (user forgot @)
|
|
3489
|
+
global_class = self._promoted_globals.get(class_name) or self.global_scope.get(class_name)
|
|
3490
|
+
if global_class and isinstance(global_class, CSSLClass) and not is_global_ref:
|
|
3491
|
+
hint = f"Class '{class_name}' exists in global scope. Use: new @{class_name}()"
|
|
3492
|
+
elif similar:
|
|
3493
|
+
hint = f"Did you mean: {', '.join(similar)}?"
|
|
3494
|
+
elif available_classes:
|
|
3495
|
+
hint = f"Available classes: {', '.join(available_classes[:5])}"
|
|
3496
|
+
else:
|
|
3497
|
+
hint = "Define the class before instantiation, or use 'global class' / 'class @Name' for global classes"
|
|
3498
|
+
|
|
3499
|
+
context = f"in expression: {source_line.strip()}" if source_line else None
|
|
3500
|
+
raise self._format_error(
|
|
3189
3501
|
node.line,
|
|
3190
|
-
|
|
3502
|
+
f"Class '{class_name}' not found",
|
|
3503
|
+
hint
|
|
3191
3504
|
)
|
|
3192
3505
|
|
|
3193
3506
|
if not isinstance(class_def, CSSLClass):
|
|
@@ -3271,17 +3584,122 @@ class CSSLRuntime:
|
|
|
3271
3584
|
except TypeError:
|
|
3272
3585
|
pass
|
|
3273
3586
|
|
|
3587
|
+
def _execute_append_reference(self, instance: CSSLInstance, ref_class: str, ref_member: str,
|
|
3588
|
+
args: list, kwargs: dict, param_values: dict, is_constructor: bool = True):
|
|
3589
|
+
"""Execute referenced parent constructor/method for append mode (++).
|
|
3590
|
+
|
|
3591
|
+
Resolves the class/instance reference and executes the specified member.
|
|
3592
|
+
Supports:
|
|
3593
|
+
- Static class references: &ClassName::member
|
|
3594
|
+
- Dynamic instance references: &$instanceVar::member
|
|
3595
|
+
- Direct function references: &FunctionName (for define functions)
|
|
3596
|
+
|
|
3597
|
+
Args:
|
|
3598
|
+
instance: Current class instance (can be None for standalone functions)
|
|
3599
|
+
ref_class: Referenced class name, $instanceVar, or function name
|
|
3600
|
+
ref_member: Member name (constructor name, 'constructors', or method name) - can be None
|
|
3601
|
+
args: Arguments to pass
|
|
3602
|
+
kwargs: Keyword arguments to pass
|
|
3603
|
+
param_values: Parameter values from class params
|
|
3604
|
+
is_constructor: True if looking for constructor, False for method
|
|
3605
|
+
"""
|
|
3606
|
+
from .cssl_builtins import CSSLizedPythonObject
|
|
3607
|
+
|
|
3608
|
+
# Handle direct function reference: &FunctionName ++ (no ::member part)
|
|
3609
|
+
if ref_member is None and not is_constructor:
|
|
3610
|
+
# ref_class is actually a function name
|
|
3611
|
+
func_name = ref_class
|
|
3612
|
+
if func_name.startswith('$'):
|
|
3613
|
+
func_name = func_name[1:]
|
|
3614
|
+
|
|
3615
|
+
# Look for the function in scope
|
|
3616
|
+
func = self.scope.get(func_name) or self.global_scope.get(func_name)
|
|
3617
|
+
if func is not None:
|
|
3618
|
+
if isinstance(func, ASTNode) and func.type in ('function', 'FUNCTION'):
|
|
3619
|
+
# Execute the referenced function
|
|
3620
|
+
if instance:
|
|
3621
|
+
self._call_method(instance, func, args, kwargs)
|
|
3622
|
+
else:
|
|
3623
|
+
self._call_function(func, args, kwargs)
|
|
3624
|
+
elif callable(func):
|
|
3625
|
+
# It's a callable (Python function or lambda)
|
|
3626
|
+
func(*args, **kwargs)
|
|
3627
|
+
return
|
|
3628
|
+
|
|
3629
|
+
# Resolve the class/instance reference
|
|
3630
|
+
if ref_class.startswith('$'):
|
|
3631
|
+
# Dynamic instance reference: &$instanceVar::member
|
|
3632
|
+
var_name = ref_class[1:]
|
|
3633
|
+
ref_obj = self.scope.get(var_name) or self.global_scope.get(var_name)
|
|
3634
|
+
if ref_obj is None:
|
|
3635
|
+
return # Instance not found, skip silently
|
|
3636
|
+
|
|
3637
|
+
if isinstance(ref_obj, CSSLInstance):
|
|
3638
|
+
# Get the class definition from the instance
|
|
3639
|
+
target_class = ref_obj.class_def
|
|
3640
|
+
elif isinstance(ref_obj, CSSLClass):
|
|
3641
|
+
target_class = ref_obj
|
|
3642
|
+
else:
|
|
3643
|
+
return # Not a valid reference
|
|
3644
|
+
else:
|
|
3645
|
+
# Static class reference: &ClassName::member
|
|
3646
|
+
target_class = self.scope.get(ref_class) or self.global_scope.get(ref_class)
|
|
3647
|
+
|
|
3648
|
+
if target_class is None:
|
|
3649
|
+
return # Class not found, skip silently
|
|
3650
|
+
|
|
3651
|
+
# Handle different target types
|
|
3652
|
+
if isinstance(target_class, CSSLInstance):
|
|
3653
|
+
# Referenced an instance variable directly
|
|
3654
|
+
target_class = target_class.class_def
|
|
3655
|
+
|
|
3656
|
+
if not isinstance(target_class, CSSLClass):
|
|
3657
|
+
return # Not a CSSL class
|
|
3658
|
+
|
|
3659
|
+
# Find and execute the referenced member
|
|
3660
|
+
if is_constructor:
|
|
3661
|
+
# Looking for a constructor
|
|
3662
|
+
if ref_member == 'constructors' or ref_member is None:
|
|
3663
|
+
# Execute all constructors from the referenced class
|
|
3664
|
+
for constr in getattr(target_class, 'constructors', []):
|
|
3665
|
+
self._call_constructor(instance, constr, args, kwargs, param_values)
|
|
3666
|
+
else:
|
|
3667
|
+
# Execute specific constructor by name
|
|
3668
|
+
for constr in getattr(target_class, 'constructors', []):
|
|
3669
|
+
if constr.value.get('name') == ref_member:
|
|
3670
|
+
self._call_constructor(instance, constr, args, kwargs, param_values)
|
|
3671
|
+
break
|
|
3672
|
+
else:
|
|
3673
|
+
# Looking for a method (define function)
|
|
3674
|
+
if ref_member:
|
|
3675
|
+
# Find method in class
|
|
3676
|
+
for member in getattr(target_class, 'members', []):
|
|
3677
|
+
if member.type in ('function', 'FUNCTION') and member.value.get('name') == ref_member:
|
|
3678
|
+
self._call_method(instance, member, args, kwargs)
|
|
3679
|
+
break
|
|
3680
|
+
# Also check in methods dict if available
|
|
3681
|
+
methods = getattr(target_class, 'methods', {})
|
|
3682
|
+
if ref_member in methods:
|
|
3683
|
+
method_node = methods[ref_member]
|
|
3684
|
+
self._call_method(instance, method_node, args, kwargs)
|
|
3685
|
+
|
|
3274
3686
|
def _call_constructor(self, instance: CSSLInstance, constr_node: ASTNode,
|
|
3275
3687
|
args: list, kwargs: dict, param_values: dict):
|
|
3276
3688
|
"""Call a constructor defined with 'constr' keyword.
|
|
3277
3689
|
|
|
3278
3690
|
Handles constructor extends/overwrites and sets up the instance scope.
|
|
3691
|
+
Supports append mode (++) for keeping parent code and adding new code.
|
|
3279
3692
|
"""
|
|
3280
3693
|
constr_info = constr_node.value
|
|
3281
3694
|
constr_params = constr_info.get('params', [])
|
|
3282
3695
|
extends_class = constr_info.get('extends_class')
|
|
3283
3696
|
extends_method = constr_info.get('extends_method')
|
|
3284
3697
|
|
|
3698
|
+
# Append mode: ++ operator for keeping parent code + adding new
|
|
3699
|
+
append_mode = constr_info.get('append_mode', False)
|
|
3700
|
+
append_ref_class = constr_info.get('append_ref_class') # &ClassName or &$instanceVar
|
|
3701
|
+
append_ref_member = constr_info.get('append_ref_member') # ::constructorName or ::methodName
|
|
3702
|
+
|
|
3285
3703
|
# Save previous instance context
|
|
3286
3704
|
prev_instance = self._current_instance
|
|
3287
3705
|
self._current_instance = instance
|
|
@@ -3313,6 +3731,13 @@ class CSSLRuntime:
|
|
|
3313
3731
|
self._call_constructor(instance, constr, args, kwargs, param_values)
|
|
3314
3732
|
break
|
|
3315
3733
|
|
|
3734
|
+
# Handle append mode (++) - execute referenced parent member first
|
|
3735
|
+
if append_mode and append_ref_class:
|
|
3736
|
+
self._execute_append_reference(
|
|
3737
|
+
instance, append_ref_class, append_ref_member,
|
|
3738
|
+
args, kwargs, param_values, is_constructor=True
|
|
3739
|
+
)
|
|
3740
|
+
|
|
3316
3741
|
# Execute constructor body
|
|
3317
3742
|
prev_scope = self.scope
|
|
3318
3743
|
self.scope = new_scope
|
|
@@ -3367,21 +3792,42 @@ class CSSLRuntime:
|
|
|
3367
3792
|
return lambda *args, **kwargs: python_method(*args, **kwargs)
|
|
3368
3793
|
return lambda *args, **kwargs: self._call_method(instance, method_node, list(args), kwargs)
|
|
3369
3794
|
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3795
|
+
# Build helpful error with available members
|
|
3796
|
+
class_name = instance._class.name
|
|
3797
|
+
available_members = list(instance._members.keys()) if hasattr(instance, '_members') else []
|
|
3798
|
+
available_methods = list(instance._methods.keys()) if hasattr(instance, '_methods') else []
|
|
3799
|
+
all_available = available_members + available_methods
|
|
3800
|
+
similar = _find_similar_names(member, all_available)
|
|
3801
|
+
|
|
3802
|
+
if similar:
|
|
3803
|
+
hint = f"Did you mean: {', '.join(similar)}?"
|
|
3804
|
+
elif all_available:
|
|
3805
|
+
hint = f"Available: {', '.join(all_available[:5])}"
|
|
3806
|
+
else:
|
|
3807
|
+
hint = f"Class '{class_name}' has no accessible members. Check class definition."
|
|
3808
|
+
|
|
3809
|
+
raise self._format_error(
|
|
3810
|
+
node.line if hasattr(node, 'line') else 0,
|
|
3811
|
+
f"'{class_name}' has no member or method '{member}'",
|
|
3812
|
+
hint
|
|
3373
3813
|
)
|
|
3374
3814
|
|
|
3375
3815
|
def _call_method(self, instance: CSSLInstance, method_node: ASTNode, args: list, kwargs: dict = None) -> Any:
|
|
3376
3816
|
"""Call a method on an instance with 'this' context.
|
|
3377
3817
|
|
|
3378
3818
|
Sets up the instance as the current 'this' context and executes the method.
|
|
3819
|
+
Supports append mode (++) for keeping parent code and adding new code.
|
|
3379
3820
|
"""
|
|
3380
3821
|
kwargs = kwargs or {}
|
|
3381
3822
|
func_info = method_node.value
|
|
3382
3823
|
params = func_info.get('params', [])
|
|
3383
3824
|
modifiers = func_info.get('modifiers', [])
|
|
3384
3825
|
|
|
3826
|
+
# Append mode: ++ operator for keeping parent code + adding new
|
|
3827
|
+
append_mode = func_info.get('append_mode', False)
|
|
3828
|
+
append_ref_class = func_info.get('append_ref_class') # &ClassName or &$instanceVar
|
|
3829
|
+
append_ref_member = func_info.get('append_ref_member') # ::methodName
|
|
3830
|
+
|
|
3385
3831
|
# Check for undefined modifier
|
|
3386
3832
|
is_undefined = 'undefined' in modifiers
|
|
3387
3833
|
|
|
@@ -3408,6 +3854,13 @@ class CSSLRuntime:
|
|
|
3408
3854
|
self._current_instance = instance
|
|
3409
3855
|
|
|
3410
3856
|
try:
|
|
3857
|
+
# Handle append mode (++) - execute referenced parent method first
|
|
3858
|
+
if append_mode and append_ref_class:
|
|
3859
|
+
self._execute_append_reference(
|
|
3860
|
+
instance, append_ref_class, append_ref_member,
|
|
3861
|
+
args, kwargs, {}, is_constructor=False
|
|
3862
|
+
)
|
|
3863
|
+
|
|
3411
3864
|
for child in method_node.children:
|
|
3412
3865
|
if not self._running:
|
|
3413
3866
|
break
|
|
@@ -3451,7 +3904,25 @@ class CSSLRuntime:
|
|
|
3451
3904
|
python_method = method_node[1]
|
|
3452
3905
|
return lambda *args, **kwargs: python_method(*args, **kwargs)
|
|
3453
3906
|
return lambda *args, **kwargs: self._call_method(obj, method_node, list(args), kwargs)
|
|
3454
|
-
|
|
3907
|
+
# Build helpful error with available members
|
|
3908
|
+
class_name = obj._class.name
|
|
3909
|
+
available_members = list(obj._members.keys()) if hasattr(obj, '_members') else []
|
|
3910
|
+
available_methods = list(obj._methods.keys()) if hasattr(obj, '_methods') else []
|
|
3911
|
+
all_available = available_members + available_methods
|
|
3912
|
+
similar = _find_similar_names(member, all_available)
|
|
3913
|
+
|
|
3914
|
+
if similar:
|
|
3915
|
+
hint = f"Did you mean: {', '.join(similar)}?"
|
|
3916
|
+
elif all_available:
|
|
3917
|
+
hint = f"Available: {', '.join(all_available[:5])}"
|
|
3918
|
+
else:
|
|
3919
|
+
hint = f"Class '{class_name}' has no accessible members."
|
|
3920
|
+
|
|
3921
|
+
raise self._format_error(
|
|
3922
|
+
node.line,
|
|
3923
|
+
f"'{class_name}' has no member or method '{member}'",
|
|
3924
|
+
hint
|
|
3925
|
+
)
|
|
3455
3926
|
|
|
3456
3927
|
# === STRING METHODS ===
|
|
3457
3928
|
if isinstance(obj, str):
|