dalance / sv-parser

SystemVerilog parser library fully compliant with IEEE 1800-2017
Other
383 stars 49 forks source link

Get syntax tree before preprocessor #96

Open yousifBilal opened 4 months ago

yousifBilal commented 4 months ago

Hi,

First of, thank you for the amazing work.

I have a situation where I want capture compiler directives in a SystemVerilog file. Be it an include orifdef. Currently, I haven't found a clean way to do it. Here is what I tried, which I've taken mostly from existing code:

pub fn get_preprocessor_text<V: BuildHasher>(
    path: PathBuf,
    pre_defines: &Defines<V>,
) -> Result<(PreprocessorText, String), Error> {
    let f = File::open(path.clone()).map_err(|x| Error::File {
        source: x,
        path: path.clone(),
    })?;
    let mut reader = BufReader::new(f);
    let mut s = String::new();

    if let Err(_) = reader.read_to_string(&mut s) {
        return Err(Error::ReadUtf8(path));
    }
    let mut defines = HashMap::new();
    let sv_cov_pre_defines = [
        ("SV_COV_START", "0"),
        ("SV_COV_STOP", "1"),
        ("SV_COV_RESET", "2"),
        ("SV_COV_CHECK", "3"),
        ("SV_COV_MODULE", "10"),
        ("SV_COV_HIER", "11"),
        ("SV_COV_ASSERTION", "20"),
        ("SV_COV_FSM_STATE", "21"),
        ("SV_COV_STATEMENT", "22"),
        ("SV_COV_TOGGLE", "23"),
        ("SV_COV_OVERFLOW", "-2"),
        ("SV_COV_ERROR", "-1"),
        ("SV_COV_NOCOV", "0"),
        ("SV_COV_OK", "1"),
        ("SV_COV_PARTIAL", "2"),
    ];
    for (k, v) in sv_cov_pre_defines {
        let define = Define {
            identifier: k.to_string(),
            arguments: Vec::new(),
            text: Some(DefineText {
                text: v.to_string(),
                origin: None,
            }),
        };
        defines.insert(k.to_string(), Some(define));
    }
    for (k, v) in pre_defines {
        defines.insert(k.clone(), (*v).clone());
    }
    let span = Span::new_extra(&s, SpanInfo::default());
    let (_, pp_text) = all_consuming(pp_parser)(span).map_err(|x| match x {
        nom::Err::Incomplete(_) => Error::Preprocess(None),
        nom::Err::Error(e) => {
            if let Some(pos) = error_position(&e) {
                Error::Preprocess(Some((path, pos)))
            } else {
                Error::Preprocess(None)
            }
        }
        nom::Err::Failure(e) => {
            if let Some(pos) = error_position(&e) {
                Error::Preprocess(Some((path, pos)))
            } else {
                Error::Preprocess(None)
            }
        }
    })?;
    Ok((pp_text, s))
}

fn identifier(node: RefNode, s: &str) -> Option<String> {
    for x in node {
        match x {
            RefNode::SimpleIdentifier(x) => {
                let x: Locate = x.nodes.0.try_into().unwrap();
                return Some(String::from(x.str(s)));
            }
            RefNode::EscapedIdentifier(x) => {
                let x: Locate = x.nodes.0.try_into().unwrap();
                let x = x.str(s);
                let x = &x[1..]; // remove \
                return Some(String::from(x));
            }
            _ => (),
        }
    }
    None
}

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

    #[test]
    fn test_get_conditional_compiler_directives() {
        let path = "some_system_verilog_file_with_conditional_compiliation.sv";
        let (preprocessor_text, s) = get_preprocessor_text(path.into(), &HashMap::new()).unwrap();
        let mut conditional_compiler_directives = vec![];
        for n in preprocessor_text.into_iter().event() {
            match n {
                NodeEvent::Enter(RefNode::IfdefDirective(x)) => {
                    let (_, _, ref ifid, _, _, _, _, _) = x.nodes;
                    let ifid = identifier(ifid.into(), &s).unwrap();
                    conditional_compiler_directives.push(ifid);
                }
                NodeEvent::Enter(RefNode::IfndefDirective(x)) => {
                    let (_, _, ref ifid, _, _, _, _, _) = x.nodes;

                    let ifid = identifier(ifid.into(), &s).unwrap();
                    conditional_compiler_directives.push(ifid);
                }
                _ => (),
            }
        }
        assert!(conditional_compiler_directives.contains(&"MY_DEFINE".to_string()));
        assert!(conditional_compiler_directives.contains(&"MY_DEFINED".to_string()));
        assert!(conditional_compiler_directives.contains(&"MY_DEFINE_WITH_VALUE".to_string()));
    }
}

This works alright, but it requires me to bring nom into the client code, which I am hesitant about. I was wondering if there is a better way to do this, or are their any plans to do it in the future?