bytecoding 0.1.0

Derive macro for encoding and decoding instructions and operands as bytecode
Documentation
use bytecoding::Bytecode;
use std::fmt;

fn test_encode<T: Bytecode + Eq + fmt::Debug>(instructions: &[T], expected_bytes: &[u8]) {
    let mut bytes = Vec::new();
    for i in instructions {
        i.encode(&mut bytes);
    }
    assert_eq!(&bytes, expected_bytes);
}

fn test_decode<T: Bytecode + Eq + fmt::Debug>(bytes: &[u8], expected_instructions: &[T]) {
    let mut bytes: &[u8] = &bytes;
    let mut instructions = Vec::new();
    while !bytes.is_empty() {
        instructions.push(T::decode(&mut bytes).unwrap());
    }
    assert_eq!(instructions, expected_instructions);
}

fn test_encode_decode<T: Bytecode + Eq + fmt::Debug>(instructions: &[T], expected_bytes: &[u8]) {
    test_encode(instructions, expected_bytes);
    test_decode(expected_bytes, instructions);
}

#[test]
fn instruction_type() {
    #[derive(Debug, PartialEq, Eq, Bytecode)]
    #[bytecode(type = u8)]
    enum InstructionU8 {
        A,
        B,
    }
    test_encode_decode(&[InstructionU8::A, InstructionU8::B], &[0, 1]);

    #[derive(Debug, PartialEq, Eq, Bytecode)]
    #[bytecode(type = u16)]
    enum InstructionU16 {
        A,
        B,
    }
    test_encode_decode(&[InstructionU16::A, InstructionU16::B], &[0, 0, 0, 1]);

    #[derive(Debug, PartialEq, Eq, Bytecode)]
    #[bytecode(type = u32)]
    enum InstructionU32 {
        A,
        B,
    }
    test_encode_decode(
        &[InstructionU32::A, InstructionU32::B],
        &[0, 0, 0, 0, 0, 0, 0, 1],
    );

    #[derive(Debug, PartialEq, Eq, Bytecode)]
    #[bytecode(type = u64)]
    enum InstructionU64 {
        A,
        B,
    }
    test_encode_decode(
        &[InstructionU64::A, InstructionU64::B],
        &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    );
}

#[test]
fn flatten() {
    #[derive(Debug, PartialEq, Eq, Bytecode)]
    #[bytecode(type = u8)]
    enum Instruction {
        A(u16, #[bytecode(flatten = [1, 0])] u64),
        B {
            #[bytecode(flatten = [10, 11, 12, 13])]
            a: u32,
            b: u8,
        },
        C(#[bytecode(flatten = [100])] u8),
    }

    let instructions = [
        Instruction::A(10, 1),
        Instruction::A(11, 0),
        Instruction::A(12, 2),
        Instruction::B { a: 10, b: 42 },
        Instruction::B { a: 13, b: 42 },
        Instruction::B { a: 0, b: 42 },
        Instruction::B { a: 14, b: 42 },
        Instruction::C(100),
        Instruction::C(99),
    ];
    #[rustfmt::skip]
    let expected_bytes = [
        0, 0, 10,
        1, 0, 11,
        2, 0, 12, 0, 0, 0, 0, 0, 0, 0, 2,
        3, 42,
        6, 42,
        7, 0, 0, 0, 0, 42,
        7, 0, 0, 0, 14, 42,
        8,
        9, 99,
    ];
    test_encode_decode(&instructions, &expected_bytes);
}

#[test]
fn flatten_all() {
    #[derive(Debug, PartialEq, Eq)]
    enum Foo {
        Bar,
        Baz,
    }

    #[derive(Debug, PartialEq, Eq, Bytecode)]
    #[bytecode(type = u8)]
    enum Instruction {
        A(#[bytecode(flatten_all = [true, false])] bool),
        B {
            #[bytecode(flatten_all = [true, false])]
            a: bool,
        },
        C(u32, #[bytecode(flatten_all = [false, true])] bool),
        D {
            #[bytecode(flatten_all = [false, true])]
            a: bool,
            b: u16,
            c: u8,
        },
        E(#[bytecode(flatten_all = [Foo::Bar, Foo::Baz])] Foo),
    }

    let instructions = [
        Instruction::A(true),
        Instruction::A(false),
        Instruction::B { a: true },
        Instruction::B { a: false },
        Instruction::C(10, false),
        Instruction::C(11, true),
        Instruction::D {
            a: false,
            b: 20,
            c: 1,
        },
        Instruction::D {
            a: true,
            b: 21,
            c: 2,
        },
        Instruction::E(Foo::Bar),
        Instruction::E(Foo::Baz),
    ];
    #[rustfmt::skip]
    let expected_bytes = [
        0,
        1,
        2,
        3,
        4, 0, 0, 0, 10,
        5, 0, 0, 0, 11,
        6, 0, 20, 1,
        7, 0, 21, 2,
        8,
        9,
    ];
    test_encode_decode(&instructions, &expected_bytes);
}

#[test]
fn skip() {
    #[derive(Debug, PartialEq, Eq, Bytecode)]
    struct Operand {
        #[bytecode(skip)]
        value1: String,
        value2: u8,
    }

    #[derive(Debug, PartialEq, Eq, Bytecode)]
    #[bytecode(type = u8)]
    enum Instruction {
        A(#[bytecode(skip)] bool),
        B {
            #[bytecode(skip)]
            a: bool,
        },
        C(u8, #[bytecode(skip)] String),
        D {
            #[bytecode(skip)]
            a: usize,
            b: u8,
            c: u16,
        },
        E(Operand),
    }

    let instructions = [
        Instruction::A(true),
        Instruction::A(false),
        Instruction::B { a: true },
        Instruction::B { a: false },
        Instruction::C(10, "foo".to_owned()),
        Instruction::C(11, String::new()),
        Instruction::D { a: 42, b: 20, c: 1 },
        Instruction::D { a: 43, b: 21, c: 2 },
        Instruction::E(Operand {
            value1: "bar".to_owned(),
            value2: 1,
        }),
        Instruction::E(Operand {
            value1: "baz".to_owned(),
            value2: 2,
        }),
    ];
    #[rustfmt::skip]
    let expected_bytes = [
        0,
        0,
        1,
        1,
        2, 10,
        2, 11,
        3, 20, 0, 1,
        3, 21, 0, 2,
        4, 1,
        4, 2,
    ];
    test_encode(&instructions, &expected_bytes);

    let expected_instructions = [
        Instruction::A(false),
        Instruction::A(false),
        Instruction::B { a: false },
        Instruction::B { a: false },
        Instruction::C(10, String::new()),
        Instruction::C(11, String::new()),
        Instruction::D { a: 0, b: 20, c: 1 },
        Instruction::D { a: 0, b: 21, c: 2 },
        Instruction::E(Operand {
            value1: String::new(),
            value2: 1,
        }),
        Instruction::E(Operand {
            value1: String::new(),
            value2: 2,
        }),
    ];
    test_decode(&expected_bytes, &expected_instructions);
}

#[test]
fn explicit_code() {
    #[derive(Debug, PartialEq, Eq, Bytecode)]
    #[bytecode(type = u8)]
    enum Instruction {
        #[bytecode(code = 3)]
        A,
        #[bytecode(code = [1])]
        B,
        #[bytecode(code = 42)]
        C,

        #[bytecode(code = [10, 11, 12, 13])]
        D(#[bytecode(flatten = [0, 1, 2])] u8),

        #[bytecode(code = [20, 21, 22, 23, 24, 25])]
        E {
            #[bytecode(flatten = [0, 1])]
            a: u16,
            #[bytecode(flatten_all = [true, false])]
            b: bool,
        },
    }

    let instructions = [
        Instruction::A,
        Instruction::B,
        Instruction::C,
        Instruction::D(0),
        Instruction::D(1),
        Instruction::D(2),
        Instruction::D(3),
        Instruction::E { a: 0, b: true },
        Instruction::E { a: 0, b: false },
        Instruction::E { a: 1, b: true },
        Instruction::E { a: 1, b: false },
        Instruction::E { a: 2, b: true },
        Instruction::E { a: 2, b: false },
    ];
    #[rustfmt::skip]
    let expected_bytes = [
        3,
        1,
        42,
        10,
        11,
        12,
        13, 3,
        20,
        21,
        22,
        23,
        24, 0, 2,
        25, 0, 2,
    ];
    test_encode_decode(&instructions, &expected_bytes);
}

#[test]
fn explicit_and_implicit_code() {
    #[derive(Debug, PartialEq, Eq, Bytecode)]
    #[bytecode(type = u8)]
    enum Instruction {
        A,
        #[bytecode(code = 0)]
        B,
        #[bytecode(code = 42)]
        C,
        D,
        #[bytecode(code = [2])]
        E,

        F {
            #[bytecode(flatten_all = [true, false])]
            a: bool,
            b: u32,
        },

        #[bytecode(code = [10, 11])]
        G(#[bytecode(flatten = [1])] u16),
    }

    let instructions = [
        Instruction::A,
        Instruction::B,
        Instruction::C,
        Instruction::D,
        Instruction::E,
        Instruction::F { a: true, b: 0 },
        Instruction::F { a: false, b: 1 },
        Instruction::G(0),
        Instruction::G(1),
        Instruction::G(2),
    ];
    #[rustfmt::skip]
    let expected_bytes = [
        1,
        0,
        42,
        3,
        2,
        4, 0, 0, 0, 0,
        5, 0, 0, 0, 1,
        11, 0, 0,
        10,
        11, 0, 2,
    ];
    test_encode_decode(&instructions, &expected_bytes);
}