Skip to main content

nwnrs_nwscript/
ncs.rs

1use std::{error::Error, fmt};
2
3/// Text prefix of an `NCS V1.0` bytecode stream.
4pub const NCS_HEADER: &str = "NCS V1.0";
5/// Total size of the fixed binary header, including the encoded bytecode size.
6pub const NCS_BINARY_HEADER_SIZE: usize = 13;
7/// Size of the opcode-plus-auxcode instruction prefix.
8pub const NCS_OPERATION_BASE_SIZE: usize = 2;
9/// Byte offset of the opcode field inside an instruction.
10pub const NCS_OPCODE_OFFSET: usize = 0;
11/// Byte offset of the auxcode field inside an instruction.
12pub const NCS_AUXCODE_OFFSET: usize = 1;
13/// Byte offset of the extra-data section inside an instruction.
14pub const NCS_EXTRA_DATA_OFFSET: usize = 2;
15
16/// Fixed metadata extracted from the leading bytes of an `NCS` file.
17#[derive(#[automatically_derived]
impl ::core::fmt::Debug for NcsHeader {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field1_finish(f, "NcsHeader",
            "code_size", &&self.code_size)
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for NcsHeader {
    #[inline]
    fn clone(&self) -> NcsHeader {
        let _: ::core::clone::AssertParamIsClone<u32>;
        *self
    }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for NcsHeader { }Copy, #[automatically_derived]
impl ::core::cmp::PartialEq for NcsHeader {
    #[inline]
    fn eq(&self, other: &NcsHeader) -> bool {
        self.code_size == other.code_size
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for NcsHeader {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<u32>;
    }
}Eq, #[automatically_derived]
impl ::core::hash::Hash for NcsHeader {
    #[inline]
    fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) {
        ::core::hash::Hash::hash(&self.code_size, state)
    }
}Hash)]
18pub struct NcsHeader {
19    /// Encoded bytecode payload size recorded in the binary header.
20    pub code_size: u32,
21}
22
23/// Errors returned while decoding the fixed `NCS` header.
24#[derive(#[automatically_derived]
impl ::core::fmt::Debug for NcsHeaderError {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            NcsHeaderError::TooShort(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "TooShort", &__self_0),
            NcsHeaderError::InvalidMagic =>
                ::core::fmt::Formatter::write_str(f, "InvalidMagic"),
            NcsHeaderError::InvalidMarker(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "InvalidMarker", &__self_0),
        }
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for NcsHeaderError {
    #[inline]
    fn clone(&self) -> NcsHeaderError {
        match self {
            NcsHeaderError::TooShort(__self_0) =>
                NcsHeaderError::TooShort(::core::clone::Clone::clone(__self_0)),
            NcsHeaderError::InvalidMagic => NcsHeaderError::InvalidMagic,
            NcsHeaderError::InvalidMarker(__self_0) =>
                NcsHeaderError::InvalidMarker(::core::clone::Clone::clone(__self_0)),
        }
    }
}Clone, #[automatically_derived]
impl ::core::cmp::PartialEq for NcsHeaderError {
    #[inline]
    fn eq(&self, other: &NcsHeaderError) -> bool {
        let __self_discr = ::core::intrinsics::discriminant_value(self);
        let __arg1_discr = ::core::intrinsics::discriminant_value(other);
        __self_discr == __arg1_discr &&
            match (self, other) {
                (NcsHeaderError::TooShort(__self_0),
                    NcsHeaderError::TooShort(__arg1_0)) => __self_0 == __arg1_0,
                (NcsHeaderError::InvalidMarker(__self_0),
                    NcsHeaderError::InvalidMarker(__arg1_0)) =>
                    __self_0 == __arg1_0,
                _ => true,
            }
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for NcsHeaderError {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<usize>;
        let _: ::core::cmp::AssertParamIsEq<u8>;
    }
}Eq)]
25pub enum NcsHeaderError {
26    /// The provided byte slice was shorter than the fixed header.
27    TooShort(usize),
28    /// The file prefix did not match the expected `NCS V1.0` signature.
29    InvalidMagic,
30    /// The binary marker byte after the text header was not `B`.
31    InvalidMarker(u8),
32}
33
34impl fmt::Display for NcsHeaderError {
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        match self {
37            Self::TooShort(len) => f.write_fmt(format_args!("NCS header too short: expected 13 bytes, got {0}",
        len))write!(f, "NCS header too short: expected 13 bytes, got {len}"),
38            Self::InvalidMagic => f.write_str("invalid NCS header magic"),
39            Self::InvalidMarker(marker) => f.write_fmt(format_args!("invalid NCS binary marker: {0:#04x}", marker))write!(f, "invalid NCS binary marker: {marker:#04x}"),
40        }
41    }
42}
43
44impl Error for NcsHeaderError {}
45
46/// Decodes the fixed binary header of an `NCS V1.0` file.
47///
48/// # Errors
49///
50/// Returns [`NcsHeaderError`] if the bytes are too short or the magic is
51/// invalid.
52pub fn decode_ncs_header(bytes: &[u8]) -> Result<NcsHeader, NcsHeaderError> {
53    if bytes.len() < NCS_BINARY_HEADER_SIZE {
54        return Err(NcsHeaderError::TooShort(bytes.len()));
55    }
56    if bytes.get(..NCS_HEADER.len()) != Some(NCS_HEADER.as_bytes()) {
57        return Err(NcsHeaderError::InvalidMagic);
58    }
59    let Some(marker) = bytes.get(8).copied() else {
60        return Err(NcsHeaderError::TooShort(bytes.len()));
61    };
62    if marker != b'B' {
63        return Err(NcsHeaderError::InvalidMarker(marker));
64    }
65    let Some(code_size_bytes) = bytes.get(9..13) else {
66        return Err(NcsHeaderError::TooShort(bytes.len()));
67    };
68
69    let code_size = <[u8; 4]>::try_from(code_size_bytes)
70        .map(u32::from_be_bytes)
71        .map_err(|_error| NcsHeaderError::TooShort(bytes.len()))?;
72    Ok(NcsHeader {
73        code_size,
74    })
75}
76
77/// One decoded `NCS` instruction.
78#[derive(#[automatically_derived]
impl ::core::fmt::Debug for NcsInstruction {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field3_finish(f,
            "NcsInstruction", "opcode", &self.opcode, "auxcode",
            &self.auxcode, "extra", &&self.extra)
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for NcsInstruction {
    #[inline]
    fn clone(&self) -> NcsInstruction {
        NcsInstruction {
            opcode: ::core::clone::Clone::clone(&self.opcode),
            auxcode: ::core::clone::Clone::clone(&self.auxcode),
            extra: ::core::clone::Clone::clone(&self.extra),
        }
    }
}Clone, #[automatically_derived]
impl ::core::cmp::PartialEq for NcsInstruction {
    #[inline]
    fn eq(&self, other: &NcsInstruction) -> bool {
        self.opcode == other.opcode && self.auxcode == other.auxcode &&
            self.extra == other.extra
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for NcsInstruction {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<NcsOpcode>;
        let _: ::core::cmp::AssertParamIsEq<NcsAuxCode>;
        let _: ::core::cmp::AssertParamIsEq<Vec<u8>>;
    }
}Eq, #[automatically_derived]
impl ::core::hash::Hash for NcsInstruction {
    #[inline]
    fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) {
        ::core::hash::Hash::hash(&self.opcode, state);
        ::core::hash::Hash::hash(&self.auxcode, state);
        ::core::hash::Hash::hash(&self.extra, state)
    }
}Hash)]
79pub struct NcsInstruction {
80    /// Operation code.
81    pub opcode:  NcsOpcode,
82    /// Auxiliary code.
83    pub auxcode: NcsAuxCode,
84    /// Encoded instruction payload after opcode and auxcode.
85    pub extra:   Vec<u8>,
86}
87
88impl NcsInstruction {
89    /// Returns the full encoded byte length of this instruction.
90    #[must_use]
91    pub fn encoded_len(&self) -> usize {
92        NCS_OPERATION_BASE_SIZE + self.extra.len()
93    }
94}
95
96/// One `NWScript` VM opcode.
97#[derive(#[automatically_derived]
impl ::core::fmt::Debug for NcsOpcode {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f,
            match self {
                NcsOpcode::Assignment => "Assignment",
                NcsOpcode::RunstackAdd => "RunstackAdd",
                NcsOpcode::RunstackCopy => "RunstackCopy",
                NcsOpcode::Constant => "Constant",
                NcsOpcode::ExecuteCommand => "ExecuteCommand",
                NcsOpcode::LogicalAnd => "LogicalAnd",
                NcsOpcode::LogicalOr => "LogicalOr",
                NcsOpcode::InclusiveOr => "InclusiveOr",
                NcsOpcode::ExclusiveOr => "ExclusiveOr",
                NcsOpcode::BooleanAnd => "BooleanAnd",
                NcsOpcode::Equal => "Equal",
                NcsOpcode::NotEqual => "NotEqual",
                NcsOpcode::Geq => "Geq",
                NcsOpcode::Gt => "Gt",
                NcsOpcode::Lt => "Lt",
                NcsOpcode::Leq => "Leq",
                NcsOpcode::ShiftLeft => "ShiftLeft",
                NcsOpcode::ShiftRight => "ShiftRight",
                NcsOpcode::UShiftRight => "UShiftRight",
                NcsOpcode::Add => "Add",
                NcsOpcode::Sub => "Sub",
                NcsOpcode::Mul => "Mul",
                NcsOpcode::Div => "Div",
                NcsOpcode::Modulus => "Modulus",
                NcsOpcode::Negation => "Negation",
                NcsOpcode::OnesComplement => "OnesComplement",
                NcsOpcode::ModifyStackPointer => "ModifyStackPointer",
                NcsOpcode::StoreIp => "StoreIp",
                NcsOpcode::Jmp => "Jmp",
                NcsOpcode::Jsr => "Jsr",
                NcsOpcode::Jz => "Jz",
                NcsOpcode::Ret => "Ret",
                NcsOpcode::DeStruct => "DeStruct",
                NcsOpcode::BooleanNot => "BooleanNot",
                NcsOpcode::Decrement => "Decrement",
                NcsOpcode::Increment => "Increment",
                NcsOpcode::Jnz => "Jnz",
                NcsOpcode::AssignmentBase => "AssignmentBase",
                NcsOpcode::RunstackCopyBase => "RunstackCopyBase",
                NcsOpcode::DecrementBase => "DecrementBase",
                NcsOpcode::IncrementBase => "IncrementBase",
                NcsOpcode::SaveBasePointer => "SaveBasePointer",
                NcsOpcode::RestoreBasePointer => "RestoreBasePointer",
                NcsOpcode::StoreState => "StoreState",
                NcsOpcode::NoOperation => "NoOperation",
            })
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for NcsOpcode {
    #[inline]
    fn clone(&self) -> NcsOpcode { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for NcsOpcode { }Copy, #[automatically_derived]
impl ::core::cmp::PartialEq for NcsOpcode {
    #[inline]
    fn eq(&self, other: &NcsOpcode) -> bool {
        let __self_discr = ::core::intrinsics::discriminant_value(self);
        let __arg1_discr = ::core::intrinsics::discriminant_value(other);
        __self_discr == __arg1_discr
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for NcsOpcode {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {}
}Eq, #[automatically_derived]
impl ::core::hash::Hash for NcsOpcode {
    #[inline]
    fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) {
        let __self_discr = ::core::intrinsics::discriminant_value(self);
        ::core::hash::Hash::hash(&__self_discr, state)
    }
}Hash)]
98#[repr(u8)]
99pub enum NcsOpcode {
100    /// `CPDOWNSP`
101    Assignment = 0x01,
102    /// `RSADD`
103    RunstackAdd = 0x02,
104    /// `CPTOPSP`
105    RunstackCopy = 0x03,
106    /// `CONST`
107    Constant = 0x04,
108    /// `ACTION`
109    ExecuteCommand = 0x05,
110    /// `LOGAND`
111    LogicalAnd = 0x06,
112    /// `LOGOR`
113    LogicalOr = 0x07,
114    /// `INCOR`
115    InclusiveOr = 0x08,
116    /// `EXCOR`
117    ExclusiveOr = 0x09,
118    /// `BOOLAND`
119    BooleanAnd = 0x0a,
120    /// `EQUAL`
121    Equal = 0x0b,
122    /// `NEQUAL`
123    NotEqual = 0x0c,
124    /// `GEQ`
125    Geq = 0x0d,
126    /// `GT`
127    Gt = 0x0e,
128    /// `LT`
129    Lt = 0x0f,
130    /// `LEQ`
131    Leq = 0x10,
132    /// `SHLEFT`
133    ShiftLeft = 0x11,
134    /// `SHRIGHT`
135    ShiftRight = 0x12,
136    /// `USHRIGHT`
137    UShiftRight = 0x13,
138    /// `ADD`
139    Add = 0x14,
140    /// `SUB`
141    Sub = 0x15,
142    /// `MUL`
143    Mul = 0x16,
144    /// `DIV`
145    Div = 0x17,
146    /// `MOD`
147    Modulus = 0x18,
148    /// `NEG`
149    Negation = 0x19,
150    /// `COMP`
151    OnesComplement = 0x1a,
152    /// `MOVSP`
153    ModifyStackPointer = 0x1b,
154    /// `STOREIP`
155    StoreIp = 0x1c,
156    /// `JMP`
157    Jmp = 0x1d,
158    /// `JSR`
159    Jsr = 0x1e,
160    /// `JZ`
161    Jz = 0x1f,
162    /// `RET`
163    Ret = 0x20,
164    /// `DESTRUCT`
165    DeStruct = 0x21,
166    /// `NOT`
167    BooleanNot = 0x22,
168    /// `DECSP`
169    Decrement = 0x23,
170    /// `INCSP`
171    Increment = 0x24,
172    /// `JNZ`
173    Jnz = 0x25,
174    /// `CPDOWNBP`
175    AssignmentBase = 0x26,
176    /// `CPTOPBP`
177    RunstackCopyBase = 0x27,
178    /// `DECBP`
179    DecrementBase = 0x28,
180    /// `INCBP`
181    IncrementBase = 0x29,
182    /// `SAVEBP`
183    SaveBasePointer = 0x2a,
184    /// `RESTOREBP`
185    RestoreBasePointer = 0x2b,
186    /// `STORESTATE`
187    StoreState = 0x2c,
188    /// `NOP`
189    NoOperation = 0x2d,
190}
191
192impl NcsOpcode {
193    /// Returns the canonical mnemonic used by the upstream assembler helper.
194    #[must_use]
195    pub fn canonical_name(self) -> &'static str {
196        match self {
197            Self::Assignment => "CPDOWNSP",
198            Self::RunstackAdd => "RSADD",
199            Self::RunstackCopy => "CPTOPSP",
200            Self::Constant => "CONST",
201            Self::ExecuteCommand => "ACTION",
202            Self::LogicalAnd => "LOGAND",
203            Self::LogicalOr => "LOGOR",
204            Self::InclusiveOr => "INCOR",
205            Self::ExclusiveOr => "EXCOR",
206            Self::BooleanAnd => "BOOLAND",
207            Self::Equal => "EQUAL",
208            Self::NotEqual => "NEQUAL",
209            Self::Geq => "GEQ",
210            Self::Gt => "GT",
211            Self::Lt => "LT",
212            Self::Leq => "LEQ",
213            Self::ShiftLeft => "SHLEFT",
214            Self::ShiftRight => "SHRIGHT",
215            Self::UShiftRight => "USHRIGHT",
216            Self::Add => "ADD",
217            Self::Sub => "SUB",
218            Self::Mul => "MUL",
219            Self::Div => "DIV",
220            Self::Modulus => "MOD",
221            Self::Negation => "NEG",
222            Self::OnesComplement => "COMP",
223            Self::ModifyStackPointer => "MOVSP",
224            Self::StoreIp => "STOREIP",
225            Self::Jmp => "JMP",
226            Self::Jsr => "JSR",
227            Self::Jz => "JZ",
228            Self::Ret => "RET",
229            Self::DeStruct => "DESTRUCT",
230            Self::BooleanNot => "NOT",
231            Self::Decrement => "DECSP",
232            Self::Increment => "INCSP",
233            Self::Jnz => "JNZ",
234            Self::AssignmentBase => "CPDOWNBP",
235            Self::RunstackCopyBase => "CPTOPBP",
236            Self::DecrementBase => "DECBP",
237            Self::IncrementBase => "INCBP",
238            Self::SaveBasePointer => "SAVEBP",
239            Self::RestoreBasePointer => "RESTOREBP",
240            Self::StoreState => "STORESTATE",
241            Self::NoOperation => "NOP",
242        }
243    }
244}
245
246impl fmt::Display for NcsOpcode {
247    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248        f.write_str(self.canonical_name())
249    }
250}
251
252/// One `NWScript` VM aux code.
253#[derive(#[automatically_derived]
impl ::core::fmt::Debug for NcsAuxCode {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f,
            match self {
                NcsAuxCode::None => "None",
                NcsAuxCode::TypeVoid => "TypeVoid",
                NcsAuxCode::TypeCommand => "TypeCommand",
                NcsAuxCode::TypeInteger => "TypeInteger",
                NcsAuxCode::TypeFloat => "TypeFloat",
                NcsAuxCode::TypeString => "TypeString",
                NcsAuxCode::TypeObject => "TypeObject",
                NcsAuxCode::TypeEngst0 => "TypeEngst0",
                NcsAuxCode::TypeEngst1 => "TypeEngst1",
                NcsAuxCode::TypeEngst2 => "TypeEngst2",
                NcsAuxCode::TypeEngst3 => "TypeEngst3",
                NcsAuxCode::TypeEngst4 => "TypeEngst4",
                NcsAuxCode::TypeEngst5 => "TypeEngst5",
                NcsAuxCode::TypeEngst6 => "TypeEngst6",
                NcsAuxCode::TypeEngst7 => "TypeEngst7",
                NcsAuxCode::TypeEngst8 => "TypeEngst8",
                NcsAuxCode::TypeEngst9 => "TypeEngst9",
                NcsAuxCode::TypeTypeIntegerInteger =>
                    "TypeTypeIntegerInteger",
                NcsAuxCode::TypeTypeFloatFloat => "TypeTypeFloatFloat",
                NcsAuxCode::TypeTypeObjectObject => "TypeTypeObjectObject",
                NcsAuxCode::TypeTypeStringString => "TypeTypeStringString",
                NcsAuxCode::TypeTypeStructStruct => "TypeTypeStructStruct",
                NcsAuxCode::TypeTypeIntegerFloat => "TypeTypeIntegerFloat",
                NcsAuxCode::TypeTypeFloatInteger => "TypeTypeFloatInteger",
                NcsAuxCode::TypeTypeEngst0Engst0 => "TypeTypeEngst0Engst0",
                NcsAuxCode::TypeTypeEngst1Engst1 => "TypeTypeEngst1Engst1",
                NcsAuxCode::TypeTypeEngst2Engst2 => "TypeTypeEngst2Engst2",
                NcsAuxCode::TypeTypeEngst3Engst3 => "TypeTypeEngst3Engst3",
                NcsAuxCode::TypeTypeEngst4Engst4 => "TypeTypeEngst4Engst4",
                NcsAuxCode::TypeTypeEngst5Engst5 => "TypeTypeEngst5Engst5",
                NcsAuxCode::TypeTypeEngst6Engst6 => "TypeTypeEngst6Engst6",
                NcsAuxCode::TypeTypeEngst7Engst7 => "TypeTypeEngst7Engst7",
                NcsAuxCode::TypeTypeEngst8Engst8 => "TypeTypeEngst8Engst8",
                NcsAuxCode::TypeTypeEngst9Engst9 => "TypeTypeEngst9Engst9",
                NcsAuxCode::TypeTypeVectorVector => "TypeTypeVectorVector",
                NcsAuxCode::TypeTypeVectorFloat => "TypeTypeVectorFloat",
                NcsAuxCode::TypeTypeFloatVector => "TypeTypeFloatVector",
                NcsAuxCode::EvalInplace => "EvalInplace",
                NcsAuxCode::EvalPostplace => "EvalPostplace",
            })
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for NcsAuxCode {
    #[inline]
    fn clone(&self) -> NcsAuxCode { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for NcsAuxCode { }Copy, #[automatically_derived]
impl ::core::cmp::PartialEq for NcsAuxCode {
    #[inline]
    fn eq(&self, other: &NcsAuxCode) -> bool {
        let __self_discr = ::core::intrinsics::discriminant_value(self);
        let __arg1_discr = ::core::intrinsics::discriminant_value(other);
        __self_discr == __arg1_discr
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for NcsAuxCode {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {}
}Eq, #[automatically_derived]
impl ::core::hash::Hash for NcsAuxCode {
    #[inline]
    fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) {
        let __self_discr = ::core::intrinsics::discriminant_value(self);
        ::core::hash::Hash::hash(&__self_discr, state)
    }
}Hash)]
254#[repr(u8)]
255pub enum NcsAuxCode {
256    /// No auxcode.
257    None = 0x00,
258    /// `void`.
259    TypeVoid = 0x01,
260    /// command type.
261    TypeCommand = 0x02,
262    /// integer type.
263    TypeInteger = 0x03,
264    /// float type.
265    TypeFloat = 0x04,
266    /// string type.
267    TypeString = 0x05,
268    /// object type.
269    TypeObject = 0x06,
270    /// engine structure 0.
271    TypeEngst0 = 0x10,
272    /// engine structure 1.
273    TypeEngst1 = 0x11,
274    /// engine structure 2.
275    TypeEngst2 = 0x12,
276    /// engine structure 3.
277    TypeEngst3 = 0x13,
278    /// engine structure 4.
279    TypeEngst4 = 0x14,
280    /// engine structure 5.
281    TypeEngst5 = 0x15,
282    /// engine structure 6.
283    TypeEngst6 = 0x16,
284    /// engine structure 7.
285    TypeEngst7 = 0x17,
286    /// engine structure 8.
287    TypeEngst8 = 0x18,
288    /// engine structure 9.
289    TypeEngst9 = 0x19,
290    /// integer/integer operator specialization.
291    TypeTypeIntegerInteger = 0x20,
292    /// float/float operator specialization.
293    TypeTypeFloatFloat = 0x21,
294    /// object/object operator specialization.
295    TypeTypeObjectObject = 0x22,
296    /// string/string operator specialization.
297    TypeTypeStringString = 0x23,
298    /// struct/struct operator specialization.
299    TypeTypeStructStruct = 0x24,
300    /// integer/float operator specialization.
301    TypeTypeIntegerFloat = 0x25,
302    /// float/integer operator specialization.
303    TypeTypeFloatInteger = 0x26,
304    /// engst0/engst0 operator specialization.
305    TypeTypeEngst0Engst0 = 0x30,
306    /// engst1/engst1 operator specialization.
307    TypeTypeEngst1Engst1 = 0x31,
308    /// engst2/engst2 operator specialization.
309    TypeTypeEngst2Engst2 = 0x32,
310    /// engst3/engst3 operator specialization.
311    TypeTypeEngst3Engst3 = 0x33,
312    /// engst4/engst4 operator specialization.
313    TypeTypeEngst4Engst4 = 0x34,
314    /// engst5/engst5 operator specialization.
315    TypeTypeEngst5Engst5 = 0x35,
316    /// engst6/engst6 operator specialization.
317    TypeTypeEngst6Engst6 = 0x36,
318    /// engst7/engst7 operator specialization.
319    TypeTypeEngst7Engst7 = 0x37,
320    /// engst8/engst8 operator specialization.
321    TypeTypeEngst8Engst8 = 0x38,
322    /// engst9/engst9 operator specialization.
323    TypeTypeEngst9Engst9 = 0x39,
324    /// vector/vector operator specialization.
325    TypeTypeVectorVector = 0x3a,
326    /// vector/float operator specialization.
327    TypeTypeVectorFloat = 0x3b,
328    /// float/vector operator specialization.
329    TypeTypeFloatVector = 0x3c,
330    /// in-place evaluation marker.
331    EvalInplace = 0x70,
332    /// post-place evaluation marker.
333    EvalPostplace = 0x71,
334}
335
336impl NcsAuxCode {
337    /// Returns the canonical short suffix used by the upstream assembler
338    /// helper.
339    #[must_use]
340    pub fn canonical_name(self) -> Option<&'static str> {
341        match self {
342            Self::None
343            | Self::TypeVoid
344            | Self::TypeCommand
345            | Self::EvalInplace
346            | Self::EvalPostplace => None,
347            Self::TypeInteger => Some("I"),
348            Self::TypeFloat => Some("F"),
349            Self::TypeString => Some("S"),
350            Self::TypeObject => Some("O"),
351            Self::TypeEngst0 => Some("E0"),
352            Self::TypeEngst1 => Some("E1"),
353            Self::TypeEngst2 => Some("E2"),
354            Self::TypeEngst3 => Some("E3"),
355            Self::TypeEngst4 => Some("E4"),
356            Self::TypeEngst5 => Some("E5"),
357            Self::TypeEngst6 => Some("E6"),
358            Self::TypeEngst7 => Some("E7"),
359            Self::TypeEngst8 => Some("E8"),
360            Self::TypeEngst9 => Some("E9"),
361            Self::TypeTypeIntegerInteger => Some("II"),
362            Self::TypeTypeFloatFloat => Some("FF"),
363            Self::TypeTypeObjectObject => Some("OO"),
364            Self::TypeTypeStringString => Some("SS"),
365            Self::TypeTypeStructStruct => Some("TT"),
366            Self::TypeTypeIntegerFloat => Some("IF"),
367            Self::TypeTypeFloatInteger => Some("FI"),
368            Self::TypeTypeEngst0Engst0 => Some("E0E0"),
369            Self::TypeTypeEngst1Engst1 => Some("E1E1"),
370            Self::TypeTypeEngst2Engst2 => Some("E2E2"),
371            Self::TypeTypeEngst3Engst3 => Some("E3E3"),
372            Self::TypeTypeEngst4Engst4 => Some("E4E4"),
373            Self::TypeTypeEngst5Engst5 => Some("E5E5"),
374            Self::TypeTypeEngst6Engst6 => Some("E6E6"),
375            Self::TypeTypeEngst7Engst7 => Some("E7E7"),
376            Self::TypeTypeEngst8Engst8 => Some("E8E8"),
377            Self::TypeTypeEngst9Engst9 => Some("E9E9"),
378            Self::TypeTypeVectorVector => Some("VV"),
379            Self::TypeTypeVectorFloat => Some("VF"),
380            Self::TypeTypeFloatVector => Some("FV"),
381        }
382    }
383}
384
385impl fmt::Display for NcsAuxCode {
386    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
387        if let Some(name) = self.canonical_name() {
388            f.write_str(name)
389        } else {
390            f.write_fmt(format_args!("{0:#04x}", *self as u8))write!(f, "{:#04x}", *self as u8)
391        }
392    }
393}
394
395/// An error returned when converting an opcode byte to [`NcsOpcode`].
396#[derive(#[automatically_derived]
impl ::core::fmt::Debug for UnknownNcsOpcode {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_tuple_field1_finish(f,
            "UnknownNcsOpcode", &&self.0)
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for UnknownNcsOpcode {
    #[inline]
    fn clone(&self) -> UnknownNcsOpcode {
        let _: ::core::clone::AssertParamIsClone<u8>;
        *self
    }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for UnknownNcsOpcode { }Copy, #[automatically_derived]
impl ::core::cmp::PartialEq for UnknownNcsOpcode {
    #[inline]
    fn eq(&self, other: &UnknownNcsOpcode) -> bool { self.0 == other.0 }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for UnknownNcsOpcode {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<u8>;
    }
}Eq)]
397pub struct UnknownNcsOpcode(pub u8);
398
399impl fmt::Display for UnknownNcsOpcode {
400    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
401        f.write_fmt(format_args!("unknown NCS opcode: {0:#04x}", self.0))write!(f, "unknown NCS opcode: {:#04x}", self.0)
402    }
403}
404
405impl Error for UnknownNcsOpcode {}
406
407/// An error returned when converting an auxcode byte to [`NcsAuxCode`].
408#[derive(#[automatically_derived]
impl ::core::fmt::Debug for UnknownNcsAuxCode {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_tuple_field1_finish(f,
            "UnknownNcsAuxCode", &&self.0)
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for UnknownNcsAuxCode {
    #[inline]
    fn clone(&self) -> UnknownNcsAuxCode {
        let _: ::core::clone::AssertParamIsClone<u8>;
        *self
    }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for UnknownNcsAuxCode { }Copy, #[automatically_derived]
impl ::core::cmp::PartialEq for UnknownNcsAuxCode {
    #[inline]
    fn eq(&self, other: &UnknownNcsAuxCode) -> bool { self.0 == other.0 }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for UnknownNcsAuxCode {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<u8>;
    }
}Eq)]
409pub struct UnknownNcsAuxCode(pub u8);
410
411impl fmt::Display for UnknownNcsAuxCode {
412    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
413        f.write_fmt(format_args!("unknown NCS aux code: {0:#04x}", self.0))write!(f, "unknown NCS aux code: {:#04x}", self.0)
414    }
415}
416
417impl Error for UnknownNcsAuxCode {}
418
419/// Errors returned while decoding a full `NCS` bytecode stream.
420#[derive(#[automatically_derived]
impl ::core::fmt::Debug for NcsReadError {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            NcsReadError::Header(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Header",
                    &__self_0),
            NcsReadError::Opcode(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Opcode",
                    &__self_0),
            NcsReadError::AuxCode(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "AuxCode", &__self_0),
            NcsReadError::TruncatedInstruction {
                offset: __self_0,
                expected_extra: __self_1,
                actual_extra: __self_2 } =>
                ::core::fmt::Formatter::debug_struct_field3_finish(f,
                    "TruncatedInstruction", "offset", __self_0,
                    "expected_extra", __self_1, "actual_extra", &__self_2),
        }
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for NcsReadError {
    #[inline]
    fn clone(&self) -> NcsReadError {
        match self {
            NcsReadError::Header(__self_0) =>
                NcsReadError::Header(::core::clone::Clone::clone(__self_0)),
            NcsReadError::Opcode(__self_0) =>
                NcsReadError::Opcode(::core::clone::Clone::clone(__self_0)),
            NcsReadError::AuxCode(__self_0) =>
                NcsReadError::AuxCode(::core::clone::Clone::clone(__self_0)),
            NcsReadError::TruncatedInstruction {
                offset: __self_0,
                expected_extra: __self_1,
                actual_extra: __self_2 } =>
                NcsReadError::TruncatedInstruction {
                    offset: ::core::clone::Clone::clone(__self_0),
                    expected_extra: ::core::clone::Clone::clone(__self_1),
                    actual_extra: ::core::clone::Clone::clone(__self_2),
                },
        }
    }
}Clone, #[automatically_derived]
impl ::core::cmp::PartialEq for NcsReadError {
    #[inline]
    fn eq(&self, other: &NcsReadError) -> bool {
        let __self_discr = ::core::intrinsics::discriminant_value(self);
        let __arg1_discr = ::core::intrinsics::discriminant_value(other);
        __self_discr == __arg1_discr &&
            match (self, other) {
                (NcsReadError::Header(__self_0),
                    NcsReadError::Header(__arg1_0)) => __self_0 == __arg1_0,
                (NcsReadError::Opcode(__self_0),
                    NcsReadError::Opcode(__arg1_0)) => __self_0 == __arg1_0,
                (NcsReadError::AuxCode(__self_0),
                    NcsReadError::AuxCode(__arg1_0)) => __self_0 == __arg1_0,
                (NcsReadError::TruncatedInstruction {
                    offset: __self_0,
                    expected_extra: __self_1,
                    actual_extra: __self_2 },
                    NcsReadError::TruncatedInstruction {
                    offset: __arg1_0,
                    expected_extra: __arg1_1,
                    actual_extra: __arg1_2 }) =>
                    __self_0 == __arg1_0 && __self_1 == __arg1_1 &&
                        __self_2 == __arg1_2,
                _ => unsafe { ::core::intrinsics::unreachable() }
            }
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for NcsReadError {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<NcsHeaderError>;
        let _: ::core::cmp::AssertParamIsEq<UnknownNcsOpcode>;
        let _: ::core::cmp::AssertParamIsEq<UnknownNcsAuxCode>;
        let _: ::core::cmp::AssertParamIsEq<usize>;
    }
}Eq)]
421pub enum NcsReadError {
422    /// Fixed header decode failed.
423    Header(NcsHeaderError),
424    /// An opcode byte was unknown.
425    Opcode(UnknownNcsOpcode),
426    /// An auxcode byte was unknown.
427    AuxCode(UnknownNcsAuxCode),
428    /// One instruction payload was truncated.
429    TruncatedInstruction {
430        /// Byte offset of the truncated instruction.
431        offset:         usize,
432        /// Expected payload size after opcode and auxcode.
433        expected_extra: usize,
434        /// Actual available payload bytes.
435        actual_extra:   usize,
436    },
437}
438
439impl fmt::Display for NcsReadError {
440    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
441        match self {
442            Self::Header(error) => error.fmt(f),
443            Self::Opcode(error) => error.fmt(f),
444            Self::AuxCode(error) => error.fmt(f),
445            Self::TruncatedInstruction {
446                offset,
447                expected_extra,
448                actual_extra,
449            } => f.write_fmt(format_args!("truncated NCS instruction at byte {0}: expected {1} payload bytes, got {2}",
        offset, expected_extra, actual_extra))write!(
450                f,
451                "truncated NCS instruction at byte {offset}: expected {expected_extra} payload \
452                 bytes, got {actual_extra}"
453            ),
454        }
455    }
456}
457
458impl Error for NcsReadError {}
459
460impl From<NcsHeaderError> for NcsReadError {
461    fn from(value: NcsHeaderError) -> Self {
462        Self::Header(value)
463    }
464}
465
466impl From<UnknownNcsOpcode> for NcsReadError {
467    fn from(value: UnknownNcsOpcode) -> Self {
468        Self::Opcode(value)
469    }
470}
471
472impl From<UnknownNcsAuxCode> for NcsReadError {
473    fn from(value: UnknownNcsAuxCode) -> Self {
474        Self::AuxCode(value)
475    }
476}
477
478impl TryFrom<u8> for NcsOpcode {
479    type Error = UnknownNcsOpcode;
480
481    fn try_from(value: u8) -> Result<Self, Self::Error> {
482        let opcode = match value {
483            0x01 => Self::Assignment,
484            0x02 => Self::RunstackAdd,
485            0x03 => Self::RunstackCopy,
486            0x04 => Self::Constant,
487            0x05 => Self::ExecuteCommand,
488            0x06 => Self::LogicalAnd,
489            0x07 => Self::LogicalOr,
490            0x08 => Self::InclusiveOr,
491            0x09 => Self::ExclusiveOr,
492            0x0a => Self::BooleanAnd,
493            0x0b => Self::Equal,
494            0x0c => Self::NotEqual,
495            0x0d => Self::Geq,
496            0x0e => Self::Gt,
497            0x0f => Self::Lt,
498            0x10 => Self::Leq,
499            0x11 => Self::ShiftLeft,
500            0x12 => Self::ShiftRight,
501            0x13 => Self::UShiftRight,
502            0x14 => Self::Add,
503            0x15 => Self::Sub,
504            0x16 => Self::Mul,
505            0x17 => Self::Div,
506            0x18 => Self::Modulus,
507            0x19 => Self::Negation,
508            0x1a => Self::OnesComplement,
509            0x1b => Self::ModifyStackPointer,
510            0x1c => Self::StoreIp,
511            0x1d => Self::Jmp,
512            0x1e => Self::Jsr,
513            0x1f => Self::Jz,
514            0x20 => Self::Ret,
515            0x21 => Self::DeStruct,
516            0x22 => Self::BooleanNot,
517            0x23 => Self::Decrement,
518            0x24 => Self::Increment,
519            0x25 => Self::Jnz,
520            0x26 => Self::AssignmentBase,
521            0x27 => Self::RunstackCopyBase,
522            0x28 => Self::DecrementBase,
523            0x29 => Self::IncrementBase,
524            0x2a => Self::SaveBasePointer,
525            0x2b => Self::RestoreBasePointer,
526            0x2c => Self::StoreState,
527            0x2d => Self::NoOperation,
528            _ => return Err(UnknownNcsOpcode(value)),
529        };
530        Ok(opcode)
531    }
532}
533
534impl TryFrom<u8> for NcsAuxCode {
535    type Error = UnknownNcsAuxCode;
536
537    fn try_from(value: u8) -> Result<Self, Self::Error> {
538        let aux = match value {
539            0x00 => Self::None,
540            0x01 => Self::TypeVoid,
541            0x02 => Self::TypeCommand,
542            0x03 => Self::TypeInteger,
543            0x04 => Self::TypeFloat,
544            0x05 => Self::TypeString,
545            0x06 => Self::TypeObject,
546            0x10 => Self::TypeEngst0,
547            0x11 => Self::TypeEngst1,
548            0x12 => Self::TypeEngst2,
549            0x13 => Self::TypeEngst3,
550            0x14 => Self::TypeEngst4,
551            0x15 => Self::TypeEngst5,
552            0x16 => Self::TypeEngst6,
553            0x17 => Self::TypeEngst7,
554            0x18 => Self::TypeEngst8,
555            0x19 => Self::TypeEngst9,
556            0x20 => Self::TypeTypeIntegerInteger,
557            0x21 => Self::TypeTypeFloatFloat,
558            0x22 => Self::TypeTypeObjectObject,
559            0x23 => Self::TypeTypeStringString,
560            0x24 => Self::TypeTypeStructStruct,
561            0x25 => Self::TypeTypeIntegerFloat,
562            0x26 => Self::TypeTypeFloatInteger,
563            0x30 => Self::TypeTypeEngst0Engst0,
564            0x31 => Self::TypeTypeEngst1Engst1,
565            0x32 => Self::TypeTypeEngst2Engst2,
566            0x33 => Self::TypeTypeEngst3Engst3,
567            0x34 => Self::TypeTypeEngst4Engst4,
568            0x35 => Self::TypeTypeEngst5Engst5,
569            0x36 => Self::TypeTypeEngst6Engst6,
570            0x37 => Self::TypeTypeEngst7Engst7,
571            0x38 => Self::TypeTypeEngst8Engst8,
572            0x39 => Self::TypeTypeEngst9Engst9,
573            0x3a => Self::TypeTypeVectorVector,
574            0x3b => Self::TypeTypeVectorFloat,
575            0x3c => Self::TypeTypeFloatVector,
576            0x70 => Self::EvalInplace,
577            0x71 => Self::EvalPostplace,
578            _ => return Err(UnknownNcsAuxCode(value)),
579        };
580        Ok(aux)
581    }
582}
583
584fn instruction_extra_size(opcode: NcsOpcode, auxcode: NcsAuxCode, bytes: &[u8]) -> usize {
585    match opcode {
586        NcsOpcode::Constant => match auxcode {
587            NcsAuxCode::TypeInteger
588            | NcsAuxCode::TypeFloat
589            | NcsAuxCode::TypeObject
590            | NcsAuxCode::TypeEngst2 => 4,
591            NcsAuxCode::TypeString | NcsAuxCode::TypeEngst7 => {
592                match bytes
593                    .get(..2)
594                    .and_then(|prefix| <[u8; 2]>::try_from(prefix).ok())
595                {
596                    Some(prefix) => 2 + usize::from(u16::from_be_bytes(prefix)),
597                    None => 2,
598                }
599            }
600            _ => 0,
601        },
602        NcsOpcode::Jmp
603        | NcsOpcode::Jsr
604        | NcsOpcode::Jz
605        | NcsOpcode::Jnz
606        | NcsOpcode::ModifyStackPointer
607        | NcsOpcode::Decrement
608        | NcsOpcode::Increment
609        | NcsOpcode::DecrementBase
610        | NcsOpcode::IncrementBase => 4,
611        NcsOpcode::StoreState => 8,
612        NcsOpcode::ExecuteCommand => 3,
613        NcsOpcode::RunstackCopy
614        | NcsOpcode::RunstackCopyBase
615        | NcsOpcode::Assignment
616        | NcsOpcode::AssignmentBase
617        | NcsOpcode::DeStruct => 6,
618        NcsOpcode::Equal | NcsOpcode::NotEqual if auxcode == NcsAuxCode::TypeTypeStructStruct => 2,
619        _ => 0,
620    }
621}
622
623/// Decodes a full `NCS V1.0` bytecode stream into individual instructions.
624///
625/// # Errors
626///
627/// Returns [`NcsReadError`] if the header is invalid or an instruction is
628/// malformed.
629pub fn decode_ncs_instructions(bytes: &[u8]) -> Result<Vec<NcsInstruction>, NcsReadError> {
630    let header = decode_ncs_header(bytes)?;
631    let mut offset = NCS_BINARY_HEADER_SIZE;
632    let code_end = NCS_BINARY_HEADER_SIZE + header.code_size as usize;
633    if bytes.len() < code_end {
634        return Err(NcsReadError::TruncatedInstruction {
635            offset,
636            expected_extra: header.code_size as usize,
637            actual_extra: bytes.len().saturating_sub(offset),
638        });
639    }
640
641    let mut instructions = Vec::new();
642    while offset < code_end {
643        let opcode = NcsOpcode::try_from(*bytes.get(offset).ok_or(
644            NcsReadError::TruncatedInstruction {
645                offset,
646                expected_extra: 1,
647                actual_extra: bytes.len().saturating_sub(offset),
648            },
649        )?)?;
650        let auxcode = NcsAuxCode::try_from(*bytes.get(offset + 1).ok_or(
651            NcsReadError::TruncatedInstruction {
652                offset,
653                expected_extra: 2,
654                actual_extra: bytes.len().saturating_sub(offset),
655            },
656        )?)?;
657        let extra_window = bytes.get(offset + 2..code_end).unwrap_or(&[]);
658        let extra_size = instruction_extra_size(opcode, auxcode, extra_window);
659        let remaining = code_end.saturating_sub(offset + 2);
660        if remaining < extra_size {
661            return Err(NcsReadError::TruncatedInstruction {
662                offset,
663                expected_extra: extra_size,
664                actual_extra: remaining,
665            });
666        }
667        let extra = bytes
668            .get(offset + 2..offset + 2 + extra_size)
669            .unwrap_or(&[])
670            .to_vec();
671        instructions.push(NcsInstruction {
672            opcode,
673            auxcode,
674            extra,
675        });
676        offset += NCS_OPERATION_BASE_SIZE + extra_size;
677    }
678
679    Ok(instructions)
680}
681
682/// Encodes one `NCS V1.0` instruction stream with the fixed binary header.
683pub fn encode_ncs_instructions(instructions: &[NcsInstruction]) -> Vec<u8> {
684    let code_size = u32::try_from(
685        instructions
686            .iter()
687            .map(NcsInstruction::encoded_len)
688            .sum::<usize>(),
689    )
690    .ok()
691    .unwrap_or(u32::MAX);
692    let mut bytes = Vec::with_capacity(
693        NCS_BINARY_HEADER_SIZE + usize::try_from(code_size).ok().unwrap_or(usize::MAX),
694    );
695    bytes.extend_from_slice(NCS_HEADER.as_bytes());
696    bytes.push(b'B');
697    bytes.extend_from_slice(&code_size.to_be_bytes());
698    for instruction in instructions {
699        bytes.push(instruction.opcode as u8);
700        bytes.push(instruction.auxcode as u8);
701        bytes.extend_from_slice(&instruction.extra);
702    }
703    bytes
704}
705
706#[cfg(test)]
707mod tests {
708    use super::*;
709
710    #[test]
711    fn decode_header_accepts_valid_prefix() -> Result<(), Box<dyn std::error::Error>> {
712        let bytes = [
713            b'N', b'C', b'S', b' ', b'V', b'1', b'.', b'0', b'B', 0x00, 0x00, 0x00, 0x2a,
714        ];
715        let header = decode_ncs_header(&bytes)?;
716        assert_eq!(header.code_size, 42);
717        Ok(())
718    }
719
720    #[test]
721    fn decode_header_rejects_bad_marker() {
722        let bytes = [
723            b'N', b'C', b'S', b' ', b'V', b'1', b'.', b'0', b'X', 0x00, 0x00, 0x00, 0x2a,
724        ];
725        assert_eq!(
726            decode_ncs_header(&bytes),
727            Err(NcsHeaderError::InvalidMarker(b'X'))
728        );
729    }
730
731    #[test]
732    fn encode_and_decode_roundtrip_instruction_stream() -> Result<(), Box<dyn std::error::Error>> {
733        let instructions = vec![
734            NcsInstruction {
735                opcode:  NcsOpcode::Constant,
736                auxcode: NcsAuxCode::TypeInteger,
737                extra:   42_i32.to_be_bytes().to_vec(),
738            },
739            NcsInstruction {
740                opcode:  NcsOpcode::Ret,
741                auxcode: NcsAuxCode::None,
742                extra:   Vec::new(),
743            },
744        ];
745
746        let bytes = encode_ncs_instructions(&instructions);
747        let decoded = decode_ncs_instructions(&bytes)?;
748        assert_eq!(decoded, instructions);
749        Ok(())
750    }
751}