zexus 1.6.2
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.
- package/LICENSE +0 -0
- package/README.md +2513 -0
- package/bin/zexus +2 -0
- package/bin/zpics +2 -0
- package/bin/zpm +2 -0
- package/bin/zx +2 -0
- package/bin/zx-deploy +2 -0
- package/bin/zx-dev +2 -0
- package/bin/zx-run +2 -0
- package/package.json +66 -0
- package/scripts/README.md +24 -0
- package/scripts/postinstall.js +44 -0
- package/shared_config.json +24 -0
- package/src/README.md +1525 -0
- package/src/tests/run_zexus_tests.py +117 -0
- package/src/tests/test_all_phases.zx +346 -0
- package/src/tests/test_blockchain_features.zx +306 -0
- package/src/tests/test_complexity_features.zx +321 -0
- package/src/tests/test_core_integration.py +185 -0
- package/src/tests/test_phase10_ecosystem.zx +177 -0
- package/src/tests/test_phase1_modifiers.zx +87 -0
- package/src/tests/test_phase2_plugins.zx +80 -0
- package/src/tests/test_phase3_security.zx +97 -0
- package/src/tests/test_phase4_vfs.zx +116 -0
- package/src/tests/test_phase5_types.zx +117 -0
- package/src/tests/test_phase6_metaprogramming.zx +125 -0
- package/src/tests/test_phase7_optimization.zx +132 -0
- package/src/tests/test_phase9_advanced_types.zx +157 -0
- package/src/tests/test_security_features.py +419 -0
- package/src/tests/test_security_features.zx +276 -0
- package/src/tests/test_simple_zx.zx +1 -0
- package/src/tests/test_verification_simple.zx +69 -0
- package/src/zexus/__init__.py +28 -0
- package/src/zexus/__main__.py +5 -0
- package/src/zexus/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/advanced_types.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/builtin_modules.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/capability_system.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/complexity_system.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/concurrency_system.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/config.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/dependency_injection.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/ecosystem.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/environment.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/error_reporter.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/hybrid_orchestrator.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/lexer.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/metaprogramming.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/module_cache.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/object.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/optimization.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/plugin_system.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/policy_engine.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/security.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/stdlib_integration.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/strategy_recovery.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/syntax_validator.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/type_system.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/virtual_filesystem.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/zexus_ast.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/zexus_token.cpython-312.pyc +0 -0
- package/src/zexus/advanced_types.py +401 -0
- package/src/zexus/blockchain/__init__.py +40 -0
- package/src/zexus/blockchain/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/blockchain/__pycache__/crypto.cpython-312.pyc +0 -0
- package/src/zexus/blockchain/__pycache__/ledger.cpython-312.pyc +0 -0
- package/src/zexus/blockchain/__pycache__/transaction.cpython-312.pyc +0 -0
- package/src/zexus/blockchain/crypto.py +463 -0
- package/src/zexus/blockchain/ledger.py +255 -0
- package/src/zexus/blockchain/transaction.py +267 -0
- package/src/zexus/builtin_modules.py +284 -0
- package/src/zexus/builtin_plugins.py +317 -0
- package/src/zexus/capability_system.py +372 -0
- package/src/zexus/cli/__init__.py +2 -0
- package/src/zexus/cli/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/cli/__pycache__/main.cpython-312.pyc +0 -0
- package/src/zexus/cli/main.py +707 -0
- package/src/zexus/cli/zpm.py +203 -0
- package/src/zexus/compare_interpreter_compiler.py +146 -0
- package/src/zexus/compiler/__init__.py +169 -0
- package/src/zexus/compiler/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/lexer.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/parser.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/zexus_ast.cpython-312.pyc +0 -0
- package/src/zexus/compiler/bytecode.py +266 -0
- package/src/zexus/compiler/compat_runtime.py +277 -0
- package/src/zexus/compiler/lexer.py +257 -0
- package/src/zexus/compiler/parser.py +779 -0
- package/src/zexus/compiler/semantic.py +118 -0
- package/src/zexus/compiler/zexus_ast.py +454 -0
- package/src/zexus/complexity_system.py +575 -0
- package/src/zexus/concurrency_system.py +493 -0
- package/src/zexus/config.py +201 -0
- package/src/zexus/crypto_bridge.py +19 -0
- package/src/zexus/dependency_injection.py +423 -0
- package/src/zexus/ecosystem.py +434 -0
- package/src/zexus/environment.py +101 -0
- package/src/zexus/environment_manager.py +119 -0
- package/src/zexus/error_reporter.py +314 -0
- package/src/zexus/evaluator/__init__.py +12 -0
- package/src/zexus/evaluator/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/bytecode_compiler.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/core.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/expressions.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/functions.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/integration.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/statements.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/utils.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/bytecode_compiler.py +700 -0
- package/src/zexus/evaluator/core.py +891 -0
- package/src/zexus/evaluator/expressions.py +827 -0
- package/src/zexus/evaluator/functions.py +3989 -0
- package/src/zexus/evaluator/integration.py +396 -0
- package/src/zexus/evaluator/statements.py +4303 -0
- package/src/zexus/evaluator/utils.py +126 -0
- package/src/zexus/evaluator_original.py +2041 -0
- package/src/zexus/external_bridge.py +16 -0
- package/src/zexus/find_affected_imports.sh +155 -0
- package/src/zexus/hybrid_orchestrator.py +152 -0
- package/src/zexus/input_validation.py +259 -0
- package/src/zexus/lexer.py +571 -0
- package/src/zexus/logging.py +89 -0
- package/src/zexus/lsp/__init__.py +9 -0
- package/src/zexus/lsp/completion_provider.py +207 -0
- package/src/zexus/lsp/definition_provider.py +22 -0
- package/src/zexus/lsp/hover_provider.py +71 -0
- package/src/zexus/lsp/server.py +269 -0
- package/src/zexus/lsp/symbol_provider.py +31 -0
- package/src/zexus/metaprogramming.py +321 -0
- package/src/zexus/module_cache.py +89 -0
- package/src/zexus/module_manager.py +107 -0
- package/src/zexus/object.py +973 -0
- package/src/zexus/optimization.py +424 -0
- package/src/zexus/parser/__init__.py +31 -0
- package/src/zexus/parser/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/parser/__pycache__/parser.cpython-312.pyc +0 -0
- package/src/zexus/parser/__pycache__/strategy_context.cpython-312.pyc +0 -0
- package/src/zexus/parser/__pycache__/strategy_structural.cpython-312.pyc +0 -0
- package/src/zexus/parser/integration.py +86 -0
- package/src/zexus/parser/parser.py +3977 -0
- package/src/zexus/parser/strategy_context.py +7254 -0
- package/src/zexus/parser/strategy_structural.py +1033 -0
- package/src/zexus/persistence.py +391 -0
- package/src/zexus/plugin_system.py +290 -0
- package/src/zexus/policy_engine.py +365 -0
- package/src/zexus/profiler/__init__.py +5 -0
- package/src/zexus/profiler/profiler.py +233 -0
- package/src/zexus/purity_system.py +398 -0
- package/src/zexus/runtime/__init__.py +20 -0
- package/src/zexus/runtime/async_runtime.py +324 -0
- package/src/zexus/search_old_imports.sh +65 -0
- package/src/zexus/security.py +1407 -0
- package/src/zexus/stack_trace.py +233 -0
- package/src/zexus/stdlib/__init__.py +27 -0
- package/src/zexus/stdlib/blockchain.py +341 -0
- package/src/zexus/stdlib/compression.py +167 -0
- package/src/zexus/stdlib/crypto.py +124 -0
- package/src/zexus/stdlib/datetime.py +163 -0
- package/src/zexus/stdlib/db_mongo.py +199 -0
- package/src/zexus/stdlib/db_mysql.py +162 -0
- package/src/zexus/stdlib/db_postgres.py +163 -0
- package/src/zexus/stdlib/db_sqlite.py +133 -0
- package/src/zexus/stdlib/encoding.py +230 -0
- package/src/zexus/stdlib/fs.py +195 -0
- package/src/zexus/stdlib/http.py +219 -0
- package/src/zexus/stdlib/http_server.py +248 -0
- package/src/zexus/stdlib/json_module.py +61 -0
- package/src/zexus/stdlib/math.py +360 -0
- package/src/zexus/stdlib/os_module.py +265 -0
- package/src/zexus/stdlib/regex.py +148 -0
- package/src/zexus/stdlib/sockets.py +253 -0
- package/src/zexus/stdlib/test_framework.zx +208 -0
- package/src/zexus/stdlib/test_runner.zx +119 -0
- package/src/zexus/stdlib_integration.py +341 -0
- package/src/zexus/strategy_recovery.py +256 -0
- package/src/zexus/syntax_validator.py +356 -0
- package/src/zexus/testing/zpics.py +407 -0
- package/src/zexus/testing/zpics_runtime.py +369 -0
- package/src/zexus/type_system.py +374 -0
- package/src/zexus/validation_system.py +569 -0
- package/src/zexus/virtual_filesystem.py +355 -0
- package/src/zexus/vm/__init__.py +8 -0
- package/src/zexus/vm/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/async_optimizer.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/bytecode.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/cache.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/jit.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/memory_manager.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/memory_pool.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/optimizer.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/parallel_vm.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/peephole_optimizer.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/profiler.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/register_allocator.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/register_vm.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/ssa_converter.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/vm.cpython-312.pyc +0 -0
- package/src/zexus/vm/async_optimizer.py +420 -0
- package/src/zexus/vm/bytecode.py +428 -0
- package/src/zexus/vm/bytecode_converter.py +297 -0
- package/src/zexus/vm/cache.py +532 -0
- package/src/zexus/vm/jit.py +720 -0
- package/src/zexus/vm/memory_manager.py +520 -0
- package/src/zexus/vm/memory_pool.py +511 -0
- package/src/zexus/vm/optimizer.py +478 -0
- package/src/zexus/vm/parallel_vm.py +899 -0
- package/src/zexus/vm/peephole_optimizer.py +452 -0
- package/src/zexus/vm/profiler.py +527 -0
- package/src/zexus/vm/register_allocator.py +462 -0
- package/src/zexus/vm/register_vm.py +520 -0
- package/src/zexus/vm/ssa_converter.py +757 -0
- package/src/zexus/vm/vm.py +1392 -0
- package/src/zexus/zexus_ast.py +1782 -0
- package/src/zexus/zexus_token.py +253 -0
- package/src/zexus/zpm/__init__.py +15 -0
- package/src/zexus/zpm/installer.py +116 -0
- package/src/zexus/zpm/package_manager.py +208 -0
- package/src/zexus/zpm/publisher.py +98 -0
- package/src/zexus/zpm/registry.py +110 -0
- package/src/zexus.egg-info/PKG-INFO +2235 -0
- package/src/zexus.egg-info/SOURCES.txt +876 -0
- package/src/zexus.egg-info/dependency_links.txt +1 -0
- package/src/zexus.egg-info/entry_points.txt +3 -0
- package/src/zexus.egg-info/not-zip-safe +1 -0
- package/src/zexus.egg-info/requires.txt +14 -0
- package/src/zexus.egg-info/top_level.txt +2 -0
- package/zexus.json +14 -0
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Bytecode Caching System for Zexus VM
|
|
3
|
+
|
|
4
|
+
This module provides a comprehensive bytecode caching system to avoid recompiling
|
|
5
|
+
the same code multiple times. Features include:
|
|
6
|
+
- LRU (Least Recently Used) eviction policy
|
|
7
|
+
- Cache statistics tracking
|
|
8
|
+
- AST-based cache keys
|
|
9
|
+
- Optional persistent disk cache
|
|
10
|
+
- Memory-efficient storage
|
|
11
|
+
|
|
12
|
+
Part of Phase 4: Bytecode Caching Enhancement
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import hashlib
|
|
16
|
+
import json
|
|
17
|
+
import pickle
|
|
18
|
+
import time
|
|
19
|
+
from collections import OrderedDict
|
|
20
|
+
from dataclasses import dataclass, field
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Any, Dict, Optional, Tuple
|
|
23
|
+
|
|
24
|
+
from .bytecode import Bytecode
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class CacheStats:
|
|
29
|
+
"""Statistics for bytecode cache"""
|
|
30
|
+
hits: int = 0
|
|
31
|
+
misses: int = 0
|
|
32
|
+
evictions: int = 0
|
|
33
|
+
memory_bytes: int = 0
|
|
34
|
+
total_entries: int = 0
|
|
35
|
+
hit_rate: float = 0.0
|
|
36
|
+
|
|
37
|
+
def update_hit_rate(self):
|
|
38
|
+
"""Update hit rate percentage"""
|
|
39
|
+
total = self.hits + self.misses
|
|
40
|
+
self.hit_rate = (self.hits / total * 100) if total > 0 else 0.0
|
|
41
|
+
|
|
42
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
43
|
+
"""Convert stats to dictionary"""
|
|
44
|
+
return {
|
|
45
|
+
'hits': self.hits,
|
|
46
|
+
'misses': self.misses,
|
|
47
|
+
'evictions': self.evictions,
|
|
48
|
+
'memory_bytes': self.memory_bytes,
|
|
49
|
+
'total_entries': self.total_entries,
|
|
50
|
+
'hit_rate': round(self.hit_rate, 2)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class CacheEntry:
|
|
56
|
+
"""Entry in the bytecode cache"""
|
|
57
|
+
bytecode: Bytecode
|
|
58
|
+
timestamp: float
|
|
59
|
+
access_count: int = 0
|
|
60
|
+
size_bytes: int = 0
|
|
61
|
+
|
|
62
|
+
def update_access(self):
|
|
63
|
+
"""Update access timestamp and count"""
|
|
64
|
+
self.timestamp = time.time()
|
|
65
|
+
self.access_count += 1
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class BytecodeCache:
|
|
69
|
+
"""
|
|
70
|
+
LRU cache for compiled bytecode
|
|
71
|
+
|
|
72
|
+
Features:
|
|
73
|
+
- AST-based cache keys (hash of AST structure)
|
|
74
|
+
- LRU eviction when cache size limit is reached
|
|
75
|
+
- Statistics tracking (hits, misses, evictions)
|
|
76
|
+
- Optional persistent disk cache
|
|
77
|
+
- Memory-efficient storage
|
|
78
|
+
|
|
79
|
+
Usage:
|
|
80
|
+
cache = BytecodeCache(max_size=1000, persistent=False)
|
|
81
|
+
|
|
82
|
+
# Check cache
|
|
83
|
+
bytecode = cache.get(ast_node)
|
|
84
|
+
if bytecode is None:
|
|
85
|
+
# Compile and store
|
|
86
|
+
bytecode = compiler.compile(ast_node)
|
|
87
|
+
cache.put(ast_node, bytecode)
|
|
88
|
+
|
|
89
|
+
# Get statistics
|
|
90
|
+
stats = cache.get_stats()
|
|
91
|
+
print(f"Hit rate: {stats.hit_rate}%")
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
def __init__(
|
|
95
|
+
self,
|
|
96
|
+
max_size: int = 1000,
|
|
97
|
+
max_memory_mb: int = 100,
|
|
98
|
+
persistent: bool = False,
|
|
99
|
+
cache_dir: Optional[str] = None,
|
|
100
|
+
debug: bool = False
|
|
101
|
+
):
|
|
102
|
+
"""
|
|
103
|
+
Initialize bytecode cache
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
max_size: Maximum number of entries (default 1000)
|
|
107
|
+
max_memory_mb: Maximum memory usage in MB (default 100)
|
|
108
|
+
persistent: Enable disk-based persistent cache
|
|
109
|
+
cache_dir: Directory for persistent cache
|
|
110
|
+
debug: Enable debug output
|
|
111
|
+
"""
|
|
112
|
+
self.max_size = max_size
|
|
113
|
+
self.max_memory_bytes = max_memory_mb * 1024 * 1024
|
|
114
|
+
self.persistent = persistent
|
|
115
|
+
self.debug = debug
|
|
116
|
+
|
|
117
|
+
# LRU cache using OrderedDict (insertion order preserved)
|
|
118
|
+
self._cache: OrderedDict[str, CacheEntry] = OrderedDict()
|
|
119
|
+
|
|
120
|
+
# Statistics
|
|
121
|
+
self.stats = CacheStats()
|
|
122
|
+
|
|
123
|
+
# Persistent cache
|
|
124
|
+
self.cache_dir = None
|
|
125
|
+
if persistent:
|
|
126
|
+
self.cache_dir = Path(cache_dir) if cache_dir else Path.home() / '.zexus' / 'cache'
|
|
127
|
+
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
|
128
|
+
if self.debug:
|
|
129
|
+
print(f"📦 Cache: Persistent cache enabled at {self.cache_dir}")
|
|
130
|
+
|
|
131
|
+
def _hash_ast(self, ast_node: Any) -> str:
|
|
132
|
+
"""
|
|
133
|
+
Generate unique hash for AST node
|
|
134
|
+
|
|
135
|
+
Uses JSON serialization of AST structure to create deterministic hash.
|
|
136
|
+
Handles circular references and complex nested structures.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
ast_node: AST node to hash
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
MD5 hash string (32 characters)
|
|
143
|
+
"""
|
|
144
|
+
try:
|
|
145
|
+
# Convert AST to hashable representation
|
|
146
|
+
ast_repr = self._ast_to_dict(ast_node)
|
|
147
|
+
ast_json = json.dumps(ast_repr, sort_keys=True)
|
|
148
|
+
return hashlib.md5(ast_json.encode()).hexdigest()
|
|
149
|
+
except Exception as e:
|
|
150
|
+
# Fallback to string representation
|
|
151
|
+
if self.debug:
|
|
152
|
+
print(f"⚠️ Cache: AST hashing fallback ({e})")
|
|
153
|
+
return hashlib.md5(str(ast_node).encode()).hexdigest()
|
|
154
|
+
|
|
155
|
+
def _ast_to_dict(self, node: Any, depth: int = 0, max_depth: int = 50) -> Any:
|
|
156
|
+
"""
|
|
157
|
+
Convert AST node to dictionary for hashing
|
|
158
|
+
|
|
159
|
+
Recursively converts AST nodes to dictionaries, handling:
|
|
160
|
+
- Node types and attributes
|
|
161
|
+
- Lists and tuples
|
|
162
|
+
- Nested nodes
|
|
163
|
+
- Circular references (via depth limit)
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
node: AST node or value
|
|
167
|
+
depth: Current recursion depth
|
|
168
|
+
max_depth: Maximum recursion depth
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
Hashable representation (dict, list, or primitive)
|
|
172
|
+
"""
|
|
173
|
+
if depth > max_depth:
|
|
174
|
+
return f"<max_depth_{type(node).__name__}>"
|
|
175
|
+
|
|
176
|
+
# Handle None
|
|
177
|
+
if node is None:
|
|
178
|
+
return None
|
|
179
|
+
|
|
180
|
+
# Handle primitives
|
|
181
|
+
if isinstance(node, (int, float, str, bool)):
|
|
182
|
+
return node
|
|
183
|
+
|
|
184
|
+
# Handle lists/tuples
|
|
185
|
+
if isinstance(node, (list, tuple)):
|
|
186
|
+
return [self._ast_to_dict(item, depth + 1, max_depth) for item in node]
|
|
187
|
+
|
|
188
|
+
# Handle dictionaries
|
|
189
|
+
if isinstance(node, dict):
|
|
190
|
+
return {k: self._ast_to_dict(v, depth + 1, max_depth) for k, v in node.items()}
|
|
191
|
+
|
|
192
|
+
# Handle AST nodes (objects with __dict__)
|
|
193
|
+
if hasattr(node, '__dict__'):
|
|
194
|
+
result = {'__type__': type(node).__name__}
|
|
195
|
+
for key, value in node.__dict__.items():
|
|
196
|
+
if not key.startswith('_'): # Skip private attributes
|
|
197
|
+
result[key] = self._ast_to_dict(value, depth + 1, max_depth)
|
|
198
|
+
return result
|
|
199
|
+
|
|
200
|
+
# Fallback to string representation
|
|
201
|
+
return f"<{type(node).__name__}>"
|
|
202
|
+
|
|
203
|
+
def _estimate_size(self, bytecode: Bytecode) -> int:
|
|
204
|
+
"""
|
|
205
|
+
Estimate bytecode size in bytes
|
|
206
|
+
|
|
207
|
+
Approximates memory usage by counting:
|
|
208
|
+
- Instructions (each ~100 bytes)
|
|
209
|
+
- Constants (pickle size)
|
|
210
|
+
- Metadata (small overhead)
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
bytecode: Bytecode object
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
Estimated size in bytes
|
|
217
|
+
"""
|
|
218
|
+
try:
|
|
219
|
+
# Count instructions
|
|
220
|
+
instruction_size = len(bytecode.instructions) * 100 # ~100 bytes per instruction
|
|
221
|
+
|
|
222
|
+
# Estimate constants size
|
|
223
|
+
constants_size = 0
|
|
224
|
+
for const in bytecode.constants:
|
|
225
|
+
try:
|
|
226
|
+
constants_size += len(pickle.dumps(const))
|
|
227
|
+
except (TypeError, pickle.PicklingError):
|
|
228
|
+
constants_size += 100 # Fallback estimate
|
|
229
|
+
|
|
230
|
+
# Add metadata overhead
|
|
231
|
+
metadata_size = 200 # Small overhead for name, line_map, etc.
|
|
232
|
+
|
|
233
|
+
return instruction_size + constants_size + metadata_size
|
|
234
|
+
except (AttributeError, TypeError):
|
|
235
|
+
# Fallback to conservative estimate
|
|
236
|
+
return len(bytecode.instructions) * 150
|
|
237
|
+
|
|
238
|
+
def _evict_lru(self):
|
|
239
|
+
"""
|
|
240
|
+
Evict least recently used entry
|
|
241
|
+
|
|
242
|
+
Removes the oldest entry (first in OrderedDict) and updates statistics.
|
|
243
|
+
"""
|
|
244
|
+
if not self._cache:
|
|
245
|
+
return
|
|
246
|
+
|
|
247
|
+
# Remove oldest entry (LRU)
|
|
248
|
+
key, entry = self._cache.popitem(last=False)
|
|
249
|
+
|
|
250
|
+
# Update statistics
|
|
251
|
+
self.stats.evictions += 1
|
|
252
|
+
self.stats.memory_bytes -= entry.size_bytes
|
|
253
|
+
self.stats.total_entries -= 1
|
|
254
|
+
|
|
255
|
+
if self.debug:
|
|
256
|
+
print(f"🗑️ Cache: Evicted LRU entry {key[:8]}... (freed {entry.size_bytes} bytes)")
|
|
257
|
+
|
|
258
|
+
def _evict_to_fit(self, new_size: int):
|
|
259
|
+
"""
|
|
260
|
+
Evict entries until new entry fits
|
|
261
|
+
|
|
262
|
+
Keeps evicting LRU entries until:
|
|
263
|
+
1. Cache size < max_size
|
|
264
|
+
2. Memory usage + new_size < max_memory_bytes
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
new_size: Size of new entry in bytes
|
|
268
|
+
"""
|
|
269
|
+
# Evict by count
|
|
270
|
+
while len(self._cache) >= self.max_size:
|
|
271
|
+
self._evict_lru()
|
|
272
|
+
|
|
273
|
+
# Evict by memory
|
|
274
|
+
while self._cache and (self.stats.memory_bytes + new_size) > self.max_memory_bytes:
|
|
275
|
+
self._evict_lru()
|
|
276
|
+
|
|
277
|
+
def get(self, ast_node: Any) -> Optional[Bytecode]:
|
|
278
|
+
"""
|
|
279
|
+
Get bytecode from cache
|
|
280
|
+
|
|
281
|
+
If found:
|
|
282
|
+
- Returns cached bytecode
|
|
283
|
+
- Updates access timestamp and count
|
|
284
|
+
- Moves entry to end (most recent in LRU)
|
|
285
|
+
- Increments hit counter
|
|
286
|
+
|
|
287
|
+
If not found:
|
|
288
|
+
- Increments miss counter
|
|
289
|
+
- Returns None
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
ast_node: AST node to look up
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
Cached bytecode or None
|
|
296
|
+
"""
|
|
297
|
+
key = self._hash_ast(ast_node)
|
|
298
|
+
|
|
299
|
+
if key in self._cache:
|
|
300
|
+
# Cache hit
|
|
301
|
+
entry = self._cache[key]
|
|
302
|
+
entry.update_access()
|
|
303
|
+
|
|
304
|
+
# Move to end (most recently used)
|
|
305
|
+
self._cache.move_to_end(key)
|
|
306
|
+
|
|
307
|
+
# Update statistics
|
|
308
|
+
self.stats.hits += 1
|
|
309
|
+
self.stats.update_hit_rate()
|
|
310
|
+
|
|
311
|
+
if self.debug:
|
|
312
|
+
print(f"✅ Cache: HIT {key[:8]}... (access #{entry.access_count})")
|
|
313
|
+
|
|
314
|
+
return entry.bytecode
|
|
315
|
+
else:
|
|
316
|
+
# Cache miss
|
|
317
|
+
self.stats.misses += 1
|
|
318
|
+
self.stats.update_hit_rate()
|
|
319
|
+
|
|
320
|
+
if self.debug:
|
|
321
|
+
print(f"❌ Cache: MISS {key[:8]}...")
|
|
322
|
+
|
|
323
|
+
# Try persistent cache if enabled
|
|
324
|
+
if self.persistent:
|
|
325
|
+
bytecode = self._load_from_disk(key)
|
|
326
|
+
if bytecode:
|
|
327
|
+
# Found in disk cache, add to memory cache
|
|
328
|
+
self.put(ast_node, bytecode, skip_disk=True)
|
|
329
|
+
return bytecode
|
|
330
|
+
|
|
331
|
+
return None
|
|
332
|
+
|
|
333
|
+
def put(self, ast_node: Any, bytecode: Bytecode, skip_disk: bool = False):
|
|
334
|
+
"""
|
|
335
|
+
Store bytecode in cache
|
|
336
|
+
|
|
337
|
+
Process:
|
|
338
|
+
1. Hash AST node to create cache key
|
|
339
|
+
2. Estimate bytecode size
|
|
340
|
+
3. Evict entries if needed to fit new entry
|
|
341
|
+
4. Store in memory cache
|
|
342
|
+
5. Optionally save to disk cache
|
|
343
|
+
|
|
344
|
+
Args:
|
|
345
|
+
ast_node: AST node (cache key)
|
|
346
|
+
bytecode: Compiled bytecode
|
|
347
|
+
skip_disk: Skip disk cache (used when loading from disk)
|
|
348
|
+
"""
|
|
349
|
+
key = self._hash_ast(ast_node)
|
|
350
|
+
size = self._estimate_size(bytecode)
|
|
351
|
+
|
|
352
|
+
# Evict if needed
|
|
353
|
+
self._evict_to_fit(size)
|
|
354
|
+
|
|
355
|
+
# Create entry
|
|
356
|
+
entry = CacheEntry(
|
|
357
|
+
bytecode=bytecode,
|
|
358
|
+
timestamp=time.time(),
|
|
359
|
+
access_count=1,
|
|
360
|
+
size_bytes=size
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
# Store in cache
|
|
364
|
+
self._cache[key] = entry
|
|
365
|
+
|
|
366
|
+
# Update statistics
|
|
367
|
+
self.stats.memory_bytes += size
|
|
368
|
+
self.stats.total_entries += 1
|
|
369
|
+
|
|
370
|
+
if self.debug:
|
|
371
|
+
print(f"💾 Cache: PUT {key[:8]}... ({size} bytes, {len(self._cache)} entries)")
|
|
372
|
+
|
|
373
|
+
# Save to disk if persistent
|
|
374
|
+
if self.persistent and not skip_disk:
|
|
375
|
+
self._save_to_disk(key, bytecode)
|
|
376
|
+
|
|
377
|
+
def invalidate(self, ast_node: Any):
|
|
378
|
+
"""
|
|
379
|
+
Remove entry from cache
|
|
380
|
+
|
|
381
|
+
Args:
|
|
382
|
+
ast_node: AST node to invalidate
|
|
383
|
+
"""
|
|
384
|
+
key = self._hash_ast(ast_node)
|
|
385
|
+
|
|
386
|
+
if key in self._cache:
|
|
387
|
+
entry = self._cache.pop(key)
|
|
388
|
+
self.stats.memory_bytes -= entry.size_bytes
|
|
389
|
+
self.stats.total_entries -= 1
|
|
390
|
+
|
|
391
|
+
if self.debug:
|
|
392
|
+
print(f"🗑️ Cache: Invalidated {key[:8]}...")
|
|
393
|
+
|
|
394
|
+
# Remove from disk cache
|
|
395
|
+
if self.persistent:
|
|
396
|
+
self._delete_from_disk(key)
|
|
397
|
+
|
|
398
|
+
def clear(self):
|
|
399
|
+
"""Clear entire cache"""
|
|
400
|
+
self._cache.clear()
|
|
401
|
+
self.stats = CacheStats()
|
|
402
|
+
|
|
403
|
+
if self.debug:
|
|
404
|
+
print("🗑️ Cache: Cleared all entries")
|
|
405
|
+
|
|
406
|
+
# Clear disk cache
|
|
407
|
+
if self.persistent and self.cache_dir:
|
|
408
|
+
for cache_file in self.cache_dir.glob('*.cache'):
|
|
409
|
+
cache_file.unlink()
|
|
410
|
+
|
|
411
|
+
def get_stats(self) -> Dict[str, Any]:
|
|
412
|
+
"""
|
|
413
|
+
Get cache statistics
|
|
414
|
+
|
|
415
|
+
Returns:
|
|
416
|
+
Dictionary with cache statistics
|
|
417
|
+
"""
|
|
418
|
+
self.stats.update_hit_rate()
|
|
419
|
+
return self.stats.to_dict()
|
|
420
|
+
|
|
421
|
+
def reset_stats(self):
|
|
422
|
+
"""Reset statistics (keeps cache entries)"""
|
|
423
|
+
self.stats = CacheStats(
|
|
424
|
+
total_entries=len(self._cache),
|
|
425
|
+
memory_bytes=self.stats.memory_bytes
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
# ==================== Persistent Cache Methods ====================
|
|
429
|
+
|
|
430
|
+
def _save_to_disk(self, key: str, bytecode: Bytecode):
|
|
431
|
+
"""Save bytecode to disk cache"""
|
|
432
|
+
if not self.cache_dir:
|
|
433
|
+
return
|
|
434
|
+
|
|
435
|
+
try:
|
|
436
|
+
cache_file = self.cache_dir / f"{key}.cache"
|
|
437
|
+
with open(cache_file, 'wb') as f:
|
|
438
|
+
pickle.dump(bytecode, f, protocol=pickle.HIGHEST_PROTOCOL)
|
|
439
|
+
|
|
440
|
+
if self.debug:
|
|
441
|
+
print(f"💾 Cache: Saved to disk {key[:8]}...")
|
|
442
|
+
except Exception as e:
|
|
443
|
+
if self.debug:
|
|
444
|
+
print(f"⚠️ Cache: Failed to save to disk: {e}")
|
|
445
|
+
|
|
446
|
+
def _load_from_disk(self, key: str) -> Optional[Bytecode]:
|
|
447
|
+
"""Load bytecode from disk cache"""
|
|
448
|
+
if not self.cache_dir:
|
|
449
|
+
return None
|
|
450
|
+
|
|
451
|
+
try:
|
|
452
|
+
cache_file = self.cache_dir / f"{key}.cache"
|
|
453
|
+
if cache_file.exists():
|
|
454
|
+
with open(cache_file, 'rb') as f:
|
|
455
|
+
bytecode = pickle.load(f)
|
|
456
|
+
|
|
457
|
+
if self.debug:
|
|
458
|
+
print(f"💾 Cache: Loaded from disk {key[:8]}...")
|
|
459
|
+
|
|
460
|
+
return bytecode
|
|
461
|
+
except Exception as e:
|
|
462
|
+
if self.debug:
|
|
463
|
+
print(f"⚠️ Cache: Failed to load from disk: {e}")
|
|
464
|
+
|
|
465
|
+
return None
|
|
466
|
+
|
|
467
|
+
def _delete_from_disk(self, key: str):
|
|
468
|
+
"""Delete cache entry from disk"""
|
|
469
|
+
if not self.cache_dir:
|
|
470
|
+
return
|
|
471
|
+
|
|
472
|
+
try:
|
|
473
|
+
cache_file = self.cache_dir / f"{key}.cache"
|
|
474
|
+
if cache_file.exists():
|
|
475
|
+
cache_file.unlink()
|
|
476
|
+
except Exception as e:
|
|
477
|
+
if self.debug:
|
|
478
|
+
print(f"⚠️ Cache: Failed to delete from disk: {e}")
|
|
479
|
+
|
|
480
|
+
# ==================== Utility Methods ====================
|
|
481
|
+
|
|
482
|
+
def size(self) -> int:
|
|
483
|
+
"""Get current cache size (number of entries)"""
|
|
484
|
+
return len(self._cache)
|
|
485
|
+
|
|
486
|
+
def memory_usage(self) -> int:
|
|
487
|
+
"""Get current memory usage in bytes"""
|
|
488
|
+
return self.stats.memory_bytes
|
|
489
|
+
|
|
490
|
+
def memory_usage_mb(self) -> float:
|
|
491
|
+
"""Get current memory usage in MB"""
|
|
492
|
+
return self.stats.memory_bytes / (1024 * 1024)
|
|
493
|
+
|
|
494
|
+
def contains(self, ast_node: Any) -> bool:
|
|
495
|
+
"""Check if AST node is in cache"""
|
|
496
|
+
key = self._hash_ast(ast_node)
|
|
497
|
+
return key in self._cache
|
|
498
|
+
|
|
499
|
+
def get_entry_info(self, ast_node: Any) -> Optional[Dict[str, Any]]:
|
|
500
|
+
"""Get information about cached entry"""
|
|
501
|
+
key = self._hash_ast(ast_node)
|
|
502
|
+
|
|
503
|
+
if key in self._cache:
|
|
504
|
+
entry = self._cache[key]
|
|
505
|
+
return {
|
|
506
|
+
'key': key,
|
|
507
|
+
'timestamp': entry.timestamp,
|
|
508
|
+
'access_count': entry.access_count,
|
|
509
|
+
'size_bytes': entry.size_bytes,
|
|
510
|
+
'instruction_count': len(entry.bytecode.instructions),
|
|
511
|
+
'constant_count': len(entry.bytecode.constants)
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
return None
|
|
515
|
+
|
|
516
|
+
def get_all_keys(self) -> list:
|
|
517
|
+
"""Get all cache keys"""
|
|
518
|
+
return list(self._cache.keys())
|
|
519
|
+
|
|
520
|
+
def __len__(self) -> int:
|
|
521
|
+
"""Get cache size"""
|
|
522
|
+
return len(self._cache)
|
|
523
|
+
|
|
524
|
+
def __contains__(self, ast_node: Any) -> bool:
|
|
525
|
+
"""Check if AST node is cached"""
|
|
526
|
+
return self.contains(ast_node)
|
|
527
|
+
|
|
528
|
+
def __repr__(self) -> str:
|
|
529
|
+
"""String representation"""
|
|
530
|
+
return (f"BytecodeCache(size={len(self._cache)}/{self.max_size}, "
|
|
531
|
+
f"memory={self.memory_usage_mb():.2f}MB, "
|
|
532
|
+
f"hit_rate={self.stats.hit_rate:.1f}%)")
|