2022-06-18 21:56:31 -07:00
|
|
|
use super::*;
|
2019-11-07 10:55:15 -08:00
|
|
|
|
|
|
|
pub(crate) struct Compiler;
|
|
|
|
|
|
|
|
impl Compiler {
|
2023-11-21 11:28:59 -08:00
|
|
|
pub(crate) fn compile<'src>(
|
2023-12-27 20:27:15 -08:00
|
|
|
unstable: bool,
|
2023-11-21 11:28:59 -08:00
|
|
|
loader: &'src Loader,
|
|
|
|
root: &Path,
|
|
|
|
) -> RunResult<'src, Compilation<'src>> {
|
|
|
|
let mut asts: HashMap<PathBuf, Ast> = HashMap::new();
|
2023-11-22 10:27:49 -08:00
|
|
|
let mut paths: HashMap<PathBuf, PathBuf> = HashMap::new();
|
2023-11-22 10:33:55 -08:00
|
|
|
let mut srcs: HashMap<PathBuf, &str> = HashMap::new();
|
2023-11-24 23:15:41 -08:00
|
|
|
let mut loaded = Vec::new();
|
2023-11-21 11:28:59 -08:00
|
|
|
|
2023-11-22 10:27:49 -08:00
|
|
|
let mut stack: Vec<PathBuf> = Vec::new();
|
|
|
|
stack.push(root.into());
|
2023-11-21 11:28:59 -08:00
|
|
|
|
2023-11-22 10:27:49 -08:00
|
|
|
while let Some(current) = stack.pop() {
|
2023-11-21 20:17:38 -08:00
|
|
|
let (relative, src) = loader.load(root, ¤t)?;
|
2023-11-24 23:15:41 -08:00
|
|
|
loaded.push(relative.into());
|
2023-11-21 20:17:38 -08:00
|
|
|
let tokens = Lexer::lex(relative, src)?;
|
2023-11-21 11:28:59 -08:00
|
|
|
let mut ast = Parser::parse(&tokens)?;
|
|
|
|
|
2023-11-22 10:33:55 -08:00
|
|
|
paths.insert(current.clone(), relative.into());
|
2023-11-21 11:28:59 -08:00
|
|
|
srcs.insert(current.clone(), src);
|
|
|
|
|
|
|
|
for item in &mut ast.items {
|
2023-12-27 20:27:15 -08:00
|
|
|
match item {
|
|
|
|
Item::Mod { name, absolute } => {
|
|
|
|
if !unstable {
|
|
|
|
return Err(Error::Unstable {
|
|
|
|
message: "Modules are currently unstable.".into(),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
let parent = current.parent().unwrap();
|
|
|
|
|
|
|
|
let import = Self::find_module_file(parent, *name)?;
|
|
|
|
|
|
|
|
if srcs.contains_key(&import) {
|
|
|
|
return Err(Error::CircularImport { current, import });
|
|
|
|
}
|
|
|
|
*absolute = Some(import.clone());
|
|
|
|
stack.push(import);
|
2023-11-21 11:28:59 -08:00
|
|
|
}
|
2023-12-27 20:27:15 -08:00
|
|
|
Item::Import { relative, absolute } => {
|
|
|
|
let import = current.parent().unwrap().join(&relative.cooked).lexiclean();
|
|
|
|
if srcs.contains_key(&import) {
|
|
|
|
return Err(Error::CircularImport { current, import });
|
|
|
|
}
|
|
|
|
*absolute = Some(import.clone());
|
|
|
|
stack.push(import);
|
|
|
|
}
|
|
|
|
_ => {}
|
2023-11-21 11:28:59 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
asts.insert(current.clone(), ast.clone());
|
|
|
|
}
|
|
|
|
|
2023-12-27 20:27:15 -08:00
|
|
|
let justfile = Analyzer::analyze(&loaded, &paths, &asts, root)?;
|
2023-11-21 11:28:59 -08:00
|
|
|
|
|
|
|
Ok(Compilation {
|
|
|
|
asts,
|
|
|
|
srcs,
|
|
|
|
justfile,
|
|
|
|
root: root.into(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-12-27 20:27:15 -08:00
|
|
|
fn find_module_file<'src>(parent: &Path, module: Name<'src>) -> RunResult<'src, PathBuf> {
|
|
|
|
let mut candidates = vec![format!("{module}.just"), format!("{module}/mod.just")]
|
|
|
|
.into_iter()
|
|
|
|
.filter(|path| parent.join(path).is_file())
|
|
|
|
.collect::<Vec<String>>();
|
|
|
|
|
|
|
|
let directory = parent.join(module.lexeme());
|
|
|
|
|
|
|
|
if directory.exists() {
|
|
|
|
let entries = fs::read_dir(&directory).map_err(|io_error| SearchError::Io {
|
|
|
|
io_error,
|
|
|
|
directory: directory.clone(),
|
|
|
|
})?;
|
|
|
|
|
|
|
|
for entry in entries {
|
|
|
|
let entry = entry.map_err(|io_error| SearchError::Io {
|
|
|
|
io_error,
|
|
|
|
directory: directory.clone(),
|
|
|
|
})?;
|
|
|
|
|
|
|
|
if let Some(name) = entry.file_name().to_str() {
|
|
|
|
for justfile_name in search::JUSTFILE_NAMES {
|
|
|
|
if name.eq_ignore_ascii_case(justfile_name) {
|
|
|
|
candidates.push(format!("{module}/{name}"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
match candidates.as_slice() {
|
|
|
|
[] => Err(Error::MissingModuleFile { module }),
|
|
|
|
[file] => Ok(parent.join(file).lexiclean()),
|
|
|
|
found => Err(Error::AmbiguousModuleFile {
|
|
|
|
found: found.into(),
|
|
|
|
module,
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-21 11:28:59 -08:00
|
|
|
#[cfg(test)]
|
|
|
|
pub(crate) fn test_compile(src: &str) -> CompileResult<Justfile> {
|
2023-11-21 20:17:38 -08:00
|
|
|
let tokens = Lexer::test_lex(src)?;
|
2019-11-07 10:55:15 -08:00
|
|
|
let ast = Parser::parse(&tokens)?;
|
2023-11-21 20:17:38 -08:00
|
|
|
let root = PathBuf::from("justfile");
|
2023-11-21 11:28:59 -08:00
|
|
|
let mut asts: HashMap<PathBuf, Ast> = HashMap::new();
|
|
|
|
asts.insert(root.clone(), ast);
|
2023-11-22 10:27:49 -08:00
|
|
|
let mut paths: HashMap<PathBuf, PathBuf> = HashMap::new();
|
|
|
|
paths.insert(root.clone(), root.clone());
|
2023-12-27 20:27:15 -08:00
|
|
|
Analyzer::analyze(&[], &paths, &asts, &root)
|
2023-11-21 11:28:59 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use {super::*, temptree::temptree};
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn include_justfile() {
|
|
|
|
let justfile_a = r#"
|
|
|
|
# A comment at the top of the file
|
2023-12-19 20:31:51 -08:00
|
|
|
import "./justfile_b"
|
2023-11-21 11:28:59 -08:00
|
|
|
|
|
|
|
#some_recipe: recipe_b
|
|
|
|
some_recipe:
|
|
|
|
echo "some recipe"
|
|
|
|
"#;
|
|
|
|
|
2023-12-19 20:31:51 -08:00
|
|
|
let justfile_b = r#"import "./subdir/justfile_c"
|
2023-11-21 11:28:59 -08:00
|
|
|
|
|
|
|
recipe_b: recipe_c
|
|
|
|
echo "recipe b"
|
|
|
|
"#;
|
|
|
|
|
|
|
|
let justfile_c = r#"recipe_c:
|
|
|
|
echo "recipe c"
|
|
|
|
"#;
|
|
|
|
|
|
|
|
let tmp = temptree! {
|
|
|
|
justfile: justfile_a,
|
|
|
|
justfile_b: justfile_b,
|
|
|
|
subdir: {
|
|
|
|
justfile_c: justfile_c
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let loader = Loader::new();
|
|
|
|
|
|
|
|
let justfile_a_path = tmp.path().join("justfile");
|
2023-12-27 20:27:15 -08:00
|
|
|
let compilation = Compiler::compile(false, &loader, &justfile_a_path).unwrap();
|
2023-11-21 11:28:59 -08:00
|
|
|
|
|
|
|
assert_eq!(compilation.root_src(), justfile_a);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn recursive_includes_fail() {
|
|
|
|
let justfile_a = r#"
|
|
|
|
# A comment at the top of the file
|
2023-12-19 20:31:51 -08:00
|
|
|
import "./subdir/justfile_b"
|
2023-11-21 11:28:59 -08:00
|
|
|
|
|
|
|
some_recipe: recipe_b
|
|
|
|
echo "some recipe"
|
|
|
|
|
|
|
|
"#;
|
|
|
|
|
|
|
|
let justfile_b = r#"
|
2023-12-19 20:31:51 -08:00
|
|
|
import "../justfile"
|
2023-11-21 11:28:59 -08:00
|
|
|
|
|
|
|
recipe_b:
|
|
|
|
echo "recipe b"
|
|
|
|
"#;
|
|
|
|
let tmp = temptree! {
|
|
|
|
justfile: justfile_a,
|
|
|
|
subdir: {
|
|
|
|
justfile_b: justfile_b
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let loader = Loader::new();
|
|
|
|
|
|
|
|
let justfile_a_path = tmp.path().join("justfile");
|
2023-12-27 20:27:15 -08:00
|
|
|
let loader_output = Compiler::compile(false, &loader, &justfile_a_path).unwrap_err();
|
2019-11-07 10:55:15 -08:00
|
|
|
|
2023-12-19 20:31:51 -08:00
|
|
|
assert_matches!(loader_output, Error::CircularImport { current, import }
|
2023-11-21 11:28:59 -08:00
|
|
|
if current == tmp.path().join("subdir").join("justfile_b").lexiclean() &&
|
2023-12-19 20:31:51 -08:00
|
|
|
import == tmp.path().join("justfile").lexiclean()
|
2023-11-21 11:28:59 -08:00
|
|
|
);
|
2019-11-07 10:55:15 -08:00
|
|
|
}
|
|
|
|
}
|