rust-code-analysis 0.0.25

Tool to compute and export code metrics
Documentation
use std::collections::HashSet;

use crate::c_langs_macros::is_predefined_macros;

const DOLLARS: [u8; 2048] = [b'$'; 2048];

#[inline(always)]
fn is_identifier_part(c: u8) -> bool {
    (b'A'..=b'Z').contains(&c)
        || (b'a'..=b'z').contains(&c)
        || (b'0'..=b'9').contains(&c)
        || c == b'_'
}

#[inline(always)]
fn is_identifier_starter(c: u8) -> bool {
    (b'A'..=b'Z').contains(&c) || (b'a'..=b'z').contains(&c) || c == b'_'
}

#[inline(always)]
fn is_macro<S: ::std::hash::BuildHasher>(mac: &str, macros: &HashSet<String, S>) -> bool {
    macros.contains(mac) | is_predefined_macros(mac)
}

pub fn replace<S: ::std::hash::BuildHasher>(
    code: &[u8],
    macros: &HashSet<String, S>,
) -> Option<Vec<u8>> {
    let mut new_code = Vec::with_capacity(code.len());
    let mut code_start = 0;
    let mut k_start = 0;

    for (i, c) in code.iter().enumerate() {
        if k_start != 0 {
            if !is_identifier_part(*c) {
                let start = k_start - 1;
                k_start = 0;
                let keyword = String::from_utf8(code[start..i].to_vec()).unwrap();
                if is_macro(&keyword, macros) {
                    new_code.extend(&code[code_start..start]);
                    new_code.extend(&DOLLARS[..(i - start)]);
                    code_start = i;
                }
            }
        } else if is_identifier_starter(*c) {
            k_start = i + 1;
        }
    }

    if k_start != 0 {
        let start = k_start - 1;
        let i = code.len();
        let keyword = String::from_utf8(code[start..].to_vec()).unwrap();
        if is_macro(&keyword, macros) {
            new_code.extend(&code[code_start..start]);
            new_code.extend(&DOLLARS[..(i - start)]);
            code_start = i;
        }
    }

    if code_start == 0 {
        None
    } else {
        if code_start < code.len() {
            new_code.extend(&code[code_start..]);
        }
        Some(new_code)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_replace() {
        let mut mac = HashSet::new();
        mac.insert("abc".to_string());

        assert!(replace(b"def ghi jkl", &mac).is_none());
        assert_eq!(
            b"$$$ def ghi jkl".to_vec(),
            replace(b"abc def ghi jkl", &mac).unwrap()
        );
        assert_eq!(
            b"def $$$ ghi jkl".to_vec(),
            replace(b"def abc ghi jkl", &mac).unwrap()
        );
        assert_eq!(
            b"def ghi $$$ jkl".to_vec(),
            replace(b"def ghi abc jkl", &mac).unwrap()
        );
        assert_eq!(
            b"def ghi jkl $$$".to_vec(),
            replace(b"def ghi jkl abc", &mac).unwrap()
        );

        mac.insert("z9_".to_string());
        assert_eq!(
            b"$$$ def ghi $$$ jkl".to_vec(),
            replace(b"abc def ghi z9_ jkl", &mac).unwrap()
        );
    }
}