pico-bytes 0.1.1

byte code abstraction for the pico-VM
Documentation
#[cfg(test)]
mod tests;

use std::{error::Error, fmt::Display};

pub type Register = u32;
pub type Address = u32;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Locator {
    Address(Address),
    FromRegister(Register),
}
pub type Bytes = (u8, u32, u32, u32);
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(u8)]
pub enum ByteCode {
    None,
    Halt,
    Jump {
        addr: Locator,
    },
    JumpIf {
        cond: Register,
        addr: Locator,
    },

    String {
        dst: Register,
        addr: u32,
    },
    Int {
        dst: Register,
        value: u64,
    },
    Float {
        dst: Register,
        value: f64,
    },
    Bool {
        dst: Register,
        value: bool,
    },

    Move {
        dst: Register,
        src: Register,
    },
    Field {
        dst: Register,
        src: Register,
        field: u32,
    },
    Call {
        addr: Locator,
        args: u32,
        dst: Register
    },

    Binary {
        op: BinaryOperation,
        dst: Register,
        left: Register,
        right: Register,
    },
    Unary {
        op: UnaryOperation,
        dst: Register,
        right: Register,
    },
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum BinaryOperation {
    Add,
    Sub,
    Div,
    Mul,
}
#[derive(Debug, Clone, PartialEq)]
pub struct BinaryOperationError;
impl TryFrom<u8> for BinaryOperation {
    type Error = BinaryOperationError;
    fn try_from(value: u8) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(Self::Add),
            1 => Ok(Self::Sub),
            2 => Ok(Self::Div),
            3 => Ok(Self::Mul),
            _ => Err(BinaryOperationError),
        }
    }
}
impl From<BinaryOperation> for u8 {
    fn from(val: BinaryOperation) -> Self {
        match val {
            BinaryOperation::Add => 0,
            BinaryOperation::Sub => 1,
            BinaryOperation::Div => 2,
            BinaryOperation::Mul => 3,
        }
    }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum UnaryOperation {
    Neg,
}
#[derive(Debug, Clone, PartialEq)]
pub struct UnaryOperationError;
impl TryFrom<u8> for UnaryOperation {
    type Error = UnaryOperationError;
    fn try_from(value: u8) -> Result<Self, Self::Error> {
        match value {
            0 => Ok(Self::Neg),
            _ => Err(UnaryOperationError),
        }
    }
}
impl From<UnaryOperation> for u8 {
    fn from(val: UnaryOperation) -> Self {
        match val {
            UnaryOperation::Neg => 0,
        }
    }
}

impl From<Locator> for u32 {
    fn from(value: Locator) -> Self {
        match value {
            Locator::FromRegister(addr) | Locator::Address(addr) => addr,
        }
    }
}
impl From<ByteCode> for Bytes {
    fn from(value: ByteCode) -> Self {
        match value {
            ByteCode::None => (0x00, 0, 0, 0),
            ByteCode::Halt => (0x01, 0, 0, 0),
            ByteCode::Jump { addr } => match addr {
                Locator::Address(addr) => (0x02, 0, addr, 0),
                Locator::FromRegister(addr) => (0x03, 0, addr, 0),
            }
            ByteCode::JumpIf { cond, addr } => match addr {
                Locator::Address(addr) => (0x04, cond, addr, 0),
                Locator::FromRegister(addr) => (0x05, cond, addr, 0),
            }
            ByteCode::String { dst, addr } => (0x10, dst, addr, 0),
            ByteCode::Int { dst, value } => {
                (0x11, dst, value as u32, (value >> 32) as u32)
            }
            ByteCode::Float { dst, value } => {
                let bits = value.to_bits();
                (0x12, dst, bits as u32, (bits >> 32) as u32)
            }
            ByteCode::Bool { dst, value } => (0x13, dst, value.into(), 0),
            ByteCode::Move { dst, src } => (0x20, dst, src, 0),
            ByteCode::Field { dst, src, field } => (0x21, dst, src, field),
            ByteCode::Call { addr, args, dst } => match addr {
                Locator::Address(addr) => (0x22, addr, args, dst),
                Locator::FromRegister(addr) => (0x23, addr, args, dst),
            }
            ByteCode::Binary { op, dst, left, right } => (0x30 + op as u8, dst, left, right),
            ByteCode::Unary { op, dst, right } => (0x40 + op as u8, dst, right, 0),
        }
    }
}

#[derive(Debug, Clone, PartialEq)]
pub enum ByteCodeError {
    InvalidOperation,
    InvalidBinaryOperation(u8),
    InvalidUnaryOperation(u8),
}
impl Display for ByteCodeError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ByteCodeError::InvalidOperation => write!(f, "invalid operation"),
            ByteCodeError::InvalidBinaryOperation(op) => {
                write!(f, "invalid binary operation 0x20 + 0x{op:2x?}")
            }
            ByteCodeError::InvalidUnaryOperation(op) => {
                write!(f, "invalid unary operation 0x30 + 0x{op:2x?}")
            }
        }
    }
}
impl Error for ByteCodeError {}
impl TryFrom<Bytes> for ByteCode {
    type Error = ByteCodeError;
    fn try_from(value: Bytes) -> Result<Self, Self::Error> {
        match value.0 {
            0x00 => Ok(Self::None),
            0x01 => Ok(Self::Halt),
            0x02 => Ok(Self::Jump {
                addr: Locator::Address(value.2),
            }),
            0x03 => Ok(Self::Jump {
                addr: Locator::FromRegister(value.2),
            }),
            0x04 => Ok(Self::JumpIf {
                cond: value.1,
                addr: Locator::Address(value.2),
            }),
            0x05 => Ok(Self::JumpIf {
                cond: value.1,
                addr: Locator::FromRegister(value.2),
            }),

            0x10 => Ok(Self::String {
                dst: value.1,
                addr: value.2,
            }),
            0x11 => Ok(Self::Int {
                dst: value.1,
                value: (value.2 as u64) | ((value.3 as u64) << 32),
            }),
            0x12 => Ok(Self::Float {
                dst: value.1,
                value: f64::from_bits((value.2 as u64) | ((value.3 as u64) << 32)),
            }),
            0x13 => Ok(Self::Bool {
                dst: value.1,
                value: value.2 != 0,
            }),

            0x20 => Ok(Self::Move {
                dst: value.1,
                src: value.2,
            }),
            0x21 => Ok(Self::Field {
                dst: value.1,
                src: value.2,
                field: value.3,
            }),
            0x22 => Ok(Self::Call {
                addr: Locator::Address(value.1),
                args: value.2,
                dst: value.3
            }),
            0x23 => Ok(Self::Call {
                addr: Locator::FromRegister(value.1),
                args: value.2,
                dst: value.3
            }),

            0x30..=0x3f => Ok(Self::Binary {
                op: BinaryOperation::try_from(value.0 - 0x20)
                    .map_err(|_| ByteCodeError::InvalidBinaryOperation(value.0 - 0x20))?,
                dst: value.1,
                left: value.2,
                right: value.3,
            }),
            0x40..=0x4f => Ok(Self::Unary {
                op: UnaryOperation::try_from(value.0 - 0x20)
                    .map_err(|_| ByteCodeError::InvalidUnaryOperation(value.0 - 0x30))?,
                dst: value.1,
                right: value.2,
            }),
            _ => Err(ByteCodeError::InvalidOperation),
        }
    }
}

#[derive(Debug, Clone, PartialEq)]
pub struct Program {
    pub strings: Vec<String>,
    pub code: Vec<ByteCode>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ProgramParseError {
    InsufficiantBytes,
    ByteCodeError(ByteCodeError)
}
impl Display for ProgramParseError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ProgramParseError::InsufficiantBytes => write!(f, "insufficiant bytes"),
            ProgramParseError::ByteCodeError(err) => err.fmt(f),
        }
    }
}
impl Error for ProgramParseError {}
impl TryFrom<&[u8]> for Program {
    type Error = ProgramParseError;
    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
        let mut bytes = value.iter();

        let size = {
            let (Some(n1), Some(n2), Some(n3), Some(n4)) = (bytes.next().copied(), bytes.next().copied(), bytes.next().copied(), bytes.next().copied()) else {
                return Err(ProgramParseError::InsufficiantBytes);
            };
            u32::from_be_bytes([n1, n2, n3, n4])
        };
        let mut strings = Vec::with_capacity(size as usize);
        for _ in 0..size {
            let string_size = {
                let (Some(n1), Some(n2), Some(n3), Some(n4)) = (bytes.next().copied(), bytes.next().copied(), bytes.next().copied(), bytes.next().copied()) else {
                    return Err(ProgramParseError::InsufficiantBytes);
                };
                u32::from_be_bytes([n1, n2, n3, n4])
            };
            let mut string = String::new();
            for _ in 0..string_size {
                let Some(c) = bytes.next().copied() else {
                    return Err(ProgramParseError::InsufficiantBytes);
                };
                string.push(c as char);
            }
            strings.push(string);
        }

        let mut code = vec![];
        while let Some(instr) = bytes.next().copied() {
            let arg1 = {
                let (Some(n1), Some(n2), Some(n3), Some(n4)) = (bytes.next().copied(), bytes.next().copied(), bytes.next().copied(), bytes.next().copied()) else {
                    return Err(ProgramParseError::InsufficiantBytes);
                };
                u32::from_be_bytes([n1, n2, n3, n4])
            };
            let arg2 = {
                let (Some(n1), Some(n2), Some(n3), Some(n4)) = (bytes.next().copied(), bytes.next().copied(), bytes.next().copied(), bytes.next().copied()) else {
                    return Err(ProgramParseError::InsufficiantBytes);
                };
                u32::from_be_bytes([n1, n2, n3, n4])
            };
            let arg3 = {
                let (Some(n1), Some(n2), Some(n3), Some(n4)) = (bytes.next().copied(), bytes.next().copied(), bytes.next().copied(), bytes.next().copied()) else {
                    return Err(ProgramParseError::InsufficiantBytes);
                };
                u32::from_be_bytes([n1, n2, n3, n4])
            };
            code.push(ByteCode::try_from((instr, arg1, arg2, arg3)).map_err(ProgramParseError::ByteCodeError)?);
        }

        Ok(Self { strings, code })
    }
}
impl From<Program> for Vec<u8> {
    fn from(program: Program) -> Self {
        let mut bytes = vec![];

        bytes.extend((program.strings.len() as u32).to_be_bytes());
        for string in program.strings {
            bytes.extend((string.len() as u32).to_be_bytes());
            bytes.extend(string.chars().map(|c| c as u8));
        }

        for bytecode in program.code {
            let (instr, arg1, arg2, arg3): Bytes = bytecode.into();
            bytes.push(instr);
            bytes.extend(arg1.to_be_bytes());
            bytes.extend(arg2.to_be_bytes());
            bytes.extend(arg3.to_be_bytes());
        }

        bytes
    }
}