IncludeCPP 3.3.20__py3-none-any.whl → 3.4.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1549 @@
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
+
16
+
17
+ class CSSLRuntimeError(Exception):
18
+ """Runtime error during CSSL execution"""
19
+ def __init__(self, message: str, line: int = 0):
20
+ self.line = line
21
+ super().__init__(f"{message} (line {line})" if line else message)
22
+
23
+
24
+ class CSSLBreak(Exception):
25
+ """Break statement"""
26
+ pass
27
+
28
+
29
+ class CSSLContinue(Exception):
30
+ """Continue statement"""
31
+ pass
32
+
33
+
34
+ class CSSLReturn(Exception):
35
+ """Return statement"""
36
+ def __init__(self, value: Any = None):
37
+ self.value = value
38
+ super().__init__()
39
+
40
+
41
+ @dataclass
42
+ class Scope:
43
+ """Variable scope"""
44
+ variables: Dict[str, Any] = field(default_factory=dict)
45
+ parent: Optional['Scope'] = None
46
+
47
+ def get(self, name: str) -> Any:
48
+ if name in self.variables:
49
+ return self.variables[name]
50
+ if self.parent:
51
+ return self.parent.get(name)
52
+ return None
53
+
54
+ def set(self, name: str, value: Any):
55
+ self.variables[name] = value
56
+
57
+ def update(self, name: str, value: Any) -> bool:
58
+ if name in self.variables:
59
+ self.variables[name] = value
60
+ return True
61
+ if self.parent:
62
+ return self.parent.update(name, value)
63
+ return False
64
+
65
+ def has(self, name: str) -> bool:
66
+ if name in self.variables:
67
+ return True
68
+ if self.parent:
69
+ return self.parent.has(name)
70
+ return False
71
+
72
+
73
+ @dataclass
74
+ class ServiceDefinition:
75
+ """Parsed service definition"""
76
+ name: str = ""
77
+ version: str = "1.0"
78
+ author: str = ""
79
+ description: str = ""
80
+ dependencies: List[str] = field(default_factory=list)
81
+ autostart: bool = False
82
+ priority: int = 0
83
+ structs: Dict[str, ASTNode] = field(default_factory=dict)
84
+ functions: Dict[str, ASTNode] = field(default_factory=dict)
85
+ event_handlers: Dict[str, List[ASTNode]] = field(default_factory=dict)
86
+
87
+
88
+ class CSSLRuntime:
89
+ """
90
+ CSSL Script Runtime
91
+ Interprets and executes CSSL Abstract Syntax Trees
92
+ """
93
+
94
+ def __init__(self, service_engine=None, output_callback: Callable[[str, str], None] = None):
95
+ self.service_engine = service_engine
96
+ self.scope = Scope()
97
+ self.global_scope = self.scope
98
+ self.builtins = CSSLBuiltins(self)
99
+ self.event_manager = get_event_manager()
100
+ self.output_buffer: List[str] = []
101
+ self.services: Dict[str, ServiceDefinition] = {}
102
+ self._modules: Dict[str, Any] = {}
103
+ self._global_structs: Dict[str, Any] = {} # Global structs for s@<name> references
104
+ self._function_injections: Dict[str, List[ASTNode]] = {} # NEW: Permanent function injections
105
+ self._promoted_globals: Dict[str, Any] = {} # NEW: Variables promoted via global()
106
+ self._running = False
107
+ self._exit_code = 0
108
+ self._output_callback = output_callback # Callback for console output (text, level)
109
+
110
+ self._setup_modules()
111
+ self._setup_builtins()
112
+
113
+ def _setup_modules(self):
114
+ """Setup module references for @KernelClient, @VSRAM, etc."""
115
+ if self.service_engine:
116
+ self._modules['KernelClient'] = self.service_engine.KernelClient
117
+ self._modules['Kernel'] = self.service_engine.KernelClient
118
+
119
+ if hasattr(self.service_engine.KernelClient, 'VSRam'):
120
+ self._modules['VSRAM'] = self.service_engine.KernelClient.VSRam
121
+ self._modules['VSRam'] = self.service_engine.KernelClient.VSRam
122
+
123
+ if hasattr(self.service_engine.KernelClient, 'WheelKernel'):
124
+ self._modules['Wheel'] = self.service_engine.KernelClient.WheelKernel
125
+ self._modules['WheelKernel'] = self.service_engine.KernelClient.WheelKernel
126
+
127
+ if hasattr(self.service_engine.KernelClient, 'CSnI'):
128
+ self._modules['Network'] = self.service_engine.KernelClient.CSnI
129
+ self._modules['CSnI'] = self.service_engine.KernelClient.CSnI
130
+
131
+ if hasattr(self.service_engine, 'ServiceOperation'):
132
+ self._modules['Service'] = self.service_engine.ServiceOperation
133
+ self._modules['ServiceOperation'] = self.service_engine.ServiceOperation
134
+
135
+ self._modules['ServiceEngine'] = self.service_engine
136
+ self._modules['Boot'] = self.service_engine
137
+
138
+ self._modules['event'] = self.event_manager
139
+ self._modules['Events'] = self.event_manager
140
+
141
+ # Register CSSL Standard Modules
142
+ module_registry = get_module_registry()
143
+ for module_name in module_registry.list_modules():
144
+ module = module_registry.get_module(module_name)
145
+ if module:
146
+ module.runtime = self
147
+ self._modules[module_name] = module
148
+
149
+ def _setup_builtins(self):
150
+ """Register built-in functions in global scope"""
151
+ for name in self.builtins.list_functions():
152
+ self.global_scope.set(name, self.builtins.get_function(name))
153
+
154
+ def get_module(self, path: str) -> Any:
155
+ """Get a module by path like 'KernelClient.VSRam'"""
156
+ parts = path.split('.')
157
+ obj = self._modules.get(parts[0])
158
+
159
+ if obj is None:
160
+ return None
161
+
162
+ for part in parts[1:]:
163
+ if hasattr(obj, part):
164
+ obj = getattr(obj, part)
165
+ elif isinstance(obj, dict) and part in obj:
166
+ obj = obj[part]
167
+ else:
168
+ return None
169
+
170
+ return obj
171
+
172
+ def register_global_struct(self, name: str, struct_data: Any):
173
+ """Register a struct as globally accessible via s@<name>"""
174
+ self._global_structs[name] = struct_data
175
+
176
+ def get_global_struct(self, path: str) -> Any:
177
+ """Get a global struct by path like 'Backend.Loop.timer'"""
178
+ parts = path.split('.')
179
+ obj = self._global_structs.get(parts[0])
180
+
181
+ if obj is None:
182
+ return None
183
+
184
+ for part in parts[1:]:
185
+ if hasattr(obj, part):
186
+ obj = getattr(obj, part)
187
+ elif isinstance(obj, dict) and part in obj:
188
+ obj = obj[part]
189
+ else:
190
+ return None
191
+
192
+ return obj
193
+
194
+ def execute(self, source: str) -> Any:
195
+ """Execute CSSL service source code"""
196
+ try:
197
+ ast = parse_cssl(source)
198
+ return self._execute_node(ast)
199
+ except CSSLSyntaxError as e:
200
+ raise CSSLRuntimeError(str(e), e.line)
201
+ except SyntaxError as e:
202
+ raise CSSLRuntimeError(f"Syntax error: {e}")
203
+
204
+ def execute_program(self, source: str) -> Any:
205
+ """Execute standalone CSSL program (no service wrapper)"""
206
+ try:
207
+ ast = parse_cssl_program(source)
208
+ return self._exec_program(ast)
209
+ except CSSLSyntaxError as e:
210
+ raise CSSLRuntimeError(str(e), e.line)
211
+ except SyntaxError as e:
212
+ raise CSSLRuntimeError(f"Syntax error: {e}")
213
+
214
+ def execute_ast(self, ast: ASTNode) -> Any:
215
+ """Execute a pre-parsed AST"""
216
+ return self._execute_node(ast)
217
+
218
+ def execute_file(self, filepath: str) -> Any:
219
+ """Execute a CSSL service file"""
220
+ with open(filepath, 'r', encoding='utf-8') as f:
221
+ source = f.read()
222
+ return self.execute(source)
223
+
224
+ def execute_program_file(self, filepath: str) -> Any:
225
+ """Execute a standalone CSSL program file"""
226
+ with open(filepath, 'r', encoding='utf-8') as f:
227
+ source = f.read()
228
+ return self.execute_program(source)
229
+
230
+ def _execute_node(self, node: ASTNode) -> Any:
231
+ """Execute an AST node"""
232
+ if node is None:
233
+ return None
234
+
235
+ method_name = f'_exec_{node.type.replace("-", "_")}'
236
+ method = getattr(self, method_name, None)
237
+
238
+ if method:
239
+ return method(node)
240
+ else:
241
+ raise CSSLRuntimeError(f"Unknown node type: {node.type}", node.line)
242
+
243
+ def _exec_service(self, node: ASTNode) -> ServiceDefinition:
244
+ """Execute service root node"""
245
+ service = ServiceDefinition()
246
+
247
+ for child in node.children:
248
+ if child.type == 'service-init':
249
+ self._exec_service_init(child, service)
250
+ elif child.type == 'service-include':
251
+ self._exec_service_include(child, service)
252
+ elif child.type == 'service-run':
253
+ self._exec_service_run(child, service)
254
+ # NEW: package block support
255
+ elif child.type == 'package':
256
+ self._exec_package(child, service)
257
+ # NEW: package-includes block support
258
+ elif child.type == 'package-includes':
259
+ self._exec_package_includes(child, service)
260
+ # NEW: struct at top level
261
+ elif child.type == 'struct':
262
+ struct_info = child.value
263
+ if isinstance(struct_info, dict):
264
+ struct_name = struct_info.get('name', '')
265
+ else:
266
+ struct_name = struct_info
267
+ service.structs[struct_name] = child
268
+ self._exec_struct(child)
269
+ # NEW: define at top level
270
+ elif child.type == 'function':
271
+ func_info = child.value
272
+ func_name = func_info.get('name')
273
+ service.functions[func_name] = child
274
+ self.scope.set(func_name, child)
275
+
276
+ return service
277
+
278
+ def _exec_program(self, node: ASTNode) -> Any:
279
+ """Execute standalone program (no service wrapper)
280
+
281
+ A program can contain:
282
+ - struct definitions
283
+ - function definitions (define)
284
+ - top-level statements (assignments, function calls, control flow)
285
+ """
286
+ result = None
287
+
288
+ for child in node.children:
289
+ if child.type == 'struct':
290
+ self._exec_struct(child)
291
+ elif child.type == 'function':
292
+ self._exec_function(child)
293
+ elif child.type in ('assignment', 'expression', 'inject', 'receive', 'flow',
294
+ 'if', 'while', 'for', 'foreach', 'switch', 'try'):
295
+ result = self._execute_node(child)
296
+ elif child.type == 'call':
297
+ result = self._eval_call(child)
298
+ else:
299
+ # Try to execute as statement
300
+ try:
301
+ result = self._execute_node(child)
302
+ except CSSLRuntimeError:
303
+ pass # Ignore unknown nodes in program mode
304
+
305
+ # Look for and execute main() if defined
306
+ main_func = self.scope.get('main')
307
+ if main_func and isinstance(main_func, ASTNode) and main_func.type == 'function':
308
+ try:
309
+ result = self._call_function(main_func, [])
310
+ except CSSLReturn as ret:
311
+ result = ret.value
312
+
313
+ return result
314
+
315
+ def _exec_service_init(self, node: ASTNode, service: ServiceDefinition):
316
+ """Execute service-init block"""
317
+ # Property aliases mapping
318
+ key_aliases = {
319
+ 'service-name': 'name',
320
+ 'service-version': 'version',
321
+ 'service-author': 'author',
322
+ 'service-description': 'description',
323
+ 'executation': 'execution',
324
+ 'execution': 'execution',
325
+ }
326
+
327
+ for child in node.children:
328
+ if child.type == 'property' and child.value:
329
+ key = child.value.get('key')
330
+ value = child.value.get('value')
331
+
332
+ # Apply alias mapping
333
+ key = key_aliases.get(key, key)
334
+
335
+ if key == 'name':
336
+ service.name = value
337
+ elif key == 'version':
338
+ service.version = value
339
+ elif key == 'author':
340
+ service.author = value
341
+ elif key == 'description':
342
+ service.description = value
343
+ elif key == 'dependencies':
344
+ if isinstance(value, list):
345
+ service.dependencies = value
346
+ elif isinstance(value, str):
347
+ service.dependencies = [value]
348
+ elif key == 'autostart':
349
+ service.autostart = bool(value)
350
+ elif key == 'priority':
351
+ service.priority = int(value)
352
+ elif key == 'execution':
353
+ # Handle execution type
354
+ if value == 'Persistent':
355
+ service.autostart = True
356
+ elif value == 'Only-Once':
357
+ service.autostart = False
358
+
359
+ def _exec_service_include(self, node: ASTNode, service: ServiceDefinition):
360
+ """Execute service-include block for importing modules and files"""
361
+ for child in node.children:
362
+ if child.type == 'expression':
363
+ # Evaluate the expression which may be:
364
+ # - @ModuleName (standard module reference)
365
+ # - include(cso_root('/path/to/file.cssl'))
366
+ # - Variable assignment
367
+ result = self._evaluate(child.value)
368
+
369
+ # If result is a module, register it
370
+ if result is not None:
371
+ # Check if it's a call result (like include())
372
+ if hasattr(result, 'name') and result.name:
373
+ # It's a ServiceDefinition from include()
374
+ # Merge its functions and structs into current service
375
+ if hasattr(result, 'functions'):
376
+ for fname, fnode in result.functions.items():
377
+ service.functions[fname] = fnode
378
+ self.scope.set(fname, fnode)
379
+ if hasattr(result, 'structs'):
380
+ for sname, snode in result.structs.items():
381
+ service.structs[sname] = snode
382
+
383
+ elif child.type == 'assignment' or child.type == 'injection':
384
+ # Handle: utils <== include(cso_root('/services/utils.cssl'));
385
+ info = child.value
386
+ name = info.get('name') or info.get('target')
387
+ if isinstance(name, ASTNode):
388
+ name = name.value if name.type == 'identifier' else str(name)
389
+ source = info.get('source') or info.get('value')
390
+ value = self._evaluate(source)
391
+ self.scope.set(name, value)
392
+
393
+ elif child.type == 'call':
394
+ # Direct function call like include(...)
395
+ self._eval_call(child)
396
+
397
+ elif child.type == 'module_ref':
398
+ # @ModuleName reference - just evaluate to register
399
+ self._evaluate(child)
400
+
401
+ def _exec_service_run(self, node: ASTNode, service: ServiceDefinition):
402
+ """Execute service-run block"""
403
+ for child in node.children:
404
+ if child.type == 'struct':
405
+ # Handle new dict format: {'name': name, 'global': is_global}
406
+ struct_info = child.value
407
+ if isinstance(struct_info, dict):
408
+ struct_name = struct_info.get('name', '')
409
+ else:
410
+ struct_name = struct_info
411
+ service.structs[struct_name] = child
412
+ self._exec_struct(child)
413
+ elif child.type == 'function':
414
+ func_info = child.value
415
+ func_name = func_info.get('name')
416
+ service.functions[func_name] = child
417
+ self.scope.set(func_name, child)
418
+
419
+ def _exec_package(self, node: ASTNode, service: ServiceDefinition):
420
+ """Execute package {} block for service metadata - NEW
421
+
422
+ Syntax:
423
+ package {
424
+ service = "ServiceName";
425
+ exec = @Start();
426
+ version = "1.0.0";
427
+ description = "Beschreibung";
428
+ }
429
+ """
430
+ exec_func = None
431
+
432
+ for child in node.children:
433
+ if child.type == 'package_property' and child.value:
434
+ key = child.value.get('key')
435
+ value_node = child.value.get('value')
436
+
437
+ # Evaluate the value
438
+ value = self._evaluate(value_node)
439
+
440
+ if key == 'service':
441
+ service.name = value
442
+ elif key == 'version':
443
+ service.version = value
444
+ elif key == 'description':
445
+ service.description = value
446
+ elif key == 'author':
447
+ service.author = value
448
+ elif key == 'exec':
449
+ # Store exec function for later execution
450
+ exec_func = value_node
451
+
452
+ # Store exec function reference for later
453
+ if exec_func:
454
+ service.functions['__exec__'] = exec_func
455
+
456
+ def _exec_package_includes(self, node: ASTNode, service: ServiceDefinition):
457
+ """Execute package-includes {} block for imports - NEW
458
+
459
+ Syntax:
460
+ package-includes {
461
+ @Lists = get('list');
462
+ @OS = get('os');
463
+ @Time = get('time');
464
+ @VSRam = get('vsramsdk');
465
+ }
466
+ """
467
+ for child in node.children:
468
+ if child.type == 'assignment':
469
+ info = child.value
470
+ target = info.get('target')
471
+ value_node = info.get('value')
472
+
473
+ # Evaluate the value (e.g., get('list'))
474
+ value = self._evaluate(value_node)
475
+
476
+ # Get target name
477
+ if isinstance(target, ASTNode):
478
+ if target.type == 'module_ref':
479
+ # @ModuleName = get(...)
480
+ module_name = target.value
481
+ self._modules[module_name] = value
482
+ elif target.type == 'identifier':
483
+ # varName = get(...)
484
+ self.scope.set(target.value, value)
485
+ elif isinstance(target, str):
486
+ self.scope.set(target, value)
487
+
488
+ elif child.type == 'expression':
489
+ # Evaluate expression statements
490
+ self._evaluate(child.value)
491
+
492
+ elif child.type == 'inject':
493
+ # Handle: @Module <== get(...);
494
+ target = child.value.get('target')
495
+ source = self._evaluate(child.value.get('source'))
496
+
497
+ if isinstance(target, ASTNode) and target.type == 'module_ref':
498
+ self._modules[target.value] = source
499
+ elif isinstance(target, ASTNode) and target.type == 'identifier':
500
+ self.scope.set(target.value, source)
501
+
502
+ def _exec_struct(self, node: ASTNode) -> Dict[str, Any]:
503
+ """Execute struct block"""
504
+ struct_data = {}
505
+
506
+ # Get struct name and global flag
507
+ struct_info = node.value
508
+ if isinstance(struct_info, dict):
509
+ struct_name = struct_info.get('name', '')
510
+ is_global = struct_info.get('global', False)
511
+ else:
512
+ # Backwards compatibility: value is just the name
513
+ struct_name = struct_info
514
+ is_global = False
515
+
516
+ for child in node.children:
517
+ if child.type == 'injection':
518
+ info = child.value
519
+ name = info.get('name')
520
+ source = info.get('source')
521
+ value = self._evaluate(source)
522
+ struct_data[name] = value
523
+ self.scope.set(name, value)
524
+
525
+ elif child.type == 'assignment':
526
+ info = child.value
527
+ name = info.get('name')
528
+ value = self._evaluate(info.get('value'))
529
+ struct_data[name] = value
530
+ self.scope.set(name, value)
531
+
532
+ elif child.type == 'function':
533
+ func_info = child.value
534
+ func_name = func_info.get('name')
535
+ struct_data[func_name] = child
536
+ self.scope.set(func_name, child)
537
+
538
+ elif child.type == 'expression':
539
+ # Execute expression statements like print()
540
+ self._evaluate(child.value)
541
+
542
+ elif child.type == 'call':
543
+ # Direct function calls
544
+ self._eval_call(child)
545
+
546
+ else:
547
+ # Try to execute other statement types
548
+ try:
549
+ self._execute_node(child)
550
+ except Exception:
551
+ pass
552
+
553
+ # Register as global struct if decorated with (@)
554
+ if is_global and struct_name:
555
+ self.register_global_struct(struct_name, struct_data)
556
+
557
+ return struct_data
558
+
559
+ def _exec_function(self, node: ASTNode) -> Any:
560
+ """Execute function definition - just registers it"""
561
+ func_info = node.value
562
+ func_name = func_info.get('name')
563
+ self.scope.set(func_name, node)
564
+ return None
565
+
566
+ def _call_function(self, func_node: ASTNode, args: List[Any]) -> Any:
567
+ """Call a function node with arguments"""
568
+ func_info = func_node.value
569
+ params = func_info.get('params', [])
570
+
571
+ # Create new scope
572
+ new_scope = Scope(parent=self.scope)
573
+
574
+ # Bind parameters
575
+ for i, param in enumerate(params):
576
+ if i < len(args):
577
+ new_scope.set(param, args[i])
578
+ else:
579
+ new_scope.set(param, None)
580
+
581
+ # Execute body
582
+ old_scope = self.scope
583
+ self.scope = new_scope
584
+
585
+ try:
586
+ for child in func_node.children:
587
+ self._execute_node(child)
588
+ except CSSLReturn as ret:
589
+ return ret.value
590
+ finally:
591
+ self.scope = old_scope
592
+
593
+ return None
594
+
595
+ def _exec_if(self, node: ASTNode) -> Any:
596
+ """Execute if statement"""
597
+ condition = self._evaluate(node.value.get('condition'))
598
+
599
+ if condition:
600
+ for child in node.children:
601
+ if child.type == 'then':
602
+ for stmt in child.children:
603
+ self._execute_node(stmt)
604
+ return None
605
+
606
+ # Execute else block
607
+ for child in node.children:
608
+ if child.type == 'else':
609
+ for stmt in child.children:
610
+ self._execute_node(stmt)
611
+ return None
612
+
613
+ return None
614
+
615
+ def _exec_while(self, node: ASTNode) -> Any:
616
+ """Execute while loop"""
617
+ while self._evaluate(node.value.get('condition')):
618
+ try:
619
+ for child in node.children:
620
+ self._execute_node(child)
621
+ except CSSLBreak:
622
+ break
623
+ except CSSLContinue:
624
+ continue
625
+
626
+ return None
627
+
628
+ def _exec_for(self, node: ASTNode) -> Any:
629
+ """Execute for loop"""
630
+ var_name = node.value.get('var')
631
+ start = int(self._evaluate(node.value.get('start')))
632
+ end = int(self._evaluate(node.value.get('end')))
633
+
634
+ for i in range(start, end):
635
+ self.scope.set(var_name, i)
636
+ try:
637
+ for child in node.children:
638
+ self._execute_node(child)
639
+ except CSSLBreak:
640
+ break
641
+ except CSSLContinue:
642
+ continue
643
+
644
+ return None
645
+
646
+ def _exec_foreach(self, node: ASTNode) -> Any:
647
+ """Execute foreach loop"""
648
+ var_name = node.value.get('var')
649
+ iterable = self._evaluate(node.value.get('iterable'))
650
+
651
+ if iterable is None:
652
+ return None
653
+
654
+ for item in iterable:
655
+ self.scope.set(var_name, item)
656
+ try:
657
+ for child in node.children:
658
+ self._execute_node(child)
659
+ except CSSLBreak:
660
+ break
661
+ except CSSLContinue:
662
+ continue
663
+
664
+ return None
665
+
666
+ def _exec_switch(self, node: ASTNode) -> Any:
667
+ """Execute switch statement"""
668
+ value = self._evaluate(node.value.get('value'))
669
+ matched = False
670
+
671
+ for child in node.children:
672
+ if child.type == 'case':
673
+ case_value = self._evaluate(child.value.get('value'))
674
+ if value == case_value:
675
+ matched = True
676
+ try:
677
+ for stmt in child.children:
678
+ self._execute_node(stmt)
679
+ except CSSLBreak:
680
+ return None
681
+ elif child.type == 'default' and not matched:
682
+ try:
683
+ for stmt in child.children:
684
+ self._execute_node(stmt)
685
+ except CSSLBreak:
686
+ return None
687
+
688
+ return None
689
+
690
+ def _exec_return(self, node: ASTNode) -> Any:
691
+ """Execute return statement"""
692
+ value = self._evaluate(node.value) if node.value else None
693
+ raise CSSLReturn(value)
694
+
695
+ def _exec_break(self, node: ASTNode) -> Any:
696
+ """Execute break statement"""
697
+ raise CSSLBreak()
698
+
699
+ def _exec_continue(self, node: ASTNode) -> Any:
700
+ """Execute continue statement"""
701
+ raise CSSLContinue()
702
+
703
+ def _exec_try(self, node: ASTNode) -> Any:
704
+ """Execute try/catch block"""
705
+ try:
706
+ for child in node.children:
707
+ if child.type == 'try-block':
708
+ for stmt in child.children:
709
+ self._execute_node(stmt)
710
+ except CSSLRuntimeError as e:
711
+ for child in node.children:
712
+ if child.type == 'catch-block':
713
+ error_var = child.value.get('error_var') if child.value else None
714
+ if error_var:
715
+ self.scope.set(error_var, str(e))
716
+ for stmt in child.children:
717
+ self._execute_node(stmt)
718
+
719
+ return None
720
+
721
+ def _exec_createcmd_inject(self, node: ASTNode) -> Any:
722
+ """Execute createcmd injection: createcmd('cmd') <== { action }"""
723
+ command_call = node.value.get('command_call')
724
+ action_block = node.value.get('action')
725
+
726
+ # Get command name from the createcmd call arguments
727
+ args = command_call.value.get('args', [])
728
+ if args:
729
+ command_name = self._evaluate(args[0])
730
+ else:
731
+ raise CSSLRuntimeError("createcmd requires a command name argument")
732
+
733
+ # Create the command handler function
734
+ def command_handler(*cmd_args):
735
+ # Create a scope for the command execution
736
+ cmd_scope = Scope(parent=self.scope)
737
+
738
+ # Set cmd_args in scope
739
+ cmd_scope.set('args', list(cmd_args))
740
+ cmd_scope.set('argc', len(cmd_args))
741
+
742
+ old_scope = self.scope
743
+ self.scope = cmd_scope
744
+
745
+ try:
746
+ # Execute action block statements
747
+ for child in action_block.children:
748
+ if child.type == 'function':
749
+ # If there's a define action { } inside, call it
750
+ func_name = child.value.get('name')
751
+ if func_name == 'action':
752
+ return self._call_function(child, list(cmd_args))
753
+ else:
754
+ self._execute_node(child)
755
+ except CSSLReturn as ret:
756
+ return ret.value
757
+ finally:
758
+ self.scope = old_scope
759
+
760
+ return None
761
+
762
+ # Register the command using the builtin
763
+ self.builtins.builtin_createcmd(command_name, command_handler)
764
+
765
+ return command_name
766
+
767
+ def _apply_injection_filter(self, source: Any, filter_info: dict) -> Any:
768
+ """Apply injection filter to extract specific data from source.
769
+
770
+ Filters:
771
+ - string::where=VALUE - Filter strings containing VALUE
772
+ - string::length=LENGTH - Filter strings of specific length
773
+ - integer::where=VALUE - Filter integers matching VALUE
774
+ - json::key=KEY - Extract values with specific key from JSON/dict
775
+ - json::value=VALUE - Filter by value in JSON/dict
776
+ - array::index=INDEX - Get specific index from array
777
+ - array::length=LENGTH - Filter arrays of specific length
778
+ - vector::where=VALUE - Filter vectors containing VALUE
779
+ - combo::filterdb - Get filter database from combo
780
+ - combo::blocked - Get blocked items from combo
781
+ - dynamic::VarName=VALUE - Filter by dynamic variable
782
+ """
783
+ if not filter_info:
784
+ return source
785
+
786
+ result = source
787
+
788
+ for filter_key, filter_value in filter_info.items():
789
+ if '::' in filter_key:
790
+ filter_type, helper = filter_key.split('::', 1)
791
+ filter_val = self._evaluate(filter_value) if isinstance(filter_value, ASTNode) else filter_value
792
+
793
+ if filter_type == 'string':
794
+ if helper == 'where':
795
+ if isinstance(result, str) and filter_val in result:
796
+ pass # Keep result
797
+ elif isinstance(result, list):
798
+ result = [item for item in result if isinstance(item, str) and filter_val in item]
799
+ elif helper == 'length':
800
+ if isinstance(result, str):
801
+ result = result if len(result) == filter_val else None
802
+ elif isinstance(result, list):
803
+ result = [item for item in result if isinstance(item, str) and len(item) == filter_val]
804
+
805
+ elif filter_type == 'integer':
806
+ if helper == 'where':
807
+ if isinstance(result, int) and result == filter_val:
808
+ pass # Keep result
809
+ elif isinstance(result, list):
810
+ result = [item for item in result if isinstance(item, int) and item == filter_val]
811
+
812
+ elif filter_type == 'json':
813
+ if helper == 'key':
814
+ if isinstance(result, dict):
815
+ result = result.get(filter_val)
816
+ elif isinstance(result, list):
817
+ result = [item.get(filter_val) for item in result if isinstance(item, dict) and filter_val in item]
818
+ elif helper == 'value':
819
+ if isinstance(result, dict):
820
+ result = {k: v for k, v in result.items() if v == filter_val}
821
+ elif isinstance(result, list):
822
+ result = [item for item in result if (isinstance(item, dict) and filter_val in item.values())]
823
+
824
+ elif filter_type == 'array' or filter_type == 'vector':
825
+ if helper == 'index':
826
+ if isinstance(result, list) and 0 <= filter_val < len(result):
827
+ result = result[filter_val]
828
+ elif helper == 'length':
829
+ if isinstance(result, list):
830
+ result = result if len(result) == filter_val else []
831
+ elif helper == 'where':
832
+ if isinstance(result, list):
833
+ result = [item for item in result if item == filter_val]
834
+
835
+ elif filter_type == 'combo':
836
+ if helper == 'filterdb':
837
+ if hasattr(result, '_filterdb'):
838
+ result = result._filterdb
839
+ elif helper == 'blocked':
840
+ if hasattr(result, '_blocked'):
841
+ result = result._blocked
842
+
843
+ elif filter_type == 'sql':
844
+ if helper == 'data':
845
+ # Return only SQL-compatible data types
846
+ if isinstance(result, (int, str, bool, float, list, dict)):
847
+ pass # Keep result
848
+ else:
849
+ result = str(result) # Convert to string
850
+
851
+ return result
852
+
853
+ def _exec_inject(self, node: ASTNode) -> Any:
854
+ """Execute inject operation (<==, +<==, -<==)
855
+
856
+ Modes:
857
+ - replace: target <== source (replace target with source)
858
+ - add: target +<== source (copy & add to target)
859
+ - move: target -<== source (move from source, remove from source)
860
+ """
861
+ target = node.value.get('target')
862
+ source_node = node.value.get('source')
863
+ mode = node.value.get('mode', 'replace')
864
+ filter_info = node.value.get('filter')
865
+
866
+ # Check if target is a function call (for permanent injection)
867
+ if isinstance(target, ASTNode) and target.type == 'call':
868
+ callee = target.value.get('callee')
869
+ if isinstance(callee, ASTNode) and callee.type == 'identifier':
870
+ func_name = callee.value
871
+ self.register_function_injection(func_name, source_node)
872
+ return None
873
+
874
+ # Evaluate source
875
+ source = self._evaluate(source_node)
876
+
877
+ # Apply filter if present
878
+ if filter_info:
879
+ source = self._apply_injection_filter(source, filter_info)
880
+
881
+ # Get current target value for add/move modes
882
+ current_value = None
883
+ if mode in ('add', 'move'):
884
+ current_value = self._evaluate(target)
885
+
886
+ # Determine final value based on mode
887
+ if mode == 'replace':
888
+ final_value = source
889
+ elif mode == 'add':
890
+ # Copy & add - preserve target and add source
891
+ if isinstance(current_value, list):
892
+ if isinstance(source, list):
893
+ final_value = current_value + source
894
+ else:
895
+ final_value = current_value + [source]
896
+ elif isinstance(current_value, dict) and isinstance(source, dict):
897
+ final_value = {**current_value, **source}
898
+ elif isinstance(current_value, str) and isinstance(source, str):
899
+ final_value = current_value + source
900
+ elif current_value is None:
901
+ final_value = [source] if not isinstance(source, list) else source
902
+ else:
903
+ final_value = [current_value, source]
904
+ elif mode == 'move':
905
+ # Move & remove from source
906
+ final_value = source
907
+ # Clear the source
908
+ if isinstance(source_node, ASTNode) and source_node.type == 'identifier':
909
+ self.scope.set(source_node.value, None)
910
+ else:
911
+ final_value = source
912
+
913
+ # Set the target
914
+ if target.type == 'identifier':
915
+ self.scope.set(target.value, final_value)
916
+ elif target.type == 'module_ref':
917
+ self._set_module_value(target.value, final_value)
918
+ elif target.type == 'member_access':
919
+ self._set_member(target, final_value)
920
+ elif target.type == 'call':
921
+ callee = target.value.get('callee')
922
+ if isinstance(callee, ASTNode) and callee.type == 'member_access':
923
+ obj = self._evaluate(callee.value.get('object'))
924
+ method_name = callee.value.get('member')
925
+ if method_name == 'add' and isinstance(obj, list):
926
+ obj.append(final_value)
927
+ return final_value
928
+
929
+ return final_value
930
+
931
+ def _exec_receive(self, node: ASTNode) -> Any:
932
+ """Execute receive operation (==>, ==>+, -==>)
933
+
934
+ Modes:
935
+ - replace: source ==> target (move source to target, replace)
936
+ - add: source ==>+ target (copy source to target, add)
937
+ - move: source -==> target (move from source, remove)
938
+ """
939
+ source_node = node.value.get('source')
940
+ target = node.value.get('target')
941
+ mode = node.value.get('mode', 'replace')
942
+ filter_info = node.value.get('filter')
943
+
944
+ # Evaluate source
945
+ source = self._evaluate(source_node)
946
+
947
+ # Apply filter if present
948
+ if filter_info:
949
+ source = self._apply_injection_filter(source, filter_info)
950
+
951
+ # Get current target value for add mode
952
+ current_value = None
953
+ if mode == 'add':
954
+ current_value = self._evaluate(target)
955
+
956
+ # Determine final value based on mode
957
+ if mode == 'replace':
958
+ final_value = source
959
+ elif mode == 'add':
960
+ if isinstance(current_value, list):
961
+ if isinstance(source, list):
962
+ final_value = current_value + source
963
+ else:
964
+ final_value = current_value + [source]
965
+ elif isinstance(current_value, dict) and isinstance(source, dict):
966
+ final_value = {**current_value, **source}
967
+ elif isinstance(current_value, str) and isinstance(source, str):
968
+ final_value = current_value + source
969
+ elif current_value is None:
970
+ final_value = [source] if not isinstance(source, list) else source
971
+ else:
972
+ final_value = [current_value, source]
973
+ elif mode == 'move':
974
+ final_value = source
975
+ # Clear the source
976
+ if isinstance(source_node, ASTNode) and source_node.type == 'identifier':
977
+ self.scope.set(source_node.value, None)
978
+ else:
979
+ final_value = source
980
+
981
+ # Set the target
982
+ if target.type == 'identifier':
983
+ self.scope.set(target.value, final_value)
984
+ elif target.type == 'module_ref':
985
+ self._set_module_value(target.value, final_value)
986
+ elif target.type == 'member_access':
987
+ self._set_member(target, final_value)
988
+
989
+ return final_value
990
+
991
+ def _exec_infuse(self, node: ASTNode) -> Any:
992
+ """Execute code infusion (<<==, +<<==, -<<==)
993
+
994
+ Modes:
995
+ - replace: func <<== { code } (inject code into function, replaces)
996
+ - add: func +<<== { code } (add code to function)
997
+ - remove: func -<<== { code } (remove matching code from function)
998
+ """
999
+ target = node.value.get('target')
1000
+ code_block = node.value.get('code')
1001
+ mode = node.value.get('mode', 'add')
1002
+
1003
+ # Get function name from target
1004
+ func_name = None
1005
+ if isinstance(target, ASTNode):
1006
+ if target.type == 'identifier':
1007
+ func_name = target.value
1008
+ elif target.type == 'call':
1009
+ callee = target.value.get('callee')
1010
+ if isinstance(callee, ASTNode) and callee.type == 'identifier':
1011
+ func_name = callee.value
1012
+
1013
+ if not func_name:
1014
+ return None
1015
+
1016
+ if mode == 'add':
1017
+ # Add code to function (permanent injection)
1018
+ self.register_function_injection(func_name, code_block)
1019
+ elif mode == 'replace':
1020
+ # Replace - clear existing and add new
1021
+ self._function_injections[func_name] = [code_block]
1022
+ elif mode == 'remove':
1023
+ # Remove matching code patterns from function
1024
+ if func_name in self._function_injections:
1025
+ # For simplicity, clear all injections for this function
1026
+ # A more sophisticated implementation would compare code blocks
1027
+ self._function_injections[func_name] = []
1028
+
1029
+ return None
1030
+
1031
+ def _exec_infuse_right(self, node: ASTNode) -> Any:
1032
+ """Execute right-side code infusion (==>>)"""
1033
+ source = node.value.get('source')
1034
+ target = node.value.get('target')
1035
+ mode = node.value.get('mode', 'replace')
1036
+
1037
+ # Similar to infuse but direction is reversed
1038
+ func_name = None
1039
+ if isinstance(target, ASTNode):
1040
+ if target.type == 'identifier':
1041
+ func_name = target.value
1042
+ elif target.type == 'call':
1043
+ callee = target.value.get('callee')
1044
+ if isinstance(callee, ASTNode) and callee.type == 'identifier':
1045
+ func_name = callee.value
1046
+
1047
+ if func_name and isinstance(source, ASTNode):
1048
+ self.register_function_injection(func_name, source)
1049
+
1050
+ return None
1051
+
1052
+ def _exec_flow(self, node: ASTNode) -> Any:
1053
+ """Execute flow operation (-> or <-)"""
1054
+ source = self._evaluate(node.value.get('source'))
1055
+ target = node.value.get('target')
1056
+
1057
+ # Flow sends data to a target (could be function call or variable)
1058
+ if target.type == 'call':
1059
+ callee = self._evaluate(target.value.get('callee'))
1060
+ args = [source] + [self._evaluate(a) for a in target.value.get('args', [])]
1061
+
1062
+ if callable(callee):
1063
+ return callee(*args)
1064
+ elif isinstance(callee, ASTNode) and callee.type == 'function':
1065
+ return self._call_function(callee, args)
1066
+ elif target.type == 'identifier':
1067
+ self.scope.set(target.value, source)
1068
+ elif target.type == 'module_ref':
1069
+ self._set_module_value(target.value, source)
1070
+
1071
+ return source
1072
+
1073
+ def _exec_assignment(self, node: ASTNode) -> Any:
1074
+ """Execute assignment"""
1075
+ target = node.value.get('target')
1076
+ value = self._evaluate(node.value.get('value'))
1077
+
1078
+ if isinstance(target, ASTNode):
1079
+ if target.type == 'identifier':
1080
+ self.scope.set(target.value, value)
1081
+ elif target.type == 'member_access':
1082
+ self._set_member(target, value)
1083
+ elif target.type == 'index_access':
1084
+ self._set_index(target, value)
1085
+ elif isinstance(target, str):
1086
+ self.scope.set(target, value)
1087
+
1088
+ return value
1089
+
1090
+ def _exec_expression(self, node: ASTNode) -> Any:
1091
+ """Execute expression statement"""
1092
+ return self._evaluate(node.value)
1093
+
1094
+ def _exec_await(self, node: ASTNode) -> Any:
1095
+ """Execute await statement - waits for expression to complete"""
1096
+ # Evaluate the awaited expression
1097
+ # The expression is typically a call like wait_for_booted()
1098
+ result = self._evaluate(node.value)
1099
+
1100
+ # If result is a callable (like a coroutine or future), wait for it
1101
+ if hasattr(result, '__await__'):
1102
+ import asyncio
1103
+ loop = asyncio.get_event_loop()
1104
+ return loop.run_until_complete(result)
1105
+
1106
+ # If result is a boolean condition waiting function, it already handled the waiting
1107
+ return result
1108
+
1109
+ def _exec_then(self, node: ASTNode) -> Any:
1110
+ """Execute then block"""
1111
+ for child in node.children:
1112
+ self._execute_node(child)
1113
+ return None
1114
+
1115
+ def _exec_else(self, node: ASTNode) -> Any:
1116
+ """Execute else block"""
1117
+ for child in node.children:
1118
+ self._execute_node(child)
1119
+ return None
1120
+
1121
+ def _exec_try_block(self, node: ASTNode) -> Any:
1122
+ """Execute try block"""
1123
+ for child in node.children:
1124
+ self._execute_node(child)
1125
+ return None
1126
+
1127
+ def _exec_catch_block(self, node: ASTNode) -> Any:
1128
+ """Execute catch block"""
1129
+ for child in node.children:
1130
+ self._execute_node(child)
1131
+ return None
1132
+
1133
+ def _evaluate(self, node: Any) -> Any:
1134
+ """Evaluate an expression node to get its value"""
1135
+ if node is None:
1136
+ return None
1137
+
1138
+ if not isinstance(node, ASTNode):
1139
+ return node
1140
+
1141
+ if node.type == 'literal':
1142
+ value = node.value
1143
+ # NEW: String interpolation - replace <variable> with scope values
1144
+ if isinstance(value, str) and '<' in value and '>' in value:
1145
+ value = self._interpolate_string(value)
1146
+ return value
1147
+
1148
+ # NEW: Type literals (list, dict) - create empty instances
1149
+ if node.type == 'type_literal':
1150
+ type_name = node.value
1151
+ if type_name == 'list':
1152
+ return []
1153
+ elif type_name == 'dict':
1154
+ return {}
1155
+ return None
1156
+
1157
+ if node.type == 'identifier':
1158
+ value = self.scope.get(node.value)
1159
+ if value is None and self.builtins.has_function(node.value):
1160
+ return self.builtins.get_function(node.value)
1161
+ return value
1162
+
1163
+ if node.type == 'module_ref':
1164
+ return self.get_module(node.value)
1165
+
1166
+ if node.type == 'self_ref':
1167
+ # s@<name> reference to global struct
1168
+ return self.get_global_struct(node.value)
1169
+
1170
+ if node.type == 'binary':
1171
+ return self._eval_binary(node)
1172
+
1173
+ if node.type == 'unary':
1174
+ return self._eval_unary(node)
1175
+
1176
+ if node.type == 'call':
1177
+ return self._eval_call(node)
1178
+
1179
+ if node.type == 'member_access':
1180
+ return self._eval_member_access(node)
1181
+
1182
+ if node.type == 'index_access':
1183
+ return self._eval_index_access(node)
1184
+
1185
+ if node.type == 'array':
1186
+ return [self._evaluate(elem) for elem in node.value]
1187
+
1188
+ if node.type == 'object':
1189
+ return {k: self._evaluate(v) for k, v in node.value.items()}
1190
+
1191
+ return None
1192
+
1193
+ def _eval_binary(self, node: ASTNode) -> Any:
1194
+ """Evaluate binary operation"""
1195
+ op = node.value.get('op')
1196
+ left = self._evaluate(node.value.get('left'))
1197
+ right = self._evaluate(node.value.get('right'))
1198
+
1199
+ operators = {
1200
+ '+': lambda a, b: a + b,
1201
+ '-': lambda a, b: a - b,
1202
+ '*': lambda a, b: a * b,
1203
+ '/': lambda a, b: a / b if b != 0 else 0,
1204
+ '%': lambda a, b: a % b if b != 0 else 0,
1205
+ '==': lambda a, b: a == b,
1206
+ '!=': lambda a, b: a != b,
1207
+ '<': lambda a, b: a < b,
1208
+ '>': lambda a, b: a > b,
1209
+ '<=': lambda a, b: a <= b,
1210
+ '>=': lambda a, b: a >= b,
1211
+ 'and': lambda a, b: a and b,
1212
+ 'or': lambda a, b: a or b,
1213
+ }
1214
+
1215
+ if op in operators:
1216
+ return operators[op](left, right)
1217
+
1218
+ return None
1219
+
1220
+ def _eval_unary(self, node: ASTNode) -> Any:
1221
+ """Evaluate unary operation"""
1222
+ op = node.value.get('op')
1223
+ operand = self._evaluate(node.value.get('operand'))
1224
+
1225
+ if op == 'not':
1226
+ return not operand
1227
+ if op == '-':
1228
+ return -operand
1229
+
1230
+ return None
1231
+
1232
+ def _eval_call(self, node: ASTNode) -> Any:
1233
+ """Evaluate function call"""
1234
+ callee_node = node.value.get('callee')
1235
+ callee = self._evaluate(callee_node)
1236
+ args = [self._evaluate(a) for a in node.value.get('args', [])]
1237
+
1238
+ # NEW: Get function name for injection check
1239
+ func_name = None
1240
+ if isinstance(callee_node, ASTNode):
1241
+ if callee_node.type == 'identifier':
1242
+ func_name = callee_node.value
1243
+ elif callee_node.type == 'member_access':
1244
+ func_name = callee_node.value.get('member')
1245
+
1246
+ # NEW: Execute any permanently injected code first
1247
+ if func_name and func_name in self._function_injections:
1248
+ self._execute_function_injections(func_name)
1249
+
1250
+ if callable(callee):
1251
+ return callee(*args)
1252
+
1253
+ if isinstance(callee, ASTNode) and callee.type == 'function':
1254
+ return self._call_function(callee, args)
1255
+
1256
+ raise CSSLRuntimeError(f"Cannot call non-function: {type(callee)}", node.line)
1257
+
1258
+ def _eval_member_access(self, node: ASTNode) -> Any:
1259
+ """Evaluate member access"""
1260
+ obj = self._evaluate(node.value.get('object'))
1261
+ member = node.value.get('member')
1262
+
1263
+ if obj is None:
1264
+ return None
1265
+
1266
+ if hasattr(obj, member):
1267
+ return getattr(obj, member)
1268
+
1269
+ if isinstance(obj, dict):
1270
+ return obj.get(member)
1271
+
1272
+ return None
1273
+
1274
+ def _eval_index_access(self, node: ASTNode) -> Any:
1275
+ """Evaluate index access"""
1276
+ obj = self._evaluate(node.value.get('object'))
1277
+ index = self._evaluate(node.value.get('index'))
1278
+
1279
+ if obj is None:
1280
+ return None
1281
+
1282
+ try:
1283
+ return obj[index]
1284
+ except (IndexError, KeyError, TypeError):
1285
+ return None
1286
+
1287
+ def _set_member(self, node: ASTNode, value: Any):
1288
+ """Set member value"""
1289
+ obj = self._evaluate(node.value.get('object'))
1290
+ member = node.value.get('member')
1291
+
1292
+ if obj is None:
1293
+ return
1294
+
1295
+ if hasattr(obj, member):
1296
+ setattr(obj, member, value)
1297
+ elif isinstance(obj, dict):
1298
+ obj[member] = value
1299
+
1300
+ def _set_index(self, node: ASTNode, value: Any):
1301
+ """Set index value"""
1302
+ obj = self._evaluate(node.value.get('object'))
1303
+ index = self._evaluate(node.value.get('index'))
1304
+
1305
+ if obj is not None:
1306
+ try:
1307
+ obj[index] = value
1308
+ except (IndexError, KeyError, TypeError):
1309
+ pass
1310
+
1311
+ def _set_module_value(self, path: str, value: Any):
1312
+ """Set a value on a module path"""
1313
+ parts = path.split('.')
1314
+ if len(parts) < 2:
1315
+ return
1316
+
1317
+ obj = self._modules.get(parts[0])
1318
+ if obj is None:
1319
+ return
1320
+
1321
+ for part in parts[1:-1]:
1322
+ if hasattr(obj, part):
1323
+ obj = getattr(obj, part)
1324
+ elif isinstance(obj, dict):
1325
+ obj = obj.get(part)
1326
+ else:
1327
+ return
1328
+
1329
+ final_attr = parts[-1]
1330
+ if hasattr(obj, final_attr):
1331
+ setattr(obj, final_attr, value)
1332
+ elif isinstance(obj, dict):
1333
+ obj[final_attr] = value
1334
+
1335
+ # NEW: String interpolation
1336
+ def _interpolate_string(self, string: str) -> str:
1337
+ """Replace <variable> placeholders with values from scope - NEW
1338
+
1339
+ Example: "Hello <name>!" becomes "Hello John!" if name = "John"
1340
+ """
1341
+ import re
1342
+ pattern = r'<([A-Za-z_][A-Za-z0-9_]*)>'
1343
+
1344
+ def replacer(match):
1345
+ var_name = match.group(1)
1346
+ # Try scope first
1347
+ value = self.scope.get(var_name)
1348
+ # Try promoted globals
1349
+ if value is None:
1350
+ value = self._promoted_globals.get(var_name)
1351
+ # Try modules
1352
+ if value is None:
1353
+ value = self._modules.get(var_name)
1354
+ # Return string representation or empty string if None
1355
+ return str(value) if value is not None else ''
1356
+
1357
+ return re.sub(pattern, replacer, string)
1358
+
1359
+ # NEW: Promote variable to global scope via global()
1360
+ def promote_to_global(self, s_ref_name: str):
1361
+ """Promote s@<name> to @<name> (make globally accessible) - NEW
1362
+
1363
+ Example: global(s@cache) makes @cache available
1364
+ """
1365
+ # Extract the base name from s@<path>
1366
+ parts = s_ref_name.split('.')
1367
+ base_name = parts[0]
1368
+
1369
+ # Get the value from global structs
1370
+ value = self.get_global_struct(s_ref_name)
1371
+
1372
+ if value is not None:
1373
+ # Register as module reference
1374
+ self._modules[base_name] = value
1375
+ # Also store in promoted globals for string interpolation
1376
+ self._promoted_globals[base_name] = value
1377
+
1378
+ # NEW: Register permanent function injection
1379
+ def register_function_injection(self, func_name: str, code_block: ASTNode):
1380
+ """Register code to be permanently injected into a function - NEW
1381
+
1382
+ Example: exit() <== { println("Cleanup..."); }
1383
+ Makes every call to exit() also execute the injected code
1384
+ """
1385
+ if func_name not in self._function_injections:
1386
+ self._function_injections[func_name] = []
1387
+ self._function_injections[func_name].append(code_block)
1388
+
1389
+ # NEW: Execute injected code for a function
1390
+ def _execute_function_injections(self, func_name: str):
1391
+ """Execute all injected code blocks for a function - NEW"""
1392
+ if func_name in self._function_injections:
1393
+ for code_block in self._function_injections[func_name]:
1394
+ if isinstance(code_block, ASTNode):
1395
+ if code_block.type == 'action_block':
1396
+ for child in code_block.children:
1397
+ self._execute_node(child)
1398
+ else:
1399
+ self._execute_node(code_block)
1400
+
1401
+ # Output functions for builtins
1402
+ def set_output_callback(self, callback: Callable[[str, str], None]):
1403
+ """Set output callback for console integration"""
1404
+ self._output_callback = callback
1405
+
1406
+ def _emit_output(self, text: str, level: str = 'normal'):
1407
+ """Emit output through callback or print"""
1408
+ if self._output_callback:
1409
+ self._output_callback(text, level)
1410
+ else:
1411
+ print(text, end='')
1412
+
1413
+ def output(self, text: str):
1414
+ """Output text"""
1415
+ self.output_buffer.append(text)
1416
+ self._emit_output(text, 'normal')
1417
+
1418
+ def debug(self, message: str):
1419
+ """Debug output"""
1420
+ text = f"[DEBUG] {message}\n"
1421
+ self._emit_output(text, 'debug')
1422
+
1423
+ def error(self, message: str):
1424
+ """Error output"""
1425
+ text = f"[ERROR] {message}\n"
1426
+ self._emit_output(text, 'error')
1427
+
1428
+ def warn(self, message: str):
1429
+ """Warning output"""
1430
+ text = f"[WARN] {message}\n"
1431
+ self._emit_output(text, 'warning')
1432
+
1433
+ def log(self, level: str, message: str):
1434
+ """Log with level"""
1435
+ level_map = {'debug': 'debug', 'info': 'normal', 'warn': 'warning', 'warning': 'warning', 'error': 'error'}
1436
+ output_level = level_map.get(level.lower(), 'normal')
1437
+ text = f"[{level.upper()}] {message}\n"
1438
+ self._emit_output(text, output_level)
1439
+
1440
+ def exit(self, code: int = 0):
1441
+ """Exit runtime"""
1442
+ self._exit_code = code
1443
+ self._running = False
1444
+
1445
+ def get_output(self) -> str:
1446
+ """Get buffered output"""
1447
+ return ''.join(self.output_buffer)
1448
+
1449
+ def clear_output(self):
1450
+ """Clear output buffer"""
1451
+ self.output_buffer.clear()
1452
+
1453
+
1454
+ class CSSLServiceRunner:
1455
+ """
1456
+ Runs CSSL services with event integration
1457
+ """
1458
+
1459
+ def __init__(self, runtime: CSSLRuntime):
1460
+ self.runtime = runtime
1461
+ self.running_services: Dict[str, ServiceDefinition] = {}
1462
+ self.event_manager = get_event_manager()
1463
+
1464
+ def load_service(self, source: str) -> ServiceDefinition:
1465
+ """Load and parse a CSSL service"""
1466
+ ast = parse_cssl(source)
1467
+ service = self.runtime._exec_service(ast)
1468
+ return service
1469
+
1470
+ def load_service_file(self, filepath: str) -> ServiceDefinition:
1471
+ """Load a service from file"""
1472
+ with open(filepath, 'r', encoding='utf-8') as f:
1473
+ source = f.read()
1474
+ return self.load_service(source)
1475
+
1476
+ def start_service(self, service: ServiceDefinition) -> bool:
1477
+ """Start a loaded service"""
1478
+ if service.name in self.running_services:
1479
+ return False
1480
+
1481
+ self.running_services[service.name] = service
1482
+
1483
+ try:
1484
+ # Execute Initialization struct first if exists
1485
+ if 'Initialization' in service.structs:
1486
+ init_struct = service.structs['Initialization']
1487
+ self.runtime._exec_struct(init_struct)
1488
+ elif 'Init' in service.structs:
1489
+ init_struct = service.structs['Init']
1490
+ self.runtime._exec_struct(init_struct)
1491
+
1492
+ # Execute main function if exists
1493
+ if 'main' in service.functions:
1494
+ main_func = service.functions['main']
1495
+ self.runtime._call_function(main_func, [])
1496
+ else:
1497
+ # Try to find a main-like function
1498
+ for func_name in ['Main', 'run', 'Run', 'start', 'Start']:
1499
+ if func_name in service.functions:
1500
+ self.runtime._call_function(service.functions[func_name], [])
1501
+ break
1502
+
1503
+ except CSSLReturn:
1504
+ pass # Normal return from main
1505
+ except CSSLRuntimeError as e:
1506
+ # Runtime error with line number
1507
+ self.runtime.error(f"Service '{service.name}' Fehler (Zeile {e.line}): {e}")
1508
+ return False
1509
+ except Exception as e:
1510
+ # Try to extract line info
1511
+ line_info = ""
1512
+ if hasattr(e, 'line') and e.line:
1513
+ line_info = f" (Zeile {e.line})"
1514
+ self.runtime.error(f"Service '{service.name}' Fehler{line_info}: {e}")
1515
+ return False
1516
+
1517
+ return True
1518
+
1519
+ def stop_service(self, service_name: str) -> bool:
1520
+ """Stop a running service"""
1521
+ if service_name not in self.running_services:
1522
+ return False
1523
+
1524
+ service = self.running_services[service_name]
1525
+
1526
+ # Execute cleanup if exists
1527
+ if 'cleanup' in service.functions:
1528
+ cleanup_func = service.functions['cleanup']
1529
+ self.runtime._call_function(cleanup_func, [])
1530
+
1531
+ del self.running_services[service_name]
1532
+ return True
1533
+
1534
+ def get_running_services(self) -> List[str]:
1535
+ """Get list of running service names"""
1536
+ return list(self.running_services.keys())
1537
+
1538
+
1539
+ # Convenience function
1540
+ def run_cssl(source: str, service_engine=None) -> Any:
1541
+ """Run CSSL source code"""
1542
+ runtime = CSSLRuntime(service_engine)
1543
+ return runtime.execute(source)
1544
+
1545
+
1546
+ def run_cssl_file(filepath: str, service_engine=None) -> Any:
1547
+ """Run a CSSL file"""
1548
+ runtime = CSSLRuntime(service_engine)
1549
+ return runtime.execute_file(filepath)