use std::path::{Path, PathBuf};
use crate::dpi;
use crate::sources::SourceSet;
use crate::timing_options::TimingOptions;
const MAX_LIST_DEPTH: usize = 32;
#[derive(Debug, Default)]
pub struct RunArgs {
pub sources: SourceSet,
pub plusargs: Vec<String>,
pub libs: Vec<PathBuf>,
pub c_sources: Vec<PathBuf>,
pub timing: TimingOptions,
pub vpi_libs: Vec<String>,
pub driver_options: Vec<String>,
pub x_initial_zero: bool,
pub coverage: bool,
pub seed: Option<u64>,
}
pub const COMPAT_FLAGS: &[&str] = &[
"--allow-use-before-declare",
"--relax-enum-conversions",
"--allow-hierarchical-const",
];
struct Arg {
text: String,
base: Option<PathBuf>,
}
impl Arg {
fn path(&self, p: &str) -> PathBuf {
match &self.base {
Some(b) if Path::new(p).is_relative() => b.join(p),
_ => PathBuf::from(p),
}
}
fn path_string(&self, p: &str) -> String {
self.path(p).to_string_lossy().into_owned()
}
}
fn flatten(args: &[Arg], depth: usize, out: &mut Vec<Arg>) -> Result<(), String> {
let mut it = args.iter();
while let Some(a) = it.next() {
let relative = match a.text.as_str() {
"-f" => false,
"-F" => true,
_ => {
out.push(Arg {
text: a.text.clone(),
base: a.base.clone(),
});
continue;
}
};
let Some(file) = it.next() else {
return Err(format!("{} needs a list file argument", a.text));
};
if depth >= MAX_LIST_DEPTH {
return Err(format!(
"list files nest more than {MAX_LIST_DEPTH} deep (`{}` includes itself?)",
file.text
));
}
let path = file.path(&file.text);
let text = std::fs::read_to_string(&path)
.map_err(|e| format!("cannot read the list file {}: {e}", path.display()))?;
let base = relative.then(|| path.parent().unwrap_or(Path::new("")).to_path_buf());
let inner: Vec<Arg> = tokenize(&text)
.map_err(|e| format!("{}: {e}", path.display()))?
.into_iter()
.map(|text| Arg {
text,
base: base.clone(),
})
.collect();
flatten(&inner, depth + 1, out)?;
}
Ok(())
}
fn tokenize(text: &str) -> Result<Vec<String>, String> {
let mut out = Vec::new();
let mut cur = String::new();
let mut quoted = false;
let mut started = false;
let mut chars = text.chars().peekable();
while let Some(c) = chars.next() {
if quoted {
if c == '"' {
quoted = false;
} else {
cur.push(c);
}
continue;
}
match c {
'"' => {
quoted = true;
started = true;
}
'#' => {
for c in chars.by_ref() {
if c == '\n' {
break;
}
}
if started {
out.push(std::mem::take(&mut cur));
started = false;
}
}
'/' if chars.peek() == Some(&'/') => {
for c in chars.by_ref() {
if c == '\n' {
break;
}
}
if started {
out.push(std::mem::take(&mut cur));
started = false;
}
}
'/' if chars.peek() == Some(&'*') => {
chars.next();
let mut prev = ' ';
let mut closed = false;
for c in chars.by_ref() {
if prev == '*' && c == '/' {
closed = true;
break;
}
prev = c;
}
if !closed {
return Err("unterminated /* comment".to_string());
}
if started {
out.push(std::mem::take(&mut cur));
started = false;
}
}
c if c.is_whitespace() => {
if started {
out.push(std::mem::take(&mut cur));
started = false;
}
}
c => {
cur.push(c);
started = true;
}
}
}
if quoted {
return Err("unterminated \" quote".to_string());
}
if started {
out.push(cur);
}
Ok(out)
}
fn option_value<'a>(
a: &'a str,
short: &str,
it: &mut std::slice::Iter<'a, Arg>,
) -> Result<Option<&'a str>, String> {
if a == short {
return match it.next() {
Some(v) => Ok(Some(v.text.as_str())),
None => Err(format!("{short} needs an argument")),
};
}
Ok(a.strip_prefix(short).filter(|v| !v.is_empty()))
}
#[derive(Debug)]
pub struct BuildAotArgs {
pub run: RunArgs,
pub out: PathBuf,
pub runtime_lib: Option<PathBuf>,
pub exe: bool,
}
pub fn parse_build_aot_args(args: &[String]) -> Result<BuildAotArgs, String> {
let mut out = None;
let mut runtime_lib = None;
let mut exe = false;
let mut rest = Vec::new();
let mut it = args.iter();
while let Some(a) = it.next() {
match a.as_str() {
"-o" | "--out" => match it.next() {
Some(v) => out = Some(PathBuf::from(v)),
None => return Err(format!("{a} needs an output path")),
},
"--runtime-lib" => match it.next() {
Some(v) => runtime_lib = Some(PathBuf::from(v)),
None => return Err("--runtime-lib needs a library path".to_string()),
},
"--exe" => exe = true,
_ => rest.push(a.clone()),
}
}
let out = out.ok_or("build-aot needs `-o OUT.so`")?;
Ok(BuildAotArgs {
run: parse_run_args(&rest)?,
out,
runtime_lib,
exe,
})
}
pub fn parse_run_args(args: &[String]) -> Result<RunArgs, String> {
let given: Vec<Arg> = args
.iter()
.map(|a| Arg {
text: a.clone(),
base: None,
})
.collect();
let mut flat = Vec::new();
flatten(&given, 0, &mut flat)?;
let mut run = RunArgs::default();
let mut it = flat.iter();
while let Some(arg) = it.next() {
let a = arg.text.as_str();
if let Some(dirs) = a.strip_prefix("+incdir+") {
for d in dirs.split('+').filter(|d| !d.is_empty()) {
run.sources.include_dirs.push(arg.path_string(d));
}
} else if let Some(defs) = a.strip_prefix("+define+") {
for d in defs.split('+').filter(|d| !d.is_empty()) {
crate::sources::define_line(d)?;
run.sources.defines.push(d.to_string());
}
} else if let Some(list) = a.strip_prefix("+vpi=") {
run.plusargs.push(a.to_string());
for lib in list.split(',').filter(|l| !l.is_empty()) {
run.vpi_libs.push(arg.path_string(lib));
}
} else if a.starts_with('+') {
run.plusargs.push(a.to_string());
} else if a == "--vpi" || a == "-vpi" {
match it.next() {
Some(lib) => run.vpi_libs.push(lib.path_string(&lib.text)),
None => return Err(format!("{a} needs a library argument")),
}
} else if let Some(over) = a.strip_prefix("-G").filter(|o| o.contains('=')) {
run.driver_options
.extend(["-G".to_string(), over.to_string()]);
} else if a == "-G" {
match it.next() {
Some(over) if over.text.contains('=') => run
.driver_options
.extend(["-G".to_string(), over.text.clone()]),
_ => return Err("-G needs NAME=VALUE".to_string()),
}
} else if COMPAT_FLAGS.contains(&a) {
run.driver_options.push(a.to_string());
} else if let Some(ts) =
option_value(a, "--timescale", &mut it)?.map(|v| v.trim_start_matches('='))
{
run.driver_options
.extend(["--timescale".to_string(), ts.to_string()]);
} else if a == "--compat" {
match it.next().map(|m| m.text.as_str()) {
Some(mode @ ("vcs" | "all")) => {
run.driver_options.extend([a.to_string(), mode.to_string()])
}
_ => return Err("--compat needs `vcs` or `all`".to_string()),
}
} else if a == "--coverage" {
run.coverage = true;
} else if a == "--x-initial" {
match it.next().map(|m| m.text.as_str()) {
Some("zero" | "0") => run.x_initial_zero = true,
Some("x") => run.x_initial_zero = false,
_ => return Err("--x-initial needs `x` (the LRM default) or `zero`".to_string()),
}
} else if a == "--seed" || a.starts_with("--seed=") {
let text = match a.strip_prefix("--seed=") {
Some(v) => Some(v),
None => it.next().map(|m| m.text.as_str()),
};
match text.and_then(|t| t.parse::<u64>().ok()) {
Some(n) => run.seed = Some(n),
None => return Err("--seed needs a non-negative integer".to_string()),
}
} else if a == "--sv-lib" || a == "-sv_lib" {
match it.next() {
Some(lib) => run.libs.push(dpi::sv_lib_path(&lib.text)),
None => return Err(format!("{a} needs a library argument")),
}
} else if a == "--top" || a == "-top" {
match it.next() {
Some(t) => run.sources.top_modules.push(t.text.clone()),
None => return Err(format!("{a} needs a module name")),
}
} else if let Some(t) = a.strip_prefix("--top=") {
run.sources.top_modules.push(t.to_string());
} else if a == "--libmap" || a == "-libmap" {
match it.next() {
Some(m) => run.sources.library_maps.push(arg.path_string(&m.text)),
None => return Err(format!("{a} needs a library map file")),
}
} else if let Some(m) = a.strip_prefix("--libmap=") {
run.sources.library_maps.push(arg.path_string(m));
} else if let Some(lib) = option_value(a, "-L", &mut it)? {
run.sources.liblist.push(lib.to_string());
} else if let Some(dir) = option_value(a, "-I", &mut it)? {
run.sources.include_dirs.push(arg.path_string(dir));
} else if let Some(def) = option_value(a, "-D", &mut it)? {
crate::sources::define_line(def)?;
run.sources.defines.push(def.to_string());
} else if run.timing.parse_arg(a)? {
} else if a.starts_with('-') {
return Err(format!("unrecognized argument `{a}`"));
} else if dpi::is_c_source(Path::new(a)) {
run.c_sources.push(arg.path(a));
} else if a.ends_with(".so") {
run.libs.push(arg.path(a));
} else {
run.sources.files.push(arg.path(a));
}
}
if run.sources.files.is_empty() {
return Err("no source file given".to_string());
}
Ok(run)
}