rubbler 0.1.2

Rubbler is a RISC-V assembler written in Rust 🦀. This library was written with the main purpose of embedding a simple RISC-V assembler inside of a RISC-V CPU test bench code written with verilator.
Documentation
//! # Rubbler
//!
//! Rubbler is a RISC-V assembler written in Rust 🦀. This library was written with the main purpose of embedding a simple RISC-V assembler inside of a RISC-V CPU test bench code written with verilator.
#![allow(dead_code)]

mod directives;
mod encoder;
mod error;
mod expression;
mod generator;
mod inst;
mod parser;
mod reg;
mod scanner;
mod stanalyzer;
mod statement;
mod token;

use generator::Generator;
use parser::Parser;
use scanner::Scanner;
use stanalyzer::Analyzer;

use std::fs;
use std::path::PathBuf;

pub mod ffi {
    //! Exports C interfaces for functions provided by the [crate] module to enable binding with
    //! foreign code.
    use std::ffi::c_char;
    use std::ffi::CStr;

    /// Wraps the functionality of [crate::rubble] inside a C interface.
    ///
    /// The resulting bytes are written to the `bytes` pointer. The `size` pointer should be
    /// initialized to point to the maximum number of bytes that can be written to `bytes`. If the
    /// source string is assembled successfully, the number of bytes written to `bytes` is written
    /// back to `size`.
    ///
    /// If there is no error, it returns true, otherwise it prints out the error explanation to
    /// standard output and return false.
    #[no_mangle]
    pub extern "C" fn rubble(source: *const c_char, bytes: *mut u8, size: *mut usize) -> bool {
        let c_str = unsafe { CStr::from_ptr(source) };
        let size_deref = unsafe { *size };
        let str = match c_str.to_str() {
            Ok(s) => s,
            Err(e) => {
                println!("[rubbler] {}", e);
                return false;
            }
        };
        match super::rubble(str) {
            Ok(b) => {
                for (i, byte) in b.iter().enumerate() {
                    if i == size_deref {
                        break;
                    }
                    unsafe { *bytes.add(i) = *byte }
                    unsafe { *size = i + 1 }
                }
                true
            }
            Err(e) => {
                println!("[rubbler] {}", e);
                false
            }
        }
    }

    /// Wraps the functionality of [crate::rubble_file] inside a C interface.
    ///
    /// The resulting bytes are written to the `bytes` pointer. The `size` pointer should be
    /// initialized to point to the maximum number of bytes that can be written to `bytes`. If the
    /// source string is assembled successfully, the number of bytes written to `bytes` is written
    /// back to `size`.
    ///
    /// If there is no error, it returns true, otherwise it prints out the error explanation to
    /// standard output and return false.
    #[no_mangle]
    pub extern "C" fn rubble_file(path: *const c_char, bytes: *mut u8, size: *mut usize) -> bool {
        let c_str = unsafe { CStr::from_ptr(path) };
        let size_deref = unsafe { *size };
        let str = match c_str.to_str() {
            Ok(s) => s,
            Err(e) => {
                println!("[rubbler] {}", e);
                return false;
            }
        };
        match super::rubble_file(str) {
            Ok(b) => {
                for (i, byte) in b.iter().enumerate() {
                    if i == size_deref {
                        break;
                    }
                    unsafe { *bytes.add(i) = *byte }
                    unsafe { *size = i + 1 }
                }
                true
            }
            Err(e) => {
                println!("[rubbler] {}", e);
                false
            }
        }
    }

    /// Wraps the functionality of [crate::rubble_inst] inside a C interface.
    ///
    /// If there is no error, it returns true, otherwise it prints out the error explanation to
    /// standard output and return false.
    #[no_mangle]
    pub extern "C" fn rubble_inst(inst: *const c_char, result: *mut u32) -> bool {
        let c_str = unsafe { CStr::from_ptr(inst) };
        let str = match c_str.to_str() {
            Ok(s) => s,
            Err(e) => {
                println!("[rubbler] {}", e);
                return false;
            }
        };
        match super::rubble_inst(str) {
            Ok(code) => {
                unsafe { *result = code };
                true
            }
            Err(e) => {
                println!("[rubbler] {}", e);
                false
            }
        }
    }
}

/// Assemble the given source string and returns a vector of bytes of the assembled source string if
/// the source string is a valid (and supported) RISC-V assembly source code.
///
/// If there is an error when assembling the source string, it returns a string explaning the error.
///
/// # Examples
/// ```
/// let source = "lui t2, -3";
/// let expected_res = vec![0b10110111,0b11110011,0b11111111,0b11111111];
/// let res = rubbler::rubble(source).unwrap();
/// assert_eq!(res, expected_res);
/// ```
pub fn rubble(source: &str) -> Result<Vec<u8>, String> {
    let scanner = Scanner::new(source.to_string());
    let tokens = scanner.scan_tokens()?;
    let parser = Parser::new(tokens);
    let stmts = parser.parse()?;
    let analyzer = Analyzer::new(stmts);
    let (stmts, ctx) = analyzer.analyze()?;
    let mut generator = Generator::new(stmts, ctx);
    generator.generate_code()
}

/// Assemble the file specified in `path` and returns a vector of bytes of the assembled source
/// file if the source file is a valid (and supported) RISC-V assembly source code.
///
/// If there is an error when assembling the source file, it returns a string explaning the error.
///
/// # Examples
/// ```
/// use std::path::PathBuf;
///
/// let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
/// path.push("resources/test.asm");
/// let path = path.to_str().unwrap();
/// let expected_res = vec![0b10110111,0b11110011,0b11111111,0b11111111];
/// let res = rubbler::rubble_file(path).unwrap();
/// assert_eq!(res, expected_res);
/// ```
pub fn rubble_file(path: &str) -> Result<Vec<u8>, String> {
    let path = PathBuf::from(path);
    let source = fs::read_to_string(path).map_err(|e| e.to_string())?;
    rubble(&source)
}

/// Assemble the given instruction string and returns a `u32` value of the assembled instruction
/// string.
///
/// If the string contains multiple lines or is not a valid (and supported) RISC-V instruction,
/// it returns a string explaining the error.
///
/// # Examples
/// ```
/// let source = "lui t2, -3";
/// let expected_res: u32 = 0b11111111111111111111001110110111;
/// let res = rubbler::rubble_inst(source).unwrap();
/// assert_eq!(res, expected_res);
/// ```
pub fn rubble_inst(inst: &str) -> Result<u32, String> {
    // Grab lines and ensure it only contains one line
    let lines: Vec<&str> = inst.lines().collect();
    if lines.len() != 1 {
        return Err("Found multiple lines".to_string());
    }
    let code = rubble(lines[0])?;
    let mut result = 0;
    for (i, byte) in code.iter().enumerate() {
        result += (*byte as u32) << (8 * i);
    }
    Ok(result)
}

#[cfg(test)]
mod test {
    use std::path::PathBuf;

    fn u32_to_vecu8(value: u32) -> Vec<u8> {
        let mut bytes = vec![];
        for i in 0..4 {
            bytes.push((value >> (8 * i)) as u8)
        }
        bytes
    }

    #[test]
    fn rubble() {
        let source = "lui t2, -3";
        let expected_code: u32 = 0b11111111111111111111_00111_0110111;
        let code = super::rubble(source).unwrap();
        assert_eq!(code, u32_to_vecu8(expected_code));
    }
    #[test]
    fn rubble_inst() {
        let inst = "lui t2, -3";
        let expected_code: u32 = 0b11111111111111111111_00111_0110111;
        let code = super::rubble_inst(inst).unwrap();
        assert_eq!(code, expected_code);
    }
    #[test]
    fn rubble_path() {
        let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
        path.push("resources/test.asm");
        let path = path.to_str().unwrap();
        let expected_code: u32 = 0b11111111111111111111_00111_0110111;
        let code = super::rubble_file(path).unwrap();
        assert_eq!(code, u32_to_vecu8(expected_code));
    }
}