Resolve alias targets (#548)

During analysis, resolve alias targets from `Name`s to `Rc<Recipe>`,
giving us type-level assurance that alias resolution was performed, and
avoiding the need to look up alias targets in a separate table when
running.
This commit is contained in:
Casey Rodarmor 2019-11-21 09:39:32 -06:00 committed by GitHub
parent 30c77f8d03
commit 4f08bb4d77
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 75 additions and 75 deletions

View File

@ -2,28 +2,39 @@ use crate::common::*;
/// An alias, e.g. `name := target`
#[derive(Debug, PartialEq)]
pub(crate) struct Alias<'src> {
pub(crate) struct Alias<'src, T = Rc<Recipe<'src>>> {
pub(crate) name: Name<'src>,
pub(crate) target: Name<'src>,
pub(crate) target: T,
}
impl<'src> Alias<'src, Name<'src>> {
pub(crate) fn line_number(&self) -> usize {
self.name.line
}
pub(crate) fn resolve(self, target: Rc<Recipe<'src>>) -> Alias<'src> {
assert_eq!(self.target.lexeme(), target.name.lexeme());
Alias {
name: self.name,
target,
}
}
}
impl Alias<'_> {
pub(crate) fn is_private(&self) -> bool {
self.name.lexeme().starts_with('_')
}
pub(crate) fn line_number(&self) -> usize {
self.name.line
}
}
impl<'src> Keyed<'src> for Alias<'src> {
impl<'src, T> Keyed<'src> for Alias<'src, T> {
fn key(&self) -> &'src str {
self.name.lexeme()
}
}
impl<'a> Display for Alias<'a> {
impl<'src> Display for Alias<'src, Name<'src>> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(
f,
@ -33,3 +44,14 @@ impl<'a> Display for Alias<'a> {
)
}
}
impl<'src> Display for Alias<'src> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(
f,
"alias {} := {}",
self.name.lexeme(),
self.target.name.lexeme()
)
}
}

View File

@ -1,52 +0,0 @@
use crate::common::*;
use CompilationErrorKind::*;
pub(crate) struct AliasResolver<'a, 'b>
where
'a: 'b,
{
aliases: &'b Table<'a, Alias<'a>>,
recipes: &'b Table<'a, Rc<Recipe<'a>>>,
}
impl<'a: 'b, 'b> AliasResolver<'a, 'b> {
pub(crate) fn resolve_aliases(
aliases: &Table<'a, Alias<'a>>,
recipes: &Table<'a, Rc<Recipe<'a>>>,
) -> CompilationResult<'a, ()> {
let resolver = AliasResolver { aliases, recipes };
resolver.resolve()?;
Ok(())
}
fn resolve(&self) -> CompilationResult<'a, ()> {
for alias in self.aliases.values() {
self.resolve_alias(alias)?;
}
Ok(())
}
fn resolve_alias(&self, alias: &Alias<'a>) -> CompilationResult<'a, ()> {
let token = alias.name.token();
// Make sure the alias doesn't conflict with any recipe
if let Some(recipe) = self.recipes.get(alias.name.lexeme()) {
return Err(token.error(AliasShadowsRecipe {
alias: alias.name.lexeme(),
recipe_line: recipe.line_number(),
}));
}
// Make sure the target recipe exists
if self.recipes.get(alias.target.lexeme()).is_none() {
return Err(token.error(UnknownAliasTarget {
alias: alias.name.lexeme(),
target: alias.target.lexeme(),
}));
}
Ok(())
}
}

View File

@ -5,7 +5,7 @@ use CompilationErrorKind::*;
pub(crate) struct Analyzer<'src> {
recipes: Table<'src, Recipe<'src, Name<'src>>>,
assignments: Table<'src, Assignment<'src>>,
aliases: Table<'src, Alias<'src>>,
aliases: Table<'src, Alias<'src, Name<'src>>>,
sets: Table<'src, Set<'src>>,
}
@ -51,7 +51,6 @@ impl<'src> Analyzer<'src> {
}
let assignments = self.assignments;
let aliases = self.aliases;
AssignmentResolver::resolve_assignments(&assignments)?;
@ -67,7 +66,10 @@ impl<'src> Analyzer<'src> {
}
}
AliasResolver::resolve_aliases(&aliases, &recipes)?;
let mut aliases = Table::new();
while let Some(alias) = self.aliases.pop() {
aliases.insert(Self::resolve_alias(&recipes, alias)?);
}
let mut settings = Settings::new();
@ -161,7 +163,7 @@ impl<'src> Analyzer<'src> {
Ok(())
}
fn analyze_alias(&self, alias: &Alias<'src>) -> CompilationResult<'src, ()> {
fn analyze_alias(&self, alias: &Alias<'src, Name<'src>>) -> CompilationResult<'src, ()> {
let name = alias.name.lexeme();
if let Some(original) = self.aliases.get(name) {
@ -184,6 +186,29 @@ impl<'src> Analyzer<'src> {
Ok(())
}
fn resolve_alias(
recipes: &Table<'src, Rc<Recipe<'src>>>,
alias: Alias<'src, Name<'src>>,
) -> CompilationResult<'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 {
alias: alias.name.lexeme(),
recipe_line: recipe.line_number(),
}));
}
// Make sure the target recipe exists
match recipes.get(alias.target.lexeme()) {
Some(target) => Ok(alias.resolve(target.clone())),
None => Err(token.error(UnknownAliasTarget {
alias: alias.name.lexeme(),
target: alias.target.lexeme(),
})),
}
}
}
#[cfg(test)]

View File

@ -47,7 +47,7 @@ pub(crate) use crate::{
// structs and enums
pub(crate) use crate::{
alias::Alias, alias_resolver::AliasResolver, analyzer::Analyzer, assignment::Assignment,
alias::Alias, analyzer::Analyzer, assignment::Assignment,
assignment_evaluator::AssignmentEvaluator, assignment_resolver::AssignmentResolver, color::Color,
compilation_error::CompilationError, compilation_error_kind::CompilationErrorKind,
compiler::Compiler, config::Config, config_error::ConfigError, count::Count,

View File

@ -441,10 +441,10 @@ impl Config {
continue;
}
if !recipe_aliases.contains_key(alias.target.lexeme()) {
recipe_aliases.insert(alias.target.lexeme(), vec![alias.name.lexeme()]);
if !recipe_aliases.contains_key(alias.target.name.lexeme()) {
recipe_aliases.insert(alias.target.name.lexeme(), vec![alias.name.lexeme()]);
} else {
let aliases = recipe_aliases.get_mut(alias.target.lexeme()).unwrap();
let aliases = recipe_aliases.get_mut(alias.target.name.lexeme()).unwrap();
aliases.push(alias.name.lexeme());
}
}
@ -542,7 +542,7 @@ impl Config {
fn show(&self, name: &str, justfile: Justfile) -> Result<(), i32> {
if let Some(alias) = justfile.get_alias(name) {
let recipe = justfile.get_recipe(alias.target.lexeme()).unwrap();
let recipe = justfile.get_recipe(alias.target.name.lexeme()).unwrap();
println!("{}", alias);
println!("{}", recipe);
Ok(())

View File

@ -3,7 +3,7 @@ use crate::common::*;
/// A single top-level item
#[derive(Debug)]
pub(crate) enum Item<'src> {
Alias(Alias<'src>),
Alias(Alias<'src, Name<'src>>),
Assignment(Assignment<'src>),
Recipe(Recipe<'src, Name<'src>>),
Set(Set<'src>),

View File

@ -4,7 +4,7 @@ use crate::common::*;
pub(crate) struct Justfile<'src> {
pub(crate) recipes: Table<'src, Rc<Recipe<'src>>>,
pub(crate) assignments: Table<'src, Assignment<'src>>,
pub(crate) aliases: Table<'src, Alias<'src>>,
pub(crate) aliases: Table<'src, Alias<'src, Rc<Recipe<'src>>>>,
pub(crate) settings: Settings<'src>,
pub(crate) warnings: Vec<Warning<'src>>,
}
@ -166,7 +166,7 @@ impl<'src> Justfile<'src> {
if let Some(recipe) = self.recipes.get(name) {
Some(recipe)
} else if let Some(alias) = self.aliases.get(name) {
self.recipes.get(alias.target.lexeme()).map(Rc::as_ref)
Some(alias.target.as_ref())
} else {
None
}

View File

@ -16,7 +16,6 @@ pub mod node;
pub(crate) mod fuzzing;
mod alias;
mod alias_resolver;
mod analyzer;
mod assignment;
mod assignment_evaluator;

View File

@ -26,7 +26,7 @@ impl<'src> Node<'src> for Item<'src> {
}
}
impl<'src> Node<'src> for Alias<'src> {
impl<'src> Node<'src> for Alias<'src, Name<'src>> {
fn tree(&self) -> Tree<'src> {
Tree::atom(keyword::ALIAS)
.push(self.name.lexeme())

View File

@ -325,7 +325,7 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
}
/// Parse an alias, e.g `alias name := target`
fn parse_alias(&mut self) -> CompilationResult<'src, Alias<'src>> {
fn parse_alias(&mut self) -> CompilationResult<'src, Alias<'src, Name<'src>>> {
self.presume_name(keyword::ALIAS)?;
let name = self.parse_name()?;
self.presume_any(&[Equals, ColonEquals])?;

View File

@ -8,6 +8,12 @@ pub(crate) struct Table<'key, V: Keyed<'key>> {
}
impl<'key, V: Keyed<'key>> Table<'key, V> {
pub(crate) fn new() -> Table<'key, V> {
Table {
map: BTreeMap::new(),
}
}
pub(crate) fn insert(&mut self, value: V) {
self.map.insert(value.key(), value);
}