├── testcase ├── file1.sv ├── file2.sv ├── file3.sv ├── files2.f └── files.f ├── .gitignore ├── .github └── workflows │ └── rust.yml ├── Cargo.toml ├── CHANGELOG.md ├── README.md ├── src ├── lib.rs ├── file_parser.rs └── line_parser.rs ├── LICENSE └── tests └── integration_tests.rs /testcase/file1.sv: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /testcase/file2.sv: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /testcase/file3.sv: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /testcase/files2.f: -------------------------------------------------------------------------------- 1 | testcase/file4.sv 2 | +define+a=bad -------------------------------------------------------------------------------- /testcase/files.f: -------------------------------------------------------------------------------- 1 | testcase/file1.sv 2 | testcase/file2.sv 3 | testcase/file3.sv 4 | +incdir+testcase/ 5 | // Some comment 6 | +define+a=b+c=d+e=f 7 | +define+$(VAR1)=var1 8 | +define+${VAR2}=var2 9 | +define+RTL 10 | -f testcase/files2.f -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Build 13 | run: cargo build --verbose 14 | - name: Run tests 15 | run: cargo test --verbose 16 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "verilog-filelist-parser" 3 | version = "0.1.2" 4 | authors = ["Raamakrishnan "] 5 | edition = "2018" 6 | license = "MIT" 7 | description = "A library to parse a Verilog Filelist and return a list of files, include directories and defines" 8 | repository = "https://github.com/Raamakrishnan/verilog-filelist-parser" 9 | readme = "README.md" 10 | categories = ["parser-implementations", "parsing", "development-tools"] 11 | keywords = ["verilog", "systemverilog", "filelist"] 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [dependencies] 16 | regex = "1.0.0" -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | 9 | ## [0.1.2] - 2020-02-20 10 | ### Fixed 11 | - Empty lines will be ignored (PR #1) 12 | 13 | ## [0.1.1] - 2020-02-07 14 | ### Changed 15 | - Argument to `parse_file` is changed to `AsRef` 16 | 17 | ## [0.1.0] - 2020-02-02 18 | ### Added 19 | - Achieved Feature parity with parser implementation in [dalance/svlint](https://github.com/dalance/svlint/) 20 | - Parse files, include directories and defines 21 | - Support environment variables -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Verilog Filelist Parser 2 | 3 | A library in Rust to parse a Verilog Filelist and return 4 | a list of files, include directories and defines. 5 | 6 | Environment variables represented with paranthesis or 7 | curly braces (i.e. `$()` or `${}`) will be automatically 8 | substituted. 9 | 10 | # Example 11 | ```rust 12 | use verilog_filelist_parser; 13 | let filelist = verilog_filelist_parser::parse_file("testcase/files.f") 14 | .expect("Cannot read filelist"); 15 | for file in filelist.files { 16 | println!("{:?}", file); 17 | } 18 | for incdir in filelist.incdirs { 19 | println!("{:?}", incdir); 20 | } 21 | for (d, t) in filelist.defines { 22 | match t { 23 | None => println!("{:?}", d), 24 | Some(te) => println!("{:?}={:?}", d, te), 25 | }; 26 | } 27 | ``` 28 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Verilog Filelist Parser 2 | //! 3 | //! A library to parse a Verilog Filelist and return 4 | //! a list of files, include directories and defines. 5 | //! 6 | //! Environment variables represented with paranthesis or 7 | //! curly braces (i.e. `$()` or `${}`) will be automatically 8 | //! substituted. 9 | //! 10 | //! # Example 11 | //! ``` 12 | //! use verilog_filelist_parser; 13 | //! let filelist = verilog_filelist_parser::parse_file("testcase/files.f") 14 | //! .expect("Cannot read filelist"); 15 | //! for file in filelist.files { 16 | //! println!("{:?}", file); 17 | //! } 18 | //! for incdir in filelist.incdirs { 19 | //! println!("{:?}", incdir); 20 | //! } 21 | //! for (d, t) in filelist.defines { 22 | //! match t { 23 | //! None => println!("{:?}", d), 24 | //! Some(te) => println!("{:?}={:?}", d, te), 25 | //! }; 26 | //! } 27 | //! ``` 28 | 29 | mod file_parser; 30 | mod line_parser; 31 | 32 | pub use file_parser::parse_file; 33 | pub use file_parser::Filelist; 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [year] [fullname] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/integration_tests.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::path::PathBuf; 3 | use verilog_filelist_parser; 4 | 5 | #[test] 6 | fn simple_test() { 7 | let mut defines = HashMap::new(); 8 | defines.insert("a".to_string(), Some("bad".to_string())); 9 | defines.insert("e".to_string(), Some("f".to_string())); 10 | defines.insert("c".to_string(), Some("d".to_string())); 11 | defines.insert("ENV_VAR1".to_string(), Some("var1".to_string())); 12 | defines.insert("ENV_VAR2".to_string(), Some("var2".to_string())); 13 | defines.insert("RTL".to_string(), None); 14 | 15 | let filelist_exp = verilog_filelist_parser::Filelist { 16 | files: vec![ 17 | PathBuf::from("testcase/file1.sv"), 18 | PathBuf::from("testcase/file2.sv"), 19 | PathBuf::from("testcase/file3.sv"), 20 | PathBuf::from("testcase/file4.sv"), 21 | ], 22 | incdirs: vec![PathBuf::from("testcase/")], 23 | defines: defines, 24 | comments_present: true, 25 | unknowns_present: false, 26 | }; 27 | 28 | // Add env vars 29 | std::env::set_var("VAR1", "ENV_VAR1"); 30 | std::env::set_var("VAR2", "ENV_VAR2"); 31 | 32 | let filelist = verilog_filelist_parser::parse_file("testcase/files.f").expect("Error parsing"); 33 | assert_eq!(filelist_exp, filelist); 34 | } 35 | -------------------------------------------------------------------------------- /src/file_parser.rs: -------------------------------------------------------------------------------- 1 | use regex::Regex; 2 | use std::collections::HashMap; 3 | use std::error::Error; 4 | use std::fs; 5 | use std::path::{Path, PathBuf}; 6 | 7 | use crate::line_parser; 8 | use crate::line_parser::LineType; 9 | 10 | /// Represents a Verilog Filelist 11 | #[derive(PartialEq, Debug, Default)] 12 | pub struct Filelist { 13 | /// List of all files 14 | pub files: Vec, 15 | /// List of all Include Directories 16 | pub incdirs: Vec, 17 | /// HashMap of all Defines 18 | pub defines: HashMap>, 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 | 25 | impl 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. 61 | pub fn parse_file(path: impl AsRef) -> Result> { 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 | 97 | fn replace_env_vars(line: &str) -> String { 98 | let re_env_brace = Regex::new(r"\$\{(?P[^}]+)\}").unwrap(); 99 | let re_env_paren = Regex::new(r"\$\((?P[^)]+)\)").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 | } 116 | -------------------------------------------------------------------------------- /src/line_parser.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | #[derive(PartialEq, Debug)] 4 | pub enum LineType<'a> { 5 | File(&'a str), 6 | IncDir(Vec<&'a str>), 7 | Define(HashMap<&'a str, Option<&'a str>>), 8 | Filelist(&'a str), 9 | Comment, 10 | Unknown, 11 | Empty, 12 | } 13 | 14 | pub fn parse_line(line: &str) -> LineType { 15 | let line = line.trim(); 16 | if line.starts_with("-f ") { 17 | let filelist_name = line.trim_start_matches("-f "); 18 | LineType::Filelist(filelist_name) 19 | } else if line.starts_with("+define+") { 20 | // remove +define+ from start and "+" from end 21 | let defines = line.trim_start_matches("+define+").trim_end_matches('+'); 22 | let mut define_map = HashMap::new(); 23 | for define in defines.split('+') { 24 | if let Some(pos) = define.find('=') { 25 | let (d, t) = define.split_at(pos); 26 | define_map.insert(d, Some(&t[1..])); 27 | } else { 28 | define_map.insert(define, None); 29 | } 30 | } 31 | LineType::Define(define_map) 32 | } else if line.starts_with("+incdir+") { 33 | // remove +incdir+ from start and "+" from end 34 | let incdirs = line.trim_start_matches("+incdir+").trim_end_matches('+'); 35 | let incdir_vec: Vec<&str> = incdirs.split('+').collect(); 36 | LineType::IncDir(incdir_vec) 37 | } else if line.starts_with("//") { 38 | LineType::Comment 39 | } else if line.starts_with('-') || line.starts_with('+') { 40 | LineType::Unknown 41 | } else if line.is_empty() { 42 | LineType::Empty 43 | } else { 44 | // Mark everything else as a File 45 | LineType::File(line) 46 | } 47 | } 48 | 49 | #[cfg(test)] 50 | mod test { 51 | use super::*; 52 | 53 | #[test] 54 | fn parse_line_filelist() { 55 | let line = "-f sample/files.f\n"; 56 | assert_eq!(parse_line(line), LineType::Filelist("sample/files.f")); 57 | } 58 | 59 | #[test] 60 | fn parse_line_define_single() { 61 | let line = "+define+CONST1=const1=23+\n"; 62 | let mut define_map = HashMap::new(); 63 | define_map.insert("CONST1", Some("const1=23")); 64 | assert_eq!(parse_line(line), LineType::Define(define_map)); 65 | } 66 | 67 | #[test] 68 | fn parse_line_define_multiple() { 69 | let line = "+define+CONST1=const1+CONST2=const2+CONST3=const3=1+CONST4+CONST5+\n"; 70 | let mut define_map = HashMap::new(); 71 | define_map.insert("CONST1", Some("const1")); 72 | define_map.insert("CONST2", Some("const2")); 73 | define_map.insert("CONST3", Some("const3=1")); 74 | define_map.insert("CONST4", None); 75 | define_map.insert("CONST5", None); 76 | assert_eq!(parse_line(line), LineType::Define(define_map)); 77 | } 78 | 79 | #[test] 80 | fn parse_line_incdir_single() { 81 | let line = "+incdir+../sample_dir1/sample_dir2\n"; 82 | let incdir_vec = vec!["../sample_dir1/sample_dir2"]; 83 | assert_eq!(parse_line(line), LineType::IncDir(incdir_vec)); 84 | } 85 | 86 | #[test] 87 | fn parse_line_incdir_multiple() { 88 | let line = "+incdir+../sample_dir1/sample_dir2+../sample_dir2/sample_dir3+sample_dir4/sample_dir5+\n"; 89 | let incdir_vec = vec![ 90 | "../sample_dir1/sample_dir2", 91 | "../sample_dir2/sample_dir3", 92 | "sample_dir4/sample_dir5", 93 | ]; 94 | assert_eq!(parse_line(line), LineType::IncDir(incdir_vec)); 95 | } 96 | 97 | #[test] 98 | fn parse_line_comment() { 99 | let line = "//random_comment"; 100 | assert_eq!(parse_line(line), LineType::Comment); 101 | } 102 | 103 | #[test] 104 | fn parse_line_unknown_hyphen() { 105 | let line = "-funcmd"; 106 | assert_eq!(parse_line(line), LineType::Unknown); 107 | } 108 | 109 | #[test] 110 | fn parse_line_unknown_plus() { 111 | let line = "+funcmd"; 112 | assert_eq!(parse_line(line), LineType::Unknown); 113 | } 114 | 115 | #[test] 116 | fn parse_line_empty() { 117 | let line = ""; 118 | assert_eq!(parse_line(line), LineType::Empty); 119 | let line = " "; 120 | assert_eq!(parse_line(line), LineType::Empty); 121 | let line = "\t"; 122 | assert_eq!(parse_line(line), LineType::Empty); 123 | } 124 | 125 | #[test] 126 | fn parse_line_file() { 127 | let line = "any_random_line_is_a_file"; 128 | assert_eq!( 129 | parse_line(line), 130 | LineType::File("any_random_line_is_a_file") 131 | ); 132 | } 133 | } 134 | --------------------------------------------------------------------------------