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