verilog_filelist_parser/
file_parser.rs

1use regex::Regex;
2use std::collections::HashMap;
3use std::error::Error;
4use std::fs;
5use std::path::{Path, PathBuf};
6
7use crate::line_parser;
8use crate::line_parser::LineType;
9
10/// Represents a Verilog Filelist
11#[derive(PartialEq, Debug, Default)]
12pub struct Filelist {
13    /// List of all files
14    pub files: Vec<PathBuf>,
15    /// List of all Include Directories
16    pub incdirs: Vec<PathBuf>,
17    /// HashMap of all Defines
18    pub defines: HashMap<String, Option<String>>,
19    /// True if comments are present in the filelist
20    pub comments_present: bool,
21    /// True if unknown arguments are present in the filelist
22    pub unknowns_present: bool,
23}
24
25impl Filelist {
26    /// Returns an empty Filelist
27    pub fn new() -> Filelist {
28        Filelist {
29            files: Vec::new(),
30            incdirs: Vec::new(),
31            defines: HashMap::new(),
32            comments_present: false,
33            unknowns_present: false,
34        }
35    }
36
37    /// Adds the elements of the other filelist to the current filelist
38    pub fn extend(&mut self, other: Filelist) {
39        self.files.extend(other.files);
40        self.incdirs.extend(other.incdirs);
41        self.defines.extend(other.defines);
42        self.comments_present |= other.comments_present;
43        self.unknowns_present |= other.unknowns_present;
44    }
45}
46
47/// Parses a filelist file.
48///
49/// Environment variables represented with paranthesis or
50/// curly braces (i.e. `$()` or `${}`) will be automatically
51/// substituted.
52///
53/// # Arguments
54///
55/// * `path` - The path to the filelist
56///
57/// # Errors
58///
59/// Returns an error if the filelist in `path` cannot be read. Also returns
60/// error if any of the nested filelists cannot be read.
61pub fn parse_file(path: impl AsRef<Path>) -> Result<Filelist, Box<dyn Error>> {
62    let path = path.as_ref();
63    let contents = fs::read_to_string(path)?;
64
65    let mut filelist = Filelist::new();
66
67    for line in contents.lines() {
68        let line = replace_env_vars(&line);
69        match line_parser::parse_line(&line) {
70            LineType::File(file) => filelist.files.push(PathBuf::from(file)),
71            LineType::Define(define_map) => {
72                for (d, t) in define_map.into_iter() {
73                    match t {
74                        Some(text) => filelist
75                            .defines
76                            .insert(d.to_string(), Some(text.to_string())),
77                        None => filelist.defines.insert(d.to_string(), None),
78                    };
79                }
80            }
81            LineType::IncDir(incdirs) => {
82                for dir in incdirs {
83                    filelist.incdirs.push(PathBuf::from(dir));
84                }
85            }
86            LineType::Comment => filelist.comments_present = true,
87            LineType::Unknown => filelist.unknowns_present = true,
88            LineType::Empty => (),
89            LineType::Filelist(path) => {
90                filelist.extend(parse_file(path)?);
91            }
92        }
93    }
94    Ok(filelist)
95}
96
97fn replace_env_vars(line: &str) -> String {
98    let re_env_brace = Regex::new(r"\$\{(?P<env>[^}]+)\}").unwrap();
99    let re_env_paren = Regex::new(r"\$\((?P<env>[^)]+)\)").unwrap();
100
101    let mut expanded_line = String::from(line);
102    for caps in re_env_brace.captures_iter(&line) {
103        let env = &caps["env"];
104        if let Ok(env_var) = std::env::var(env) {
105            expanded_line = expanded_line.replace(&format!("${{{}}}", env), &env_var);
106        }
107    }
108    for caps in re_env_paren.captures_iter(&line) {
109        let env = &caps["env"];
110        if let Ok(env_var) = std::env::var(env) {
111            expanded_line = expanded_line.replace(&format!("$({})", env), &env_var);
112        }
113    }
114    expanded_line
115}