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.
- includecpp/__init__.py +59 -58
- includecpp/cli/commands.py +400 -21
- includecpp/core/cppy_converter.py +143 -18
- includecpp/core/cssl/__init__.py +40 -0
- includecpp/core/cssl/cssl_builtins.py +1693 -0
- includecpp/core/cssl/cssl_events.py +621 -0
- includecpp/core/cssl/cssl_modules.py +2803 -0
- includecpp/core/cssl/cssl_parser.py +1791 -0
- includecpp/core/cssl/cssl_runtime.py +1587 -0
- includecpp/core/cssl/cssl_syntax.py +488 -0
- includecpp/core/cssl/cssl_types.py +438 -0
- includecpp/core/cssl_bridge.py +409 -0
- includecpp/core/cssl_bridge.pyi +311 -0
- includecpp/core/project_ui.py +684 -34
- includecpp/generator/parser.cpp +81 -0
- {includecpp-3.3.20.dist-info → includecpp-3.4.8.dist-info}/METADATA +48 -4
- includecpp-3.4.8.dist-info/RECORD +41 -0
- includecpp-3.3.20.dist-info/RECORD +0 -31
- {includecpp-3.3.20.dist-info → includecpp-3.4.8.dist-info}/WHEEL +0 -0
- {includecpp-3.3.20.dist-info → includecpp-3.4.8.dist-info}/entry_points.txt +0 -0
- {includecpp-3.3.20.dist-info → includecpp-3.4.8.dist-info}/licenses/LICENSE +0 -0
- {includecpp-3.3.20.dist-info → includecpp-3.4.8.dist-info}/top_level.txt +0 -0
|
@@ -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)
|