use std::ffi::OsStr;
use std::fs;
use std::io;
use std::path::PathBuf;
use std::{path::Path, sync::Arc};
use anyhow::{anyhow, Context as _, Result};
use crate::alloc::{Vec, VecDeque};
use crate::cli::{visitor, Io, SharedFlags};
use crate::compile::FileSourceLoader;
use crate::{Context, Diagnostics, Hash, ItemBuf, Options, Source, Sources, Unit};
pub(super) struct Load {
pub(super) unit: Arc<Unit>,
pub(super) sources: Sources,
pub(super) functions: Vec<(Hash, ItemBuf)>,
}
pub(super) fn load(
io: &mut Io<'_>,
context: &Context,
shared: &SharedFlags,
options: &Options,
path: &Path,
attribute: visitor::Attribute,
) -> Result<Load> {
let bytecode_path = path.with_extension("rnc");
let source =
Source::from_path(path).with_context(|| anyhow!("cannot read file: {}", path.display()))?;
let mut sources = Sources::new();
sources.insert(source)?;
let use_cache = options.bytecode && should_cache_be_used(path, &bytecode_path)?;
let maybe_unit = if use_cache {
let f = fs::File::open(&bytecode_path)?;
match bincode::deserialize_from::<_, Unit>(f) {
Ok(unit) => {
tracing::trace!("Using cache: {}", bytecode_path.display());
Some(Arc::new(unit))
}
Err(_error) => {
tracing::error!(
"Failed to deserialize: {}: {}",
bytecode_path.display(),
_error
);
None
}
}
} else {
None
};
let (unit, functions) = match maybe_unit {
Some(unit) => (unit, Default::default()),
None => {
tracing::trace!("building file: {}", path.display());
let mut diagnostics = if shared.warnings {
Diagnostics::new()
} else {
Diagnostics::without_warnings()
};
let mut functions = visitor::FunctionVisitor::new(attribute);
let mut source_loader = FileSourceLoader::new();
let result = crate::prepare(&mut sources)
.with_context(context)
.with_diagnostics(&mut diagnostics)
.with_options(options)
.with_visitor(&mut functions)?
.with_source_loader(&mut source_loader)
.build();
diagnostics.emit(io.stdout, &sources)?;
let unit = result?;
if options.bytecode {
tracing::trace!("serializing cache: {}", bytecode_path.display());
let f = fs::File::create(&bytecode_path)?;
bincode::serialize_into(f, &unit)?;
}
(Arc::new(unit), functions.into_functions())
}
};
Ok(Load {
unit,
sources,
functions,
})
}
fn should_cache_be_used(source: &Path, cached: &Path) -> io::Result<bool> {
let source = fs::metadata(source)?;
let cached = match fs::metadata(cached) {
Ok(cached) => cached,
Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(false),
Err(error) => return Err(error),
};
Ok(source.modified()? < cached.modified()?)
}
pub(super) fn recurse_paths(
recursive: bool,
first: PathBuf,
) -> impl Iterator<Item = Result<PathBuf>> {
let mut queue = VecDeque::new();
let mut first = Some(first);
std::iter::from_fn(move || loop {
let path = first.take().or_else(|| queue.pop_front())?;
if !recursive {
return Some(Ok(path));
}
if path.is_file() {
if path.extension() == Some(OsStr::new("rn")) {
return Some(Ok(path));
}
continue;
}
let d = match fs::read_dir(path) {
Ok(d) => d,
Err(error) => return Some(Err(anyhow::Error::from(error))),
};
for e in d {
let e = match e {
Ok(e) => e,
Err(error) => return Some(Err(anyhow::Error::from(error))),
};
if let Err(error) = queue.try_push_back(e.path()) {
return Some(Err(anyhow::Error::from(error)));
}
}
})
}