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.

Files changed (49) hide show
  1. includecpp/__init__.py +59 -0
  2. includecpp/__init__.pyi +255 -0
  3. includecpp/__main__.py +4 -0
  4. includecpp/cli/__init__.py +4 -0
  5. includecpp/cli/commands.py +8270 -0
  6. includecpp/cli/config_parser.py +127 -0
  7. includecpp/core/__init__.py +19 -0
  8. includecpp/core/ai_integration.py +2132 -0
  9. includecpp/core/build_manager.py +2416 -0
  10. includecpp/core/cpp_api.py +376 -0
  11. includecpp/core/cpp_api.pyi +95 -0
  12. includecpp/core/cppy_converter.py +3448 -0
  13. includecpp/core/cssl/CSSL_DOCUMENTATION.md +2075 -0
  14. includecpp/core/cssl/__init__.py +42 -0
  15. includecpp/core/cssl/cssl_builtins.py +2271 -0
  16. includecpp/core/cssl/cssl_builtins.pyi +1393 -0
  17. includecpp/core/cssl/cssl_events.py +621 -0
  18. includecpp/core/cssl/cssl_modules.py +2803 -0
  19. includecpp/core/cssl/cssl_parser.py +2575 -0
  20. includecpp/core/cssl/cssl_runtime.py +3051 -0
  21. includecpp/core/cssl/cssl_syntax.py +488 -0
  22. includecpp/core/cssl/cssl_types.py +1512 -0
  23. includecpp/core/cssl_bridge.py +882 -0
  24. includecpp/core/cssl_bridge.pyi +488 -0
  25. includecpp/core/error_catalog.py +802 -0
  26. includecpp/core/error_formatter.py +1016 -0
  27. includecpp/core/exceptions.py +97 -0
  28. includecpp/core/path_discovery.py +77 -0
  29. includecpp/core/project_ui.py +3370 -0
  30. includecpp/core/settings_ui.py +326 -0
  31. includecpp/generator/__init__.py +1 -0
  32. includecpp/generator/parser.cpp +1903 -0
  33. includecpp/generator/parser.h +281 -0
  34. includecpp/generator/type_resolver.cpp +363 -0
  35. includecpp/generator/type_resolver.h +68 -0
  36. includecpp/py.typed +0 -0
  37. includecpp/templates/cpp.proj.template +18 -0
  38. includecpp/vscode/__init__.py +1 -0
  39. includecpp/vscode/cssl/__init__.py +1 -0
  40. includecpp/vscode/cssl/language-configuration.json +38 -0
  41. includecpp/vscode/cssl/package.json +50 -0
  42. includecpp/vscode/cssl/snippets/cssl.snippets.json +1080 -0
  43. includecpp/vscode/cssl/syntaxes/cssl.tmLanguage.json +341 -0
  44. includecpp-3.7.3.dist-info/METADATA +1076 -0
  45. includecpp-3.7.3.dist-info/RECORD +49 -0
  46. includecpp-3.7.3.dist-info/WHEEL +5 -0
  47. includecpp-3.7.3.dist-info/entry_points.txt +2 -0
  48. includecpp-3.7.3.dist-info/licenses/LICENSE +21 -0
  49. 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)