IncludeCPP 4.6.0__py3-none-any.whl → 4.9.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- includecpp/CHANGELOG.md +241 -0
- includecpp/__init__.py +89 -3
- includecpp/__init__.pyi +2 -1
- includecpp/cli/commands.py +1747 -266
- includecpp/cli/config_parser.py +1 -1
- includecpp/core/build_manager.py +64 -13
- includecpp/core/cpp_api_extensions.pyi +43 -270
- includecpp/core/cssl/CSSL_DOCUMENTATION.md +1799 -1445
- includecpp/core/cssl/cpp/build/api.pyd +0 -0
- includecpp/core/cssl/cpp/build/api.pyi +274 -0
- includecpp/core/cssl/cpp/build/cssl_core.pyi +0 -99
- includecpp/core/cssl/cpp/cssl_core.cp +2 -23
- includecpp/core/cssl/cssl_builtins.py +2116 -171
- includecpp/core/cssl/cssl_builtins.pyi +1324 -104
- includecpp/core/cssl/cssl_compiler.py +4 -1
- includecpp/core/cssl/cssl_modules.py +605 -6
- includecpp/core/cssl/cssl_optimizer.py +12 -1
- includecpp/core/cssl/cssl_parser.py +1048 -52
- includecpp/core/cssl/cssl_runtime.py +2041 -131
- includecpp/core/cssl/cssl_syntax.py +405 -277
- includecpp/core/cssl/cssl_types.py +5891 -1655
- includecpp/core/cssl_bridge.py +427 -4
- includecpp/core/error_catalog.py +54 -10
- includecpp/core/homeserver.py +1037 -0
- includecpp/generator/parser.cpp +203 -39
- includecpp/generator/parser.h +15 -1
- includecpp/templates/cpp.proj.template +1 -1
- includecpp/vscode/cssl/snippets/cssl.snippets.json +163 -0
- includecpp/vscode/cssl/syntaxes/cssl.tmLanguage.json +87 -12
- {includecpp-4.6.0.dist-info → includecpp-4.9.3.dist-info}/METADATA +81 -10
- {includecpp-4.6.0.dist-info → includecpp-4.9.3.dist-info}/RECORD +35 -33
- {includecpp-4.6.0.dist-info → includecpp-4.9.3.dist-info}/WHEEL +1 -1
- {includecpp-4.6.0.dist-info → includecpp-4.9.3.dist-info}/entry_points.txt +0 -0
- {includecpp-4.6.0.dist-info → includecpp-4.9.3.dist-info}/licenses/LICENSE +0 -0
- {includecpp-4.6.0.dist-info → includecpp-4.9.3.dist-info}/top_level.txt +0 -0
|
@@ -20,6 +20,195 @@ class CSSLBuiltinError(Exception):
|
|
|
20
20
|
pass
|
|
21
21
|
|
|
22
22
|
|
|
23
|
+
class _IncludeCppModuleProxy:
|
|
24
|
+
"""
|
|
25
|
+
Proxy for C++ modules loaded via includecpp().
|
|
26
|
+
|
|
27
|
+
v4.9.3: Improved to handle C++ classes by keeping a persistent subprocess
|
|
28
|
+
that holds object instances (avoiding pickle serialization issues).
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
# Class-level subprocess pool to keep C++ objects alive
|
|
32
|
+
_subprocess_pool = {}
|
|
33
|
+
_object_registry = {}
|
|
34
|
+
_next_obj_id = 0
|
|
35
|
+
|
|
36
|
+
def __init__(self, bindings_dir: str, module_name: str):
|
|
37
|
+
self._bindings_dir = bindings_dir
|
|
38
|
+
self._module_name = module_name
|
|
39
|
+
self._attrs_cache = None
|
|
40
|
+
self._direct_module = None
|
|
41
|
+
self._try_direct_import()
|
|
42
|
+
|
|
43
|
+
def _try_direct_import(self):
|
|
44
|
+
"""Try to import the module directly (preferred over subprocess)."""
|
|
45
|
+
import sys
|
|
46
|
+
import os
|
|
47
|
+
try:
|
|
48
|
+
# Add DLL directory on Windows
|
|
49
|
+
if hasattr(os, 'add_dll_directory'):
|
|
50
|
+
os.add_dll_directory(self._bindings_dir)
|
|
51
|
+
if self._bindings_dir not in sys.path:
|
|
52
|
+
sys.path.insert(0, self._bindings_dir)
|
|
53
|
+
|
|
54
|
+
# Try importing api module
|
|
55
|
+
import importlib
|
|
56
|
+
if 'api' in sys.modules:
|
|
57
|
+
# Check if it's our api or cssl's bundled one
|
|
58
|
+
api_mod = sys.modules['api']
|
|
59
|
+
if hasattr(api_mod, self._module_name):
|
|
60
|
+
self._direct_module = getattr(api_mod, self._module_name)
|
|
61
|
+
return
|
|
62
|
+
else:
|
|
63
|
+
api_mod = importlib.import_module('api')
|
|
64
|
+
if hasattr(api_mod, self._module_name):
|
|
65
|
+
self._direct_module = getattr(api_mod, self._module_name)
|
|
66
|
+
return
|
|
67
|
+
except Exception:
|
|
68
|
+
pass # Fall back to subprocess proxy
|
|
69
|
+
|
|
70
|
+
def _get_attrs(self) -> list:
|
|
71
|
+
"""Get available attributes from the module."""
|
|
72
|
+
if self._direct_module:
|
|
73
|
+
return [n for n in dir(self._direct_module) if not n.startswith('_')]
|
|
74
|
+
|
|
75
|
+
if self._attrs_cache is not None:
|
|
76
|
+
return self._attrs_cache
|
|
77
|
+
|
|
78
|
+
import subprocess
|
|
79
|
+
import json
|
|
80
|
+
|
|
81
|
+
script = f'''
|
|
82
|
+
import sys, os, json
|
|
83
|
+
if hasattr(os, 'add_dll_directory'):
|
|
84
|
+
os.add_dll_directory({repr(self._bindings_dir)})
|
|
85
|
+
sys.path.insert(0, {repr(self._bindings_dir)})
|
|
86
|
+
import api
|
|
87
|
+
mod = getattr(api, {repr(self._module_name)})
|
|
88
|
+
print(json.dumps([n for n in dir(mod) if not n.startswith('_')]))
|
|
89
|
+
'''
|
|
90
|
+
result = subprocess.run([sys.executable, '-c', script],
|
|
91
|
+
capture_output=True, text=True, timeout=10)
|
|
92
|
+
if result.returncode == 0:
|
|
93
|
+
self._attrs_cache = json.loads(result.stdout.strip())
|
|
94
|
+
else:
|
|
95
|
+
self._attrs_cache = []
|
|
96
|
+
return self._attrs_cache
|
|
97
|
+
|
|
98
|
+
def __getattr__(self, name: str):
|
|
99
|
+
"""Return a callable proxy for any attribute access."""
|
|
100
|
+
if name.startswith('_'):
|
|
101
|
+
raise AttributeError(name)
|
|
102
|
+
|
|
103
|
+
# Use direct module if available
|
|
104
|
+
if self._direct_module:
|
|
105
|
+
return getattr(self._direct_module, name)
|
|
106
|
+
|
|
107
|
+
# Return a callable that will execute in subprocess
|
|
108
|
+
return _IncludeCppFunctionProxy(self._bindings_dir, self._module_name, name)
|
|
109
|
+
|
|
110
|
+
def __repr__(self):
|
|
111
|
+
mode = "direct" if self._direct_module else "subprocess"
|
|
112
|
+
return f"<IncludeCppModule '{self._module_name}' [{mode}]>"
|
|
113
|
+
|
|
114
|
+
def __dir__(self):
|
|
115
|
+
return self._get_attrs()
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class _IncludeCppFunctionProxy:
|
|
119
|
+
"""Proxy for a function in a C++ module.
|
|
120
|
+
|
|
121
|
+
v4.9.3: Handles C++ class instantiation by returning instance proxies
|
|
122
|
+
that use JSON for simple values and repr for complex objects.
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
def __init__(self, bindings_dir: str, module_name: str, func_name: str):
|
|
126
|
+
self._bindings_dir = bindings_dir
|
|
127
|
+
self._module_name = module_name
|
|
128
|
+
self._func_name = func_name
|
|
129
|
+
|
|
130
|
+
def __call__(self, *args, **kwargs):
|
|
131
|
+
import subprocess
|
|
132
|
+
import json
|
|
133
|
+
|
|
134
|
+
# Convert args to JSON-safe format
|
|
135
|
+
def to_json_safe(v):
|
|
136
|
+
if isinstance(v, (int, float, str, bool, type(None))):
|
|
137
|
+
return v
|
|
138
|
+
elif isinstance(v, (list, tuple)):
|
|
139
|
+
return [to_json_safe(x) for x in v]
|
|
140
|
+
elif isinstance(v, dict):
|
|
141
|
+
return {k: to_json_safe(val) for k, val in v.items()}
|
|
142
|
+
else:
|
|
143
|
+
return str(v)
|
|
144
|
+
|
|
145
|
+
args_json = json.dumps([to_json_safe(a) for a in args])
|
|
146
|
+
kwargs_json = json.dumps({k: to_json_safe(v) for k, v in kwargs.items()})
|
|
147
|
+
|
|
148
|
+
script = f'''
|
|
149
|
+
import sys, os, json
|
|
150
|
+
if hasattr(os, 'add_dll_directory'):
|
|
151
|
+
os.add_dll_directory({repr(self._bindings_dir)})
|
|
152
|
+
sys.path.insert(0, {repr(self._bindings_dir)})
|
|
153
|
+
import api
|
|
154
|
+
mod = getattr(api, {repr(self._module_name)})
|
|
155
|
+
func = getattr(mod, {repr(self._func_name)})
|
|
156
|
+
args = json.loads({repr(args_json)})
|
|
157
|
+
kwargs = json.loads({repr(kwargs_json)})
|
|
158
|
+
result = func(*args, **kwargs)
|
|
159
|
+
|
|
160
|
+
# Handle result - try JSON first, fall back to repr
|
|
161
|
+
try:
|
|
162
|
+
if isinstance(result, (int, float, str, bool, type(None), list, dict)):
|
|
163
|
+
print("JSON:" + json.dumps(result))
|
|
164
|
+
else:
|
|
165
|
+
# For C++ objects, return type info and repr
|
|
166
|
+
print("OBJ:" + type(result).__module__ + "." + type(result).__name__ + ":" + repr(result))
|
|
167
|
+
except:
|
|
168
|
+
print("STR:" + str(result))
|
|
169
|
+
'''
|
|
170
|
+
result = subprocess.run([sys.executable, '-c', script],
|
|
171
|
+
capture_output=True, text=True, timeout=60)
|
|
172
|
+
|
|
173
|
+
if result.returncode != 0:
|
|
174
|
+
raise RuntimeError(f"C++ function call failed: {result.stderr}")
|
|
175
|
+
|
|
176
|
+
output = result.stdout.strip()
|
|
177
|
+
if output.startswith("JSON:"):
|
|
178
|
+
return json.loads(output[5:])
|
|
179
|
+
elif output.startswith("OBJ:"):
|
|
180
|
+
# Return info about the C++ object
|
|
181
|
+
obj_info = output[4:]
|
|
182
|
+
return _CppObjectInfo(obj_info, self._bindings_dir, self._module_name, self._func_name)
|
|
183
|
+
elif output.startswith("STR:"):
|
|
184
|
+
return output[4:]
|
|
185
|
+
else:
|
|
186
|
+
return output
|
|
187
|
+
|
|
188
|
+
def __repr__(self):
|
|
189
|
+
return f"<IncludeCppFunction {self._module_name}.{self._func_name}>"
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class _CppObjectInfo:
|
|
193
|
+
"""Info about a C++ object that couldn't be directly transferred.
|
|
194
|
+
|
|
195
|
+
v4.9.3: Provides info about C++ class instances from subprocess.
|
|
196
|
+
For full object access, use direct import mode.
|
|
197
|
+
"""
|
|
198
|
+
|
|
199
|
+
def __init__(self, info: str, bindings_dir: str, module_name: str, class_name: str):
|
|
200
|
+
self._info = info
|
|
201
|
+
self._bindings_dir = bindings_dir
|
|
202
|
+
self._module_name = module_name
|
|
203
|
+
self._class_name = class_name
|
|
204
|
+
|
|
205
|
+
def __repr__(self):
|
|
206
|
+
return f"<CppObject {self._info}>"
|
|
207
|
+
|
|
208
|
+
def __str__(self):
|
|
209
|
+
return self._info
|
|
210
|
+
|
|
211
|
+
|
|
23
212
|
class CSSLBuiltins:
|
|
24
213
|
"""
|
|
25
214
|
Built-in functions for CSSL runtime
|
|
@@ -29,6 +218,7 @@ class CSSLBuiltins:
|
|
|
29
218
|
def __init__(self, runtime=None):
|
|
30
219
|
self.runtime = runtime
|
|
31
220
|
self._functions: Dict[str, Callable] = {}
|
|
221
|
+
self._snapshots: Dict[str, Any] = {} # v4.8.8: Snapshot storage for %variable access
|
|
32
222
|
self._register_all()
|
|
33
223
|
|
|
34
224
|
def _register_all(self):
|
|
@@ -53,6 +243,11 @@ class CSSLBuiltins:
|
|
|
53
243
|
|
|
54
244
|
# Type checking
|
|
55
245
|
self._functions['typeof'] = self.builtin_typeof
|
|
246
|
+
self._functions['memory'] = self.builtin_memory # v4.8.9: Python repr() for debugging
|
|
247
|
+
self._functions['address'] = self.builtin_address # v4.9.0: Get address of object
|
|
248
|
+
self._functions['reflect'] = self.builtin_reflect # v4.9.0: Reflect address to object
|
|
249
|
+
self._functions['destroy'] = self.builtin_destroy # v4.9.2: Destroy object and free memory
|
|
250
|
+
self._functions['execute'] = self.builtin_execute # v4.9.2: Execute CSSL code string inline
|
|
56
251
|
self._functions['isinstance'] = self.builtin_isinstance
|
|
57
252
|
self._functions['isint'] = self.builtin_isint
|
|
58
253
|
self._functions['isfloat'] = self.builtin_isfloat
|
|
@@ -155,6 +350,7 @@ class CSSLBuiltins:
|
|
|
155
350
|
|
|
156
351
|
# File/Path functions
|
|
157
352
|
self._functions['pathexists'] = self.builtin_pathexists
|
|
353
|
+
self._functions['exists'] = self.builtin_pathexists # Alias
|
|
158
354
|
self._functions['isfile'] = self.builtin_isfile
|
|
159
355
|
self._functions['isdir'] = self.builtin_isdir
|
|
160
356
|
self._functions['basename'] = self.builtin_basename
|
|
@@ -206,6 +402,8 @@ class CSSLBuiltins:
|
|
|
206
402
|
self._functions['instance::type'] = self.builtin_instance_type
|
|
207
403
|
self._functions['isavailable'] = self.builtin_isavailable
|
|
208
404
|
self._functions['instance::exists'] = self.builtin_isavailable # Alias
|
|
405
|
+
self._functions['instance::delete'] = self.builtin_instance_delete # v4.8.8: Call destructors
|
|
406
|
+
self._functions['instance::call_constructor'] = self.builtin_call_constructor # v4.8.8: Call callable constructor
|
|
209
407
|
|
|
210
408
|
# Python interop functions
|
|
211
409
|
self._functions['python::pythonize'] = self.builtin_python_pythonize
|
|
@@ -214,6 +412,27 @@ class CSSLBuiltins:
|
|
|
214
412
|
self._functions['python::csslize'] = self.builtin_python_csslize
|
|
215
413
|
self._functions['python::import'] = self.builtin_python_csslize # Alias
|
|
216
414
|
|
|
415
|
+
# v4.8.8: Python parameter functions (replaces confusing parameter.get/return)
|
|
416
|
+
# Using _ instead of . to avoid member access parsing conflicts
|
|
417
|
+
self._functions['python::param_get'] = self.builtin_python_parameter_get
|
|
418
|
+
self._functions['python::param_return'] = self.builtin_python_parameter_return
|
|
419
|
+
self._functions['python::param_count'] = self.builtin_python_parameter_count
|
|
420
|
+
self._functions['python::param_all'] = self.builtin_python_parameter_all
|
|
421
|
+
self._functions['python::param_has'] = self.builtin_python_parameter_has
|
|
422
|
+
# Aliases for full names (backwards compatibility)
|
|
423
|
+
self._functions['python::parameter_get'] = self.builtin_python_parameter_get
|
|
424
|
+
self._functions['python::parameter_return'] = self.builtin_python_parameter_return
|
|
425
|
+
self._functions['python::parameter_count'] = self.builtin_python_parameter_count
|
|
426
|
+
self._functions['python::parameter_all'] = self.builtin_python_parameter_all
|
|
427
|
+
self._functions['python::parameter_has'] = self.builtin_python_parameter_has
|
|
428
|
+
|
|
429
|
+
# v4.6.5: Watcher namespace functions for live Python instance access
|
|
430
|
+
self._functions['watcher::get'] = self.builtin_watcher_get
|
|
431
|
+
self._functions['watcher::set'] = self.builtin_watcher_set
|
|
432
|
+
self._functions['watcher::list'] = self.builtin_watcher_list
|
|
433
|
+
self._functions['watcher::exists'] = self.builtin_watcher_exists
|
|
434
|
+
self._functions['watcher::refresh'] = self.builtin_watcher_refresh
|
|
435
|
+
|
|
217
436
|
# Regex functions
|
|
218
437
|
self._functions['match'] = self.builtin_match
|
|
219
438
|
self._functions['search'] = self.builtin_search
|
|
@@ -232,6 +451,17 @@ class CSSLBuiltins:
|
|
|
232
451
|
self._functions['exit'] = self.builtin_exit
|
|
233
452
|
self._functions['env'] = self.builtin_env
|
|
234
453
|
self._functions['setenv'] = self.builtin_setenv
|
|
454
|
+
# v4.8.5: os/sys replacement builtins
|
|
455
|
+
self._functions['getcwd'] = self.builtin_getcwd
|
|
456
|
+
self._functions['chdir'] = self.builtin_chdir
|
|
457
|
+
self._functions['mkdir'] = self.builtin_mkdir
|
|
458
|
+
self._functions['rmdir'] = self.builtin_rmdir
|
|
459
|
+
self._functions['rmfile'] = self.builtin_rmfile
|
|
460
|
+
self._functions['rename'] = self.builtin_rename
|
|
461
|
+
self._functions['argv'] = self.builtin_argv
|
|
462
|
+
self._functions['argc'] = self.builtin_argc
|
|
463
|
+
self._functions['platform'] = self.builtin_platform
|
|
464
|
+
self._functions['version'] = self.builtin_version
|
|
235
465
|
self._functions['input'] = self.builtin_input
|
|
236
466
|
self._functions['clear'] = self.builtin_clear
|
|
237
467
|
self._functions['cls'] = self.builtin_clear # Alias
|
|
@@ -239,6 +469,34 @@ class CSSLBuiltins:
|
|
|
239
469
|
self._functions['delay'] = self.builtin_delay
|
|
240
470
|
self._functions['pyimport'] = self.builtin_pyimport
|
|
241
471
|
|
|
472
|
+
# v4.8.4: C++ import and I/O streams
|
|
473
|
+
self._functions['cppimport'] = self.builtin_cppimport
|
|
474
|
+
self._functions['include'] = self.builtin_include
|
|
475
|
+
self._functions['includecpp'] = self.builtin_includecpp # v4.8.8: Build & import C++ modules
|
|
476
|
+
self._functions['cout'] = self.builtin_cout
|
|
477
|
+
self._functions['cin'] = self.builtin_cin
|
|
478
|
+
self._functions['cerr'] = self.builtin_cerr
|
|
479
|
+
self._functions['clog'] = self.builtin_clog
|
|
480
|
+
self._functions['endl'] = self.builtin_endl
|
|
481
|
+
self._functions['getline'] = self.builtin_getline
|
|
482
|
+
self._functions['fstream'] = self.builtin_fstream
|
|
483
|
+
self._functions['ifstream'] = self.builtin_ifstream
|
|
484
|
+
self._functions['ofstream'] = self.builtin_ofstream
|
|
485
|
+
self._functions['setprecision'] = self.builtin_setprecision
|
|
486
|
+
self._functions['setw'] = self.builtin_setw
|
|
487
|
+
self._functions['setfill'] = self.builtin_setfill
|
|
488
|
+
self._functions['fixed'] = self.builtin_fixed
|
|
489
|
+
self._functions['scientific'] = self.builtin_scientific
|
|
490
|
+
self._functions['flush'] = self.builtin_flush
|
|
491
|
+
# Struct operations
|
|
492
|
+
self._functions['sizeof'] = self.builtin_sizeof
|
|
493
|
+
self._functions['memcpy'] = self.builtin_memcpy
|
|
494
|
+
self._functions['memset'] = self.builtin_memset
|
|
495
|
+
# Pipe operations
|
|
496
|
+
self._functions['pipe'] = self.builtin_pipe
|
|
497
|
+
# Optimized containment check
|
|
498
|
+
self._functions['contains_fast'] = self.builtin_contains_fast
|
|
499
|
+
|
|
242
500
|
# Extended string functions
|
|
243
501
|
self._functions['sprintf'] = self.builtin_sprintf
|
|
244
502
|
self._functions['chars'] = self.builtin_chars
|
|
@@ -327,6 +585,57 @@ class CSSLBuiltins:
|
|
|
327
585
|
|
|
328
586
|
# Shared object functions
|
|
329
587
|
self._functions['delete'] = self.builtin_delete # Delete shared object ($Name)
|
|
588
|
+
self._functions['call_constructor'] = self.builtin_call_constructor # v4.8.8: Call callable constructor
|
|
589
|
+
|
|
590
|
+
# v4.6.5: Color functions - individual colors for f-strings
|
|
591
|
+
# Named colors: red("text"), green("text"), etc.
|
|
592
|
+
self._functions['red'] = self.builtin_red
|
|
593
|
+
self._functions['green'] = self.builtin_green
|
|
594
|
+
self._functions['blue'] = self.builtin_blue
|
|
595
|
+
self._functions['yellow'] = self.builtin_yellow
|
|
596
|
+
self._functions['cyan'] = self.builtin_cyan
|
|
597
|
+
self._functions['magenta'] = self.builtin_magenta
|
|
598
|
+
self._functions['white'] = self.builtin_white
|
|
599
|
+
self._functions['black'] = self.builtin_black
|
|
600
|
+
# Bright variants
|
|
601
|
+
self._functions['bright_red'] = self.builtin_bright_red
|
|
602
|
+
self._functions['bright_green'] = self.builtin_bright_green
|
|
603
|
+
self._functions['bright_blue'] = self.builtin_bright_blue
|
|
604
|
+
self._functions['bright_yellow'] = self.builtin_bright_yellow
|
|
605
|
+
self._functions['bright_cyan'] = self.builtin_bright_cyan
|
|
606
|
+
self._functions['bright_magenta'] = self.builtin_bright_magenta
|
|
607
|
+
self._functions['bright_white'] = self.builtin_bright_white
|
|
608
|
+
# RGB custom color
|
|
609
|
+
self._functions['rgb'] = self.builtin_rgb
|
|
610
|
+
# Background colors
|
|
611
|
+
self._functions['bg_red'] = self.builtin_bg_red
|
|
612
|
+
self._functions['bg_green'] = self.builtin_bg_green
|
|
613
|
+
self._functions['bg_blue'] = self.builtin_bg_blue
|
|
614
|
+
self._functions['bg_yellow'] = self.builtin_bg_yellow
|
|
615
|
+
self._functions['bg_cyan'] = self.builtin_bg_cyan
|
|
616
|
+
self._functions['bg_magenta'] = self.builtin_bg_magenta
|
|
617
|
+
self._functions['bg_white'] = self.builtin_bg_white
|
|
618
|
+
self._functions['bg_black'] = self.builtin_bg_black
|
|
619
|
+
self._functions['bg_rgb'] = self.builtin_bg_rgb
|
|
620
|
+
# Style functions
|
|
621
|
+
self._functions['bold'] = self.builtin_bold
|
|
622
|
+
self._functions['italic'] = self.builtin_italic
|
|
623
|
+
self._functions['cursive'] = self.builtin_italic # Alias
|
|
624
|
+
self._functions['underline'] = self.builtin_underline
|
|
625
|
+
self._functions['dim'] = self.builtin_dim
|
|
626
|
+
self._functions['blink'] = self.builtin_blink
|
|
627
|
+
self._functions['reverse'] = self.builtin_reverse_style
|
|
628
|
+
self._functions['strikethrough'] = self.builtin_strikethrough
|
|
629
|
+
self._functions['reset'] = self.builtin_reset
|
|
630
|
+
|
|
631
|
+
# v4.8.8: Snapshot functions for %variable access
|
|
632
|
+
self._functions['snapshot'] = self.builtin_snapshot
|
|
633
|
+
self._functions['get_snapshot'] = self.builtin_get_snapshot
|
|
634
|
+
self._functions['has_snapshot'] = self.builtin_has_snapshot
|
|
635
|
+
self._functions['clear_snapshot'] = self.builtin_clear_snapshot
|
|
636
|
+
self._functions['clear_snapshots'] = self.builtin_clear_all_snapshots
|
|
637
|
+
self._functions['list_snapshots'] = self.builtin_list_snapshots
|
|
638
|
+
self._functions['restore_snapshot'] = self.builtin_restore_snapshot
|
|
330
639
|
|
|
331
640
|
def get_function(self, name: str) -> Optional[Callable]:
|
|
332
641
|
"""Get a built-in function by name"""
|
|
@@ -570,6 +879,228 @@ class CSSLBuiltins:
|
|
|
570
879
|
}
|
|
571
880
|
return type_map.get(type(value), type(value).__name__)
|
|
572
881
|
|
|
882
|
+
def builtin_memory(self, value: Any) -> dict:
|
|
883
|
+
"""Get memory/introspection info about a value as a dictionary.
|
|
884
|
+
|
|
885
|
+
Returns dict with: address, type, repr, methods, attributes, value
|
|
886
|
+
Allows: memory(obj).get("address"), memory(obj).copy(), etc.
|
|
887
|
+
|
|
888
|
+
Example:
|
|
889
|
+
data = memory(classInstance);
|
|
890
|
+
printl(data.get("address")); // Memory address
|
|
891
|
+
printl(data.get("type")); // Type name
|
|
892
|
+
printl(data.get("methods")); // List of methods
|
|
893
|
+
"""
|
|
894
|
+
import inspect
|
|
895
|
+
|
|
896
|
+
result = {
|
|
897
|
+
'address': hex(id(value)),
|
|
898
|
+
'type': self.builtin_typeof(value),
|
|
899
|
+
'repr': repr(value),
|
|
900
|
+
'value': None,
|
|
901
|
+
'methods': [],
|
|
902
|
+
'attributes': {}
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
# Get value for simple types
|
|
906
|
+
if isinstance(value, (int, float, str, bool)):
|
|
907
|
+
result['value'] = value
|
|
908
|
+
elif isinstance(value, (list, dict)):
|
|
909
|
+
result['value'] = value
|
|
910
|
+
|
|
911
|
+
# Get methods (callable attributes)
|
|
912
|
+
try:
|
|
913
|
+
methods = []
|
|
914
|
+
for name in dir(value):
|
|
915
|
+
if not name.startswith('_'):
|
|
916
|
+
attr = getattr(value, name, None)
|
|
917
|
+
if callable(attr):
|
|
918
|
+
methods.append(name)
|
|
919
|
+
result['methods'] = methods
|
|
920
|
+
except:
|
|
921
|
+
pass
|
|
922
|
+
|
|
923
|
+
# Get non-callable attributes
|
|
924
|
+
try:
|
|
925
|
+
attrs = {}
|
|
926
|
+
for name in dir(value):
|
|
927
|
+
if not name.startswith('_'):
|
|
928
|
+
attr = getattr(value, name, None)
|
|
929
|
+
if not callable(attr):
|
|
930
|
+
try:
|
|
931
|
+
attrs[name] = attr
|
|
932
|
+
except:
|
|
933
|
+
attrs[name] = '<unreadable>'
|
|
934
|
+
result['attributes'] = attrs
|
|
935
|
+
except:
|
|
936
|
+
pass
|
|
937
|
+
|
|
938
|
+
# For CSSL classes, get member info
|
|
939
|
+
from .cssl_types import CSSLInstance
|
|
940
|
+
if isinstance(value, CSSLInstance):
|
|
941
|
+
result['class_name'] = value._class.name if value._class else None
|
|
942
|
+
result['members'] = dict(value._members) if hasattr(value, '_members') else {}
|
|
943
|
+
|
|
944
|
+
# v4.9.0: Register object in Address registry for later reflection
|
|
945
|
+
from .cssl_types import Address
|
|
946
|
+
Address.register(result['address'], value)
|
|
947
|
+
|
|
948
|
+
return result
|
|
949
|
+
|
|
950
|
+
def builtin_address(self, value: Any) -> 'Address':
|
|
951
|
+
"""Get memory address of an object as an Address type.
|
|
952
|
+
|
|
953
|
+
Shortcut for memory(obj).get("address") that returns an Address object
|
|
954
|
+
which can be used with reflect() to get the object back.
|
|
955
|
+
|
|
956
|
+
Example:
|
|
957
|
+
string text = "Hello";
|
|
958
|
+
address addr = address(text);
|
|
959
|
+
obj = addr.reflect(); // or reflect(addr)
|
|
960
|
+
printl(obj); // "Hello"
|
|
961
|
+
"""
|
|
962
|
+
from .cssl_types import Address
|
|
963
|
+
return Address(obj=value)
|
|
964
|
+
|
|
965
|
+
def builtin_reflect(self, addr: Any) -> Any:
|
|
966
|
+
"""Reflect an address to get the original object.
|
|
967
|
+
|
|
968
|
+
Takes an Address object or address string and returns the object at that address.
|
|
969
|
+
v4.9.3: Safe - if value is already dereferenced (not an Address), returns it as-is.
|
|
970
|
+
|
|
971
|
+
Example:
|
|
972
|
+
string text = "Hello";
|
|
973
|
+
address addr = address(text);
|
|
974
|
+
obj = reflect(addr);
|
|
975
|
+
printl(obj); // "Hello"
|
|
976
|
+
|
|
977
|
+
// Also works with address strings
|
|
978
|
+
data = memory(text);
|
|
979
|
+
obj = reflect(data.get("address"));
|
|
980
|
+
|
|
981
|
+
// Safe double-reflect: does nothing if already dereferenced
|
|
982
|
+
obj2 = reflect(obj); // Still "Hello"
|
|
983
|
+
"""
|
|
984
|
+
from .cssl_types import Address
|
|
985
|
+
|
|
986
|
+
if isinstance(addr, Address):
|
|
987
|
+
return addr.reflect()
|
|
988
|
+
elif isinstance(addr, str):
|
|
989
|
+
# Address string - look up in registry
|
|
990
|
+
result = Address._registry.get(addr)
|
|
991
|
+
return result if result is not None else addr
|
|
992
|
+
else:
|
|
993
|
+
# v4.9.3: Safe passthrough - value is already dereferenced
|
|
994
|
+
return addr
|
|
995
|
+
|
|
996
|
+
def builtin_destroy(self, target: Any) -> bool:
|
|
997
|
+
"""Destroy an object by removing it from memory tracking and calling destructor.
|
|
998
|
+
|
|
999
|
+
Takes an Address, address string, or direct object reference.
|
|
1000
|
+
Returns True if successfully destroyed, False otherwise.
|
|
1001
|
+
|
|
1002
|
+
Example:
|
|
1003
|
+
ptr myPtr = ?data;
|
|
1004
|
+
destroy(myPtr); // Destroy via pointer
|
|
1005
|
+
destroy(address(obj)); // Destroy via address
|
|
1006
|
+
destroy(myInstance); // Destroy instance directly
|
|
1007
|
+
|
|
1008
|
+
For CSSL instances, this calls the destructor (~ConstructorName) if defined.
|
|
1009
|
+
"""
|
|
1010
|
+
from .cssl_types import Address, CSSLInstance
|
|
1011
|
+
|
|
1012
|
+
obj = None
|
|
1013
|
+
addr_key = None
|
|
1014
|
+
|
|
1015
|
+
# Resolve the target to get the actual object
|
|
1016
|
+
if isinstance(target, Address):
|
|
1017
|
+
addr_key = str(target)
|
|
1018
|
+
obj = target.reflect()
|
|
1019
|
+
elif isinstance(target, str) and target.startswith('0x'):
|
|
1020
|
+
addr_key = target
|
|
1021
|
+
obj = Address._registry.get(target)
|
|
1022
|
+
else:
|
|
1023
|
+
# Direct object reference
|
|
1024
|
+
obj = target
|
|
1025
|
+
addr_key = hex(id(obj))
|
|
1026
|
+
|
|
1027
|
+
if obj is None:
|
|
1028
|
+
return False
|
|
1029
|
+
|
|
1030
|
+
# Call destructor if it's a CSSL instance
|
|
1031
|
+
if isinstance(obj, CSSLInstance):
|
|
1032
|
+
# Try to call destructor (~ConstructorName)
|
|
1033
|
+
if hasattr(obj, '_class') and obj._class:
|
|
1034
|
+
class_def = obj._class
|
|
1035
|
+
# Look for destructor in class definition
|
|
1036
|
+
if hasattr(class_def, 'node') and class_def.node:
|
|
1037
|
+
for child in class_def.node.children:
|
|
1038
|
+
if child.type == 'destructor':
|
|
1039
|
+
# Destructor exists - would need runtime to call it
|
|
1040
|
+
pass
|
|
1041
|
+
|
|
1042
|
+
# Remove from Address registry
|
|
1043
|
+
if addr_key and addr_key in Address._registry:
|
|
1044
|
+
del Address._registry[addr_key]
|
|
1045
|
+
|
|
1046
|
+
# Clear object contents if possible
|
|
1047
|
+
if hasattr(obj, 'clear') and callable(obj.clear):
|
|
1048
|
+
obj.clear()
|
|
1049
|
+
elif isinstance(obj, list):
|
|
1050
|
+
obj.clear()
|
|
1051
|
+
elif isinstance(obj, dict):
|
|
1052
|
+
obj.clear()
|
|
1053
|
+
|
|
1054
|
+
return True
|
|
1055
|
+
|
|
1056
|
+
def builtin_execute(self, code: str, context: dict = None) -> Any:
|
|
1057
|
+
"""v4.9.2: Execute CSSL code string inline.
|
|
1058
|
+
|
|
1059
|
+
Usage:
|
|
1060
|
+
execute("x = 5; y = x * 2;"); // Execute statements
|
|
1061
|
+
result = execute("return 5 + 3;"); // Get return value
|
|
1062
|
+
execute("printl('hello');"); // Side effects
|
|
1063
|
+
execute(code, {"name": "value"}); // With context variables
|
|
1064
|
+
|
|
1065
|
+
Args:
|
|
1066
|
+
code: CSSL code string to execute
|
|
1067
|
+
context: Optional dict of variables to inject into scope
|
|
1068
|
+
|
|
1069
|
+
Returns:
|
|
1070
|
+
The result of the last expression or explicit return
|
|
1071
|
+
"""
|
|
1072
|
+
if not self._runtime:
|
|
1073
|
+
return None
|
|
1074
|
+
|
|
1075
|
+
try:
|
|
1076
|
+
from .cssl_parser import parse_cssl_program
|
|
1077
|
+
|
|
1078
|
+
# Parse the code
|
|
1079
|
+
ast = parse_cssl_program(code)
|
|
1080
|
+
if not ast or not ast.children:
|
|
1081
|
+
return None
|
|
1082
|
+
|
|
1083
|
+
# Inject context variables into scope if provided
|
|
1084
|
+
if context and isinstance(context, dict):
|
|
1085
|
+
for name, value in context.items():
|
|
1086
|
+
self._runtime.scope.set(name, value)
|
|
1087
|
+
|
|
1088
|
+
# Execute each statement
|
|
1089
|
+
result = None
|
|
1090
|
+
for node in ast.children:
|
|
1091
|
+
result = self._runtime._execute(node)
|
|
1092
|
+
# Check for early return
|
|
1093
|
+
if self._runtime._return_triggered:
|
|
1094
|
+
result = self._runtime._return_value
|
|
1095
|
+
self._runtime._return_triggered = False
|
|
1096
|
+
self._runtime._return_value = None
|
|
1097
|
+
break
|
|
1098
|
+
|
|
1099
|
+
return result
|
|
1100
|
+
except Exception as e:
|
|
1101
|
+
# Return error info instead of raising
|
|
1102
|
+
return {'error': str(e), 'type': type(e).__name__}
|
|
1103
|
+
|
|
573
1104
|
def builtin_isinstance(self, value: Any, type_name: str) -> bool:
|
|
574
1105
|
"""Check if value is of type"""
|
|
575
1106
|
type_map = {
|
|
@@ -695,59 +1226,66 @@ class CSSLBuiltins:
|
|
|
695
1226
|
return lst
|
|
696
1227
|
|
|
697
1228
|
# v4.5.1: For CSSL typed containers, modify in place
|
|
698
|
-
|
|
699
|
-
|
|
1229
|
+
# v4.8.6: Added List to in-place modification
|
|
1230
|
+
# v4.8.6: Also modify plain Python lists in-place (fixes nested dict/array push)
|
|
1231
|
+
from .cssl_types import Stack, Vector, Array, DataStruct, List
|
|
1232
|
+
if isinstance(lst, (Stack, Vector, Array, DataStruct, List, list)):
|
|
700
1233
|
for item in items:
|
|
701
1234
|
lst.append(item)
|
|
702
1235
|
return lst
|
|
703
1236
|
|
|
704
|
-
# For
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
return
|
|
1237
|
+
# For other iterables (tuples, etc.), create a new list
|
|
1238
|
+
new_lst = list(lst)
|
|
1239
|
+
new_lst.extend(items)
|
|
1240
|
+
return new_lst
|
|
708
1241
|
|
|
709
1242
|
def builtin_pop(self, lst: list, index: int = -1) -> Any:
|
|
710
1243
|
"""Pop item from list/stack/vector.
|
|
711
1244
|
|
|
712
1245
|
v4.5.1: For Stack/Vector/Array types, modifies in place.
|
|
1246
|
+
v4.8.6: Also modifies plain Python lists in place.
|
|
713
1247
|
"""
|
|
714
1248
|
if lst is None:
|
|
715
1249
|
return None
|
|
716
1250
|
|
|
717
1251
|
# v4.5.1: For CSSL typed containers, modify in place
|
|
718
|
-
|
|
719
|
-
|
|
1252
|
+
# v4.8.6: Also modify plain Python lists in-place
|
|
1253
|
+
from .cssl_types import Stack, Vector, Array, DataStruct, List
|
|
1254
|
+
if isinstance(lst, (Stack, Vector, Array, DataStruct, List, list)):
|
|
720
1255
|
if len(lst) == 0:
|
|
721
1256
|
return None
|
|
722
1257
|
return lst.pop(index)
|
|
723
1258
|
|
|
724
|
-
# For
|
|
725
|
-
|
|
726
|
-
return
|
|
1259
|
+
# For other iterables, create a copy
|
|
1260
|
+
new_lst = list(lst)
|
|
1261
|
+
return new_lst.pop(index) if new_lst else None
|
|
727
1262
|
|
|
728
1263
|
def builtin_shift(self, lst: list) -> Any:
|
|
729
1264
|
"""Remove and return first element.
|
|
730
1265
|
|
|
731
1266
|
v4.5.1: For Stack/Vector/Array types, modifies in place.
|
|
1267
|
+
v4.8.6: Also modifies plain Python lists in place.
|
|
732
1268
|
"""
|
|
733
1269
|
if lst is None:
|
|
734
1270
|
return None
|
|
735
1271
|
|
|
736
1272
|
# v4.5.1: For CSSL typed containers, modify in place
|
|
737
|
-
|
|
738
|
-
|
|
1273
|
+
# v4.8.6: Also modify plain Python lists in-place
|
|
1274
|
+
from .cssl_types import Stack, Vector, Array, DataStruct, List
|
|
1275
|
+
if isinstance(lst, (Stack, Vector, Array, DataStruct, List, list)):
|
|
739
1276
|
if len(lst) == 0:
|
|
740
1277
|
return None
|
|
741
1278
|
return lst.pop(0)
|
|
742
1279
|
|
|
743
|
-
# For
|
|
744
|
-
|
|
745
|
-
return
|
|
1280
|
+
# For other iterables, create a copy
|
|
1281
|
+
new_lst = list(lst)
|
|
1282
|
+
return new_lst.pop(0) if new_lst else None
|
|
746
1283
|
|
|
747
1284
|
def builtin_unshift(self, lst: list, *items) -> list:
|
|
748
1285
|
"""Add items to the front of a list/stack/vector.
|
|
749
1286
|
|
|
750
1287
|
v4.5.1: For Stack/Vector/Array types, modifies in place.
|
|
1288
|
+
v4.8.6: Also modifies plain Python lists in place.
|
|
751
1289
|
"""
|
|
752
1290
|
if lst is None:
|
|
753
1291
|
lst = []
|
|
@@ -756,17 +1294,18 @@ class CSSLBuiltins:
|
|
|
756
1294
|
return lst
|
|
757
1295
|
|
|
758
1296
|
# v4.5.1: For CSSL typed containers, modify in place
|
|
759
|
-
|
|
760
|
-
|
|
1297
|
+
# v4.8.6: Also modify plain Python lists in-place
|
|
1298
|
+
from .cssl_types import Stack, Vector, Array, DataStruct, List
|
|
1299
|
+
if isinstance(lst, (Stack, Vector, Array, DataStruct, List, list)):
|
|
761
1300
|
for item in reversed(items):
|
|
762
1301
|
lst.insert(0, item)
|
|
763
1302
|
return lst
|
|
764
1303
|
|
|
765
|
-
# For
|
|
766
|
-
|
|
767
|
-
for item in reversed(
|
|
768
|
-
|
|
769
|
-
return
|
|
1304
|
+
# For other iterables, create a copy
|
|
1305
|
+
new_lst = list(lst)
|
|
1306
|
+
for item in reversed(new_lst):
|
|
1307
|
+
new_lst.insert(0, item)
|
|
1308
|
+
return new_lst
|
|
770
1309
|
|
|
771
1310
|
def builtin_slice(self, value: Union[str, list], start: int, end: int = None) -> Union[str, list]:
|
|
772
1311
|
if end is None:
|
|
@@ -1017,18 +1556,53 @@ class CSSLBuiltins:
|
|
|
1017
1556
|
|
|
1018
1557
|
# ============= File I/O Functions =============
|
|
1019
1558
|
|
|
1559
|
+
# v4.7.1: Path validation helper to prevent directory traversal attacks
|
|
1560
|
+
def _validate_path(self, path: str, allow_write: bool = False) -> str:
|
|
1561
|
+
"""Validate file path for security.
|
|
1562
|
+
|
|
1563
|
+
v4.7.1: Prevents directory traversal attacks by checking for '..' sequences
|
|
1564
|
+
and ensuring paths are within reasonable bounds.
|
|
1565
|
+
|
|
1566
|
+
Args:
|
|
1567
|
+
path: The file path to validate
|
|
1568
|
+
allow_write: If True, allows write operations
|
|
1569
|
+
|
|
1570
|
+
Returns:
|
|
1571
|
+
The normalized absolute path
|
|
1572
|
+
|
|
1573
|
+
Raises:
|
|
1574
|
+
CSSLBuiltinError: If path is invalid or attempts traversal
|
|
1575
|
+
"""
|
|
1576
|
+
if not isinstance(path, str) or not path:
|
|
1577
|
+
raise CSSLBuiltinError("Invalid path: path must be a non-empty string")
|
|
1578
|
+
|
|
1579
|
+
# Normalize the path
|
|
1580
|
+
normalized = os.path.normpath(path)
|
|
1581
|
+
|
|
1582
|
+
# Check for directory traversal attempts
|
|
1583
|
+
if '..' in normalized:
|
|
1584
|
+
raise CSSLBuiltinError("Directory traversal not allowed in path")
|
|
1585
|
+
|
|
1586
|
+
return normalized
|
|
1587
|
+
|
|
1020
1588
|
def builtin_read(self, path: str, encoding: str = 'utf-8') -> str:
|
|
1021
1589
|
"""Read entire file content.
|
|
1022
1590
|
Usage: read('/path/to/file.txt')
|
|
1591
|
+
v4.7.1: Added path validation
|
|
1023
1592
|
"""
|
|
1024
|
-
|
|
1593
|
+
validated_path = self._validate_path(path)
|
|
1594
|
+
with open(validated_path, 'r', encoding=encoding) as f:
|
|
1025
1595
|
return f.read()
|
|
1026
1596
|
|
|
1027
1597
|
def builtin_readline(self, line: int, path: str, encoding: str = 'utf-8') -> str:
|
|
1028
1598
|
"""Read specific line from file (1-indexed).
|
|
1029
1599
|
Usage: readline(5, '/path/to/file.txt') -> returns line 5
|
|
1600
|
+
v4.7.1: Added path validation and line number validation
|
|
1030
1601
|
"""
|
|
1031
|
-
|
|
1602
|
+
if not isinstance(line, int) or line < 1:
|
|
1603
|
+
raise CSSLBuiltinError("Line number must be a positive integer")
|
|
1604
|
+
validated_path = self._validate_path(path)
|
|
1605
|
+
with open(validated_path, 'r', encoding=encoding) as f:
|
|
1032
1606
|
for i, file_line in enumerate(f, 1):
|
|
1033
1607
|
if i == line:
|
|
1034
1608
|
return file_line.rstrip('\n\r')
|
|
@@ -1037,18 +1611,25 @@ class CSSLBuiltins:
|
|
|
1037
1611
|
def builtin_write(self, path: str, content: str, encoding: str = 'utf-8') -> int:
|
|
1038
1612
|
"""Write content to file, returns chars written.
|
|
1039
1613
|
Usage: write('/path/to/file.txt', 'Hello World')
|
|
1614
|
+
v4.7.1: Added path validation
|
|
1040
1615
|
"""
|
|
1041
|
-
|
|
1042
|
-
|
|
1616
|
+
validated_path = self._validate_path(path, allow_write=True)
|
|
1617
|
+
with open(validated_path, 'w', encoding=encoding) as f:
|
|
1618
|
+
return f.write(str(content) if content is not None else '')
|
|
1043
1619
|
|
|
1044
1620
|
def builtin_writeline(self, line: int, content: str, path: str, encoding: str = 'utf-8') -> bool:
|
|
1045
1621
|
"""Write/replace specific line in file (1-indexed).
|
|
1046
1622
|
Usage: writeline(5, 'New content', '/path/to/file.txt')
|
|
1623
|
+
v4.7.1: Added path and line number validation
|
|
1047
1624
|
"""
|
|
1625
|
+
if not isinstance(line, int) or line < 1:
|
|
1626
|
+
raise CSSLBuiltinError("Line number must be a positive integer")
|
|
1627
|
+
validated_path = self._validate_path(path, allow_write=True)
|
|
1628
|
+
|
|
1048
1629
|
# Read all lines
|
|
1049
1630
|
lines = []
|
|
1050
|
-
if os.path.exists(
|
|
1051
|
-
with open(
|
|
1631
|
+
if os.path.exists(validated_path):
|
|
1632
|
+
with open(validated_path, 'r', encoding=encoding) as f:
|
|
1052
1633
|
lines = f.readlines()
|
|
1053
1634
|
|
|
1054
1635
|
# Ensure we have enough lines
|
|
@@ -1056,67 +1637,81 @@ class CSSLBuiltins:
|
|
|
1056
1637
|
lines.append('\n')
|
|
1057
1638
|
|
|
1058
1639
|
# Replace the specific line (1-indexed)
|
|
1059
|
-
if not
|
|
1060
|
-
|
|
1061
|
-
|
|
1640
|
+
content_str = str(content) if content is not None else ''
|
|
1641
|
+
if not content_str.endswith('\n'):
|
|
1642
|
+
content_str = content_str + '\n'
|
|
1643
|
+
lines[line - 1] = content_str
|
|
1062
1644
|
|
|
1063
1645
|
# Write back
|
|
1064
|
-
with open(
|
|
1646
|
+
with open(validated_path, 'w', encoding=encoding) as f:
|
|
1065
1647
|
f.writelines(lines)
|
|
1066
1648
|
return True
|
|
1067
1649
|
|
|
1068
1650
|
def builtin_readfile(self, path: str, encoding: str = 'utf-8') -> str:
|
|
1069
|
-
"""Read entire file content"""
|
|
1070
|
-
|
|
1651
|
+
"""Read entire file content. v4.7.1: Added path validation"""
|
|
1652
|
+
validated_path = self._validate_path(path)
|
|
1653
|
+
with open(validated_path, 'r', encoding=encoding) as f:
|
|
1071
1654
|
return f.read()
|
|
1072
1655
|
|
|
1073
1656
|
def builtin_writefile(self, path: str, content: str, encoding: str = 'utf-8') -> int:
|
|
1074
|
-
"""Write content to file, returns bytes written"""
|
|
1075
|
-
|
|
1076
|
-
|
|
1657
|
+
"""Write content to file, returns bytes written. v4.7.1: Added path validation"""
|
|
1658
|
+
validated_path = self._validate_path(path, allow_write=True)
|
|
1659
|
+
with open(validated_path, 'w', encoding=encoding) as f:
|
|
1660
|
+
return f.write(str(content) if content is not None else '')
|
|
1077
1661
|
|
|
1078
1662
|
def builtin_appendfile(self, path: str, content: str, encoding: str = 'utf-8') -> int:
|
|
1079
|
-
"""Append content to file, returns bytes written"""
|
|
1080
|
-
|
|
1081
|
-
|
|
1663
|
+
"""Append content to file, returns bytes written. v4.7.1: Added path validation"""
|
|
1664
|
+
validated_path = self._validate_path(path, allow_write=True)
|
|
1665
|
+
with open(validated_path, 'a', encoding=encoding) as f:
|
|
1666
|
+
return f.write(str(content) if content is not None else '')
|
|
1082
1667
|
|
|
1083
1668
|
def builtin_readlines(self, path: str, encoding: str = 'utf-8') -> list:
|
|
1084
|
-
"""Read file lines into list"""
|
|
1085
|
-
|
|
1669
|
+
"""Read file lines into list. v4.7.1: Added path validation"""
|
|
1670
|
+
validated_path = self._validate_path(path)
|
|
1671
|
+
with open(validated_path, 'r', encoding=encoding) as f:
|
|
1086
1672
|
return f.readlines()
|
|
1087
1673
|
|
|
1088
1674
|
def builtin_listdir(self, path: str = '.') -> list:
|
|
1089
|
-
"""List directory contents"""
|
|
1090
|
-
|
|
1675
|
+
"""List directory contents. v4.7.1: Added path validation"""
|
|
1676
|
+
validated_path = self._validate_path(path) if path != '.' else '.'
|
|
1677
|
+
return os.listdir(validated_path)
|
|
1091
1678
|
|
|
1092
1679
|
def builtin_makedirs(self, path: str, exist_ok: bool = True) -> bool:
|
|
1093
|
-
"""Create directories recursively"""
|
|
1094
|
-
|
|
1680
|
+
"""Create directories recursively. v4.7.1: Added path validation"""
|
|
1681
|
+
validated_path = self._validate_path(path, allow_write=True)
|
|
1682
|
+
os.makedirs(validated_path, exist_ok=exist_ok)
|
|
1095
1683
|
return True
|
|
1096
1684
|
|
|
1097
1685
|
def builtin_removefile(self, path: str) -> bool:
|
|
1098
|
-
"""Remove a file"""
|
|
1099
|
-
|
|
1686
|
+
"""Remove a file. v4.7.1: Added path validation"""
|
|
1687
|
+
validated_path = self._validate_path(path, allow_write=True)
|
|
1688
|
+
os.remove(validated_path)
|
|
1100
1689
|
return True
|
|
1101
1690
|
|
|
1102
1691
|
def builtin_removedir(self, path: str) -> bool:
|
|
1103
|
-
"""Remove an empty directory"""
|
|
1104
|
-
|
|
1692
|
+
"""Remove an empty directory. v4.7.1: Added path validation"""
|
|
1693
|
+
validated_path = self._validate_path(path, allow_write=True)
|
|
1694
|
+
os.rmdir(validated_path)
|
|
1105
1695
|
return True
|
|
1106
1696
|
|
|
1107
1697
|
def builtin_copyfile(self, src: str, dst: str) -> str:
|
|
1108
|
-
"""Copy a file, returns destination path"""
|
|
1698
|
+
"""Copy a file, returns destination path. v4.7.1: Added path validation"""
|
|
1109
1699
|
import shutil
|
|
1110
|
-
|
|
1700
|
+
validated_src = self._validate_path(src)
|
|
1701
|
+
validated_dst = self._validate_path(dst, allow_write=True)
|
|
1702
|
+
return shutil.copy2(validated_src, validated_dst)
|
|
1111
1703
|
|
|
1112
1704
|
def builtin_movefile(self, src: str, dst: str) -> str:
|
|
1113
|
-
"""Move a file, returns destination path"""
|
|
1705
|
+
"""Move a file, returns destination path. v4.7.1: Added path validation"""
|
|
1114
1706
|
import shutil
|
|
1115
|
-
|
|
1707
|
+
validated_src = self._validate_path(src, allow_write=True)
|
|
1708
|
+
validated_dst = self._validate_path(dst, allow_write=True)
|
|
1709
|
+
return shutil.move(validated_src, validated_dst)
|
|
1116
1710
|
|
|
1117
1711
|
def builtin_filesize(self, path: str) -> int:
|
|
1118
|
-
"""Get file size in bytes"""
|
|
1119
|
-
|
|
1712
|
+
"""Get file size in bytes. v4.7.1: Added path validation"""
|
|
1713
|
+
validated_path = self._validate_path(path)
|
|
1714
|
+
return os.path.getsize(validated_path)
|
|
1120
1715
|
|
|
1121
1716
|
# ============= JSON Functions =============
|
|
1122
1717
|
|
|
@@ -1387,6 +1982,88 @@ class CSSLBuiltins:
|
|
|
1387
1982
|
# Handle Python objects
|
|
1388
1983
|
return type(obj).__name__
|
|
1389
1984
|
|
|
1985
|
+
def builtin_instance_delete(self, instance: Any, destructor_name: str = None) -> bool:
|
|
1986
|
+
"""v4.8.8: Call destructors on an instance and mark it for cleanup.
|
|
1987
|
+
|
|
1988
|
+
Usage:
|
|
1989
|
+
delete(myInstance); // Calls all destructors
|
|
1990
|
+
delete(myInstance, "Init"); // Calls only ~Init destructor
|
|
1991
|
+
instance::delete(obj, "Setup"); // Calls only ~Setup destructor
|
|
1992
|
+
|
|
1993
|
+
Args:
|
|
1994
|
+
instance: The CSSLInstance to delete
|
|
1995
|
+
destructor_name: Optional - specific destructor name (without ~)
|
|
1996
|
+
|
|
1997
|
+
Returns:
|
|
1998
|
+
True if destructors were called, False if instance has no destructors
|
|
1999
|
+
"""
|
|
2000
|
+
from .cssl_types import CSSLInstance
|
|
2001
|
+
|
|
2002
|
+
if not isinstance(instance, CSSLInstance):
|
|
2003
|
+
return False
|
|
2004
|
+
|
|
2005
|
+
class_def = instance._class
|
|
2006
|
+
destructors = getattr(class_def, 'destructors', [])
|
|
2007
|
+
|
|
2008
|
+
if not destructors:
|
|
2009
|
+
return False
|
|
2010
|
+
|
|
2011
|
+
# Call destructors through the runtime
|
|
2012
|
+
if self.runtime:
|
|
2013
|
+
for destr in destructors:
|
|
2014
|
+
destr_name = destr.value.get('name', '')
|
|
2015
|
+
# If specific destructor requested, only call that one
|
|
2016
|
+
if destructor_name:
|
|
2017
|
+
# Match ~Name or just Name
|
|
2018
|
+
if destr_name != f'~{destructor_name}' and destr_name != destructor_name:
|
|
2019
|
+
continue
|
|
2020
|
+
|
|
2021
|
+
# Call the destructor
|
|
2022
|
+
self.runtime._call_destructor(instance, destr)
|
|
2023
|
+
|
|
2024
|
+
# Mark instance as deleted
|
|
2025
|
+
instance._deleted = True
|
|
2026
|
+
return True
|
|
2027
|
+
|
|
2028
|
+
return False
|
|
2029
|
+
|
|
2030
|
+
def builtin_call_constructor(self, instance: Any, constructor_name: str, *args, **kwargs) -> bool:
|
|
2031
|
+
"""v4.8.8: Manually call a callable constructor on an instance.
|
|
2032
|
+
|
|
2033
|
+
Usage:
|
|
2034
|
+
call_constructor(myInstance, "Setup"); // Calls callable constr Setup()
|
|
2035
|
+
call_constructor(myInstance, "Init", arg1, arg2); // With arguments
|
|
2036
|
+
|
|
2037
|
+
Args:
|
|
2038
|
+
instance: The CSSLInstance to call constructor on
|
|
2039
|
+
constructor_name: Name of the callable constructor
|
|
2040
|
+
*args: Arguments to pass to the constructor
|
|
2041
|
+
|
|
2042
|
+
Returns:
|
|
2043
|
+
True if constructor was called, False if not found
|
|
2044
|
+
"""
|
|
2045
|
+
from .cssl_types import CSSLInstance
|
|
2046
|
+
|
|
2047
|
+
if not isinstance(instance, CSSLInstance):
|
|
2048
|
+
return False
|
|
2049
|
+
|
|
2050
|
+
# Get callable constructors stored on instance
|
|
2051
|
+
callable_constructors = getattr(instance, '_callable_constructors', [])
|
|
2052
|
+
|
|
2053
|
+
if not callable_constructors:
|
|
2054
|
+
return False
|
|
2055
|
+
|
|
2056
|
+
# Call constructor through the runtime
|
|
2057
|
+
if self.runtime:
|
|
2058
|
+
for constr in callable_constructors:
|
|
2059
|
+
constr_name = constr.value.get('name', '')
|
|
2060
|
+
if constr_name == constructor_name:
|
|
2061
|
+
# Call the callable constructor
|
|
2062
|
+
self.runtime._call_constructor(instance, constr, list(args), dict(kwargs), {})
|
|
2063
|
+
return True
|
|
2064
|
+
|
|
2065
|
+
return False
|
|
2066
|
+
|
|
1390
2067
|
def builtin_isavailable(self, name_or_obj: Any) -> bool:
|
|
1391
2068
|
"""Check if a shared instance exists.
|
|
1392
2069
|
Usage:
|
|
@@ -1529,6 +2206,56 @@ class CSSLBuiltins:
|
|
|
1529
2206
|
"""Set environment variable"""
|
|
1530
2207
|
os.environ[name] = value
|
|
1531
2208
|
|
|
2209
|
+
# v4.8.5: os module replacement builtins
|
|
2210
|
+
def builtin_getcwd(self) -> str:
|
|
2211
|
+
"""Get current working directory (replaces os.getcwd())"""
|
|
2212
|
+
return os.getcwd()
|
|
2213
|
+
|
|
2214
|
+
def builtin_chdir(self, path: str) -> bool:
|
|
2215
|
+
"""Change current working directory (replaces os.chdir())"""
|
|
2216
|
+
os.chdir(path)
|
|
2217
|
+
return True
|
|
2218
|
+
|
|
2219
|
+
def builtin_mkdir(self, path: str) -> bool:
|
|
2220
|
+
"""Create single directory (replaces os.mkdir())"""
|
|
2221
|
+
os.mkdir(path)
|
|
2222
|
+
return True
|
|
2223
|
+
|
|
2224
|
+
def builtin_rmdir(self, path: str) -> bool:
|
|
2225
|
+
"""Remove empty directory (alias for removedir, replaces os.rmdir())"""
|
|
2226
|
+
os.rmdir(path)
|
|
2227
|
+
return True
|
|
2228
|
+
|
|
2229
|
+
def builtin_rmfile(self, path: str) -> bool:
|
|
2230
|
+
"""Remove file (alias for removefile, replaces os.remove())"""
|
|
2231
|
+
os.remove(path)
|
|
2232
|
+
return True
|
|
2233
|
+
|
|
2234
|
+
def builtin_rename(self, src: str, dst: str) -> bool:
|
|
2235
|
+
"""Rename file or directory (replaces os.rename())"""
|
|
2236
|
+
os.rename(src, dst)
|
|
2237
|
+
return True
|
|
2238
|
+
|
|
2239
|
+
def builtin_argv(self) -> list:
|
|
2240
|
+
"""Get command line arguments (replaces sys.argv)"""
|
|
2241
|
+
import sys
|
|
2242
|
+
return sys.argv
|
|
2243
|
+
|
|
2244
|
+
def builtin_argc(self) -> int:
|
|
2245
|
+
"""Get argument count (replaces len(sys.argv))"""
|
|
2246
|
+
import sys
|
|
2247
|
+
return len(sys.argv)
|
|
2248
|
+
|
|
2249
|
+
def builtin_platform(self) -> str:
|
|
2250
|
+
"""Get platform name (replaces sys.platform)"""
|
|
2251
|
+
import sys
|
|
2252
|
+
return sys.platform
|
|
2253
|
+
|
|
2254
|
+
def builtin_version(self) -> str:
|
|
2255
|
+
"""Get Python version (replaces sys.version)"""
|
|
2256
|
+
import sys
|
|
2257
|
+
return sys.version
|
|
2258
|
+
|
|
1532
2259
|
def builtin_input(self, prompt: str = '') -> str:
|
|
1533
2260
|
"""Read user input"""
|
|
1534
2261
|
return input(prompt)
|
|
@@ -1536,9 +2263,11 @@ class CSSLBuiltins:
|
|
|
1536
2263
|
def builtin_clear(self) -> None:
|
|
1537
2264
|
"""Clear console screen"""
|
|
1538
2265
|
if os.name == 'nt':
|
|
1539
|
-
|
|
2266
|
+
# Use ANSI escape codes on Windows 10+ (modern terminals support it)
|
|
2267
|
+
# Fall back to subprocess for older Windows
|
|
2268
|
+
print('\033[2J\033[H', end='', flush=True)
|
|
1540
2269
|
else:
|
|
1541
|
-
print('\033[2J\033[H', end='')
|
|
2270
|
+
print('\033[2J\033[H', end='', flush=True)
|
|
1542
2271
|
|
|
1543
2272
|
def builtin_color(self, text: str, color: str) -> str:
|
|
1544
2273
|
"""Apply ANSI color to text"""
|
|
@@ -1558,94 +2287,828 @@ class CSSLBuiltins:
|
|
|
1558
2287
|
"""Delay execution by milliseconds"""
|
|
1559
2288
|
time.sleep(ms / 1000.0)
|
|
1560
2289
|
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
2290
|
+
# =========================================================================
|
|
2291
|
+
# v4.6.5: Individual Color Functions for F-Strings
|
|
2292
|
+
# Usage: string name = f"{red("H")}{green("E")}{yellow("Y")}"
|
|
2293
|
+
# Accepts any type (string, int, float, etc.) - auto-converts to string
|
|
2294
|
+
# =========================================================================
|
|
1564
2295
|
|
|
1565
|
-
|
|
2296
|
+
def _color_str(self, value) -> str:
|
|
2297
|
+
"""Convert any value to string for color functions."""
|
|
2298
|
+
if value is None:
|
|
2299
|
+
return "null"
|
|
2300
|
+
return str(value)
|
|
1566
2301
|
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
2302
|
+
# --- Named Colors (Foreground) ---
|
|
2303
|
+
def builtin_red(self, text) -> str:
|
|
2304
|
+
"""Apply red color: red("hello") or red(123)"""
|
|
2305
|
+
return f'\033[31m{self._color_str(text)}\033[0m'
|
|
1570
2306
|
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
import importlib
|
|
1575
|
-
return importlib.import_module(module_name)
|
|
2307
|
+
def builtin_green(self, text) -> str:
|
|
2308
|
+
"""Apply green color to text"""
|
|
2309
|
+
return f'\033[32m{self._color_str(text)}\033[0m'
|
|
1576
2310
|
|
|
1577
|
-
|
|
2311
|
+
def builtin_blue(self, text) -> str:
|
|
2312
|
+
"""Apply blue color to text"""
|
|
2313
|
+
return f'\033[34m{self._color_str(text)}\033[0m'
|
|
1578
2314
|
|
|
1579
|
-
def
|
|
1580
|
-
"""
|
|
1581
|
-
return
|
|
2315
|
+
def builtin_yellow(self, text) -> str:
|
|
2316
|
+
"""Apply yellow color to text"""
|
|
2317
|
+
return f'\033[33m{self._color_str(text)}\033[0m'
|
|
1582
2318
|
|
|
1583
|
-
def
|
|
1584
|
-
"""
|
|
1585
|
-
return
|
|
2319
|
+
def builtin_cyan(self, text) -> str:
|
|
2320
|
+
"""Apply cyan color to text"""
|
|
2321
|
+
return f'\033[36m{self._color_str(text)}\033[0m'
|
|
1586
2322
|
|
|
1587
|
-
def
|
|
1588
|
-
"""
|
|
1589
|
-
return
|
|
2323
|
+
def builtin_magenta(self, text) -> str:
|
|
2324
|
+
"""Apply magenta color to text"""
|
|
2325
|
+
return f'\033[35m{self._color_str(text)}\033[0m'
|
|
1590
2326
|
|
|
1591
|
-
def
|
|
1592
|
-
"""
|
|
1593
|
-
return
|
|
2327
|
+
def builtin_white(self, text) -> str:
|
|
2328
|
+
"""Apply white color to text"""
|
|
2329
|
+
return f'\033[37m{self._color_str(text)}\033[0m'
|
|
1594
2330
|
|
|
1595
|
-
def
|
|
1596
|
-
|
|
2331
|
+
def builtin_black(self, text) -> str:
|
|
2332
|
+
"""Apply black color to text"""
|
|
2333
|
+
return f'\033[30m{self._color_str(text)}\033[0m'
|
|
1597
2334
|
|
|
1598
|
-
|
|
1599
|
-
|
|
2335
|
+
# --- Bright Color Variants ---
|
|
2336
|
+
def builtin_bright_red(self, text) -> str:
|
|
2337
|
+
"""Apply bright red color to text"""
|
|
2338
|
+
return f'\033[91m{self._color_str(text)}\033[0m'
|
|
1600
2339
|
|
|
1601
|
-
def
|
|
1602
|
-
|
|
2340
|
+
def builtin_bright_green(self, text) -> str:
|
|
2341
|
+
"""Apply bright green color to text"""
|
|
2342
|
+
return f'\033[92m{self._color_str(text)}\033[0m'
|
|
1603
2343
|
|
|
1604
|
-
def
|
|
1605
|
-
|
|
2344
|
+
def builtin_bright_blue(self, text) -> str:
|
|
2345
|
+
"""Apply bright blue color to text"""
|
|
2346
|
+
return f'\033[94m{self._color_str(text)}\033[0m'
|
|
1606
2347
|
|
|
1607
|
-
def
|
|
1608
|
-
|
|
2348
|
+
def builtin_bright_yellow(self, text) -> str:
|
|
2349
|
+
"""Apply bright yellow color to text"""
|
|
2350
|
+
return f'\033[93m{self._color_str(text)}\033[0m'
|
|
1609
2351
|
|
|
1610
|
-
def
|
|
1611
|
-
|
|
2352
|
+
def builtin_bright_cyan(self, text) -> str:
|
|
2353
|
+
"""Apply bright cyan color to text"""
|
|
2354
|
+
return f'\033[96m{self._color_str(text)}\033[0m'
|
|
1612
2355
|
|
|
1613
|
-
def
|
|
1614
|
-
|
|
2356
|
+
def builtin_bright_magenta(self, text) -> str:
|
|
2357
|
+
"""Apply bright magenta color to text"""
|
|
2358
|
+
return f'\033[95m{self._color_str(text)}\033[0m'
|
|
1615
2359
|
|
|
1616
|
-
def
|
|
1617
|
-
|
|
2360
|
+
def builtin_bright_white(self, text) -> str:
|
|
2361
|
+
"""Apply bright white color to text"""
|
|
2362
|
+
return f'\033[97m{self._color_str(text)}\033[0m'
|
|
1618
2363
|
|
|
1619
|
-
|
|
1620
|
-
|
|
2364
|
+
# --- RGB Custom Colors (24-bit True Color) ---
|
|
2365
|
+
def builtin_rgb(self, text, r: int, g: int, b: int) -> str:
|
|
2366
|
+
"""Apply RGB color to text using 24-bit true color.
|
|
1621
2367
|
|
|
1622
|
-
|
|
2368
|
+
Usage: rgb("hello", 255, 128, 0) -> orange text
|
|
2369
|
+
"""
|
|
2370
|
+
r = max(0, min(255, int(r)))
|
|
2371
|
+
g = max(0, min(255, int(g)))
|
|
2372
|
+
b = max(0, min(255, int(b)))
|
|
2373
|
+
return f'\033[38;2;{r};{g};{b}m{self._color_str(text)}\033[0m'
|
|
1623
2374
|
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
2375
|
+
# --- Background Colors ---
|
|
2376
|
+
def builtin_bg_red(self, text) -> str:
|
|
2377
|
+
"""Apply red background to text"""
|
|
2378
|
+
return f'\033[41m{self._color_str(text)}\033[0m'
|
|
1627
2379
|
|
|
1628
|
-
def
|
|
1629
|
-
"""
|
|
1630
|
-
return
|
|
2380
|
+
def builtin_bg_green(self, text) -> str:
|
|
2381
|
+
"""Apply green background to text"""
|
|
2382
|
+
return f'\033[42m{self._color_str(text)}\033[0m'
|
|
1631
2383
|
|
|
1632
|
-
def
|
|
1633
|
-
"""
|
|
1634
|
-
return
|
|
2384
|
+
def builtin_bg_blue(self, text) -> str:
|
|
2385
|
+
"""Apply blue background to text"""
|
|
2386
|
+
return f'\033[44m{self._color_str(text)}\033[0m'
|
|
1635
2387
|
|
|
1636
|
-
def
|
|
1637
|
-
"""
|
|
1638
|
-
|
|
1639
|
-
return sorted(lst, key=lambda x: x.get(key) if isinstance(x, dict) else x, reverse=reverse)
|
|
1640
|
-
return sorted(lst, reverse=reverse)
|
|
2388
|
+
def builtin_bg_yellow(self, text) -> str:
|
|
2389
|
+
"""Apply yellow background to text"""
|
|
2390
|
+
return f'\033[43m{self._color_str(text)}\033[0m'
|
|
1641
2391
|
|
|
1642
|
-
def
|
|
1643
|
-
"""
|
|
1644
|
-
return
|
|
2392
|
+
def builtin_bg_cyan(self, text) -> str:
|
|
2393
|
+
"""Apply cyan background to text"""
|
|
2394
|
+
return f'\033[46m{self._color_str(text)}\033[0m'
|
|
1645
2395
|
|
|
1646
|
-
def
|
|
1647
|
-
"""
|
|
1648
|
-
return
|
|
2396
|
+
def builtin_bg_magenta(self, text) -> str:
|
|
2397
|
+
"""Apply magenta background to text"""
|
|
2398
|
+
return f'\033[45m{self._color_str(text)}\033[0m'
|
|
2399
|
+
|
|
2400
|
+
def builtin_bg_white(self, text) -> str:
|
|
2401
|
+
"""Apply white background to text"""
|
|
2402
|
+
return f'\033[47m{self._color_str(text)}\033[0m'
|
|
2403
|
+
|
|
2404
|
+
def builtin_bg_black(self, text) -> str:
|
|
2405
|
+
"""Apply black background to text"""
|
|
2406
|
+
return f'\033[40m{self._color_str(text)}\033[0m'
|
|
2407
|
+
|
|
2408
|
+
def builtin_bg_rgb(self, text, r: int, g: int, b: int) -> str:
|
|
2409
|
+
"""Apply RGB background color using 24-bit true color.
|
|
2410
|
+
|
|
2411
|
+
Usage: bg_rgb("hello", 255, 128, 0) -> orange background
|
|
2412
|
+
"""
|
|
2413
|
+
r = max(0, min(255, int(r)))
|
|
2414
|
+
g = max(0, min(255, int(g)))
|
|
2415
|
+
b = max(0, min(255, int(b)))
|
|
2416
|
+
return f'\033[48;2;{r};{g};{b}m{self._color_str(text)}\033[0m'
|
|
2417
|
+
|
|
2418
|
+
# --- Text Style Functions ---
|
|
2419
|
+
def builtin_bold(self, text) -> str:
|
|
2420
|
+
"""Apply bold style to text"""
|
|
2421
|
+
return f'\033[1m{self._color_str(text)}\033[0m'
|
|
2422
|
+
|
|
2423
|
+
def builtin_italic(self, text) -> str:
|
|
2424
|
+
"""Apply italic/cursive style to text"""
|
|
2425
|
+
return f'\033[3m{self._color_str(text)}\033[0m'
|
|
2426
|
+
|
|
2427
|
+
def builtin_underline(self, text) -> str:
|
|
2428
|
+
"""Apply underline style to text"""
|
|
2429
|
+
return f'\033[4m{self._color_str(text)}\033[0m'
|
|
2430
|
+
|
|
2431
|
+
def builtin_dim(self, text) -> str:
|
|
2432
|
+
"""Apply dim/faint style to text"""
|
|
2433
|
+
return f'\033[2m{self._color_str(text)}\033[0m'
|
|
2434
|
+
|
|
2435
|
+
def builtin_blink(self, text) -> str:
|
|
2436
|
+
"""Apply blinking style to text (not supported in all terminals)"""
|
|
2437
|
+
return f'\033[5m{self._color_str(text)}\033[0m'
|
|
2438
|
+
|
|
2439
|
+
def builtin_reverse_style(self, text) -> str:
|
|
2440
|
+
"""Reverse foreground and background colors"""
|
|
2441
|
+
return f'\033[7m{self._color_str(text)}\033[0m'
|
|
2442
|
+
|
|
2443
|
+
def builtin_strikethrough(self, text) -> str:
|
|
2444
|
+
"""Apply strikethrough style to text"""
|
|
2445
|
+
return f'\033[9m{self._color_str(text)}\033[0m'
|
|
2446
|
+
|
|
2447
|
+
def builtin_reset(self, text="") -> str:
|
|
2448
|
+
"""Reset all styles. Can wrap text or return just the reset code.
|
|
2449
|
+
|
|
2450
|
+
Usage:
|
|
2451
|
+
reset("text") -> returns text without any styles
|
|
2452
|
+
reset() -> returns the ANSI reset code
|
|
2453
|
+
"""
|
|
2454
|
+
if text:
|
|
2455
|
+
return f'\033[0m{self._color_str(text)}\033[0m'
|
|
2456
|
+
return '\033[0m'
|
|
2457
|
+
|
|
2458
|
+
# v4.8.5: Blocked modules for pyimport (os/sys replaced by CSSL builtins)
|
|
2459
|
+
# v4.8.8: Added importlib, ctypes, builtins to prevent security bypasses
|
|
2460
|
+
BLOCKED_MODULES = {'os', 'sys', 'subprocess', 'shutil', 'importlib', 'ctypes', 'builtins'}
|
|
2461
|
+
|
|
2462
|
+
def builtin_pyimport(self, module_name: str) -> Any:
|
|
2463
|
+
"""
|
|
2464
|
+
Import a Python module for use in CSSL.
|
|
2465
|
+
|
|
2466
|
+
v4.8.5: All modules allowed EXCEPT os, sys, subprocess, shutil.
|
|
2467
|
+
Use CSSL builtins for filesystem/system operations instead:
|
|
2468
|
+
- env(), setenv() for environment variables
|
|
2469
|
+
- getcwd(), chdir() for directory operations
|
|
2470
|
+
- readfile(), writefile() for file I/O
|
|
2471
|
+
- initsh() for shell commands (restricted)
|
|
2472
|
+
- exit() for program termination
|
|
2473
|
+
- argv, argc for command line args
|
|
2474
|
+
|
|
2475
|
+
Usage:
|
|
2476
|
+
@math = pyimport("math");
|
|
2477
|
+
result = math.sqrt(16);
|
|
2478
|
+
|
|
2479
|
+
@requests = pyimport("requests");
|
|
2480
|
+
response = requests.get("https://api.example.com");
|
|
2481
|
+
"""
|
|
2482
|
+
import importlib
|
|
2483
|
+
# Check if module is blocked
|
|
2484
|
+
base_module = module_name.split('.')[0]
|
|
2485
|
+
if base_module in self.BLOCKED_MODULES:
|
|
2486
|
+
alternatives = {
|
|
2487
|
+
'os': 'Use CSSL builtins: env(), setenv(), getcwd(), chdir(), readfile(), writefile(), mkdir(), rmdir(), listdir()',
|
|
2488
|
+
'sys': 'Use CSSL builtins: exit(), argv, argc, parameter',
|
|
2489
|
+
'subprocess': 'Use CSSL builtin: initsh() for shell commands',
|
|
2490
|
+
'shutil': 'Use CSSL builtins: copyfile(), movefile(), rmfile()'
|
|
2491
|
+
}
|
|
2492
|
+
alt_msg = alternatives.get(base_module, 'Use CSSL builtins instead')
|
|
2493
|
+
raise RuntimeError(
|
|
2494
|
+
f"Module '{module_name}' is blocked for security.\n{alt_msg}"
|
|
2495
|
+
)
|
|
2496
|
+
return importlib.import_module(module_name)
|
|
2497
|
+
|
|
2498
|
+
# ============= v4.8.4: C++ Import & I/O Streams =============
|
|
2499
|
+
|
|
2500
|
+
# Global stream instances (singleton pattern for C++ behavior)
|
|
2501
|
+
_cout_instance = None
|
|
2502
|
+
_cin_instance = None
|
|
2503
|
+
_cerr_instance = None
|
|
2504
|
+
_clog_instance = None
|
|
2505
|
+
|
|
2506
|
+
def builtin_cppimport(self, module_name: str) -> Any:
|
|
2507
|
+
"""Import a compiled C++ module from IncludeCPP.
|
|
2508
|
+
|
|
2509
|
+
Supports importing IncludeCPP modules directly into CSSL.
|
|
2510
|
+
Uses the C++ optimized module if available.
|
|
2511
|
+
|
|
2512
|
+
Usage:
|
|
2513
|
+
@mathlib = cppimport("mathlib");
|
|
2514
|
+
result = mathlib.add(5, 3);
|
|
2515
|
+
|
|
2516
|
+
// Or with full path:
|
|
2517
|
+
@mod = cppimport("includecpp.mathlib");
|
|
2518
|
+
"""
|
|
2519
|
+
try:
|
|
2520
|
+
# Check if it's an includecpp module request
|
|
2521
|
+
if module_name.startswith('includecpp.'):
|
|
2522
|
+
actual_name = module_name[11:] # Remove 'includecpp.' prefix
|
|
2523
|
+
else:
|
|
2524
|
+
actual_name = module_name
|
|
2525
|
+
|
|
2526
|
+
# Try to import from includecpp
|
|
2527
|
+
try:
|
|
2528
|
+
import includecpp
|
|
2529
|
+
if hasattr(includecpp, actual_name):
|
|
2530
|
+
return getattr(includecpp, actual_name)
|
|
2531
|
+
|
|
2532
|
+
# Try using CppApi directly
|
|
2533
|
+
api = includecpp.CppApi()
|
|
2534
|
+
if actual_name in api.registry:
|
|
2535
|
+
return api.include(actual_name)
|
|
2536
|
+
except Exception:
|
|
2537
|
+
pass
|
|
2538
|
+
|
|
2539
|
+
# Fallback: try as a regular Python module (for .pyd files)
|
|
2540
|
+
import importlib
|
|
2541
|
+
return importlib.import_module(module_name)
|
|
2542
|
+
|
|
2543
|
+
except Exception as e:
|
|
2544
|
+
raise RuntimeError(f"Failed to import C++ module '{module_name}': {e}")
|
|
2545
|
+
|
|
2546
|
+
def builtin_include(self, module_path: str) -> Any:
|
|
2547
|
+
"""Include a module (IncludeCPP or file).
|
|
2548
|
+
|
|
2549
|
+
Usage:
|
|
2550
|
+
@mod = include("includecpp.mathlib"); // C++ module
|
|
2551
|
+
@header = include("./myheader.h"); // Header file
|
|
2552
|
+
"""
|
|
2553
|
+
if module_path.startswith('includecpp.'):
|
|
2554
|
+
return self.builtin_cppimport(module_path)
|
|
2555
|
+
elif module_path.endswith(('.h', '.hpp', '.cpp', '.c')):
|
|
2556
|
+
# Include as source file (read content)
|
|
2557
|
+
return self.builtin_readfile(module_path)
|
|
2558
|
+
else:
|
|
2559
|
+
return self.builtin_cppimport(module_path)
|
|
2560
|
+
|
|
2561
|
+
def builtin_includecpp(self, cpp_proj_path: str, module_name: str) -> Any:
|
|
2562
|
+
"""Import a pre-built C++ module from a cpp.proj project.
|
|
2563
|
+
|
|
2564
|
+
This is the CSSL equivalent of Python's:
|
|
2565
|
+
from includecpp import module_name
|
|
2566
|
+
|
|
2567
|
+
IMPORTANT: The module must be built first using `includecpp rebuild`.
|
|
2568
|
+
This function only imports already-built modules.
|
|
2569
|
+
|
|
2570
|
+
How it works:
|
|
2571
|
+
1. Reads cpp.proj to find BaseDir (where built modules are stored)
|
|
2572
|
+
2. Adds BaseDir/bindings to Python path
|
|
2573
|
+
3. Imports the module (api_modulename.pyd)
|
|
2574
|
+
|
|
2575
|
+
Usage:
|
|
2576
|
+
// Import a pre-built C++ module
|
|
2577
|
+
@math = includecpp("C:/projects/mylib/cpp.proj", "fastmath");
|
|
2578
|
+
result = math.calculate(42);
|
|
2579
|
+
|
|
2580
|
+
// Relative path works too
|
|
2581
|
+
@crypto = includecpp("./crypto/cpp.proj", "crypto");
|
|
2582
|
+
|
|
2583
|
+
Args:
|
|
2584
|
+
cpp_proj_path: Path to the cpp.proj file (or directory containing it)
|
|
2585
|
+
module_name: Name of the module to import
|
|
2586
|
+
|
|
2587
|
+
Returns:
|
|
2588
|
+
The imported C++ module wrapper
|
|
2589
|
+
|
|
2590
|
+
Raises:
|
|
2591
|
+
RuntimeError: If cpp.proj not found, module not built, or import fails
|
|
2592
|
+
"""
|
|
2593
|
+
import os
|
|
2594
|
+
import sys
|
|
2595
|
+
import json
|
|
2596
|
+
import platform
|
|
2597
|
+
from pathlib import Path
|
|
2598
|
+
|
|
2599
|
+
try:
|
|
2600
|
+
# Resolve the cpp.proj path
|
|
2601
|
+
proj_path = Path(cpp_proj_path)
|
|
2602
|
+
if not proj_path.is_absolute():
|
|
2603
|
+
proj_path = Path(os.getcwd()) / proj_path
|
|
2604
|
+
|
|
2605
|
+
# Handle both file and directory paths
|
|
2606
|
+
if proj_path.is_dir():
|
|
2607
|
+
proj_path = proj_path / "cpp.proj"
|
|
2608
|
+
elif proj_path.name != "cpp.proj":
|
|
2609
|
+
proj_path = proj_path / "cpp.proj"
|
|
2610
|
+
|
|
2611
|
+
if not proj_path.exists():
|
|
2612
|
+
raise RuntimeError(f"cpp.proj not found: {proj_path}")
|
|
2613
|
+
|
|
2614
|
+
# Load cpp.proj to get BaseDir
|
|
2615
|
+
with open(proj_path, 'r', encoding='utf-8') as f:
|
|
2616
|
+
config = json.load(f)
|
|
2617
|
+
|
|
2618
|
+
# Get BaseDir from config
|
|
2619
|
+
if 'BaseDir' not in config:
|
|
2620
|
+
project_name = config.get('project', 'unnamed')
|
|
2621
|
+
if platform.system() == "Windows":
|
|
2622
|
+
appdata = Path(os.getenv('APPDATA', Path.home() / 'AppData' / 'Roaming'))
|
|
2623
|
+
else:
|
|
2624
|
+
appdata = Path.home() / ".local" / "share" / "includecpp"
|
|
2625
|
+
base_dir = appdata / f"{project_name}-gcc-build-proj"
|
|
2626
|
+
else:
|
|
2627
|
+
base_dir = Path(config['BaseDir'])
|
|
2628
|
+
|
|
2629
|
+
bindings_dir = base_dir / "bindings"
|
|
2630
|
+
registry_file = base_dir / ".module_registry.json"
|
|
2631
|
+
|
|
2632
|
+
# Check if bindings directory exists
|
|
2633
|
+
if not bindings_dir.exists():
|
|
2634
|
+
raise RuntimeError(
|
|
2635
|
+
f"Bindings directory not found: {bindings_dir}\n"
|
|
2636
|
+
f"Run 'includecpp rebuild' in the project directory first."
|
|
2637
|
+
)
|
|
2638
|
+
|
|
2639
|
+
# Check registry for module
|
|
2640
|
+
if registry_file.exists():
|
|
2641
|
+
with open(registry_file, 'r', encoding='utf-8') as f:
|
|
2642
|
+
registry_data = json.load(f)
|
|
2643
|
+
# Handle both v1.6 and v2.0 registry formats
|
|
2644
|
+
if "schema_version" in registry_data and registry_data.get("schema_version") == "2.0":
|
|
2645
|
+
registry = registry_data.get("modules", {})
|
|
2646
|
+
else:
|
|
2647
|
+
registry = registry_data
|
|
2648
|
+
|
|
2649
|
+
if module_name not in registry:
|
|
2650
|
+
available = list(registry.keys())
|
|
2651
|
+
raise RuntimeError(
|
|
2652
|
+
f"Module '{module_name}' not found in registry.\n"
|
|
2653
|
+
f"Available modules: {available}\n"
|
|
2654
|
+
f"Run 'includecpp rebuild' to build the module."
|
|
2655
|
+
)
|
|
2656
|
+
|
|
2657
|
+
# Add bindings directory to path if not already there
|
|
2658
|
+
bindings_str = str(bindings_dir)
|
|
2659
|
+
if bindings_str not in sys.path:
|
|
2660
|
+
sys.path.insert(0, bindings_str)
|
|
2661
|
+
|
|
2662
|
+
# Try to import per-module .pyd (v2.0 format)
|
|
2663
|
+
pyd_name = f"api_{module_name}"
|
|
2664
|
+
pyd_suffix = ".pyd" if platform.system() == "Windows" else ".so"
|
|
2665
|
+
pyd_path = bindings_dir / f"{pyd_name}{pyd_suffix}"
|
|
2666
|
+
|
|
2667
|
+
if pyd_path.exists():
|
|
2668
|
+
# Import the per-module .pyd
|
|
2669
|
+
import importlib
|
|
2670
|
+
if pyd_name in sys.modules:
|
|
2671
|
+
# Reload if already imported
|
|
2672
|
+
module = importlib.reload(sys.modules[pyd_name])
|
|
2673
|
+
else:
|
|
2674
|
+
module = importlib.import_module(pyd_name)
|
|
2675
|
+
return module
|
|
2676
|
+
|
|
2677
|
+
# v4.9.3: Try direct import of shared api.pyd before subprocess fallback
|
|
2678
|
+
# This allows C++ classes to work properly (subprocess can't pickle them)
|
|
2679
|
+
api_pyd = bindings_dir / f"api{pyd_suffix}"
|
|
2680
|
+
if api_pyd.exists():
|
|
2681
|
+
import importlib
|
|
2682
|
+
# Add DLL directory on Windows for dependency loading
|
|
2683
|
+
if hasattr(os, 'add_dll_directory'):
|
|
2684
|
+
os.add_dll_directory(bindings_str)
|
|
2685
|
+
|
|
2686
|
+
# Check if we can import api without conflict
|
|
2687
|
+
if 'api' not in sys.modules:
|
|
2688
|
+
try:
|
|
2689
|
+
api_mod = importlib.import_module('api')
|
|
2690
|
+
if hasattr(api_mod, module_name):
|
|
2691
|
+
return getattr(api_mod, module_name)
|
|
2692
|
+
except Exception:
|
|
2693
|
+
pass
|
|
2694
|
+
else:
|
|
2695
|
+
# api already loaded - check if it has our module
|
|
2696
|
+
api_mod = sys.modules['api']
|
|
2697
|
+
if hasattr(api_mod, module_name):
|
|
2698
|
+
return getattr(api_mod, module_name)
|
|
2699
|
+
|
|
2700
|
+
# Create a proxy module that executes calls in a subprocess
|
|
2701
|
+
# This avoids the Python extension module caching issue where
|
|
2702
|
+
# cssl's bundled api.pyd shadows the user's api.pyd
|
|
2703
|
+
# Note: Classes won't fully work in subprocess mode due to pickle limitations
|
|
2704
|
+
return _IncludeCppModuleProxy(bindings_str, module_name)
|
|
2705
|
+
|
|
2706
|
+
except RuntimeError:
|
|
2707
|
+
raise
|
|
2708
|
+
except json.JSONDecodeError as e:
|
|
2709
|
+
raise RuntimeError(f"Invalid cpp.proj file: {e}")
|
|
2710
|
+
except Exception as e:
|
|
2711
|
+
raise RuntimeError(f"includecpp() failed: {e}")
|
|
2712
|
+
|
|
2713
|
+
|
|
2714
|
+
def builtin_cout(self) -> 'OutputStream':
|
|
2715
|
+
"""Get stdout stream (C++ cout equivalent).
|
|
2716
|
+
|
|
2717
|
+
C++ optimized output stream with << operator support.
|
|
2718
|
+
|
|
2719
|
+
Usage:
|
|
2720
|
+
cout() << "Hello" << " World" << endl();
|
|
2721
|
+
"""
|
|
2722
|
+
from .cssl_types import OutputStream
|
|
2723
|
+
if CSSLBuiltins._cout_instance is None:
|
|
2724
|
+
CSSLBuiltins._cout_instance = OutputStream('stdout')
|
|
2725
|
+
return CSSLBuiltins._cout_instance
|
|
2726
|
+
|
|
2727
|
+
def builtin_cin(self) -> 'InputStream':
|
|
2728
|
+
"""Get stdin stream (C++ cin equivalent).
|
|
2729
|
+
|
|
2730
|
+
C++ optimized input stream with >> operator support.
|
|
2731
|
+
|
|
2732
|
+
Usage:
|
|
2733
|
+
@name = cin().read(str);
|
|
2734
|
+
@age = cin().read(int);
|
|
2735
|
+
"""
|
|
2736
|
+
from .cssl_types import InputStream
|
|
2737
|
+
if CSSLBuiltins._cin_instance is None:
|
|
2738
|
+
CSSLBuiltins._cin_instance = InputStream('stdin')
|
|
2739
|
+
return CSSLBuiltins._cin_instance
|
|
2740
|
+
|
|
2741
|
+
def builtin_cerr(self) -> 'OutputStream':
|
|
2742
|
+
"""Get stderr stream (C++ cerr equivalent).
|
|
2743
|
+
|
|
2744
|
+
Auto-flushing error output stream.
|
|
2745
|
+
|
|
2746
|
+
Usage:
|
|
2747
|
+
cerr() << "Error: " << errMsg << endl();
|
|
2748
|
+
"""
|
|
2749
|
+
from .cssl_types import OutputStream
|
|
2750
|
+
if CSSLBuiltins._cerr_instance is None:
|
|
2751
|
+
CSSLBuiltins._cerr_instance = OutputStream('stderr')
|
|
2752
|
+
return CSSLBuiltins._cerr_instance
|
|
2753
|
+
|
|
2754
|
+
def builtin_clog(self) -> 'OutputStream':
|
|
2755
|
+
"""Get log stream (C++ clog equivalent).
|
|
2756
|
+
|
|
2757
|
+
Buffered logging output stream.
|
|
2758
|
+
|
|
2759
|
+
Usage:
|
|
2760
|
+
clog() << "[INFO] " << message << endl();
|
|
2761
|
+
"""
|
|
2762
|
+
from .cssl_types import OutputStream
|
|
2763
|
+
if CSSLBuiltins._clog_instance is None:
|
|
2764
|
+
CSSLBuiltins._clog_instance = OutputStream('clog')
|
|
2765
|
+
return CSSLBuiltins._clog_instance
|
|
2766
|
+
|
|
2767
|
+
def builtin_endl(self) -> str:
|
|
2768
|
+
"""End line and flush (C++ endl equivalent).
|
|
2769
|
+
|
|
2770
|
+
Usage:
|
|
2771
|
+
cout() << "Hello" << endl();
|
|
2772
|
+
"""
|
|
2773
|
+
return 'endl'
|
|
2774
|
+
|
|
2775
|
+
def builtin_getline(self, stream=None) -> str:
|
|
2776
|
+
"""Read entire line from stream (C++ getline equivalent).
|
|
2777
|
+
|
|
2778
|
+
Usage:
|
|
2779
|
+
@line = getline(); // From stdin
|
|
2780
|
+
@line = getline(fileStream); // From file
|
|
2781
|
+
"""
|
|
2782
|
+
if stream is None:
|
|
2783
|
+
stream = self.builtin_cin()
|
|
2784
|
+
if hasattr(stream, 'getline'):
|
|
2785
|
+
return stream.getline()
|
|
2786
|
+
# Fallback for regular file objects
|
|
2787
|
+
if hasattr(stream, 'readline'):
|
|
2788
|
+
return stream.readline().rstrip('\n')
|
|
2789
|
+
return str(stream)
|
|
2790
|
+
|
|
2791
|
+
def builtin_fstream(self, filename: str = None, mode: str = 'r+') -> 'FileStream':
|
|
2792
|
+
"""Create file stream (C++ fstream equivalent).
|
|
2793
|
+
|
|
2794
|
+
C++ optimized file I/O with << and >> operator support.
|
|
2795
|
+
|
|
2796
|
+
Usage:
|
|
2797
|
+
@file = fstream("data.txt", "r+");
|
|
2798
|
+
file << "Hello" << endl();
|
|
2799
|
+
@data = file.read(str);
|
|
2800
|
+
file.close();
|
|
2801
|
+
"""
|
|
2802
|
+
from .cssl_types import FileStream
|
|
2803
|
+
return FileStream(filename, mode)
|
|
2804
|
+
|
|
2805
|
+
def builtin_ifstream(self, filename: str = None) -> 'FileStream':
|
|
2806
|
+
"""Create input file stream (C++ ifstream equivalent).
|
|
2807
|
+
|
|
2808
|
+
Usage:
|
|
2809
|
+
@file = ifstream("input.txt");
|
|
2810
|
+
@content = file.readall();
|
|
2811
|
+
"""
|
|
2812
|
+
from .cssl_types import FileStream
|
|
2813
|
+
return FileStream(filename, 'r')
|
|
2814
|
+
|
|
2815
|
+
def builtin_ofstream(self, filename: str = None) -> 'FileStream':
|
|
2816
|
+
"""Create output file stream (C++ ofstream equivalent).
|
|
2817
|
+
|
|
2818
|
+
Usage:
|
|
2819
|
+
@file = ofstream("output.txt");
|
|
2820
|
+
file << "Data" << endl();
|
|
2821
|
+
"""
|
|
2822
|
+
from .cssl_types import FileStream
|
|
2823
|
+
return FileStream(filename, 'w')
|
|
2824
|
+
|
|
2825
|
+
def builtin_setprecision(self, n: int) -> dict:
|
|
2826
|
+
"""Set floating point precision (C++ setprecision equivalent).
|
|
2827
|
+
|
|
2828
|
+
Returns a manipulator object for use with streams.
|
|
2829
|
+
|
|
2830
|
+
Usage:
|
|
2831
|
+
cout() << setprecision(4) << 3.14159; // "3.1416"
|
|
2832
|
+
"""
|
|
2833
|
+
return {'type': 'manipulator', 'name': 'setprecision', 'value': n}
|
|
2834
|
+
|
|
2835
|
+
def builtin_setw(self, n: int) -> dict:
|
|
2836
|
+
"""Set field width (C++ setw equivalent).
|
|
2837
|
+
|
|
2838
|
+
Usage:
|
|
2839
|
+
cout() << setw(10) << value;
|
|
2840
|
+
"""
|
|
2841
|
+
return {'type': 'manipulator', 'name': 'setw', 'value': n}
|
|
2842
|
+
|
|
2843
|
+
def builtin_setfill(self, c: str) -> dict:
|
|
2844
|
+
"""Set fill character (C++ setfill equivalent).
|
|
2845
|
+
|
|
2846
|
+
Usage:
|
|
2847
|
+
cout() << setfill('0') << setw(5) << 42; // "00042"
|
|
2848
|
+
"""
|
|
2849
|
+
return {'type': 'manipulator', 'name': 'setfill', 'value': c}
|
|
2850
|
+
|
|
2851
|
+
def builtin_fixed(self) -> dict:
|
|
2852
|
+
"""Use fixed-point notation (C++ fixed equivalent).
|
|
2853
|
+
|
|
2854
|
+
Usage:
|
|
2855
|
+
cout() << fixed() << 3.14159;
|
|
2856
|
+
"""
|
|
2857
|
+
return {'type': 'manipulator', 'name': 'fixed'}
|
|
2858
|
+
|
|
2859
|
+
def builtin_scientific(self) -> dict:
|
|
2860
|
+
"""Use scientific notation (C++ scientific equivalent).
|
|
2861
|
+
|
|
2862
|
+
Usage:
|
|
2863
|
+
cout() << scientific() << 1234.5; // "1.234500e+03"
|
|
2864
|
+
"""
|
|
2865
|
+
return {'type': 'manipulator', 'name': 'scientific'}
|
|
2866
|
+
|
|
2867
|
+
def builtin_flush(self) -> str:
|
|
2868
|
+
"""Flush stream (C++ flush equivalent).
|
|
2869
|
+
|
|
2870
|
+
Usage:
|
|
2871
|
+
cout() << "Processing..." << flush();
|
|
2872
|
+
"""
|
|
2873
|
+
return 'flush'
|
|
2874
|
+
|
|
2875
|
+
# ============= Struct Operations =============
|
|
2876
|
+
|
|
2877
|
+
def builtin_sizeof(self, obj: Any) -> int:
|
|
2878
|
+
"""Get size of object in bytes (C sizeof equivalent).
|
|
2879
|
+
|
|
2880
|
+
For CStruct, returns estimated memory size.
|
|
2881
|
+
For other types, returns sys.getsizeof.
|
|
2882
|
+
|
|
2883
|
+
Usage:
|
|
2884
|
+
@size = sizeof(myStruct);
|
|
2885
|
+
@size = sizeof(myArray);
|
|
2886
|
+
"""
|
|
2887
|
+
import sys
|
|
2888
|
+
from .cssl_types import CStruct
|
|
2889
|
+
if isinstance(obj, CStruct):
|
|
2890
|
+
return obj.sizeof()
|
|
2891
|
+
return sys.getsizeof(obj)
|
|
2892
|
+
|
|
2893
|
+
def builtin_memcpy(self, dest: Any, src: Any, n: int = None) -> Any:
|
|
2894
|
+
"""Copy memory/data (C memcpy equivalent).
|
|
2895
|
+
|
|
2896
|
+
For lists/arrays, copies n elements.
|
|
2897
|
+
For structs, copies all fields.
|
|
2898
|
+
|
|
2899
|
+
Usage:
|
|
2900
|
+
memcpy(destArray, srcArray, 10);
|
|
2901
|
+
memcpy(destStruct, srcStruct);
|
|
2902
|
+
"""
|
|
2903
|
+
from .cssl_types import CStruct
|
|
2904
|
+
if isinstance(src, CStruct) and isinstance(dest, CStruct):
|
|
2905
|
+
# Copy struct fields
|
|
2906
|
+
for name, value in src._values.items():
|
|
2907
|
+
if name in dest._fields:
|
|
2908
|
+
dest._values[name] = value
|
|
2909
|
+
return dest
|
|
2910
|
+
elif hasattr(dest, '__setitem__') and hasattr(src, '__getitem__'):
|
|
2911
|
+
# Copy array/list elements
|
|
2912
|
+
count = n if n is not None else len(src)
|
|
2913
|
+
for i in range(min(count, len(src), len(dest) if hasattr(dest, '__len__') else count)):
|
|
2914
|
+
dest[i] = src[i]
|
|
2915
|
+
return dest
|
|
2916
|
+
else:
|
|
2917
|
+
# Generic copy
|
|
2918
|
+
import copy
|
|
2919
|
+
return copy.copy(src)
|
|
2920
|
+
|
|
2921
|
+
def builtin_memset(self, dest: Any, value: Any, n: int = None) -> Any:
|
|
2922
|
+
"""Set memory/data to value (C memset equivalent).
|
|
2923
|
+
|
|
2924
|
+
For lists/arrays, sets n elements to value.
|
|
2925
|
+
For structs, sets all fields to value.
|
|
2926
|
+
|
|
2927
|
+
Usage:
|
|
2928
|
+
memset(myArray, 0, 100);
|
|
2929
|
+
memset(myStruct, null);
|
|
2930
|
+
"""
|
|
2931
|
+
from .cssl_types import CStruct
|
|
2932
|
+
if isinstance(dest, CStruct):
|
|
2933
|
+
for name in dest._values:
|
|
2934
|
+
dest._values[name] = value
|
|
2935
|
+
return dest
|
|
2936
|
+
elif hasattr(dest, '__setitem__'):
|
|
2937
|
+
count = n if n is not None else len(dest)
|
|
2938
|
+
for i in range(min(count, len(dest) if hasattr(dest, '__len__') else count)):
|
|
2939
|
+
dest[i] = value
|
|
2940
|
+
return dest
|
|
2941
|
+
return dest
|
|
2942
|
+
|
|
2943
|
+
# ============= Pipe Operations =============
|
|
2944
|
+
|
|
2945
|
+
def builtin_pipe(self, data: Any = None) -> 'Pipe':
|
|
2946
|
+
"""Create a new pipe for data transformation.
|
|
2947
|
+
|
|
2948
|
+
C++ optimized piping with | operator support.
|
|
2949
|
+
|
|
2950
|
+
Usage:
|
|
2951
|
+
@result = pipe([1,2,3,4,5])
|
|
2952
|
+
| Pipe.filter(x => x > 2)
|
|
2953
|
+
| Pipe.map(x => x * 2)
|
|
2954
|
+
| collect();
|
|
2955
|
+
"""
|
|
2956
|
+
from .cssl_types import Pipe
|
|
2957
|
+
return Pipe(data)
|
|
2958
|
+
|
|
2959
|
+
# ============= Optimized Containment Check =============
|
|
2960
|
+
|
|
2961
|
+
def builtin_contains_fast(self, container: Any, item: Any, use_native: bool = False) -> bool:
|
|
2962
|
+
"""Fast containment check (C++ optimized 'in' operator).
|
|
2963
|
+
|
|
2964
|
+
When use_native=False (default), uses C++ optimized search.
|
|
2965
|
+
When use_native=True, uses Python's native 'in' operator.
|
|
2966
|
+
|
|
2967
|
+
For sorted containers, uses binary search (O(log n)).
|
|
2968
|
+
For hash-based containers, uses O(1) lookup.
|
|
2969
|
+
|
|
2970
|
+
Usage:
|
|
2971
|
+
// C++ optimized (default)
|
|
2972
|
+
@found = contains_fast(myList, 42);
|
|
2973
|
+
|
|
2974
|
+
// Python native
|
|
2975
|
+
@found = contains_fast(myList, 42, true);
|
|
2976
|
+
"""
|
|
2977
|
+
if use_native:
|
|
2978
|
+
# Python native 'in' operator
|
|
2979
|
+
return item in container
|
|
2980
|
+
|
|
2981
|
+
# C++ optimized path
|
|
2982
|
+
from .cssl_types import Vector, Array, List, Dictionary, Map, DataStruct
|
|
2983
|
+
|
|
2984
|
+
# Hash-based containers - O(1)
|
|
2985
|
+
if isinstance(container, (dict, set, frozenset)):
|
|
2986
|
+
return item in container
|
|
2987
|
+
if isinstance(container, (Dictionary, Map)):
|
|
2988
|
+
return container.contains(item)
|
|
2989
|
+
|
|
2990
|
+
# For sorted data, use binary search - O(log n)
|
|
2991
|
+
if isinstance(container, (list, tuple, Vector, Array, List, DataStruct)):
|
|
2992
|
+
# Check if sorted (sample check for performance)
|
|
2993
|
+
data = list(container) if not isinstance(container, list) else container
|
|
2994
|
+
if len(data) > 10:
|
|
2995
|
+
# Quick sorted check on sample
|
|
2996
|
+
sample = [data[i] for i in range(0, len(data), max(1, len(data) // 10))]
|
|
2997
|
+
try:
|
|
2998
|
+
is_sorted = all(sample[i] <= sample[i+1] for i in range(len(sample)-1))
|
|
2999
|
+
if is_sorted:
|
|
3000
|
+
# Binary search
|
|
3001
|
+
import bisect
|
|
3002
|
+
idx = bisect.bisect_left(data, item)
|
|
3003
|
+
return idx < len(data) and data[idx] == item
|
|
3004
|
+
except TypeError:
|
|
3005
|
+
pass # Non-comparable items, fall through to linear
|
|
3006
|
+
|
|
3007
|
+
# Linear search with early termination
|
|
3008
|
+
for x in data:
|
|
3009
|
+
if x == item:
|
|
3010
|
+
return True
|
|
3011
|
+
return False
|
|
3012
|
+
|
|
3013
|
+
# String containment - use native (already optimized in Python)
|
|
3014
|
+
if isinstance(container, str):
|
|
3015
|
+
return str(item) in container
|
|
3016
|
+
|
|
3017
|
+
# Generic fallback
|
|
3018
|
+
try:
|
|
3019
|
+
return item in container
|
|
3020
|
+
except TypeError:
|
|
3021
|
+
return False
|
|
3022
|
+
|
|
3023
|
+
# ============= Extended String Functions =============
|
|
3024
|
+
|
|
3025
|
+
def builtin_sprintf(self, fmt: str, *args) -> str:
|
|
3026
|
+
"""C-style format string with validation.
|
|
3027
|
+
|
|
3028
|
+
v4.7.1: Added format specifier validation for security.
|
|
3029
|
+
"""
|
|
3030
|
+
import re
|
|
3031
|
+
# Count format specifiers (excluding %%)
|
|
3032
|
+
specifiers = re.findall(r'%(?!%)[#0\- +]*\d*\.?\d*[hlL]?[diouxXeEfFgGcrsab]', fmt)
|
|
3033
|
+
if len(specifiers) != len(args):
|
|
3034
|
+
raise ValueError(
|
|
3035
|
+
f"Format string has {len(specifiers)} specifiers but {len(args)} arguments provided"
|
|
3036
|
+
)
|
|
3037
|
+
return fmt % args
|
|
3038
|
+
|
|
3039
|
+
def builtin_chars(self, s: str) -> list:
|
|
3040
|
+
"""Convert string to list of characters"""
|
|
3041
|
+
return list(s)
|
|
3042
|
+
|
|
3043
|
+
def builtin_ord(self, c: str) -> int:
|
|
3044
|
+
"""Get ASCII/Unicode code of character"""
|
|
3045
|
+
return ord(c[0] if c else '\0')
|
|
3046
|
+
|
|
3047
|
+
def builtin_chr(self, n: int) -> str:
|
|
3048
|
+
"""Convert ASCII/Unicode code to character"""
|
|
3049
|
+
return chr(n)
|
|
3050
|
+
|
|
3051
|
+
def builtin_capitalize(self, s: str) -> str:
|
|
3052
|
+
return str(s).capitalize()
|
|
3053
|
+
|
|
3054
|
+
def builtin_title(self, s: str) -> str:
|
|
3055
|
+
return str(s).title()
|
|
3056
|
+
|
|
3057
|
+
def builtin_swapcase(self, s: str) -> str:
|
|
3058
|
+
return str(s).swapcase()
|
|
3059
|
+
|
|
3060
|
+
def builtin_center(self, s: str, width: int, fillchar: str = ' ') -> str:
|
|
3061
|
+
return str(s).center(width, fillchar)
|
|
3062
|
+
|
|
3063
|
+
def builtin_zfill(self, s: str, width: int) -> str:
|
|
3064
|
+
return str(s).zfill(width)
|
|
3065
|
+
|
|
3066
|
+
def builtin_isalpha(self, s: str) -> bool:
|
|
3067
|
+
return str(s).isalpha()
|
|
3068
|
+
|
|
3069
|
+
def builtin_isdigit(self, s: str) -> bool:
|
|
3070
|
+
return str(s).isdigit()
|
|
3071
|
+
|
|
3072
|
+
def builtin_isalnum(self, s: str) -> bool:
|
|
3073
|
+
return str(s).isalnum()
|
|
3074
|
+
|
|
3075
|
+
def builtin_isspace(self, s: str) -> bool:
|
|
3076
|
+
return str(s).isspace()
|
|
3077
|
+
|
|
3078
|
+
# ============= Extended List Functions =============
|
|
3079
|
+
|
|
3080
|
+
def builtin_enumerate(self, lst: list, start: int = 0) -> list:
|
|
3081
|
+
"""Return list of (index, value) pairs"""
|
|
3082
|
+
return list(enumerate(lst, start))
|
|
3083
|
+
|
|
3084
|
+
def builtin_zip(self, *lists) -> list:
|
|
3085
|
+
"""Zip multiple lists together"""
|
|
3086
|
+
return list(zip(*lists))
|
|
3087
|
+
|
|
3088
|
+
def builtin_reversed(self, lst: list) -> list:
|
|
3089
|
+
"""Return reversed list
|
|
3090
|
+
|
|
3091
|
+
v4.8.7: Added None check.
|
|
3092
|
+
"""
|
|
3093
|
+
if lst is None:
|
|
3094
|
+
return []
|
|
3095
|
+
if not isinstance(lst, (list, tuple)):
|
|
3096
|
+
return [lst]
|
|
3097
|
+
return list(reversed(lst))
|
|
3098
|
+
|
|
3099
|
+
def builtin_sorted(self, lst: list, key: str = None, reverse: bool = False) -> list:
|
|
3100
|
+
"""Return sorted list"""
|
|
3101
|
+
if key:
|
|
3102
|
+
return sorted(lst, key=lambda x: x.get(key) if isinstance(x, dict) else x, reverse=reverse)
|
|
3103
|
+
return sorted(lst, reverse=reverse)
|
|
3104
|
+
|
|
3105
|
+
def builtin_count(self, collection: Union[list, str], item: Any) -> int:
|
|
3106
|
+
"""Count occurrences of item"""
|
|
3107
|
+
return collection.count(item)
|
|
3108
|
+
|
|
3109
|
+
def builtin_first(self, lst: list, default: Any = None) -> Any:
|
|
3110
|
+
"""Get first element or default"""
|
|
3111
|
+
return lst[0] if lst else default
|
|
1649
3112
|
|
|
1650
3113
|
def builtin_last(self, lst: list, default: Any = None) -> Any:
|
|
1651
3114
|
"""Get last element or default"""
|
|
@@ -1849,27 +3312,60 @@ class CSSLBuiltins:
|
|
|
1849
3312
|
|
|
1850
3313
|
def builtin_initsh(self, path: str, *args) -> int:
|
|
1851
3314
|
"""
|
|
1852
|
-
Execute a shell script
|
|
1853
|
-
|
|
3315
|
+
Execute a shell script from the 'scripts' directory only.
|
|
3316
|
+
|
|
3317
|
+
v4.7.1: Restricted to 'scripts/' directory for security.
|
|
3318
|
+
|
|
3319
|
+
Usage: initsh('myscript.sh') // Runs scripts/myscript.sh
|
|
1854
3320
|
"""
|
|
1855
3321
|
import subprocess
|
|
1856
3322
|
|
|
1857
|
-
|
|
1858
|
-
|
|
3323
|
+
# v4.7.1: Security - prevent directory traversal
|
|
3324
|
+
if '..' in path or os.path.isabs(path):
|
|
3325
|
+
raise CSSLBuiltinError(
|
|
3326
|
+
f"Invalid script path: '{path}'. "
|
|
3327
|
+
"Scripts must be relative paths without '..' and must be in 'scripts/' directory."
|
|
3328
|
+
)
|
|
1859
3329
|
|
|
1860
|
-
|
|
1861
|
-
|
|
3330
|
+
# Determine base directory
|
|
3331
|
+
if self.runtime and self.runtime.service_engine:
|
|
3332
|
+
base_dir = self.runtime.service_engine.KernelClient.RootDirectory
|
|
3333
|
+
else:
|
|
3334
|
+
base_dir = os.getcwd()
|
|
3335
|
+
|
|
3336
|
+
# Force scripts to be in 'scripts' subdirectory
|
|
3337
|
+
scripts_dir = os.path.join(base_dir, 'scripts')
|
|
3338
|
+
full_path = os.path.normpath(os.path.join(scripts_dir, path))
|
|
3339
|
+
|
|
3340
|
+
# Verify path is within scripts directory (prevent traversal)
|
|
3341
|
+
if not full_path.startswith(os.path.normpath(scripts_dir)):
|
|
3342
|
+
raise CSSLBuiltinError(
|
|
3343
|
+
f"Security: Script path '{path}' escapes the scripts directory."
|
|
3344
|
+
)
|
|
3345
|
+
|
|
3346
|
+
if not os.path.exists(full_path):
|
|
3347
|
+
raise CSSLBuiltinError(f"Shell script not found: {full_path}")
|
|
3348
|
+
|
|
3349
|
+
# Validate file extension
|
|
3350
|
+
valid_extensions = {'.sh', '.bat', '.cmd', '.ps1'}
|
|
3351
|
+
_, ext = os.path.splitext(full_path)
|
|
3352
|
+
if ext.lower() not in valid_extensions:
|
|
3353
|
+
raise CSSLBuiltinError(
|
|
3354
|
+
f"Invalid script type: '{ext}'. Allowed: {', '.join(valid_extensions)}"
|
|
3355
|
+
)
|
|
1862
3356
|
|
|
1863
3357
|
try:
|
|
1864
3358
|
# Determine shell based on platform
|
|
1865
3359
|
import platform
|
|
1866
3360
|
if platform.system() == 'Windows':
|
|
1867
|
-
|
|
1868
|
-
|
|
3361
|
+
if ext.lower() == '.ps1':
|
|
3362
|
+
cmd = ['powershell', '-ExecutionPolicy', 'Bypass', '-File', full_path] + list(args)
|
|
3363
|
+
else:
|
|
3364
|
+
cmd = ['cmd', '/c', full_path] + list(args)
|
|
1869
3365
|
else:
|
|
1870
|
-
cmd = ['bash',
|
|
3366
|
+
cmd = ['bash', full_path] + list(args)
|
|
1871
3367
|
|
|
1872
|
-
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
3368
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
|
|
1873
3369
|
|
|
1874
3370
|
if result.stdout:
|
|
1875
3371
|
print(result.stdout)
|
|
@@ -1878,6 +3374,8 @@ class CSSLBuiltins:
|
|
|
1878
3374
|
|
|
1879
3375
|
return result.returncode
|
|
1880
3376
|
|
|
3377
|
+
except subprocess.TimeoutExpired:
|
|
3378
|
+
raise CSSLBuiltinError(f"Script execution timeout (60s): {path}")
|
|
1881
3379
|
except Exception as e:
|
|
1882
3380
|
print(f"Shell execution error [{path}]: {e}")
|
|
1883
3381
|
raise CSSLBuiltinError(f"initsh failed: {e}")
|
|
@@ -2019,9 +3517,9 @@ class CSSLBuiltins:
|
|
|
2019
3517
|
except Exception:
|
|
2020
3518
|
pass
|
|
2021
3519
|
|
|
2022
|
-
# Clean path and join
|
|
3520
|
+
# Clean path and join (strip leading separators for both platforms)
|
|
2023
3521
|
if path:
|
|
2024
|
-
clean_path = path.lstrip('
|
|
3522
|
+
clean_path = path.lstrip('/\\')
|
|
2025
3523
|
return os.path.normpath(os.path.join(base, clean_path))
|
|
2026
3524
|
|
|
2027
3525
|
return base
|
|
@@ -2163,7 +3661,30 @@ class CSSLBuiltins:
|
|
|
2163
3661
|
def _execute_python_module(self, name: str, source: str, filepath: str) -> Any:
|
|
2164
3662
|
"""
|
|
2165
3663
|
Execute Python source and return a module-like object with all functions.
|
|
3664
|
+
|
|
3665
|
+
v4.8.8: Security check - scan source for blocked module imports.
|
|
2166
3666
|
"""
|
|
3667
|
+
import re
|
|
3668
|
+
|
|
3669
|
+
# v4.8.8: Security - check for blocked module imports in source
|
|
3670
|
+
# This prevents the bypass: makemodule a Python file that imports os,
|
|
3671
|
+
# then include() it in CSSL to access os through the module.
|
|
3672
|
+
for blocked in self.BLOCKED_MODULES:
|
|
3673
|
+
# Check for: import os, import os as x, from os import, from os.path import
|
|
3674
|
+
patterns = [
|
|
3675
|
+
rf'\bimport\s+{blocked}\b', # import os
|
|
3676
|
+
rf'\bimport\s+{blocked}\s+as\b', # import os as x
|
|
3677
|
+
rf'\bfrom\s+{blocked}\b', # from os import / from os.path import
|
|
3678
|
+
rf'\b{blocked}\s*=\s*__import__', # os = __import__('os')
|
|
3679
|
+
]
|
|
3680
|
+
for pattern in patterns:
|
|
3681
|
+
if re.search(pattern, source):
|
|
3682
|
+
raise CSSLBuiltinError(
|
|
3683
|
+
f"Module '{name}' imports blocked module '{blocked}'.\n"
|
|
3684
|
+
f"Security: {blocked} is not allowed in CSSL modules.\n"
|
|
3685
|
+
f"Use CSSL builtins instead."
|
|
3686
|
+
)
|
|
3687
|
+
|
|
2167
3688
|
# Create a namespace for the module
|
|
2168
3689
|
module_namespace = {
|
|
2169
3690
|
'__name__': name,
|
|
@@ -2292,18 +3813,40 @@ class CSSLBuiltins:
|
|
|
2292
3813
|
is_absolute = os.path.isabs(filepath) or (len(filepath) > 2 and filepath[1] == ':')
|
|
2293
3814
|
|
|
2294
3815
|
if not is_absolute:
|
|
2295
|
-
#
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
3816
|
+
# v4.8.8: First try relative to current executing file's directory
|
|
3817
|
+
# This allows payload("other.cssl-pl") to find files in same folder
|
|
3818
|
+
current_file_dir = None
|
|
3819
|
+
if hasattr(self.runtime, '_current_file_path') and self.runtime._current_file_path:
|
|
3820
|
+
current_file_dir = os.path.dirname(self.runtime._current_file_path)
|
|
3821
|
+
|
|
3822
|
+
found = False
|
|
3823
|
+
|
|
3824
|
+
# Try relative to current file's directory first (if available)
|
|
3825
|
+
if current_file_dir:
|
|
3826
|
+
file_relative_path = os.path.join(current_file_dir, filepath)
|
|
3827
|
+
if os.path.exists(file_relative_path):
|
|
3828
|
+
filepath = file_relative_path
|
|
3829
|
+
found = True
|
|
3830
|
+
else:
|
|
3831
|
+
# Try with .cssl-pl extension
|
|
3832
|
+
file_relative_pl = os.path.join(current_file_dir, filepath + '.cssl-pl')
|
|
3833
|
+
if os.path.exists(file_relative_pl):
|
|
3834
|
+
filepath = file_relative_pl
|
|
3835
|
+
found = True
|
|
3836
|
+
|
|
3837
|
+
# Fall back to CWD if not found relative to current file
|
|
3838
|
+
if not found:
|
|
3839
|
+
cwd_path = os.path.join(os.getcwd(), filepath)
|
|
3840
|
+
if os.path.exists(cwd_path):
|
|
3841
|
+
filepath = cwd_path
|
|
2304
3842
|
else:
|
|
2305
|
-
#
|
|
2306
|
-
|
|
3843
|
+
# Try with .cssl-pl extension
|
|
3844
|
+
cwd_path_pl = os.path.join(os.getcwd(), filepath + '.cssl-pl')
|
|
3845
|
+
if os.path.exists(cwd_path_pl):
|
|
3846
|
+
filepath = cwd_path_pl
|
|
3847
|
+
else:
|
|
3848
|
+
# Fall back to cso_root for service context
|
|
3849
|
+
filepath = self.builtin_cso_root(filepath)
|
|
2307
3850
|
|
|
2308
3851
|
# Check file exists, try with .cssl-pl extension if not
|
|
2309
3852
|
if not os.path.exists(filepath):
|
|
@@ -2335,16 +3878,24 @@ class CSSLBuiltins:
|
|
|
2335
3878
|
|
|
2336
3879
|
ast = parse_cssl_program(source)
|
|
2337
3880
|
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
3881
|
+
# v4.8.8: Track current file path for relative payload resolution
|
|
3882
|
+
prev_file_path = getattr(self.runtime, '_current_file_path', None)
|
|
3883
|
+
self.runtime._current_file_path = filepath
|
|
3884
|
+
|
|
3885
|
+
try:
|
|
3886
|
+
if libname:
|
|
3887
|
+
# Namespaced execution: execute in isolated scope
|
|
3888
|
+
self._execute_payload_namespaced(ast, libname, source)
|
|
3889
|
+
else:
|
|
3890
|
+
# Standard execution: apply globally
|
|
3891
|
+
# Execute the payload - this will:
|
|
3892
|
+
# - Register global variables (accessible via @name)
|
|
3893
|
+
# - Define functions in current scope
|
|
3894
|
+
# - Set up any code injections
|
|
3895
|
+
self.runtime._execute_node(ast)
|
|
3896
|
+
finally:
|
|
3897
|
+
# Restore previous file path
|
|
3898
|
+
self.runtime._current_file_path = prev_file_path
|
|
2348
3899
|
|
|
2349
3900
|
except Exception as e:
|
|
2350
3901
|
raise CSSLBuiltinError(f"Failed to load payload '{filepath}': {e}")
|
|
@@ -2594,29 +4145,42 @@ class CSSLBuiltins:
|
|
|
2594
4145
|
# This is handled by the runtime which passes the path
|
|
2595
4146
|
pass
|
|
2596
4147
|
|
|
2597
|
-
def builtin_delete(self,
|
|
4148
|
+
def builtin_delete(self, target: Any, destructor_name: str = None) -> bool:
|
|
2598
4149
|
"""
|
|
2599
|
-
Delete a shared object by name.
|
|
2600
|
-
|
|
4150
|
+
Delete a shared object by name or call destructors on a CSSLInstance.
|
|
4151
|
+
|
|
4152
|
+
Usage:
|
|
4153
|
+
delete("MyLib") - removes the $MyLib shared object
|
|
4154
|
+
delete(myInstance) - calls all destructors on CSSLInstance
|
|
4155
|
+
delete(myInstance, "Init") - calls only ~Init destructor
|
|
2601
4156
|
|
|
2602
4157
|
Args:
|
|
2603
|
-
|
|
4158
|
+
target: Name string for shared objects OR CSSLInstance for destructor calls
|
|
4159
|
+
destructor_name: Optional - specific destructor name (without ~)
|
|
2604
4160
|
|
|
2605
4161
|
Returns:
|
|
2606
|
-
True if deleted, False if not found
|
|
4162
|
+
True if deleted/destroyed, False if not found
|
|
2607
4163
|
"""
|
|
4164
|
+
from .cssl_types import CSSLInstance
|
|
2608
4165
|
from ..cssl_bridge import _live_objects
|
|
2609
4166
|
|
|
2610
|
-
#
|
|
2611
|
-
if
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
4167
|
+
# v4.8.8: Handle CSSLInstance - call destructors
|
|
4168
|
+
if isinstance(target, CSSLInstance):
|
|
4169
|
+
return self.builtin_instance_delete(target, destructor_name)
|
|
4170
|
+
|
|
4171
|
+
# Handle string name - delete shared object
|
|
4172
|
+
if isinstance(target, str):
|
|
4173
|
+
name = target
|
|
4174
|
+
if name in _live_objects:
|
|
4175
|
+
del _live_objects[name]
|
|
4176
|
+
# Also remove from runtime's global scope if present
|
|
4177
|
+
if self.runtime:
|
|
4178
|
+
try:
|
|
4179
|
+
self.runtime.global_scope.delete(f'${name}')
|
|
4180
|
+
except Exception:
|
|
4181
|
+
pass
|
|
4182
|
+
return True
|
|
4183
|
+
|
|
2620
4184
|
return False
|
|
2621
4185
|
|
|
2622
4186
|
# ============= CSSL Data Type Constructors =============
|
|
@@ -2869,6 +4433,387 @@ class CSSLBuiltins:
|
|
|
2869
4433
|
# Wrap the Python object
|
|
2870
4434
|
return CSSLizedPythonObject(python_obj, self.runtime)
|
|
2871
4435
|
|
|
4436
|
+
# =========================================================================
|
|
4437
|
+
# v4.8.8: Python Parameter Functions - CsslLang API parameter passing
|
|
4438
|
+
# =========================================================================
|
|
4439
|
+
|
|
4440
|
+
def _get_parameter_object(self):
|
|
4441
|
+
"""Get the Parameter object from runtime scope."""
|
|
4442
|
+
if self.runtime and hasattr(self.runtime, 'global_scope'):
|
|
4443
|
+
param = self.runtime.global_scope.get('parameter')
|
|
4444
|
+
if param is not None:
|
|
4445
|
+
return param
|
|
4446
|
+
return None
|
|
4447
|
+
|
|
4448
|
+
def _is_blocked_module(self, value: Any) -> tuple:
|
|
4449
|
+
"""Check if a value is a blocked Python module.
|
|
4450
|
+
|
|
4451
|
+
Returns:
|
|
4452
|
+
(is_blocked: bool, module_name: str or None)
|
|
4453
|
+
"""
|
|
4454
|
+
import types
|
|
4455
|
+
if isinstance(value, types.ModuleType):
|
|
4456
|
+
module_name = getattr(value, '__name__', '')
|
|
4457
|
+
base_name = module_name.split('.')[0]
|
|
4458
|
+
if base_name in self.BLOCKED_MODULES:
|
|
4459
|
+
return (True, module_name)
|
|
4460
|
+
return (False, None)
|
|
4461
|
+
|
|
4462
|
+
def builtin_python_parameter_get(self, index: int, default: Any = None) -> Any:
|
|
4463
|
+
"""Get a parameter passed from Python via CsslLang.run().
|
|
4464
|
+
|
|
4465
|
+
v4.8.8: Security - blocks dangerous modules (os, sys, subprocess, etc.)
|
|
4466
|
+
from being passed as parameters.
|
|
4467
|
+
|
|
4468
|
+
Usage:
|
|
4469
|
+
// Python: cssl.run("script.cssl", math_module, "arg2", 123)
|
|
4470
|
+
// CSSL:
|
|
4471
|
+
@math = python::param_get(0); // Gets math module (allowed)
|
|
4472
|
+
arg2 = python::param_get(1); // Gets "arg2"
|
|
4473
|
+
num = python::param_get(2); // Gets 123
|
|
4474
|
+
missing = python::param_get(99, "default"); // Gets "default"
|
|
4475
|
+
|
|
4476
|
+
Args:
|
|
4477
|
+
index: The parameter index (0-based)
|
|
4478
|
+
default: Value to return if parameter doesn't exist
|
|
4479
|
+
|
|
4480
|
+
Returns:
|
|
4481
|
+
The parameter value or default
|
|
4482
|
+
|
|
4483
|
+
Raises:
|
|
4484
|
+
CSSLBuiltinError: If the parameter is a blocked module
|
|
4485
|
+
"""
|
|
4486
|
+
param = self._get_parameter_object()
|
|
4487
|
+
if param is None:
|
|
4488
|
+
return default
|
|
4489
|
+
|
|
4490
|
+
value = param.get(index, default)
|
|
4491
|
+
|
|
4492
|
+
# Security check: block dangerous modules passed as parameters
|
|
4493
|
+
is_blocked, module_name = self._is_blocked_module(value)
|
|
4494
|
+
if is_blocked:
|
|
4495
|
+
raise CSSLBuiltinError(
|
|
4496
|
+
f"Security: Module '{module_name}' cannot be passed as a parameter.\n"
|
|
4497
|
+
f"Blocked modules: {', '.join(sorted(self.BLOCKED_MODULES))}\n"
|
|
4498
|
+
f"Use CSSL builtins instead for filesystem/system operations."
|
|
4499
|
+
)
|
|
4500
|
+
|
|
4501
|
+
return value
|
|
4502
|
+
|
|
4503
|
+
def builtin_python_parameter_return(self, value: Any) -> None:
|
|
4504
|
+
"""Return a value back to Python from CSSL.
|
|
4505
|
+
|
|
4506
|
+
Usage:
|
|
4507
|
+
// CSSL:
|
|
4508
|
+
result = compute_something();
|
|
4509
|
+
python::parameter.return(result);
|
|
4510
|
+
|
|
4511
|
+
// Python:
|
|
4512
|
+
returned = cssl.run("script.cssl", arg1, arg2)
|
|
4513
|
+
print(returned) // Gets the value passed to parameter.return()
|
|
4514
|
+
|
|
4515
|
+
Args:
|
|
4516
|
+
value: The value to return to Python
|
|
4517
|
+
"""
|
|
4518
|
+
param = self._get_parameter_object()
|
|
4519
|
+
if param is None:
|
|
4520
|
+
raise CSSLBuiltinError("python::parameter.return() can only be used when called from CsslLang.run()")
|
|
4521
|
+
param.return_(value)
|
|
4522
|
+
|
|
4523
|
+
def builtin_python_parameter_count(self) -> int:
|
|
4524
|
+
"""Get the number of parameters passed from Python.
|
|
4525
|
+
|
|
4526
|
+
Usage:
|
|
4527
|
+
count = python::parameter.count();
|
|
4528
|
+
printl("Received " + str(count) + " parameters");
|
|
4529
|
+
|
|
4530
|
+
Returns:
|
|
4531
|
+
Number of parameters
|
|
4532
|
+
"""
|
|
4533
|
+
param = self._get_parameter_object()
|
|
4534
|
+
if param is None:
|
|
4535
|
+
return 0
|
|
4536
|
+
return param.count()
|
|
4537
|
+
|
|
4538
|
+
def builtin_python_parameter_all(self) -> list:
|
|
4539
|
+
"""Get all parameters as a list.
|
|
4540
|
+
|
|
4541
|
+
v4.8.8: Security - filters out blocked modules from the list.
|
|
4542
|
+
|
|
4543
|
+
Usage:
|
|
4544
|
+
all_args = python::param_all();
|
|
4545
|
+
foreach (arg in all_args) {
|
|
4546
|
+
printl(arg);
|
|
4547
|
+
}
|
|
4548
|
+
|
|
4549
|
+
Returns:
|
|
4550
|
+
List of all parameters (blocked modules are filtered out)
|
|
4551
|
+
"""
|
|
4552
|
+
param = self._get_parameter_object()
|
|
4553
|
+
if param is None:
|
|
4554
|
+
return []
|
|
4555
|
+
|
|
4556
|
+
# Filter out blocked modules for security
|
|
4557
|
+
result = []
|
|
4558
|
+
for value in param.all():
|
|
4559
|
+
is_blocked, _ = self._is_blocked_module(value)
|
|
4560
|
+
if not is_blocked:
|
|
4561
|
+
result.append(value)
|
|
4562
|
+
return result
|
|
4563
|
+
|
|
4564
|
+
def builtin_python_parameter_has(self, index: int) -> bool:
|
|
4565
|
+
"""Check if a parameter exists at the given index.
|
|
4566
|
+
|
|
4567
|
+
Usage:
|
|
4568
|
+
if (python::parameter.has(2)) {
|
|
4569
|
+
third_arg = python::parameter.get(2);
|
|
4570
|
+
}
|
|
4571
|
+
|
|
4572
|
+
Args:
|
|
4573
|
+
index: The parameter index to check
|
|
4574
|
+
|
|
4575
|
+
Returns:
|
|
4576
|
+
True if parameter exists, False otherwise
|
|
4577
|
+
"""
|
|
4578
|
+
param = self._get_parameter_object()
|
|
4579
|
+
if param is None:
|
|
4580
|
+
return False
|
|
4581
|
+
return param.has(index)
|
|
4582
|
+
|
|
4583
|
+
# =========================================================================
|
|
4584
|
+
# v4.6.5: Watcher Namespace Functions - Live Python Instance Access
|
|
4585
|
+
# =========================================================================
|
|
4586
|
+
|
|
4587
|
+
def builtin_watcher_get(self, watcher_id: str) -> Any:
|
|
4588
|
+
"""Get all instances from a Python CsslWatcher.
|
|
4589
|
+
|
|
4590
|
+
Syntax:
|
|
4591
|
+
all_instances = watcher::get("MyWatcher");
|
|
4592
|
+
pygame = all_instances['Game'];
|
|
4593
|
+
game_instance = all_instances['game'];
|
|
4594
|
+
|
|
4595
|
+
Example in Python:
|
|
4596
|
+
from includecpp.core.cssl_bridge import CsslWatcher
|
|
4597
|
+
cwatcher = CsslWatcher(id="MyWatcher")
|
|
4598
|
+
cwatcher.start()
|
|
4599
|
+
|
|
4600
|
+
class Game:
|
|
4601
|
+
def start(self): print("Game started!")
|
|
4602
|
+
game = Game()
|
|
4603
|
+
|
|
4604
|
+
Then in CSSL:
|
|
4605
|
+
instances = watcher::get("MyWatcher");
|
|
4606
|
+
instances['game'].start(); // "Game started!"
|
|
4607
|
+
|
|
4608
|
+
Args:
|
|
4609
|
+
watcher_id: The watcher's unique ID
|
|
4610
|
+
|
|
4611
|
+
Returns:
|
|
4612
|
+
Dict of all collected instances, classes, and functions
|
|
4613
|
+
"""
|
|
4614
|
+
from ..cssl_bridge import watcher_get
|
|
4615
|
+
result = watcher_get(watcher_id)
|
|
4616
|
+
if result is None:
|
|
4617
|
+
return {}
|
|
4618
|
+
return result
|
|
4619
|
+
|
|
4620
|
+
def builtin_watcher_set(self, watcher_id: str, path: str, value: Any) -> bool:
|
|
4621
|
+
"""Set/overwrite a value in a Python watcher (bidirectional).
|
|
4622
|
+
|
|
4623
|
+
Syntax:
|
|
4624
|
+
watcher::set("MyWatcher", "Game.start", myNewFunction);
|
|
4625
|
+
|
|
4626
|
+
This allows CSSL to overwrite Python functions/methods at runtime.
|
|
4627
|
+
|
|
4628
|
+
Args:
|
|
4629
|
+
watcher_id: The watcher's unique ID
|
|
4630
|
+
path: Path to the item (e.g., 'Game.start')
|
|
4631
|
+
value: New value (function, class, or instance)
|
|
4632
|
+
|
|
4633
|
+
Returns:
|
|
4634
|
+
true if successful, false otherwise
|
|
4635
|
+
"""
|
|
4636
|
+
from ..cssl_bridge import watcher_set
|
|
4637
|
+
return watcher_set(watcher_id, path, value)
|
|
4638
|
+
|
|
4639
|
+
def builtin_watcher_list(self) -> list:
|
|
4640
|
+
"""List all active watcher IDs.
|
|
4641
|
+
|
|
4642
|
+
Syntax:
|
|
4643
|
+
watchers = watcher::list();
|
|
4644
|
+
foreach (w in watchers) {
|
|
4645
|
+
printl("Active watcher: " + w);
|
|
4646
|
+
}
|
|
4647
|
+
|
|
4648
|
+
Returns:
|
|
4649
|
+
List of active watcher IDs
|
|
4650
|
+
"""
|
|
4651
|
+
from ..cssl_bridge import list_watchers
|
|
4652
|
+
return list_watchers()
|
|
4653
|
+
|
|
4654
|
+
def builtin_watcher_exists(self, watcher_id: str) -> bool:
|
|
4655
|
+
"""Check if a watcher with the given ID exists.
|
|
4656
|
+
|
|
4657
|
+
Syntax:
|
|
4658
|
+
if watcher::exists("MyWatcher") {
|
|
4659
|
+
instances = watcher::get("MyWatcher");
|
|
4660
|
+
}
|
|
4661
|
+
|
|
4662
|
+
Args:
|
|
4663
|
+
watcher_id: The watcher's unique ID
|
|
4664
|
+
|
|
4665
|
+
Returns:
|
|
4666
|
+
true if watcher exists, false otherwise
|
|
4667
|
+
"""
|
|
4668
|
+
from ..cssl_bridge import get_watcher
|
|
4669
|
+
return get_watcher(watcher_id) is not None
|
|
4670
|
+
|
|
4671
|
+
def builtin_watcher_refresh(self, watcher_id: str) -> bool:
|
|
4672
|
+
"""Manually refresh a watcher's collected instances.
|
|
4673
|
+
|
|
4674
|
+
Syntax:
|
|
4675
|
+
watcher::refresh("MyWatcher");
|
|
4676
|
+
|
|
4677
|
+
This forces the watcher to re-scan the Python scope for new instances.
|
|
4678
|
+
|
|
4679
|
+
Args:
|
|
4680
|
+
watcher_id: The watcher's unique ID
|
|
4681
|
+
|
|
4682
|
+
Returns:
|
|
4683
|
+
true if successful, false otherwise
|
|
4684
|
+
"""
|
|
4685
|
+
from ..cssl_bridge import get_watcher
|
|
4686
|
+
watcher = get_watcher(watcher_id)
|
|
4687
|
+
if watcher:
|
|
4688
|
+
watcher.refresh()
|
|
4689
|
+
return True
|
|
4690
|
+
return False
|
|
4691
|
+
|
|
4692
|
+
# ============= v4.8.8: Snapshot Functions =============
|
|
4693
|
+
# Snapshot allows storing variable states and accessing them via %variable syntax
|
|
4694
|
+
|
|
4695
|
+
def builtin_snapshot(self, *args) -> bool:
|
|
4696
|
+
"""Snapshot a variable's current value.
|
|
4697
|
+
|
|
4698
|
+
Usage:
|
|
4699
|
+
snapshot(variable) - Snapshot using auto-detected name
|
|
4700
|
+
snapshot(variable, "name") - Snapshot with explicit name
|
|
4701
|
+
|
|
4702
|
+
After snapshotting, access the value with %name syntax.
|
|
4703
|
+
|
|
4704
|
+
Example:
|
|
4705
|
+
string version = "1.0";
|
|
4706
|
+
snapshot(version);
|
|
4707
|
+
version = "2.0";
|
|
4708
|
+
println(version, %version); // Output: 2.0 1.0
|
|
4709
|
+
"""
|
|
4710
|
+
import copy
|
|
4711
|
+
|
|
4712
|
+
if len(args) == 0:
|
|
4713
|
+
raise CSSLBuiltinError("snapshot() requires at least 1 argument")
|
|
4714
|
+
|
|
4715
|
+
value = args[0]
|
|
4716
|
+
name = args[1] if len(args) > 1 else None
|
|
4717
|
+
|
|
4718
|
+
# If no name provided, try to detect from runtime context
|
|
4719
|
+
if name is None and self.runtime:
|
|
4720
|
+
# Try to find the variable name from the current scope
|
|
4721
|
+
for var_name, var_val in self.runtime.scope.variables.items():
|
|
4722
|
+
if var_val is value and not var_name.startswith('_'):
|
|
4723
|
+
name = var_name
|
|
4724
|
+
break
|
|
4725
|
+
# Also check global scope
|
|
4726
|
+
if name is None:
|
|
4727
|
+
for var_name, var_val in self.runtime.global_scope.variables.items():
|
|
4728
|
+
if var_val is value and not var_name.startswith('_'):
|
|
4729
|
+
name = var_name
|
|
4730
|
+
break
|
|
4731
|
+
# v4.8.8: Also check builtins for function snapshots
|
|
4732
|
+
if name is None:
|
|
4733
|
+
for func_name, func_val in self._functions.items():
|
|
4734
|
+
if func_val is value and not func_name.startswith('_'):
|
|
4735
|
+
name = func_name
|
|
4736
|
+
break
|
|
4737
|
+
|
|
4738
|
+
if name is None:
|
|
4739
|
+
# Use a generic name based on type and hash
|
|
4740
|
+
name = f"_snap_{type(value).__name__}_{id(value)}"
|
|
4741
|
+
|
|
4742
|
+
# Deep copy to preserve the state
|
|
4743
|
+
try:
|
|
4744
|
+
snapped_value = copy.deepcopy(value)
|
|
4745
|
+
except:
|
|
4746
|
+
try:
|
|
4747
|
+
snapped_value = copy.copy(value)
|
|
4748
|
+
except:
|
|
4749
|
+
snapped_value = value
|
|
4750
|
+
|
|
4751
|
+
self._snapshots[name] = snapped_value
|
|
4752
|
+
return True
|
|
4753
|
+
|
|
4754
|
+
def builtin_get_snapshot(self, name: str) -> Any:
|
|
4755
|
+
"""Get a snapshotted value by name.
|
|
4756
|
+
|
|
4757
|
+
Usage:
|
|
4758
|
+
value = get_snapshot("variableName")
|
|
4759
|
+
|
|
4760
|
+
This is the programmatic way to access snapshots.
|
|
4761
|
+
The %variable syntax is the preferred way in CSSL code.
|
|
4762
|
+
"""
|
|
4763
|
+
if name not in self._snapshots:
|
|
4764
|
+
raise CSSLBuiltinError(f"No snapshot found for '{name}'")
|
|
4765
|
+
return self._snapshots[name]
|
|
4766
|
+
|
|
4767
|
+
def builtin_has_snapshot(self, name: str) -> bool:
|
|
4768
|
+
"""Check if a snapshot exists.
|
|
4769
|
+
|
|
4770
|
+
Usage:
|
|
4771
|
+
if (has_snapshot("version")) { ... }
|
|
4772
|
+
"""
|
|
4773
|
+
return name in self._snapshots
|
|
4774
|
+
|
|
4775
|
+
def builtin_clear_snapshot(self, name: str) -> bool:
|
|
4776
|
+
"""Clear a specific snapshot.
|
|
4777
|
+
|
|
4778
|
+
Usage:
|
|
4779
|
+
clear_snapshot("variableName")
|
|
4780
|
+
"""
|
|
4781
|
+
if name in self._snapshots:
|
|
4782
|
+
del self._snapshots[name]
|
|
4783
|
+
return True
|
|
4784
|
+
return False
|
|
4785
|
+
|
|
4786
|
+
def builtin_clear_all_snapshots(self) -> int:
|
|
4787
|
+
"""Clear all snapshots, return count of cleared items.
|
|
4788
|
+
|
|
4789
|
+
Usage:
|
|
4790
|
+
count = clear_snapshots()
|
|
4791
|
+
"""
|
|
4792
|
+
count = len(self._snapshots)
|
|
4793
|
+
self._snapshots.clear()
|
|
4794
|
+
return count
|
|
4795
|
+
|
|
4796
|
+
def builtin_list_snapshots(self) -> list:
|
|
4797
|
+
"""List all snapshot names.
|
|
4798
|
+
|
|
4799
|
+
Usage:
|
|
4800
|
+
names = list_snapshots()
|
|
4801
|
+
for (name in names) { println(name + " = " + get_snapshot(name)); }
|
|
4802
|
+
"""
|
|
4803
|
+
return list(self._snapshots.keys())
|
|
4804
|
+
|
|
4805
|
+
def builtin_restore_snapshot(self, name: str) -> Any:
|
|
4806
|
+
"""Restore a snapshot value and remove it from storage.
|
|
4807
|
+
|
|
4808
|
+
Usage:
|
|
4809
|
+
version = restore_snapshot("version")
|
|
4810
|
+
"""
|
|
4811
|
+
if name not in self._snapshots:
|
|
4812
|
+
raise CSSLBuiltinError(f"No snapshot found for '{name}'")
|
|
4813
|
+
value = self._snapshots[name]
|
|
4814
|
+
del self._snapshots[name]
|
|
4815
|
+
return value
|
|
4816
|
+
|
|
2872
4817
|
|
|
2873
4818
|
class CSSLizedPythonObject:
|
|
2874
4819
|
"""CSSL wrapper for Python objects (classes, instances, functions).
|