IncludeCPP 4.5.2__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 +429 -3
- 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.5.2.dist-info → includecpp-4.9.3.dist-info}/METADATA +81 -10
- {includecpp-4.5.2.dist-info → includecpp-4.9.3.dist-info}/RECORD +35 -33
- {includecpp-4.5.2.dist-info → includecpp-4.9.3.dist-info}/WHEEL +1 -1
- {includecpp-4.5.2.dist-info → includecpp-4.9.3.dist-info}/entry_points.txt +0 -0
- {includecpp-4.5.2.dist-info → includecpp-4.9.3.dist-info}/licenses/LICENSE +0 -0
- {includecpp-4.5.2.dist-info → includecpp-4.9.3.dist-info}/top_level.txt +0 -0
includecpp/core/cssl_bridge.py
CHANGED
|
@@ -405,7 +405,7 @@ class CsslLang:
|
|
|
405
405
|
return 'cssl-pl'
|
|
406
406
|
return 'cssl'
|
|
407
407
|
|
|
408
|
-
def run(self, path_or_code: str, *args) -> Any:
|
|
408
|
+
def run(self, path_or_code: str, *args, force_python: bool = False) -> Any:
|
|
409
409
|
"""
|
|
410
410
|
Execute CSSL code or file.
|
|
411
411
|
|
|
@@ -415,6 +415,7 @@ class CsslLang:
|
|
|
415
415
|
Args:
|
|
416
416
|
path_or_code: Path to .cssl file or CSSL code string
|
|
417
417
|
*args: Arguments to pass to the script (accessible via parameter.get())
|
|
418
|
+
force_python: Force Python interpreter (for full builtin support)
|
|
418
419
|
|
|
419
420
|
Returns:
|
|
420
421
|
Execution result. If parameter.return() was called, returns
|
|
@@ -444,9 +445,63 @@ class CsslLang:
|
|
|
444
445
|
# Path too long or invalid - treat as code
|
|
445
446
|
pass
|
|
446
447
|
|
|
448
|
+
# v4.6.5: Check for native/unative keywords
|
|
449
|
+
import re
|
|
450
|
+
has_native = bool(re.search(r'\bnative\b', source))
|
|
451
|
+
has_unative = bool(re.search(r'\bunative\b', source))
|
|
452
|
+
|
|
453
|
+
# v4.8.5: Python-only builtins (not available in C++ runtime)
|
|
454
|
+
# Auto-detect and use Python when these are present
|
|
455
|
+
PYTHON_ONLY_BUILTINS = {
|
|
456
|
+
# os/sys replacements
|
|
457
|
+
'getcwd', 'chdir', 'mkdir', 'rmdir', 'rmfile', 'rename',
|
|
458
|
+
'argv', 'argc', 'platform', 'version', 'exit',
|
|
459
|
+
# File operations
|
|
460
|
+
'listdir', 'makedirs', 'removefile', 'removedir', 'copyfile', 'movefile',
|
|
461
|
+
'readfile', 'writefile', 'appendfile', 'readlines', 'filesize',
|
|
462
|
+
'pathexists', 'exists', 'isfile', 'isdir',
|
|
463
|
+
# Environment
|
|
464
|
+
'env', 'setenv',
|
|
465
|
+
# Module imports
|
|
466
|
+
'pyimport', 'cppimport', 'include', 'libinclude',
|
|
467
|
+
# Advanced features
|
|
468
|
+
'initpy', 'initsh', 'appexec', 'createcmd',
|
|
469
|
+
# Instance reflection
|
|
470
|
+
'instance_getMethods', 'instance_getClasses', 'instance_getVars',
|
|
471
|
+
'instance_getAll', 'instance_call', 'instance_has', 'instance_type',
|
|
472
|
+
# Console/terminal functions
|
|
473
|
+
'clear', 'input', 'color',
|
|
474
|
+
'red', 'green', 'blue', 'yellow', 'cyan', 'magenta', 'white', 'black',
|
|
475
|
+
'bold', 'dim', 'italic', 'underline', 'blink', 'reverse',
|
|
476
|
+
# v4.9.0: Memory introspection and snapshot (Python reflection)
|
|
477
|
+
'memory', 'snapshot', 'address', 'reflect',
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
# Check if source uses any Python-only builtins
|
|
481
|
+
needs_python = any(
|
|
482
|
+
re.search(rf'\b{builtin}\s*\(', source)
|
|
483
|
+
for builtin in PYTHON_ONLY_BUILTINS
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
# Also detect module usage that requires Python runtime
|
|
487
|
+
has_python_modules = bool(re.search(r'\b(fmt|Console|Process|Config|Server)::', source))
|
|
488
|
+
|
|
489
|
+
# v4.9.3: Detect python:: namespace usage (parameter passing, pythonize, etc.)
|
|
490
|
+
has_python_namespace = bool(re.search(r'\bpython::', source))
|
|
491
|
+
|
|
492
|
+
# v4.9.0: Detect bit/byte/address type declarations (Python-only types)
|
|
493
|
+
has_binary_types = bool(re.search(r'\b(bit|byte|address)\s+\w+', source))
|
|
494
|
+
|
|
495
|
+
# unative forces Python execution (skip C++ entirely)
|
|
496
|
+
# force_python flag also skips C++ (for full builtin support like getcwd, listdir)
|
|
497
|
+
# Auto-detect Python-only builtins and use Python automatically
|
|
498
|
+
# v4.9.0: Also skip C++ for bit/byte types (Python-only)
|
|
499
|
+
# v4.9.3: Also skip C++ for python:: namespace usage
|
|
500
|
+
if has_unative or force_python or needs_python or has_python_modules or has_binary_types or has_python_namespace:
|
|
501
|
+
pass # Skip C++ block, go directly to Python execution
|
|
447
502
|
# Try C++ accelerated execution first (375x faster)
|
|
448
503
|
# Only use C++ for simple scripts without parameter passing
|
|
449
|
-
|
|
504
|
+
elif not args:
|
|
450
505
|
try:
|
|
451
506
|
from .cssl import run_cssl, run_cssl_file
|
|
452
507
|
if is_file and file_path:
|
|
@@ -454,9 +509,19 @@ class CsslLang:
|
|
|
454
509
|
else:
|
|
455
510
|
return run_cssl(source)
|
|
456
511
|
except Exception as cpp_error:
|
|
512
|
+
# native keyword forces C++ - no fallback allowed
|
|
513
|
+
if has_native:
|
|
514
|
+
raise RuntimeError(f"C++ execution failed (native mode): {cpp_error}") from cpp_error
|
|
515
|
+
|
|
457
516
|
# Fall back to Python for unsupported features
|
|
517
|
+
# v4.8.5: Extended fallback triggers for advanced CSSL syntax
|
|
458
518
|
error_msg = str(cpp_error).lower()
|
|
459
|
-
|
|
519
|
+
fallback_triggers = [
|
|
520
|
+
'unsupported', 'not implemented', 'unexpected', 'expected',
|
|
521
|
+
'syntax error', 'unknown identifier', 'undefined', 'not defined'
|
|
522
|
+
]
|
|
523
|
+
should_fallback = any(trigger in error_msg for trigger in fallback_triggers)
|
|
524
|
+
if not should_fallback:
|
|
460
525
|
# Real error - re-raise it
|
|
461
526
|
raise RuntimeError(str(cpp_error)) from cpp_error
|
|
462
527
|
# Otherwise fall through to Python
|
|
@@ -464,6 +529,10 @@ class CsslLang:
|
|
|
464
529
|
# Python execution (for scripts with args or when C++ fails)
|
|
465
530
|
runtime = self._get_runtime()
|
|
466
531
|
|
|
532
|
+
# v4.8.5: Strip unative directive before parsing (it's just a marker)
|
|
533
|
+
if has_unative:
|
|
534
|
+
source = re.sub(r'\bunative\s*;?\s*', '', source, count=1)
|
|
535
|
+
|
|
467
536
|
# Set arguments in runtime scope
|
|
468
537
|
from .cssl import Parameter
|
|
469
538
|
param = Parameter(list(args))
|
|
@@ -1552,6 +1621,357 @@ def makemodule(
|
|
|
1552
1621
|
return get_cssl().makemodule(main_script, payload_script, name, bind)
|
|
1553
1622
|
|
|
1554
1623
|
|
|
1624
|
+
# =============================================================================
|
|
1625
|
+
# v4.6.5: CsslWatcher - Live Python Instance Collection for CSSL Access
|
|
1626
|
+
# =============================================================================
|
|
1627
|
+
|
|
1628
|
+
# Global registry of active watchers
|
|
1629
|
+
_active_watchers: Dict[str, 'CsslWatcher'] = {}
|
|
1630
|
+
|
|
1631
|
+
|
|
1632
|
+
class CsslWatcher:
|
|
1633
|
+
"""
|
|
1634
|
+
Live Python instance watcher that collects all active instances, classes,
|
|
1635
|
+
and functions from the Python scope and makes them available to CSSL.
|
|
1636
|
+
|
|
1637
|
+
Usage in Python:
|
|
1638
|
+
from includecpp.core.cssl_bridge import CsslWatcher
|
|
1639
|
+
|
|
1640
|
+
cwatcher = CsslWatcher(id="MyWatcher")
|
|
1641
|
+
cwatcher.start()
|
|
1642
|
+
|
|
1643
|
+
class Game:
|
|
1644
|
+
def __init__(self):
|
|
1645
|
+
self.score = 0
|
|
1646
|
+
def start(self):
|
|
1647
|
+
print("Game started!")
|
|
1648
|
+
|
|
1649
|
+
game = Game()
|
|
1650
|
+
|
|
1651
|
+
# ... later
|
|
1652
|
+
cwatcher.end()
|
|
1653
|
+
|
|
1654
|
+
Usage in CSSL:
|
|
1655
|
+
# Get all instances from a watcher
|
|
1656
|
+
all_instances = watcher::get("MyWatcher");
|
|
1657
|
+
|
|
1658
|
+
# Access Python class/instance
|
|
1659
|
+
pygameclass = all_instances['Game'];
|
|
1660
|
+
game_instance = all_instances['game'];
|
|
1661
|
+
|
|
1662
|
+
# Call Python methods
|
|
1663
|
+
game_instance.start();
|
|
1664
|
+
|
|
1665
|
+
# Bidirectional: CSSL can overwrite Python functions
|
|
1666
|
+
int start() : overwrites all_instances['Game.start'] {
|
|
1667
|
+
printl("Overwritten by CSSL!");
|
|
1668
|
+
return 0;
|
|
1669
|
+
}
|
|
1670
|
+
"""
|
|
1671
|
+
|
|
1672
|
+
def __init__(self, id: str, auto_collect: bool = True, depth: int = 1):
|
|
1673
|
+
"""
|
|
1674
|
+
Initialize a new CsslWatcher.
|
|
1675
|
+
|
|
1676
|
+
Args:
|
|
1677
|
+
id: Unique identifier for this watcher (used in watcher::get("id"))
|
|
1678
|
+
auto_collect: If True, automatically collect instances periodically
|
|
1679
|
+
depth: Stack frame depth to look for variables (1 = caller's scope)
|
|
1680
|
+
"""
|
|
1681
|
+
self._id = id
|
|
1682
|
+
self._auto_collect = auto_collect
|
|
1683
|
+
self._depth = depth
|
|
1684
|
+
self._running = False
|
|
1685
|
+
self._thread: Optional[threading.Thread] = None
|
|
1686
|
+
self._lock = threading.Lock()
|
|
1687
|
+
self._instances: Dict[str, Any] = {}
|
|
1688
|
+
self._classes: Dict[str, type] = {}
|
|
1689
|
+
self._functions: Dict[str, Callable] = {}
|
|
1690
|
+
self._caller_frame = None
|
|
1691
|
+
self._caller_locals = {}
|
|
1692
|
+
self._caller_globals = {}
|
|
1693
|
+
|
|
1694
|
+
@property
|
|
1695
|
+
def id(self) -> str:
|
|
1696
|
+
"""Get the watcher ID."""
|
|
1697
|
+
return self._id
|
|
1698
|
+
|
|
1699
|
+
def start(self) -> 'CsslWatcher':
|
|
1700
|
+
"""
|
|
1701
|
+
Start the watcher. Collects instances from the caller's scope
|
|
1702
|
+
and registers this watcher globally.
|
|
1703
|
+
|
|
1704
|
+
Returns:
|
|
1705
|
+
self for chaining
|
|
1706
|
+
"""
|
|
1707
|
+
import inspect
|
|
1708
|
+
|
|
1709
|
+
# Get caller's frame
|
|
1710
|
+
frame = inspect.currentframe()
|
|
1711
|
+
try:
|
|
1712
|
+
# Go up the stack to find the caller
|
|
1713
|
+
for _ in range(self._depth + 1):
|
|
1714
|
+
if frame.f_back:
|
|
1715
|
+
frame = frame.f_back
|
|
1716
|
+
|
|
1717
|
+
self._caller_frame = frame
|
|
1718
|
+
self._caller_locals = frame.f_locals
|
|
1719
|
+
self._caller_globals = frame.f_globals
|
|
1720
|
+
finally:
|
|
1721
|
+
del frame
|
|
1722
|
+
|
|
1723
|
+
# Initial collection
|
|
1724
|
+
self._collect_instances()
|
|
1725
|
+
|
|
1726
|
+
# Register globally
|
|
1727
|
+
with self._lock:
|
|
1728
|
+
_active_watchers[self._id] = self
|
|
1729
|
+
self._running = True
|
|
1730
|
+
|
|
1731
|
+
# Start background thread if auto_collect
|
|
1732
|
+
if self._auto_collect:
|
|
1733
|
+
self._thread = threading.Thread(
|
|
1734
|
+
target=self._background_collect,
|
|
1735
|
+
daemon=True,
|
|
1736
|
+
name=f"CsslWatcher-{self._id}"
|
|
1737
|
+
)
|
|
1738
|
+
self._thread.start()
|
|
1739
|
+
|
|
1740
|
+
return self
|
|
1741
|
+
|
|
1742
|
+
def end(self) -> None:
|
|
1743
|
+
"""Stop the watcher and unregister it."""
|
|
1744
|
+
with self._lock:
|
|
1745
|
+
self._running = False
|
|
1746
|
+
if self._id in _active_watchers:
|
|
1747
|
+
del _active_watchers[self._id]
|
|
1748
|
+
|
|
1749
|
+
if self._thread and self._thread.is_alive():
|
|
1750
|
+
self._thread.join(timeout=1.0)
|
|
1751
|
+
|
|
1752
|
+
self._caller_frame = None
|
|
1753
|
+
self._caller_locals = {}
|
|
1754
|
+
self._caller_globals = {}
|
|
1755
|
+
|
|
1756
|
+
def stop(self) -> None:
|
|
1757
|
+
"""Alias for end()."""
|
|
1758
|
+
self.end()
|
|
1759
|
+
|
|
1760
|
+
def _collect_instances(self) -> None:
|
|
1761
|
+
"""Collect all instances, classes, and functions from the watched scope."""
|
|
1762
|
+
import inspect
|
|
1763
|
+
|
|
1764
|
+
with self._lock:
|
|
1765
|
+
# Combine locals and globals
|
|
1766
|
+
all_vars = {**self._caller_globals, **self._caller_locals}
|
|
1767
|
+
|
|
1768
|
+
for name, obj in all_vars.items():
|
|
1769
|
+
# Skip private/magic names and modules
|
|
1770
|
+
if name.startswith('_'):
|
|
1771
|
+
continue
|
|
1772
|
+
if inspect.ismodule(obj):
|
|
1773
|
+
continue
|
|
1774
|
+
|
|
1775
|
+
# Classify the object
|
|
1776
|
+
if inspect.isclass(obj):
|
|
1777
|
+
self._classes[name] = obj
|
|
1778
|
+
# Also collect class methods
|
|
1779
|
+
for method_name, method in inspect.getmembers(obj, predicate=inspect.isfunction):
|
|
1780
|
+
if not method_name.startswith('_'):
|
|
1781
|
+
self._functions[f"{name}.{method_name}"] = method
|
|
1782
|
+
elif callable(obj) and not isinstance(obj, type):
|
|
1783
|
+
self._functions[name] = obj
|
|
1784
|
+
elif hasattr(obj, '__class__') and not isinstance(obj, (int, float, str, bool, list, dict, tuple, set)):
|
|
1785
|
+
# It's an instance of a custom class
|
|
1786
|
+
self._instances[name] = obj
|
|
1787
|
+
# Also collect instance methods
|
|
1788
|
+
obj_class = obj.__class__
|
|
1789
|
+
for method_name in dir(obj):
|
|
1790
|
+
if not method_name.startswith('_'):
|
|
1791
|
+
try:
|
|
1792
|
+
method = getattr(obj, method_name)
|
|
1793
|
+
if callable(method):
|
|
1794
|
+
self._functions[f"{name}.{method_name}"] = method
|
|
1795
|
+
except Exception:
|
|
1796
|
+
pass
|
|
1797
|
+
|
|
1798
|
+
def _background_collect(self) -> None:
|
|
1799
|
+
"""Background thread for periodic collection."""
|
|
1800
|
+
import time
|
|
1801
|
+
while self._running:
|
|
1802
|
+
time.sleep(0.5) # Collect every 500ms
|
|
1803
|
+
if self._running:
|
|
1804
|
+
try:
|
|
1805
|
+
self._collect_instances()
|
|
1806
|
+
except Exception:
|
|
1807
|
+
pass
|
|
1808
|
+
|
|
1809
|
+
def refresh(self) -> None:
|
|
1810
|
+
"""Manually refresh the collected instances."""
|
|
1811
|
+
self._collect_instances()
|
|
1812
|
+
|
|
1813
|
+
def get_all(self) -> Dict[str, Any]:
|
|
1814
|
+
"""
|
|
1815
|
+
Get all collected items as a dictionary.
|
|
1816
|
+
|
|
1817
|
+
Returns:
|
|
1818
|
+
Dict with all instances, classes, and functions
|
|
1819
|
+
"""
|
|
1820
|
+
with self._lock:
|
|
1821
|
+
result = {}
|
|
1822
|
+
result.update(self._classes)
|
|
1823
|
+
result.update(self._instances)
|
|
1824
|
+
result.update(self._functions)
|
|
1825
|
+
return result
|
|
1826
|
+
|
|
1827
|
+
def get(self, path: str) -> Any:
|
|
1828
|
+
"""
|
|
1829
|
+
Get a specific item by path.
|
|
1830
|
+
|
|
1831
|
+
Args:
|
|
1832
|
+
path: Name or dotted path like 'Game' or 'game.start'
|
|
1833
|
+
|
|
1834
|
+
Returns:
|
|
1835
|
+
The requested object or None
|
|
1836
|
+
"""
|
|
1837
|
+
with self._lock:
|
|
1838
|
+
# Check direct matches first
|
|
1839
|
+
if path in self._classes:
|
|
1840
|
+
return self._classes[path]
|
|
1841
|
+
if path in self._instances:
|
|
1842
|
+
return self._instances[path]
|
|
1843
|
+
if path in self._functions:
|
|
1844
|
+
return self._functions[path]
|
|
1845
|
+
|
|
1846
|
+
# Handle dotted paths
|
|
1847
|
+
if '.' in path:
|
|
1848
|
+
parts = path.split('.', 1)
|
|
1849
|
+
base = parts[0]
|
|
1850
|
+
rest = parts[1]
|
|
1851
|
+
|
|
1852
|
+
# Get the base object
|
|
1853
|
+
obj = self._instances.get(base) or self._classes.get(base)
|
|
1854
|
+
if obj:
|
|
1855
|
+
try:
|
|
1856
|
+
# Navigate the path
|
|
1857
|
+
for part in rest.split('.'):
|
|
1858
|
+
obj = getattr(obj, part)
|
|
1859
|
+
return obj
|
|
1860
|
+
except AttributeError:
|
|
1861
|
+
pass
|
|
1862
|
+
|
|
1863
|
+
return None
|
|
1864
|
+
|
|
1865
|
+
def set(self, path: str, value: Any) -> bool:
|
|
1866
|
+
"""
|
|
1867
|
+
Set/overwrite a value at the given path (bidirectional).
|
|
1868
|
+
|
|
1869
|
+
Args:
|
|
1870
|
+
path: Name or dotted path like 'Game.start'
|
|
1871
|
+
value: New value (function, class, or instance)
|
|
1872
|
+
|
|
1873
|
+
Returns:
|
|
1874
|
+
True if successful
|
|
1875
|
+
"""
|
|
1876
|
+
with self._lock:
|
|
1877
|
+
if '.' in path:
|
|
1878
|
+
parts = path.rsplit('.', 1)
|
|
1879
|
+
base_path = parts[0]
|
|
1880
|
+
attr_name = parts[1]
|
|
1881
|
+
|
|
1882
|
+
# Get the base object
|
|
1883
|
+
obj = self.get(base_path)
|
|
1884
|
+
if obj:
|
|
1885
|
+
try:
|
|
1886
|
+
setattr(obj, attr_name, value)
|
|
1887
|
+
# Update our registry
|
|
1888
|
+
self._functions[path] = value
|
|
1889
|
+
return True
|
|
1890
|
+
except Exception:
|
|
1891
|
+
pass
|
|
1892
|
+
else:
|
|
1893
|
+
# Direct assignment to scope
|
|
1894
|
+
if callable(value) and not isinstance(value, type):
|
|
1895
|
+
self._functions[path] = value
|
|
1896
|
+
elif isinstance(value, type):
|
|
1897
|
+
self._classes[path] = value
|
|
1898
|
+
else:
|
|
1899
|
+
self._instances[path] = value
|
|
1900
|
+
|
|
1901
|
+
# Also update caller's scope
|
|
1902
|
+
if path in self._caller_locals:
|
|
1903
|
+
self._caller_locals[path] = value
|
|
1904
|
+
elif path in self._caller_globals:
|
|
1905
|
+
self._caller_globals[path] = value
|
|
1906
|
+
|
|
1907
|
+
return True
|
|
1908
|
+
|
|
1909
|
+
return False
|
|
1910
|
+
|
|
1911
|
+
def __getitem__(self, key: str) -> Any:
|
|
1912
|
+
"""Dict-like access: watcher['Game']"""
|
|
1913
|
+
return self.get(key)
|
|
1914
|
+
|
|
1915
|
+
def __setitem__(self, key: str, value: Any) -> None:
|
|
1916
|
+
"""Dict-like assignment: watcher['Game.start'] = new_func"""
|
|
1917
|
+
self.set(key, value)
|
|
1918
|
+
|
|
1919
|
+
def __contains__(self, key: str) -> bool:
|
|
1920
|
+
"""Check if key exists: 'Game' in watcher"""
|
|
1921
|
+
return self.get(key) is not None
|
|
1922
|
+
|
|
1923
|
+
def __repr__(self) -> str:
|
|
1924
|
+
with self._lock:
|
|
1925
|
+
return (f"CsslWatcher(id='{self._id}', "
|
|
1926
|
+
f"classes={len(self._classes)}, "
|
|
1927
|
+
f"instances={len(self._instances)}, "
|
|
1928
|
+
f"functions={len(self._functions)}, "
|
|
1929
|
+
f"running={self._running})")
|
|
1930
|
+
|
|
1931
|
+
|
|
1932
|
+
def watcher_get(watcher_id: str) -> Optional[Dict[str, Any]]:
|
|
1933
|
+
"""
|
|
1934
|
+
Get all instances from a watcher by ID.
|
|
1935
|
+
This is the Python-side implementation for watcher::get("id").
|
|
1936
|
+
|
|
1937
|
+
Args:
|
|
1938
|
+
watcher_id: The watcher's unique ID
|
|
1939
|
+
|
|
1940
|
+
Returns:
|
|
1941
|
+
Dict of all collected instances, classes, and functions
|
|
1942
|
+
"""
|
|
1943
|
+
if watcher_id in _active_watchers:
|
|
1944
|
+
return _active_watchers[watcher_id].get_all()
|
|
1945
|
+
return None
|
|
1946
|
+
|
|
1947
|
+
|
|
1948
|
+
def watcher_set(watcher_id: str, path: str, value: Any) -> bool:
|
|
1949
|
+
"""
|
|
1950
|
+
Set a value in a watcher (bidirectional overwrite).
|
|
1951
|
+
|
|
1952
|
+
Args:
|
|
1953
|
+
watcher_id: The watcher's unique ID
|
|
1954
|
+
path: Path to the item (e.g., 'Game.start')
|
|
1955
|
+
value: New value
|
|
1956
|
+
|
|
1957
|
+
Returns:
|
|
1958
|
+
True if successful
|
|
1959
|
+
"""
|
|
1960
|
+
if watcher_id in _active_watchers:
|
|
1961
|
+
return _active_watchers[watcher_id].set(path, value)
|
|
1962
|
+
return False
|
|
1963
|
+
|
|
1964
|
+
|
|
1965
|
+
def get_watcher(watcher_id: str) -> Optional[CsslWatcher]:
|
|
1966
|
+
"""Get a watcher instance by ID."""
|
|
1967
|
+
return _active_watchers.get(watcher_id)
|
|
1968
|
+
|
|
1969
|
+
|
|
1970
|
+
def list_watchers() -> List[str]:
|
|
1971
|
+
"""List all active watcher IDs."""
|
|
1972
|
+
return list(_active_watchers.keys())
|
|
1973
|
+
|
|
1974
|
+
|
|
1555
1975
|
# Export all
|
|
1556
1976
|
__all__ = [
|
|
1557
1977
|
'CsslLang',
|
|
@@ -1586,4 +2006,10 @@ __all__ = [
|
|
|
1586
2006
|
'shared',
|
|
1587
2007
|
'get_shared',
|
|
1588
2008
|
'cleanup_shared',
|
|
2009
|
+
# v4.6.5: CsslWatcher for live Python instance access
|
|
2010
|
+
'CsslWatcher',
|
|
2011
|
+
'watcher_get',
|
|
2012
|
+
'watcher_set',
|
|
2013
|
+
'get_watcher',
|
|
2014
|
+
'list_watchers',
|
|
1589
2015
|
]
|
includecpp/core/error_catalog.py
CHANGED
|
@@ -10,6 +10,28 @@ from typing import Optional, Dict, Tuple
|
|
|
10
10
|
# Error catalog: pattern -> (message, category)
|
|
11
11
|
# Categories: beginner, compiler, linker, cmake, pybind11, runtime, plugin, import
|
|
12
12
|
ERROR_CATALOG = {
|
|
13
|
+
# ========== PRIORITY: FILE LOCK ERRORS (checked FIRST) ==========
|
|
14
|
+
# These must be at the top to catch file-locked errors before other patterns match
|
|
15
|
+
"FILE_LOCKED_WIN32": {
|
|
16
|
+
"pattern": r"WinError 32|being used by another process|cannot access.*because.*used|process cannot access|PermissionError.*api\.pyd|Permission denied.*\.pyd",
|
|
17
|
+
"message": "Module file is locked by another process!\n\nThis usually means:\n - A compiled .exe using this module is still running\n - Another Python script has imported this module\n - Your IDE has the file open\n\nFix:\n 1. Close any running .exe that uses this module\n 2. Close Python REPL/scripts using this module\n 3. Restart your IDE if it imported the module\n 4. Then run 'includecpp rebuild' again",
|
|
18
|
+
"category": "import"
|
|
19
|
+
},
|
|
20
|
+
"FILE_LOCKED_COPY": {
|
|
21
|
+
"pattern": r"copy.*failed.*permission|cannot (copy|overwrite|write).*\.pyd|failed.*copy.*bindings",
|
|
22
|
+
"message": "Cannot overwrite module file - it's locked!\n\nThe compiled .pyd/.so file is in use by another process.\n\nFix:\n 1. Close any running .exe that uses this module\n 2. Close all Python processes that imported this module\n 3. Run 'includecpp rebuild' again",
|
|
23
|
+
"category": "import"
|
|
24
|
+
},
|
|
25
|
+
"FILE_LOCKED_LINKER": {
|
|
26
|
+
"pattern": r"cannot open output file.*\.pyd|cannot open.*api\.pyd|ld\.exe:.*cannot open|ld\.exe:.*Permission denied|ld:.*cannot open output|error: unable to open output file|collect2.*ld.*Permission denied|LINK :.*cannot open.*\.pyd",
|
|
27
|
+
"message": "Linker cannot write output file - it's locked!\n\nThe .pyd/.so file is being used by another process.\n\nFix:\n 1. Close any running .exe using this module\n 2. Close Python/IDE that imported this module\n 3. Run 'includecpp rebuild --clean'",
|
|
28
|
+
"category": "linker"
|
|
29
|
+
},
|
|
30
|
+
"PERMISSION_DENIED_PYD": {
|
|
31
|
+
"pattern": r"Permission denied.*\.pyd|Permission denied.*\.so|PermissionError.*bindings",
|
|
32
|
+
"message": "Permission denied writing module file!\n\nThe .pyd/.so file is locked (probably by a running process).\n\nFix: Close any .exe or Python script using this module, then rebuild.",
|
|
33
|
+
"category": "import"
|
|
34
|
+
},
|
|
13
35
|
# ========== A. BEGINNER MISTAKES (1-20) ==========
|
|
14
36
|
"MODULE_NOT_BUILT": {
|
|
15
37
|
"pattern": r"Module '(\w+)'.*not found|No module named '(\w+)'",
|
|
@@ -208,11 +230,8 @@ ERROR_CATALOG = {
|
|
|
208
230
|
"message": "Array index possibly out of bounds.\n\nFix: Check array indices",
|
|
209
231
|
"category": "compiler"
|
|
210
232
|
},
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
"message": "Comparing signed and unsigned.\n\nFix: Cast one to match the other",
|
|
214
|
-
"category": "compiler"
|
|
215
|
-
},
|
|
233
|
+
# SIGNED_UNSIGNED removed in v4.6.6 - this is a warning, not an error
|
|
234
|
+
# pybind11 generates code that triggers this warning, we suppress it with -Wno-sign-compare
|
|
216
235
|
"REDEFINITION": {
|
|
217
236
|
"pattern": r"redefinition of ['\"`]([^'\"`]+)['\"`]",
|
|
218
237
|
"message": "'{name}' defined twice.\n\nFix: Remove duplicate or use different names",
|
|
@@ -276,12 +295,12 @@ ERROR_CATALOG = {
|
|
|
276
295
|
"category": "linker"
|
|
277
296
|
},
|
|
278
297
|
"MISSING_LIBSTDCPP": {
|
|
279
|
-
"pattern": r"
|
|
298
|
+
"pattern": r"cannot find -lstdc\+\+|ld:.*cannot find.*libstdc\+\+|error:.*libstdc\+\+\.so.*not found|/usr/lib.*libstdc\+\+.*not found",
|
|
280
299
|
"message": "C++ runtime missing.\n\nFix: Install gcc-libs/libstdc++ package",
|
|
281
300
|
"category": "linker"
|
|
282
301
|
},
|
|
283
302
|
"MISSING_LIBPYTHON": {
|
|
284
|
-
"pattern": r"
|
|
303
|
+
"pattern": r"cannot find -lpython|cannot find.*libpython|error:.*libpython.*not found|ld:.*-lpython.*not found",
|
|
285
304
|
"message": "Python dev libs missing.\n\nFix: Install python3-dev/python3-devel",
|
|
286
305
|
"category": "linker"
|
|
287
306
|
},
|
|
@@ -655,14 +674,39 @@ ERROR_CATALOG = {
|
|
|
655
674
|
"message": "Module not in Python path.\n\nFix: Check PYTHONPATH or install location",
|
|
656
675
|
"category": "import"
|
|
657
676
|
},
|
|
677
|
+
"FILE_LOCKED_WINDOWS": {
|
|
678
|
+
"pattern": r"WinError 32|being used by another process|cannot access.*because.*used|process cannot access",
|
|
679
|
+
"message": "File is locked by another process!\n\nThis usually means:\n - A compiled .exe is still running\n - Another Python script is using this module\n - An IDE has the file open\n\nFix: Close any running .exe or Python process that uses this module,\n then run 'includecpp rebuild' again.",
|
|
680
|
+
"category": "import"
|
|
681
|
+
},
|
|
682
|
+
"FILE_LOCKED_LINUX": {
|
|
683
|
+
"pattern": r"ETXTBSY|EBUSY|Text file busy|Device or resource busy|\[Errno 16\]|\[Errno 26\]",
|
|
684
|
+
"message": "File is busy (in use by another process)!\n\nThis usually means:\n - A running process is using this shared library\n - Another Python script has imported this module\n\nFix: Close any process using this module, then run 'includecpp rebuild' again.",
|
|
685
|
+
"category": "import"
|
|
686
|
+
},
|
|
687
|
+
"MODULE_FILE_LOCKED": {
|
|
688
|
+
"pattern": r"(api\.pyd|\.pyd|\.so|\.dll).*(?:Permission|WinError|denied|locked|in use|busy)",
|
|
689
|
+
"message": "Module file (.pyd/.so/.dll) is locked!\n\nThe compiled module cannot be overwritten because it's in use.\n\nFix:\n 1. Close any running executable that uses this module\n 2. Close Python scripts/REPL using this module\n 3. Restart your IDE if it imported the module\n 4. Then run 'includecpp rebuild'",
|
|
690
|
+
"category": "import"
|
|
691
|
+
},
|
|
692
|
+
"PERMISSION_ERROR_WINDOWS": {
|
|
693
|
+
"pattern": r"WinError 5|WinError 13|Access is denied",
|
|
694
|
+
"message": "Permission denied (Windows).\n\nPossible causes:\n - File is locked by another process (close running .exe)\n - Antivirus blocking write access\n - Need administrator rights\n\nFix: Close any process using this file, or run as administrator.",
|
|
695
|
+
"category": "import"
|
|
696
|
+
},
|
|
697
|
+
"PERMISSION_ERROR_LINUX": {
|
|
698
|
+
"pattern": r"\[Errno 13\]|EACCES|Operation not permitted|\[Errno 1\]",
|
|
699
|
+
"message": "Permission denied (Linux/Mac).\n\nPossible causes:\n - File is locked by another process\n - Insufficient permissions\n - File owned by different user\n\nFix: Close any process using this file, check ownership, or use sudo.",
|
|
700
|
+
"category": "import"
|
|
701
|
+
},
|
|
658
702
|
"PERMISSION_ERROR": {
|
|
659
703
|
"pattern": r"permission denied|PermissionError|access.*denied",
|
|
660
|
-
"message": "Permission denied.\n\nFix: Check file permissions or
|
|
704
|
+
"message": "Permission denied.\n\nFix: Check file permissions or close processes using the file",
|
|
661
705
|
"category": "import"
|
|
662
706
|
},
|
|
663
707
|
"FILE_LOCKED": {
|
|
664
|
-
"pattern": r"file.*locked|file.*in use|being used.*another",
|
|
665
|
-
"message": "File in use by another process.\n\nFix: Close
|
|
708
|
+
"pattern": r"file.*locked|file.*in use|being used.*another|file is busy",
|
|
709
|
+
"message": "File in use by another process.\n\nFix: Close Python processes or executables using this module",
|
|
666
710
|
"category": "import"
|
|
667
711
|
},
|
|
668
712
|
"REGISTRY_MISSING": {
|