use std::{env, path::PathBuf};
use tracing::{error, warn};
use crate::util::path::maybe_canonicalize;
pub fn tokenize(cli: &str) -> Vec<String> {
let mut out = Vec::new();
let mut chars = cli.chars().peekable();
loop {
while let Some(' ') | Some('\t') = chars.peek() {
chars.next();
}
if chars.peek().is_none() {
break;
}
let mut arg = String::new();
let mut in_quotes = false;
loop {
match chars.peek() {
Some('\\') => {
chars.next();
}
Some('"') => {
chars.next();
in_quotes = !in_quotes;
}
Some(c) if c.is_ascii_whitespace() && !in_quotes => break,
None => break,
_ => (),
}
if let Some(c) = chars.next() {
arg.push(c);
}
}
if arg.is_empty() {
continue;
}
out.push(arg);
}
out
}
#[derive(Debug)]
pub enum BscBackend {
Bluesim,
Verilog,
}
#[derive(Debug, Default)]
pub struct BscArgs {
pub compiler: Option<PathBuf>,
pub bluespec_dir: Option<PathBuf>,
pub include_dirs: Vec<PathBuf>,
pub bdir: Option<PathBuf>,
pub defines: Vec<(String, String)>,
pub cpp: bool,
pub vpp: bool,
pub top: Option<String>,
pub backend: Option<BscBackend>,
pub file: Option<PathBuf>,
pub recursive: bool,
pub use_prelude: bool,
}
impl BscArgs {
pub fn include_dirs_all(&self) -> impl Iterator<Item = &PathBuf> {
self.bluespec_dir.iter().chain(self.include_dirs.iter())
}
}
pub fn parse_args(
mut args: impl Iterator<Item = String>,
bluespec_dir: Option<PathBuf>,
) -> Option<BscArgs> {
let mut out = BscArgs {
compiler: None,
bluespec_dir,
include_dirs: Vec::new(),
bdir: None,
defines: Vec::new(),
cpp: false,
vpp: true,
top: None,
backend: None,
file: None,
recursive: false,
use_prelude: true,
};
if let Some(bluespec_dir) = &out.bluespec_dir {
let bsc_libraries_path = bluespec_dir.join("Libraries/");
if bsc_libraries_path.is_dir() {
out.include_dirs.push(bluespec_dir.join("Libraries/"));
} else {
warn!(
"computed Bluespec library dir '{}' does not exist",
bsc_libraries_path.display()
);
}
}
loop {
let Some(arg) = args.next() else {
break;
};
if arg == "+RTS" {
loop {
match args.next().as_deref() {
Some("-RTS") | None => break,
_ => (),
}
}
continue;
}
if !arg.starts_with('-') {
if out.file.is_some() {
error!("multiple source file paths");
}
out.file = Some(arg.into());
continue;
}
let arg = if let Some(arg) = arg.strip_prefix("--") {
arg
} else {
&arg[1..]
};
let (arg, inverted) = if let Some(arg) = arg.strip_prefix("no-") {
(&arg[3..], true)
} else {
(arg, false)
};
match arg {
"bdir" => {
let Some(arg) = args.next() else {
error!("'-bdir' is missing an argument");
continue;
};
out.bdir = Some(PathBuf::from(arg));
}
"cpp" => {
out.cpp = !inverted;
}
"vpp" => {
out.vpp = !inverted;
}
"D" => {
let Some(arg) = args.next() else {
error!("'-D' is missing an argument");
continue;
};
let (name, value) = match arg.split_once('=') {
Some((name, value)) => (name.into(), value.into()),
None => (arg, String::new()),
};
out.defines.push((name, value));
}
"e" => {
return None;
}
"g" => {
let Some(arg) = args.next() else {
error!("'-g' is missing an argument");
continue;
};
out.top = Some(arg);
}
"i" => {
let Some(arg) = args.next() else {
error!("'-i' is missing an argument");
continue;
};
out.bluespec_dir = Some(arg.into());
}
"p" => {
let Some(arg) = args.next() else {
error!("'-p' is missing an argument");
continue;
};
let old = std::mem::take(&mut out.include_dirs);
for path in arg.split(':') {
match path {
"%" => {
if let Some(dir) = &out.bluespec_dir {
out.include_dirs.push(dir.clone());
}
}
"+" => {
out.include_dirs.extend(old.iter().cloned());
}
"" => (),
path => out.include_dirs.push(path.into()),
}
}
}
"sim" | "systemc" => {
out.backend = Some(BscBackend::Bluesim);
}
"vsim" => {
let Some(_) = args.next() else {
error!("'-vsim' is missing an argument");
continue;
};
out.backend = Some(BscBackend::Verilog);
}
"u" => {
out.recursive = !inverted;
}
"use-prelude" => {
out.use_prelude = !inverted;
}
"demote-errors"
| "expand-ATS-limit"
| "fdir"
| "final-cleanup"
| "I"
| "info-dir"
| "L"
| "l"
| "max-tcheck-stack-depth"
| "o"
| "parallel-sim-link"
| "promote-warnings"
| "reset-prefix"
| "simdir"
| "steps"
| "steps-warn-interval"
| "steps-max-intervals"
| "suppress-warnings"
| "unspecified-to"
| "vdir"
| "verilog-filter"
| "vsearch"
| "Xc"
| "Xc++"
| "Xcpp"
| "Xl"
| "Xv" => {
args.next();
}
"show-rule-rel" => {
args.next();
args.next();
}
_ => (),
}
}
Some(out)
}
pub fn parse_compdb(mut args: impl Iterator<Item = String>, cwd: PathBuf) -> Option<BscArgs> {
let Some(compiler) = args.next() else {
error!("entry has no args");
return None;
};
let compiler = PathBuf::from(compiler);
let compiler = match which::which_in(compiler, env::var_os("PATH"), &cwd) {
Ok(compiler) => Some(maybe_canonicalize(&compiler)),
Err(e) => {
error!("could not resolve compiler path: {e}");
None
}
};
let bluespec_dir = match &compiler {
Some(bsc) => {
if !bsc.ends_with("bin/bsc") {
warn!("bsc path has unusual structure (does not end with 'bin/bsc')");
return None;
}
let bsc_install_path = bsc.parent()?.parent()?;
let bsc_lib_path = bsc_install_path.join("lib/");
if !bsc_lib_path.is_dir() {
warn!(
"computed BLUESPECDIR '{}' does not exist",
bsc_lib_path.display()
);
return None;
}
Some(maybe_canonicalize(&bsc_lib_path))
}
None => None,
};
let mut args = parse_args(args, bluespec_dir)?;
args.compiler = compiler;
for path in &mut args.include_dirs {
*path = maybe_canonicalize(&cwd.join(&path));
}
if let Some(bdir) = &mut args.bdir {
*bdir = maybe_canonicalize(&cwd.join(&bdir));
}
if let Some(file) = &mut args.file {
*file = maybe_canonicalize(&cwd.join(&file));
}
Some(args)
}
#[cfg(test)]
mod test {
#[test]
fn cli_tokenize() {
let cli = r#"foo bar ba\ z "b a a \" a a r""#;
let args = super::tokenize(cli);
assert_eq!(args, ["foo", "bar", "ba z", r#"b a a " a a r"#]);
}
}