use std::path::{Path, PathBuf};
use crate::host::{full_path, is_hidden, relative_path, report_and_exit, Color, Exit, Host, HostExt};
pub mod pproj;
pub const DEFAULT_NAME: &str = "generatedOutput";
#[derive(Debug, Clone, PartialEq)]
pub struct SourceFile {
pub path: String,
pub name: String,
pub text: String,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Sources {
pub project_name: String,
pub files: Vec<SourceFile>,
}
impl Sources {
pub fn full_path(&self, id: crate::syntax::FileId) -> &str {
&self.files[id.0 as usize].path
}
pub fn source_map(&self) -> crate::syntax::SourceMap {
let mut sm = crate::syntax::SourceMap::new();
for s in &self.files {
sm.add(s.name.clone(), s.text.clone());
}
sm
}
}
#[derive(Debug, Clone, Default)]
pub struct Input {
pub pproj: Option<String>,
pub pfiles: Vec<String>,
pub project_name: Option<String>,
pub includes: pproj::Includes,
}
#[derive(Debug, Clone)]
pub struct Resolved {
pub project_name: String,
pub p_files: Vec<PathBuf>,
pub foreign_files: Vec<PathBuf>,
}
pub fn resolve(host: &mut dyn Host, input: &Input) -> Result<Resolved, Exit> {
let mut r = Resolved { project_name: DEFAULT_NAME.into(), p_files: Vec::new(), foreign_files: Vec::new() };
let pproj = match (&input.pproj, input.pfiles.is_empty()) {
(Some(p), _) => Some(p.clone()),
(None, true) => pproj::find_local_pproj(host),
(None, false) => None,
};
let add = |r: &mut Resolved, f: PathBuf| {
let list =
if f.to_string_lossy().to_lowercase().ends_with(".p") { &mut r.p_files } else { &mut r.foreign_files };
if !list.contains(&f) {
list.push(f);
}
};
if let Some(pp) = pproj {
let proj = pproj::parse(host, &pp, input.includes)?;
r.project_name = proj.name;
for f in proj.input_files {
add(&mut r, f);
}
}
for f in &input.pfiles {
let Some(full) = pproj::legal_p_file(host, f) else {
return Err(report_and_exit(host, &format!("P file {} not found.", f)));
};
add(&mut r, full);
}
if r.p_files.is_empty() {
host.out_line(".. Searching for P files locally in the current folder");
let mut files = Vec::new();
pproj::list_files(host, &host.cwd(), Some(1), &mut files);
for f in files {
if is_hidden(&f) || f.extension().map(|e| e != "p").unwrap_or(true) {
continue;
}
host.out_line(&format!(".. Adding P file: {}", f.display()));
r.p_files.push(f);
}
}
if r.p_files.is_empty() {
return Err(report_and_exit(host, "Could not find any *.p file."));
}
if let Some(n) = &input.project_name {
r.project_name = n.clone();
}
if !pproj::is_legal_project_name(&r.project_name) {
return Err(report_and_exit(host, &format!("{} is not a legal project name", r.project_name)));
}
Ok(r)
}
pub fn read(host: &dyn Host, project_name: &str, files: &[PathBuf], root: &Path) -> Result<Sources, String> {
let mut out = Vec::new();
for f in files {
let full = full_path(&host.cwd(), f);
let text = host.read_to_string(&full).map_err(|e| format!("{}: {}", full.display(), e))?;
out.push(SourceFile { path: full.to_string_lossy().to_string(), name: relative_path(root, &full), text });
}
Ok(Sources { project_name: project_name.into(), files: out })
}
fn write_error(host: &mut dyn Host, msg: &str) {
host.out_colored_line(msg, Color::Red);
}
pub fn report_result(
host: &mut dyn Host,
src: &Sources,
sm: &crate::syntax::SourceMap,
r: &Result<crate::sema::Analysis, crate::sema::CompileError>,
upstream: bool,
) -> bool {
use crate::sema::CompileError;
match r {
Err(CompileError::Parse { file, message, lexer }) => {
if !*lexer && upstream {
host.out_colored_line("Reverting to LL(*) parsing strategy.", Color::Yellow);
}
write_error(host, &format!("[Parser Error:] [{}] parse error: {}", sm.file(*file).base_name, message));
false
}
Err(CompileError::NotSupported { file, line, col, keyword }) => {
write_error(
host,
&format!(
"[NotSupportedError:] {} (line {}:{}): \"{}\" keyword is only supported by PVerifier backend.",
src.full_path(*file),
line,
col,
keyword
),
);
false
}
Err(CompileError::Analysis(a)) => {
for w in &a.warnings {
host.out_colored_line(w, Color::Yellow);
}
let msgs = a.messages(sm);
let mut rest = msgs.as_slice();
if a.thrown.is_some() {
if let Some((first, others)) = rest.split_first() {
write_error(host, &format!("[Error:] {}", first));
rest = others;
}
}
for (i, m) in rest.iter().enumerate() {
if i > 0 {
write_error(host, "");
}
write_error(host, &format!("[Error:] {}", m));
}
false
}
Ok(a) => {
for w in &a.warnings {
host.out_colored_line(w, Color::Yellow);
}
true
}
}
}