rascal_bytecode 0.1.2

Rascal programming language bytecode.
Documentation
use crate::{opcodes::Op, types::Value};
use alloc::{collections::BTreeMap, string::String, vec::Vec};
use core::convert::TryFrom;

use self::disassemble::disassemble;

#[derive(Debug, Clone)]
pub struct Chunk {
    bytecode: Vec<u8>,
    constants: Vec<[u8; 8]>,
    src_lines_map: BTreeMap<usize, usize>,
}
impl Chunk {
    pub fn new() -> Chunk {
        Chunk {
            bytecode: Vec::new(),
            constants: Vec::new(),
            src_lines_map: BTreeMap::new(),
        }
    }
    pub fn push_op(&mut self, src_line: usize, opcode: Op) {
        if !self.src_lines_map.contains_key(&src_line) {
            self.src_lines_map.insert(src_line, self.bytecode.len());
        }
        self.bytecode.push(opcode.into());
    }
    pub fn push_const(&mut self, src_line: usize, value: Value) {
        let index: u8 = u8::try_from(self.constants.len()).expect("Constants vec longer than 256");
        self.constants.push(value.into());
        self.push_op(src_line, Op::Constant);
        self.bytecode.push(index);
    }
    pub fn disassemble(&self) -> String {
        disassemble(self)
    }

    /// Get a reference to the chunk's constants.
    pub fn constants(&self) -> &[[u8; 8]] {
        self.constants.as_slice()
    }

    /// Get a reference to the chunk's bytecode.
    pub fn bytecode(&self) -> &[u8] {
        self.bytecode.as_slice()
    }
}

pub mod disassemble {
    use crate::{chunk::Chunk, opcodes::Op};
    use alloc::{format, string::String};
    use core::{convert::TryInto, iter::Enumerate, slice::Iter};

    pub fn disassemble(chunk: &Chunk) -> String {
        // Generate a sorted list of line numbers from the line map, note that lines may not be contiguous.
        let mut line_list = chunk.src_lines_map.iter().peekable();
        let mut code = chunk.bytecode.iter().enumerate();
        let mut result = String::new();
        while let Some((n_byte, bytes)) = code.next() {
            let line = if let Some((&next_line, &src_line_byte)) = line_list.peek() {
                if src_line_byte == n_byte {
                    line_list.next();
                    format!("{}", next_line)
                } else {
                    String::from("|")
                }
            } else {
                // Reached on the last line
                String::from("|")
            };
            result.push_str(&format!("{:0>4} {:>4} ", n_byte, line));
            let opcode = (*bytes).try_into().unwrap();
            match opcode {
                Op::Constant => constant(chunk, &mut code, &mut result),
                Op::Negate => negate(&mut result),
                _ => result.push_str(&format!("{:?}", opcode)),
            }
        }
        result
    }

    pub fn constant(chunk: &Chunk, code: &mut Enumerate<Iter<u8>>, output: &mut String) {
        let (_, const_index) = code.next().expect("OpConstant not followed by an operand");
        let constant = f64::from_be_bytes(
            *(chunk
                .constants()
                .get(*const_index as usize)
                .expect("Provided constant index out of bounds")),
        );
        output.push_str(&format!(
            "{:?} {:>4} '{}'",
            Op::Constant,
            const_index,
            constant
        ));
    }
    pub fn negate(output: &mut String) {
        output.push_str(&format!("{:?}", Op::Negate));
    }
}