IncludeCPP 3.8.9__py3-none-any.whl → 4.0.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/__init__.py +1 -1
- includecpp/cli/commands.py +7 -14
- includecpp/core/cssl/CSSL_DOCUMENTATION.md +869 -1872
- includecpp/core/cssl/CSSL_DOCUMENTATION_NEW.md +1348 -0
- includecpp/core/cssl/cssl_builtins.pyi +231 -0
- includecpp/core/cssl/cssl_parser.py +365 -88
- includecpp/core/cssl/cssl_runtime.py +564 -41
- includecpp/core/cssl_bridge.py +4 -4
- includecpp/core/cssl_bridge.pyi +586 -193
- {includecpp-3.8.9.dist-info → includecpp-4.0.2.dist-info}/METADATA +2 -1
- {includecpp-3.8.9.dist-info → includecpp-4.0.2.dist-info}/RECORD +15 -14
- {includecpp-3.8.9.dist-info → includecpp-4.0.2.dist-info}/WHEEL +0 -0
- {includecpp-3.8.9.dist-info → includecpp-4.0.2.dist-info}/entry_points.txt +0 -0
- {includecpp-3.8.9.dist-info → includecpp-4.0.2.dist-info}/licenses/LICENSE +0 -0
- {includecpp-3.8.9.dist-info → includecpp-4.0.2.dist-info}/top_level.txt +0 -0
|
@@ -68,11 +68,11 @@ class CSSLRuntimeError(Exception):
|
|
|
68
68
|
# Build detailed error message
|
|
69
69
|
error_parts = []
|
|
70
70
|
|
|
71
|
-
# Main error message
|
|
71
|
+
# Main error message (no "Error:" prefix - CLI handles that)
|
|
72
72
|
if line:
|
|
73
|
-
error_parts.append(f"
|
|
73
|
+
error_parts.append(f"Line {line}: {message}")
|
|
74
74
|
else:
|
|
75
|
-
error_parts.append(
|
|
75
|
+
error_parts.append(message)
|
|
76
76
|
|
|
77
77
|
# Add context if available
|
|
78
78
|
if context:
|
|
@@ -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
|
|
@@ -280,11 +363,11 @@ class CSSLRuntime:
|
|
|
280
363
|
"""Format a detailed error with source context"""
|
|
281
364
|
error_parts = []
|
|
282
365
|
|
|
283
|
-
# Main error header
|
|
366
|
+
# Main error header (no "Error:" prefix - CLI handles that)
|
|
284
367
|
if line and line > 0:
|
|
285
|
-
error_parts.append(f"
|
|
368
|
+
error_parts.append(f"Line {line} in {self._current_file}:")
|
|
286
369
|
else:
|
|
287
|
-
error_parts.append(f"
|
|
370
|
+
error_parts.append(f"In {self._current_file}:")
|
|
288
371
|
|
|
289
372
|
# Extract message without existing line info
|
|
290
373
|
clean_msg = message
|
|
@@ -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,24 +991,43 @@ 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')
|
|
900
1006
|
overwrites_is_python = func_info.get('overwrites_is_python', False)
|
|
901
1007
|
|
|
1008
|
+
# Get append/overwrite reference info (&Class::method syntax)
|
|
1009
|
+
append_mode = func_info.get('append_mode', False)
|
|
1010
|
+
append_ref_class = func_info.get('append_ref_class')
|
|
1011
|
+
append_ref_member = func_info.get('append_ref_member')
|
|
1012
|
+
|
|
902
1013
|
# Store function extends info for runtime use
|
|
903
1014
|
if extends_func:
|
|
904
1015
|
node.value['_extends_resolved'] = self._resolve_function_target(
|
|
905
1016
|
extends_func, extends_is_python
|
|
906
1017
|
)
|
|
907
1018
|
|
|
908
|
-
# Handle
|
|
1019
|
+
# Handle &Class::method syntax
|
|
1020
|
+
# Without ++ = full replacement
|
|
1021
|
+
# With ++ = append (run original first, then new code)
|
|
1022
|
+
if append_ref_class:
|
|
1023
|
+
if append_mode:
|
|
1024
|
+
# Append mode: wrap original to run original + new
|
|
1025
|
+
self._append_to_target(append_ref_class, append_ref_member, node)
|
|
1026
|
+
else:
|
|
1027
|
+
# Full replacement
|
|
1028
|
+
self._overwrite_target(append_ref_class, append_ref_member, node)
|
|
1029
|
+
|
|
1030
|
+
# Handle overwrites keyword - replace the target function
|
|
909
1031
|
if overwrites_func:
|
|
910
1032
|
target = self._resolve_function_target(overwrites_func, overwrites_is_python)
|
|
911
1033
|
if target is not None:
|
|
@@ -922,8 +1044,11 @@ class CSSLRuntime:
|
|
|
922
1044
|
self.scope.set(overwrites_func, node)
|
|
923
1045
|
self.global_scope.set(overwrites_func, node)
|
|
924
1046
|
|
|
925
|
-
# Register the function
|
|
1047
|
+
# Register the function (local by default, global if marked)
|
|
926
1048
|
self.scope.set(func_name, node)
|
|
1049
|
+
if is_global:
|
|
1050
|
+
self.global_scope.set(func_name, node)
|
|
1051
|
+
self._promoted_globals[func_name] = node
|
|
927
1052
|
return None
|
|
928
1053
|
|
|
929
1054
|
def _resolve_function_target(self, name: str, is_python: bool) -> Any:
|
|
@@ -945,6 +1070,142 @@ class CSSLRuntime:
|
|
|
945
1070
|
return self._call_function(func_node, list(args), kwargs)
|
|
946
1071
|
return wrapper
|
|
947
1072
|
|
|
1073
|
+
def _overwrite_target(self, ref_class: str, ref_member: str, replacement_node: ASTNode):
|
|
1074
|
+
"""Overwrite a class method or function with the replacement.
|
|
1075
|
+
|
|
1076
|
+
Handles:
|
|
1077
|
+
- &ClassName::method - Overwrite method in CSSL class
|
|
1078
|
+
- &$PyObject.method - Overwrite method in Python shared object
|
|
1079
|
+
- &functionName - Overwrite standalone function
|
|
1080
|
+
"""
|
|
1081
|
+
from ..cssl_bridge import _live_objects, SharedObjectProxy
|
|
1082
|
+
|
|
1083
|
+
# Handle Python shared objects
|
|
1084
|
+
if ref_class.startswith('$'):
|
|
1085
|
+
var_name = ref_class[1:]
|
|
1086
|
+
ref_obj = _live_objects.get(var_name)
|
|
1087
|
+
if ref_obj is None:
|
|
1088
|
+
ref_obj = self.scope.get(var_name) or self.global_scope.get(var_name)
|
|
1089
|
+
|
|
1090
|
+
if ref_obj is None:
|
|
1091
|
+
return
|
|
1092
|
+
|
|
1093
|
+
# Unwrap SharedObjectProxy
|
|
1094
|
+
if isinstance(ref_obj, SharedObjectProxy):
|
|
1095
|
+
ref_obj = ref_obj._obj
|
|
1096
|
+
|
|
1097
|
+
# Overwrite Python object method
|
|
1098
|
+
if ref_member and hasattr(ref_obj, ref_member):
|
|
1099
|
+
wrapper = self._create_python_wrapper(replacement_node)
|
|
1100
|
+
try:
|
|
1101
|
+
setattr(ref_obj, ref_member, wrapper)
|
|
1102
|
+
except (AttributeError, TypeError):
|
|
1103
|
+
pass # Can't overwrite (immutable or builtin)
|
|
1104
|
+
return
|
|
1105
|
+
|
|
1106
|
+
# Handle CSSL class method overwrite
|
|
1107
|
+
target_class = self.scope.get(ref_class) or self.global_scope.get(ref_class)
|
|
1108
|
+
if target_class is None:
|
|
1109
|
+
# Maybe it's a standalone function reference (no ::member)
|
|
1110
|
+
if ref_member is None:
|
|
1111
|
+
# &functionName - overwrite the function
|
|
1112
|
+
self.scope.set(ref_class, replacement_node)
|
|
1113
|
+
self.global_scope.set(ref_class, replacement_node)
|
|
1114
|
+
return
|
|
1115
|
+
|
|
1116
|
+
if isinstance(target_class, CSSLClass) and ref_member:
|
|
1117
|
+
# Overwrite method in the class
|
|
1118
|
+
if hasattr(target_class, 'methods') and isinstance(target_class.methods, dict):
|
|
1119
|
+
target_class.methods[ref_member] = replacement_node
|
|
1120
|
+
# Also check members list
|
|
1121
|
+
if hasattr(target_class, 'members'):
|
|
1122
|
+
for i, member in enumerate(target_class.members):
|
|
1123
|
+
if member.type in ('function', 'FUNCTION') and member.value.get('name') == ref_member:
|
|
1124
|
+
target_class.members[i] = replacement_node
|
|
1125
|
+
break
|
|
1126
|
+
|
|
1127
|
+
def _append_to_target(self, ref_class: str, ref_member: str, append_node: ASTNode):
|
|
1128
|
+
"""Append new code to an existing class method or function.
|
|
1129
|
+
|
|
1130
|
+
Creates a wrapper that runs original first, then the appended code.
|
|
1131
|
+
Handles:
|
|
1132
|
+
- &ClassName::method ++ - Append to method in CSSL class
|
|
1133
|
+
- &$PyObject.method ++ - Append to method in Python shared object
|
|
1134
|
+
- &functionName ++ - Append to standalone function
|
|
1135
|
+
"""
|
|
1136
|
+
from ..cssl_bridge import _live_objects, SharedObjectProxy
|
|
1137
|
+
|
|
1138
|
+
# Handle Python shared objects
|
|
1139
|
+
if ref_class.startswith('$'):
|
|
1140
|
+
var_name = ref_class[1:]
|
|
1141
|
+
ref_obj = _live_objects.get(var_name)
|
|
1142
|
+
if ref_obj is None:
|
|
1143
|
+
ref_obj = self.scope.get(var_name) or self.global_scope.get(var_name)
|
|
1144
|
+
|
|
1145
|
+
if ref_obj is None:
|
|
1146
|
+
return
|
|
1147
|
+
|
|
1148
|
+
if isinstance(ref_obj, SharedObjectProxy):
|
|
1149
|
+
ref_obj = ref_obj._obj
|
|
1150
|
+
|
|
1151
|
+
# Create wrapper that calls original + new
|
|
1152
|
+
if ref_member and hasattr(ref_obj, ref_member):
|
|
1153
|
+
original_method = getattr(ref_obj, ref_member)
|
|
1154
|
+
runtime = self
|
|
1155
|
+
def appended_wrapper(*args, **kwargs):
|
|
1156
|
+
# Run original first
|
|
1157
|
+
result = None
|
|
1158
|
+
if callable(original_method):
|
|
1159
|
+
try:
|
|
1160
|
+
result = original_method(*args, **kwargs)
|
|
1161
|
+
except:
|
|
1162
|
+
pass
|
|
1163
|
+
# Then run appended code
|
|
1164
|
+
return runtime._call_function(append_node, list(args), kwargs)
|
|
1165
|
+
try:
|
|
1166
|
+
setattr(ref_obj, ref_member, appended_wrapper)
|
|
1167
|
+
except (AttributeError, TypeError):
|
|
1168
|
+
pass
|
|
1169
|
+
return
|
|
1170
|
+
|
|
1171
|
+
# Handle CSSL class method append
|
|
1172
|
+
target_class = self.scope.get(ref_class) or self.global_scope.get(ref_class)
|
|
1173
|
+
if target_class is None:
|
|
1174
|
+
# Standalone function append
|
|
1175
|
+
if ref_member is None:
|
|
1176
|
+
original_func = self.scope.get(ref_class) or self.global_scope.get(ref_class)
|
|
1177
|
+
if original_func:
|
|
1178
|
+
# Store original in append_node so it can run first
|
|
1179
|
+
append_node.value['_original_func'] = original_func
|
|
1180
|
+
self.scope.set(ref_class, append_node)
|
|
1181
|
+
self.global_scope.set(ref_class, append_node)
|
|
1182
|
+
return
|
|
1183
|
+
|
|
1184
|
+
if isinstance(target_class, CSSLClass) and ref_member:
|
|
1185
|
+
# Find original method
|
|
1186
|
+
original_method = None
|
|
1187
|
+
if hasattr(target_class, 'methods') and isinstance(target_class.methods, dict):
|
|
1188
|
+
original_method = target_class.methods.get(ref_member)
|
|
1189
|
+
|
|
1190
|
+
if original_method is None and hasattr(target_class, 'members'):
|
|
1191
|
+
for member in target_class.members:
|
|
1192
|
+
if member.type in ('function', 'FUNCTION') and member.value.get('name') == ref_member:
|
|
1193
|
+
original_method = member
|
|
1194
|
+
break
|
|
1195
|
+
|
|
1196
|
+
# Store original in append_node for runtime execution
|
|
1197
|
+
if original_method:
|
|
1198
|
+
append_node.value['_original_method'] = original_method
|
|
1199
|
+
|
|
1200
|
+
# Replace with append_node (which will call original first via _call_function)
|
|
1201
|
+
if hasattr(target_class, 'methods') and isinstance(target_class.methods, dict):
|
|
1202
|
+
target_class.methods[ref_member] = append_node
|
|
1203
|
+
if hasattr(target_class, 'members'):
|
|
1204
|
+
for i, member in enumerate(target_class.members):
|
|
1205
|
+
if member.type in ('function', 'FUNCTION') and member.value.get('name') == ref_member:
|
|
1206
|
+
target_class.members[i] = append_node
|
|
1207
|
+
break
|
|
1208
|
+
|
|
948
1209
|
def _exec_typed_declaration(self, node: ASTNode) -> Any:
|
|
949
1210
|
"""Execute typed variable declaration: type<T> varName = value;
|
|
950
1211
|
|
|
@@ -1243,12 +1504,15 @@ class CSSLRuntime:
|
|
|
1243
1504
|
if not self._running:
|
|
1244
1505
|
break
|
|
1245
1506
|
self._execute_node(child)
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1507
|
+
except CSSLReturn:
|
|
1508
|
+
# Parent returned - that's fine, we just want the local vars
|
|
1509
|
+
pass
|
|
1249
1510
|
except:
|
|
1250
1511
|
pass
|
|
1251
1512
|
finally:
|
|
1513
|
+
# ALWAYS copy local vars (even if parent returned)
|
|
1514
|
+
for name, value in temp_scope.variables.items():
|
|
1515
|
+
new_scope.set(name, value)
|
|
1252
1516
|
self.scope = old_scope
|
|
1253
1517
|
|
|
1254
1518
|
# Bind parameters - handle both positional and named arguments
|
|
@@ -1307,7 +1571,80 @@ class CSSLRuntime:
|
|
|
1307
1571
|
break
|
|
1308
1572
|
self._execute_node(child)
|
|
1309
1573
|
except CSSLReturn as ret:
|
|
1310
|
-
|
|
1574
|
+
return_value = ret.value
|
|
1575
|
+
|
|
1576
|
+
# Check exclude_type: *[type] - must NOT return excluded type
|
|
1577
|
+
exclude_type = func_info.get('exclude_type')
|
|
1578
|
+
if exclude_type and isinstance(exclude_type, str):
|
|
1579
|
+
type_map = {
|
|
1580
|
+
'string': str, 'int': int, 'float': float, 'bool': bool,
|
|
1581
|
+
'null': type(None), 'none': type(None),
|
|
1582
|
+
'list': list, 'array': list, 'dict': dict, 'json': dict,
|
|
1583
|
+
}
|
|
1584
|
+
excluded_py_type = type_map.get(exclude_type.lower())
|
|
1585
|
+
# For shuffled returns (tuples), check each element
|
|
1586
|
+
if isinstance(return_value, tuple):
|
|
1587
|
+
for val in return_value:
|
|
1588
|
+
if excluded_py_type and isinstance(val, excluded_py_type):
|
|
1589
|
+
raise CSSLRuntimeError(f"Type exclusion: function must NOT return '{exclude_type}' values")
|
|
1590
|
+
elif excluded_py_type and isinstance(return_value, excluded_py_type):
|
|
1591
|
+
raise CSSLRuntimeError(f"Type exclusion: function must NOT return '{exclude_type}'")
|
|
1592
|
+
|
|
1593
|
+
# Enforce return type for typed functions (like C++)
|
|
1594
|
+
# Typed functions MUST return the declared type
|
|
1595
|
+
# Exception: 'meta' modifier allows any return type
|
|
1596
|
+
enforce_return_type = func_info.get('enforce_return_type', False)
|
|
1597
|
+
return_type = func_info.get('return_type')
|
|
1598
|
+
|
|
1599
|
+
if enforce_return_type and return_type and return_type != 'void':
|
|
1600
|
+
# Type mapping from CSSL types to Python types
|
|
1601
|
+
type_validate_map = {
|
|
1602
|
+
'string': str, 'int': int, 'float': (int, float), 'bool': bool,
|
|
1603
|
+
'list': list, 'array': list, 'dict': dict, 'json': dict,
|
|
1604
|
+
'dynamic': object, # Any type
|
|
1605
|
+
'void': type(None),
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
# Generic container types - accept lists/tuples
|
|
1609
|
+
container_types = {
|
|
1610
|
+
'vector', 'stack', 'datastruct', 'dataspace',
|
|
1611
|
+
'shuffled', 'iterator', 'combo', 'openquote', 'map'
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
if return_type in container_types:
|
|
1615
|
+
# Container types accept list, tuple, dict depending on type
|
|
1616
|
+
if return_type == 'map':
|
|
1617
|
+
expected = dict
|
|
1618
|
+
elif return_type == 'shuffled':
|
|
1619
|
+
expected = (list, tuple)
|
|
1620
|
+
else:
|
|
1621
|
+
expected = (list, tuple, object)
|
|
1622
|
+
|
|
1623
|
+
if not isinstance(return_value, expected):
|
|
1624
|
+
func_name = func_info.get('name', 'unknown')
|
|
1625
|
+
actual_type = type(return_value).__name__
|
|
1626
|
+
raise CSSLRuntimeError(
|
|
1627
|
+
f"Type error in '{func_name}': declared return type '{return_type}' "
|
|
1628
|
+
f"but returned '{actual_type}'. Typed functions must return declared type."
|
|
1629
|
+
)
|
|
1630
|
+
elif return_type in type_validate_map:
|
|
1631
|
+
expected = type_validate_map[return_type]
|
|
1632
|
+
if expected != object and return_value is not None:
|
|
1633
|
+
if not isinstance(return_value, expected):
|
|
1634
|
+
func_name = func_info.get('name', 'unknown')
|
|
1635
|
+
actual_type = type(return_value).__name__
|
|
1636
|
+
raise CSSLRuntimeError(
|
|
1637
|
+
f"Type error in '{func_name}': declared return type '{return_type}' "
|
|
1638
|
+
f"but returned '{actual_type}'. Typed functions must return declared type."
|
|
1639
|
+
)
|
|
1640
|
+
|
|
1641
|
+
# Check non_null: function must return a value (not None)
|
|
1642
|
+
non_null = func_info.get('non_null', False)
|
|
1643
|
+
if non_null and return_value is None:
|
|
1644
|
+
func_name = func_info.get('name', 'unknown')
|
|
1645
|
+
raise CSSLRuntimeError(f"Non-null function '{func_name}' returned null/None")
|
|
1646
|
+
|
|
1647
|
+
return return_value
|
|
1311
1648
|
except Exception as e:
|
|
1312
1649
|
# If undefined modifier, suppress all errors
|
|
1313
1650
|
if is_undefined:
|
|
@@ -2715,7 +3052,21 @@ class CSSLRuntime:
|
|
|
2715
3052
|
scoped_val = self.global_scope.get(f'${name}')
|
|
2716
3053
|
if scoped_val is not None:
|
|
2717
3054
|
return scoped_val
|
|
2718
|
-
|
|
3055
|
+
# List available shared objects for helpful error
|
|
3056
|
+
available_shared = list(_live_objects.keys())
|
|
3057
|
+
similar = _find_similar_names(name, available_shared)
|
|
3058
|
+
if similar:
|
|
3059
|
+
hint = f"Did you mean: ${', $'.join(similar)}?"
|
|
3060
|
+
elif available_shared:
|
|
3061
|
+
hint = f"Available shared objects: ${', $'.join(available_shared[:5])}"
|
|
3062
|
+
else:
|
|
3063
|
+
hint = "Use share(name, object) from Python to share objects first."
|
|
3064
|
+
|
|
3065
|
+
raise self._format_error(
|
|
3066
|
+
node.line if hasattr(node, 'line') else 0,
|
|
3067
|
+
f"Shared object '${name}' not found",
|
|
3068
|
+
hint
|
|
3069
|
+
)
|
|
2719
3070
|
|
|
2720
3071
|
if node.type == 'captured_ref':
|
|
2721
3072
|
# %<name> captured reference - use value captured at infusion registration time
|
|
@@ -2742,7 +3093,13 @@ class CSSLRuntime:
|
|
|
2742
3093
|
value = self._original_functions.get(name)
|
|
2743
3094
|
if value is not None:
|
|
2744
3095
|
return value
|
|
2745
|
-
|
|
3096
|
+
# Build helpful error for captured reference
|
|
3097
|
+
hint = f"Variable '{name}' must exist when the infusion is registered. Check that '%{name}' is defined before the <<== operator."
|
|
3098
|
+
raise self._format_error(
|
|
3099
|
+
node.line if hasattr(node, 'line') else 0,
|
|
3100
|
+
f"Captured reference '%{name}' not found",
|
|
3101
|
+
hint
|
|
3102
|
+
)
|
|
2746
3103
|
|
|
2747
3104
|
if node.type == 'instance_ref':
|
|
2748
3105
|
# instance<"name"> - get shared instance by name
|
|
@@ -2831,6 +3188,58 @@ class CSSLRuntime:
|
|
|
2831
3188
|
if node.type == 'unary':
|
|
2832
3189
|
return self._eval_unary(node)
|
|
2833
3190
|
|
|
3191
|
+
if node.type == 'non_null_assert':
|
|
3192
|
+
# *$var, *@module, *identifier - assert value is not null/None
|
|
3193
|
+
operand = node.value.get('operand')
|
|
3194
|
+
value = self._evaluate(operand)
|
|
3195
|
+
if value is None:
|
|
3196
|
+
# Get name of the operand for better error message
|
|
3197
|
+
operand_name = "unknown"
|
|
3198
|
+
if isinstance(operand, ASTNode):
|
|
3199
|
+
if operand.type == 'identifier':
|
|
3200
|
+
operand_name = operand.value
|
|
3201
|
+
elif operand.type == 'shared_ref':
|
|
3202
|
+
operand_name = f"${operand.value}"
|
|
3203
|
+
elif operand.type == 'module_ref':
|
|
3204
|
+
operand_name = f"@{operand.value}"
|
|
3205
|
+
elif operand.type == 'global_ref':
|
|
3206
|
+
operand_name = f"r@{operand.value}"
|
|
3207
|
+
raise self._format_error(
|
|
3208
|
+
node.line if hasattr(node, 'line') else 0,
|
|
3209
|
+
f"Non-null assertion failed: '{operand_name}' is null/None",
|
|
3210
|
+
f"The value accessed via '*{operand_name}' must not be null. Check that it is defined and initialized."
|
|
3211
|
+
)
|
|
3212
|
+
return value
|
|
3213
|
+
|
|
3214
|
+
if node.type == 'type_exclude_assert':
|
|
3215
|
+
# *[type]expr - assert value is NOT of excluded type
|
|
3216
|
+
exclude_type = node.value.get('exclude_type')
|
|
3217
|
+
operand = node.value.get('operand')
|
|
3218
|
+
value = self._evaluate(operand)
|
|
3219
|
+
|
|
3220
|
+
# Map CSSL types to Python types
|
|
3221
|
+
type_map = {
|
|
3222
|
+
'string': str,
|
|
3223
|
+
'int': int,
|
|
3224
|
+
'float': float,
|
|
3225
|
+
'bool': bool,
|
|
3226
|
+
'null': type(None),
|
|
3227
|
+
'none': type(None),
|
|
3228
|
+
'list': list,
|
|
3229
|
+
'array': list,
|
|
3230
|
+
'dict': dict,
|
|
3231
|
+
'json': dict,
|
|
3232
|
+
}
|
|
3233
|
+
|
|
3234
|
+
excluded_py_type = type_map.get(exclude_type.lower() if isinstance(exclude_type, str) else exclude_type)
|
|
3235
|
+
if excluded_py_type and isinstance(value, excluded_py_type):
|
|
3236
|
+
raise self._format_error(
|
|
3237
|
+
node.line if hasattr(node, 'line') else 0,
|
|
3238
|
+
f"Type exclusion assertion failed: value is of excluded type '{exclude_type}'",
|
|
3239
|
+
f"The expression was marked *[{exclude_type}] meaning it must NOT return {exclude_type}, but it did."
|
|
3240
|
+
)
|
|
3241
|
+
return value
|
|
3242
|
+
|
|
2834
3243
|
if node.type == 'call':
|
|
2835
3244
|
return self._eval_call(node)
|
|
2836
3245
|
|
|
@@ -3108,12 +3517,33 @@ class CSSLRuntime:
|
|
|
3108
3517
|
return self._call_function(callee, args, kwargs)
|
|
3109
3518
|
|
|
3110
3519
|
callee_name = callee_node.value if isinstance(callee_node, ASTNode) and hasattr(callee_node, 'value') else str(callee_node)
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3520
|
+
|
|
3521
|
+
# Build detailed error with suggestions
|
|
3522
|
+
available_funcs = _get_available_functions(self.scope, self.global_scope, self.builtins)
|
|
3523
|
+
similar = _find_similar_names(callee_name, available_funcs)
|
|
3524
|
+
|
|
3525
|
+
if callee is None:
|
|
3526
|
+
# Function not found at all
|
|
3527
|
+
if similar:
|
|
3528
|
+
hint = f"Did you mean: {', '.join(similar)}?"
|
|
3529
|
+
else:
|
|
3530
|
+
hint = f"Function '{callee_name}' is not defined. Define it with: define {callee_name}() {{ }}"
|
|
3531
|
+
raise self._format_error(
|
|
3532
|
+
node.line,
|
|
3533
|
+
f"Function '{callee_name}' not found",
|
|
3534
|
+
hint
|
|
3535
|
+
)
|
|
3536
|
+
else:
|
|
3537
|
+
# Found something but it's not callable
|
|
3538
|
+
if similar:
|
|
3539
|
+
hint = f"'{callee_name}' is a {type(callee).__name__}, not a function. Did you mean: {', '.join(similar)}?"
|
|
3540
|
+
else:
|
|
3541
|
+
hint = f"'{callee_name}' is a {type(callee).__name__}. Functions must be defined with 'define' keyword."
|
|
3542
|
+
raise self._format_error(
|
|
3543
|
+
node.line,
|
|
3544
|
+
f"Cannot call '{callee_name}' - it is not a function",
|
|
3545
|
+
hint
|
|
3546
|
+
)
|
|
3117
3547
|
|
|
3118
3548
|
def _eval_typed_call(self, node: ASTNode) -> Any:
|
|
3119
3549
|
"""Evaluate typed function call like OpenFind<string>(0) or OpenFind<dynamic, "name">"""
|
|
@@ -3179,26 +3609,53 @@ class CSSLRuntime:
|
|
|
3179
3609
|
)
|
|
3180
3610
|
|
|
3181
3611
|
def _eval_new(self, node: ASTNode) -> CSSLInstance:
|
|
3182
|
-
"""Evaluate 'new ClassName(args)' expression.
|
|
3612
|
+
"""Evaluate 'new ClassName(args)' or 'new @ClassName(args)' expression.
|
|
3183
3613
|
|
|
3184
3614
|
Creates a new instance of a CSSL class and calls its constructor.
|
|
3185
3615
|
Supports multiple constructors (constr keyword), class parameters,
|
|
3186
3616
|
and automatic parent constructor calling.
|
|
3617
|
+
|
|
3618
|
+
With '@' prefix (new @ClassName), looks only in global scope.
|
|
3187
3619
|
"""
|
|
3188
3620
|
class_name = node.value.get('class')
|
|
3621
|
+
is_global_ref = node.value.get('is_global_ref', False)
|
|
3189
3622
|
args = [self._evaluate(arg) for arg in node.value.get('args', [])]
|
|
3190
3623
|
kwargs = {k: self._evaluate(v) for k, v in node.value.get('kwargs', {}).items()}
|
|
3191
3624
|
|
|
3192
3625
|
# Get class definition from scope
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
class_def = self.
|
|
3626
|
+
if is_global_ref:
|
|
3627
|
+
# With @ prefix, only look in global scope
|
|
3628
|
+
class_def = self._promoted_globals.get(class_name)
|
|
3629
|
+
if class_def is None:
|
|
3630
|
+
class_def = self.global_scope.get(class_name)
|
|
3631
|
+
else:
|
|
3632
|
+
# Normal lookup: local scope first, then global
|
|
3633
|
+
class_def = self.scope.get(class_name)
|
|
3634
|
+
if class_def is None:
|
|
3635
|
+
class_def = self.global_scope.get(class_name)
|
|
3196
3636
|
|
|
3197
3637
|
if class_def is None:
|
|
3198
|
-
|
|
3199
|
-
|
|
3638
|
+
# Build detailed error with suggestions
|
|
3639
|
+
source_line = self._get_source_line(node.line)
|
|
3640
|
+
available_classes = _get_available_classes(self.scope, self.global_scope, self._promoted_globals)
|
|
3641
|
+
similar = _find_similar_names(class_name, available_classes)
|
|
3642
|
+
|
|
3643
|
+
# Check if class exists in global scope (user forgot @)
|
|
3644
|
+
global_class = self._promoted_globals.get(class_name) or self.global_scope.get(class_name)
|
|
3645
|
+
if global_class and isinstance(global_class, CSSLClass) and not is_global_ref:
|
|
3646
|
+
hint = f"Class '{class_name}' exists in global scope. Use: new @{class_name}()"
|
|
3647
|
+
elif similar:
|
|
3648
|
+
hint = f"Did you mean: {', '.join(similar)}?"
|
|
3649
|
+
elif available_classes:
|
|
3650
|
+
hint = f"Available classes: {', '.join(available_classes[:5])}"
|
|
3651
|
+
else:
|
|
3652
|
+
hint = "Define the class before instantiation, or use 'global class' / 'class @Name' for global classes"
|
|
3653
|
+
|
|
3654
|
+
context = f"in expression: {source_line.strip()}" if source_line else None
|
|
3655
|
+
raise self._format_error(
|
|
3200
3656
|
node.line,
|
|
3201
|
-
|
|
3657
|
+
f"Class '{class_name}' not found",
|
|
3658
|
+
hint
|
|
3202
3659
|
)
|
|
3203
3660
|
|
|
3204
3661
|
if not isinstance(class_def, CSSLClass):
|
|
@@ -3326,12 +3783,34 @@ class CSSLRuntime:
|
|
|
3326
3783
|
|
|
3327
3784
|
# Resolve the class/instance reference
|
|
3328
3785
|
if ref_class.startswith('$'):
|
|
3329
|
-
# Dynamic instance reference: &$instanceVar::member
|
|
3786
|
+
# Dynamic instance reference: &$instanceVar::member or &$PyObject.method
|
|
3330
3787
|
var_name = ref_class[1:]
|
|
3331
|
-
|
|
3788
|
+
|
|
3789
|
+
# First check in _live_objects for Python shared objects
|
|
3790
|
+
from ..cssl_bridge import _live_objects, SharedObjectProxy
|
|
3791
|
+
ref_obj = _live_objects.get(var_name)
|
|
3792
|
+
if ref_obj is None:
|
|
3793
|
+
ref_obj = self.scope.get(var_name) or self.global_scope.get(var_name)
|
|
3794
|
+
|
|
3332
3795
|
if ref_obj is None:
|
|
3333
3796
|
return # Instance not found, skip silently
|
|
3334
3797
|
|
|
3798
|
+
# Handle Python shared objects
|
|
3799
|
+
if isinstance(ref_obj, SharedObjectProxy):
|
|
3800
|
+
ref_obj = ref_obj._obj
|
|
3801
|
+
|
|
3802
|
+
# If it's a Python object (not CSSL), call the method directly
|
|
3803
|
+
if not isinstance(ref_obj, (CSSLInstance, CSSLClass)):
|
|
3804
|
+
if ref_member and hasattr(ref_obj, ref_member):
|
|
3805
|
+
method = getattr(ref_obj, ref_member)
|
|
3806
|
+
if callable(method):
|
|
3807
|
+
try:
|
|
3808
|
+
method(*args, **kwargs)
|
|
3809
|
+
except TypeError:
|
|
3810
|
+
# Try without args
|
|
3811
|
+
method()
|
|
3812
|
+
return
|
|
3813
|
+
|
|
3335
3814
|
if isinstance(ref_obj, CSSLInstance):
|
|
3336
3815
|
# Get the class definition from the instance
|
|
3337
3816
|
target_class = ref_obj.class_def
|
|
@@ -3490,9 +3969,24 @@ class CSSLRuntime:
|
|
|
3490
3969
|
return lambda *args, **kwargs: python_method(*args, **kwargs)
|
|
3491
3970
|
return lambda *args, **kwargs: self._call_method(instance, method_node, list(args), kwargs)
|
|
3492
3971
|
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3972
|
+
# Build helpful error with available members
|
|
3973
|
+
class_name = instance._class.name
|
|
3974
|
+
available_members = list(instance._members.keys()) if hasattr(instance, '_members') else []
|
|
3975
|
+
available_methods = list(instance._methods.keys()) if hasattr(instance, '_methods') else []
|
|
3976
|
+
all_available = available_members + available_methods
|
|
3977
|
+
similar = _find_similar_names(member, all_available)
|
|
3978
|
+
|
|
3979
|
+
if similar:
|
|
3980
|
+
hint = f"Did you mean: {', '.join(similar)}?"
|
|
3981
|
+
elif all_available:
|
|
3982
|
+
hint = f"Available: {', '.join(all_available[:5])}"
|
|
3983
|
+
else:
|
|
3984
|
+
hint = f"Class '{class_name}' has no accessible members. Check class definition."
|
|
3985
|
+
|
|
3986
|
+
raise self._format_error(
|
|
3987
|
+
node.line if hasattr(node, 'line') else 0,
|
|
3988
|
+
f"'{class_name}' has no member or method '{member}'",
|
|
3989
|
+
hint
|
|
3496
3990
|
)
|
|
3497
3991
|
|
|
3498
3992
|
def _call_method(self, instance: CSSLInstance, method_node: ASTNode, args: list, kwargs: dict = None) -> Any:
|
|
@@ -3536,9 +4030,19 @@ class CSSLRuntime:
|
|
|
3536
4030
|
self.scope = new_scope
|
|
3537
4031
|
self._current_instance = instance
|
|
3538
4032
|
|
|
4033
|
+
original_return = None
|
|
3539
4034
|
try:
|
|
4035
|
+
# Handle append mode via _append_to_target (stored original)
|
|
4036
|
+
original_method = func_info.get('_original_method')
|
|
4037
|
+
if original_method:
|
|
4038
|
+
# Execute original method first - capture return for fallback
|
|
4039
|
+
try:
|
|
4040
|
+
original_return = self._call_method(instance, original_method, args, kwargs)
|
|
4041
|
+
except CSSLReturn as ret:
|
|
4042
|
+
original_return = ret.value
|
|
4043
|
+
|
|
3540
4044
|
# Handle append mode (++) - execute referenced parent method first
|
|
3541
|
-
|
|
4045
|
+
elif append_mode and append_ref_class:
|
|
3542
4046
|
self._execute_append_reference(
|
|
3543
4047
|
instance, append_ref_class, append_ref_member,
|
|
3544
4048
|
args, kwargs, {}, is_constructor=False
|
|
@@ -3559,7 +4063,8 @@ class CSSLRuntime:
|
|
|
3559
4063
|
self.scope = old_scope
|
|
3560
4064
|
self._current_instance = old_instance
|
|
3561
4065
|
|
|
3562
|
-
return
|
|
4066
|
+
# If no return in appended code, use original's return
|
|
4067
|
+
return original_return
|
|
3563
4068
|
|
|
3564
4069
|
def _eval_member_access(self, node: ASTNode) -> Any:
|
|
3565
4070
|
"""Evaluate member access"""
|
|
@@ -3587,7 +4092,25 @@ class CSSLRuntime:
|
|
|
3587
4092
|
python_method = method_node[1]
|
|
3588
4093
|
return lambda *args, **kwargs: python_method(*args, **kwargs)
|
|
3589
4094
|
return lambda *args, **kwargs: self._call_method(obj, method_node, list(args), kwargs)
|
|
3590
|
-
|
|
4095
|
+
# Build helpful error with available members
|
|
4096
|
+
class_name = obj._class.name
|
|
4097
|
+
available_members = list(obj._members.keys()) if hasattr(obj, '_members') else []
|
|
4098
|
+
available_methods = list(obj._methods.keys()) if hasattr(obj, '_methods') else []
|
|
4099
|
+
all_available = available_members + available_methods
|
|
4100
|
+
similar = _find_similar_names(member, all_available)
|
|
4101
|
+
|
|
4102
|
+
if similar:
|
|
4103
|
+
hint = f"Did you mean: {', '.join(similar)}?"
|
|
4104
|
+
elif all_available:
|
|
4105
|
+
hint = f"Available: {', '.join(all_available[:5])}"
|
|
4106
|
+
else:
|
|
4107
|
+
hint = f"Class '{class_name}' has no accessible members."
|
|
4108
|
+
|
|
4109
|
+
raise self._format_error(
|
|
4110
|
+
node.line,
|
|
4111
|
+
f"'{class_name}' has no member or method '{member}'",
|
|
4112
|
+
hint
|
|
4113
|
+
)
|
|
3591
4114
|
|
|
3592
4115
|
# === STRING METHODS ===
|
|
3593
4116
|
if isinstance(obj, str):
|