use std::path::{Path, PathBuf};
use vj_codegen::frontend::{UnitOptions, compile_units};
use crate::run::RunError;
#[derive(Debug, Clone, Default)]
pub struct SourceSet {
pub files: Vec<PathBuf>,
pub include_dirs: Vec<String>,
pub defines: Vec<String>,
pub top_modules: Vec<String>,
pub library_maps: Vec<String>,
pub liblist: Vec<String>,
}
impl SourceSet {
pub fn single(path: &Path) -> Self {
SourceSet {
files: vec![path.to_path_buf()],
..SourceSet::default()
}
}
pub(crate) fn label(&self) -> &Path {
self.files.first().map_or(Path::new("<no source>"), |p| p)
}
fn is_plain(&self) -> bool {
self.files.len() == 1
&& self.include_dirs.is_empty()
&& self.defines.is_empty()
&& self.top_modules.is_empty()
&& self.library_maps.is_empty()
&& self.liblist.is_empty()
}
}
pub fn define_line(define: &str) -> Result<String, String> {
let (name, value) = define.split_once('=').unwrap_or((define, "1"));
let ident = !name.is_empty()
&& name.chars().enumerate().all(|(i, c)| {
c == '_' || c.is_ascii_alphabetic() || (i > 0 && (c.is_ascii_digit() || c == '$'))
});
if !ident {
return Err(format!(
"`{define}` does not define a macro: `{name}` is not an identifier"
));
}
if value.contains(['\n', '\r']) {
return Err(format!("the value of macro `{name}` contains a line break"));
}
Ok(format!("`define {name} {value}\n"))
}
fn with_defines(src: &str, name: &str, defines: &[String]) -> Result<String, String> {
if defines.is_empty() {
return Ok(src.to_string());
}
let mut text = String::new();
for d in defines {
text.push_str(&define_line(d)?);
}
let quoted = name.replace('\\', "\\\\").replace('"', "\\\"");
text.push_str(&format!("`line 1 \"{quoted}\" 0\n"));
text.push_str(src);
Ok(text)
}
pub(crate) fn compile(
set: &SourceSet,
) -> Result<(sv_lang::Design, Vec<vj_codegen::vpi_systf::SysTfDecl>), RunError> {
use vj_sim::process::cfg;
if set.files.is_empty() {
return Err(RunError::Diagnostics("no source files".to_string()));
}
if set.is_plain() {
let path = &set.files[0];
let src = std::fs::read_to_string(path).map_err(RunError::Io)?;
let src =
crate::randseq::expand(&src, &path.to_string_lossy()).map_err(RunError::Diagnostics)?;
let (src, systf_decls, systf_codegen) = crate::vpi::prepare_systf(&src);
cfg::set_source_text(Some(&src));
cfg::set_source_name(Some(&path.display().to_string()));
let design = vj_codegen::frontend::compile_source_named_with_systfs(
&src,
&path.to_string_lossy(),
&systf_decls,
)
.map_err(RunError::Compile)?;
return Ok((design, systf_codegen));
}
if !set.library_maps.is_empty() || !set.liblist.is_empty() {
return compile_with_libraries(set);
}
let mut units: Vec<(String, String, String)> = Vec::new();
let mut seen: Vec<PathBuf> = Vec::new();
let mut decls: Vec<vj_codegen::frontend::SystfDeclaration> = Vec::new();
let mut called: Vec<String> = Vec::new();
let mut codegen = Vec::new();
for path in &set.files {
let key = std::fs::canonicalize(path).unwrap_or_else(|_| path.clone());
if seen.contains(&key) {
continue;
}
seen.push(key);
let name = path.to_string_lossy().into_owned();
let src = std::fs::read_to_string(path)
.map_err(|e| RunError::Io(std::io::Error::new(e.kind(), format!("{name}: {e}"))))?;
let text = with_defines(&src, &name, &set.defines).map_err(RunError::Diagnostics)?;
let text = crate::randseq::expand_with_includes(&text, &name, &set.include_dirs)
.map_err(RunError::Diagnostics)?;
let (text, file_decls, file_codegen) = crate::vpi::prepare_systf(&text);
for d in file_decls {
let calls = crate::vpi::systf::is_called(&text, &d.0);
match decls.iter().position(|e| e.0 == d.0) {
None => decls.push(d.clone()),
Some(at) if calls && !called.contains(&d.0) => decls[at] = d.clone(),
Some(_) => {}
}
if calls && !called.contains(&d.0) {
called.push(d.0);
}
}
codegen = file_codegen;
units.push((name.clone(), name, text));
}
cfg::set_source_texts(
units
.iter()
.map(|(name, _, text)| (name.clone(), text.clone()))
.collect(),
);
cfg::set_source_name(Some(&units[0].0));
let options = UnitOptions {
include_dirs: set.include_dirs.clone(),
top_modules: set.top_modules.clone(),
..UnitOptions::default()
};
vj_codegen::frontend::set_system_functions(decls);
let design = compile_units(&units, &options).map_err(RunError::Compile)?;
Ok((design, codegen))
}
fn compile_with_libraries(
set: &SourceSet,
) -> Result<(sv_lang::Design, Vec<vj_codegen::vpi_systf::SysTfDecl>), RunError> {
use vj_sim::process::cfg;
if !set.defines.is_empty() {
return Err(RunError::Diagnostics(
"-D/+define+ cannot be combined with --libmap/-L (the library loader reads the files itself)"
.to_string(),
));
}
let mut files: Vec<String> = Vec::new();
let mut texts: Vec<(String, String)> = Vec::new();
for path in &set.files {
let name = path.to_string_lossy().into_owned();
let src = std::fs::read_to_string(path)
.map_err(|e| RunError::Io(std::io::Error::new(e.kind(), format!("{name}: {e}"))))?;
let rewritten = crate::randseq::expand_with_includes(&src, &name, &set.include_dirs)
.map_err(RunError::Diagnostics)?;
let (rewritten, decls, _) = crate::vpi::prepare_systf(&rewritten);
if rewritten != src || !decls.is_empty() {
return Err(RunError::Diagnostics(format!(
"{name}: randsequence / VPI system-task sources cannot be combined with --libmap/-L"
)));
}
texts.push((name.clone(), src));
files.push(name);
}
cfg::set_source_texts(texts);
cfg::set_source_name(files.first().map(String::as_str));
let options = UnitOptions {
include_dirs: set.include_dirs.clone(),
top_modules: set.top_modules.clone(),
library_maps: set.library_maps.clone(),
liblist: set.liblist.clone(),
};
let design =
vj_codegen::frontend::compile_library_units(&files, &options).map_err(RunError::Compile)?;
Ok((design, Vec::new()))
}
pub fn parse_only(set: &SourceSet) -> Result<(), RunError> {
if set.files.is_empty() {
return Err(RunError::Diagnostics("no source files".to_string()));
}
let mut units: Vec<(String, String, String)> = Vec::new();
let mut seen: Vec<PathBuf> = Vec::new();
for path in &set.files {
let key = std::fs::canonicalize(path).unwrap_or_else(|_| path.clone());
if seen.contains(&key) {
continue;
}
seen.push(key);
let name = path.to_string_lossy().into_owned();
let src = std::fs::read_to_string(path)
.map_err(|e| RunError::Io(std::io::Error::new(e.kind(), format!("{name}: {e}"))))?;
let text = with_defines(&src, &name, &set.defines).map_err(RunError::Diagnostics)?;
units.push((name.clone(), name, text));
}
let options = UnitOptions {
include_dirs: set.include_dirs.clone(),
top_modules: set.top_modules.clone(),
..UnitOptions::default()
};
vj_codegen::frontend::parse_units(&units, &options).map_err(RunError::Compile)
}