Enum variants u8 rep conversion

Currently I am writing a stack-based VM. For that I have enum defined for op codes. However I am not getting the same functionality of enum variants getting converted into u8 reps directly as it's done in C.
I want the below code to work:

pub enum OpCode {
    OP_RETURN,
    OP_CONSTANT,
}

pub struct Chunk {
    code: Vec<u8>,
}
impl Chunk {
    pub fn new() -> Self {
        Chunk {
            code: vec![],
        }
    }

    pub fn write_op(&mut self, op_code: OpCode) {
        self.code.push(op_code);
    }

    pub fn disassemble(&self) ->  Vec<String> {
        let mut offset = 0;
        let mut parsed_instructions: Vec<String> = vec![];
        while offset < self.code.len() {
            let (str_rep, new_offset) = self.disassemble_instruction(offset);
            parsed_instructions.push(str_rep);
            offset = new_offset;
        }
        parsed_instructions
    }

    pub fn disassemble_instruction(&self, offset: usize) -> (String, usize) {
        match self.code[offset] {
            OpCode::OP_RETURN => todo!(),
            OpCode::OP_CONSTANT => todo!(),
        }
    }
}

Is there a standard way (preferably not a pre-mature crate) which would help me achieve this kind of interaction with enum variants ?

This doesn't address your wish, but there's no guarantee your enum is represented by a u8 by default. Use #[repr(u8)] (or #[repr(C)]).

As far as I know the standard way is to convert your u8s to OpCodes before the match (for example) -- if you have a Vec of codes, store a Vec<OpCode>. But perhaps someone can suggest a suitable crate to wire up a bunch of consts or something. You won't be able to have a match on a u8 that doesn't cover all possible u8 values though, so you'd still need a catch-all arm.

Can the techniques in the answers of Mapping enum <=> u8 work ?

Maybe FromPrimitive from the num-derive crate is what you're looking for? It'd allow you to do something like:

use num_traits::FromPrimitive;
match OpCode::from_u8(self.code[offset]).expect("invalid opcode") {
    OpCode::OP_RETURN => todo!(),
    OpCode::OP_CONSTANT => todo!(),
}
1 Like

not really @zeroexcuses , they are also using u8 while matching, I believe matching OpCode enum variants is much more cleaner and better way.

Yeah @jessa0 This will work. Meanwhile I implemented it like below for now

use super::helper::get_machine_byte_multiple;
use std::{convert::TryInto, fmt::Display, vec};
use std::rc::Rc;

pub enum OpCode {
    OP_RETURN,   // 0
    OP_CONSTANT, // 1
}
impl OpCode {
    pub fn to_byte(&self) -> u8 {
        match self {
            OpCode::OP_RETURN => 0,
            OpCode::OP_CONSTANT => 1,
        }
    }
}

const OP_CODES_MAP: [OpCode; 2] = [OpCode::OP_RETURN, OpCode::OP_CONSTANT];

#[derive(Clone)]
pub enum Data {
    INT(i32),
    FLOAT(f32),
    LITERAL(Rc<String>),
    BOOL(bool),
}
impl Display for Data {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Data::INT(val) => write!(f, "{}", val),
            Data::FLOAT(val) => write!(f, "{}", val),
            Data::LITERAL(val) => write!(f, "{}", val),
            Data::BOOL(val) => write!(f, "{}", val),
        }
    }
}

pub struct Chunk {
    code: Vec<u8>,
    constants: Vec<Data>,
    line_numbers: Vec<usize>,
}
impl Chunk {
    pub fn new() -> Self {
        Chunk {
            code: vec![],
            constants: vec![],
            line_numbers: vec![],
        }
    }

    pub fn write_byte(&mut self, byte: u8, line_number: usize) {
        self.code.push(byte);
        self.line_numbers.push(line_number);
    }

    pub fn write_constant(&mut self, const_value: Data, line_number: usize) {
        let const_index = self.constants.len();
        self.constants.push(const_value);
        self.code.push(OpCode::OP_CONSTANT.to_byte());
        self.code.extend_from_slice(&const_index.to_be_bytes());
        self.line_numbers.push(line_number);
    }

    pub fn disassemble(&self) -> Vec<String> {
        let mut offset = 0;
        let mut parsed_instructions: Vec<String> = vec![];
        let mut inst_index = 0;
        while offset < self.code.len() {
            let (str_rep, new_offset) = self.disassemble_instruction(offset);
            let mut inst_str = format!("{}: ", self.line_numbers[inst_index]);
            inst_str.push_str(&str_rep);
            parsed_instructions.push(inst_str);
            offset = new_offset;
            inst_index = inst_index + 1;
        }
        parsed_instructions
    }

    pub fn disassemble_instruction(&self, offset: usize) -> (String, usize) {
        match OP_CODES_MAP[usize::from(self.code[offset])] {
            OpCode::OP_RETURN => ("RETURN".to_string(), offset + 1),
            OpCode::OP_CONSTANT => {
                let byte_multiple = get_machine_byte_multiple();
                let v = self.code[offset + 1..offset + (byte_multiple + 1)]
                    .try_into()
                    .unwrap();
                let const_value = &self.constants[usize::from_be_bytes(v)];
                (
                    format!("CONSTANT {}", const_value),
                    offset + (byte_multiple + 1),
                )
            }
        }
    }
}
impl Display for Chunk {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let mut display_str = "".to_string();
        let inst_vec = self.disassemble();
        for inst in inst_vec {
            display_str.push_str("\n");
            display_str.push_str(&inst);
        }
        write!(f, "{}", display_str)
    }
}

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.