use std::{
error,
fmt::{self, Debug, Display, Formatter},
ops::ControlFlow,
path::{Path, PathBuf},
};
use directories::ProjectDirs;
use tokio::fs;
use dolang::{
compile::{self, Compiler, Diag, EmitDiag, EmitToken, Mode, Severity},
extension::CompilerExt,
runtime::{
Bytecode, Error, Result, Slot, Strand,
error::{ErrorKind, ResultExt},
},
};
use crate::interactive::DYNAMIC_PRELUDE;
#[derive(Debug)]
struct Stop;
impl Display for Stop {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "compilation stopped due to too many errors")
}
}
impl error::Error for Stop {}
pub(crate) fn compile<'v, 's, 'a>(
strand: &mut Strand<'v, 's>,
path: &'a Path,
source: &'a str,
dynamic: Option<&[String]>,
mode: Mode,
strict: bool,
) -> Result<'v, 's, Vec<u8>> {
let mut out = Vec::new();
let mut errors = 0usize;
let mut warnings = 0usize;
let compiler = compile_setup(path, source, dynamic, mode);
compiler
.compile(&mut out, &mut |diag: Diag| -> ControlFlow<Stop> {
match diag.severity() {
Severity::Error => errors += 1,
Severity::Warning => warnings += 1,
_ => (),
}
let disp = path.display().to_string();
dolang_ext_shell::print_compile_diag_stderr(&disp, source, &diag);
if errors > 10 {
ControlFlow::Break(Stop)
} else {
ControlFlow::Continue(())
}
})
.into_do(strand)?;
if warnings != 0 && strict {
Err(Error::compile(
strand,
"warnings treated as errors due to --strict flag",
))
} else {
Ok(out)
}
}
fn compile_setup<'a>(
path: &'a Path,
source: &'a str,
dynamic: Option<&'a [String]>,
mode: Mode<'a>,
) -> Compiler<'a> {
let mut compiler = Compiler::new(Path::new(path), source.as_bytes());
compiler.mode(mode);
for ext in compiler.extensions() {
ext.apply(&mut compiler).unwrap();
}
if let Some(dynamic) = dynamic {
compiler
.prelude()
.import_items(DYNAMIC_PRELUDE)
.items(dynamic)
.commit();
}
compiler
}
pub(crate) fn analyze<'a, D: EmitDiag, T: EmitToken<Break = D::Break>>(
path: &'a Path,
source: &'a str,
dynamic: Option<&[String]>,
diags: &mut D,
tokens: &mut T,
) -> std::result::Result<(), compile::Error<D::Break>> {
let compiler = compile_setup(path, source, dynamic, Mode::Repl);
compiler.analyze(diags, tokens)
}
async fn file_is_newer(older: &Path, newer: &Path) -> bool {
let older = fs::metadata(older).await.and_then(|older| older.modified());
let newer = fs::metadata(newer).await.and_then(|newer| newer.modified());
older
.and_then(|older| newer.map(|newer| newer > older))
.unwrap_or(false)
}
pub(crate) fn dirs<'v, 's>(strand: &mut Strand<'v, 's>) -> Result<'v, 's, ProjectDirs> {
ProjectDirs::from("", "", "dolang")
.ok_or_else(|| Error::runtime(strand, "can't locate application directories"))
}
fn get_module_search_paths<'v, 's>(
strand: &mut Strand<'v, 's>,
module_paths: &[PathBuf],
) -> Result<'v, 's, Vec<PathBuf>> {
let mut paths = module_paths.to_vec();
paths.push(dirs(strand)?.data_dir().join("site"));
Ok(paths)
}
pub(crate) async fn find_module_file<'v, 's>(
strand: &mut Strand<'v, 's>,
name: &str,
module_paths: &[PathBuf],
) -> Result<'v, 's, PathBuf> {
let search_paths = get_module_search_paths(strand, module_paths)?;
let mut relative_path = PathBuf::new();
relative_path.extend(name.split('.'));
let mut relative_path_alt = relative_path.clone();
relative_path.set_extension("dol");
relative_path_alt.push("mod.dol");
for base_path in search_paths {
for relative_path in [&relative_path, &relative_path_alt].into_iter() {
let mut module_path = base_path.clone();
module_path.extend(relative_path);
if fs::try_exists(&module_path).await.into_do(strand)? {
return Ok(module_path);
}
}
}
Err(Error::import(strand, name))
}
async fn compile_script<'v, 's>(
strand: &mut Strand<'v, 's>,
path: &Path,
strict: bool,
) -> Result<'v, 's, Vec<u8>> {
if fs::try_exists(path).await.into_do(strand)? {
let source = fs::read_to_string(path).await.into_do(strand)?;
compile(strand, path, &source, None, Mode::Script, strict)
} else {
Err(Error::runtime(
strand,
format!("could not find file: {}", path.display()),
))
}
}
pub(crate) async fn compile_only<'v, 's>(
strand: &mut Strand<'v, 's>,
path: &Path,
strict: bool,
) -> Result<'v, 's, ()> {
compile_script(strand, path, strict).await?;
Ok(())
}
pub(crate) async fn compile_to_file<'v, 's>(
strand: &mut Strand<'v, 's>,
path: &Path,
output: &Path,
strict: bool,
) -> Result<'v, 's, ()> {
let data = compile_script(strand, path, strict).await?;
fs::write(output, &data).await.into_do(strand)?;
Ok(())
}
pub(crate) async fn load<'v, 's>(
strand: &mut Strand<'v, 's>,
path: &Path,
mode: Mode<'_>,
strict: bool,
mut out: Slot<'v, '_>,
) -> Result<'v, 's, ()> {
let mut bc = dirs(strand)?.cache_dir().join("bytecode").clone();
let mut hasher = blake3::Hasher::new();
hasher.update(path.as_os_str().as_encoded_bytes());
match mode {
Mode::Script => hasher.update(b"script"),
Mode::Module { name } => {
hasher.update(b"module");
hasher.update(name.as_bytes())
}
Mode::Repl => hasher.update(b"repl"),
_ => hasher.update(b"unknown"),
};
bc.push(hasher.finalize().to_hex().as_str());
bc.set_extension("dolc");
if fs::try_exists(&bc).await.into_do(strand)? && !file_is_newer(&bc, path).await {
let data = fs::read(&bc).await.into_do(strand)?;
let bytecode = Bytecode::new(data);
match bytecode.run(strand, &mut out).await {
Ok(()) => return Ok(()),
Err(e) if e.kind() == ErrorKind::Bytecode => (),
Err(e) => return Err(e),
}
}
let source = fs::read_to_string(path).await.into_do(strand)?;
let data = compile(strand, path, &source, None, mode, strict)?;
fs::create_dir_all(bc.parent().unwrap())
.await
.into_do(strand)?;
fs::write(&bc, &data).await.into_do(strand)?;
let bytecode = Bytecode::new(data);
bytecode.run(strand, &mut out).await?;
Ok(())
}