IncludeCPP 3.7.3__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.
Potentially problematic release.
This version of IncludeCPP might be problematic. Click here for more details.
- includecpp/__init__.py +59 -0
- includecpp/__init__.pyi +255 -0
- includecpp/__main__.py +4 -0
- includecpp/cli/__init__.py +4 -0
- includecpp/cli/commands.py +8270 -0
- includecpp/cli/config_parser.py +127 -0
- includecpp/core/__init__.py +19 -0
- includecpp/core/ai_integration.py +2132 -0
- includecpp/core/build_manager.py +2416 -0
- includecpp/core/cpp_api.py +376 -0
- includecpp/core/cpp_api.pyi +95 -0
- includecpp/core/cppy_converter.py +3448 -0
- includecpp/core/cssl/CSSL_DOCUMENTATION.md +2075 -0
- includecpp/core/cssl/__init__.py +42 -0
- includecpp/core/cssl/cssl_builtins.py +2271 -0
- includecpp/core/cssl/cssl_builtins.pyi +1393 -0
- includecpp/core/cssl/cssl_events.py +621 -0
- includecpp/core/cssl/cssl_modules.py +2803 -0
- includecpp/core/cssl/cssl_parser.py +2575 -0
- includecpp/core/cssl/cssl_runtime.py +3051 -0
- includecpp/core/cssl/cssl_syntax.py +488 -0
- includecpp/core/cssl/cssl_types.py +1512 -0
- includecpp/core/cssl_bridge.py +882 -0
- includecpp/core/cssl_bridge.pyi +488 -0
- includecpp/core/error_catalog.py +802 -0
- includecpp/core/error_formatter.py +1016 -0
- includecpp/core/exceptions.py +97 -0
- includecpp/core/path_discovery.py +77 -0
- includecpp/core/project_ui.py +3370 -0
- includecpp/core/settings_ui.py +326 -0
- includecpp/generator/__init__.py +1 -0
- includecpp/generator/parser.cpp +1903 -0
- includecpp/generator/parser.h +281 -0
- includecpp/generator/type_resolver.cpp +363 -0
- includecpp/generator/type_resolver.h +68 -0
- includecpp/py.typed +0 -0
- includecpp/templates/cpp.proj.template +18 -0
- includecpp/vscode/__init__.py +1 -0
- includecpp/vscode/cssl/__init__.py +1 -0
- includecpp/vscode/cssl/language-configuration.json +38 -0
- includecpp/vscode/cssl/package.json +50 -0
- includecpp/vscode/cssl/snippets/cssl.snippets.json +1080 -0
- includecpp/vscode/cssl/syntaxes/cssl.tmLanguage.json +341 -0
- includecpp-3.7.3.dist-info/METADATA +1076 -0
- includecpp-3.7.3.dist-info/RECORD +49 -0
- includecpp-3.7.3.dist-info/WHEEL +5 -0
- includecpp-3.7.3.dist-info/entry_points.txt +2 -0
- includecpp-3.7.3.dist-info/licenses/LICENSE +21 -0
- includecpp-3.7.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,3051 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CSSL Runtime Environment
|
|
3
|
+
Executes CSSL scripts by interpreting the AST
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import time
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from typing import Any, Dict, List, Optional, Callable, Union
|
|
10
|
+
|
|
11
|
+
from .cssl_parser import ASTNode, parse_cssl, parse_cssl_program, CSSLSyntaxError
|
|
12
|
+
from .cssl_events import CSSLEventManager, EventType, EventData, get_event_manager
|
|
13
|
+
from .cssl_builtins import CSSLBuiltins
|
|
14
|
+
from .cssl_modules import get_module_registry, get_standard_module
|
|
15
|
+
from .cssl_types import (
|
|
16
|
+
Parameter, DataStruct, Shuffled, Iterator, Combo,
|
|
17
|
+
Stack, Vector, Array, DataSpace, OpenQuote, List, Dictionary, Map,
|
|
18
|
+
CSSLClass, CSSLInstance
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class CSSLRuntimeError(Exception):
|
|
23
|
+
"""Runtime error during CSSL execution with detailed context"""
|
|
24
|
+
def __init__(self, message: str, line: int = 0, context: str = None, hint: str = None):
|
|
25
|
+
self.line = line
|
|
26
|
+
self.context = context
|
|
27
|
+
self.hint = hint
|
|
28
|
+
|
|
29
|
+
# Build detailed error message
|
|
30
|
+
error_parts = []
|
|
31
|
+
|
|
32
|
+
# Main error message
|
|
33
|
+
if line:
|
|
34
|
+
error_parts.append(f"Error at line {line}: {message}")
|
|
35
|
+
else:
|
|
36
|
+
error_parts.append(f"Error: {message}")
|
|
37
|
+
|
|
38
|
+
# Add context if available
|
|
39
|
+
if context:
|
|
40
|
+
error_parts.append(f" Context: {context}")
|
|
41
|
+
|
|
42
|
+
# Add hint if available
|
|
43
|
+
if hint:
|
|
44
|
+
error_parts.append(f" Hint: {hint}")
|
|
45
|
+
|
|
46
|
+
super().__init__("\n".join(error_parts))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# Common error hints for better user experience
|
|
50
|
+
ERROR_HINTS = {
|
|
51
|
+
'undefined_variable': "Did you forget to declare the variable? Use 'string x = ...' or 'int x = ...'",
|
|
52
|
+
'undefined_function': "Check function name spelling. Functions are case-sensitive.",
|
|
53
|
+
'type_mismatch': "Try using explicit type conversion: toInt(), toFloat(), toString()",
|
|
54
|
+
'null_reference': "Variable is null. Check if it was properly initialized.",
|
|
55
|
+
'index_out_of_bounds': "Array index must be >= 0 and < array.length()",
|
|
56
|
+
'division_by_zero': "Cannot divide by zero. Add a check: if (divisor != 0) { ... }",
|
|
57
|
+
'invalid_operation': "This operation is not supported for this type.",
|
|
58
|
+
'missing_semicolon': "Statement might be missing a semicolon (;)",
|
|
59
|
+
'missing_brace': "Check for matching opening and closing braces { }",
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class CSSLBreak(Exception):
|
|
64
|
+
"""Break statement"""
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class CSSLContinue(Exception):
|
|
69
|
+
"""Continue statement"""
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class CSSLReturn(Exception):
|
|
74
|
+
"""Return statement"""
|
|
75
|
+
def __init__(self, value: Any = None):
|
|
76
|
+
self.value = value
|
|
77
|
+
super().__init__()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@dataclass
|
|
81
|
+
class Scope:
|
|
82
|
+
"""Variable scope"""
|
|
83
|
+
variables: Dict[str, Any] = field(default_factory=dict)
|
|
84
|
+
parent: Optional['Scope'] = None
|
|
85
|
+
|
|
86
|
+
def get(self, name: str) -> Any:
|
|
87
|
+
if name in self.variables:
|
|
88
|
+
return self.variables[name]
|
|
89
|
+
if self.parent:
|
|
90
|
+
return self.parent.get(name)
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
def set(self, name: str, value: Any):
|
|
94
|
+
self.variables[name] = value
|
|
95
|
+
|
|
96
|
+
def update(self, name: str, value: Any) -> bool:
|
|
97
|
+
if name in self.variables:
|
|
98
|
+
self.variables[name] = value
|
|
99
|
+
return True
|
|
100
|
+
if self.parent:
|
|
101
|
+
return self.parent.update(name, value)
|
|
102
|
+
return False
|
|
103
|
+
|
|
104
|
+
def has(self, name: str) -> bool:
|
|
105
|
+
if name in self.variables:
|
|
106
|
+
return True
|
|
107
|
+
if self.parent:
|
|
108
|
+
return self.parent.has(name)
|
|
109
|
+
return False
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@dataclass
|
|
113
|
+
class ServiceDefinition:
|
|
114
|
+
"""Parsed service definition"""
|
|
115
|
+
name: str = ""
|
|
116
|
+
version: str = "1.0"
|
|
117
|
+
author: str = ""
|
|
118
|
+
description: str = ""
|
|
119
|
+
dependencies: List[str] = field(default_factory=list)
|
|
120
|
+
autostart: bool = False
|
|
121
|
+
priority: int = 0
|
|
122
|
+
structs: Dict[str, ASTNode] = field(default_factory=dict)
|
|
123
|
+
functions: Dict[str, ASTNode] = field(default_factory=dict)
|
|
124
|
+
event_handlers: Dict[str, List[ASTNode]] = field(default_factory=dict)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class CSSLRuntime:
|
|
128
|
+
"""
|
|
129
|
+
CSSL Script Runtime
|
|
130
|
+
Interprets and executes CSSL Abstract Syntax Trees
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
def __init__(self, service_engine=None, output_callback: Callable[[str, str], None] = None):
|
|
134
|
+
self.service_engine = service_engine
|
|
135
|
+
self.scope = Scope()
|
|
136
|
+
self.global_scope = self.scope
|
|
137
|
+
self.builtins = CSSLBuiltins(self)
|
|
138
|
+
self.event_manager = get_event_manager()
|
|
139
|
+
self.output_buffer: List[str] = []
|
|
140
|
+
self.services: Dict[str, ServiceDefinition] = {}
|
|
141
|
+
self._modules: Dict[str, Any] = {}
|
|
142
|
+
self._global_structs: Dict[str, Any] = {} # Global structs for s@<name> references
|
|
143
|
+
self._function_injections: Dict[str, List[tuple]] = {} # List of (code_block, captured_values_dict)
|
|
144
|
+
self._function_replaced: Dict[str, bool] = {} # NEW: Track replaced functions (<<==)
|
|
145
|
+
self._original_functions: Dict[str, Any] = {} # Store originals before replacement
|
|
146
|
+
self._injection_captures: Dict[str, Dict[str, Any]] = {} # Captured %vars per injection
|
|
147
|
+
self._current_captured_values: Dict[str, Any] = {} # Current captured values during injection execution
|
|
148
|
+
self._promoted_globals: Dict[str, Any] = {} # NEW: Variables promoted via global()
|
|
149
|
+
self._current_instance: Optional[CSSLInstance] = None # Current class instance for this-> access
|
|
150
|
+
self._running = False
|
|
151
|
+
self._exit_code = 0
|
|
152
|
+
self._output_callback = output_callback # Callback for console output (text, level)
|
|
153
|
+
self._source_lines: List[str] = [] # Store source code lines for error reporting
|
|
154
|
+
self._current_file: str = "<code>" # Current file being executed
|
|
155
|
+
|
|
156
|
+
self._setup_modules()
|
|
157
|
+
self._setup_builtins()
|
|
158
|
+
|
|
159
|
+
def _setup_modules(self):
|
|
160
|
+
"""Setup module references for @KernelClient, @VSRAM, etc."""
|
|
161
|
+
if self.service_engine:
|
|
162
|
+
self._modules['KernelClient'] = self.service_engine.KernelClient
|
|
163
|
+
self._modules['Kernel'] = self.service_engine.KernelClient
|
|
164
|
+
|
|
165
|
+
if hasattr(self.service_engine.KernelClient, 'VSRam'):
|
|
166
|
+
self._modules['VSRAM'] = self.service_engine.KernelClient.VSRam
|
|
167
|
+
self._modules['VSRam'] = self.service_engine.KernelClient.VSRam
|
|
168
|
+
|
|
169
|
+
if hasattr(self.service_engine.KernelClient, 'WheelKernel'):
|
|
170
|
+
self._modules['Wheel'] = self.service_engine.KernelClient.WheelKernel
|
|
171
|
+
self._modules['WheelKernel'] = self.service_engine.KernelClient.WheelKernel
|
|
172
|
+
|
|
173
|
+
if hasattr(self.service_engine.KernelClient, 'CSnI'):
|
|
174
|
+
self._modules['Network'] = self.service_engine.KernelClient.CSnI
|
|
175
|
+
self._modules['CSnI'] = self.service_engine.KernelClient.CSnI
|
|
176
|
+
|
|
177
|
+
if hasattr(self.service_engine, 'ServiceOperation'):
|
|
178
|
+
self._modules['Service'] = self.service_engine.ServiceOperation
|
|
179
|
+
self._modules['ServiceOperation'] = self.service_engine.ServiceOperation
|
|
180
|
+
|
|
181
|
+
self._modules['ServiceEngine'] = self.service_engine
|
|
182
|
+
self._modules['Boot'] = self.service_engine
|
|
183
|
+
|
|
184
|
+
self._modules['event'] = self.event_manager
|
|
185
|
+
self._modules['Events'] = self.event_manager
|
|
186
|
+
|
|
187
|
+
# Register CSSL Standard Modules
|
|
188
|
+
module_registry = get_module_registry()
|
|
189
|
+
for module_name in module_registry.list_modules():
|
|
190
|
+
module = module_registry.get_module(module_name)
|
|
191
|
+
if module:
|
|
192
|
+
module.runtime = self
|
|
193
|
+
self._modules[module_name] = module
|
|
194
|
+
|
|
195
|
+
def _setup_builtins(self):
|
|
196
|
+
"""Register built-in functions in global scope"""
|
|
197
|
+
for name in self.builtins.list_functions():
|
|
198
|
+
self.global_scope.set(name, self.builtins.get_function(name))
|
|
199
|
+
|
|
200
|
+
def get_module(self, path: str) -> Any:
|
|
201
|
+
"""Get a module by path like 'KernelClient.VSRam'"""
|
|
202
|
+
parts = path.split('.')
|
|
203
|
+
obj = self._modules.get(parts[0])
|
|
204
|
+
|
|
205
|
+
if obj is None:
|
|
206
|
+
return None
|
|
207
|
+
|
|
208
|
+
for part in parts[1:]:
|
|
209
|
+
if hasattr(obj, part):
|
|
210
|
+
obj = getattr(obj, part)
|
|
211
|
+
elif isinstance(obj, dict) and part in obj:
|
|
212
|
+
obj = obj[part]
|
|
213
|
+
else:
|
|
214
|
+
return None
|
|
215
|
+
|
|
216
|
+
return obj
|
|
217
|
+
|
|
218
|
+
def register_global_struct(self, name: str, struct_data: Any):
|
|
219
|
+
"""Register a struct as globally accessible via s@<name>"""
|
|
220
|
+
self._global_structs[name] = struct_data
|
|
221
|
+
|
|
222
|
+
def get_global_struct(self, path: str) -> Any:
|
|
223
|
+
"""Get a global struct by path like 'Backend.Loop.timer'"""
|
|
224
|
+
parts = path.split('.')
|
|
225
|
+
obj = self._global_structs.get(parts[0])
|
|
226
|
+
|
|
227
|
+
if obj is None:
|
|
228
|
+
return None
|
|
229
|
+
|
|
230
|
+
for part in parts[1:]:
|
|
231
|
+
if hasattr(obj, part):
|
|
232
|
+
obj = getattr(obj, part)
|
|
233
|
+
elif isinstance(obj, dict) and part in obj:
|
|
234
|
+
obj = obj[part]
|
|
235
|
+
else:
|
|
236
|
+
return None
|
|
237
|
+
|
|
238
|
+
return obj
|
|
239
|
+
|
|
240
|
+
def _format_error(self, line: int, message: str, hint: str = None) -> CSSLRuntimeError:
|
|
241
|
+
"""Format a detailed error with source context"""
|
|
242
|
+
error_parts = []
|
|
243
|
+
|
|
244
|
+
# Main error header
|
|
245
|
+
if line and line > 0:
|
|
246
|
+
error_parts.append(f"Error at line {line} in {self._current_file}:")
|
|
247
|
+
else:
|
|
248
|
+
error_parts.append(f"Error in {self._current_file}:")
|
|
249
|
+
|
|
250
|
+
# Extract message without existing line info
|
|
251
|
+
clean_msg = message
|
|
252
|
+
if "at line" in clean_msg.lower():
|
|
253
|
+
# Remove redundant line info from message
|
|
254
|
+
clean_msg = clean_msg.split(":", 1)[-1].strip() if ":" in clean_msg else clean_msg
|
|
255
|
+
|
|
256
|
+
error_parts.append(f" {clean_msg}")
|
|
257
|
+
|
|
258
|
+
# Show source context (3 lines before and after)
|
|
259
|
+
if self._source_lines and line and line > 0:
|
|
260
|
+
error_parts.append("")
|
|
261
|
+
start = max(0, line - 3)
|
|
262
|
+
end = min(len(self._source_lines), line + 2)
|
|
263
|
+
|
|
264
|
+
for i in range(start, end):
|
|
265
|
+
line_num = i + 1
|
|
266
|
+
source_line = self._source_lines[i] if i < len(self._source_lines) else ""
|
|
267
|
+
marker = ">>>" if line_num == line else " "
|
|
268
|
+
error_parts.append(f" {marker} {line_num:4d} | {source_line}")
|
|
269
|
+
|
|
270
|
+
# Add hint
|
|
271
|
+
if hint:
|
|
272
|
+
error_parts.append("")
|
|
273
|
+
error_parts.append(f" Hint: {hint}")
|
|
274
|
+
|
|
275
|
+
return CSSLRuntimeError("\n".join(error_parts), line)
|
|
276
|
+
|
|
277
|
+
def _get_source_line(self, line: int) -> str:
|
|
278
|
+
"""Get source line by number (1-indexed)"""
|
|
279
|
+
if self._source_lines and 0 < line <= len(self._source_lines):
|
|
280
|
+
return self._source_lines[line - 1]
|
|
281
|
+
return ""
|
|
282
|
+
|
|
283
|
+
def execute(self, source: str) -> Any:
|
|
284
|
+
"""Execute CSSL service source code"""
|
|
285
|
+
self._source_lines = source.splitlines()
|
|
286
|
+
try:
|
|
287
|
+
ast = parse_cssl(source)
|
|
288
|
+
return self._execute_node(ast)
|
|
289
|
+
except CSSLSyntaxError as e:
|
|
290
|
+
raise self._format_error(e.line, str(e))
|
|
291
|
+
except SyntaxError as e:
|
|
292
|
+
raise CSSLRuntimeError(f"Syntax error: {e}")
|
|
293
|
+
|
|
294
|
+
def execute_program(self, source: str) -> Any:
|
|
295
|
+
"""Execute standalone CSSL program (no service wrapper)"""
|
|
296
|
+
self._source_lines = source.splitlines()
|
|
297
|
+
try:
|
|
298
|
+
ast = parse_cssl_program(source)
|
|
299
|
+
return self._exec_program(ast)
|
|
300
|
+
except CSSLSyntaxError as e:
|
|
301
|
+
raise self._format_error(e.line, str(e))
|
|
302
|
+
except SyntaxError as e:
|
|
303
|
+
raise CSSLRuntimeError(f"Syntax error: {e}")
|
|
304
|
+
|
|
305
|
+
def execute_ast(self, ast: ASTNode) -> Any:
|
|
306
|
+
"""Execute a pre-parsed AST"""
|
|
307
|
+
return self._execute_node(ast)
|
|
308
|
+
|
|
309
|
+
def execute_file(self, filepath: str) -> Any:
|
|
310
|
+
"""Execute a CSSL service file"""
|
|
311
|
+
import os
|
|
312
|
+
self._current_file = os.path.basename(filepath)
|
|
313
|
+
with open(filepath, 'r', encoding='utf-8') as f:
|
|
314
|
+
source = f.read()
|
|
315
|
+
return self.execute(source)
|
|
316
|
+
|
|
317
|
+
def execute_program_file(self, filepath: str) -> Any:
|
|
318
|
+
"""Execute a standalone CSSL program file"""
|
|
319
|
+
import os
|
|
320
|
+
self._current_file = os.path.basename(filepath)
|
|
321
|
+
with open(filepath, 'r', encoding='utf-8') as f:
|
|
322
|
+
source = f.read()
|
|
323
|
+
return self.execute_program(source)
|
|
324
|
+
|
|
325
|
+
def _execute_node(self, node: ASTNode) -> Any:
|
|
326
|
+
"""Execute an AST node"""
|
|
327
|
+
if node is None:
|
|
328
|
+
return None
|
|
329
|
+
|
|
330
|
+
method_name = f'_exec_{node.type.replace("-", "_")}'
|
|
331
|
+
method = getattr(self, method_name, None)
|
|
332
|
+
|
|
333
|
+
if method:
|
|
334
|
+
return method(node)
|
|
335
|
+
else:
|
|
336
|
+
raise CSSLRuntimeError(f"Unknown node type: {node.type}", node.line)
|
|
337
|
+
|
|
338
|
+
def _exec_service(self, node: ASTNode) -> ServiceDefinition:
|
|
339
|
+
"""Execute service root node"""
|
|
340
|
+
service = ServiceDefinition()
|
|
341
|
+
|
|
342
|
+
for child in node.children:
|
|
343
|
+
if child.type == 'service-init':
|
|
344
|
+
self._exec_service_init(child, service)
|
|
345
|
+
elif child.type == 'service-include':
|
|
346
|
+
self._exec_service_include(child, service)
|
|
347
|
+
elif child.type == 'service-run':
|
|
348
|
+
self._exec_service_run(child, service)
|
|
349
|
+
# NEW: package block support
|
|
350
|
+
elif child.type == 'package':
|
|
351
|
+
self._exec_package(child, service)
|
|
352
|
+
# NEW: package-includes block support
|
|
353
|
+
elif child.type == 'package-includes':
|
|
354
|
+
self._exec_package_includes(child, service)
|
|
355
|
+
# NEW: struct at top level
|
|
356
|
+
elif child.type == 'struct':
|
|
357
|
+
struct_info = child.value
|
|
358
|
+
if isinstance(struct_info, dict):
|
|
359
|
+
struct_name = struct_info.get('name', '')
|
|
360
|
+
else:
|
|
361
|
+
struct_name = struct_info
|
|
362
|
+
service.structs[struct_name] = child
|
|
363
|
+
self._exec_struct(child)
|
|
364
|
+
# NEW: define at top level
|
|
365
|
+
elif child.type == 'function':
|
|
366
|
+
func_info = child.value
|
|
367
|
+
func_name = func_info.get('name')
|
|
368
|
+
service.functions[func_name] = child
|
|
369
|
+
self.scope.set(func_name, child)
|
|
370
|
+
|
|
371
|
+
return service
|
|
372
|
+
|
|
373
|
+
def _exec_program(self, node: ASTNode) -> Any:
|
|
374
|
+
"""Execute standalone program (no service wrapper)
|
|
375
|
+
|
|
376
|
+
A program can contain:
|
|
377
|
+
- struct definitions
|
|
378
|
+
- function definitions (define)
|
|
379
|
+
- top-level statements (assignments, function calls, control flow)
|
|
380
|
+
"""
|
|
381
|
+
result = None
|
|
382
|
+
self._running = True # Start running
|
|
383
|
+
|
|
384
|
+
for child in node.children:
|
|
385
|
+
# Check if exit() was called
|
|
386
|
+
if not self._running:
|
|
387
|
+
break
|
|
388
|
+
|
|
389
|
+
if child.type == 'struct':
|
|
390
|
+
self._exec_struct(child)
|
|
391
|
+
elif child.type == 'class':
|
|
392
|
+
self._exec_class(child)
|
|
393
|
+
elif child.type == 'function':
|
|
394
|
+
self._exec_function(child)
|
|
395
|
+
elif child.type == 'global_assignment':
|
|
396
|
+
# Handle global variable declaration: global Name = value
|
|
397
|
+
result = self._exec_global_assignment(child)
|
|
398
|
+
elif child.type == 'typed_declaration':
|
|
399
|
+
# Handle typed variable declaration: type<T> varName = value;
|
|
400
|
+
result = self._exec_typed_declaration(child)
|
|
401
|
+
elif child.type == 'instance_declaration':
|
|
402
|
+
# Handle instance declaration: instance<"name"> varName;
|
|
403
|
+
result = self._exec_instance_declaration(child)
|
|
404
|
+
elif child.type in ('assignment', 'expression', 'inject', 'receive', 'flow',
|
|
405
|
+
'if', 'while', 'for', 'c_for', 'foreach', 'switch', 'try'):
|
|
406
|
+
result = self._execute_node(child)
|
|
407
|
+
elif child.type == 'call':
|
|
408
|
+
result = self._eval_call(child)
|
|
409
|
+
else:
|
|
410
|
+
# Try to execute as statement
|
|
411
|
+
try:
|
|
412
|
+
result = self._execute_node(child)
|
|
413
|
+
except CSSLRuntimeError:
|
|
414
|
+
pass # Ignore unknown nodes in program mode
|
|
415
|
+
|
|
416
|
+
# Look for and execute main() if defined (only if still running)
|
|
417
|
+
if self._running:
|
|
418
|
+
main_func = self.scope.get('main')
|
|
419
|
+
if main_func and isinstance(main_func, ASTNode) and main_func.type == 'function':
|
|
420
|
+
try:
|
|
421
|
+
result = self._call_function(main_func, [])
|
|
422
|
+
except CSSLReturn as ret:
|
|
423
|
+
result = ret.value
|
|
424
|
+
|
|
425
|
+
return result
|
|
426
|
+
|
|
427
|
+
def _exec_service_init(self, node: ASTNode, service: ServiceDefinition):
|
|
428
|
+
"""Execute service-init block"""
|
|
429
|
+
# Property aliases mapping
|
|
430
|
+
key_aliases = {
|
|
431
|
+
'service-name': 'name',
|
|
432
|
+
'service-version': 'version',
|
|
433
|
+
'service-author': 'author',
|
|
434
|
+
'service-description': 'description',
|
|
435
|
+
'executation': 'execution',
|
|
436
|
+
'execution': 'execution',
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
for child in node.children:
|
|
440
|
+
if child.type == 'property' and child.value:
|
|
441
|
+
key = child.value.get('key')
|
|
442
|
+
value = child.value.get('value')
|
|
443
|
+
|
|
444
|
+
# Apply alias mapping
|
|
445
|
+
key = key_aliases.get(key, key)
|
|
446
|
+
|
|
447
|
+
if key == 'name':
|
|
448
|
+
service.name = value
|
|
449
|
+
elif key == 'version':
|
|
450
|
+
service.version = value
|
|
451
|
+
elif key == 'author':
|
|
452
|
+
service.author = value
|
|
453
|
+
elif key == 'description':
|
|
454
|
+
service.description = value
|
|
455
|
+
elif key == 'dependencies':
|
|
456
|
+
if isinstance(value, list):
|
|
457
|
+
service.dependencies = value
|
|
458
|
+
elif isinstance(value, str):
|
|
459
|
+
service.dependencies = [value]
|
|
460
|
+
elif key == 'autostart':
|
|
461
|
+
service.autostart = bool(value)
|
|
462
|
+
elif key == 'priority':
|
|
463
|
+
service.priority = int(value)
|
|
464
|
+
elif key == 'execution':
|
|
465
|
+
# Handle execution type
|
|
466
|
+
if value == 'Persistent':
|
|
467
|
+
service.autostart = True
|
|
468
|
+
elif value == 'Only-Once':
|
|
469
|
+
service.autostart = False
|
|
470
|
+
|
|
471
|
+
def _exec_service_include(self, node: ASTNode, service: ServiceDefinition):
|
|
472
|
+
"""Execute service-include block for importing modules and files"""
|
|
473
|
+
for child in node.children:
|
|
474
|
+
if child.type == 'expression':
|
|
475
|
+
# Evaluate the expression which may be:
|
|
476
|
+
# - @ModuleName (standard module reference)
|
|
477
|
+
# - include(cso_root('/path/to/file.cssl'))
|
|
478
|
+
# - Variable assignment
|
|
479
|
+
result = self._evaluate(child.value)
|
|
480
|
+
|
|
481
|
+
# If result is a module, register it
|
|
482
|
+
if result is not None:
|
|
483
|
+
# Check if it's a call result (like include())
|
|
484
|
+
if hasattr(result, 'name') and result.name:
|
|
485
|
+
# It's a ServiceDefinition from include()
|
|
486
|
+
# Merge its functions and structs into current service
|
|
487
|
+
if hasattr(result, 'functions'):
|
|
488
|
+
for fname, fnode in result.functions.items():
|
|
489
|
+
service.functions[fname] = fnode
|
|
490
|
+
self.scope.set(fname, fnode)
|
|
491
|
+
if hasattr(result, 'structs'):
|
|
492
|
+
for sname, snode in result.structs.items():
|
|
493
|
+
service.structs[sname] = snode
|
|
494
|
+
|
|
495
|
+
elif child.type == 'assignment' or child.type == 'injection':
|
|
496
|
+
# Handle: utils <== include(cso_root('/services/utils.cssl'));
|
|
497
|
+
info = child.value
|
|
498
|
+
name = info.get('name') or info.get('target')
|
|
499
|
+
if isinstance(name, ASTNode):
|
|
500
|
+
name = name.value if name.type == 'identifier' else str(name)
|
|
501
|
+
source = info.get('source') or info.get('value')
|
|
502
|
+
value = self._evaluate(source)
|
|
503
|
+
self.scope.set(name, value)
|
|
504
|
+
|
|
505
|
+
elif child.type == 'call':
|
|
506
|
+
# Direct function call like include(...)
|
|
507
|
+
self._eval_call(child)
|
|
508
|
+
|
|
509
|
+
elif child.type == 'module_ref':
|
|
510
|
+
# @ModuleName reference - just evaluate to register
|
|
511
|
+
self._evaluate(child)
|
|
512
|
+
|
|
513
|
+
def _exec_service_run(self, node: ASTNode, service: ServiceDefinition):
|
|
514
|
+
"""Execute service-run block"""
|
|
515
|
+
for child in node.children:
|
|
516
|
+
if child.type == 'struct':
|
|
517
|
+
# Handle new dict format: {'name': name, 'global': is_global}
|
|
518
|
+
struct_info = child.value
|
|
519
|
+
if isinstance(struct_info, dict):
|
|
520
|
+
struct_name = struct_info.get('name', '')
|
|
521
|
+
else:
|
|
522
|
+
struct_name = struct_info
|
|
523
|
+
service.structs[struct_name] = child
|
|
524
|
+
self._exec_struct(child)
|
|
525
|
+
elif child.type == 'function':
|
|
526
|
+
func_info = child.value
|
|
527
|
+
func_name = func_info.get('name')
|
|
528
|
+
service.functions[func_name] = child
|
|
529
|
+
self.scope.set(func_name, child)
|
|
530
|
+
|
|
531
|
+
def _exec_package(self, node: ASTNode, service: ServiceDefinition):
|
|
532
|
+
"""Execute package {} block for service metadata - NEW
|
|
533
|
+
|
|
534
|
+
Syntax:
|
|
535
|
+
package {
|
|
536
|
+
service = "ServiceName";
|
|
537
|
+
exec = @Start();
|
|
538
|
+
version = "1.0.0";
|
|
539
|
+
description = "Beschreibung";
|
|
540
|
+
}
|
|
541
|
+
"""
|
|
542
|
+
exec_func = None
|
|
543
|
+
|
|
544
|
+
for child in node.children:
|
|
545
|
+
if child.type == 'package_property' and child.value:
|
|
546
|
+
key = child.value.get('key')
|
|
547
|
+
value_node = child.value.get('value')
|
|
548
|
+
|
|
549
|
+
# Evaluate the value
|
|
550
|
+
value = self._evaluate(value_node)
|
|
551
|
+
|
|
552
|
+
if key == 'service':
|
|
553
|
+
service.name = value
|
|
554
|
+
elif key == 'version':
|
|
555
|
+
service.version = value
|
|
556
|
+
elif key == 'description':
|
|
557
|
+
service.description = value
|
|
558
|
+
elif key == 'author':
|
|
559
|
+
service.author = value
|
|
560
|
+
elif key == 'exec':
|
|
561
|
+
# Store exec function for later execution
|
|
562
|
+
exec_func = value_node
|
|
563
|
+
|
|
564
|
+
# Store exec function reference for later
|
|
565
|
+
if exec_func:
|
|
566
|
+
service.functions['__exec__'] = exec_func
|
|
567
|
+
|
|
568
|
+
def _exec_package_includes(self, node: ASTNode, service: ServiceDefinition):
|
|
569
|
+
"""Execute package-includes {} block for imports - NEW
|
|
570
|
+
|
|
571
|
+
Syntax:
|
|
572
|
+
package-includes {
|
|
573
|
+
@Lists = get('list');
|
|
574
|
+
@OS = get('os');
|
|
575
|
+
@Time = get('time');
|
|
576
|
+
@VSRam = get('vsramsdk');
|
|
577
|
+
}
|
|
578
|
+
"""
|
|
579
|
+
for child in node.children:
|
|
580
|
+
if child.type == 'assignment':
|
|
581
|
+
info = child.value
|
|
582
|
+
target = info.get('target')
|
|
583
|
+
value_node = info.get('value')
|
|
584
|
+
|
|
585
|
+
# Evaluate the value (e.g., get('list'))
|
|
586
|
+
value = self._evaluate(value_node)
|
|
587
|
+
|
|
588
|
+
# Get target name
|
|
589
|
+
if isinstance(target, ASTNode):
|
|
590
|
+
if target.type == 'module_ref':
|
|
591
|
+
# @ModuleName = get(...)
|
|
592
|
+
module_name = target.value
|
|
593
|
+
self._modules[module_name] = value
|
|
594
|
+
elif target.type == 'identifier':
|
|
595
|
+
# varName = get(...)
|
|
596
|
+
self.scope.set(target.value, value)
|
|
597
|
+
elif isinstance(target, str):
|
|
598
|
+
self.scope.set(target, value)
|
|
599
|
+
|
|
600
|
+
elif child.type == 'expression':
|
|
601
|
+
# Evaluate expression statements
|
|
602
|
+
self._evaluate(child.value)
|
|
603
|
+
|
|
604
|
+
elif child.type == 'inject':
|
|
605
|
+
# Handle: @Module <== get(...);
|
|
606
|
+
target = child.value.get('target')
|
|
607
|
+
source = self._evaluate(child.value.get('source'))
|
|
608
|
+
|
|
609
|
+
if isinstance(target, ASTNode) and target.type == 'module_ref':
|
|
610
|
+
self._modules[target.value] = source
|
|
611
|
+
elif isinstance(target, ASTNode) and target.type == 'identifier':
|
|
612
|
+
self.scope.set(target.value, source)
|
|
613
|
+
|
|
614
|
+
def _exec_struct(self, node: ASTNode) -> Dict[str, Any]:
|
|
615
|
+
"""Execute struct block"""
|
|
616
|
+
struct_data = {}
|
|
617
|
+
|
|
618
|
+
# Get struct name and global flag
|
|
619
|
+
struct_info = node.value
|
|
620
|
+
if isinstance(struct_info, dict):
|
|
621
|
+
struct_name = struct_info.get('name', '')
|
|
622
|
+
is_global = struct_info.get('global', False)
|
|
623
|
+
else:
|
|
624
|
+
# Backwards compatibility: value is just the name
|
|
625
|
+
struct_name = struct_info
|
|
626
|
+
is_global = False
|
|
627
|
+
|
|
628
|
+
for child in node.children:
|
|
629
|
+
if child.type == 'injection':
|
|
630
|
+
info = child.value
|
|
631
|
+
name = info.get('name')
|
|
632
|
+
source = info.get('source')
|
|
633
|
+
value = self._evaluate(source)
|
|
634
|
+
struct_data[name] = value
|
|
635
|
+
self.scope.set(name, value)
|
|
636
|
+
|
|
637
|
+
elif child.type == 'assignment':
|
|
638
|
+
info = child.value
|
|
639
|
+
name = info.get('name')
|
|
640
|
+
value = self._evaluate(info.get('value'))
|
|
641
|
+
struct_data[name] = value
|
|
642
|
+
self.scope.set(name, value)
|
|
643
|
+
|
|
644
|
+
elif child.type == 'function':
|
|
645
|
+
func_info = child.value
|
|
646
|
+
func_name = func_info.get('name')
|
|
647
|
+
struct_data[func_name] = child
|
|
648
|
+
self.scope.set(func_name, child)
|
|
649
|
+
|
|
650
|
+
elif child.type == 'expression':
|
|
651
|
+
# Execute expression statements like print()
|
|
652
|
+
self._evaluate(child.value)
|
|
653
|
+
|
|
654
|
+
elif child.type == 'call':
|
|
655
|
+
# Direct function calls
|
|
656
|
+
self._eval_call(child)
|
|
657
|
+
|
|
658
|
+
else:
|
|
659
|
+
# Try to execute other statement types
|
|
660
|
+
try:
|
|
661
|
+
self._execute_node(child)
|
|
662
|
+
except Exception:
|
|
663
|
+
pass
|
|
664
|
+
|
|
665
|
+
# Register as global struct if decorated with (@)
|
|
666
|
+
if is_global and struct_name:
|
|
667
|
+
self.register_global_struct(struct_name, struct_data)
|
|
668
|
+
|
|
669
|
+
return struct_data
|
|
670
|
+
|
|
671
|
+
def _exec_class(self, node: ASTNode) -> CSSLClass:
|
|
672
|
+
"""Execute class definition - registers class in scope.
|
|
673
|
+
|
|
674
|
+
Parses class members and methods, creating a CSSLClass object
|
|
675
|
+
that can be instantiated with 'new'.
|
|
676
|
+
"""
|
|
677
|
+
class_info = node.value
|
|
678
|
+
class_name = class_info.get('name')
|
|
679
|
+
|
|
680
|
+
members = {} # Member variable defaults/types
|
|
681
|
+
methods = {} # Method AST nodes
|
|
682
|
+
constructor = None
|
|
683
|
+
|
|
684
|
+
for child in node.children:
|
|
685
|
+
if child.type == 'function':
|
|
686
|
+
# This is a method
|
|
687
|
+
func_info = child.value
|
|
688
|
+
method_name = func_info.get('name')
|
|
689
|
+
|
|
690
|
+
if func_info.get('is_constructor') or method_name == class_name or method_name == '__init__':
|
|
691
|
+
constructor = child
|
|
692
|
+
else:
|
|
693
|
+
methods[method_name] = child
|
|
694
|
+
|
|
695
|
+
elif child.type == 'typed_declaration':
|
|
696
|
+
# This is a member variable
|
|
697
|
+
decl = child.value
|
|
698
|
+
member_name = decl.get('name')
|
|
699
|
+
member_type = decl.get('type')
|
|
700
|
+
member_value = decl.get('value')
|
|
701
|
+
|
|
702
|
+
# Store member info with type and optional default
|
|
703
|
+
members[member_name] = {
|
|
704
|
+
'type': member_type,
|
|
705
|
+
'default': self._evaluate(member_value) if member_value else None
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
# Create class definition object
|
|
709
|
+
class_def = CSSLClass(
|
|
710
|
+
name=class_name,
|
|
711
|
+
members=members,
|
|
712
|
+
methods=methods,
|
|
713
|
+
constructor=constructor
|
|
714
|
+
)
|
|
715
|
+
|
|
716
|
+
# Register class in scope
|
|
717
|
+
self.scope.set(class_name, class_def)
|
|
718
|
+
self.global_scope.set(class_name, class_def)
|
|
719
|
+
|
|
720
|
+
return class_def
|
|
721
|
+
|
|
722
|
+
def _exec_function(self, node: ASTNode) -> Any:
|
|
723
|
+
"""Execute function definition - just registers it"""
|
|
724
|
+
func_info = node.value
|
|
725
|
+
func_name = func_info.get('name')
|
|
726
|
+
self.scope.set(func_name, node)
|
|
727
|
+
return None
|
|
728
|
+
|
|
729
|
+
def _exec_typed_declaration(self, node: ASTNode) -> Any:
|
|
730
|
+
"""Execute typed variable declaration: type<T> varName = value;
|
|
731
|
+
|
|
732
|
+
Creates appropriate type instances for stack, vector, datastruct, etc.
|
|
733
|
+
"""
|
|
734
|
+
decl = node.value
|
|
735
|
+
type_name = decl.get('type')
|
|
736
|
+
element_type = decl.get('element_type', 'dynamic')
|
|
737
|
+
var_name = decl.get('name')
|
|
738
|
+
value_node = decl.get('value')
|
|
739
|
+
|
|
740
|
+
# Create the appropriate type instance
|
|
741
|
+
if type_name == 'stack':
|
|
742
|
+
instance = Stack(element_type)
|
|
743
|
+
elif type_name == 'vector':
|
|
744
|
+
instance = Vector(element_type)
|
|
745
|
+
elif type_name == 'datastruct':
|
|
746
|
+
instance = DataStruct(element_type)
|
|
747
|
+
elif type_name == 'shuffled':
|
|
748
|
+
instance = Shuffled(element_type)
|
|
749
|
+
elif type_name == 'iterator':
|
|
750
|
+
instance = Iterator(element_type)
|
|
751
|
+
elif type_name == 'combo':
|
|
752
|
+
instance = Combo(element_type)
|
|
753
|
+
elif type_name == 'dataspace':
|
|
754
|
+
instance = DataSpace(element_type)
|
|
755
|
+
elif type_name == 'openquote':
|
|
756
|
+
instance = OpenQuote()
|
|
757
|
+
elif type_name in ('int', 'integer'):
|
|
758
|
+
instance = 0 if value_node is None else self._evaluate(value_node)
|
|
759
|
+
elif type_name in ('string', 'str'):
|
|
760
|
+
instance = "" if value_node is None else self._evaluate(value_node)
|
|
761
|
+
elif type_name in ('float', 'double'):
|
|
762
|
+
instance = 0.0 if value_node is None else self._evaluate(value_node)
|
|
763
|
+
elif type_name == 'bool':
|
|
764
|
+
instance = False if value_node is None else self._evaluate(value_node)
|
|
765
|
+
elif type_name == 'dynamic':
|
|
766
|
+
instance = None if value_node is None else self._evaluate(value_node)
|
|
767
|
+
elif type_name == 'json':
|
|
768
|
+
instance = {} if value_node is None else self._evaluate(value_node)
|
|
769
|
+
elif type_name == 'array':
|
|
770
|
+
instance = Array(element_type)
|
|
771
|
+
elif type_name == 'list':
|
|
772
|
+
instance = List(element_type)
|
|
773
|
+
elif type_name in ('dictionary', 'dict'):
|
|
774
|
+
instance = Dictionary(element_type)
|
|
775
|
+
elif type_name == 'map':
|
|
776
|
+
instance = Map(element_type)
|
|
777
|
+
else:
|
|
778
|
+
# Default: evaluate the value or set to None
|
|
779
|
+
instance = self._evaluate(value_node) if value_node else None
|
|
780
|
+
|
|
781
|
+
# If there's an explicit value, use it instead
|
|
782
|
+
if value_node and type_name not in ('int', 'integer', 'string', 'str', 'float', 'double', 'bool', 'dynamic', 'json', 'array'):
|
|
783
|
+
# For container types, the value might be initialization data
|
|
784
|
+
init_value = self._evaluate(value_node)
|
|
785
|
+
if isinstance(init_value, (list, tuple)):
|
|
786
|
+
instance.extend(init_value)
|
|
787
|
+
elif init_value is not None:
|
|
788
|
+
if hasattr(instance, 'append'):
|
|
789
|
+
instance.append(init_value)
|
|
790
|
+
|
|
791
|
+
# Store in scope
|
|
792
|
+
self.scope.set(var_name, instance)
|
|
793
|
+
return instance
|
|
794
|
+
|
|
795
|
+
def _exec_instance_declaration(self, node: ASTNode) -> Any:
|
|
796
|
+
"""Execute instance declaration: instance<"name"> varName;
|
|
797
|
+
|
|
798
|
+
Gets or creates a shared instance by name.
|
|
799
|
+
"""
|
|
800
|
+
from ..cssl_bridge import _live_objects, SharedObjectProxy
|
|
801
|
+
decl = node.value
|
|
802
|
+
instance_name = decl.get('instance_name')
|
|
803
|
+
var_name = decl.get('name')
|
|
804
|
+
value_node = decl.get('value')
|
|
805
|
+
|
|
806
|
+
# Get existing shared instance
|
|
807
|
+
instance = None
|
|
808
|
+
if instance_name in _live_objects:
|
|
809
|
+
instance = SharedObjectProxy(instance_name, _live_objects[instance_name])
|
|
810
|
+
elif self.global_scope.has(f'${instance_name}'):
|
|
811
|
+
instance = self.global_scope.get(f'${instance_name}')
|
|
812
|
+
|
|
813
|
+
# If value is provided, use that and register as shared
|
|
814
|
+
if value_node:
|
|
815
|
+
instance = self._evaluate(value_node)
|
|
816
|
+
# Register in global scope for future access
|
|
817
|
+
self.global_scope.set(f'${instance_name}', instance)
|
|
818
|
+
|
|
819
|
+
# Store in scope
|
|
820
|
+
self.scope.set(var_name, instance)
|
|
821
|
+
return instance
|
|
822
|
+
|
|
823
|
+
def _exec_global_assignment(self, node: ASTNode) -> Any:
|
|
824
|
+
"""Execute global variable assignment: global Name = value
|
|
825
|
+
|
|
826
|
+
Stores the value in _promoted_globals so it can be accessed via @Name
|
|
827
|
+
"""
|
|
828
|
+
inner = node.value # The wrapped assignment/expression node
|
|
829
|
+
|
|
830
|
+
if inner is None:
|
|
831
|
+
return None
|
|
832
|
+
|
|
833
|
+
# Handle assignment node: global Name = value
|
|
834
|
+
if inner.type == 'assignment':
|
|
835
|
+
target = inner.value.get('target')
|
|
836
|
+
value = self._evaluate(inner.value.get('value'))
|
|
837
|
+
|
|
838
|
+
# Get variable name from target
|
|
839
|
+
if isinstance(target, ASTNode):
|
|
840
|
+
if target.type == 'identifier':
|
|
841
|
+
var_name = target.value
|
|
842
|
+
elif target.type == 'global_ref':
|
|
843
|
+
# r@Name = value
|
|
844
|
+
var_name = target.value
|
|
845
|
+
else:
|
|
846
|
+
var_name = str(target.value) if hasattr(target, 'value') else str(target)
|
|
847
|
+
elif isinstance(target, str):
|
|
848
|
+
var_name = target
|
|
849
|
+
else:
|
|
850
|
+
var_name = str(target)
|
|
851
|
+
|
|
852
|
+
# Store in promoted globals for @Name access
|
|
853
|
+
self._promoted_globals[var_name] = value
|
|
854
|
+
# Also store in global scope for regular access
|
|
855
|
+
self.global_scope.set(var_name, value)
|
|
856
|
+
return value
|
|
857
|
+
|
|
858
|
+
# Handle expression that results in assignment
|
|
859
|
+
elif inner.type == 'expression':
|
|
860
|
+
result = self._evaluate(inner.value)
|
|
861
|
+
return result
|
|
862
|
+
|
|
863
|
+
# Fallback: execute normally
|
|
864
|
+
return self._execute_node(inner)
|
|
865
|
+
|
|
866
|
+
def _call_function(self, func_node: ASTNode, args: List[Any], kwargs: Dict[str, Any] = None) -> Any:
|
|
867
|
+
"""Call a function node with arguments (positional and named)
|
|
868
|
+
|
|
869
|
+
Args:
|
|
870
|
+
func_node: The function AST node
|
|
871
|
+
args: List of positional arguments
|
|
872
|
+
kwargs: Dict of named arguments (param_name -> value)
|
|
873
|
+
"""
|
|
874
|
+
func_info = func_node.value
|
|
875
|
+
params = func_info.get('params', [])
|
|
876
|
+
modifiers = func_info.get('modifiers', [])
|
|
877
|
+
kwargs = kwargs or {}
|
|
878
|
+
|
|
879
|
+
# Check for undefined modifier - suppress errors if present
|
|
880
|
+
is_undefined = 'undefined' in modifiers
|
|
881
|
+
|
|
882
|
+
# Create new scope
|
|
883
|
+
new_scope = Scope(parent=self.scope)
|
|
884
|
+
|
|
885
|
+
# Bind parameters - handle both positional and named arguments
|
|
886
|
+
for i, param in enumerate(params):
|
|
887
|
+
# Extract param name from dict format: {'name': 'a', 'type': 'int'}
|
|
888
|
+
param_name = param['name'] if isinstance(param, dict) else param
|
|
889
|
+
|
|
890
|
+
if param_name in kwargs:
|
|
891
|
+
# Named argument takes priority
|
|
892
|
+
new_scope.set(param_name, kwargs[param_name])
|
|
893
|
+
elif i < len(args):
|
|
894
|
+
# Positional argument
|
|
895
|
+
new_scope.set(param_name, args[i])
|
|
896
|
+
else:
|
|
897
|
+
new_scope.set(param_name, None)
|
|
898
|
+
|
|
899
|
+
# Execute body
|
|
900
|
+
old_scope = self.scope
|
|
901
|
+
self.scope = new_scope
|
|
902
|
+
|
|
903
|
+
try:
|
|
904
|
+
for child in func_node.children:
|
|
905
|
+
# Check if exit() was called
|
|
906
|
+
if not self._running:
|
|
907
|
+
break
|
|
908
|
+
self._execute_node(child)
|
|
909
|
+
except CSSLReturn as ret:
|
|
910
|
+
return ret.value
|
|
911
|
+
except Exception as e:
|
|
912
|
+
# If undefined modifier, suppress all errors
|
|
913
|
+
if is_undefined:
|
|
914
|
+
return None
|
|
915
|
+
raise
|
|
916
|
+
finally:
|
|
917
|
+
self.scope = old_scope
|
|
918
|
+
|
|
919
|
+
return None
|
|
920
|
+
|
|
921
|
+
def _exec_if(self, node: ASTNode) -> Any:
|
|
922
|
+
"""Execute if statement"""
|
|
923
|
+
condition = self._evaluate(node.value.get('condition'))
|
|
924
|
+
|
|
925
|
+
if condition:
|
|
926
|
+
for child in node.children:
|
|
927
|
+
if child.type == 'then':
|
|
928
|
+
for stmt in child.children:
|
|
929
|
+
self._execute_node(stmt)
|
|
930
|
+
return None
|
|
931
|
+
|
|
932
|
+
# Execute else block
|
|
933
|
+
for child in node.children:
|
|
934
|
+
if child.type == 'else':
|
|
935
|
+
for stmt in child.children:
|
|
936
|
+
self._execute_node(stmt)
|
|
937
|
+
return None
|
|
938
|
+
|
|
939
|
+
return None
|
|
940
|
+
|
|
941
|
+
def _exec_while(self, node: ASTNode) -> Any:
|
|
942
|
+
"""Execute while loop"""
|
|
943
|
+
while self._running and self._evaluate(node.value.get('condition')):
|
|
944
|
+
try:
|
|
945
|
+
for child in node.children:
|
|
946
|
+
if not self._running:
|
|
947
|
+
break
|
|
948
|
+
self._execute_node(child)
|
|
949
|
+
except CSSLBreak:
|
|
950
|
+
break
|
|
951
|
+
except CSSLContinue:
|
|
952
|
+
continue
|
|
953
|
+
|
|
954
|
+
return None
|
|
955
|
+
|
|
956
|
+
def _exec_for(self, node: ASTNode) -> Any:
|
|
957
|
+
"""Execute Python-style for loop: for (i in range(start, end, step)) { }"""
|
|
958
|
+
var_name = node.value.get('var')
|
|
959
|
+
start = int(self._evaluate(node.value.get('start')))
|
|
960
|
+
end = int(self._evaluate(node.value.get('end')))
|
|
961
|
+
|
|
962
|
+
# Optional step parameter (default is 1)
|
|
963
|
+
step_node = node.value.get('step')
|
|
964
|
+
step = int(self._evaluate(step_node)) if step_node else 1
|
|
965
|
+
|
|
966
|
+
for i in range(start, end, step):
|
|
967
|
+
if not self._running:
|
|
968
|
+
break
|
|
969
|
+
self.scope.set(var_name, i)
|
|
970
|
+
try:
|
|
971
|
+
for child in node.children:
|
|
972
|
+
if not self._running:
|
|
973
|
+
break
|
|
974
|
+
self._execute_node(child)
|
|
975
|
+
except CSSLBreak:
|
|
976
|
+
break
|
|
977
|
+
except CSSLContinue:
|
|
978
|
+
continue
|
|
979
|
+
|
|
980
|
+
return None
|
|
981
|
+
|
|
982
|
+
def _exec_c_for(self, node: ASTNode) -> Any:
|
|
983
|
+
"""Execute C-style for loop: for (init; condition; update) { }
|
|
984
|
+
|
|
985
|
+
Supports:
|
|
986
|
+
- for (int i = 0; i < n; i++) { }
|
|
987
|
+
- for (int i = 0; i < n; i = i + 1) { }
|
|
988
|
+
- for (i = 0; i < n; i += 1) { }
|
|
989
|
+
- for (; condition; ) { } (infinite loop with condition)
|
|
990
|
+
"""
|
|
991
|
+
init = node.value.get('init')
|
|
992
|
+
condition = node.value.get('condition')
|
|
993
|
+
update = node.value.get('update')
|
|
994
|
+
|
|
995
|
+
# Execute init statement
|
|
996
|
+
if init:
|
|
997
|
+
var_name = init.value.get('var')
|
|
998
|
+
init_value = self._evaluate(init.value.get('value'))
|
|
999
|
+
self.scope.set(var_name, init_value)
|
|
1000
|
+
else:
|
|
1001
|
+
var_name = None
|
|
1002
|
+
|
|
1003
|
+
# Main loop
|
|
1004
|
+
while self._running:
|
|
1005
|
+
# Check condition
|
|
1006
|
+
if condition:
|
|
1007
|
+
cond_result = self._evaluate(condition)
|
|
1008
|
+
if not cond_result:
|
|
1009
|
+
break
|
|
1010
|
+
# If no condition, this would be infinite - we still need a way to break
|
|
1011
|
+
|
|
1012
|
+
# Execute body
|
|
1013
|
+
try:
|
|
1014
|
+
for child in node.children:
|
|
1015
|
+
if not self._running:
|
|
1016
|
+
break
|
|
1017
|
+
self._execute_node(child)
|
|
1018
|
+
except CSSLBreak:
|
|
1019
|
+
break
|
|
1020
|
+
except CSSLContinue:
|
|
1021
|
+
pass # Continue to update, then next iteration
|
|
1022
|
+
|
|
1023
|
+
# Execute update
|
|
1024
|
+
if update:
|
|
1025
|
+
self._exec_c_for_update(update)
|
|
1026
|
+
|
|
1027
|
+
return None
|
|
1028
|
+
|
|
1029
|
+
def _exec_c_for_update(self, update: 'ASTNode') -> None:
|
|
1030
|
+
"""Execute the update part of a C-style for loop."""
|
|
1031
|
+
var_name = update.value.get('var')
|
|
1032
|
+
op = update.value.get('op')
|
|
1033
|
+
value_node = update.value.get('value')
|
|
1034
|
+
|
|
1035
|
+
current = self.scope.get(var_name) or 0
|
|
1036
|
+
|
|
1037
|
+
if op == 'increment':
|
|
1038
|
+
self.scope.set(var_name, current + 1)
|
|
1039
|
+
elif op == 'decrement':
|
|
1040
|
+
self.scope.set(var_name, current - 1)
|
|
1041
|
+
elif op == 'add':
|
|
1042
|
+
add_value = self._evaluate(value_node)
|
|
1043
|
+
self.scope.set(var_name, current + add_value)
|
|
1044
|
+
elif op == 'subtract':
|
|
1045
|
+
sub_value = self._evaluate(value_node)
|
|
1046
|
+
self.scope.set(var_name, current - sub_value)
|
|
1047
|
+
elif op == 'assign':
|
|
1048
|
+
new_value = self._evaluate(value_node)
|
|
1049
|
+
self.scope.set(var_name, new_value)
|
|
1050
|
+
|
|
1051
|
+
def _exec_foreach(self, node: ASTNode) -> Any:
|
|
1052
|
+
"""Execute foreach loop"""
|
|
1053
|
+
var_name = node.value.get('var')
|
|
1054
|
+
iterable = self._evaluate(node.value.get('iterable'))
|
|
1055
|
+
|
|
1056
|
+
if iterable is None:
|
|
1057
|
+
return None
|
|
1058
|
+
|
|
1059
|
+
for item in iterable:
|
|
1060
|
+
if not self._running:
|
|
1061
|
+
break
|
|
1062
|
+
self.scope.set(var_name, item)
|
|
1063
|
+
try:
|
|
1064
|
+
for child in node.children:
|
|
1065
|
+
if not self._running:
|
|
1066
|
+
break
|
|
1067
|
+
self._execute_node(child)
|
|
1068
|
+
except CSSLBreak:
|
|
1069
|
+
break
|
|
1070
|
+
except CSSLContinue:
|
|
1071
|
+
continue
|
|
1072
|
+
|
|
1073
|
+
return None
|
|
1074
|
+
|
|
1075
|
+
def _exec_switch(self, node: ASTNode) -> Any:
|
|
1076
|
+
"""Execute switch statement"""
|
|
1077
|
+
value = self._evaluate(node.value.get('value'))
|
|
1078
|
+
matched = False
|
|
1079
|
+
|
|
1080
|
+
for child in node.children:
|
|
1081
|
+
if child.type == 'case':
|
|
1082
|
+
case_value = self._evaluate(child.value.get('value'))
|
|
1083
|
+
if value == case_value:
|
|
1084
|
+
matched = True
|
|
1085
|
+
try:
|
|
1086
|
+
for stmt in child.children:
|
|
1087
|
+
self._execute_node(stmt)
|
|
1088
|
+
except CSSLBreak:
|
|
1089
|
+
return None
|
|
1090
|
+
elif child.type == 'default' and not matched:
|
|
1091
|
+
try:
|
|
1092
|
+
for stmt in child.children:
|
|
1093
|
+
self._execute_node(stmt)
|
|
1094
|
+
except CSSLBreak:
|
|
1095
|
+
return None
|
|
1096
|
+
|
|
1097
|
+
return None
|
|
1098
|
+
|
|
1099
|
+
def _exec_return(self, node: ASTNode) -> Any:
|
|
1100
|
+
"""Execute return statement"""
|
|
1101
|
+
value = self._evaluate(node.value) if node.value else None
|
|
1102
|
+
raise CSSLReturn(value)
|
|
1103
|
+
|
|
1104
|
+
def _exec_break(self, node: ASTNode) -> Any:
|
|
1105
|
+
"""Execute break statement"""
|
|
1106
|
+
raise CSSLBreak()
|
|
1107
|
+
|
|
1108
|
+
def _exec_continue(self, node: ASTNode) -> Any:
|
|
1109
|
+
"""Execute continue statement"""
|
|
1110
|
+
raise CSSLContinue()
|
|
1111
|
+
|
|
1112
|
+
def _exec_try(self, node: ASTNode) -> Any:
|
|
1113
|
+
"""Execute try/catch block"""
|
|
1114
|
+
try:
|
|
1115
|
+
for child in node.children:
|
|
1116
|
+
if child.type == 'try-block':
|
|
1117
|
+
for stmt in child.children:
|
|
1118
|
+
self._execute_node(stmt)
|
|
1119
|
+
except CSSLRuntimeError as e:
|
|
1120
|
+
for child in node.children:
|
|
1121
|
+
if child.type == 'catch-block':
|
|
1122
|
+
error_var = child.value.get('error_var') if child.value else None
|
|
1123
|
+
if error_var:
|
|
1124
|
+
self.scope.set(error_var, str(e))
|
|
1125
|
+
for stmt in child.children:
|
|
1126
|
+
self._execute_node(stmt)
|
|
1127
|
+
|
|
1128
|
+
return None
|
|
1129
|
+
|
|
1130
|
+
def _exec_createcmd_inject(self, node: ASTNode) -> Any:
|
|
1131
|
+
"""Execute createcmd injection: createcmd('cmd') <== { action }"""
|
|
1132
|
+
command_call = node.value.get('command_call')
|
|
1133
|
+
action_block = node.value.get('action')
|
|
1134
|
+
|
|
1135
|
+
# Get command name from the createcmd call arguments
|
|
1136
|
+
args = command_call.value.get('args', [])
|
|
1137
|
+
if args:
|
|
1138
|
+
command_name = self._evaluate(args[0])
|
|
1139
|
+
else:
|
|
1140
|
+
raise CSSLRuntimeError("createcmd requires a command name argument")
|
|
1141
|
+
|
|
1142
|
+
# Create the command handler function
|
|
1143
|
+
def command_handler(*cmd_args):
|
|
1144
|
+
# Create a scope for the command execution
|
|
1145
|
+
cmd_scope = Scope(parent=self.scope)
|
|
1146
|
+
|
|
1147
|
+
# Set cmd_args in scope
|
|
1148
|
+
cmd_scope.set('args', list(cmd_args))
|
|
1149
|
+
cmd_scope.set('argc', len(cmd_args))
|
|
1150
|
+
|
|
1151
|
+
old_scope = self.scope
|
|
1152
|
+
self.scope = cmd_scope
|
|
1153
|
+
|
|
1154
|
+
try:
|
|
1155
|
+
# Execute action block statements
|
|
1156
|
+
for child in action_block.children:
|
|
1157
|
+
if child.type == 'function':
|
|
1158
|
+
# If there's a define action { } inside, call it
|
|
1159
|
+
func_name = child.value.get('name')
|
|
1160
|
+
if func_name == 'action':
|
|
1161
|
+
return self._call_function(child, list(cmd_args))
|
|
1162
|
+
else:
|
|
1163
|
+
self._execute_node(child)
|
|
1164
|
+
except CSSLReturn as ret:
|
|
1165
|
+
return ret.value
|
|
1166
|
+
finally:
|
|
1167
|
+
self.scope = old_scope
|
|
1168
|
+
|
|
1169
|
+
return None
|
|
1170
|
+
|
|
1171
|
+
# Register the command using the builtin
|
|
1172
|
+
self.builtins.builtin_createcmd(command_name, command_handler)
|
|
1173
|
+
|
|
1174
|
+
return command_name
|
|
1175
|
+
|
|
1176
|
+
def _apply_injection_filter(self, source: Any, filter_info: dict) -> Any:
|
|
1177
|
+
"""Apply injection filter to extract specific data from source.
|
|
1178
|
+
|
|
1179
|
+
All BruteInjector Helpers:
|
|
1180
|
+
- string::where=VALUE - Filter strings containing VALUE
|
|
1181
|
+
- string::length=LENGTH - Filter strings of specific length
|
|
1182
|
+
- integer::where=VALUE - Filter integers matching VALUE
|
|
1183
|
+
- json::key=KEY - Extract values with specific key from JSON/dict
|
|
1184
|
+
- json::value=VALUE - Filter by value in JSON/dict
|
|
1185
|
+
- array::index=INDEX - Get specific index from array
|
|
1186
|
+
- array::length=LENGTH - Filter arrays of specific length
|
|
1187
|
+
- vector::where=VALUE - Filter vectors containing VALUE
|
|
1188
|
+
- vector::index=INDEX - Get specific index from vector
|
|
1189
|
+
- vector::length=LENGTH - Filter vectors of specific length
|
|
1190
|
+
- combo::filterdb - Get filter database from combo
|
|
1191
|
+
- combo::blocked - Get blocked items from combo
|
|
1192
|
+
- dynamic::VarName=VALUE - Filter by dynamic variable value
|
|
1193
|
+
- sql::data - Return only SQL-compatible data
|
|
1194
|
+
"""
|
|
1195
|
+
if not filter_info:
|
|
1196
|
+
return source
|
|
1197
|
+
|
|
1198
|
+
result = source
|
|
1199
|
+
|
|
1200
|
+
for filter_key, filter_value in filter_info.items():
|
|
1201
|
+
if '::' in filter_key:
|
|
1202
|
+
filter_type, helper = filter_key.split('::', 1)
|
|
1203
|
+
filter_val = self._evaluate(filter_value) if isinstance(filter_value, ASTNode) else filter_value
|
|
1204
|
+
|
|
1205
|
+
# === STRING HELPERS ===
|
|
1206
|
+
if filter_type == 'string':
|
|
1207
|
+
if helper == 'where':
|
|
1208
|
+
# Exact match
|
|
1209
|
+
if isinstance(result, str):
|
|
1210
|
+
result = result if result == filter_val else None
|
|
1211
|
+
elif isinstance(result, list):
|
|
1212
|
+
result = [item for item in result if isinstance(item, str) and item == filter_val]
|
|
1213
|
+
elif helper == 'contains':
|
|
1214
|
+
# Contains substring
|
|
1215
|
+
if isinstance(result, str):
|
|
1216
|
+
result = result if filter_val in result else None
|
|
1217
|
+
elif isinstance(result, list):
|
|
1218
|
+
result = [item for item in result if isinstance(item, str) and filter_val in item]
|
|
1219
|
+
elif helper == 'not':
|
|
1220
|
+
# Exclude matching
|
|
1221
|
+
if isinstance(result, str):
|
|
1222
|
+
result = result if result != filter_val else None
|
|
1223
|
+
elif isinstance(result, list):
|
|
1224
|
+
result = [item for item in result if not (isinstance(item, str) and item == filter_val)]
|
|
1225
|
+
elif helper == 'startsWith':
|
|
1226
|
+
if isinstance(result, str):
|
|
1227
|
+
result = result if result.startswith(filter_val) else None
|
|
1228
|
+
elif isinstance(result, list):
|
|
1229
|
+
result = [item for item in result if isinstance(item, str) and item.startswith(filter_val)]
|
|
1230
|
+
elif helper == 'endsWith':
|
|
1231
|
+
if isinstance(result, str):
|
|
1232
|
+
result = result if result.endswith(filter_val) else None
|
|
1233
|
+
elif isinstance(result, list):
|
|
1234
|
+
result = [item for item in result if isinstance(item, str) and item.endswith(filter_val)]
|
|
1235
|
+
elif helper in ('length', 'lenght'): # Support common typo
|
|
1236
|
+
if isinstance(result, str):
|
|
1237
|
+
result = result if len(result) == filter_val else None
|
|
1238
|
+
elif isinstance(result, list):
|
|
1239
|
+
result = [item for item in result if isinstance(item, str) and len(item) == filter_val]
|
|
1240
|
+
elif helper == 'cut':
|
|
1241
|
+
# Cut string - returns the part BEFORE the index/substring
|
|
1242
|
+
# x = <==[string::cut=2] "20:200-1" --> x = "20"
|
|
1243
|
+
# x = <==[string::cut="1.0"] "1.0.0" --> x = "" (before "1.0")
|
|
1244
|
+
if isinstance(result, str):
|
|
1245
|
+
if isinstance(filter_val, str):
|
|
1246
|
+
# Cut at substring position
|
|
1247
|
+
idx = result.find(filter_val)
|
|
1248
|
+
result = result[:idx] if idx >= 0 else result
|
|
1249
|
+
else:
|
|
1250
|
+
# Cut at integer index
|
|
1251
|
+
idx = int(filter_val)
|
|
1252
|
+
result = result[:idx] if 0 <= idx <= len(result) else result
|
|
1253
|
+
elif isinstance(result, list):
|
|
1254
|
+
def cut_item(item):
|
|
1255
|
+
if not isinstance(item, str):
|
|
1256
|
+
return item
|
|
1257
|
+
if isinstance(filter_val, str):
|
|
1258
|
+
idx = item.find(filter_val)
|
|
1259
|
+
return item[:idx] if idx >= 0 else item
|
|
1260
|
+
return item[:int(filter_val)]
|
|
1261
|
+
result = [cut_item(item) for item in result]
|
|
1262
|
+
elif helper == 'cutAfter':
|
|
1263
|
+
# Get the part AFTER the index/substring
|
|
1264
|
+
# x = <==[string::cutAfter=2] "20:200-1" --> x = ":200-1"
|
|
1265
|
+
# x = <==[string::cutAfter="1.0"] "1.0.0" --> x = ".0" (after "1.0")
|
|
1266
|
+
if isinstance(result, str):
|
|
1267
|
+
if isinstance(filter_val, str):
|
|
1268
|
+
# Cut after substring
|
|
1269
|
+
idx = result.find(filter_val)
|
|
1270
|
+
result = result[idx + len(filter_val):] if idx >= 0 else result
|
|
1271
|
+
else:
|
|
1272
|
+
# Cut after integer index
|
|
1273
|
+
idx = int(filter_val)
|
|
1274
|
+
result = result[idx:] if 0 <= idx <= len(result) else result
|
|
1275
|
+
elif isinstance(result, list):
|
|
1276
|
+
def cut_after_item(item):
|
|
1277
|
+
if not isinstance(item, str):
|
|
1278
|
+
return item
|
|
1279
|
+
if isinstance(filter_val, str):
|
|
1280
|
+
idx = item.find(filter_val)
|
|
1281
|
+
return item[idx + len(filter_val):] if idx >= 0 else item
|
|
1282
|
+
return item[int(filter_val):]
|
|
1283
|
+
result = [cut_after_item(item) for item in result]
|
|
1284
|
+
elif helper == 'slice':
|
|
1285
|
+
# Slice string with start:end format (e.g., "2:5")
|
|
1286
|
+
if isinstance(result, str) and isinstance(filter_val, str) and ':' in filter_val:
|
|
1287
|
+
parts = filter_val.split(':')
|
|
1288
|
+
start = int(parts[0]) if parts[0] else 0
|
|
1289
|
+
end = int(parts[1]) if parts[1] else len(result)
|
|
1290
|
+
result = result[start:end]
|
|
1291
|
+
elif helper == 'split':
|
|
1292
|
+
# Split string by delimiter
|
|
1293
|
+
if isinstance(result, str):
|
|
1294
|
+
result = result.split(str(filter_val))
|
|
1295
|
+
elif helper == 'replace':
|
|
1296
|
+
# Replace in string (format: "old:new")
|
|
1297
|
+
if isinstance(result, str) and isinstance(filter_val, str) and ':' in filter_val:
|
|
1298
|
+
parts = filter_val.split(':', 1)
|
|
1299
|
+
if len(parts) == 2:
|
|
1300
|
+
result = result.replace(parts[0], parts[1])
|
|
1301
|
+
elif helper == 'upper':
|
|
1302
|
+
if isinstance(result, str):
|
|
1303
|
+
result = result.upper()
|
|
1304
|
+
elif helper == 'lower':
|
|
1305
|
+
if isinstance(result, str):
|
|
1306
|
+
result = result.lower()
|
|
1307
|
+
elif helper == 'trim':
|
|
1308
|
+
if isinstance(result, str):
|
|
1309
|
+
result = result.strip()
|
|
1310
|
+
|
|
1311
|
+
# === INTEGER HELPERS ===
|
|
1312
|
+
elif filter_type == 'integer':
|
|
1313
|
+
if helper == 'where':
|
|
1314
|
+
if isinstance(result, int):
|
|
1315
|
+
result = result if result == filter_val else None
|
|
1316
|
+
elif isinstance(result, list):
|
|
1317
|
+
result = [item for item in result if isinstance(item, int) and item == filter_val]
|
|
1318
|
+
|
|
1319
|
+
# === JSON HELPERS ===
|
|
1320
|
+
elif filter_type == 'json':
|
|
1321
|
+
if helper == 'key':
|
|
1322
|
+
if isinstance(result, dict):
|
|
1323
|
+
result = result.get(filter_val)
|
|
1324
|
+
elif isinstance(result, list):
|
|
1325
|
+
result = [item.get(filter_val) for item in result if isinstance(item, dict) and filter_val in item]
|
|
1326
|
+
elif helper == 'value':
|
|
1327
|
+
if isinstance(result, dict):
|
|
1328
|
+
# Find key(s) with matching value
|
|
1329
|
+
matches = [k for k, v in result.items() if v == filter_val]
|
|
1330
|
+
result = matches[0] if len(matches) == 1 else matches
|
|
1331
|
+
elif isinstance(result, list):
|
|
1332
|
+
result = [item for item in result if (isinstance(item, dict) and filter_val in item.values())]
|
|
1333
|
+
|
|
1334
|
+
# === ARRAY HELPERS ===
|
|
1335
|
+
elif filter_type == 'array':
|
|
1336
|
+
if helper == 'index':
|
|
1337
|
+
if isinstance(result, (list, tuple)):
|
|
1338
|
+
idx = int(filter_val) if not isinstance(filter_val, int) else filter_val
|
|
1339
|
+
if 0 <= idx < len(result):
|
|
1340
|
+
result = result[idx]
|
|
1341
|
+
else:
|
|
1342
|
+
result = None
|
|
1343
|
+
elif helper in ('length', 'lenght'): # Support common typo
|
|
1344
|
+
if isinstance(result, (list, tuple)):
|
|
1345
|
+
result = result if len(result) == filter_val else []
|
|
1346
|
+
elif helper == 'where':
|
|
1347
|
+
if isinstance(result, list):
|
|
1348
|
+
result = [item for item in result if item == filter_val]
|
|
1349
|
+
|
|
1350
|
+
# === VECTOR HELPERS ===
|
|
1351
|
+
elif filter_type == 'vector':
|
|
1352
|
+
if helper == 'index':
|
|
1353
|
+
if isinstance(result, (list, tuple)):
|
|
1354
|
+
idx = int(filter_val) if not isinstance(filter_val, int) else filter_val
|
|
1355
|
+
if 0 <= idx < len(result):
|
|
1356
|
+
result = result[idx]
|
|
1357
|
+
else:
|
|
1358
|
+
result = None
|
|
1359
|
+
elif helper in ('length', 'lenght'): # Support common typo
|
|
1360
|
+
if isinstance(result, (list, tuple)):
|
|
1361
|
+
result = result if len(result) == filter_val else []
|
|
1362
|
+
elif helper == 'where':
|
|
1363
|
+
if isinstance(result, list):
|
|
1364
|
+
result = [item for item in result if item == filter_val]
|
|
1365
|
+
|
|
1366
|
+
# === COMBO HELPERS ===
|
|
1367
|
+
elif filter_type == 'combo':
|
|
1368
|
+
if helper == 'filterdb':
|
|
1369
|
+
if hasattr(result, '_filterdb'):
|
|
1370
|
+
result = result._filterdb
|
|
1371
|
+
elif hasattr(result, 'filterdb'):
|
|
1372
|
+
result = result.filterdb
|
|
1373
|
+
elif helper == 'blocked':
|
|
1374
|
+
if hasattr(result, '_blocked'):
|
|
1375
|
+
result = result._blocked
|
|
1376
|
+
elif hasattr(result, 'blocked'):
|
|
1377
|
+
result = result.blocked
|
|
1378
|
+
|
|
1379
|
+
# === DYNAMIC HELPERS ===
|
|
1380
|
+
elif filter_type == 'dynamic':
|
|
1381
|
+
# dynamic::VarName=VALUE - Match if variable equals value
|
|
1382
|
+
var_name = helper
|
|
1383
|
+
var_value = self.scope.get(var_name)
|
|
1384
|
+
if var_value == filter_val:
|
|
1385
|
+
pass # Keep result
|
|
1386
|
+
else:
|
|
1387
|
+
result = None
|
|
1388
|
+
|
|
1389
|
+
# === SQL HELPERS ===
|
|
1390
|
+
elif filter_type == 'sql':
|
|
1391
|
+
if helper == 'data':
|
|
1392
|
+
# Return only SQL-compatible data types
|
|
1393
|
+
if isinstance(result, (int, str, bool, float, list, dict)):
|
|
1394
|
+
pass # Keep result
|
|
1395
|
+
else:
|
|
1396
|
+
result = str(result) # Convert to string
|
|
1397
|
+
|
|
1398
|
+
return result
|
|
1399
|
+
|
|
1400
|
+
def _exec_inject(self, node: ASTNode) -> Any:
|
|
1401
|
+
"""Execute inject operation (<==, +<==, -<==)
|
|
1402
|
+
|
|
1403
|
+
Modes:
|
|
1404
|
+
- replace: target <== source (replace target with source)
|
|
1405
|
+
- add: target +<== source (copy & add to target)
|
|
1406
|
+
- move: target -<== source (move from source, remove from source)
|
|
1407
|
+
"""
|
|
1408
|
+
target = node.value.get('target')
|
|
1409
|
+
source_node = node.value.get('source')
|
|
1410
|
+
mode = node.value.get('mode', 'replace')
|
|
1411
|
+
filter_info = node.value.get('filter')
|
|
1412
|
+
|
|
1413
|
+
# Check if target is a function call (for permanent injection)
|
|
1414
|
+
if isinstance(target, ASTNode) and target.type == 'call':
|
|
1415
|
+
callee = target.value.get('callee')
|
|
1416
|
+
if isinstance(callee, ASTNode) and callee.type == 'identifier':
|
|
1417
|
+
func_name = callee.value
|
|
1418
|
+
self.register_function_injection(func_name, source_node)
|
|
1419
|
+
return None
|
|
1420
|
+
|
|
1421
|
+
# Check if source is an action_block with %<name> captures
|
|
1422
|
+
# If so, capture values NOW and evaluate the block with those captures
|
|
1423
|
+
if isinstance(source_node, ASTNode) and source_node.type == 'action_block':
|
|
1424
|
+
# Scan for %<name> captured references and capture their current values
|
|
1425
|
+
captured_values = self._scan_and_capture_refs(source_node)
|
|
1426
|
+
old_captured = self._current_captured_values.copy()
|
|
1427
|
+
self._current_captured_values = captured_values
|
|
1428
|
+
try:
|
|
1429
|
+
# Execute the action block and get the last expression's value
|
|
1430
|
+
source = self._evaluate_action_block(source_node)
|
|
1431
|
+
finally:
|
|
1432
|
+
self._current_captured_values = old_captured
|
|
1433
|
+
else:
|
|
1434
|
+
# Evaluate source normally
|
|
1435
|
+
source = self._evaluate(source_node)
|
|
1436
|
+
|
|
1437
|
+
# Apply filter if present
|
|
1438
|
+
if filter_info:
|
|
1439
|
+
source = self._apply_injection_filter(source, filter_info)
|
|
1440
|
+
|
|
1441
|
+
# Get current target value for add/move modes
|
|
1442
|
+
current_value = None
|
|
1443
|
+
if mode in ('add', 'move'):
|
|
1444
|
+
try:
|
|
1445
|
+
current_value = self._evaluate(target)
|
|
1446
|
+
except Exception:
|
|
1447
|
+
# Target might not exist yet, that's okay for add mode
|
|
1448
|
+
current_value = None
|
|
1449
|
+
|
|
1450
|
+
# Determine final value based on mode
|
|
1451
|
+
if mode == 'replace':
|
|
1452
|
+
final_value = source
|
|
1453
|
+
elif mode == 'add':
|
|
1454
|
+
# Copy & add - preserve target and add source
|
|
1455
|
+
if isinstance(current_value, list):
|
|
1456
|
+
if isinstance(source, list):
|
|
1457
|
+
final_value = current_value + source
|
|
1458
|
+
else:
|
|
1459
|
+
final_value = current_value + [source]
|
|
1460
|
+
elif isinstance(current_value, dict) and isinstance(source, dict):
|
|
1461
|
+
final_value = {**current_value, **source}
|
|
1462
|
+
elif isinstance(current_value, str) and isinstance(source, str):
|
|
1463
|
+
final_value = current_value + source
|
|
1464
|
+
elif current_value is None:
|
|
1465
|
+
final_value = [source] if not isinstance(source, list) else source
|
|
1466
|
+
else:
|
|
1467
|
+
final_value = [current_value, source]
|
|
1468
|
+
elif mode == 'move':
|
|
1469
|
+
# Move & remove from source
|
|
1470
|
+
final_value = source
|
|
1471
|
+
# Clear the source
|
|
1472
|
+
if isinstance(source_node, ASTNode) and source_node.type == 'identifier':
|
|
1473
|
+
self.scope.set(source_node.value, None)
|
|
1474
|
+
else:
|
|
1475
|
+
final_value = source
|
|
1476
|
+
|
|
1477
|
+
# Set the target
|
|
1478
|
+
if target.type == 'identifier':
|
|
1479
|
+
self.scope.set(target.value, final_value)
|
|
1480
|
+
elif target.type == 'module_ref':
|
|
1481
|
+
self._set_module_value(target.value, final_value)
|
|
1482
|
+
elif target.type == 'shared_ref':
|
|
1483
|
+
# $Name <== value - create/update shared object
|
|
1484
|
+
from ..cssl_bridge import _live_objects, SharedObjectProxy
|
|
1485
|
+
name = target.value
|
|
1486
|
+
_live_objects[name] = final_value
|
|
1487
|
+
self.global_scope.set(f'${name}', SharedObjectProxy(name, final_value))
|
|
1488
|
+
elif target.type == 'member_access':
|
|
1489
|
+
self._set_member(target, final_value)
|
|
1490
|
+
elif target.type == 'call':
|
|
1491
|
+
callee = target.value.get('callee')
|
|
1492
|
+
if isinstance(callee, ASTNode) and callee.type == 'member_access':
|
|
1493
|
+
obj = self._evaluate(callee.value.get('object'))
|
|
1494
|
+
method_name = callee.value.get('member')
|
|
1495
|
+
if method_name == 'add' and isinstance(obj, list):
|
|
1496
|
+
obj.append(final_value)
|
|
1497
|
+
return final_value
|
|
1498
|
+
|
|
1499
|
+
return final_value
|
|
1500
|
+
|
|
1501
|
+
def _exec_receive(self, node: ASTNode) -> Any:
|
|
1502
|
+
"""Execute receive operation (==>, ==>+, -==>)
|
|
1503
|
+
|
|
1504
|
+
Modes:
|
|
1505
|
+
- replace: source ==> target (move source to target, replace)
|
|
1506
|
+
- add: source ==>+ target (copy source to target, add)
|
|
1507
|
+
- move: source -==> target (move from source, remove)
|
|
1508
|
+
"""
|
|
1509
|
+
source_node = node.value.get('source')
|
|
1510
|
+
target = node.value.get('target')
|
|
1511
|
+
mode = node.value.get('mode', 'replace')
|
|
1512
|
+
filter_info = node.value.get('filter')
|
|
1513
|
+
|
|
1514
|
+
# Evaluate source
|
|
1515
|
+
source = self._evaluate(source_node)
|
|
1516
|
+
|
|
1517
|
+
# Apply filter if present
|
|
1518
|
+
if filter_info:
|
|
1519
|
+
source = self._apply_injection_filter(source, filter_info)
|
|
1520
|
+
|
|
1521
|
+
# Get current target value for add mode
|
|
1522
|
+
current_value = None
|
|
1523
|
+
if mode == 'add':
|
|
1524
|
+
current_value = self._evaluate(target)
|
|
1525
|
+
|
|
1526
|
+
# Determine final value based on mode
|
|
1527
|
+
if mode == 'replace':
|
|
1528
|
+
final_value = source
|
|
1529
|
+
elif mode == 'add':
|
|
1530
|
+
if isinstance(current_value, list):
|
|
1531
|
+
if isinstance(source, list):
|
|
1532
|
+
final_value = current_value + source
|
|
1533
|
+
else:
|
|
1534
|
+
final_value = current_value + [source]
|
|
1535
|
+
elif isinstance(current_value, dict) and isinstance(source, dict):
|
|
1536
|
+
final_value = {**current_value, **source}
|
|
1537
|
+
elif isinstance(current_value, str) and isinstance(source, str):
|
|
1538
|
+
final_value = current_value + source
|
|
1539
|
+
elif current_value is None:
|
|
1540
|
+
final_value = [source] if not isinstance(source, list) else source
|
|
1541
|
+
else:
|
|
1542
|
+
final_value = [current_value, source]
|
|
1543
|
+
elif mode == 'move':
|
|
1544
|
+
final_value = source
|
|
1545
|
+
# Clear the source
|
|
1546
|
+
if isinstance(source_node, ASTNode) and source_node.type == 'identifier':
|
|
1547
|
+
self.scope.set(source_node.value, None)
|
|
1548
|
+
else:
|
|
1549
|
+
final_value = source
|
|
1550
|
+
|
|
1551
|
+
# Set the target
|
|
1552
|
+
if target.type == 'identifier':
|
|
1553
|
+
self.scope.set(target.value, final_value)
|
|
1554
|
+
elif target.type == 'module_ref':
|
|
1555
|
+
self._set_module_value(target.value, final_value)
|
|
1556
|
+
elif target.type == 'shared_ref':
|
|
1557
|
+
# value ==> $Name - create/update shared object
|
|
1558
|
+
from ..cssl_bridge import _live_objects, SharedObjectProxy
|
|
1559
|
+
name = target.value
|
|
1560
|
+
_live_objects[name] = final_value
|
|
1561
|
+
self.global_scope.set(f'${name}', SharedObjectProxy(name, final_value))
|
|
1562
|
+
elif target.type == 'instance_ref':
|
|
1563
|
+
# value ==> instance<"name"> - create/update shared object
|
|
1564
|
+
from ..cssl_bridge import _live_objects, SharedObjectProxy
|
|
1565
|
+
name = target.value
|
|
1566
|
+
_live_objects[name] = final_value
|
|
1567
|
+
self.global_scope.set(f'${name}', SharedObjectProxy(name, final_value))
|
|
1568
|
+
elif target.type == 'member_access':
|
|
1569
|
+
self._set_member(target, final_value)
|
|
1570
|
+
|
|
1571
|
+
return final_value
|
|
1572
|
+
|
|
1573
|
+
def _exec_infuse(self, node: ASTNode) -> Any:
|
|
1574
|
+
"""Execute code infusion (<<==, +<<==, -<<==)
|
|
1575
|
+
|
|
1576
|
+
Modes:
|
|
1577
|
+
- replace: func <<== { code } - REPLACES function body (original won't execute)
|
|
1578
|
+
- add: func +<<== { code } - ADDS code to function (both execute)
|
|
1579
|
+
- remove: func -<<== { code } - REMOVES matching code from function
|
|
1580
|
+
|
|
1581
|
+
Also supports expression form: func <<== %exit() (wraps in action_block)
|
|
1582
|
+
"""
|
|
1583
|
+
target = node.value.get('target')
|
|
1584
|
+
code_block = node.value.get('code')
|
|
1585
|
+
source_expr = node.value.get('source') # For expression form: func <<== expr
|
|
1586
|
+
mode = node.value.get('mode', 'replace') # Default is REPLACE for <<==
|
|
1587
|
+
|
|
1588
|
+
# If source expression is provided instead of code block, wrap it
|
|
1589
|
+
if code_block is None and source_expr is not None:
|
|
1590
|
+
# Wrap in expression node so _execute_node can handle it
|
|
1591
|
+
expr_node = ASTNode('expression', value=source_expr)
|
|
1592
|
+
code_block = ASTNode('action_block', children=[expr_node])
|
|
1593
|
+
|
|
1594
|
+
# Get function name from target
|
|
1595
|
+
func_name = None
|
|
1596
|
+
if isinstance(target, ASTNode):
|
|
1597
|
+
if target.type == 'identifier':
|
|
1598
|
+
func_name = target.value
|
|
1599
|
+
elif target.type == 'call':
|
|
1600
|
+
callee = target.value.get('callee')
|
|
1601
|
+
if isinstance(callee, ASTNode) and callee.type == 'identifier':
|
|
1602
|
+
func_name = callee.value
|
|
1603
|
+
|
|
1604
|
+
if not func_name or code_block is None:
|
|
1605
|
+
return None
|
|
1606
|
+
|
|
1607
|
+
if mode == 'add':
|
|
1608
|
+
# +<<== : Add code to function (both injection + original execute)
|
|
1609
|
+
self.register_function_injection(func_name, code_block)
|
|
1610
|
+
self._function_replaced[func_name] = False # Don't replace, just add
|
|
1611
|
+
elif mode == 'replace':
|
|
1612
|
+
# <<== : Replace function body (only injection executes, original skipped)
|
|
1613
|
+
# Save original function BEFORE replacing (for original() access)
|
|
1614
|
+
if func_name not in self._original_functions:
|
|
1615
|
+
# Try to find original in scope or builtins
|
|
1616
|
+
original = self.scope.get(func_name)
|
|
1617
|
+
if original is None:
|
|
1618
|
+
original = getattr(self.builtins, f'builtin_{func_name}', None)
|
|
1619
|
+
if original is not None:
|
|
1620
|
+
self._original_functions[func_name] = original
|
|
1621
|
+
# Capture %<name> references at registration time
|
|
1622
|
+
captured_values = self._scan_and_capture_refs(code_block)
|
|
1623
|
+
self._function_injections[func_name] = [(code_block, captured_values)]
|
|
1624
|
+
self._function_replaced[func_name] = True # Mark as replaced
|
|
1625
|
+
elif mode == 'remove':
|
|
1626
|
+
# -<<== or -<<==[n] : Remove matching code from function body
|
|
1627
|
+
remove_index = node.value.get('index')
|
|
1628
|
+
|
|
1629
|
+
if func_name in self._function_injections:
|
|
1630
|
+
if remove_index is not None:
|
|
1631
|
+
# Indexed removal: -<<==[n] removes only the nth injection
|
|
1632
|
+
if 0 <= remove_index < len(self._function_injections[func_name]):
|
|
1633
|
+
self._function_injections[func_name].pop(remove_index)
|
|
1634
|
+
else:
|
|
1635
|
+
# No index: -<<== removes all injections
|
|
1636
|
+
self._function_injections[func_name] = []
|
|
1637
|
+
self._function_replaced[func_name] = False
|
|
1638
|
+
|
|
1639
|
+
return None
|
|
1640
|
+
|
|
1641
|
+
def _exec_infuse_right(self, node: ASTNode) -> Any:
|
|
1642
|
+
"""Execute right-side code infusion (==>>)"""
|
|
1643
|
+
source = node.value.get('source')
|
|
1644
|
+
target = node.value.get('target')
|
|
1645
|
+
mode = node.value.get('mode', 'replace')
|
|
1646
|
+
|
|
1647
|
+
# Similar to infuse but direction is reversed
|
|
1648
|
+
func_name = None
|
|
1649
|
+
if isinstance(target, ASTNode):
|
|
1650
|
+
if target.type == 'identifier':
|
|
1651
|
+
func_name = target.value
|
|
1652
|
+
elif target.type == 'call':
|
|
1653
|
+
callee = target.value.get('callee')
|
|
1654
|
+
if isinstance(callee, ASTNode) and callee.type == 'identifier':
|
|
1655
|
+
func_name = callee.value
|
|
1656
|
+
|
|
1657
|
+
if func_name and isinstance(source, ASTNode):
|
|
1658
|
+
self.register_function_injection(func_name, source)
|
|
1659
|
+
|
|
1660
|
+
return None
|
|
1661
|
+
|
|
1662
|
+
def _exec_flow(self, node: ASTNode) -> Any:
|
|
1663
|
+
"""Execute flow operation (-> or <-)"""
|
|
1664
|
+
source = self._evaluate(node.value.get('source'))
|
|
1665
|
+
target = node.value.get('target')
|
|
1666
|
+
|
|
1667
|
+
# Flow sends data to a target (could be function call or variable)
|
|
1668
|
+
if target.type == 'call':
|
|
1669
|
+
callee = self._evaluate(target.value.get('callee'))
|
|
1670
|
+
args = [source] + [self._evaluate(a) for a in target.value.get('args', [])]
|
|
1671
|
+
|
|
1672
|
+
if callable(callee):
|
|
1673
|
+
return callee(*args)
|
|
1674
|
+
elif isinstance(callee, ASTNode) and callee.type == 'function':
|
|
1675
|
+
return self._call_function(callee, args)
|
|
1676
|
+
elif target.type == 'identifier':
|
|
1677
|
+
self.scope.set(target.value, source)
|
|
1678
|
+
elif target.type == 'module_ref':
|
|
1679
|
+
self._set_module_value(target.value, source)
|
|
1680
|
+
elif target.type == 'shared_ref':
|
|
1681
|
+
# $Name <== value - create/update shared object
|
|
1682
|
+
from ..cssl_bridge import _live_objects, SharedObjectProxy
|
|
1683
|
+
name = target.value
|
|
1684
|
+
_live_objects[name] = source
|
|
1685
|
+
self.global_scope.set(f'${name}', SharedObjectProxy(name, source))
|
|
1686
|
+
elif target.type == 'member_access':
|
|
1687
|
+
self._set_member(target, source)
|
|
1688
|
+
|
|
1689
|
+
return source
|
|
1690
|
+
|
|
1691
|
+
def _exec_assignment(self, node: ASTNode) -> Any:
|
|
1692
|
+
"""Execute assignment"""
|
|
1693
|
+
target = node.value.get('target')
|
|
1694
|
+
value = self._evaluate(node.value.get('value'))
|
|
1695
|
+
|
|
1696
|
+
if isinstance(target, ASTNode):
|
|
1697
|
+
if target.type == 'identifier':
|
|
1698
|
+
self.scope.set(target.value, value)
|
|
1699
|
+
elif target.type == 'global_ref':
|
|
1700
|
+
# r@Name = value - store in promoted globals
|
|
1701
|
+
self._promoted_globals[target.value] = value
|
|
1702
|
+
self.global_scope.set(target.value, value)
|
|
1703
|
+
elif target.type == 'shared_ref':
|
|
1704
|
+
# $Name = value - create/update shared object
|
|
1705
|
+
from ..cssl_bridge import _live_objects, SharedObjectProxy
|
|
1706
|
+
name = target.value
|
|
1707
|
+
_live_objects[name] = value
|
|
1708
|
+
self.global_scope.set(f'${name}', SharedObjectProxy(name, value))
|
|
1709
|
+
elif target.type == 'module_ref':
|
|
1710
|
+
# @Name = value - store in promoted globals (like global keyword)
|
|
1711
|
+
self._promoted_globals[target.value] = value
|
|
1712
|
+
self.global_scope.set(target.value, value)
|
|
1713
|
+
elif target.type == 'member_access':
|
|
1714
|
+
self._set_member(target, value)
|
|
1715
|
+
elif target.type == 'index_access':
|
|
1716
|
+
self._set_index(target, value)
|
|
1717
|
+
elif target.type == 'this_access':
|
|
1718
|
+
# this->member = value
|
|
1719
|
+
if self._current_instance is None:
|
|
1720
|
+
raise CSSLRuntimeError("'this' used outside of class method context")
|
|
1721
|
+
member = target.value.get('member')
|
|
1722
|
+
self._current_instance.set_member(member, value)
|
|
1723
|
+
elif isinstance(target, str):
|
|
1724
|
+
self.scope.set(target, value)
|
|
1725
|
+
|
|
1726
|
+
return value
|
|
1727
|
+
|
|
1728
|
+
def _exec_expression(self, node: ASTNode) -> Any:
|
|
1729
|
+
"""Execute expression statement"""
|
|
1730
|
+
return self._evaluate(node.value)
|
|
1731
|
+
|
|
1732
|
+
def _exec_await(self, node: ASTNode) -> Any:
|
|
1733
|
+
"""Execute await statement - waits for expression to complete"""
|
|
1734
|
+
# Evaluate the awaited expression
|
|
1735
|
+
# The expression is typically a call like wait_for_booted()
|
|
1736
|
+
result = self._evaluate(node.value)
|
|
1737
|
+
|
|
1738
|
+
# If result is a callable (like a coroutine or future), wait for it
|
|
1739
|
+
if hasattr(result, '__await__'):
|
|
1740
|
+
import asyncio
|
|
1741
|
+
loop = asyncio.get_event_loop()
|
|
1742
|
+
return loop.run_until_complete(result)
|
|
1743
|
+
|
|
1744
|
+
# If result is a boolean condition waiting function, it already handled the waiting
|
|
1745
|
+
return result
|
|
1746
|
+
|
|
1747
|
+
def _exec_then(self, node: ASTNode) -> Any:
|
|
1748
|
+
"""Execute then block"""
|
|
1749
|
+
for child in node.children:
|
|
1750
|
+
self._execute_node(child)
|
|
1751
|
+
return None
|
|
1752
|
+
|
|
1753
|
+
def _exec_else(self, node: ASTNode) -> Any:
|
|
1754
|
+
"""Execute else block"""
|
|
1755
|
+
for child in node.children:
|
|
1756
|
+
self._execute_node(child)
|
|
1757
|
+
return None
|
|
1758
|
+
|
|
1759
|
+
def _exec_try_block(self, node: ASTNode) -> Any:
|
|
1760
|
+
"""Execute try block"""
|
|
1761
|
+
for child in node.children:
|
|
1762
|
+
self._execute_node(child)
|
|
1763
|
+
return None
|
|
1764
|
+
|
|
1765
|
+
def _exec_catch_block(self, node: ASTNode) -> Any:
|
|
1766
|
+
"""Execute catch block"""
|
|
1767
|
+
for child in node.children:
|
|
1768
|
+
self._execute_node(child)
|
|
1769
|
+
return None
|
|
1770
|
+
|
|
1771
|
+
def _evaluate(self, node: Any) -> Any:
|
|
1772
|
+
"""Evaluate an expression node to get its value"""
|
|
1773
|
+
if node is None:
|
|
1774
|
+
return None
|
|
1775
|
+
|
|
1776
|
+
if not isinstance(node, ASTNode):
|
|
1777
|
+
return node
|
|
1778
|
+
|
|
1779
|
+
if node.type == 'literal':
|
|
1780
|
+
value = node.value
|
|
1781
|
+
# NEW: String interpolation - replace <variable> with scope values
|
|
1782
|
+
if isinstance(value, str) and '<' in value and '>' in value:
|
|
1783
|
+
value = self._interpolate_string(value)
|
|
1784
|
+
return value
|
|
1785
|
+
|
|
1786
|
+
# NEW: Type literals (list, dict) - create empty instances
|
|
1787
|
+
if node.type == 'type_literal':
|
|
1788
|
+
type_name = node.value
|
|
1789
|
+
if type_name == 'list':
|
|
1790
|
+
return []
|
|
1791
|
+
elif type_name == 'dict':
|
|
1792
|
+
return {}
|
|
1793
|
+
return None
|
|
1794
|
+
|
|
1795
|
+
if node.type == 'identifier':
|
|
1796
|
+
name = node.value
|
|
1797
|
+
value = self.scope.get(name)
|
|
1798
|
+
# Fallback to global scope
|
|
1799
|
+
if value is None:
|
|
1800
|
+
value = self.global_scope.get(name)
|
|
1801
|
+
# Fallback to promoted globals (from 'global' keyword)
|
|
1802
|
+
if value is None:
|
|
1803
|
+
value = self._promoted_globals.get(name)
|
|
1804
|
+
# Fallback to builtins
|
|
1805
|
+
if value is None and self.builtins.has_function(name):
|
|
1806
|
+
return self.builtins.get_function(name)
|
|
1807
|
+
return value
|
|
1808
|
+
|
|
1809
|
+
if node.type == 'module_ref':
|
|
1810
|
+
# Check modules first, then promoted globals, then scope
|
|
1811
|
+
value = self.get_module(node.value)
|
|
1812
|
+
if value is None:
|
|
1813
|
+
value = self._promoted_globals.get(node.value)
|
|
1814
|
+
if value is None:
|
|
1815
|
+
value = self.global_scope.get(node.value)
|
|
1816
|
+
return value
|
|
1817
|
+
|
|
1818
|
+
if node.type == 'self_ref':
|
|
1819
|
+
# s@<name> reference to global struct
|
|
1820
|
+
return self.get_global_struct(node.value)
|
|
1821
|
+
|
|
1822
|
+
if node.type == 'global_ref':
|
|
1823
|
+
# r@<name> global variable reference
|
|
1824
|
+
# Check promoted globals first, then global scope
|
|
1825
|
+
value = self._promoted_globals.get(node.value)
|
|
1826
|
+
if value is None:
|
|
1827
|
+
value = self.global_scope.get(node.value)
|
|
1828
|
+
return value
|
|
1829
|
+
|
|
1830
|
+
if node.type == 'shared_ref':
|
|
1831
|
+
# $<name> shared object reference
|
|
1832
|
+
# Returns the SharedObjectProxy for live access
|
|
1833
|
+
from ..cssl_bridge import _live_objects, SharedObjectProxy
|
|
1834
|
+
name = node.value
|
|
1835
|
+
if name in _live_objects:
|
|
1836
|
+
return SharedObjectProxy(name, _live_objects[name])
|
|
1837
|
+
# Check if stored in runtime's scope as $name
|
|
1838
|
+
scoped_val = self.global_scope.get(f'${name}')
|
|
1839
|
+
if scoped_val is not None:
|
|
1840
|
+
return scoped_val
|
|
1841
|
+
raise CSSLRuntimeError(f"Shared object '${name}' not found. Use share() to share objects.")
|
|
1842
|
+
|
|
1843
|
+
if node.type == 'captured_ref':
|
|
1844
|
+
# %<name> captured reference - use value captured at infusion registration time
|
|
1845
|
+
name = node.value
|
|
1846
|
+
# First check captured values from current injection context
|
|
1847
|
+
if name in self._current_captured_values:
|
|
1848
|
+
captured_value = self._current_captured_values[name]
|
|
1849
|
+
# Only use captured value if it's not None
|
|
1850
|
+
if captured_value is not None:
|
|
1851
|
+
return captured_value
|
|
1852
|
+
# Fall back to normal resolution if not captured or capture was None
|
|
1853
|
+
value = self.scope.get(name)
|
|
1854
|
+
if value is None:
|
|
1855
|
+
value = self.global_scope.get(name)
|
|
1856
|
+
if value is None:
|
|
1857
|
+
# For critical builtins like 'exit', create direct wrapper
|
|
1858
|
+
if name == 'exit':
|
|
1859
|
+
runtime = self
|
|
1860
|
+
value = lambda code=0, rt=runtime: rt.exit(code)
|
|
1861
|
+
else:
|
|
1862
|
+
value = getattr(self.builtins, f'builtin_{name}', None)
|
|
1863
|
+
if value is None:
|
|
1864
|
+
# Check original functions (for replaced functions)
|
|
1865
|
+
value = self._original_functions.get(name)
|
|
1866
|
+
if value is not None:
|
|
1867
|
+
return value
|
|
1868
|
+
raise CSSLRuntimeError(f"Captured reference '%{name}' not found.")
|
|
1869
|
+
|
|
1870
|
+
if node.type == 'instance_ref':
|
|
1871
|
+
# instance<"name"> - get shared instance by name
|
|
1872
|
+
# Works like $name but with explicit syntax
|
|
1873
|
+
from ..cssl_bridge import _live_objects, SharedObjectProxy
|
|
1874
|
+
name = node.value
|
|
1875
|
+
if name in _live_objects:
|
|
1876
|
+
return SharedObjectProxy(name, _live_objects[name])
|
|
1877
|
+
# Check if stored in runtime's scope
|
|
1878
|
+
scoped_val = self.global_scope.get(f'${name}')
|
|
1879
|
+
if scoped_val is not None:
|
|
1880
|
+
return scoped_val
|
|
1881
|
+
# Return None if instance doesn't exist (can be created via ==>)
|
|
1882
|
+
return None
|
|
1883
|
+
|
|
1884
|
+
if node.type == 'new':
|
|
1885
|
+
# Create new instance of a class: new ClassName(args)
|
|
1886
|
+
return self._eval_new(node)
|
|
1887
|
+
|
|
1888
|
+
if node.type == 'this_access':
|
|
1889
|
+
# this->member access
|
|
1890
|
+
return self._eval_this_access(node)
|
|
1891
|
+
|
|
1892
|
+
if node.type == 'type_instantiation':
|
|
1893
|
+
# Create new instance of a type: stack<string>, vector<int>, map<K,V>, etc.
|
|
1894
|
+
type_name = node.value.get('type')
|
|
1895
|
+
element_type = node.value.get('element_type', 'dynamic')
|
|
1896
|
+
value_type = node.value.get('value_type') # For map<K, V>
|
|
1897
|
+
init_values = node.value.get('init_values') # For inline init: map<K,V>{...}
|
|
1898
|
+
|
|
1899
|
+
if type_name == 'stack':
|
|
1900
|
+
return Stack(element_type)
|
|
1901
|
+
elif type_name == 'vector':
|
|
1902
|
+
return Vector(element_type)
|
|
1903
|
+
elif type_name == 'datastruct':
|
|
1904
|
+
return DataStruct(element_type)
|
|
1905
|
+
elif type_name == 'shuffled':
|
|
1906
|
+
return Shuffled(element_type)
|
|
1907
|
+
elif type_name == 'iterator':
|
|
1908
|
+
return Iterator(element_type)
|
|
1909
|
+
elif type_name == 'combo':
|
|
1910
|
+
return Combo(element_type)
|
|
1911
|
+
elif type_name == 'dataspace':
|
|
1912
|
+
return DataSpace(element_type)
|
|
1913
|
+
elif type_name == 'openquote':
|
|
1914
|
+
return OpenQuote()
|
|
1915
|
+
elif type_name == 'array':
|
|
1916
|
+
return Array(element_type)
|
|
1917
|
+
elif type_name == 'list':
|
|
1918
|
+
return List(element_type)
|
|
1919
|
+
elif type_name in ('dictionary', 'dict'):
|
|
1920
|
+
return Dictionary(element_type)
|
|
1921
|
+
elif type_name == 'map':
|
|
1922
|
+
# Create Map with key_type and value_type
|
|
1923
|
+
m = Map(element_type, value_type or 'dynamic')
|
|
1924
|
+
# If inline initialization provided, populate the map
|
|
1925
|
+
if init_values:
|
|
1926
|
+
for key, value_node in init_values.items():
|
|
1927
|
+
value = self._evaluate(value_node)
|
|
1928
|
+
m.insert(key, value)
|
|
1929
|
+
return m
|
|
1930
|
+
else:
|
|
1931
|
+
return None
|
|
1932
|
+
|
|
1933
|
+
if node.type == 'binary':
|
|
1934
|
+
return self._eval_binary(node)
|
|
1935
|
+
|
|
1936
|
+
if node.type == 'unary':
|
|
1937
|
+
return self._eval_unary(node)
|
|
1938
|
+
|
|
1939
|
+
if node.type == 'call':
|
|
1940
|
+
return self._eval_call(node)
|
|
1941
|
+
|
|
1942
|
+
if node.type == 'typed_call':
|
|
1943
|
+
# Handle OpenFind<type>(args) style calls
|
|
1944
|
+
return self._eval_typed_call(node)
|
|
1945
|
+
|
|
1946
|
+
if node.type == 'member_access':
|
|
1947
|
+
return self._eval_member_access(node)
|
|
1948
|
+
|
|
1949
|
+
if node.type == 'index_access':
|
|
1950
|
+
return self._eval_index_access(node)
|
|
1951
|
+
|
|
1952
|
+
if node.type == 'array':
|
|
1953
|
+
return [self._evaluate(elem) for elem in node.value]
|
|
1954
|
+
|
|
1955
|
+
if node.type == 'object':
|
|
1956
|
+
return {k: self._evaluate(v) for k, v in node.value.items()}
|
|
1957
|
+
|
|
1958
|
+
if node.type == 'reference':
|
|
1959
|
+
# &variable - return a reference object wrapping the actual value
|
|
1960
|
+
inner = node.value
|
|
1961
|
+
if isinstance(inner, ASTNode):
|
|
1962
|
+
if inner.type == 'identifier':
|
|
1963
|
+
# Return a reference wrapper with the variable name
|
|
1964
|
+
return {'__ref__': True, 'name': inner.value, 'value': self.scope.get(inner.value)}
|
|
1965
|
+
elif inner.type == 'module_ref':
|
|
1966
|
+
return {'__ref__': True, 'name': inner.value, 'value': self.get_module(inner.value)}
|
|
1967
|
+
return {'__ref__': True, 'value': self._evaluate(inner)}
|
|
1968
|
+
|
|
1969
|
+
# Handle action_block - execute and return last expression value
|
|
1970
|
+
if node.type == 'action_block':
|
|
1971
|
+
return self._evaluate_action_block(node)
|
|
1972
|
+
|
|
1973
|
+
return None
|
|
1974
|
+
|
|
1975
|
+
def _evaluate_action_block(self, node: ASTNode) -> Any:
|
|
1976
|
+
"""Evaluate an action block and return the last expression's value.
|
|
1977
|
+
|
|
1978
|
+
Used for: v <== { %version; } - captures %version at this moment
|
|
1979
|
+
|
|
1980
|
+
Returns the value of the last expression in the block.
|
|
1981
|
+
If the block contains a captured_ref (%name), that's what gets returned.
|
|
1982
|
+
"""
|
|
1983
|
+
last_value = None
|
|
1984
|
+
for child in node.children:
|
|
1985
|
+
if child.type == 'captured_ref':
|
|
1986
|
+
# Direct captured reference - return its value
|
|
1987
|
+
last_value = self._evaluate(child)
|
|
1988
|
+
elif child.type == 'expression':
|
|
1989
|
+
# Expression statement - evaluate and keep value
|
|
1990
|
+
last_value = self._evaluate(child.value if hasattr(child, 'value') else child)
|
|
1991
|
+
elif child.type == 'identifier':
|
|
1992
|
+
# Just an identifier - evaluate it
|
|
1993
|
+
last_value = self._evaluate(child)
|
|
1994
|
+
elif child.type in ('call', 'member_access', 'binary', 'unary'):
|
|
1995
|
+
# Expression types
|
|
1996
|
+
last_value = self._evaluate(child)
|
|
1997
|
+
else:
|
|
1998
|
+
# Execute other statements
|
|
1999
|
+
result = self._execute_node(child)
|
|
2000
|
+
if result is not None:
|
|
2001
|
+
last_value = result
|
|
2002
|
+
return last_value
|
|
2003
|
+
|
|
2004
|
+
def _eval_binary(self, node: ASTNode) -> Any:
|
|
2005
|
+
"""Evaluate binary operation with auto-casting support"""
|
|
2006
|
+
op = node.value.get('op')
|
|
2007
|
+
left = self._evaluate(node.value.get('left'))
|
|
2008
|
+
right = self._evaluate(node.value.get('right'))
|
|
2009
|
+
|
|
2010
|
+
# === AUTO-CAST FOR STRING OPERATIONS ===
|
|
2011
|
+
if op == '+':
|
|
2012
|
+
# String concatenation with auto-cast
|
|
2013
|
+
if isinstance(left, str) or isinstance(right, str):
|
|
2014
|
+
return str(left if left is not None else '') + str(right if right is not None else '')
|
|
2015
|
+
# List concatenation
|
|
2016
|
+
if isinstance(left, list) and isinstance(right, list):
|
|
2017
|
+
result = type(left)(left._element_type) if hasattr(left, '_element_type') else []
|
|
2018
|
+
if hasattr(result, 'extend'):
|
|
2019
|
+
result.extend(left)
|
|
2020
|
+
result.extend(right)
|
|
2021
|
+
else:
|
|
2022
|
+
result = list(left) + list(right)
|
|
2023
|
+
return result
|
|
2024
|
+
# Numeric addition
|
|
2025
|
+
return (left or 0) + (right or 0)
|
|
2026
|
+
|
|
2027
|
+
if op == '-':
|
|
2028
|
+
return self._to_number(left) - self._to_number(right)
|
|
2029
|
+
|
|
2030
|
+
if op == '*':
|
|
2031
|
+
# String repeat: "abc" * 3 = "abcabcabc"
|
|
2032
|
+
if isinstance(left, str) and isinstance(right, (int, float)):
|
|
2033
|
+
return left * int(right)
|
|
2034
|
+
if isinstance(right, str) and isinstance(left, (int, float)):
|
|
2035
|
+
return right * int(left)
|
|
2036
|
+
return self._to_number(left) * self._to_number(right)
|
|
2037
|
+
|
|
2038
|
+
if op == '/':
|
|
2039
|
+
r = self._to_number(right)
|
|
2040
|
+
return self._to_number(left) / r if r != 0 else 0
|
|
2041
|
+
|
|
2042
|
+
if op == '//':
|
|
2043
|
+
r = self._to_number(right)
|
|
2044
|
+
return self._to_number(left) // r if r != 0 else 0
|
|
2045
|
+
|
|
2046
|
+
if op == '%':
|
|
2047
|
+
r = self._to_number(right)
|
|
2048
|
+
return self._to_number(left) % r if r != 0 else 0
|
|
2049
|
+
|
|
2050
|
+
if op == '**':
|
|
2051
|
+
return self._to_number(left) ** self._to_number(right)
|
|
2052
|
+
|
|
2053
|
+
# === COMPARISON OPERATIONS ===
|
|
2054
|
+
if op == '==':
|
|
2055
|
+
return left == right
|
|
2056
|
+
if op == '!=':
|
|
2057
|
+
return left != right
|
|
2058
|
+
if op == '<':
|
|
2059
|
+
return self._compare(left, right) < 0
|
|
2060
|
+
if op == '>':
|
|
2061
|
+
return self._compare(left, right) > 0
|
|
2062
|
+
if op == '<=':
|
|
2063
|
+
return self._compare(left, right) <= 0
|
|
2064
|
+
if op == '>=':
|
|
2065
|
+
return self._compare(left, right) >= 0
|
|
2066
|
+
|
|
2067
|
+
# === LOGICAL OPERATIONS ===
|
|
2068
|
+
if op == 'and' or op == '&&':
|
|
2069
|
+
return left and right
|
|
2070
|
+
if op == 'or' or op == '||':
|
|
2071
|
+
return left or right
|
|
2072
|
+
|
|
2073
|
+
# === BITWISE OPERATIONS ===
|
|
2074
|
+
if op == '&':
|
|
2075
|
+
return int(left or 0) & int(right or 0)
|
|
2076
|
+
if op == '|':
|
|
2077
|
+
return int(left or 0) | int(right or 0)
|
|
2078
|
+
if op == '^':
|
|
2079
|
+
return int(left or 0) ^ int(right or 0)
|
|
2080
|
+
if op == '<<':
|
|
2081
|
+
return int(left or 0) << int(right or 0)
|
|
2082
|
+
if op == '>>':
|
|
2083
|
+
return int(left or 0) >> int(right or 0)
|
|
2084
|
+
|
|
2085
|
+
# === IN OPERATOR ===
|
|
2086
|
+
if op == 'in':
|
|
2087
|
+
if right is None:
|
|
2088
|
+
return False
|
|
2089
|
+
return left in right
|
|
2090
|
+
|
|
2091
|
+
return None
|
|
2092
|
+
|
|
2093
|
+
def _to_number(self, value: Any) -> Union[int, float]:
|
|
2094
|
+
"""Convert value to number with auto-casting"""
|
|
2095
|
+
if value is None:
|
|
2096
|
+
return 0
|
|
2097
|
+
if isinstance(value, (int, float)):
|
|
2098
|
+
return value
|
|
2099
|
+
if isinstance(value, str):
|
|
2100
|
+
value = value.strip()
|
|
2101
|
+
if not value:
|
|
2102
|
+
return 0
|
|
2103
|
+
try:
|
|
2104
|
+
if '.' in value:
|
|
2105
|
+
return float(value)
|
|
2106
|
+
return int(value)
|
|
2107
|
+
except ValueError:
|
|
2108
|
+
return 0
|
|
2109
|
+
if isinstance(value, bool):
|
|
2110
|
+
return 1 if value else 0
|
|
2111
|
+
if isinstance(value, (list, tuple)):
|
|
2112
|
+
return len(value)
|
|
2113
|
+
return 0
|
|
2114
|
+
|
|
2115
|
+
def _compare(self, left: Any, right: Any) -> int:
|
|
2116
|
+
"""Compare two values with auto-casting, returns -1, 0, or 1"""
|
|
2117
|
+
# Handle None
|
|
2118
|
+
if left is None and right is None:
|
|
2119
|
+
return 0
|
|
2120
|
+
if left is None:
|
|
2121
|
+
return -1
|
|
2122
|
+
if right is None:
|
|
2123
|
+
return 1
|
|
2124
|
+
|
|
2125
|
+
# Both strings - compare as strings
|
|
2126
|
+
if isinstance(left, str) and isinstance(right, str):
|
|
2127
|
+
if left < right:
|
|
2128
|
+
return -1
|
|
2129
|
+
elif left > right:
|
|
2130
|
+
return 1
|
|
2131
|
+
return 0
|
|
2132
|
+
|
|
2133
|
+
# Both numbers - compare as numbers
|
|
2134
|
+
if isinstance(left, (int, float)) and isinstance(right, (int, float)):
|
|
2135
|
+
if left < right:
|
|
2136
|
+
return -1
|
|
2137
|
+
elif left > right:
|
|
2138
|
+
return 1
|
|
2139
|
+
return 0
|
|
2140
|
+
|
|
2141
|
+
# Mixed types - try to convert to numbers
|
|
2142
|
+
try:
|
|
2143
|
+
l = self._to_number(left)
|
|
2144
|
+
r = self._to_number(right)
|
|
2145
|
+
if l < r:
|
|
2146
|
+
return -1
|
|
2147
|
+
elif l > r:
|
|
2148
|
+
return 1
|
|
2149
|
+
return 0
|
|
2150
|
+
except:
|
|
2151
|
+
# Fallback to string comparison
|
|
2152
|
+
l_str = str(left)
|
|
2153
|
+
r_str = str(right)
|
|
2154
|
+
if l_str < r_str:
|
|
2155
|
+
return -1
|
|
2156
|
+
elif l_str > r_str:
|
|
2157
|
+
return 1
|
|
2158
|
+
return 0
|
|
2159
|
+
|
|
2160
|
+
def _eval_unary(self, node: ASTNode) -> Any:
|
|
2161
|
+
"""Evaluate unary operation"""
|
|
2162
|
+
op = node.value.get('op')
|
|
2163
|
+
operand = self._evaluate(node.value.get('operand'))
|
|
2164
|
+
|
|
2165
|
+
if op == 'not':
|
|
2166
|
+
return not operand
|
|
2167
|
+
if op == '-':
|
|
2168
|
+
return -operand
|
|
2169
|
+
|
|
2170
|
+
return None
|
|
2171
|
+
|
|
2172
|
+
def _eval_call(self, node: ASTNode) -> Any:
|
|
2173
|
+
"""Evaluate function call with optional named arguments"""
|
|
2174
|
+
callee_node = node.value.get('callee')
|
|
2175
|
+
args = [self._evaluate(a) for a in node.value.get('args', [])]
|
|
2176
|
+
|
|
2177
|
+
# Evaluate named arguments (kwargs)
|
|
2178
|
+
kwargs_raw = node.value.get('kwargs', {})
|
|
2179
|
+
kwargs = {k: self._evaluate(v) for k, v in kwargs_raw.items()} if kwargs_raw else {}
|
|
2180
|
+
|
|
2181
|
+
# Get function name for injection check FIRST (before evaluating callee)
|
|
2182
|
+
func_name = None
|
|
2183
|
+
if isinstance(callee_node, ASTNode):
|
|
2184
|
+
if callee_node.type == 'identifier':
|
|
2185
|
+
func_name = callee_node.value
|
|
2186
|
+
elif callee_node.type == 'member_access':
|
|
2187
|
+
func_name = callee_node.value.get('member')
|
|
2188
|
+
|
|
2189
|
+
# Check if function has injections
|
|
2190
|
+
has_injections = func_name and func_name in self._function_injections
|
|
2191
|
+
is_replaced = func_name and self._function_replaced.get(func_name, False)
|
|
2192
|
+
|
|
2193
|
+
# If function is FULLY REPLACED (<<==), run injection and skip original
|
|
2194
|
+
# This allows creating new functions via infusion: new_func <<== { ... }
|
|
2195
|
+
if is_replaced:
|
|
2196
|
+
self._execute_function_injections(func_name)
|
|
2197
|
+
return None # Injection ran, don't try to find original
|
|
2198
|
+
|
|
2199
|
+
# Now evaluate the callee (only if not replaced)
|
|
2200
|
+
callee = self._evaluate(callee_node)
|
|
2201
|
+
|
|
2202
|
+
# Execute added injections (+<<==) before original
|
|
2203
|
+
if has_injections and not is_replaced:
|
|
2204
|
+
self._execute_function_injections(func_name)
|
|
2205
|
+
|
|
2206
|
+
# Execute original function
|
|
2207
|
+
if callable(callee):
|
|
2208
|
+
if kwargs:
|
|
2209
|
+
return callee(*args, **kwargs)
|
|
2210
|
+
return callee(*args)
|
|
2211
|
+
|
|
2212
|
+
if isinstance(callee, ASTNode) and callee.type == 'function':
|
|
2213
|
+
return self._call_function(callee, args, kwargs)
|
|
2214
|
+
|
|
2215
|
+
callee_name = callee_node.value if isinstance(callee_node, ASTNode) and hasattr(callee_node, 'value') else str(callee_node)
|
|
2216
|
+
raise CSSLRuntimeError(
|
|
2217
|
+
f"Cannot call '{callee_name}' - it is not a function",
|
|
2218
|
+
node.line,
|
|
2219
|
+
context=f"Type: {type(callee).__name__}",
|
|
2220
|
+
hint=ERROR_HINTS['undefined_function']
|
|
2221
|
+
)
|
|
2222
|
+
|
|
2223
|
+
def _eval_typed_call(self, node: ASTNode) -> Any:
|
|
2224
|
+
"""Evaluate typed function call like OpenFind<string>(0)"""
|
|
2225
|
+
name = node.value.get('name')
|
|
2226
|
+
type_param = node.value.get('type_param', 'dynamic')
|
|
2227
|
+
args = [self._evaluate(a) for a in node.value.get('args', [])]
|
|
2228
|
+
|
|
2229
|
+
# Handle OpenFind<type>(index)
|
|
2230
|
+
if name == 'OpenFind':
|
|
2231
|
+
# OpenFind searches for a value of the specified type
|
|
2232
|
+
# from the open parameters in scope
|
|
2233
|
+
open_params = self.scope.get('Params') or []
|
|
2234
|
+
index = args[0] if args else 0
|
|
2235
|
+
|
|
2236
|
+
# Search for value of matching type at or near the index
|
|
2237
|
+
type_map = {
|
|
2238
|
+
'string': str, 'str': str,
|
|
2239
|
+
'int': int, 'integer': int,
|
|
2240
|
+
'float': float, 'double': float,
|
|
2241
|
+
'bool': bool, 'boolean': bool,
|
|
2242
|
+
'list': list, 'array': list,
|
|
2243
|
+
'dict': dict, 'json': dict,
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
target_type = type_map.get(type_param.lower())
|
|
2247
|
+
|
|
2248
|
+
if isinstance(open_params, (list, tuple)):
|
|
2249
|
+
# Find first matching type starting from index
|
|
2250
|
+
for i in range(index, len(open_params)):
|
|
2251
|
+
if target_type is None or isinstance(open_params[i], target_type):
|
|
2252
|
+
return open_params[i]
|
|
2253
|
+
# Also search before index
|
|
2254
|
+
for i in range(0, min(index, len(open_params))):
|
|
2255
|
+
if target_type is None or isinstance(open_params[i], target_type):
|
|
2256
|
+
return open_params[i]
|
|
2257
|
+
|
|
2258
|
+
return None
|
|
2259
|
+
|
|
2260
|
+
# Fallback: call as regular function with type hint
|
|
2261
|
+
func = self.builtins.get_function(name)
|
|
2262
|
+
if func and callable(func):
|
|
2263
|
+
return func(type_param, *args)
|
|
2264
|
+
|
|
2265
|
+
raise CSSLRuntimeError(
|
|
2266
|
+
f"Unknown typed function: {name}<{type_param}>",
|
|
2267
|
+
node.line,
|
|
2268
|
+
context=f"Available typed functions: OpenFind<type>",
|
|
2269
|
+
hint="Typed functions use format: FunctionName<Type>(args)"
|
|
2270
|
+
)
|
|
2271
|
+
|
|
2272
|
+
def _eval_new(self, node: ASTNode) -> CSSLInstance:
|
|
2273
|
+
"""Evaluate 'new ClassName(args)' expression.
|
|
2274
|
+
|
|
2275
|
+
Creates a new instance of a CSSL class and calls its constructor.
|
|
2276
|
+
"""
|
|
2277
|
+
class_name = node.value.get('class')
|
|
2278
|
+
args = [self._evaluate(arg) for arg in node.value.get('args', [])]
|
|
2279
|
+
kwargs = {k: self._evaluate(v) for k, v in node.value.get('kwargs', {}).items()}
|
|
2280
|
+
|
|
2281
|
+
# Get class definition from scope
|
|
2282
|
+
class_def = self.scope.get(class_name)
|
|
2283
|
+
if class_def is None:
|
|
2284
|
+
class_def = self.global_scope.get(class_name)
|
|
2285
|
+
|
|
2286
|
+
if class_def is None:
|
|
2287
|
+
raise CSSLRuntimeError(
|
|
2288
|
+
f"Class '{class_name}' not found",
|
|
2289
|
+
node.line,
|
|
2290
|
+
hint="Make sure the class is defined before instantiation"
|
|
2291
|
+
)
|
|
2292
|
+
|
|
2293
|
+
if not isinstance(class_def, CSSLClass):
|
|
2294
|
+
raise CSSLRuntimeError(
|
|
2295
|
+
f"'{class_name}' is not a class",
|
|
2296
|
+
node.line,
|
|
2297
|
+
hint=f"'{class_name}' is of type {type(class_def).__name__}"
|
|
2298
|
+
)
|
|
2299
|
+
|
|
2300
|
+
# Create new instance
|
|
2301
|
+
instance = CSSLInstance(class_def)
|
|
2302
|
+
|
|
2303
|
+
# Call constructor if defined
|
|
2304
|
+
if class_def.constructor:
|
|
2305
|
+
self._call_method(instance, class_def.constructor, args, kwargs)
|
|
2306
|
+
|
|
2307
|
+
return instance
|
|
2308
|
+
|
|
2309
|
+
def _eval_this_access(self, node: ASTNode) -> Any:
|
|
2310
|
+
"""Evaluate 'this->member' access.
|
|
2311
|
+
|
|
2312
|
+
Returns the value of a member from the current class instance.
|
|
2313
|
+
"""
|
|
2314
|
+
if self._current_instance is None:
|
|
2315
|
+
raise CSSLRuntimeError(
|
|
2316
|
+
"'this' used outside of class method context",
|
|
2317
|
+
node.line if hasattr(node, 'line') else 0,
|
|
2318
|
+
hint="'this->' can only be used inside class methods"
|
|
2319
|
+
)
|
|
2320
|
+
|
|
2321
|
+
member = node.value.get('member')
|
|
2322
|
+
|
|
2323
|
+
# Check if it's a chained access (this->a->b)
|
|
2324
|
+
if 'object' in node.value:
|
|
2325
|
+
# First evaluate the object part
|
|
2326
|
+
obj = self._evaluate(node.value.get('object'))
|
|
2327
|
+
if obj is None:
|
|
2328
|
+
return None
|
|
2329
|
+
if hasattr(obj, member):
|
|
2330
|
+
return getattr(obj, member)
|
|
2331
|
+
if isinstance(obj, dict):
|
|
2332
|
+
return obj.get(member)
|
|
2333
|
+
return None
|
|
2334
|
+
|
|
2335
|
+
# Direct this->member access
|
|
2336
|
+
instance = self._current_instance
|
|
2337
|
+
|
|
2338
|
+
# Check if it's a member variable
|
|
2339
|
+
if instance.has_member(member):
|
|
2340
|
+
return instance.get_member(member)
|
|
2341
|
+
|
|
2342
|
+
# Check if it's a method
|
|
2343
|
+
if instance.has_method(member):
|
|
2344
|
+
# Return a callable that will invoke the method with instance context
|
|
2345
|
+
method_node = instance.get_method(member)
|
|
2346
|
+
return lambda *args, **kwargs: self._call_method(instance, method_node, list(args), kwargs)
|
|
2347
|
+
|
|
2348
|
+
raise CSSLRuntimeError(
|
|
2349
|
+
f"'{instance._class.name}' has no member or method '{member}'",
|
|
2350
|
+
node.line if hasattr(node, 'line') else 0
|
|
2351
|
+
)
|
|
2352
|
+
|
|
2353
|
+
def _call_method(self, instance: CSSLInstance, method_node: ASTNode, args: list, kwargs: dict = None) -> Any:
|
|
2354
|
+
"""Call a method on an instance with 'this' context.
|
|
2355
|
+
|
|
2356
|
+
Sets up the instance as the current 'this' context and executes the method.
|
|
2357
|
+
"""
|
|
2358
|
+
kwargs = kwargs or {}
|
|
2359
|
+
func_info = method_node.value
|
|
2360
|
+
params = func_info.get('params', [])
|
|
2361
|
+
modifiers = func_info.get('modifiers', [])
|
|
2362
|
+
|
|
2363
|
+
# Check for undefined modifier
|
|
2364
|
+
is_undefined = 'undefined' in modifiers
|
|
2365
|
+
|
|
2366
|
+
# Create new scope for method
|
|
2367
|
+
new_scope = Scope(parent=self.scope)
|
|
2368
|
+
|
|
2369
|
+
# Bind parameters
|
|
2370
|
+
for i, param in enumerate(params):
|
|
2371
|
+
param_name = param['name'] if isinstance(param, dict) else param
|
|
2372
|
+
|
|
2373
|
+
if param_name in kwargs:
|
|
2374
|
+
new_scope.set(param_name, kwargs[param_name])
|
|
2375
|
+
elif i < len(args):
|
|
2376
|
+
new_scope.set(param_name, args[i])
|
|
2377
|
+
else:
|
|
2378
|
+
new_scope.set(param_name, None)
|
|
2379
|
+
|
|
2380
|
+
# Save current state
|
|
2381
|
+
old_scope = self.scope
|
|
2382
|
+
old_instance = self._current_instance
|
|
2383
|
+
|
|
2384
|
+
# Set up method context
|
|
2385
|
+
self.scope = new_scope
|
|
2386
|
+
self._current_instance = instance
|
|
2387
|
+
|
|
2388
|
+
try:
|
|
2389
|
+
for child in method_node.children:
|
|
2390
|
+
if not self._running:
|
|
2391
|
+
break
|
|
2392
|
+
self._execute_node(child)
|
|
2393
|
+
except CSSLReturn as ret:
|
|
2394
|
+
return ret.value
|
|
2395
|
+
except Exception as e:
|
|
2396
|
+
if is_undefined:
|
|
2397
|
+
return None
|
|
2398
|
+
raise
|
|
2399
|
+
finally:
|
|
2400
|
+
# Restore previous state
|
|
2401
|
+
self.scope = old_scope
|
|
2402
|
+
self._current_instance = old_instance
|
|
2403
|
+
|
|
2404
|
+
return None
|
|
2405
|
+
|
|
2406
|
+
def _eval_member_access(self, node: ASTNode) -> Any:
|
|
2407
|
+
"""Evaluate member access"""
|
|
2408
|
+
obj = self._evaluate(node.value.get('object'))
|
|
2409
|
+
member = node.value.get('member')
|
|
2410
|
+
|
|
2411
|
+
if obj is None:
|
|
2412
|
+
return None
|
|
2413
|
+
|
|
2414
|
+
# Special handling for Parameter.return() -> Parameter.return_()
|
|
2415
|
+
# since 'return' is a Python keyword
|
|
2416
|
+
if isinstance(obj, Parameter) and member == 'return':
|
|
2417
|
+
member = 'return_'
|
|
2418
|
+
|
|
2419
|
+
# === CSSL CLASS INSTANCE METHODS ===
|
|
2420
|
+
if isinstance(obj, CSSLInstance):
|
|
2421
|
+
# Check for member variable
|
|
2422
|
+
if obj.has_member(member):
|
|
2423
|
+
return obj.get_member(member)
|
|
2424
|
+
# Check for method
|
|
2425
|
+
if obj.has_method(member):
|
|
2426
|
+
method_node = obj.get_method(member)
|
|
2427
|
+
return lambda *args, **kwargs: self._call_method(obj, method_node, list(args), kwargs)
|
|
2428
|
+
raise CSSLRuntimeError(f"'{obj._class.name}' has no member or method '{member}'")
|
|
2429
|
+
|
|
2430
|
+
# === STRING METHODS ===
|
|
2431
|
+
if isinstance(obj, str):
|
|
2432
|
+
string_methods = self._get_string_method(obj, member)
|
|
2433
|
+
if string_methods is not None:
|
|
2434
|
+
return string_methods
|
|
2435
|
+
|
|
2436
|
+
# === LIST/ARRAY METHODS for plain lists ===
|
|
2437
|
+
if isinstance(obj, list) and not isinstance(obj, (Stack, Vector, Array)):
|
|
2438
|
+
list_methods = self._get_list_method(obj, member)
|
|
2439
|
+
if list_methods is not None:
|
|
2440
|
+
return list_methods
|
|
2441
|
+
|
|
2442
|
+
if hasattr(obj, member):
|
|
2443
|
+
return getattr(obj, member)
|
|
2444
|
+
|
|
2445
|
+
if isinstance(obj, dict):
|
|
2446
|
+
return obj.get(member)
|
|
2447
|
+
|
|
2448
|
+
return None
|
|
2449
|
+
|
|
2450
|
+
def _get_string_method(self, s: str, method: str) -> Any:
|
|
2451
|
+
"""Get string method implementation for CSSL.
|
|
2452
|
+
|
|
2453
|
+
Provides C++/Java/JS style string methods that Python doesn't have.
|
|
2454
|
+
"""
|
|
2455
|
+
# === C++/Java/JS STRING METHODS ===
|
|
2456
|
+
if method == 'contains':
|
|
2457
|
+
return lambda substr: substr in s
|
|
2458
|
+
elif method == 'indexOf':
|
|
2459
|
+
return lambda substr, start=0: s.find(substr, start)
|
|
2460
|
+
elif method == 'lastIndexOf':
|
|
2461
|
+
return lambda substr: s.rfind(substr)
|
|
2462
|
+
elif method == 'charAt':
|
|
2463
|
+
return lambda index: s[index] if 0 <= index < len(s) else ''
|
|
2464
|
+
elif method == 'charCodeAt':
|
|
2465
|
+
return lambda index: ord(s[index]) if 0 <= index < len(s) else -1
|
|
2466
|
+
elif method == 'substring':
|
|
2467
|
+
return lambda start, end=None: s[start:end] if end else s[start:]
|
|
2468
|
+
elif method == 'substr':
|
|
2469
|
+
return lambda start, length=None: s[start:start+length] if length else s[start:]
|
|
2470
|
+
elif method == 'slice':
|
|
2471
|
+
return lambda start, end=None: s[start:end] if end else s[start:]
|
|
2472
|
+
|
|
2473
|
+
# === TRIM METHODS ===
|
|
2474
|
+
elif method == 'trim':
|
|
2475
|
+
return lambda: s.strip()
|
|
2476
|
+
elif method == 'trimStart' or method == 'trimLeft' or method == 'ltrim':
|
|
2477
|
+
return lambda: s.lstrip()
|
|
2478
|
+
elif method == 'trimEnd' or method == 'trimRight' or method == 'rtrim':
|
|
2479
|
+
return lambda: s.rstrip()
|
|
2480
|
+
|
|
2481
|
+
# === CASE METHODS ===
|
|
2482
|
+
elif method in ('toUpperCase', 'toUpper', 'upper'):
|
|
2483
|
+
return lambda: s.upper()
|
|
2484
|
+
elif method in ('toLowerCase', 'toLower', 'lower'):
|
|
2485
|
+
return lambda: s.lower()
|
|
2486
|
+
elif method == 'capitalize':
|
|
2487
|
+
return lambda: s.capitalize()
|
|
2488
|
+
elif method == 'title':
|
|
2489
|
+
return lambda: s.title()
|
|
2490
|
+
elif method == 'swapcase':
|
|
2491
|
+
return lambda: s.swapcase()
|
|
2492
|
+
|
|
2493
|
+
# === REPLACE METHODS ===
|
|
2494
|
+
elif method == 'replaceAll':
|
|
2495
|
+
return lambda old, new: s.replace(old, new)
|
|
2496
|
+
elif method == 'replaceFirst':
|
|
2497
|
+
return lambda old, new: s.replace(old, new, 1)
|
|
2498
|
+
|
|
2499
|
+
# === CHECK METHODS ===
|
|
2500
|
+
elif method == 'isEmpty':
|
|
2501
|
+
return lambda: len(s) == 0
|
|
2502
|
+
elif method == 'isBlank':
|
|
2503
|
+
return lambda: len(s.strip()) == 0
|
|
2504
|
+
elif method == 'isDigit' or method == 'isNumeric':
|
|
2505
|
+
return lambda: s.isdigit()
|
|
2506
|
+
elif method == 'isAlpha':
|
|
2507
|
+
return lambda: s.isalpha()
|
|
2508
|
+
elif method == 'isAlphaNumeric' or method == 'isAlnum':
|
|
2509
|
+
return lambda: s.isalnum()
|
|
2510
|
+
elif method == 'isSpace' or method == 'isWhitespace':
|
|
2511
|
+
return lambda: s.isspace()
|
|
2512
|
+
elif method == 'isUpper':
|
|
2513
|
+
return lambda: s.isupper()
|
|
2514
|
+
elif method == 'isLower':
|
|
2515
|
+
return lambda: s.islower()
|
|
2516
|
+
|
|
2517
|
+
# === STARTS/ENDS WITH ===
|
|
2518
|
+
elif method == 'startsWith' or method == 'startswith':
|
|
2519
|
+
return lambda prefix: s.startswith(prefix)
|
|
2520
|
+
elif method == 'endsWith' or method == 'endswith':
|
|
2521
|
+
return lambda suffix: s.endswith(suffix)
|
|
2522
|
+
|
|
2523
|
+
# === LENGTH/SIZE ===
|
|
2524
|
+
elif method == 'length' or method == 'size':
|
|
2525
|
+
return lambda: len(s)
|
|
2526
|
+
|
|
2527
|
+
# === SPLIT/JOIN ===
|
|
2528
|
+
elif method == 'toArray':
|
|
2529
|
+
return lambda sep=None: list(s.split(sep) if sep else list(s))
|
|
2530
|
+
elif method == 'lines':
|
|
2531
|
+
return lambda: s.splitlines()
|
|
2532
|
+
elif method == 'words':
|
|
2533
|
+
return lambda: s.split()
|
|
2534
|
+
|
|
2535
|
+
# === PADDING ===
|
|
2536
|
+
elif method == 'padStart' or method == 'padLeft' or method == 'lpad':
|
|
2537
|
+
return lambda width, char=' ': s.rjust(width, char[0] if char else ' ')
|
|
2538
|
+
elif method == 'padEnd' or method == 'padRight' or method == 'rpad':
|
|
2539
|
+
return lambda width, char=' ': s.ljust(width, char[0] if char else ' ')
|
|
2540
|
+
elif method == 'center':
|
|
2541
|
+
return lambda width, char=' ': s.center(width, char[0] if char else ' ')
|
|
2542
|
+
elif method == 'zfill':
|
|
2543
|
+
return lambda width: s.zfill(width)
|
|
2544
|
+
|
|
2545
|
+
# === REPEAT ===
|
|
2546
|
+
elif method == 'repeat':
|
|
2547
|
+
return lambda n: s * n
|
|
2548
|
+
|
|
2549
|
+
# === REVERSE ===
|
|
2550
|
+
elif method == 'reverse':
|
|
2551
|
+
return lambda: s[::-1]
|
|
2552
|
+
|
|
2553
|
+
# === FORMAT ===
|
|
2554
|
+
elif method == 'format':
|
|
2555
|
+
return lambda *args, **kwargs: s.format(*args, **kwargs)
|
|
2556
|
+
|
|
2557
|
+
# === ENCODING ===
|
|
2558
|
+
elif method == 'encode':
|
|
2559
|
+
return lambda encoding='utf-8': s.encode(encoding)
|
|
2560
|
+
elif method == 'bytes':
|
|
2561
|
+
return lambda encoding='utf-8': list(s.encode(encoding))
|
|
2562
|
+
|
|
2563
|
+
# === NUMERIC CONVERSION ===
|
|
2564
|
+
elif method == 'toInt' or method == 'toInteger':
|
|
2565
|
+
return lambda base=10: int(s, base) if s.lstrip('-').isdigit() else 0
|
|
2566
|
+
elif method == 'toFloat' or method == 'toDouble':
|
|
2567
|
+
try:
|
|
2568
|
+
return lambda: float(s)
|
|
2569
|
+
except:
|
|
2570
|
+
return lambda: 0.0
|
|
2571
|
+
elif method == 'toBool':
|
|
2572
|
+
return lambda: s.lower() in ('true', '1', 'yes', 'on')
|
|
2573
|
+
|
|
2574
|
+
# === C++ ITERATOR STYLE ===
|
|
2575
|
+
elif method == 'begin':
|
|
2576
|
+
return lambda: 0
|
|
2577
|
+
elif method == 'end':
|
|
2578
|
+
return lambda: len(s)
|
|
2579
|
+
|
|
2580
|
+
# Return None if not a string method
|
|
2581
|
+
return None
|
|
2582
|
+
|
|
2583
|
+
def _get_list_method(self, lst: list, method: str) -> Any:
|
|
2584
|
+
"""Get list method implementation for plain Python lists in CSSL."""
|
|
2585
|
+
if method == 'contains':
|
|
2586
|
+
return lambda item: item in lst
|
|
2587
|
+
elif method == 'indexOf':
|
|
2588
|
+
def index_of(item):
|
|
2589
|
+
try:
|
|
2590
|
+
return lst.index(item)
|
|
2591
|
+
except ValueError:
|
|
2592
|
+
return -1
|
|
2593
|
+
return index_of
|
|
2594
|
+
elif method == 'lastIndexOf':
|
|
2595
|
+
def last_index_of(item):
|
|
2596
|
+
for i in range(len(lst) - 1, -1, -1):
|
|
2597
|
+
if lst[i] == item:
|
|
2598
|
+
return i
|
|
2599
|
+
return -1
|
|
2600
|
+
return last_index_of
|
|
2601
|
+
elif method == 'length' or method == 'size':
|
|
2602
|
+
return lambda: len(lst)
|
|
2603
|
+
elif method == 'isEmpty':
|
|
2604
|
+
return lambda: len(lst) == 0
|
|
2605
|
+
elif method == 'first':
|
|
2606
|
+
return lambda: lst[0] if lst else None
|
|
2607
|
+
elif method == 'last':
|
|
2608
|
+
return lambda: lst[-1] if lst else None
|
|
2609
|
+
elif method == 'at':
|
|
2610
|
+
return lambda i: lst[i] if 0 <= i < len(lst) else None
|
|
2611
|
+
elif method == 'slice':
|
|
2612
|
+
return lambda start, end=None: lst[start:end] if end else lst[start:]
|
|
2613
|
+
elif method == 'join':
|
|
2614
|
+
return lambda sep=',': sep.join(str(x) for x in lst)
|
|
2615
|
+
elif method == 'find':
|
|
2616
|
+
def find_item(val):
|
|
2617
|
+
for item in lst:
|
|
2618
|
+
if item == val:
|
|
2619
|
+
return item
|
|
2620
|
+
return None
|
|
2621
|
+
return find_item
|
|
2622
|
+
elif method == 'push':
|
|
2623
|
+
def push_item(item):
|
|
2624
|
+
lst.append(item)
|
|
2625
|
+
return lst
|
|
2626
|
+
return push_item
|
|
2627
|
+
elif method == 'push_back':
|
|
2628
|
+
def push_back_item(item):
|
|
2629
|
+
lst.append(item)
|
|
2630
|
+
return lst
|
|
2631
|
+
return push_back_item
|
|
2632
|
+
elif method == 'toArray':
|
|
2633
|
+
return lambda: list(lst)
|
|
2634
|
+
# === C++ ITERATOR STYLE ===
|
|
2635
|
+
elif method == 'begin':
|
|
2636
|
+
return lambda: 0
|
|
2637
|
+
elif method == 'end':
|
|
2638
|
+
return lambda: len(lst)
|
|
2639
|
+
|
|
2640
|
+
return None
|
|
2641
|
+
|
|
2642
|
+
def _eval_index_access(self, node: ASTNode) -> Any:
|
|
2643
|
+
"""Evaluate index access"""
|
|
2644
|
+
obj = self._evaluate(node.value.get('object'))
|
|
2645
|
+
index = self._evaluate(node.value.get('index'))
|
|
2646
|
+
|
|
2647
|
+
if obj is None:
|
|
2648
|
+
return None
|
|
2649
|
+
|
|
2650
|
+
try:
|
|
2651
|
+
return obj[index]
|
|
2652
|
+
except (IndexError, KeyError, TypeError):
|
|
2653
|
+
return None
|
|
2654
|
+
|
|
2655
|
+
def _set_member(self, node: ASTNode, value: Any):
|
|
2656
|
+
"""Set member value"""
|
|
2657
|
+
obj = self._evaluate(node.value.get('object'))
|
|
2658
|
+
member = node.value.get('member')
|
|
2659
|
+
|
|
2660
|
+
if obj is None:
|
|
2661
|
+
return
|
|
2662
|
+
|
|
2663
|
+
# Check for CSSLInstance - use set_member method
|
|
2664
|
+
if isinstance(obj, CSSLInstance):
|
|
2665
|
+
obj.set_member(member, value)
|
|
2666
|
+
return
|
|
2667
|
+
|
|
2668
|
+
# Check for SharedObjectProxy - directly access underlying object
|
|
2669
|
+
# This is more robust than relying on the proxy's __setattr__
|
|
2670
|
+
if hasattr(obj, '_direct_object') and hasattr(obj, '_name'):
|
|
2671
|
+
# This is a SharedObjectProxy - get the real object directly
|
|
2672
|
+
real_obj = object.__getattribute__(obj, '_direct_object')
|
|
2673
|
+
if real_obj is None:
|
|
2674
|
+
# Fallback to _live_objects registry
|
|
2675
|
+
name = object.__getattribute__(obj, '_name')
|
|
2676
|
+
from ..cssl_bridge import _live_objects
|
|
2677
|
+
real_obj = _live_objects.get(name)
|
|
2678
|
+
if real_obj is not None:
|
|
2679
|
+
setattr(real_obj, member, value)
|
|
2680
|
+
return
|
|
2681
|
+
|
|
2682
|
+
if hasattr(obj, member):
|
|
2683
|
+
setattr(obj, member, value)
|
|
2684
|
+
elif isinstance(obj, dict):
|
|
2685
|
+
obj[member] = value
|
|
2686
|
+
else:
|
|
2687
|
+
# Try setattr anyway for objects that support dynamic attributes
|
|
2688
|
+
try:
|
|
2689
|
+
setattr(obj, member, value)
|
|
2690
|
+
except (AttributeError, TypeError):
|
|
2691
|
+
pass
|
|
2692
|
+
|
|
2693
|
+
def _set_index(self, node: ASTNode, value: Any):
|
|
2694
|
+
"""Set index value"""
|
|
2695
|
+
obj = self._evaluate(node.value.get('object'))
|
|
2696
|
+
index = self._evaluate(node.value.get('index'))
|
|
2697
|
+
|
|
2698
|
+
if obj is not None:
|
|
2699
|
+
try:
|
|
2700
|
+
obj[index] = value
|
|
2701
|
+
except (IndexError, KeyError, TypeError):
|
|
2702
|
+
pass
|
|
2703
|
+
|
|
2704
|
+
def _set_module_value(self, path: str, value: Any):
|
|
2705
|
+
"""Set a value on a module path"""
|
|
2706
|
+
parts = path.split('.')
|
|
2707
|
+
if len(parts) < 2:
|
|
2708
|
+
return
|
|
2709
|
+
|
|
2710
|
+
obj = self._modules.get(parts[0])
|
|
2711
|
+
if obj is None:
|
|
2712
|
+
return
|
|
2713
|
+
|
|
2714
|
+
for part in parts[1:-1]:
|
|
2715
|
+
if hasattr(obj, part):
|
|
2716
|
+
obj = getattr(obj, part)
|
|
2717
|
+
elif isinstance(obj, dict):
|
|
2718
|
+
obj = obj.get(part)
|
|
2719
|
+
else:
|
|
2720
|
+
return
|
|
2721
|
+
|
|
2722
|
+
final_attr = parts[-1]
|
|
2723
|
+
if hasattr(obj, final_attr):
|
|
2724
|
+
setattr(obj, final_attr, value)
|
|
2725
|
+
elif isinstance(obj, dict):
|
|
2726
|
+
obj[final_attr] = value
|
|
2727
|
+
|
|
2728
|
+
# NEW: String interpolation
|
|
2729
|
+
def _interpolate_string(self, string: str) -> str:
|
|
2730
|
+
"""Replace <variable> placeholders with values from scope - NEW
|
|
2731
|
+
|
|
2732
|
+
Example: "Hello <name>!" becomes "Hello John!" if name = "John"
|
|
2733
|
+
"""
|
|
2734
|
+
import re
|
|
2735
|
+
pattern = r'<([A-Za-z_][A-Za-z0-9_]*)>'
|
|
2736
|
+
|
|
2737
|
+
def replacer(match):
|
|
2738
|
+
var_name = match.group(1)
|
|
2739
|
+
# Try scope first
|
|
2740
|
+
value = self.scope.get(var_name)
|
|
2741
|
+
# Try promoted globals
|
|
2742
|
+
if value is None:
|
|
2743
|
+
value = self._promoted_globals.get(var_name)
|
|
2744
|
+
# Try modules
|
|
2745
|
+
if value is None:
|
|
2746
|
+
value = self._modules.get(var_name)
|
|
2747
|
+
# Return string representation or empty string if None
|
|
2748
|
+
return str(value) if value is not None else ''
|
|
2749
|
+
|
|
2750
|
+
return re.sub(pattern, replacer, string)
|
|
2751
|
+
|
|
2752
|
+
# NEW: Promote variable to global scope via global()
|
|
2753
|
+
def promote_to_global(self, s_ref_name: str):
|
|
2754
|
+
"""Promote s@<name> to @<name> (make globally accessible) - NEW
|
|
2755
|
+
|
|
2756
|
+
Example: global(s@cache) makes @cache available
|
|
2757
|
+
"""
|
|
2758
|
+
# Extract the base name from s@<path>
|
|
2759
|
+
parts = s_ref_name.split('.')
|
|
2760
|
+
base_name = parts[0]
|
|
2761
|
+
|
|
2762
|
+
# Get the value from global structs
|
|
2763
|
+
value = self.get_global_struct(s_ref_name)
|
|
2764
|
+
|
|
2765
|
+
if value is not None:
|
|
2766
|
+
# Register as module reference
|
|
2767
|
+
self._modules[base_name] = value
|
|
2768
|
+
# Also store in promoted globals for string interpolation
|
|
2769
|
+
self._promoted_globals[base_name] = value
|
|
2770
|
+
|
|
2771
|
+
# NEW: Scan for captured_ref nodes and capture their current values
|
|
2772
|
+
def _scan_and_capture_refs(self, node: ASTNode) -> Dict[str, Any]:
|
|
2773
|
+
"""Scan AST for %<name> captured references and capture their current values.
|
|
2774
|
+
|
|
2775
|
+
This is called at infusion registration time to capture values.
|
|
2776
|
+
Example: old_exit <<== { %exit(); } captures 'exit' at definition time.
|
|
2777
|
+
"""
|
|
2778
|
+
captured = {}
|
|
2779
|
+
|
|
2780
|
+
def scan_node(n):
|
|
2781
|
+
if not isinstance(n, ASTNode):
|
|
2782
|
+
return
|
|
2783
|
+
|
|
2784
|
+
# Found a captured_ref - capture its current value
|
|
2785
|
+
if n.type == 'captured_ref':
|
|
2786
|
+
name = n.value
|
|
2787
|
+
if name not in captured:
|
|
2788
|
+
# Try to find value - check multiple sources
|
|
2789
|
+
value = None
|
|
2790
|
+
|
|
2791
|
+
# 1. Check _original_functions first (for functions that were JUST replaced)
|
|
2792
|
+
if value is None:
|
|
2793
|
+
value = self._original_functions.get(name)
|
|
2794
|
+
|
|
2795
|
+
# 2. Check scope
|
|
2796
|
+
if value is None:
|
|
2797
|
+
value = self.scope.get(name)
|
|
2798
|
+
|
|
2799
|
+
# 3. Check global_scope
|
|
2800
|
+
if value is None:
|
|
2801
|
+
value = self.global_scope.get(name)
|
|
2802
|
+
|
|
2803
|
+
# 4. Check builtins (most common case for exit, print, etc.)
|
|
2804
|
+
if value is None:
|
|
2805
|
+
# For critical builtins like 'exit', create a direct wrapper
|
|
2806
|
+
# that captures the runtime reference to ensure correct behavior
|
|
2807
|
+
if name == 'exit':
|
|
2808
|
+
runtime = self # Capture runtime in closure
|
|
2809
|
+
value = lambda code=0, rt=runtime: rt.exit(code)
|
|
2810
|
+
else:
|
|
2811
|
+
value = getattr(self.builtins, f'builtin_{name}', None)
|
|
2812
|
+
|
|
2813
|
+
# 5. Check if there's a user-defined function in scope
|
|
2814
|
+
if value is None:
|
|
2815
|
+
# Look for function definitions
|
|
2816
|
+
func_def = self.global_scope.get(f'__func_{name}')
|
|
2817
|
+
if func_def is not None:
|
|
2818
|
+
value = func_def
|
|
2819
|
+
|
|
2820
|
+
# Only capture if we found something
|
|
2821
|
+
if value is not None:
|
|
2822
|
+
captured[name] = value
|
|
2823
|
+
|
|
2824
|
+
# Check call node's callee
|
|
2825
|
+
if n.type == 'call':
|
|
2826
|
+
callee = n.value.get('callee')
|
|
2827
|
+
if callee:
|
|
2828
|
+
scan_node(callee)
|
|
2829
|
+
for arg in n.value.get('args', []):
|
|
2830
|
+
scan_node(arg)
|
|
2831
|
+
|
|
2832
|
+
# Recurse into children
|
|
2833
|
+
if hasattr(n, 'children') and n.children:
|
|
2834
|
+
for child in n.children:
|
|
2835
|
+
scan_node(child)
|
|
2836
|
+
|
|
2837
|
+
# Check value dict for nested nodes
|
|
2838
|
+
if hasattr(n, 'value') and isinstance(n.value, dict):
|
|
2839
|
+
for key, val in n.value.items():
|
|
2840
|
+
if isinstance(val, ASTNode):
|
|
2841
|
+
scan_node(val)
|
|
2842
|
+
elif isinstance(val, list):
|
|
2843
|
+
for item in val:
|
|
2844
|
+
if isinstance(item, ASTNode):
|
|
2845
|
+
scan_node(item)
|
|
2846
|
+
|
|
2847
|
+
scan_node(node)
|
|
2848
|
+
return captured
|
|
2849
|
+
|
|
2850
|
+
# NEW: Register permanent function injection
|
|
2851
|
+
def register_function_injection(self, func_name: str, code_block: ASTNode):
|
|
2852
|
+
"""Register code to be permanently injected into a function - NEW
|
|
2853
|
+
|
|
2854
|
+
Example: exit() <== { println("Cleanup..."); }
|
|
2855
|
+
Makes every call to exit() also execute the injected code
|
|
2856
|
+
|
|
2857
|
+
Captures %<name> references at registration time.
|
|
2858
|
+
"""
|
|
2859
|
+
# Scan for %<name> captured references and capture their current values
|
|
2860
|
+
captured_values = self._scan_and_capture_refs(code_block)
|
|
2861
|
+
|
|
2862
|
+
if func_name not in self._function_injections:
|
|
2863
|
+
self._function_injections[func_name] = []
|
|
2864
|
+
self._function_injections[func_name].append((code_block, captured_values))
|
|
2865
|
+
|
|
2866
|
+
# NEW: Execute injected code for a function
|
|
2867
|
+
def _execute_function_injections(self, func_name: str):
|
|
2868
|
+
"""Execute all injected code blocks for a function - NEW
|
|
2869
|
+
|
|
2870
|
+
Includes protection against recursive execution to prevent doubled output.
|
|
2871
|
+
Uses captured values for %<name> references.
|
|
2872
|
+
"""
|
|
2873
|
+
# Prevent recursive injection execution (fixes doubled output bug)
|
|
2874
|
+
if getattr(self, '_injection_executing', False):
|
|
2875
|
+
return
|
|
2876
|
+
|
|
2877
|
+
if func_name in self._function_injections:
|
|
2878
|
+
self._injection_executing = True
|
|
2879
|
+
old_captured = self._current_captured_values.copy()
|
|
2880
|
+
try:
|
|
2881
|
+
for injection in self._function_injections[func_name]:
|
|
2882
|
+
# Handle both tuple format (code_block, captured_values) and legacy ASTNode format
|
|
2883
|
+
if isinstance(injection, tuple):
|
|
2884
|
+
code_block, captured_values = injection
|
|
2885
|
+
self._current_captured_values = captured_values
|
|
2886
|
+
else:
|
|
2887
|
+
code_block = injection
|
|
2888
|
+
self._current_captured_values = {}
|
|
2889
|
+
|
|
2890
|
+
if isinstance(code_block, ASTNode):
|
|
2891
|
+
if code_block.type == 'action_block':
|
|
2892
|
+
for child in code_block.children:
|
|
2893
|
+
# Check if exit() was called
|
|
2894
|
+
if not self._running:
|
|
2895
|
+
break
|
|
2896
|
+
self._execute_node(child)
|
|
2897
|
+
else:
|
|
2898
|
+
self._execute_node(code_block)
|
|
2899
|
+
finally:
|
|
2900
|
+
self._injection_executing = False
|
|
2901
|
+
self._current_captured_values = old_captured
|
|
2902
|
+
|
|
2903
|
+
# Output functions for builtins
|
|
2904
|
+
def set_output_callback(self, callback: Callable[[str, str], None]):
|
|
2905
|
+
"""Set output callback for console integration"""
|
|
2906
|
+
self._output_callback = callback
|
|
2907
|
+
|
|
2908
|
+
def _emit_output(self, text: str, level: str = 'normal'):
|
|
2909
|
+
"""Emit output through callback or print"""
|
|
2910
|
+
if self._output_callback:
|
|
2911
|
+
self._output_callback(text, level)
|
|
2912
|
+
else:
|
|
2913
|
+
print(text, end='')
|
|
2914
|
+
|
|
2915
|
+
def output(self, text: str):
|
|
2916
|
+
"""Output text"""
|
|
2917
|
+
self.output_buffer.append(text)
|
|
2918
|
+
self._emit_output(text, 'normal')
|
|
2919
|
+
|
|
2920
|
+
def debug(self, message: str):
|
|
2921
|
+
"""Debug output"""
|
|
2922
|
+
text = f"[DEBUG] {message}\n"
|
|
2923
|
+
self._emit_output(text, 'debug')
|
|
2924
|
+
|
|
2925
|
+
def error(self, message: str):
|
|
2926
|
+
"""Error output"""
|
|
2927
|
+
text = f"[ERROR] {message}\n"
|
|
2928
|
+
self._emit_output(text, 'error')
|
|
2929
|
+
|
|
2930
|
+
def warn(self, message: str):
|
|
2931
|
+
"""Warning output"""
|
|
2932
|
+
text = f"[WARN] {message}\n"
|
|
2933
|
+
self._emit_output(text, 'warning')
|
|
2934
|
+
|
|
2935
|
+
def log(self, level: str, message: str):
|
|
2936
|
+
"""Log with level"""
|
|
2937
|
+
level_map = {'debug': 'debug', 'info': 'normal', 'warn': 'warning', 'warning': 'warning', 'error': 'error'}
|
|
2938
|
+
output_level = level_map.get(level.lower(), 'normal')
|
|
2939
|
+
text = f"[{level.upper()}] {message}\n"
|
|
2940
|
+
self._emit_output(text, output_level)
|
|
2941
|
+
|
|
2942
|
+
def exit(self, code: int = 0):
|
|
2943
|
+
"""Exit runtime"""
|
|
2944
|
+
self._exit_code = code
|
|
2945
|
+
self._running = False
|
|
2946
|
+
|
|
2947
|
+
def get_output(self) -> str:
|
|
2948
|
+
"""Get buffered output"""
|
|
2949
|
+
return ''.join(self.output_buffer)
|
|
2950
|
+
|
|
2951
|
+
def clear_output(self):
|
|
2952
|
+
"""Clear output buffer"""
|
|
2953
|
+
self.output_buffer.clear()
|
|
2954
|
+
|
|
2955
|
+
|
|
2956
|
+
class CSSLServiceRunner:
|
|
2957
|
+
"""
|
|
2958
|
+
Runs CSSL services with event integration
|
|
2959
|
+
"""
|
|
2960
|
+
|
|
2961
|
+
def __init__(self, runtime: CSSLRuntime):
|
|
2962
|
+
self.runtime = runtime
|
|
2963
|
+
self.running_services: Dict[str, ServiceDefinition] = {}
|
|
2964
|
+
self.event_manager = get_event_manager()
|
|
2965
|
+
|
|
2966
|
+
def load_service(self, source: str) -> ServiceDefinition:
|
|
2967
|
+
"""Load and parse a CSSL service"""
|
|
2968
|
+
ast = parse_cssl(source)
|
|
2969
|
+
service = self.runtime._exec_service(ast)
|
|
2970
|
+
return service
|
|
2971
|
+
|
|
2972
|
+
def load_service_file(self, filepath: str) -> ServiceDefinition:
|
|
2973
|
+
"""Load a service from file"""
|
|
2974
|
+
with open(filepath, 'r', encoding='utf-8') as f:
|
|
2975
|
+
source = f.read()
|
|
2976
|
+
return self.load_service(source)
|
|
2977
|
+
|
|
2978
|
+
def start_service(self, service: ServiceDefinition) -> bool:
|
|
2979
|
+
"""Start a loaded service"""
|
|
2980
|
+
if service.name in self.running_services:
|
|
2981
|
+
return False
|
|
2982
|
+
|
|
2983
|
+
self.running_services[service.name] = service
|
|
2984
|
+
|
|
2985
|
+
try:
|
|
2986
|
+
# Execute Initialization struct first if exists
|
|
2987
|
+
if 'Initialization' in service.structs:
|
|
2988
|
+
init_struct = service.structs['Initialization']
|
|
2989
|
+
self.runtime._exec_struct(init_struct)
|
|
2990
|
+
elif 'Init' in service.structs:
|
|
2991
|
+
init_struct = service.structs['Init']
|
|
2992
|
+
self.runtime._exec_struct(init_struct)
|
|
2993
|
+
|
|
2994
|
+
# Execute main function if exists
|
|
2995
|
+
if 'main' in service.functions:
|
|
2996
|
+
main_func = service.functions['main']
|
|
2997
|
+
self.runtime._call_function(main_func, [])
|
|
2998
|
+
else:
|
|
2999
|
+
# Try to find a main-like function
|
|
3000
|
+
for func_name in ['Main', 'run', 'Run', 'start', 'Start']:
|
|
3001
|
+
if func_name in service.functions:
|
|
3002
|
+
self.runtime._call_function(service.functions[func_name], [])
|
|
3003
|
+
break
|
|
3004
|
+
|
|
3005
|
+
except CSSLReturn:
|
|
3006
|
+
pass # Normal return from main
|
|
3007
|
+
except CSSLRuntimeError as e:
|
|
3008
|
+
# Runtime error with line number
|
|
3009
|
+
self.runtime.error(f"Service '{service.name}' Fehler (Zeile {e.line}): {e}")
|
|
3010
|
+
return False
|
|
3011
|
+
except Exception as e:
|
|
3012
|
+
# Try to extract line info
|
|
3013
|
+
line_info = ""
|
|
3014
|
+
if hasattr(e, 'line') and e.line:
|
|
3015
|
+
line_info = f" (Zeile {e.line})"
|
|
3016
|
+
self.runtime.error(f"Service '{service.name}' Fehler{line_info}: {e}")
|
|
3017
|
+
return False
|
|
3018
|
+
|
|
3019
|
+
return True
|
|
3020
|
+
|
|
3021
|
+
def stop_service(self, service_name: str) -> bool:
|
|
3022
|
+
"""Stop a running service"""
|
|
3023
|
+
if service_name not in self.running_services:
|
|
3024
|
+
return False
|
|
3025
|
+
|
|
3026
|
+
service = self.running_services[service_name]
|
|
3027
|
+
|
|
3028
|
+
# Execute cleanup if exists
|
|
3029
|
+
if 'cleanup' in service.functions:
|
|
3030
|
+
cleanup_func = service.functions['cleanup']
|
|
3031
|
+
self.runtime._call_function(cleanup_func, [])
|
|
3032
|
+
|
|
3033
|
+
del self.running_services[service_name]
|
|
3034
|
+
return True
|
|
3035
|
+
|
|
3036
|
+
def get_running_services(self) -> List[str]:
|
|
3037
|
+
"""Get list of running service names"""
|
|
3038
|
+
return list(self.running_services.keys())
|
|
3039
|
+
|
|
3040
|
+
|
|
3041
|
+
# Convenience function
|
|
3042
|
+
def run_cssl(source: str, service_engine=None) -> Any:
|
|
3043
|
+
"""Run CSSL source code"""
|
|
3044
|
+
runtime = CSSLRuntime(service_engine)
|
|
3045
|
+
return runtime.execute(source)
|
|
3046
|
+
|
|
3047
|
+
|
|
3048
|
+
def run_cssl_file(filepath: str, service_engine=None) -> Any:
|
|
3049
|
+
"""Run a CSSL file"""
|
|
3050
|
+
runtime = CSSLRuntime(service_engine)
|
|
3051
|
+
return runtime.execute_file(filepath)
|