summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCasey Rodarmor <casey@rodarmor.com>2024-01-08 13:26:33 -0800
committerGitHub <noreply@github.com>2024-01-08 13:26:33 -0800
commit1ea5e6ac31e947e48b2cec0c591ddee4ae879c6a (patch)
treed6bc92c2abd9b37a2b16d1afc5f8c06454767bcd
parent0dbd5bf0b6d5da7f60dfda4fff3436ba6984fc0a (diff)
Don't conflate recipes with the same name in different modules (#1825)
-rw-r--r--src/analyzer.rs17
-rw-r--r--src/assignment_resolver.rs15
-rw-r--r--src/compiler.rs35
-rw-r--r--src/error.rs4
-rw-r--r--src/evaluator.rs6
-rw-r--r--src/justfile.rs42
-rw-r--r--src/lib.rs26
-rw-r--r--src/name.rs48
-rw-r--r--src/namepath.rs28
-rw-r--r--src/parser.rs77
-rw-r--r--src/ran.rs18
-rw-r--r--src/recipe.rs13
-rw-r--r--src/source.rs33
-rw-r--r--src/testing.rs3
-rw-r--r--src/token.rs2
-rw-r--r--src/unresolved_recipe.rs3
-rw-r--r--src/variables.rs2
-rw-r--r--tests/json.rs24
-rw-r--r--tests/modules.rs20
19 files changed, 257 insertions, 159 deletions
diff --git a/src/analyzer.rs b/src/analyzer.rs
index dcaf93e0..eb306421 100644
--- a/src/analyzer.rs
+++ b/src/analyzer.rs
@@ -47,7 +47,7 @@ impl<'src> Analyzer<'src> {
(*original, name)
};
- return Err(redefinition.token().error(Redefinition {
+ return Err(redefinition.token.error(Redefinition {
first_type,
second_type,
name: name.lexeme(),
@@ -83,7 +83,7 @@ impl<'src> Analyzer<'src> {
if let Some(absolute) = absolute {
define(*name, "module", false)?;
modules.insert(
- name.to_string(),
+ name.lexeme().into(),
(*name, Self::analyze(loaded, paths, asts, absolute)?),
);
}
@@ -160,7 +160,7 @@ impl<'src> Analyzer<'src> {
for parameter in &recipe.parameters {
if parameters.contains(parameter.name.lexeme()) {
- return Err(parameter.name.token().error(DuplicateParameter {
+ return Err(parameter.name.token.error(DuplicateParameter {
recipe: recipe.name.lexeme(),
parameter: parameter.name.lexeme(),
}));
@@ -173,7 +173,7 @@ impl<'src> Analyzer<'src> {
return Err(
parameter
.name
- .token()
+ .token
.error(RequiredParameterFollowsDefaultParameter {
parameter: parameter.name.lexeme(),
}),
@@ -201,7 +201,7 @@ impl<'src> Analyzer<'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 {
+ return Err(assignment.name.token.error(DuplicateVariable {
variable: assignment.name.lexeme(),
}));
}
@@ -213,7 +213,7 @@ impl<'src> Analyzer<'src> {
for attr in &alias.attributes {
if *attr != Attribute::Private {
- return Err(alias.name.token().error(AliasInvalidAttribute {
+ return Err(alias.name.token.error(AliasInvalidAttribute {
alias: name,
attr: *attr,
}));
@@ -238,10 +238,9 @@ impl<'src> Analyzer<'src> {
recipes: &Table<'src, Rc<Recipe<'src>>>,
alias: Alias<'src, Name<'src>>,
) -> CompileResult<'src, Alias<'src>> {
- let token = alias.name.token();
// Make sure the alias doesn't conflict with any recipe
if let Some(recipe) = recipes.get(alias.name.lexeme()) {
- return Err(token.error(AliasShadowsRecipe {
+ return Err(alias.name.token.error(AliasShadowsRecipe {
alias: alias.name.lexeme(),
recipe_line: recipe.line_number(),
}));
@@ -250,7 +249,7 @@ impl<'src> Analyzer<'src> {
// Make sure the target recipe exists
match recipes.get(alias.target.lexeme()) {
Some(target) => Ok(alias.resolve(Rc::clone(target))),
- None => Err(token.error(UnknownAliasTarget {
+ None => Err(alias.name.token.error(UnknownAliasTarget {
alias: alias.name.lexeme(),
target: alias.target.lexeme(),
})),
diff --git a/src/assignment_resolver.rs b/src/assignment_resolver.rs
index ee428104..2223900b 100644
--- a/src/assignment_resolver.rs
+++ b/src/assignment_resolver.rs
@@ -59,16 +59,19 @@ impl<'src: 'run, 'run> AssignmentResolver<'src, 'run> {
if self.evaluated.contains(variable) {
Ok(())
} else if self.stack.contains(&variable) {
- let token = self.assignments[variable].name.token();
self.stack.push(variable);
- Err(token.error(CircularVariableDependency {
- variable,
- circle: self.stack.clone(),
- }))
+ Err(
+ self.assignments[variable]
+ .name
+ .error(CircularVariableDependency {
+ variable,
+ circle: self.stack.clone(),
+ }),
+ )
} else if self.assignments.contains_key(variable) {
self.resolve_assignment(variable)
} else {
- Err(name.token().error(UndefinedVariable { variable }))
+ Err(name.token.error(UndefinedVariable { variable }))
}
}
Expression::Call { thunk } => match thunk {
diff --git a/src/compiler.rs b/src/compiler.rs
index 40573a22..c58408c6 100644
--- a/src/compiler.rs
+++ b/src/compiler.rs
@@ -13,17 +13,17 @@ impl Compiler {
let mut srcs: HashMap<PathBuf, &str> = HashMap::new();
let mut loaded = Vec::new();
- let mut stack: Vec<(PathBuf, u32)> = Vec::new();
- stack.push((root.into(), 0));
+ let mut stack = Vec::new();
+ stack.push(Source::root(root));
- while let Some((current, depth)) = stack.pop() {
- let (relative, src) = loader.load(root, &current)?;
+ while let Some(current) = stack.pop() {
+ let (relative, src) = loader.load(root, &current.path)?;
loaded.push(relative.into());
let tokens = Lexer::lex(relative, src)?;
- let mut ast = Parser::parse(depth, &current, &tokens)?;
+ let mut ast = Parser::parse(&current.path, &current.namepath, current.depth, &tokens)?;
- paths.insert(current.clone(), relative.into());
- srcs.insert(current.clone(), src);
+ paths.insert(current.path.clone(), relative.into());
+ srcs.insert(current.path.clone(), src);
for item in &mut ast.items {
match item {
@@ -39,7 +39,7 @@ impl Compiler {
});
}
- let parent = current.parent().unwrap();
+ let parent = current.path.parent().unwrap();
let import = if let Some(relative) = relative {
let path = parent.join(Self::expand_tilde(&relative.cooked)?);
@@ -55,10 +55,13 @@ impl Compiler {
if let Some(import) = import {
if srcs.contains_key(&import) {
- return Err(Error::CircularImport { current, import });
+ return Err(Error::CircularImport {
+ current: current.path,
+ import,
+ });
}
*absolute = Some(import.clone());
- stack.push((import, depth + 1));
+ stack.push(current.module(*name, import));
} else if !*optional {
return Err(Error::MissingModuleFile { module: *name });
}
@@ -70,6 +73,7 @@ impl Compiler {
path,
} => {
let import = current
+ .path
.parent()
.unwrap()
.join(Self::expand_tilde(&relative.cooked)?)
@@ -77,10 +81,13 @@ impl Compiler {
if import.is_file() {
if srcs.contains_key(&import) {
- return Err(Error::CircularImport { current, import });
+ return Err(Error::CircularImport {
+ current: current.path,
+ import,
+ });
}
*absolute = Some(import.clone());
- stack.push((import, depth + 1));
+ stack.push(current.import(import));
} else if !*optional {
return Err(Error::MissingImportFile { path: *path });
}
@@ -89,7 +96,7 @@ impl Compiler {
}
}
- asts.insert(current.clone(), ast.clone());
+ asts.insert(current.path, ast.clone());
}
let justfile = Analyzer::analyze(&loaded, &paths, &asts, root)?;
@@ -155,7 +162,7 @@ impl Compiler {
#[cfg(test)]
pub(crate) fn test_compile(src: &str) -> CompileResult<Justfile> {
let tokens = Lexer::test_lex(src)?;
- let ast = Parser::parse(0, &PathBuf::new(), &tokens)?;
+ let ast = Parser::parse(&PathBuf::new(), &Namepath::default(), 0, &tokens)?;
let root = PathBuf::from("justfile");
let mut asts: HashMap<PathBuf, Ast> = HashMap::new();
asts.insert(root.clone(), ast);
diff --git a/src/error.rs b/src/error.rs
index 1cc072d3..c71cbb92 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -179,11 +179,11 @@ impl<'src> Error<'src> {
fn context(&self) -> Option<Token<'src>> {
match self {
Self::AmbiguousModuleFile { module, .. } | Self::MissingModuleFile { module, .. } => {
- Some(module.token())
+ Some(module.token)
}
Self::Backtick { token, .. } => Some(*token),
Self::Compile { compile_error } => Some(compile_error.context()),
- Self::FunctionCall { function, .. } => Some(function.token()),
+ Self::FunctionCall { function, .. } => Some(function.token),
Self::MissingImportFile { path } => Some(*path),
_ => None,
}
diff --git a/src/evaluator.rs b/src/evaluator.rs
index 067d0b49..c768625d 100644
--- a/src/evaluator.rs
+++ b/src/evaluator.rs
@@ -255,7 +255,7 @@ impl<'src, 'run> Evaluator<'src, 'run> {
config: &'run Config,
dotenv: &'run BTreeMap<String, String>,
parameters: &[Parameter<'src>],
- arguments: &[&str],
+ arguments: &[String],
scope: &'run Scope<'src, 'run>,
settings: &'run Settings,
search: &'run Search,
@@ -289,13 +289,13 @@ impl<'src, 'run> Evaluator<'src, 'run> {
}
} else if parameter.kind.is_variadic() {
for value in rest {
- positional.push((*value).to_owned());
+ positional.push(value.clone());
}
let value = rest.to_vec().join(" ");
rest = &[];
value
} else {
- let value = rest[0].to_owned();
+ let value = rest[0].clone();
positional.push(value.clone());
rest = &rest[1..];
value
diff --git a/src/justfile.rs b/src/justfile.rs
index 031b6679..55cc70df 100644
--- a/src/justfile.rs
+++ b/src/justfile.rs
@@ -271,7 +271,7 @@ impl<'src> Justfile<'src> {
});
}
- let mut ran = BTreeSet::new();
+ let mut ran = Ran::default();
for invocation in invocations {
let context = RecipeContext {
settings: invocation.settings,
@@ -283,7 +283,12 @@ impl<'src> Justfile<'src> {
Self::run_recipe(
&context,
invocation.recipe,
- &invocation.arguments,
+ &invocation
+ .arguments
+ .iter()
+ .copied()
+ .map(str::to_string)
+ .collect::<Vec<String>>(),
&dotenv,
search,
&mut ran,
@@ -399,17 +404,12 @@ impl<'src> Justfile<'src> {
fn run_recipe(
context: &RecipeContext<'src, '_>,
recipe: &Recipe<'src>,
- arguments: &[&str],
+ arguments: &[String],
dotenv: &BTreeMap<String, String>,
search: &Search,
- ran: &mut BTreeSet<Vec<String>>,
+ ran: &mut Ran<'src>,
) -> RunResult<'src> {
- let mut invocation = vec![recipe.name().to_owned()];
- for argument in arguments {
- invocation.push((*argument).to_string());
- }
-
- if ran.contains(&invocation) {
+ if ran.has_run(&recipe.namepath, arguments) {
return Ok(());
}
@@ -440,20 +440,13 @@ impl<'src> Justfile<'src> {
.map(|argument| evaluator.evaluate_expression(argument))
.collect::<RunResult<Vec<String>>>()?;
- Self::run_recipe(
- context,
- recipe,
- &arguments.iter().map(String::as_ref).collect::<Vec<&str>>(),
- dotenv,
- search,
- ran,
- )?;
+ Self::run_recipe(context, recipe, &arguments, dotenv, search, ran)?;
}
recipe.run(context, dotenv, scope.child(), search, &positional)?;
{
- let mut ran = BTreeSet::new();
+ let mut ran = Ran::default();
for Dependency { recipe, arguments } in recipe.dependencies.iter().skip(recipe.priors) {
let mut evaluated = Vec::new();
@@ -462,18 +455,11 @@ impl<'src> Justfile<'src> {
evaluated.push(evaluator.evaluate_expression(argument)?);
}
- Self::run_recipe(
- context,
- recipe,
- &evaluated.iter().map(String::as_ref).collect::<Vec<&str>>(),
- dotenv,
- search,
- &mut ran,
- )?;
+ Self::run_recipe(context, recipe, &evaluated, dotenv, search, &mut ran)?;
}
}
- ran.insert(invocation);
+ ran.ran(&recipe.namepath, arguments.to_vec());
Ok(())
}
diff --git a/src/lib.rs b/src/lib.rs
index a30dd45b..445e8190 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -25,17 +25,17 @@ pub(crate) use {
fragment::Fragment, function::Function, function_context::FunctionContext,
interrupt_guard::InterruptGuard, interrupt_handler::InterruptHandler, item::Item,
justfile::Justfile, keyed::Keyed, keyword::Keyword, lexer::Lexer, line::Line, list::List,
- load_dotenv::load_dotenv, loader::Loader, name::Name, ordinal::Ordinal, output::output,
- output_error::OutputError, parameter::Parameter, parameter_kind::ParameterKind, parser::Parser,
- platform::Platform, platform_interface::PlatformInterface, position::Position,
- positional::Positional, range_ext::RangeExt, recipe::Recipe, recipe_context::RecipeContext,
- recipe_resolver::RecipeResolver, scope::Scope, search::Search, search_config::SearchConfig,
- search_error::SearchError, set::Set, setting::Setting, settings::Settings, shebang::Shebang,
- shell::Shell, show_whitespace::ShowWhitespace, string_kind::StringKind,
- string_literal::StringLiteral, subcommand::Subcommand, suggestion::Suggestion, table::Table,
- thunk::Thunk, token::Token, token_kind::TokenKind, unresolved_dependency::UnresolvedDependency,
- unresolved_recipe::UnresolvedRecipe, use_color::UseColor, variables::Variables,
- verbosity::Verbosity, warning::Warning,
+ load_dotenv::load_dotenv, loader::Loader, name::Name, namepath::Namepath, ordinal::Ordinal,
+ output::output, output_error::OutputError, parameter::Parameter, parameter_kind::ParameterKind,
+ parser::Parser, platform::Platform, platform_interface::PlatformInterface, position::Position,
+ positional::Positional, ran::Ran, range_ext::RangeExt, recipe::Recipe,
+ recipe_context::RecipeContext, recipe_resolver::RecipeResolver, scope::Scope, search::Search,
+ search_config::SearchConfig, search_error::SearchError, set::Set, setting::Setting,
+ settings::Settings, shebang::Shebang, shell::Shell, show_whitespace::ShowWhitespace,
+ source::Source, string_kind::StringKind, string_literal::StringLiteral, subcommand::Subcommand,
+ suggestion::Suggestion, table::Table, thunk::Thunk, token::Token, token_kind::TokenKind,
+ unresolved_dependency::UnresolvedDependency, unresolved_recipe::UnresolvedRecipe,
+ use_color::UseColor, variables::Variables, verbosity::Verbosity, warning::Warning,
},
std::{
cmp,
@@ -47,6 +47,7 @@ pub(crate) use {
io::{self, Cursor, Write},
iter::{self, FromIterator},
mem,
+ ops::Deref,
ops::{Index, Range, RangeInclusive},
path::{self, Path, PathBuf},
process::{self, Command, ExitStatus, Stdio},
@@ -149,6 +150,7 @@ mod list;
mod load_dotenv;
mod loader;
mod name;
+mod namepath;
mod ordinal;
mod output;
mod output_error;
@@ -159,6 +161,7 @@ mod platform;
mod platform_interface;
mod position;
mod positional;
+mod ran;
mod range_ext;
mod recipe;
mod recipe_context;
@@ -174,6 +177,7 @@ mod settings;
mod shebang;
mod shell;
mod show_whitespace;
+mod source;
mod string_kind;
mod string_literal;
mod subcommand;
diff --git a/src/name.rs b/src/name.rs
index 9823430e..f86063b3 100644
--- a/src/name.rs
+++ b/src/name.rs
@@ -1,50 +1,24 @@
use super::*;
-/// A name. This is effectively just a `Token` of kind `Identifier`, but we give
-/// it its own type for clarity.
+/// A name. This is just a `Token` of kind `Identifier`, but we give it its own
+/// type for clarity.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)]
pub(crate) struct Name<'src> {
- pub(crate) column: usize,
- pub(crate) length: usize,
- pub(crate) line: usize,
- pub(crate) offset: usize,
- pub(crate) path: &'src Path,
- pub(crate) src: &'src str,
+ pub(crate) token: Token<'src>,
}
impl<'src> Name<'src> {
- /// The name's text contents
- pub(crate) fn lexeme(&self) -> &'src str {
- &self.src[self.offset..self.offset + self.length]
- }
-
- /// Turn this name back into a token
- pub(crate) fn token(&self) -> Token<'src> {
- Token {
- column: self.column,
- kind: TokenKind::Identifier,
- length: self.length,
- line: self.line,
- offset: self.offset,
- path: self.path,
- src: self.src,
- }
- }
-
- pub(crate) fn from_identifier(token: Token<'src>) -> Name {
+ pub(crate) fn from_identifier(token: Token<'src>) -> Self {
assert_eq!(token.kind, TokenKind::Identifier);
- Name {
- column: token.column,
- length: token.length,
- line: token.line,
- offset: token.offset,
- path: token.path,
- src: token.src,
- }
+ Self { token }
}
+}
+
+impl<'src> Deref for Name<'src> {
+ type Target = Token<'src>;
- pub(crate) fn error(&self, kind: CompileErrorKind<'src>) -> CompileError<'src> {
- self.token().error(kind)
+ fn deref(&self) -> &Self::Target {
+ &self.token
}
}
diff --git a/src/namepath.rs b/src/namepath.rs
new file mode 100644
index 00000000..899b32fb
--- /dev/null
+++ b/src/namepath.rs
@@ -0,0 +1,28 @@
+use super::*;
+
+#[derive(Default, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
+pub(crate) struct Namepath<'src>(Vec<Name<'src>>);
+
+impl<'src> Namepath<'src> {
+ pub(crate) fn join(&self, name: Name<'src>) -> Self {
+ Self(self.0.iter().copied().chain(iter::once(name)).collect())
+ }
+}
+
+impl<'str> Serialize for Namepath<'str> {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ let mut path = String::new();
+
+ for (i, name) in self.0.iter().enumerate() {
+ if i > 0 {
+ path.push_str("::");
+ }
+ path.push_str(name.lexeme());
+ }
+
+ serializer.serialize_str(&path)
+ }
+}
diff --git a/src/parser.rs b/src/parser.rs
index 92941a83..d4662eac 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -23,34 +23,31 @@ use {super::*, TokenKind::*};
/// find it, it adds that token to the set. When the parser accepts a token, the
/// set is cleared. If the parser finds a token which is unexpected, the
/// contents of the set is printed in the resultant error message.
-pub(crate) struct Parser<'tokens, 'src> {
- /// Source tokens
- tokens: &'tokens [Token<'src>],
- /// Index of the next un-parsed token
- next: usize,
- /// Current expected tokens
- expected: BTreeSet<TokenKind>,
- /// Current recursion depth
- depth: usize,
- /// Path to the file being parsed
- path: PathBuf,
- /// Depth of submodule being parsed
- submodule: u32,
+pub(crate) struct Parser<'run, 'src> {
+ expected_tokens: BTreeSet<TokenKind>,
+ file_path: &'run Path,
+ module_namepath: &'run Namepath<'src>,
+ next_token: usize,
+ recursion_depth: usize,
+ submodule_depth: u32,
+ tokens: &'run [Token<'src>],
}
-impl<'tokens, 'src> Parser<'tokens, 'src> {
+impl<'run, 'src> Parser<'run, 'src> {
/// Parse `tokens` into an `Ast`
pub(crate) fn parse(
- submodule: u32,
- path: &Path,
- tokens: &'tokens [Token<'src>],
+ file_path: &'run Path,
+ module_namepath: &'run Namepath<'src>,
+ submodule_depth: u32,
+ tokens: &'run [Token<'src>],
) -> CompileResult<'src, Ast<'src>> {
- Parser {
- depth: 0,
- expected: BTreeSet::new(),
- next: 0,
- path: path.into(),
- submodule,
+ Self {
+ expected_tokens: BTreeSet::new(),
+ file_path,
+ module_namepath,
+ next_token: 0,
+ recursion_depth: 0,
+ submodule_depth,
tokens,
}
.parse_ast()
@@ -65,7 +62,7 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
fn unexpected_token(&self) -> CompileResult<'src, CompileError<'src>> {
self.error(CompileErrorKind::UnexpectedToken {
expected: self
- .expected
+ .expected_tokens
.iter()
.copied()
.filter(|kind| *kind != ByteOrderMark)
@@ -81,8 +78,8 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
}
/// An iterator over the remaining significant tokens
- fn rest(&self) -> impl Iterator<Item = Token<'src>> + 'tokens {
- self.tokens[self.next..]
+ fn rest(&self) -> impl Iterator<Item = Token<'src>> + 'run {
+ self.tokens[self.next_token..]
.iter()
.copied()
.filter(|token| token.kind != Whitespace)
@@ -107,7 +104,7 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
/// The first token in `kinds` will be added to the expected token set.
fn next_are(&mut self, kinds: &[TokenKind]) -> bool {
if let Some(&kind) = kinds.first() {
- self.expected.insert(kind);
+ self.expected_tokens.insert(kind);
}
let mut rest = self.rest();
@@ -126,10 +123,10 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
/// Advance past one significant token, clearing the expected token set.
fn advance(&mut self) -> CompileResult<'src, Token<'src>> {
- self.expected.clear();
+ self.expected_tokens.clear();
- for skipped in &self.tokens[self.next..] {
- self.next += 1;
+ for skipped in &self.tokens[self.next_token..] {
+ self.next_token += 1;
if skipped.kind != Whitespace {
return Ok(*skipped);
@@ -419,7 +416,7 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
}
}
- if self.next == self.tokens.len() {
+ if self.next_token == self.tokens.len() {
Ok(Ast {
warnings: Vec::new(),
items,
@@ -427,7 +424,7 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
} else {
Err(self.internal_error(format!(
"Parse completed with {} unparsed tokens",
- self.tokens.len() - self.next,
+ self.tokens.len() - self.next_token,
))?)
}
}
@@ -464,7 +461,7 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
/// Parse an expression, e.g. `1 + 2`
fn parse_expression(&mut self) -> CompileResult<'src, Expression<'src>> {
- if self.depth == if cfg!(windows) { 48 } else { 256 } {
+ if self.recursion_depth == if cfg!(windows) { 48 } else { 256 } {
let token = self.next()?;
return Err(CompileError::new(
token,
@@ -472,7 +469,7 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
));
}
- self.depth += 1;
+ self.recursion_depth += 1;
let expression = if self.accepted_keyword(Keyword::If)? {
self.parse_conditional()?
@@ -496,7 +493,7 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
}
};
- self.depth -= 1;
+ self.recursion_depth -= 1;
Ok(expression)
}
@@ -740,11 +737,12 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
doc,
name,
parameters: positional.into_iter().chain(variadic).collect(),
- path: self.path.clone(),
+ file_path: self.file_path.into(),
priors,
private: name.lexeme().starts_with('_'),
quiet,
- depth: self.submodule,
+ depth: self.submodule_depth,
+ namepath: self.module_namepath.join(name),
})
}
@@ -962,7 +960,8 @@ mod tests {
fn test(text: &str, want: Tree) {
let unindented = unindent(text);
let tokens = Lexer::test_lex(&unindented).expect("lexing failed");
- let justfile = Parser::parse(0, &PathBuf::new(), &tokens).expect("parsing failed");
+ let justfile =
+ Parser::parse(&PathBuf::new(), &Namepath::default(), 0, &tokens).expect("parsing failed");
let have = justfile.tree();
if have != want {
println!("parsed text: {unindented}");
@@ -1000,7 +999,7 @@ mod tests {
) {
let tokens = Lexer::test_lex(src).expect("Lexing failed in parse test...");
- match Parser::parse(0, &PathBuf::new(), &tokens) {
+ match Parser::parse(&PathBuf::new(), &Namepath::default(), 0, &tokens) {
Ok(_) => panic!("Parsing unexpectedly succeeded"),
Err(have) => {
let want = CompileError {
diff --git a/src/ran.rs b/src/ran.rs
new file mode 100644
index 00000000..a19c12db
--- /dev/null
+++ b/src/ran.rs
@@ -0,0 +1,18 @@
+use super::*;
+
+#[derive(Default)]
+pub(crate) struct Ran<'src>(BTreeMap<Namepath<'src>, BTreeSet<Vec<String>>>);
+
+impl<'src> Ran<'src> {
+ pub(crate) fn has_run(&self, recipe: &Namepath<'src>, arguments: &[String]) -> bool {
+ self
+ .0
+ .get(recipe)
+ .map(|ran| ran.contains(arguments))
+ .unwrap_or_default()
+ }
+
+ pub(crate) fn ran(&mut self, recipe: &Namepath<'src>, arguments: Vec<String>) {
+ self.0.entry(recipe.clone()).or_default().insert(arguments);
+ }
+}
diff --git a/src/recipe.rs b/src/recipe.rs
index 7acd1cea..01aa273f 100644
--- a/src/recipe.rs
+++ b/src/recipe.rs
@@ -25,17 +25,18 @@ pub(crate) struct Recipe<'src, D = Dependency<'src>> {
pub(crate) attributes: BTreeSet<Attribute>,
pub(crate) body: Vec<Line<'src>>,
pub(crate) dependencies: Vec<D>,
+ #[serde(skip)]
+ pub(crate) depth: u32,
pub(crate) doc: Option<&'src str>,
+ #[serde(skip)]
+ pub(crate) file_path: PathBuf,
pub(crate) name: Name<'src>,
+ pub(crate) namepath: Namepath<'src>,
pub(crate) parameters: Vec<Parameter<'src>>,
- #[serde(skip)]
- pub(crate) path: PathBuf,
pub(crate) priors: usize,
pub(crate) private: bool,
pub(crate) quiet: bool,
pub(crate) shebang: bool,
- #[serde(skip)]
- pub(crate) depth: u32,
}
impl<'src, D> Recipe<'src, D> {
@@ -223,7 +224,7 @@ impl<'src, D> Recipe<'src, D> {
if self.change_directory() {
cmd.current_dir(if self.depth > 0 {
- self.path.parent().unwrap()
+ self.file_path.parent().unwrap()
} else {
&context.search.working_directory
});
@@ -363,7 +364,7 @@ impl<'src, D> Recipe<'src, D> {
&path,
if self.change_directory() {
if self.depth > 0 {
- Some(self.path.parent().unwrap())
+ Some(self.file_path.parent().unwrap())
} else {
Some(&context.search.working_directory)
}
diff --git a/src/source.rs b/src/source.rs
new file mode 100644
index 00000000..8efd3c86
--- /dev/null
+++ b/src/source.rs
@@ -0,0 +1,33 @@
+use super::*;
+
+pub(crate) struct Source<'src> {
+ pub(crate) path: PathBuf,
+ pub(crate) depth: u32,
+ pub(crate) namepath: Namepath<'src>,
+}
+
+impl<'src> Source<'src> {
+ pub(crate) fn root(path: &Path) -> Self {
+ Self {
+ path: path.into(),
+ depth: 0,
+ namepath: Namepath::default(),
+ }
+ }
+
+ pub(crate) fn import(&self, path: PathBuf) -> Self {
+ Self {
+ depth: self.depth + 1,
+ path,
+ namepath: self.namepath.clone(),
+ }
+ }
+
+ pub(crate) fn module(&self, name: Name<'src>, path: PathBuf) -> Self {
+ Self {
+ path,
+ depth: self.depth + 1,
+ namepath: self.namepath.join(name),
+ }
+ }
+}
diff --git a/src/testing.rs b/src/testing.rs
index e97883f8..8a0e626b 100644
--- a/src/testing.rs
+++ b/src/testing.rs
@@ -59,7 +59,8 @@ pub(crate) fn analysis_error(
) {
let tokens = Lexer::test_lex(src).expect("Lexing failed in parse test...");
- let ast = Parser::parse(0, &PathBuf::new(), &tokens).expect("Parsing failed in analysis test...");
+ let ast = Parser::parse(&PathBuf::new(), &Namepath::default(), 0, &tokens)
+ .expect("Parsing failed in analysis test...");
let root = PathBuf::from("justfile");
let mut asts: HashMap<PathBuf, Ast> = HashMap::new();
diff --git a/src/token.rs b/src/token.rs
index d8a7bf21..5ff1a53a 100644
--- a/src/token.rs
+++ b/src/token.rs
@@ -1,6 +1,6 @@
use super::*;
-#[derive(Debug, PartialEq, Clone, Copy)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd)]
pub(crate) struct Token<'src> {
pub(crate) column: usize,
pub(crate) kind: TokenKind,
diff --git a/src/unresolved_recipe.rs b/src/unresolved_recipe.rs
index c545394a..170d0cd9 100644
--- a/src/unresolved_recipe.rs
+++ b/src/unresolved_recipe.rs
@@ -50,9 +50,10 @@ impl<'src> UnresolvedRecipe<'src> {
dependencies,
depth: self.depth,
doc: self.doc,
+ file_path: self.file_path,
name: self.name,
+ namepath: self.namepath,
parameters: self.parameters,
- path: self.path,
priors: self.priors,
private: self.private,
quiet: self.quiet,
diff --git a/src/variables.rs b/src/variables.rs
index 821f6503..8a17254a 100644
--- a/src/variables.rs
+++ b/src/variables.rs
@@ -60,7 +60,7 @@ impl<'expression, 'src> Iterator for Variables<'expression, 'src> {
self.stack.push(rhs);
self.stack.push(lhs);
}
- Expression::Variable { name, .. } => return Some(name.token()),
+ Expression::Variable { name, .. } => return Some(name.token),
Expression::Concatenation { lhs, rhs } => {
self.stack.push(rhs);
self.stack.push(lhs);
diff --git a/tests/json.rs b/tests/json.rs
index 48c2e45b..c70063f2 100644
--- a/tests/json.rs
+++ b/tests/json.rs
@@ -34,6 +34,7 @@ fn alias() {
"dependencies": [],
"doc": null,
"name": "foo",
+ "namepath": "foo",
"parameters": [],
"priors": 0,
"private": false,