use clap::Args;
use scarf_parser::*;
use scarf_syntax::{Node, NodeID, Nodes};
use std::{
collections::HashSet,
ops::{Deref, DerefMut},
path::PathBuf,
};
#[derive(Args)]
pub struct ConstructsArgs {
paths: Vec<PathBuf>,
#[arg(short = 'I', long, value_name = "DIR_PATH")]
include: Vec<PathBuf>,
#[arg(short = 'D', long, value_name = "NAME[=VALUE]")]
define: Vec<String>,
#[arg(short = 'a', long, value_name = "CONSTRUCT")]
allow: Vec<String>,
#[arg(short = 'd', long, value_name = "CONSTRUCT")]
disallow: Vec<String>,
#[arg(long, value_name = "DIR_PATH")]
debug: Option<PathBuf>,
}
pub struct AllowedConstructs {
ids: HashSet<NodeID>,
}
impl Default for AllowedConstructs {
fn default() -> Self {
Self {
ids: HashSet::new(),
}
}
}
impl Deref for AllowedConstructs {
type Target = HashSet<NodeID>;
fn deref(&self) -> &Self::Target {
&self.ids
}
}
impl DerefMut for AllowedConstructs {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.ids
}
}
impl AllowedConstructs {
pub fn parse_allow(
&mut self,
allowed_constructs: &Vec<String>,
) -> Result<(), String> {
for construct in allowed_constructs {
let id = match NodeID::try_from_name(construct.as_str()) {
Some(id) => id,
None => {
return Err(format!(
"Error: {} (passed with `--allow`) isn't a valid SystemVerilog construct",
construct
));
}
};
if !self.ids.insert(id) {
return Err(format!(
"Error: {} (passed with `--allow`) was allowed multiple times",
construct
));
}
}
Ok(())
}
pub fn parse_disallow(
&mut self,
disallowed_constructs: &Vec<String>,
) -> Result<(), String> {
for construct in disallowed_constructs {
let id = match NodeID::try_from_name(construct.as_str()) {
Some(id) => id,
None => {
return Err(format!(
"Error: {} (passed with `--disallow`) isn't a valid SystemVerilog construct",
construct
));
}
};
if !self.ids.remove(&id) {
return Err(format!(
"Error: {} (passed with `--disallow`) wasn't allowed already",
construct
));
}
}
Ok(())
}
pub fn allowed(&self, node: &Node) -> bool {
self.ids.contains(&NodeID::from_node(node))
}
}
fn find_disallowed<'a, 'b>(
node: &Node<'a, 'b>,
allowed_constructs: &AllowedConstructs,
) -> Vec<(NodeID, Span<'a>)> {
if allowed_constructs.allowed(node) {
node.children()
.iter()
.map(|child_node| find_disallowed(child_node, allowed_constructs))
.flatten()
.collect()
} else {
vec![(NodeID::from_node(node), node.span())]
}
}
pub fn constructs(args: &ConstructsArgs) {
let string_cache = PreprocessorCache::new();
let mut state = PreprocessorState::new(
args.include
.iter()
.map(|pathbuf| pathbuf.as_path())
.collect(),
vec![],
);
let mut allowed_constructs = AllowedConstructs::default();
match allowed_constructs.parse_allow(&args.allow) {
Ok(()) => (),
Err(err) => {
println!("{}", err);
return;
}
};
match allowed_constructs.parse_disallow(&args.disallow) {
Ok(()) => (),
Err(err) => {
println!("{}", err);
return;
}
};
for path in &args.paths {
let src = match std::fs::read_to_string(&path) {
Ok(content) => content,
Err(err) => {
println! {"Error reading {}: {}", path.to_string_lossy(), err};
continue;
}
};
state.make_fresh(vec![]);
let (path, src) = state.retain_file(
path.to_string_lossy().to_string(),
src,
&string_cache,
);
let lexed_src = lex(src, path).process();
if let Some(debug_path) = &args.debug {
match lexed_src.dump(&debug_path) {
Ok(_) => (),
Err(err) => println!("{}", err),
}
}
let lex_errors = lexed_src.report_errors().collect::<Vec<_>>();
if !lex_errors.is_empty() {
for report in lex_errors {
report.print((path, Source::from(src))).unwrap()
}
return;
}
let token_stream = lexed_src.tokens();
let preprocess_result = preprocess(
&mut TokenIterator::new(token_stream.into_iter()),
&mut state,
&string_cache,
);
let mut error_sources = sources(state.included_files());
for warning in &state.warnings {
let report: Report<'_, (String, std::ops::Range<usize>)> =
warning.into();
report.print(&mut error_sources).unwrap();
}
let preprocessed_stream = match preprocess_result {
Err(err) => {
let report: Report<'_, (String, std::ops::Range<usize>)> =
(&err).into();
report.print(&mut error_sources).unwrap();
return;
}
Ok(preprocessed_stream) => preprocessed_stream,
};
let parsed_src = parse(&preprocessed_stream);
let source_text = match parsed_src {
Err(err) => {
println!("{:?}", err);
let report: Report<'_, (String, std::ops::Range<usize>)> =
err.report("P1");
report.print(&mut error_sources).unwrap();
continue;
}
Ok(source_text) => source_text,
};
let disallowed_nodes =
find_disallowed(&((&source_text).into()), &allowed_constructs);
if disallowed_nodes.is_empty() {
println!("No invalid constructs in {}", path);
} else {
for disallowed_node in disallowed_nodes {
println!(
"Found disallowed node: {}",
NodeID::into_name(disallowed_node.0)
)
}
}
}
}