zexus 1.8.0 → 1.8.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/README.md +34 -6
- package/bin/zexus +12 -2
- package/bin/zpics +12 -2
- package/bin/zpm +12 -2
- package/bin/zx +12 -2
- package/bin/zx-deploy +12 -2
- package/bin/zx-dev +12 -2
- package/bin/zx-run +12 -2
- package/package.json +2 -1
- package/rust_core/Cargo.lock +603 -0
- package/rust_core/Cargo.toml +26 -0
- package/rust_core/README.md +15 -0
- package/rust_core/pyproject.toml +25 -0
- package/rust_core/src/binary_bytecode.rs +543 -0
- package/rust_core/src/contract_vm.rs +643 -0
- package/rust_core/src/executor.rs +847 -0
- package/rust_core/src/hasher.rs +90 -0
- package/rust_core/src/lib.rs +71 -0
- package/rust_core/src/merkle.rs +128 -0
- package/rust_core/src/rust_vm.rs +2313 -0
- package/rust_core/src/signature.rs +79 -0
- package/rust_core/src/state_adapter.rs +281 -0
- package/rust_core/src/validator.rs +116 -0
- package/scripts/postinstall.js +204 -21
- package/src/zexus/__init__.py +1 -1
- package/src/zexus/cli/main.py +1 -1
- package/src/zexus/cli/zpm.py +1 -1
- package/src/zexus/evaluator/bytecode_compiler.py +150 -52
- package/src/zexus/evaluator/core.py +151 -809
- package/src/zexus/evaluator/expressions.py +27 -22
- package/src/zexus/evaluator/functions.py +171 -126
- package/src/zexus/evaluator/statements.py +55 -112
- package/src/zexus/module_cache.py +20 -9
- package/src/zexus/object.py +330 -38
- package/src/zexus/parser/parser.py +103 -23
- package/src/zexus/parser/strategy_context.py +318 -6
- package/src/zexus/parser/strategy_structural.py +2 -2
- package/src/zexus/persistence.py +46 -17
- package/src/zexus/security.py +140 -234
- package/src/zexus/type_checker.py +44 -5
- package/src/zexus/vm/binary_bytecode.py +7 -3
- package/src/zexus/vm/bytecode.py +6 -0
- package/src/zexus/vm/cache.py +24 -46
- package/src/zexus/vm/compiler.py +549 -68
- package/src/zexus/vm/memory_pool.py +21 -9
- package/src/zexus/vm/vm.py +609 -95
- package/src/zexus/zpm/package_manager.py +1 -1
- package/src/zexus.egg-info/PKG-INFO +56 -12
- package/src/zexus.egg-info/SOURCES.txt +14 -0
- package/src/zexus.egg-info/entry_points.txt +5 -1
- package/src/zexus.egg-info/requires.txt +26 -0
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
2
|
+
// Zexus Blockchain — Binary Bytecode Format (Rust Deserializer)
|
|
3
|
+
// ─────────────────────────────────────────────────────────────────────
|
|
4
|
+
//
|
|
5
|
+
// Deserializes the .zxc binary bytecode format produced by Python's
|
|
6
|
+
// `binary_bytecode.serialize()`. The format is intentionally simple
|
|
7
|
+
// (little-endian, no alignment padding) so both Python and Rust can
|
|
8
|
+
// read it without external dependencies.
|
|
9
|
+
//
|
|
10
|
+
// This module is GIL-free — it operates on raw `&[u8]` data that was
|
|
11
|
+
// already copied out of Python.
|
|
12
|
+
|
|
13
|
+
use pyo3::prelude::*;
|
|
14
|
+
use pyo3::types::{PyBytes, PyDict, PyList};
|
|
15
|
+
use pyo3::ToPyObject;
|
|
16
|
+
use serde::{Deserialize, Serialize};
|
|
17
|
+
use std::fmt;
|
|
18
|
+
|
|
19
|
+
// ── Format constants ──────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
const ZXC_MAGIC: &[u8; 4] = b"ZXC\x00";
|
|
22
|
+
const ZXC_HEADER_SIZE: usize = 16;
|
|
23
|
+
|
|
24
|
+
// ── Constant tags ─────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
27
|
+
#[repr(u8)]
|
|
28
|
+
enum ConstTag {
|
|
29
|
+
Null = 0x00,
|
|
30
|
+
Bool = 0x01,
|
|
31
|
+
Int32 = 0x02,
|
|
32
|
+
Int64 = 0x03,
|
|
33
|
+
Float64 = 0x04,
|
|
34
|
+
String = 0x05,
|
|
35
|
+
FuncDesc = 0x06,
|
|
36
|
+
List = 0x07,
|
|
37
|
+
Map = 0x08,
|
|
38
|
+
Opaque = 0xFF,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
impl ConstTag {
|
|
42
|
+
fn from_u8(val: u8) -> Option<Self> {
|
|
43
|
+
match val {
|
|
44
|
+
0x00 => Some(Self::Null),
|
|
45
|
+
0x01 => Some(Self::Bool),
|
|
46
|
+
0x02 => Some(Self::Int32),
|
|
47
|
+
0x03 => Some(Self::Int64),
|
|
48
|
+
0x04 => Some(Self::Float64),
|
|
49
|
+
0x05 => Some(Self::String),
|
|
50
|
+
0x06 => Some(Self::FuncDesc),
|
|
51
|
+
0x07 => Some(Self::List),
|
|
52
|
+
0x08 => Some(Self::Map),
|
|
53
|
+
0xFF => Some(Self::Opaque),
|
|
54
|
+
_ => None,
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ── Operand types ─────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
62
|
+
#[repr(u8)]
|
|
63
|
+
enum OperandType {
|
|
64
|
+
None = 0x00,
|
|
65
|
+
U32 = 0x01,
|
|
66
|
+
I64 = 0x02,
|
|
67
|
+
PairU32 = 0x03,
|
|
68
|
+
TripleU32 = 0x04,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
impl OperandType {
|
|
72
|
+
fn from_u8(val: u8) -> Option<Self> {
|
|
73
|
+
match val {
|
|
74
|
+
0x00 => Some(Self::None),
|
|
75
|
+
0x01 => Some(Self::U32),
|
|
76
|
+
0x02 => Some(Self::I64),
|
|
77
|
+
0x03 => Some(Self::PairU32),
|
|
78
|
+
0x04 => Some(Self::TripleU32),
|
|
79
|
+
_ => None,
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ── Value types for the constant pool ─────────────────────────────────
|
|
85
|
+
|
|
86
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
87
|
+
pub enum ZxcValue {
|
|
88
|
+
Null,
|
|
89
|
+
Bool(bool),
|
|
90
|
+
Int(i64),
|
|
91
|
+
Float(f64),
|
|
92
|
+
String(String),
|
|
93
|
+
FuncDesc(String), // JSON-encoded function descriptor
|
|
94
|
+
List(Vec<ZxcValue>),
|
|
95
|
+
Map(Vec<(ZxcValue, ZxcValue)>),
|
|
96
|
+
Opaque(Vec<u8>), // Python-pickled data (not interpreted in Rust)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
impl fmt::Display for ZxcValue {
|
|
100
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
101
|
+
match self {
|
|
102
|
+
ZxcValue::Null => write!(f, "null"),
|
|
103
|
+
ZxcValue::Bool(b) => write!(f, "{}", b),
|
|
104
|
+
ZxcValue::Int(i) => write!(f, "{}", i),
|
|
105
|
+
ZxcValue::Float(v) => write!(f, "{}", v),
|
|
106
|
+
ZxcValue::String(s) => write!(f, "\"{}\"", s),
|
|
107
|
+
ZxcValue::FuncDesc(s) => write!(f, "func({})", s),
|
|
108
|
+
ZxcValue::List(items) => write!(f, "[{} items]", items.len()),
|
|
109
|
+
ZxcValue::Map(pairs) => write!(f, "{{{} pairs}}", pairs.len()),
|
|
110
|
+
ZxcValue::Opaque(data) => write!(f, "<opaque {} bytes>", data.len()),
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ── Instruction operand ───────────────────────────────────────────────
|
|
116
|
+
|
|
117
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
118
|
+
pub enum Operand {
|
|
119
|
+
None,
|
|
120
|
+
U32(u32),
|
|
121
|
+
I64(i64),
|
|
122
|
+
Pair(u32, u32),
|
|
123
|
+
Triple(u32, u32, u32),
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ── Decoded instruction ───────────────────────────────────────────────
|
|
127
|
+
|
|
128
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
129
|
+
pub struct Instruction {
|
|
130
|
+
pub opcode: u16,
|
|
131
|
+
pub operand: Operand,
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ── Decoded bytecode module ───────────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
137
|
+
pub struct ZxcModule {
|
|
138
|
+
pub version: u16,
|
|
139
|
+
pub flags: u16,
|
|
140
|
+
pub constants: Vec<ZxcValue>,
|
|
141
|
+
pub instructions: Vec<Instruction>,
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
impl ZxcModule {
|
|
145
|
+
pub fn n_constants(&self) -> usize {
|
|
146
|
+
self.constants.len()
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
pub fn n_instructions(&self) -> usize {
|
|
150
|
+
self.instructions.len()
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ── Binary reader ─────────────────────────────────────────────────────
|
|
155
|
+
|
|
156
|
+
struct Reader<'a> {
|
|
157
|
+
data: &'a [u8],
|
|
158
|
+
pos: usize,
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
impl<'a> Reader<'a> {
|
|
162
|
+
fn new(data: &'a [u8]) -> Self {
|
|
163
|
+
Self { data, pos: 0 }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
fn remaining(&self) -> usize {
|
|
167
|
+
self.data.len().saturating_sub(self.pos)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
fn read(&mut self, n: usize) -> Result<&'a [u8], String> {
|
|
171
|
+
let end = self.pos + n;
|
|
172
|
+
if end > self.data.len() {
|
|
173
|
+
return Err(format!(
|
|
174
|
+
"Unexpected end of data at offset {}, need {} bytes",
|
|
175
|
+
self.pos, n
|
|
176
|
+
));
|
|
177
|
+
}
|
|
178
|
+
let slice = &self.data[self.pos..end];
|
|
179
|
+
self.pos = end;
|
|
180
|
+
Ok(slice)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
fn u8(&mut self) -> Result<u8, String> {
|
|
184
|
+
Ok(self.read(1)?[0])
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
fn u16(&mut self) -> Result<u16, String> {
|
|
188
|
+
let bytes = self.read(2)?;
|
|
189
|
+
Ok(u16::from_le_bytes([bytes[0], bytes[1]]))
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
fn u32(&mut self) -> Result<u32, String> {
|
|
193
|
+
let bytes = self.read(4)?;
|
|
194
|
+
Ok(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
fn i32(&mut self) -> Result<i32, String> {
|
|
198
|
+
let bytes = self.read(4)?;
|
|
199
|
+
Ok(i32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
fn i64(&mut self) -> Result<i64, String> {
|
|
203
|
+
let bytes = self.read(8)?;
|
|
204
|
+
Ok(i64::from_le_bytes([
|
|
205
|
+
bytes[0], bytes[1], bytes[2], bytes[3],
|
|
206
|
+
bytes[4], bytes[5], bytes[6], bytes[7],
|
|
207
|
+
]))
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
fn f64(&mut self) -> Result<f64, String> {
|
|
211
|
+
let bytes = self.read(8)?;
|
|
212
|
+
Ok(f64::from_le_bytes([
|
|
213
|
+
bytes[0], bytes[1], bytes[2], bytes[3],
|
|
214
|
+
bytes[4], bytes[5], bytes[6], bytes[7],
|
|
215
|
+
]))
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
fn string(&mut self) -> Result<String, String> {
|
|
219
|
+
let len = self.u32()? as usize;
|
|
220
|
+
let bytes = self.read(len)?;
|
|
221
|
+
String::from_utf8(bytes.to_vec())
|
|
222
|
+
.map_err(|e| format!("Invalid UTF-8 string at offset {}: {}", self.pos - len, e))
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
fn raw_bytes(&mut self) -> Result<Vec<u8>, String> {
|
|
226
|
+
let len = self.u32()? as usize;
|
|
227
|
+
Ok(self.read(len)?.to_vec())
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ── Deserialization functions ─────────────────────────────────────────
|
|
232
|
+
|
|
233
|
+
fn read_constant(r: &mut Reader) -> Result<ZxcValue, String> {
|
|
234
|
+
let tag_byte = r.u8()?;
|
|
235
|
+
let tag = ConstTag::from_u8(tag_byte)
|
|
236
|
+
.ok_or_else(|| format!("Unknown constant tag: 0x{:02x}", tag_byte))?;
|
|
237
|
+
|
|
238
|
+
match tag {
|
|
239
|
+
ConstTag::Null => Ok(ZxcValue::Null),
|
|
240
|
+
ConstTag::Bool => Ok(ZxcValue::Bool(r.u8()? != 0)),
|
|
241
|
+
ConstTag::Int32 => Ok(ZxcValue::Int(r.i32()? as i64)),
|
|
242
|
+
ConstTag::Int64 => Ok(ZxcValue::Int(r.i64()?)),
|
|
243
|
+
ConstTag::Float64 => Ok(ZxcValue::Float(r.f64()?)),
|
|
244
|
+
ConstTag::String => Ok(ZxcValue::String(r.string()?)),
|
|
245
|
+
ConstTag::FuncDesc => Ok(ZxcValue::FuncDesc(r.string()?)),
|
|
246
|
+
ConstTag::List => {
|
|
247
|
+
let count = r.u32()? as usize;
|
|
248
|
+
let mut items = Vec::with_capacity(count);
|
|
249
|
+
for _ in 0..count {
|
|
250
|
+
items.push(read_constant(r)?);
|
|
251
|
+
}
|
|
252
|
+
Ok(ZxcValue::List(items))
|
|
253
|
+
}
|
|
254
|
+
ConstTag::Map => {
|
|
255
|
+
let count = r.u32()? as usize;
|
|
256
|
+
let mut pairs = Vec::with_capacity(count);
|
|
257
|
+
for _ in 0..count {
|
|
258
|
+
let key = read_constant(r)?;
|
|
259
|
+
let val = read_constant(r)?;
|
|
260
|
+
pairs.push((key, val));
|
|
261
|
+
}
|
|
262
|
+
Ok(ZxcValue::Map(pairs))
|
|
263
|
+
}
|
|
264
|
+
ConstTag::Opaque => Ok(ZxcValue::Opaque(r.raw_bytes()?)),
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
fn read_instruction(r: &mut Reader) -> Result<Instruction, String> {
|
|
269
|
+
let opcode = r.u16()?;
|
|
270
|
+
let op_type_byte = r.u8()?;
|
|
271
|
+
let op_type = OperandType::from_u8(op_type_byte)
|
|
272
|
+
.ok_or_else(|| format!("Unknown operand type: 0x{:02x}", op_type_byte))?;
|
|
273
|
+
|
|
274
|
+
let operand = match op_type {
|
|
275
|
+
OperandType::None => Operand::None,
|
|
276
|
+
OperandType::U32 => Operand::U32(r.u32()?),
|
|
277
|
+
OperandType::I64 => Operand::I64(r.i64()?),
|
|
278
|
+
OperandType::PairU32 => {
|
|
279
|
+
let a = r.u32()?;
|
|
280
|
+
let b = r.u32()?;
|
|
281
|
+
Operand::Pair(a, b)
|
|
282
|
+
}
|
|
283
|
+
OperandType::TripleU32 => {
|
|
284
|
+
let a = r.u32()?;
|
|
285
|
+
let b = r.u32()?;
|
|
286
|
+
let c = r.u32()?;
|
|
287
|
+
Operand::Triple(a, b, c)
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
Ok(Instruction { opcode, operand })
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/// Deserialize a .zxc binary buffer into a ZxcModule.
|
|
295
|
+
///
|
|
296
|
+
/// This function is fully GIL-free — it operates on raw bytes.
|
|
297
|
+
pub fn deserialize_zxc(data: &[u8], verify_checksum: bool) -> Result<ZxcModule, String> {
|
|
298
|
+
if data.len() < ZXC_HEADER_SIZE {
|
|
299
|
+
return Err(format!(
|
|
300
|
+
"Data too short for header: {} bytes (need {})",
|
|
301
|
+
data.len(),
|
|
302
|
+
ZXC_HEADER_SIZE
|
|
303
|
+
));
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Verify CRC32 checksum (last 4 bytes)
|
|
307
|
+
let body = if verify_checksum && data.len() > ZXC_HEADER_SIZE + 4 {
|
|
308
|
+
let body = &data[..data.len() - 4];
|
|
309
|
+
let stored_bytes = &data[data.len() - 4..];
|
|
310
|
+
let stored_crc = u32::from_le_bytes([
|
|
311
|
+
stored_bytes[0],
|
|
312
|
+
stored_bytes[1],
|
|
313
|
+
stored_bytes[2],
|
|
314
|
+
stored_bytes[3],
|
|
315
|
+
]);
|
|
316
|
+
let computed_crc = crc32(body);
|
|
317
|
+
if stored_crc != computed_crc {
|
|
318
|
+
return Err(format!(
|
|
319
|
+
"Checksum mismatch: stored=0x{:08x}, computed=0x{:08x}",
|
|
320
|
+
stored_crc, computed_crc
|
|
321
|
+
));
|
|
322
|
+
}
|
|
323
|
+
body
|
|
324
|
+
} else {
|
|
325
|
+
data
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
let mut r = Reader::new(body);
|
|
329
|
+
|
|
330
|
+
// Header
|
|
331
|
+
let magic = r.read(4)?;
|
|
332
|
+
if magic != ZXC_MAGIC {
|
|
333
|
+
return Err(format!("Invalid magic: {:?}", magic));
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
let version = r.u16()?;
|
|
337
|
+
if version > 1 {
|
|
338
|
+
return Err(format!("Unsupported version: {}", version));
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
let flags = r.u16()?;
|
|
342
|
+
let n_consts = r.u32()? as usize;
|
|
343
|
+
let n_instrs = r.u32()? as usize;
|
|
344
|
+
|
|
345
|
+
// Constants
|
|
346
|
+
let mut constants = Vec::with_capacity(n_consts);
|
|
347
|
+
for _ in 0..n_consts {
|
|
348
|
+
constants.push(read_constant(&mut r)?);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Instructions
|
|
352
|
+
let mut instructions = Vec::with_capacity(n_instrs);
|
|
353
|
+
for _ in 0..n_instrs {
|
|
354
|
+
instructions.push(read_instruction(&mut r)?);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
Ok(ZxcModule {
|
|
358
|
+
version,
|
|
359
|
+
flags,
|
|
360
|
+
constants,
|
|
361
|
+
instructions,
|
|
362
|
+
})
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// ── CRC32 (IEEE 802.3 / zlib-compatible) ──────────────────────────────
|
|
366
|
+
|
|
367
|
+
/// Compute CRC32 matching Python's `zlib.crc32()`.
|
|
368
|
+
fn crc32(data: &[u8]) -> u32 {
|
|
369
|
+
let mut crc: u32 = 0xFFFFFFFF;
|
|
370
|
+
for &byte in data {
|
|
371
|
+
crc ^= byte as u32;
|
|
372
|
+
for _ in 0..8 {
|
|
373
|
+
if crc & 1 != 0 {
|
|
374
|
+
crc = (crc >> 1) ^ 0xEDB88320;
|
|
375
|
+
} else {
|
|
376
|
+
crc >>= 1;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
crc ^ 0xFFFFFFFF
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// ── PyO3 wrapper ──────────────────────────────────────────────────────
|
|
384
|
+
|
|
385
|
+
/// Python-visible bytecode deserializer.
|
|
386
|
+
#[pyclass(name = "RustBytecodeReader")]
|
|
387
|
+
pub struct RustBytecodeReader;
|
|
388
|
+
|
|
389
|
+
#[pymethods]
|
|
390
|
+
impl RustBytecodeReader {
|
|
391
|
+
#[new]
|
|
392
|
+
fn new() -> Self {
|
|
393
|
+
Self
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/// Deserialize a .zxc binary buffer and return a dict with the module contents.
|
|
397
|
+
///
|
|
398
|
+
/// Returns a dict with keys: "version", "flags", "n_constants", "n_instructions",
|
|
399
|
+
/// "constants" (list of dicts), "instructions" (list of dicts).
|
|
400
|
+
#[pyo3(signature = (data, verify_checksum = true))]
|
|
401
|
+
fn deserialize<'py>(
|
|
402
|
+
&self,
|
|
403
|
+
py: Python<'py>,
|
|
404
|
+
data: &Bound<'py, PyBytes>,
|
|
405
|
+
verify_checksum: bool,
|
|
406
|
+
) -> PyResult<Bound<'py, PyDict>> {
|
|
407
|
+
let bytes = data.as_bytes();
|
|
408
|
+
|
|
409
|
+
// Deserialize outside the GIL for CPU-heavy work
|
|
410
|
+
let module = py.allow_threads(|| deserialize_zxc(bytes, verify_checksum))
|
|
411
|
+
.map_err(|e| pyo3::exceptions::PyValueError::new_err(e))?;
|
|
412
|
+
|
|
413
|
+
// Convert to Python dict
|
|
414
|
+
let result = PyDict::new_bound(py);
|
|
415
|
+
result.set_item("version", module.version)?;
|
|
416
|
+
result.set_item("flags", module.flags)?;
|
|
417
|
+
result.set_item("n_constants", module.n_constants())?;
|
|
418
|
+
result.set_item("n_instructions", module.n_instructions())?;
|
|
419
|
+
|
|
420
|
+
// Constants
|
|
421
|
+
let py_consts = PyList::empty_bound(py);
|
|
422
|
+
for c in &module.constants {
|
|
423
|
+
py_consts.append(zxc_value_to_py(py, c)?)?;
|
|
424
|
+
}
|
|
425
|
+
result.set_item("constants", py_consts)?;
|
|
426
|
+
|
|
427
|
+
// Instructions
|
|
428
|
+
let py_instrs = PyList::empty_bound(py);
|
|
429
|
+
for instr in &module.instructions {
|
|
430
|
+
let d = PyDict::new_bound(py);
|
|
431
|
+
d.set_item("opcode", instr.opcode)?;
|
|
432
|
+
match &instr.operand {
|
|
433
|
+
Operand::None => d.set_item("operand", py.None())?,
|
|
434
|
+
Operand::U32(v) => d.set_item("operand", *v)?,
|
|
435
|
+
Operand::I64(v) => d.set_item("operand", *v)?,
|
|
436
|
+
Operand::Pair(a, b) => d.set_item("operand", (*a, *b))?,
|
|
437
|
+
Operand::Triple(a, b, c) => d.set_item("operand", (*a, *b, *c))?,
|
|
438
|
+
}
|
|
439
|
+
py_instrs.append(d)?;
|
|
440
|
+
}
|
|
441
|
+
result.set_item("instructions", py_instrs)?;
|
|
442
|
+
|
|
443
|
+
Ok(result)
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/// Quick validation — check if binary data is a valid .zxc file.
|
|
447
|
+
#[pyo3(signature = (data, verify_checksum = true))]
|
|
448
|
+
fn validate(&self, py: Python<'_>, data: &Bound<'_, PyBytes>, verify_checksum: bool) -> PyResult<bool> {
|
|
449
|
+
let bytes = data.as_bytes();
|
|
450
|
+
let result = py.allow_threads(|| deserialize_zxc(bytes, verify_checksum));
|
|
451
|
+
Ok(result.is_ok())
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/// Get basic info from .zxc header without full deserialization.
|
|
455
|
+
fn header_info<'py>(&self, py: Python<'py>, data: &Bound<'py, PyBytes>) -> PyResult<Bound<'py, PyDict>> {
|
|
456
|
+
let bytes = data.as_bytes();
|
|
457
|
+
if bytes.len() < ZXC_HEADER_SIZE {
|
|
458
|
+
return Err(pyo3::exceptions::PyValueError::new_err("Data too short"));
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
let magic = &bytes[0..4];
|
|
462
|
+
let version = u16::from_le_bytes([bytes[4], bytes[5]]);
|
|
463
|
+
let flags = u16::from_le_bytes([bytes[6], bytes[7]]);
|
|
464
|
+
let n_consts = u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]);
|
|
465
|
+
let n_instrs = u32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]);
|
|
466
|
+
|
|
467
|
+
let result = PyDict::new_bound(py);
|
|
468
|
+
result.set_item("magic_ok", magic == ZXC_MAGIC)?;
|
|
469
|
+
result.set_item("version", version)?;
|
|
470
|
+
result.set_item("flags", flags)?;
|
|
471
|
+
result.set_item("n_constants", n_consts)?;
|
|
472
|
+
result.set_item("n_instructions", n_instrs)?;
|
|
473
|
+
result.set_item("total_bytes", bytes.len())?;
|
|
474
|
+
Ok(result)
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/// Convert a ZxcValue to a Python object.
|
|
479
|
+
fn zxc_value_to_py(py: Python<'_>, val: &ZxcValue) -> PyResult<PyObject> {
|
|
480
|
+
match val {
|
|
481
|
+
ZxcValue::Null => Ok(py.None()),
|
|
482
|
+
ZxcValue::Bool(b) => Ok(b.to_object(py)),
|
|
483
|
+
ZxcValue::Int(i) => Ok(i.to_object(py)),
|
|
484
|
+
ZxcValue::Float(f) => Ok(f.to_object(py)),
|
|
485
|
+
ZxcValue::String(s) => Ok(s.to_object(py)),
|
|
486
|
+
ZxcValue::FuncDesc(s) => {
|
|
487
|
+
// Parse JSON back to Python dict
|
|
488
|
+
let json_val: serde_json::Value = serde_json::from_str(s)
|
|
489
|
+
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("Invalid JSON: {}", e)))?;
|
|
490
|
+
json_to_py(py, &json_val)
|
|
491
|
+
}
|
|
492
|
+
ZxcValue::List(items) => {
|
|
493
|
+
let list = PyList::empty_bound(py);
|
|
494
|
+
for item in items {
|
|
495
|
+
list.append(zxc_value_to_py(py, item)?)?;
|
|
496
|
+
}
|
|
497
|
+
Ok(list.to_object(py))
|
|
498
|
+
}
|
|
499
|
+
ZxcValue::Map(pairs) => {
|
|
500
|
+
let dict = PyDict::new_bound(py);
|
|
501
|
+
for (k, v) in pairs {
|
|
502
|
+
dict.set_item(zxc_value_to_py(py, k)?, zxc_value_to_py(py, v)?)?;
|
|
503
|
+
}
|
|
504
|
+
Ok(dict.to_object(py))
|
|
505
|
+
}
|
|
506
|
+
ZxcValue::Opaque(data) => {
|
|
507
|
+
// Return raw bytes — Python can unpickle if needed
|
|
508
|
+
Ok(PyBytes::new_bound(py, data).to_object(py))
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/// Convert serde_json::Value to a Python object.
|
|
514
|
+
fn json_to_py(py: Python<'_>, val: &serde_json::Value) -> PyResult<PyObject> {
|
|
515
|
+
match val {
|
|
516
|
+
serde_json::Value::Null => Ok(py.None()),
|
|
517
|
+
serde_json::Value::Bool(b) => Ok(b.to_object(py)),
|
|
518
|
+
serde_json::Value::Number(n) => {
|
|
519
|
+
if let Some(i) = n.as_i64() {
|
|
520
|
+
Ok(i.to_object(py))
|
|
521
|
+
} else if let Some(f) = n.as_f64() {
|
|
522
|
+
Ok(f.to_object(py))
|
|
523
|
+
} else {
|
|
524
|
+
Ok(py.None())
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
serde_json::Value::String(s) => Ok(s.to_object(py)),
|
|
528
|
+
serde_json::Value::Array(arr) => {
|
|
529
|
+
let list = PyList::empty_bound(py);
|
|
530
|
+
for item in arr {
|
|
531
|
+
list.append(json_to_py(py, item)?)?;
|
|
532
|
+
}
|
|
533
|
+
Ok(list.to_object(py))
|
|
534
|
+
}
|
|
535
|
+
serde_json::Value::Object(obj) => {
|
|
536
|
+
let dict = PyDict::new_bound(py);
|
|
537
|
+
for (k, v) in obj {
|
|
538
|
+
dict.set_item(k, json_to_py(py, v)?)?;
|
|
539
|
+
}
|
|
540
|
+
Ok(dict.to_object(py))
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|