zexus 1.6.8 → 1.7.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.
Files changed (177) hide show
  1. package/README.md +12 -5
  2. package/package.json +1 -1
  3. package/src/__init__.py +7 -0
  4. package/src/zexus/__init__.py +1 -1
  5. package/src/zexus/__pycache__/__init__.cpython-312.pyc +0 -0
  6. package/src/zexus/__pycache__/capability_system.cpython-312.pyc +0 -0
  7. package/src/zexus/__pycache__/debug_sanitizer.cpython-312.pyc +0 -0
  8. package/src/zexus/__pycache__/environment.cpython-312.pyc +0 -0
  9. package/src/zexus/__pycache__/error_reporter.cpython-312.pyc +0 -0
  10. package/src/zexus/__pycache__/input_validation.cpython-312.pyc +0 -0
  11. package/src/zexus/__pycache__/lexer.cpython-312.pyc +0 -0
  12. package/src/zexus/__pycache__/module_cache.cpython-312.pyc +0 -0
  13. package/src/zexus/__pycache__/module_manager.cpython-312.pyc +0 -0
  14. package/src/zexus/__pycache__/object.cpython-312.pyc +0 -0
  15. package/src/zexus/__pycache__/security.cpython-312.pyc +0 -0
  16. package/src/zexus/__pycache__/security_enforcement.cpython-312.pyc +0 -0
  17. package/src/zexus/__pycache__/syntax_validator.cpython-312.pyc +0 -0
  18. package/src/zexus/__pycache__/zexus_ast.cpython-312.pyc +0 -0
  19. package/src/zexus/__pycache__/zexus_token.cpython-312.pyc +0 -0
  20. package/src/zexus/access_control_system/__pycache__/__init__.cpython-312.pyc +0 -0
  21. package/src/zexus/access_control_system/__pycache__/access_control.cpython-312.pyc +0 -0
  22. package/src/zexus/advanced_types.py +17 -2
  23. package/src/zexus/blockchain/__init__.py +411 -0
  24. package/src/zexus/blockchain/accelerator.py +1160 -0
  25. package/src/zexus/blockchain/chain.py +660 -0
  26. package/src/zexus/blockchain/consensus.py +821 -0
  27. package/src/zexus/blockchain/contract_vm.py +1019 -0
  28. package/src/zexus/blockchain/crypto.py +79 -14
  29. package/src/zexus/blockchain/events.py +526 -0
  30. package/src/zexus/blockchain/loadtest.py +721 -0
  31. package/src/zexus/blockchain/monitoring.py +350 -0
  32. package/src/zexus/blockchain/mpt.py +716 -0
  33. package/src/zexus/blockchain/multichain.py +951 -0
  34. package/src/zexus/blockchain/multiprocess_executor.py +338 -0
  35. package/src/zexus/blockchain/network.py +886 -0
  36. package/src/zexus/blockchain/node.py +666 -0
  37. package/src/zexus/blockchain/rpc.py +1203 -0
  38. package/src/zexus/blockchain/rust_bridge.py +421 -0
  39. package/src/zexus/blockchain/storage.py +423 -0
  40. package/src/zexus/blockchain/tokens.py +750 -0
  41. package/src/zexus/blockchain/upgradeable.py +1004 -0
  42. package/src/zexus/blockchain/verification.py +1602 -0
  43. package/src/zexus/blockchain/wallet.py +621 -0
  44. package/src/zexus/capability_system.py +184 -9
  45. package/src/zexus/cli/__pycache__/main.cpython-312.pyc +0 -0
  46. package/src/zexus/cli/main.py +383 -34
  47. package/src/zexus/cli/zpm.py +1 -1
  48. package/src/zexus/compiler/__pycache__/bytecode.cpython-312.pyc +0 -0
  49. package/src/zexus/compiler/__pycache__/lexer.cpython-312.pyc +0 -0
  50. package/src/zexus/compiler/__pycache__/parser.cpython-312.pyc +0 -0
  51. package/src/zexus/compiler/__pycache__/semantic.cpython-312.pyc +0 -0
  52. package/src/zexus/compiler/__pycache__/zexus_ast.cpython-312.pyc +0 -0
  53. package/src/zexus/compiler/bytecode.py +124 -7
  54. package/src/zexus/compiler/compat_runtime.py +6 -2
  55. package/src/zexus/compiler/lexer.py +16 -5
  56. package/src/zexus/compiler/parser.py +108 -7
  57. package/src/zexus/compiler/semantic.py +18 -19
  58. package/src/zexus/compiler/zexus_ast.py +26 -1
  59. package/src/zexus/concurrency_system.py +79 -0
  60. package/src/zexus/config.py +54 -0
  61. package/src/zexus/crypto_bridge.py +244 -8
  62. package/src/zexus/dap/__init__.py +10 -0
  63. package/src/zexus/dap/__main__.py +4 -0
  64. package/src/zexus/dap/dap_server.py +391 -0
  65. package/src/zexus/dap/debug_engine.py +298 -0
  66. package/src/zexus/environment.py +112 -9
  67. package/src/zexus/evaluator/__pycache__/bytecode_compiler.cpython-312.pyc +0 -0
  68. package/src/zexus/evaluator/__pycache__/core.cpython-312.pyc +0 -0
  69. package/src/zexus/evaluator/__pycache__/expressions.cpython-312.pyc +0 -0
  70. package/src/zexus/evaluator/__pycache__/functions.cpython-312.pyc +0 -0
  71. package/src/zexus/evaluator/__pycache__/resource_limiter.cpython-312.pyc +0 -0
  72. package/src/zexus/evaluator/__pycache__/statements.cpython-312.pyc +0 -0
  73. package/src/zexus/evaluator/__pycache__/unified_execution.cpython-312.pyc +0 -0
  74. package/src/zexus/evaluator/__pycache__/utils.cpython-312.pyc +0 -0
  75. package/src/zexus/evaluator/bytecode_compiler.py +457 -37
  76. package/src/zexus/evaluator/core.py +644 -50
  77. package/src/zexus/evaluator/expressions.py +358 -62
  78. package/src/zexus/evaluator/functions.py +458 -20
  79. package/src/zexus/evaluator/resource_limiter.py +4 -4
  80. package/src/zexus/evaluator/statements.py +774 -122
  81. package/src/zexus/evaluator/unified_execution.py +573 -72
  82. package/src/zexus/evaluator/utils.py +14 -2
  83. package/src/zexus/evaluator_original.py +1 -1
  84. package/src/zexus/event_loop.py +186 -0
  85. package/src/zexus/lexer.py +742 -458
  86. package/src/zexus/lsp/__init__.py +1 -1
  87. package/src/zexus/lsp/definition_provider.py +163 -9
  88. package/src/zexus/lsp/server.py +22 -8
  89. package/src/zexus/lsp/symbol_provider.py +182 -9
  90. package/src/zexus/module_cache.py +239 -9
  91. package/src/zexus/module_manager.py +129 -1
  92. package/src/zexus/object.py +76 -6
  93. package/src/zexus/parser/__pycache__/parser.cpython-312.pyc +0 -0
  94. package/src/zexus/parser/__pycache__/strategy_context.cpython-312.pyc +0 -0
  95. package/src/zexus/parser/__pycache__/strategy_structural.cpython-312.pyc +0 -0
  96. package/src/zexus/parser/parser.py +1349 -408
  97. package/src/zexus/parser/strategy_context.py +755 -58
  98. package/src/zexus/parser/strategy_structural.py +121 -21
  99. package/src/zexus/persistence.py +15 -1
  100. package/src/zexus/renderer/__init__.py +61 -0
  101. package/src/zexus/renderer/__pycache__/__init__.cpython-312.pyc +0 -0
  102. package/src/zexus/renderer/__pycache__/backend.cpython-312.pyc +0 -0
  103. package/src/zexus/renderer/__pycache__/canvas.cpython-312.pyc +0 -0
  104. package/src/zexus/renderer/__pycache__/color_system.cpython-312.pyc +0 -0
  105. package/src/zexus/renderer/__pycache__/layout.cpython-312.pyc +0 -0
  106. package/src/zexus/renderer/__pycache__/main_renderer.cpython-312.pyc +0 -0
  107. package/src/zexus/renderer/__pycache__/painter.cpython-312.pyc +0 -0
  108. package/src/zexus/renderer/backend.py +261 -0
  109. package/src/zexus/renderer/canvas.py +78 -0
  110. package/src/zexus/renderer/color_system.py +201 -0
  111. package/src/zexus/renderer/graphics.py +31 -0
  112. package/src/zexus/renderer/layout.py +222 -0
  113. package/src/zexus/renderer/main_renderer.py +66 -0
  114. package/src/zexus/renderer/painter.py +30 -0
  115. package/src/zexus/renderer/tk_backend.py +208 -0
  116. package/src/zexus/renderer/web_backend.py +260 -0
  117. package/src/zexus/runtime/__init__.py +10 -2
  118. package/src/zexus/runtime/__pycache__/__init__.cpython-312.pyc +0 -0
  119. package/src/zexus/runtime/__pycache__/async_runtime.cpython-312.pyc +0 -0
  120. package/src/zexus/runtime/__pycache__/load_manager.cpython-312.pyc +0 -0
  121. package/src/zexus/runtime/file_flags.py +137 -0
  122. package/src/zexus/runtime/load_manager.py +368 -0
  123. package/src/zexus/safety/__pycache__/__init__.cpython-312.pyc +0 -0
  124. package/src/zexus/safety/__pycache__/memory_safety.cpython-312.pyc +0 -0
  125. package/src/zexus/security.py +424 -34
  126. package/src/zexus/stdlib/fs.py +23 -18
  127. package/src/zexus/stdlib/http.py +289 -186
  128. package/src/zexus/stdlib/sockets.py +207 -163
  129. package/src/zexus/stdlib/websockets.py +282 -0
  130. package/src/zexus/stdlib_integration.py +369 -2
  131. package/src/zexus/strategy_recovery.py +6 -3
  132. package/src/zexus/type_checker.py +423 -0
  133. package/src/zexus/virtual_filesystem.py +189 -2
  134. package/src/zexus/vm/__init__.py +113 -3
  135. package/src/zexus/vm/__pycache__/async_optimizer.cpython-312.pyc +0 -0
  136. package/src/zexus/vm/__pycache__/bytecode.cpython-312.pyc +0 -0
  137. package/src/zexus/vm/__pycache__/bytecode_converter.cpython-312.pyc +0 -0
  138. package/src/zexus/vm/__pycache__/cache.cpython-312.pyc +0 -0
  139. package/src/zexus/vm/__pycache__/compiler.cpython-312.pyc +0 -0
  140. package/src/zexus/vm/__pycache__/gas_metering.cpython-312.pyc +0 -0
  141. package/src/zexus/vm/__pycache__/jit.cpython-312.pyc +0 -0
  142. package/src/zexus/vm/__pycache__/parallel_vm.cpython-312.pyc +0 -0
  143. package/src/zexus/vm/__pycache__/vm.cpython-312.pyc +0 -0
  144. package/src/zexus/vm/async_optimizer.py +80 -6
  145. package/src/zexus/vm/binary_bytecode.py +659 -0
  146. package/src/zexus/vm/bytecode.py +59 -11
  147. package/src/zexus/vm/bytecode_converter.py +26 -12
  148. package/src/zexus/vm/cabi.c +1985 -0
  149. package/src/zexus/vm/cabi.cpython-312-x86_64-linux-gnu.so +0 -0
  150. package/src/zexus/vm/cabi.h +127 -0
  151. package/src/zexus/vm/cache.py +561 -17
  152. package/src/zexus/vm/compiler.py +818 -51
  153. package/src/zexus/vm/fastops.c +15743 -0
  154. package/src/zexus/vm/fastops.cpython-312-x86_64-linux-gnu.so +0 -0
  155. package/src/zexus/vm/fastops.pyx +288 -0
  156. package/src/zexus/vm/gas_metering.py +50 -9
  157. package/src/zexus/vm/jit.py +364 -20
  158. package/src/zexus/vm/native_jit_backend.py +1816 -0
  159. package/src/zexus/vm/native_runtime.cpp +1388 -0
  160. package/src/zexus/vm/native_runtime.cpython-312-x86_64-linux-gnu.so +0 -0
  161. package/src/zexus/vm/optimizer.py +161 -11
  162. package/src/zexus/vm/parallel_vm.py +140 -45
  163. package/src/zexus/vm/peephole_optimizer.py +82 -4
  164. package/src/zexus/vm/profiler.py +38 -18
  165. package/src/zexus/vm/register_allocator.py +16 -5
  166. package/src/zexus/vm/register_vm.py +8 -5
  167. package/src/zexus/vm/vm.py +3581 -531
  168. package/src/zexus/vm/wasm_compiler.py +658 -0
  169. package/src/zexus/zexus_ast.py +137 -11
  170. package/src/zexus/zexus_token.py +16 -5
  171. package/src/zexus/zpm/installer.py +55 -15
  172. package/src/zexus/zpm/package_manager.py +1 -1
  173. package/src/zexus/zpm/registry.py +257 -28
  174. package/src/zexus.egg-info/PKG-INFO +16 -6
  175. package/src/zexus.egg-info/SOURCES.txt +129 -17
  176. package/src/zexus.egg-info/entry_points.txt +1 -0
  177. package/src/zexus.egg-info/requires.txt +4 -0
@@ -0,0 +1,659 @@
1
+ """
2
+ Zexus VM — Binary Bytecode Format (Phase 1)
3
+
4
+ Compact binary representation of Zexus bytecode that can be deserialized
5
+ by both Python and Rust without GIL overhead.
6
+
7
+ File Format (.zxc)
8
+ ==================
9
+
10
+ Header (16 bytes):
11
+ magic: 4 bytes b"ZXC\\x00"
12
+ version: 2 bytes uint16 LE (currently 1)
13
+ flags: 2 bytes uint16 LE (reserved)
14
+ n_consts: 4 bytes uint32 LE (number of constants)
15
+ n_instrs: 4 bytes uint32 LE (number of instructions)
16
+
17
+ Constants Section:
18
+ For each constant:
19
+ tag: 1 byte (ConstTag enum)
20
+ data: variable (depends on tag)
21
+
22
+ ConstTag values:
23
+ 0x00 NULL — no data
24
+ 0x01 BOOL — 1 byte (0=false, 1=true)
25
+ 0x02 INT32 — 4 bytes int32 LE
26
+ 0x03 INT64 — 8 bytes int64 LE
27
+ 0x04 FLOAT64 — 8 bytes float64 LE (IEEE 754)
28
+ 0x05 STRING — 4 bytes uint32 LE (length) + UTF-8 bytes
29
+ 0x06 FUNC_DESC — 4 bytes uint32 LE (length) + UTF-8 bytes (JSON-encoded)
30
+ 0x07 LIST — 4 bytes uint32 LE (count) + count × constant entries (recursive)
31
+ 0x08 MAP — 4 bytes uint32 LE (count) + count × (key_const, val_const) entries
32
+ 0xFF OPAQUE — 4 bytes uint32 LE (length) + raw bytes (not Rust-readable)
33
+
34
+ Instructions Section:
35
+ For each instruction (variable-width):
36
+ opcode: 2 bytes uint16 LE (Opcode IntEnum value)
37
+ op_type: 1 byte (OperandType enum)
38
+ operand: variable (depends on op_type)
39
+
40
+ OperandType values:
41
+ 0x00 NONE — no operand data
42
+ 0x01 U32 — 4 bytes uint32 LE (constant index, jump target, etc.)
43
+ 0x02 I64 — 8 bytes int64 LE (immediate integer)
44
+ 0x03 PAIR_U32 — 8 bytes (2 × uint32 LE) — e.g. (name_idx, arg_count)
45
+ 0x04 TRIPLE_U32 — 12 bytes (3 × uint32 LE) — e.g. register ops (r1, r2, r3)
46
+
47
+ Footer (optional, for integrity):
48
+ checksum: 4 bytes CRC32 of everything before the checksum
49
+
50
+ Usage
51
+ -----
52
+ ::
53
+
54
+ from zexus.vm.binary_bytecode import serialize, deserialize
55
+
56
+ # Convert Bytecode → bytes
57
+ data = serialize(bytecode_obj)
58
+
59
+ # Convert bytes → Bytecode
60
+ bytecode_obj = deserialize(data)
61
+
62
+ # File I/O
63
+ save_zxc("contract.zxc", bytecode_obj)
64
+ bytecode_obj = load_zxc("contract.zxc")
65
+ """
66
+
67
+ from __future__ import annotations
68
+
69
+ import struct
70
+ import json
71
+ import zlib
72
+ from enum import IntEnum
73
+ from typing import Any, List, Tuple, Optional, Dict
74
+
75
+ from .bytecode import Bytecode, Opcode
76
+
77
+ # ---------------------------------------------------------------------------
78
+ # Constants
79
+ # ---------------------------------------------------------------------------
80
+
81
+ ZXC_MAGIC = b"ZXC\x00"
82
+ ZXC_VERSION = 1
83
+ ZXC_HEADER_SIZE = 16 # magic(4) + version(2) + flags(2) + n_consts(4) + n_instrs(4)
84
+
85
+
86
+ class ConstTag(IntEnum):
87
+ """Type tags for the constant pool."""
88
+ NULL = 0x00
89
+ BOOL = 0x01
90
+ INT32 = 0x02
91
+ INT64 = 0x03
92
+ FLOAT64 = 0x04
93
+ STRING = 0x05
94
+ FUNC_DESC = 0x06
95
+ LIST = 0x07
96
+ MAP = 0x08
97
+ OPAQUE = 0xFF
98
+
99
+
100
+ class OperandType(IntEnum):
101
+ """Operand encoding types for instructions."""
102
+ NONE = 0x00
103
+ U32 = 0x01
104
+ I64 = 0x02
105
+ PAIR_U32 = 0x03
106
+ TRIPLE_U32 = 0x04
107
+
108
+
109
+ # ---------------------------------------------------------------------------
110
+ # Serialization (Python Bytecode → Binary)
111
+ # ---------------------------------------------------------------------------
112
+
113
+ class _Writer:
114
+ """Buffered binary writer."""
115
+ __slots__ = ("_parts",)
116
+
117
+ def __init__(self):
118
+ self._parts: List[bytes] = []
119
+
120
+ def write(self, data: bytes):
121
+ self._parts.append(data)
122
+
123
+ def u8(self, val: int):
124
+ self._parts.append(struct.pack("<B", val & 0xFF))
125
+
126
+ def u16(self, val: int):
127
+ self._parts.append(struct.pack("<H", val & 0xFFFF))
128
+
129
+ def u32(self, val: int):
130
+ self._parts.append(struct.pack("<I", val & 0xFFFFFFFF))
131
+
132
+ def i32(self, val: int):
133
+ self._parts.append(struct.pack("<i", val))
134
+
135
+ def i64(self, val: int):
136
+ self._parts.append(struct.pack("<q", val))
137
+
138
+ def f64(self, val: float):
139
+ self._parts.append(struct.pack("<d", val))
140
+
141
+ def string(self, s: str):
142
+ encoded = s.encode("utf-8")
143
+ self.u32(len(encoded))
144
+ self._parts.append(encoded)
145
+
146
+ def raw_bytes(self, data: bytes):
147
+ self.u32(len(data))
148
+ self._parts.append(data)
149
+
150
+ def getvalue(self) -> bytes:
151
+ return b"".join(self._parts)
152
+
153
+
154
+ class _Reader:
155
+ """Binary reader with position tracking."""
156
+ __slots__ = ("_data", "_pos")
157
+
158
+ def __init__(self, data: bytes):
159
+ self._data = data
160
+ self._pos = 0
161
+
162
+ @property
163
+ def remaining(self) -> int:
164
+ return len(self._data) - self._pos
165
+
166
+ def read(self, n: int) -> bytes:
167
+ end = self._pos + n
168
+ if end > len(self._data):
169
+ raise ValueError(f"Unexpected end of data at offset {self._pos}, need {n} bytes")
170
+ chunk = self._data[self._pos:end]
171
+ self._pos = end
172
+ return chunk
173
+
174
+ def u8(self) -> int:
175
+ return struct.unpack("<B", self.read(1))[0]
176
+
177
+ def u16(self) -> int:
178
+ return struct.unpack("<H", self.read(2))[0]
179
+
180
+ def u32(self) -> int:
181
+ return struct.unpack("<I", self.read(4))[0]
182
+
183
+ def i32(self) -> int:
184
+ return struct.unpack("<i", self.read(4))[0]
185
+
186
+ def i64(self) -> int:
187
+ return struct.unpack("<q", self.read(8))[0]
188
+
189
+ def f64(self) -> float:
190
+ return struct.unpack("<d", self.read(8))[0]
191
+
192
+ def string(self) -> str:
193
+ length = self.u32()
194
+ return self.read(length).decode("utf-8")
195
+
196
+ def raw_bytes(self) -> bytes:
197
+ length = self.u32()
198
+ return self.read(length)
199
+
200
+
201
+ def _serialize_constant(w: _Writer, value: Any):
202
+ """Serialize a single constant to the writer."""
203
+ if value is None:
204
+ w.u8(ConstTag.NULL)
205
+ elif isinstance(value, bool):
206
+ # Must check bool before int (bool is a subclass of int)
207
+ w.u8(ConstTag.BOOL)
208
+ w.u8(1 if value else 0)
209
+ elif isinstance(value, int):
210
+ if -(2**31) <= value < 2**31:
211
+ w.u8(ConstTag.INT32)
212
+ w.i32(value)
213
+ else:
214
+ w.u8(ConstTag.INT64)
215
+ w.i64(value)
216
+ elif isinstance(value, float):
217
+ w.u8(ConstTag.FLOAT64)
218
+ w.f64(value)
219
+ elif isinstance(value, str):
220
+ w.u8(ConstTag.STRING)
221
+ w.string(value)
222
+ elif isinstance(value, (list, tuple)):
223
+ # Check if it's a simple list of serializable values
224
+ w.u8(ConstTag.LIST)
225
+ w.u32(len(value))
226
+ for item in value:
227
+ _serialize_constant(w, item)
228
+ elif isinstance(value, dict):
229
+ # Serialize as JSON string for portability
230
+ try:
231
+ json_str = json.dumps(value, default=str)
232
+ w.u8(ConstTag.FUNC_DESC)
233
+ w.string(json_str)
234
+ except (TypeError, ValueError):
235
+ # Fall back to opaque
236
+ import pickle
237
+ data = pickle.dumps(value, protocol=pickle.HIGHEST_PROTOCOL)
238
+ w.u8(ConstTag.OPAQUE)
239
+ w.raw_bytes(data)
240
+ else:
241
+ # Callable, AST nodes, or other Python objects → opaque
242
+ import pickle
243
+ try:
244
+ data = pickle.dumps(value, protocol=pickle.HIGHEST_PROTOCOL)
245
+ w.u8(ConstTag.OPAQUE)
246
+ w.raw_bytes(data)
247
+ except (pickle.PicklingError, TypeError, AttributeError):
248
+ # Truly unserializable → store repr as string
249
+ w.u8(ConstTag.STRING)
250
+ w.string(repr(value))
251
+
252
+
253
+ def _serialize_instruction(w: _Writer, opcode_val: int, operand: Any):
254
+ """Serialize a single instruction to the writer."""
255
+ w.u16(opcode_val)
256
+
257
+ if operand is None:
258
+ w.u8(OperandType.NONE)
259
+ elif isinstance(operand, int):
260
+ if operand >= 0 and operand < 2**32:
261
+ w.u8(OperandType.U32)
262
+ w.u32(operand)
263
+ else:
264
+ w.u8(OperandType.I64)
265
+ w.i64(operand)
266
+ elif isinstance(operand, tuple):
267
+ if len(operand) == 2:
268
+ w.u8(OperandType.PAIR_U32)
269
+ w.u32(int(operand[0]) & 0xFFFFFFFF)
270
+ w.u32(int(operand[1]) & 0xFFFFFFFF)
271
+ elif len(operand) == 3:
272
+ w.u8(OperandType.TRIPLE_U32)
273
+ w.u32(int(operand[0]) & 0xFFFFFFFF)
274
+ w.u32(int(operand[1]) & 0xFFFFFFFF)
275
+ w.u32(int(operand[2]) & 0xFFFFFFFF)
276
+ else:
277
+ # Fallback — encode as I64 of first element
278
+ w.u8(OperandType.I64)
279
+ w.i64(int(operand[0]) if operand else 0)
280
+ elif isinstance(operand, str):
281
+ # String operands (label names, etc.) → treat as U32 after storing in constants
282
+ # This shouldn't normally happen after compilation, but handle gracefully
283
+ w.u8(OperandType.I64)
284
+ w.i64(hash(operand) & 0x7FFFFFFFFFFFFFFF)
285
+ else:
286
+ # Numeric-like
287
+ w.u8(OperandType.U32)
288
+ w.u32(int(operand) & 0xFFFFFFFF)
289
+
290
+
291
+ def _resolve_opcode(op: Any) -> int:
292
+ """Resolve an opcode to its integer value."""
293
+ if isinstance(op, int):
294
+ return op
295
+ if isinstance(op, Opcode):
296
+ return int(op)
297
+ if isinstance(op, str):
298
+ try:
299
+ return int(Opcode[op])
300
+ except KeyError:
301
+ # Unknown opcode → NOP
302
+ return int(Opcode.NOP)
303
+ return int(Opcode.NOP)
304
+
305
+
306
+ def serialize(bytecode: Bytecode, *, include_checksum: bool = True) -> bytes:
307
+ """Serialize a Bytecode object to compact binary format.
308
+
309
+ Parameters
310
+ ----------
311
+ bytecode : Bytecode
312
+ The bytecode to serialize.
313
+ include_checksum : bool
314
+ If True, append a CRC32 checksum.
315
+
316
+ Returns
317
+ -------
318
+ bytes
319
+ The binary-encoded bytecode.
320
+ """
321
+ w = _Writer()
322
+
323
+ consts = bytecode.constants or []
324
+ instrs = bytecode.instructions or []
325
+
326
+ # Header
327
+ w.write(ZXC_MAGIC)
328
+ w.u16(ZXC_VERSION)
329
+ w.u16(0) # flags (reserved)
330
+ w.u32(len(consts))
331
+ w.u32(len(instrs))
332
+
333
+ # Constants
334
+ for c in consts:
335
+ _serialize_constant(w, c)
336
+
337
+ # Instructions
338
+ for instr in instrs:
339
+ if instr is None:
340
+ continue
341
+ if isinstance(instr, tuple) and len(instr) >= 2:
342
+ op = instr[0]
343
+ operand = instr[1]
344
+ else:
345
+ op = instr
346
+ operand = None
347
+ opcode_val = _resolve_opcode(op)
348
+ _serialize_instruction(w, opcode_val, operand)
349
+
350
+ body = w.getvalue()
351
+
352
+ if include_checksum:
353
+ crc = zlib.crc32(body) & 0xFFFFFFFF
354
+ body += struct.pack("<I", crc)
355
+
356
+ return body
357
+
358
+
359
+ # ---------------------------------------------------------------------------
360
+ # Deserialization (Binary → Python Bytecode)
361
+ # ---------------------------------------------------------------------------
362
+
363
+ def _deserialize_constant(r: _Reader) -> Any:
364
+ """Deserialize a single constant from the reader."""
365
+ tag = r.u8()
366
+
367
+ if tag == ConstTag.NULL:
368
+ return None
369
+ elif tag == ConstTag.BOOL:
370
+ return bool(r.u8())
371
+ elif tag == ConstTag.INT32:
372
+ return r.i32()
373
+ elif tag == ConstTag.INT64:
374
+ return r.i64()
375
+ elif tag == ConstTag.FLOAT64:
376
+ return r.f64()
377
+ elif tag == ConstTag.STRING:
378
+ return r.string()
379
+ elif tag == ConstTag.FUNC_DESC:
380
+ json_str = r.string()
381
+ return json.loads(json_str)
382
+ elif tag == ConstTag.LIST:
383
+ count = r.u32()
384
+ return [_deserialize_constant(r) for _ in range(count)]
385
+ elif tag == ConstTag.MAP:
386
+ count = r.u32()
387
+ result = {}
388
+ for _ in range(count):
389
+ key = _deserialize_constant(r)
390
+ val = _deserialize_constant(r)
391
+ result[key] = val
392
+ return result
393
+ elif tag == ConstTag.OPAQUE:
394
+ import pickle
395
+ data = r.raw_bytes()
396
+ return pickle.loads(data)
397
+ else:
398
+ raise ValueError(f"Unknown constant tag: 0x{tag:02x}")
399
+
400
+
401
+ def _deserialize_instruction(r: _Reader) -> Tuple[Any, Any]:
402
+ """Deserialize a single instruction from the reader."""
403
+ opcode_val = r.u16()
404
+ op_type = r.u8()
405
+
406
+ # Resolve opcode to Opcode enum
407
+ try:
408
+ opcode = Opcode(opcode_val)
409
+ except ValueError:
410
+ opcode = opcode_val # Unknown opcode — keep as int
411
+
412
+ if op_type == OperandType.NONE:
413
+ return (opcode, None)
414
+ elif op_type == OperandType.U32:
415
+ return (opcode, r.u32())
416
+ elif op_type == OperandType.I64:
417
+ return (opcode, r.i64())
418
+ elif op_type == OperandType.PAIR_U32:
419
+ a = r.u32()
420
+ b = r.u32()
421
+ return (opcode, (a, b))
422
+ elif op_type == OperandType.TRIPLE_U32:
423
+ a = r.u32()
424
+ b = r.u32()
425
+ c = r.u32()
426
+ return (opcode, (a, b, c))
427
+ else:
428
+ raise ValueError(f"Unknown operand type: 0x{op_type:02x}")
429
+
430
+
431
+ def deserialize(data: bytes, *, verify_checksum: bool = True) -> Bytecode:
432
+ """Deserialize binary data into a Bytecode object.
433
+
434
+ Parameters
435
+ ----------
436
+ data : bytes
437
+ The binary-encoded bytecode.
438
+ verify_checksum : bool
439
+ If True, verify the trailing CRC32 checksum.
440
+
441
+ Returns
442
+ -------
443
+ Bytecode
444
+ The deserialized Bytecode object.
445
+
446
+ Raises
447
+ ------
448
+ ValueError
449
+ If the data is malformed or the checksum doesn't match.
450
+ """
451
+ if len(data) < ZXC_HEADER_SIZE:
452
+ raise ValueError(f"Data too short for header: {len(data)} bytes")
453
+
454
+ # Verify checksum first (last 4 bytes)
455
+ if verify_checksum and len(data) > ZXC_HEADER_SIZE + 4:
456
+ body = data[:-4]
457
+ stored_crc = struct.unpack("<I", data[-4:])[0]
458
+ computed_crc = zlib.crc32(body) & 0xFFFFFFFF
459
+ if stored_crc != computed_crc:
460
+ raise ValueError(
461
+ f"Checksum mismatch: stored=0x{stored_crc:08x}, "
462
+ f"computed=0x{computed_crc:08x}"
463
+ )
464
+ data = body # Strip checksum for reading
465
+
466
+ r = _Reader(data)
467
+
468
+ # Header
469
+ magic = r.read(4)
470
+ if magic != ZXC_MAGIC:
471
+ raise ValueError(f"Invalid magic: {magic!r}, expected {ZXC_MAGIC!r}")
472
+
473
+ version = r.u16()
474
+ if version > ZXC_VERSION:
475
+ raise ValueError(f"Unsupported version: {version} (max supported: {ZXC_VERSION})")
476
+
477
+ _flags = r.u16() # reserved
478
+ n_consts = r.u32()
479
+ n_instrs = r.u32()
480
+
481
+ # Constants
482
+ constants = [_deserialize_constant(r) for _ in range(n_consts)]
483
+
484
+ # Instructions
485
+ instructions = [_deserialize_instruction(r) for _ in range(n_instrs)]
486
+
487
+ # Build Bytecode object
488
+ bc = Bytecode(instructions=instructions, constants=constants)
489
+ bc.metadata["source_file"] = None
490
+ bc.metadata["version"] = f"binary-{version}"
491
+ bc.metadata["created_by"] = "binary_deserializer"
492
+
493
+ return bc
494
+
495
+
496
+ # ---------------------------------------------------------------------------
497
+ # File I/O
498
+ # ---------------------------------------------------------------------------
499
+
500
+ def save_zxc(path: str, bytecode: Bytecode, *, include_checksum: bool = True):
501
+ """Save a Bytecode object to a .zxc file.
502
+
503
+ Parameters
504
+ ----------
505
+ path : str
506
+ Output file path (typically ending in `.zxc`).
507
+ bytecode : Bytecode
508
+ The bytecode to save.
509
+ include_checksum : bool
510
+ If True, append CRC32 checksum.
511
+ """
512
+ data = serialize(bytecode, include_checksum=include_checksum)
513
+ with open(path, "wb") as f:
514
+ f.write(data)
515
+
516
+
517
+ def load_zxc(path: str, *, verify_checksum: bool = True) -> Bytecode:
518
+ """Load a Bytecode object from a .zxc file.
519
+
520
+ Parameters
521
+ ----------
522
+ path : str
523
+ Input file path.
524
+ verify_checksum : bool
525
+ If True, verify CRC32 checksum.
526
+
527
+ Returns
528
+ -------
529
+ Bytecode
530
+ The deserialized Bytecode object.
531
+ """
532
+ with open(path, "rb") as f:
533
+ data = f.read()
534
+ return deserialize(data, verify_checksum=verify_checksum)
535
+
536
+
537
+ # ---------------------------------------------------------------------------
538
+ # Utility — size comparison
539
+ # ---------------------------------------------------------------------------
540
+
541
+ def compare_sizes(bytecode: Bytecode) -> Dict[str, Any]:
542
+ """Compare binary size vs Python tuple representation size.
543
+
544
+ Returns a dict with size information for analysis.
545
+ """
546
+ import sys
547
+
548
+ binary = serialize(bytecode)
549
+ binary_size = len(binary)
550
+
551
+ # Estimate Python tuple size
552
+ py_size = sys.getsizeof(bytecode.instructions)
553
+ for instr in bytecode.instructions:
554
+ py_size += sys.getsizeof(instr)
555
+ if isinstance(instr, tuple):
556
+ for item in instr:
557
+ py_size += sys.getsizeof(item)
558
+ py_size += sys.getsizeof(bytecode.constants)
559
+ for c in bytecode.constants:
560
+ py_size += sys.getsizeof(c)
561
+
562
+ return {
563
+ "binary_bytes": binary_size,
564
+ "python_bytes": py_size,
565
+ "ratio": binary_size / py_size if py_size > 0 else 0.0,
566
+ "n_instructions": len(bytecode.instructions),
567
+ "n_constants": len(bytecode.constants),
568
+ }
569
+
570
+
571
+ # ---------------------------------------------------------------------------
572
+ # Multi-Bytecode Container (for file-level caching)
573
+ # ---------------------------------------------------------------------------
574
+ #
575
+ # Format: ZXCM magic (4B) + count (4B) + [length(4B) + zxc_data]*count
576
+ #
577
+
578
+ ZXCM_MAGIC = b"ZXCM"
579
+
580
+
581
+ def serialize_multi(bytecodes: List[Bytecode], *, include_checksum: bool = True) -> bytes:
582
+ """Serialize a list of Bytecode objects into a single binary blob.
583
+
584
+ Used for file-level caching where multiple Bytecodes represent
585
+ individual statements from a single source file.
586
+ """
587
+ w = _Writer()
588
+ w.write(ZXCM_MAGIC)
589
+ w.u32(len(bytecodes))
590
+ for bc in bytecodes:
591
+ chunk = serialize(bc, include_checksum=include_checksum)
592
+ w.u32(len(chunk))
593
+ w.write(chunk)
594
+ return w.getvalue()
595
+
596
+
597
+ def deserialize_multi(data: bytes, *, verify_checksum: bool = True) -> List[Bytecode]:
598
+ """Deserialize a multi-bytecode container back to a list of Bytecodes."""
599
+ if len(data) < 8:
600
+ raise ValueError("Multi-bytecode data too short")
601
+ if data[:4] != ZXCM_MAGIC:
602
+ raise ValueError(f"Invalid multi-bytecode magic: {data[:4]!r}")
603
+ count = struct.unpack_from("<I", data, 4)[0]
604
+ offset = 8
605
+ result: List[Bytecode] = []
606
+ for _ in range(count):
607
+ if offset + 4 > len(data):
608
+ raise ValueError("Truncated multi-bytecode container")
609
+ chunk_len = struct.unpack_from("<I", data, offset)[0]
610
+ offset += 4
611
+ if offset + chunk_len > len(data):
612
+ raise ValueError("Truncated multi-bytecode chunk")
613
+ chunk = data[offset:offset + chunk_len]
614
+ result.append(deserialize(chunk, verify_checksum=verify_checksum))
615
+ offset += chunk_len
616
+ return result
617
+
618
+
619
+ def save_zxcm(path: str, bytecodes: List[Bytecode]):
620
+ """Save multiple Bytecodes to a .zxc multi-container file."""
621
+ data = serialize_multi(bytecodes)
622
+ with open(path, "wb") as f:
623
+ f.write(data)
624
+
625
+
626
+ def load_zxcm(path: str) -> List[Bytecode]:
627
+ """Load multiple Bytecodes from a .zxc multi-container file."""
628
+ with open(path, "rb") as f:
629
+ data = f.read()
630
+ return deserialize_multi(data)
631
+
632
+
633
+ # ---------------------------------------------------------------------------
634
+ # Co-located .zxc helper
635
+ # ---------------------------------------------------------------------------
636
+
637
+ import os
638
+
639
+
640
+ def zxc_path_for(source_path: str) -> str:
641
+ """Return the .zxc path co-located with a .zx source file.
642
+
643
+ ``foo.zx`` → ``foo.zxc``
644
+ ``bar.zexus`` → ``bar.zxc``
645
+ Other extensions just append ``.zxc``.
646
+ """
647
+ root, ext = os.path.splitext(source_path)
648
+ return root + ".zxc"
649
+
650
+
651
+ def is_zxc_fresh(source_path: str) -> bool:
652
+ """Return True if a co-located .zxc exists and is newer than the source."""
653
+ zxc = zxc_path_for(source_path)
654
+ try:
655
+ if not os.path.exists(zxc):
656
+ return False
657
+ return os.path.getmtime(zxc) >= os.path.getmtime(source_path)
658
+ except OSError:
659
+ return False