Skip to content

Commit

Permalink
Optional modules and imports (#1797)
Browse files Browse the repository at this point in the history
  • Loading branch information
casey authored Dec 29, 2023
1 parent 177bb68 commit e2c0d86
Show file tree
Hide file tree
Showing 18 changed files with 1,047 additions and 353 deletions.
12 changes: 9 additions & 3 deletions GRAMMAR.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,14 @@ grammar
```
justfile : item* EOF
item : recipe
| alias
item : alias
| assignment
| eol
| export
| import
| module
| recipe
| setting
| eol
eol : NEWLINE
| COMMENT NEWLINE
Expand All @@ -72,6 +74,10 @@ setting : 'set' 'allow-duplicate-recipes' boolean?
| 'set' 'windows-powershell' boolean?
| 'set' 'windows-shell' ':=' '[' string (',' string)* ','? ']'
import : 'import' '?'? string?
module : 'mod' '?'? NAME string?
boolean : ':=' ('true' | 'false')
expression : 'if' condition '{' expression '}' 'else' '{' expression '}'
Expand Down
933 changes: 671 additions & 262 deletions README.md

Large diffs are not rendered by default.

31 changes: 16 additions & 15 deletions src/analyzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ impl<'src> Analyzer<'src> {
let mut define = |name: Name<'src>,
second_type: &'static str,
duplicates_allowed: bool|
-> CompileResult<'src, ()> {
-> CompileResult<'src> {
if let Some((first_type, original)) = definitions.get(name.lexeme()) {
if !(*first_type == second_type && duplicates_allowed) {
let (original, redefinition) = if name.line < original.line {
Expand Down Expand Up @@ -75,17 +75,18 @@ impl<'src> Analyzer<'src> {
}
Item::Comment(_) => (),
Item::Import { absolute, .. } => {
stack.push(asts.get(absolute.as_ref().unwrap()).unwrap());
if let Some(absolute) = absolute {
stack.push(asts.get(absolute).unwrap());
}
}
Item::Mod { absolute, name, .. } => {
define(*name, "module", false)?;
modules.insert(
name.to_string(),
(
*name,
Self::analyze(loaded, paths, asts, absolute.as_ref().unwrap())?,
),
);
Item::Module { absolute, name, .. } => {
if let Some(absolute) = absolute {
define(*name, "module", false)?;
modules.insert(
name.to_string(),
(*name, Self::analyze(loaded, paths, asts, absolute)?),
);
}
}
Item::Recipe(recipe) => {
if recipe.enabled() {
Expand Down Expand Up @@ -153,7 +154,7 @@ impl<'src> Analyzer<'src> {
})
}

fn analyze_recipe(recipe: &UnresolvedRecipe<'src>) -> CompileResult<'src, ()> {
fn analyze_recipe(recipe: &UnresolvedRecipe<'src>) -> CompileResult<'src> {
let mut parameters = BTreeSet::new();
let mut passed_default = false;

Expand Down Expand Up @@ -198,7 +199,7 @@ impl<'src> Analyzer<'src> {
Ok(())
}

fn analyze_assignment(&self, assignment: &Assignment<'src>) -> CompileResult<'src, ()> {
fn analyze_assignment(&self, assignment: &Assignment<'src>) -> CompileResult<'src> {
if self.assignments.contains_key(assignment.name.lexeme()) {
return Err(assignment.name.token().error(DuplicateVariable {
variable: assignment.name.lexeme(),
Expand All @@ -207,7 +208,7 @@ impl<'src> Analyzer<'src> {
Ok(())
}

fn analyze_alias(alias: &Alias<'src, Name<'src>>) -> CompileResult<'src, ()> {
fn analyze_alias(alias: &Alias<'src, Name<'src>>) -> CompileResult<'src> {
let name = alias.name.lexeme();

for attr in &alias.attributes {
Expand All @@ -222,7 +223,7 @@ impl<'src> Analyzer<'src> {
Ok(())
}

fn analyze_set(&self, set: &Set<'src>) -> CompileResult<'src, ()> {
fn analyze_set(&self, set: &Set<'src>) -> CompileResult<'src> {
if let Some(original) = self.sets.get(set.name.lexeme()) {
return Err(set.name.error(DuplicateSet {
setting: original.name.lexeme(),
Expand Down
6 changes: 3 additions & 3 deletions src/assignment_resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pub(crate) struct AssignmentResolver<'src: 'run, 'run> {
impl<'src: 'run, 'run> AssignmentResolver<'src, 'run> {
pub(crate) fn resolve_assignments(
assignments: &'run Table<'src, Assignment<'src>>,
) -> CompileResult<'src, ()> {
) -> CompileResult<'src> {
let mut resolver = Self {
stack: Vec::new(),
evaluated: BTreeSet::new(),
Expand All @@ -23,7 +23,7 @@ impl<'src: 'run, 'run> AssignmentResolver<'src, 'run> {
Ok(())
}

fn resolve_assignment(&mut self, name: &'src str) -> CompileResult<'src, ()> {
fn resolve_assignment(&mut self, name: &'src str) -> CompileResult<'src> {
if self.evaluated.contains(name) {
return Ok(());
}
Expand Down Expand Up @@ -52,7 +52,7 @@ impl<'src: 'run, 'run> AssignmentResolver<'src, 'run> {
Ok(())
}

fn resolve_expression(&mut self, expression: &Expression<'src>) -> CompileResult<'src, ()> {
fn resolve_expression(&mut self, expression: &Expression<'src>) -> CompileResult<'src> {
match expression {
Expression::Variable { name } => {
let variable = name.lexeme();
Expand Down
55 changes: 38 additions & 17 deletions src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ impl Compiler {

for item in &mut ast.items {
match item {
Item::Mod {
name,
Item::Module {
absolute,
path,
name,
optional,
relative,
} => {
if !unstable {
return Err(Error::Unstable {
Expand All @@ -40,29 +41,49 @@ impl Compiler {

let parent = current.parent().unwrap();

let import = if let Some(path) = path {
parent.join(Self::expand_tilde(&path.cooked)?)
let import = if let Some(relative) = relative {
let path = parent.join(Self::expand_tilde(&relative.cooked)?);

if path.is_file() {
Some(path)
} else {
None
}
} else {
Self::find_module_file(parent, *name)?
};

if srcs.contains_key(&import) {
return Err(Error::CircularImport { current, import });
if let Some(import) = import {
if srcs.contains_key(&import) {
return Err(Error::CircularImport { current, import });
}
*absolute = Some(import.clone());
stack.push((import, depth + 1));
} else if !*optional {
return Err(Error::MissingModuleFile { module: *name });
}
*absolute = Some(import.clone());
stack.push((import, depth + 1));
}
Item::Import { relative, absolute } => {
Item::Import {
relative,
absolute,
optional,
path,
} => {
let import = current
.parent()
.unwrap()
.join(Self::expand_tilde(&relative.cooked)?)
.lexiclean();
if srcs.contains_key(&import) {
return Err(Error::CircularImport { current, import });

if import.is_file() {
if srcs.contains_key(&import) {
return Err(Error::CircularImport { current, import });
}
*absolute = Some(import.clone());
stack.push((import, depth + 1));
} else if !*optional {
return Err(Error::MissingImportFile { path: *path });
}
*absolute = Some(import.clone());
stack.push((import, depth + 1));
}
_ => {}
}
Expand All @@ -81,7 +102,7 @@ impl Compiler {
})
}

fn find_module_file<'src>(parent: &Path, module: Name<'src>) -> RunResult<'src, PathBuf> {
fn find_module_file<'src>(parent: &Path, module: Name<'src>) -> RunResult<'src, Option<PathBuf>> {
let mut candidates = vec![format!("{module}.just"), format!("{module}/mod.just")]
.into_iter()
.filter(|path| parent.join(path).is_file())
Expand Down Expand Up @@ -112,8 +133,8 @@ impl Compiler {
}

match candidates.as_slice() {
[] => Err(Error::MissingModuleFile { module }),
[file] => Ok(parent.join(file).lexiclean()),
[] => Ok(None),
[file] => Ok(Some(parent.join(file).lexiclean())),
found => Err(Error::AmbiguousModuleFile {
found: found.into(),
module,
Expand Down
5 changes: 5 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ pub(crate) enum Error<'src> {
path: PathBuf,
io_error: io::Error,
},
MissingImportFile {
path: Token<'src>,
},
MissingModuleFile {
module: Name<'src>,
},
Expand Down Expand Up @@ -181,6 +184,7 @@ impl<'src> Error<'src> {
Self::Backtick { token, .. } => Some(*token),
Self::Compile { compile_error } => Some(compile_error.context()),
Self::FunctionCall { function, .. } => Some(function.token()),
Self::MissingImportFile { path } => Some(*path),
_ => None,
}
}
Expand Down Expand Up @@ -369,6 +373,7 @@ impl<'src> ColorDisplay for Error<'src> {
let path = path.display();
write!(f, "Failed to read justfile at `{path}`: {io_error}")?;
}
MissingImportFile { .. } => write!(f, "Could not find source file for import.")?,
MissingModuleFile { module } => write!(f, "Could not find source file for module `{module}`.")?,
NoChoosableRecipes => write!(f, "Justfile contains no choosable recipes.")?,
NoDefaultRecipe => write!(f, "Justfile contains no default recipe.")?,
Expand Down
40 changes: 32 additions & 8 deletions src/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ pub(crate) enum Item<'src> {
Assignment(Assignment<'src>),
Comment(&'src str),
Import {
relative: StringLiteral<'src>,
absolute: Option<PathBuf>,
optional: bool,
path: Token<'src>,
relative: StringLiteral<'src>,
},
Mod {
name: Name<'src>,
Module {
absolute: Option<PathBuf>,
path: Option<StringLiteral<'src>>,
name: Name<'src>,
optional: bool,
relative: Option<StringLiteral<'src>>,
},
Recipe(UnresolvedRecipe<'src>),
Set(Set<'src>),
Expand All @@ -25,11 +28,32 @@ impl<'src> Display for Item<'src> {
Item::Alias(alias) => write!(f, "{alias}"),
Item::Assignment(assignment) => write!(f, "{assignment}"),
Item::Comment(comment) => write!(f, "{comment}"),
Item::Import { relative, .. } => write!(f, "import {relative}"),
Item::Mod { name, path, .. } => {
write!(f, "mod {name}")?;
Item::Import {
relative, optional, ..
} => {
write!(f, "import")?;

if *optional {
write!(f, "?")?;
}

write!(f, " {relative}")
}
Item::Module {
name,
relative,
optional,
..
} => {
write!(f, "mod")?;

if *optional {
write!(f, "?")?;
}

write!(f, " {name}")?;

if let Some(path) = path {
if let Some(path) = relative {
write!(f, " {path}")?;
}

Expand Down
4 changes: 2 additions & 2 deletions src/justfile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ impl<'src> Justfile<'src> {
search: &Search,
overrides: &BTreeMap<String, String>,
arguments: &[String],
) -> RunResult<'src, ()> {
) -> RunResult<'src> {
let unknown_overrides = overrides
.keys()
.filter(|name| !self.assignments.contains_key(name.as_str()))
Expand Down Expand Up @@ -393,7 +393,7 @@ impl<'src> Justfile<'src> {
dotenv: &BTreeMap<String, String>,
search: &Search,
ran: &mut BTreeSet<Vec<String>>,
) -> RunResult<'src, ()> {
) -> RunResult<'src> {
let mut invocation = vec![recipe.name().to_owned()];
for argument in arguments {
invocation.push((*argument).to_string());
Expand Down
Loading

0 comments on commit e2c0d86

Please sign in to comment.