diff options
-rw-r--r-- | src/main.rs | 17 | ||||
-rw-r--r-- | src/makefile/eval_context.rs | 7 | ||||
-rw-r--r-- | src/makefile/functions.rs | 218 | ||||
-rw-r--r-- | src/makefile/input.rs | 49 | ||||
-rw-r--r-- | src/makefile/macro.rs | 252 | ||||
-rw-r--r-- | src/makefile/macro_scope.rs | 211 | ||||
-rw-r--r-- | src/makefile/mod.rs | 16 |
7 files changed, 394 insertions, 376 deletions
diff --git a/src/main.rs b/src/main.rs index d4c0cdc..0b3e9f7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,7 +32,7 @@ mod args; mod makefile; use args::Args; -use makefile::{Makefile, MakefileReader}; +use makefile::{MacroScopeStack, MacroSet, Makefile, MakefileReader}; const DEFAULT_PATHS: &[&str] = &[ #[cfg(feature = "full")] @@ -66,14 +66,16 @@ fn main() -> Result<()> { let mut makefile = Makefile::new(&args); let paths = Default::default(); for filename in &args.makefile { - let macros = makefile.macros.with_overlay(); + let stack = MacroScopeStack::default().with_scope(&makefile.macros); if filename == &PathBuf::from("-") { - let file = MakefileReader::read(&args, macros, stdin().lock(), "-", Rc::clone(&paths))? - .finish(); + let macros = MacroSet::new(); + let file = + MakefileReader::read(&args, stack, macros, stdin().lock(), "-", Rc::clone(&paths))? + .finish(); makefile.extend(file)?; } else { let file = - MakefileReader::read_file(&args, macros, filename, Rc::clone(&paths))?.finish(); + MakefileReader::read_file(&args, stack, filename, Rc::clone(&paths))?.finish(); makefile.extend(file)?; }; } @@ -91,9 +93,8 @@ fn main() -> Result<()> { for outdated in makefiles_outdated { eprintln!("makefile {} out of date, rebuilding", outdated); makefile.update_target(&outdated)?; - let macros = makefile.macros.with_overlay(); - let file = - MakefileReader::read_file(&args, macros, &outdated, Default::default())?.finish(); + let stack = MacroScopeStack::default().with_scope(&makefile.macros); + let file = MakefileReader::read_file(&args, stack, &outdated, Default::default())?.finish(); // TODO forget the stale data // TODO reread all the things, not just this one makefile.extend(file)?; diff --git a/src/makefile/eval_context.rs b/src/makefile/eval_context.rs index 06f4fbf..ba5f4a0 100644 --- a/src/makefile/eval_context.rs +++ b/src/makefile/eval_context.rs @@ -2,8 +2,7 @@ use eyre::{Result, WrapErr}; use std::io::{BufRead, Cursor}; use std::rc::Rc; -use crate::makefile::input::FinishedMakefileReader; -use crate::makefile::MakefileReader; +use super::{FinishedMakefileReader, MacroSet, MakefileReader}; pub struct DeferredEvalContext<'parent, 'args, 'grandparent, R: BufRead> { parent: &'parent MakefileReader<'args, 'grandparent, R>, @@ -25,9 +24,11 @@ impl<'parent, 'args, 'grandparent, R: BufRead> } pub fn eval(&mut self, to_eval: String) -> Result<()> { - let child_macros = self.parent.macros.with_overlay(); + let child_stack = self.parent.stack.with_scope(&self.parent.macros); + let child_macros = MacroSet::new(); let child = MakefileReader::read( self.parent.args, + child_stack, child_macros, Cursor::new(to_eval), "<eval>", diff --git a/src/makefile/functions.rs b/src/makefile/functions.rs index 1f3c897..05f6750 100644 --- a/src/makefile/functions.rs +++ b/src/makefile/functions.rs @@ -6,9 +6,8 @@ use eyre::{bail, Result, WrapErr}; use super::eval_context::DeferredEvalContext; use super::pattern::r#match; -use super::r#macro::{Macro, Set as MacroSet}; -use super::token::TokenString; -use super::ItemSource; +use super::r#macro::Macro; +use super::{ItemSource, MacroScopeStack, MacroSet, TokenString}; pub const NO_EVAL: Option<&mut DeferredEvalContext<&[u8]>> = None; @@ -16,114 +15,114 @@ pub const NO_EVAL: Option<&mut DeferredEvalContext<&[u8]>> = None; pub fn expand_call( name: &str, args: &[TokenString], - macros: &MacroSet, + stack: &MacroScopeStack, mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, ) -> Result<String> { match name { "subst" => { assert_eq!(args.len(), 3); - text::subst(macros, &args[0], &args[1], &args[2], eval_context) + text::subst(stack, &args[0], &args[1], &args[2], eval_context) } "patsubst" => { assert_eq!(args.len(), 3); - text::patsubst(macros, &args[0], &args[1], &args[2], eval_context) + text::patsubst(stack, &args[0], &args[1], &args[2], eval_context) } "strip" => { assert_eq!(args.len(), 1); - text::strip(macros, &args[0], eval_context) + text::strip(stack, &args[0], eval_context) } "findstring" => { assert_eq!(args.len(), 2); - text::findstring(macros, &args[0], &args[1], eval_context) + text::findstring(stack, &args[0], &args[1], eval_context) } "filter" => { assert_eq!(args.len(), 2); - text::filter(macros, &args[0], &args[1], eval_context) + text::filter(stack, &args[0], &args[1], eval_context) } "filter-out" => { assert_eq!(args.len(), 2); - text::filter_out(macros, &args[0], &args[1], eval_context) + text::filter_out(stack, &args[0], &args[1], eval_context) } "sort" => { assert_eq!(args.len(), 1); - text::sort(macros, &args[0], eval_context) + text::sort(stack, &args[0], eval_context) } "word" => { assert_eq!(args.len(), 2); - text::word(macros, &args[0], &args[1], eval_context) + text::word(stack, &args[0], &args[1], eval_context) } "words" => { assert_eq!(args.len(), 1); - text::words(macros, &args[0], eval_context) + text::words(stack, &args[0], eval_context) } "firstword" => { assert_eq!(args.len(), 1); - text::firstword(macros, &args[0], eval_context) + text::firstword(stack, &args[0], eval_context) } "lastword" => { assert_eq!(args.len(), 1); - text::lastword(macros, &args[0], eval_context) + text::lastword(stack, &args[0], eval_context) } "dir" => { assert_eq!(args.len(), 1); - file_name::dir(macros, &args[0], eval_context) + file_name::dir(stack, &args[0], eval_context) } "notdir" => { assert_eq!(args.len(), 1); - file_name::notdir(macros, &args[0], eval_context) + file_name::notdir(stack, &args[0], eval_context) } "basename" => { assert_eq!(args.len(), 1); - file_name::basename(macros, &args[0], eval_context) + file_name::basename(stack, &args[0], eval_context) } "addsuffix" => { assert_eq!(args.len(), 2); - file_name::addsuffix(macros, &args[0], &args[1], eval_context) + file_name::addsuffix(stack, &args[0], &args[1], eval_context) } "addprefix" => { assert_eq!(args.len(), 2); - file_name::addprefix(macros, &args[0], &args[1], eval_context) + file_name::addprefix(stack, &args[0], &args[1], eval_context) } "wildcard" => { assert_eq!(args.len(), 1); - file_name::wildcard(macros, &args[0], eval_context) + file_name::wildcard(stack, &args[0], eval_context) } "realpath" => { assert_eq!(args.len(), 1); - file_name::realpath(macros, &args[0], eval_context) + file_name::realpath(stack, &args[0], eval_context) } "abspath" => { assert_eq!(args.len(), 1); - file_name::abspath(macros, &args[0], eval_context) + file_name::abspath(stack, &args[0], eval_context) } "if" => { assert!(args.len() == 2 || args.len() == 3); - conditional::r#if(macros, &args[0], &args[1], args.get(2), eval_context) + conditional::r#if(stack, &args[0], &args[1], args.get(2), eval_context) } "or" => { assert!(!args.is_empty()); - conditional::or(macros, args.iter(), eval_context) + conditional::or(stack, args.iter(), eval_context) } "and" => { assert!(!args.is_empty()); - conditional::and(macros, args.iter(), eval_context) + conditional::and(stack, args.iter(), eval_context) } "foreach" => { assert_eq!(args.len(), 3); - foreach(macros, &args[0], &args[1], &args[2], eval_context) + foreach(stack, &args[0], &args[1], &args[2], eval_context) } "call" => { assert!(!args.is_empty()); - call(macros, args.iter(), eval_context) + call(stack, args.iter(), eval_context) } "eval" => { assert_eq!(args.len(), 1); - let should_eval = eval(macros, &args[0], eval_context.as_deref_mut())?; + let should_eval = eval(stack, &args[0], eval_context.as_deref_mut())?; if let Some(eval_context) = eval_context { eval_context.eval(should_eval)?; } else { @@ -134,17 +133,17 @@ pub fn expand_call( "origin" => { assert_eq!(args.len(), 1); - origin(macros, &args[0], eval_context) + origin(stack, &args[0], eval_context) } "error" => { assert_eq!(args.len(), 1); - meta::error(macros, &args[0], eval_context) + meta::error(stack, &args[0], eval_context) } "shell" => { assert_eq!(args.len(), 1); - shell(macros, &args[0], eval_context) + shell(stack, &args[0], eval_context) } // fallback @@ -157,28 +156,28 @@ mod text { use super::*; pub fn subst( - macros: &MacroSet, + stack: &MacroScopeStack, from: &TokenString, to: &TokenString, text: &TokenString, mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, ) -> Result<String> { - let from = macros.expand(from, eval_context.as_deref_mut())?; - let to = macros.expand(to, eval_context.as_deref_mut())?; - let text = macros.expand(text, eval_context)?; + let from = stack.expand(from, eval_context.as_deref_mut())?; + let to = stack.expand(to, eval_context.as_deref_mut())?; + let text = stack.expand(text, eval_context)?; Ok(text.replace(&from, &to)) } pub fn patsubst( - macros: &MacroSet, + stack: &MacroScopeStack, from: &TokenString, to: &TokenString, text: &TokenString, mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, ) -> Result<String> { - let from = macros.expand(from, eval_context.as_deref_mut())?; - let to = macros.expand(to, eval_context.as_deref_mut())?; - let text = macros.expand(text, eval_context)?; + let from = stack.expand(from, eval_context.as_deref_mut())?; + let to = stack.expand(to, eval_context.as_deref_mut())?; + let text = stack.expand(text, eval_context)?; let words = text.split_whitespace() .map(|word| { @@ -191,24 +190,24 @@ mod text { } pub fn strip( - macros: &MacroSet, + stack: &MacroScopeStack, text: &TokenString, eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, ) -> Result<String> { - let text = macros.expand(text, eval_context)?; + let text = stack.expand(text, eval_context)?; // TODO don't allocate this vec let words = text.split_whitespace().collect::<Vec<_>>(); Ok(words.join(" ")) } pub fn findstring( - macros: &MacroSet, + stack: &MacroScopeStack, needle: &TokenString, haystack: &TokenString, mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, ) -> Result<String> { - let needle = macros.expand(needle, eval_context.as_deref_mut())?; - let haystack = macros.expand(haystack, eval_context)?; + let needle = stack.expand(needle, eval_context.as_deref_mut())?; + let haystack = stack.expand(haystack, eval_context)?; if haystack.contains(&needle) { Ok(needle) } else { @@ -217,14 +216,14 @@ mod text { } pub fn filter( - macros: &MacroSet, + stack: &MacroScopeStack, patterns: &TokenString, text: &TokenString, mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, ) -> Result<String> { - let patterns = macros.expand(patterns, eval_context.as_deref_mut())?; + let patterns = stack.expand(patterns, eval_context.as_deref_mut())?; let patterns = patterns.split_whitespace().collect::<Vec<_>>(); - let text = macros.expand(text, eval_context)?; + let text = stack.expand(text, eval_context)?; let text = text.split_whitespace(); let mut result_pieces = vec![]; for word in text { @@ -239,14 +238,14 @@ mod text { } pub fn filter_out( - macros: &MacroSet, + stack: &MacroScopeStack, patterns: &TokenString, text: &TokenString, mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, ) -> Result<String> { - let patterns = macros.expand(patterns, eval_context.as_deref_mut())?; + let patterns = stack.expand(patterns, eval_context.as_deref_mut())?; let patterns = patterns.split_whitespace().collect::<Vec<_>>(); - let text = macros.expand(text, eval_context)?; + let text = stack.expand(text, eval_context)?; let text = text.split_whitespace(); let mut result_pieces = vec![]; for word in text { @@ -261,11 +260,11 @@ mod text { } pub fn sort( - macros: &MacroSet, + stack: &MacroScopeStack, words: &TokenString, eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, ) -> Result<String> { - let words = macros.expand(words, eval_context)?; + let words = stack.expand(words, eval_context)?; let mut words = words.split_whitespace().collect::<Vec<_>>(); words.sort_unstable(); words.dedup(); @@ -273,14 +272,14 @@ mod text { } pub fn word( - macros: &MacroSet, + stack: &MacroScopeStack, n: &TokenString, text: &TokenString, mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, ) -> Result<String> { - let n = macros.expand(n, eval_context.as_deref_mut())?; + let n = stack.expand(n, eval_context.as_deref_mut())?; let n: usize = n.parse().wrap_err("while calling `word`")?; - let text = macros.expand(text, eval_context)?; + let text = stack.expand(text, eval_context)?; Ok(text .split_whitespace() .nth(n.saturating_add(1)) @@ -289,29 +288,29 @@ mod text { } pub fn words( - macros: &MacroSet, + stack: &MacroScopeStack, words: &TokenString, eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, ) -> Result<String> { - let words = macros.expand(words, eval_context)?; + let words = stack.expand(words, eval_context)?; Ok(words.split_whitespace().count().to_string()) } pub fn firstword( - macros: &MacroSet, + stack: &MacroScopeStack, words: &TokenString, eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, ) -> Result<String> { - let words = macros.expand(words, eval_context)?; + let words = stack.expand(words, eval_context)?; Ok(words.split_whitespace().next().unwrap_or("").to_owned()) } pub fn lastword( - macros: &MacroSet, + stack: &MacroScopeStack, words: &TokenString, eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, ) -> Result<String> { - let words = macros.expand(words, eval_context)?; + let words = stack.expand(words, eval_context)?; Ok(words.split_whitespace().last().unwrap_or("").to_owned()) } } @@ -329,11 +328,11 @@ mod file_name { use eyre::WrapErr; pub fn dir( - macros: &MacroSet, + stack: &MacroScopeStack, words: &TokenString, eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, ) -> Result<String> { - let words = macros.expand(words, eval_context)?; + let words = stack.expand(words, eval_context)?; let words = words .split_whitespace() .map(|word| { @@ -349,11 +348,11 @@ mod file_name { } pub fn notdir( - macros: &MacroSet, + stack: &MacroScopeStack, words: &TokenString, eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, ) -> Result<String> { - let words = macros.expand(words, eval_context)?; + let words = stack.expand(words, eval_context)?; let words = words .split_whitespace() .map(|word| { @@ -367,11 +366,11 @@ mod file_name { } pub fn basename( - macros: &MacroSet, + stack: &MacroScopeStack, words: &TokenString, eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, ) -> Result<String> { - let words = macros.expand(words, eval_context)?; + let words = stack.expand(words, eval_context)?; let words = words .split_whitespace() .map(|word| { @@ -385,13 +384,13 @@ mod file_name { } pub fn addsuffix( - macros: &MacroSet, + stack: &MacroScopeStack, suffix: &TokenString, targets: &TokenString, mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, ) -> Result<String> { - let suffix = macros.expand(suffix, eval_context.as_deref_mut())?; - let targets = macros.expand(targets, eval_context)?; + let suffix = stack.expand(suffix, eval_context.as_deref_mut())?; + let targets = stack.expand(targets, eval_context)?; let results = targets .split_whitespace() .map(|t| format!("{}{}", t, suffix)) @@ -400,13 +399,13 @@ mod file_name { } pub fn addprefix( - macros: &MacroSet, + stack: &MacroScopeStack, prefix: &TokenString, targets: &TokenString, mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, ) -> Result<String> { - let prefix = macros.expand(prefix, eval_context.as_deref_mut())?; - let targets = macros.expand(targets, eval_context)?; + let prefix = stack.expand(prefix, eval_context.as_deref_mut())?; + let targets = stack.expand(targets, eval_context)?; let results = targets .split_whitespace() .map(|t| format!("{}{}", prefix, t)) @@ -415,11 +414,11 @@ mod file_name { } pub fn wildcard( - macros: &MacroSet, + stack: &MacroScopeStack, pattern: &TokenString, eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, ) -> Result<String> { - let pattern = macros.expand(pattern, eval_context)?; + let pattern = stack.expand(pattern, eval_context)?; let home_dir = env::var("HOME") .ok() .or_else(|| dirs::home_dir().and_then(|p| p.to_str().map(String::from))); @@ -439,11 +438,11 @@ mod file_name { } pub fn realpath( - macros: &MacroSet, + stack: &MacroScopeStack, targets: &TokenString, eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, ) -> Result<String> { - let targets = macros.expand(targets, eval_context)?; + let targets = stack.expand(targets, eval_context)?; let results = targets .split_whitespace() .map(|x| { @@ -456,12 +455,12 @@ mod file_name { } pub fn abspath( - macros: &MacroSet, + stack: &MacroScopeStack, targets: &TokenString, eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, ) -> Result<String> { // TODO don't resolve symlinks - realpath(macros, targets, eval_context) + realpath(stack, targets, eval_context) } } @@ -470,7 +469,7 @@ mod conditional { use super::*; pub fn r#if( - macros: &MacroSet, + stack: &MacroScopeStack, condition: &TokenString, if_true: &TokenString, if_false: Option<&TokenString>, @@ -479,24 +478,24 @@ mod conditional { let mut condition = condition.clone(); condition.trim_start(); condition.trim_end(); - let condition = macros.expand(&condition, eval_context.as_deref_mut())?; + let condition = stack.expand(&condition, eval_context.as_deref_mut())?; if condition.is_empty() { if_false.map_or_else( || Ok(String::new()), - |if_false| macros.expand(if_false, eval_context), + |if_false| stack.expand(if_false, eval_context), ) } else { - macros.expand(if_true, eval_context) + stack.expand(if_true, eval_context) } } pub fn or<'a>( - macros: &MacroSet, + stack: &MacroScopeStack, args: impl Iterator<Item = &'a TokenString>, mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, ) -> Result<String> { for arg in args { - let arg = macros.expand(arg, eval_context.as_deref_mut())?; + let arg = stack.expand(arg, eval_context.as_deref_mut())?; if !arg.is_empty() { return Ok(arg); } @@ -505,13 +504,13 @@ mod conditional { } pub fn and<'a>( - macros: &MacroSet, + stack: &MacroScopeStack, args: impl Iterator<Item = &'a TokenString>, mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, ) -> Result<String> { let mut last = String::new(); for arg in args { - last = macros.expand(arg, eval_context.as_deref_mut())?; + last = stack.expand(arg, eval_context.as_deref_mut())?; if last.is_empty() { return Ok(String::new()); } @@ -521,19 +520,19 @@ mod conditional { } pub fn foreach( - macros: &MacroSet, + stack: &MacroScopeStack, var: &TokenString, list: &TokenString, text: &TokenString, mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, ) -> Result<String> { - let var = macros.expand(var, eval_context.as_deref_mut())?; - let list = macros.expand(list, eval_context.as_deref_mut())?; + let var = stack.expand(var, eval_context.as_deref_mut())?; + let list = stack.expand(list, eval_context.as_deref_mut())?; let words = list.split_whitespace(); - let mut macros = macros.with_overlay(); let results = words .map(|word| { + let mut macros = MacroSet::new(); macros.set( var.clone(), Macro { @@ -543,24 +542,26 @@ pub fn foreach( eagerly_expanded: false, }, ); - macros.expand(text, eval_context.as_deref_mut()) + stack + .with_scope(¯os) + .expand(text, eval_context.as_deref_mut()) }) .collect::<Result<Vec<_>, _>>()?; Ok(results.join(" ")) } pub fn call<'a>( - macros: &MacroSet, + stack: &MacroScopeStack, args: impl Iterator<Item = &'a TokenString>, mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, ) -> Result<String> { let args = args - .map(|arg| macros.expand(arg, eval_context.as_deref_mut())) + .map(|arg| stack.expand(arg, eval_context.as_deref_mut())) .collect::<Result<Vec<_>, _>>()?; let function = args[0].clone(); // TODO if function is a builtin, call the builtin instead - let mut macros = macros.with_overlay(); + let mut macros = MacroSet::new(); for (i, x) in args.into_iter().enumerate() { macros.set( i.to_string(), @@ -572,47 +573,49 @@ pub fn call<'a>( }, ); } - macros.expand(&TokenString::r#macro(function), eval_context) + stack + .with_scope(¯os) + .expand(&TokenString::r#macro(function), eval_context) } // TODO consider bringing eval logic in here since we put the Vec in MacroSet IIRC pub fn eval( - macros: &MacroSet, + stack: &MacroScopeStack, arg: &TokenString, eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, ) -> Result<String> { - macros.expand(arg, eval_context) + stack.expand(arg, eval_context) } pub fn origin( - macros: &MacroSet, + stack: &MacroScopeStack, variable: &TokenString, eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, ) -> Result<String> { - let variable = macros.expand(variable, eval_context)?; - Ok(macros.origin(&variable).to_owned()) + let variable = stack.expand(variable, eval_context)?; + Ok(stack.origin(&variable).to_owned()) } mod meta { use super::*; pub fn error( - macros: &MacroSet, + stack: &MacroScopeStack, text: &TokenString, eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, ) -> Result<String> { - let text = macros.expand(text, eval_context)?; + let text = stack.expand(text, eval_context)?; bail!("{}", text); } } pub fn shell( - macros: &MacroSet, + stack: &MacroScopeStack, command: &TokenString, eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, ) -> Result<String> { // TODO bring this in from command_line - let command = macros.expand(command, eval_context)?; + let command = stack.expand(command, eval_context)?; let (program, args) = if cfg!(windows) { let cmd = env::var("COMSPEC").unwrap_or_else(|_| "cmd.exe".into()); let args = vec!["/c", &command]; @@ -641,7 +644,8 @@ mod test { type R = Result<()>; fn call(name: &str, args: &[TokenString], macros: &MacroSet) -> Result<String> { - expand_call(name, args, macros, NO_EVAL) + let stack = MacroScopeStack::default().with_scope(macros); + expand_call(name, args, &stack, NO_EVAL) } macro_rules! call { diff --git a/src/makefile/input.rs b/src/makefile/input.rs index bbfdaee..40720c2 100644 --- a/src/makefile/input.rs +++ b/src/makefile/input.rs @@ -13,19 +13,19 @@ use regex::Regex; use crate::args::Args; -use super::command_line::CommandLine; #[cfg(feature = "full")] use super::conditional::{Line as ConditionalLine, State as ConditionalState}; #[cfg(feature = "full")] use super::eval_context::DeferredEvalContext; -use super::inference_rules::InferenceRule; #[cfg(feature = "full")] use super::r#macro::ExportConfig; -use super::r#macro::{Macro, Set as MacroSet}; -use super::target::{StaticTargetSet, Target}; -use super::token::{Token, TokenString}; -use super::LookupInternal; -use super::{builtin_targets, ItemSource}; +use super::r#macro::Macro; +use super::target::StaticTargetSet; +use super::token::Token; +use super::{ + builtin_targets, CommandLine, InferenceRule, ItemSource, LookupInternal, MacroScopeStack, + MacroSet, Target, TokenString, +}; enum LineType { Rule, @@ -188,7 +188,8 @@ impl Default for NextLineSettings { pub struct MakefileReader<'a, 'parent, R: BufRead> { file_name: String, pub inference_rules: Vec<InferenceRule>, - pub macros: MacroSet<'parent, 'static>, + pub stack: MacroScopeStack<'parent>, + pub macros: MacroSet, pub targets: StaticTargetSet, built_in_targets: HashMap<String, Target>, pub first_non_special_target: Option<String>, @@ -205,10 +206,11 @@ pub struct MakefileReader<'a, 'parent, R: BufRead> { impl<'a, 'parent> MakefileReader<'a, 'parent, BufReader<File>> { pub fn read_file( args: &'a Args, - mut macros: MacroSet<'parent, 'static>, + stack: MacroScopeStack<'parent>, path: impl AsRef<Path>, file_names: Rc<RefCell<Vec<String>>>, ) -> Result<Self> { + let mut macros = MacroSet::new(); #[cfg(feature = "full")] if let Some(mut old_makefile_list) = macros.pop("MAKEFILE_LIST") { old_makefile_list.text.extend(TokenString::text(format!( @@ -233,14 +235,15 @@ impl<'a, 'parent> MakefileReader<'a, 'parent, BufReader<File>> { // TODO handle errors let file = file.context("couldn't open makefile!")?; let file_reader = BufReader::new(file); - Self::read(args, macros, file_reader, file_name, file_names) + Self::read(args, stack, macros, file_reader, file_name, file_names) } } impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> { pub fn read( args: &'a Args, - macros: MacroSet<'parent, 'static>, + stack: MacroScopeStack<'parent>, + macros: MacroSet, source: R, name: impl Into<String>, file_names: Rc<RefCell<Vec<String>>>, @@ -249,6 +252,7 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> { let mut reader = Self { file_name: name.clone(), inference_rules: Vec::new(), + stack, macros, targets: Default::default(), built_in_targets: HashMap::new(), @@ -525,10 +529,10 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> { // handles arbitrarily many filenames, and it's not like that's more work for field in fields { log::trace!("{}:{}: including {}", &self.file_name, line_number, field); - let child_macros = self.macros.with_overlay(); + let child_stack = self.stack.with_scope(&self.macros); let child = MakefileReader::read_file( self.args, - child_macros, + child_stack, field, Rc::clone(&self.file_names), ) @@ -591,8 +595,9 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> { #[cfg(feature = "full")] let mut deferred_eval_context = DeferredEvalContext::new(self); let prerequisites = self - .macros - .with_lookup(LookupInternal::new_partial(&targets)) + .stack + .with_scope(&self.macros) + .with_scope(&LookupInternal::new_partial(&targets)) .expand( &prerequisites, #[cfg(feature = "full")] @@ -890,7 +895,8 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> { text: &TokenString, #[cfg(feature = "full")] deferred_eval_context: &mut DeferredEvalContext<R>, ) -> Result<String> { - self.macros + self.stack + .with_scope(&self.macros) .expand( text, #[cfg(feature = "full")] @@ -960,6 +966,7 @@ a: $(x) b \\ let args = Args::empty(); let makefile = MakefileReader::read( &args, + MacroScopeStack::default(), MacroSet::new(), Cursor::new(file), "", @@ -988,6 +995,7 @@ endif let args = Args::empty(); let mut makefile = MakefileReader::read( &args, + MacroScopeStack::default(), MacroSet::new(), Cursor::new(file), "", @@ -1012,6 +1020,7 @@ endif let args = Args::empty(); let makefile = MakefileReader::read( &args, + MacroScopeStack::default(), MacroSet::new(), Cursor::new(file), "", @@ -1034,6 +1043,7 @@ endef let args = Args::empty(); let mut makefile = MakefileReader::read( &args, + MacroScopeStack::default(), MacroSet::new(), Cursor::new(file), "", @@ -1062,6 +1072,7 @@ FOO = bar let args = Args::empty(); let mut makefile = MakefileReader::read( &args, + MacroScopeStack::default(), MacroSet::new(), Cursor::new(file), "", @@ -1110,6 +1121,7 @@ clean: let args = Args::empty(); let makefile = MakefileReader::read( &args, + MacroScopeStack::default(), MacroSet::new(), Cursor::new(file), "", @@ -1131,6 +1143,7 @@ info: let args = Args::empty(); let makefile = MakefileReader::read( &args, + MacroScopeStack::default(), MacroSet::new(), Cursor::new(file), "", @@ -1181,6 +1194,7 @@ cursed: let args = Args::empty(); let makefile = MakefileReader::read( &args, + MacroScopeStack::default(), MacroSet::new(), Cursor::new(file), "", @@ -1205,6 +1219,7 @@ cursed: let args = Args::empty(); let makefile = MakefileReader::read( &args, + MacroScopeStack::default(), MacroSet::new(), Cursor::new(file), "", @@ -1226,6 +1241,7 @@ test: c let args = Args::empty(); let makefile = MakefileReader::read( &args, + MacroScopeStack::default(), MacroSet::new(), Cursor::new(file), "", @@ -1246,6 +1262,7 @@ test: c let args = Args::empty(); let makefile = MakefileReader::read( &args, + MacroScopeStack::default(), MacroSet::new(), Cursor::new(file), "", diff --git a/src/makefile/macro.rs b/src/makefile/macro.rs index 4a82fe9..fb0367b 100644 --- a/src/makefile/macro.rs +++ b/src/makefile/macro.rs @@ -1,20 +1,16 @@ -use std::cell::RefCell; use std::collections::HashMap; use std::collections::HashSet; use std::env; use std::fmt; #[cfg(feature = "full")] use std::io::BufRead; -use std::rc::Rc; + #[cfg(feature = "full")] -use super::functions; -use super::token::{Token, TokenString}; +use super::eval_context::DeferredEvalContext; use super::ItemSource; -use super::LookupInternal; -#[cfg(feature = "full")] -use crate::makefile::eval_context::DeferredEvalContext; -use eyre::{bail, Result, WrapErr}; +use super::{MacroScopeStack, TokenString}; +use eyre::Result; #[cfg(not(feature = "full"))] use regex::Regex; @@ -69,13 +65,6 @@ impl ExportConfig { } } - fn same_type(&self) -> Self { - match self { - Self::Only(_) => Self::only(), - Self::AllBut(_) => Self::all_but(), - } - } - fn should_export(&self, x: &str) -> bool { match self { Self::Only(exported) => exported.contains(x), @@ -84,25 +73,19 @@ impl ExportConfig { } } -#[derive(Clone)] -pub struct Set<'parent, 'lookup> { - parent: Option<&'parent Set<'parent, 'lookup>>, +#[derive(Clone, Debug)] +pub struct Set { pub data: HashMap<String, Macro>, - lookup_internal: Option<LookupInternal<'lookup>>, #[cfg(feature = "full")] pub exported: ExportConfig, - warnings: Rc<RefCell<HashSet<String>>>, } -impl<'parent, 'lookup> Set<'parent, 'lookup> { +impl Set { pub fn new() -> Self { Self { - parent: None, data: HashMap::new(), - lookup_internal: None, #[cfg(feature = "full")] exported: ExportConfig::only(), - warnings: Default::default(), } } @@ -136,23 +119,8 @@ impl<'parent, 'lookup> Set<'parent, 'lookup> { } } - fn lookup_internal(&self, name: &str) -> Result<String> { - if let Some(lookup) = self.lookup_internal.as_ref() { - lookup.lookup(name) - } else if let Some(parent) = self.parent { - parent.lookup_internal(name) - } else { - bail!( - "tried to lookup {:?} but no lookup function is available", - name - ) - } - } - pub fn get(&self, name: &str) -> Option<&Macro> { - self.data - .get(name) - .or_else(|| self.parent.and_then(|parent| parent.get(name))) + self.data.get(name) } pub fn set(&mut self, name: String, r#macro: Macro) { @@ -167,9 +135,7 @@ impl<'parent, 'lookup> Set<'parent, 'lookup> { // `remove` is fine, but I think for "remove-and-return" `pop` is better pub fn pop(&mut self, name: &str) -> Option<Macro> { // TODO figure out a better way to handle inheritance - self.data - .remove(name) - .or_else(|| self.parent.and_then(|p| p.get(name).cloned())) + self.data.remove(name) } pub fn extend( @@ -195,171 +161,6 @@ impl<'parent, 'lookup> Set<'parent, 'lookup> { self.data.extend(other); } - fn warn(&self, text: String) { - if !self.warnings.borrow().contains(&text) { - log::warn!("{}", &text); - self.warnings.borrow_mut().insert(text); - } - } - - pub fn expand<#[cfg(feature = "full")] R: BufRead>( - &self, - text: &TokenString, - #[cfg(feature = "full")] mut eval_context: Option<&mut DeferredEvalContext<R>>, - ) -> Result<String> { - let mut result = String::new(); - for token in text.tokens() { - match token { - Token::Text(t) => result.push_str(t), - Token::MacroExpansion { name, replacement } => { - let name = self - .expand( - name, - #[cfg(feature = "full")] - eval_context.as_deref_mut(), - ) - .wrap_err_with(|| format!("while expanding \"{}\"", name))?; - let internal_macro_names = &['@', '?', '<', '*', '^'][..]; - let internal_macro_suffices = &['D', 'F'][..]; - let just_internal = name.len() == 1 && name.starts_with(internal_macro_names); - let suffixed_internal = name.len() == 2 - && name.starts_with(internal_macro_names) - && name.ends_with(internal_macro_suffices); - let macro_value = if just_internal || suffixed_internal { - self.lookup_internal(&name) - .wrap_err_with(|| format!("while expanding $\"{}\"", name))? - } else { - self.get(&name).map_or_else( - || { - self.warn(format!("undefined macro {}", name)); - Ok(String::new()) - }, - |x| { - self.expand( - &x.text, - #[cfg(feature = "full")] - eval_context.as_deref_mut(), - ) - .wrap_err_with(|| format!("while expanding \"{}\"", &x.text)) - }, - )? - }; - let macro_value = match replacement { - Some((subst1, subst2)) => { - let subst1 = self.expand( - subst1, - #[cfg(feature = "full")] - eval_context.as_deref_mut(), - )?; - #[cfg(feature = "full")] - { - let (subst1, subst2) = if subst1.contains('%') { - (subst1, subst2.clone()) - } else { - let mut real_subst2 = TokenString::text("%"); - real_subst2.extend(subst2.clone()); - (format!("%{}", subst1), real_subst2) - }; - let args = [ - TokenString::text(subst1), - subst2, - TokenString::text(macro_value), - ]; - functions::expand_call( - "patsubst", - &args, - self, - eval_context.as_deref_mut(), - )? - } - #[cfg(not(feature = "full"))] - { - let subst1_suffix = regex::escape(&subst1); - let subst1_suffix = - Regex::new(&format!(r"{}(\s|$)", subst1_suffix)) - .context("formed invalid regex somehow")?; - let subst2 = self.expand(subst2)?; - subst1_suffix - .replace_all(¯o_value, |c: ®ex::Captures| { - format!("{}{}", subst2, c.get(1).unwrap().as_str()) - }) - .to_string() - } - } - None => macro_value, - }; - log::trace!( - "expanded {} (from {:?}) into \"{}\"", - token, - self.get(&name).map(|x| &x.source), - ¯o_value - ); - result.push_str(¯o_value); - } - #[cfg(feature = "full")] - Token::FunctionCall { name, args } => { - let name = self.expand(name, eval_context.as_deref_mut())?; - let fn_result = - functions::expand_call(&name, args, self, eval_context.as_deref_mut())?; - log::trace!("expanded {} into \"{}\"", token, &fn_result); - result.push_str(&fn_result); - } - } - } - Ok(result) - } - - #[cfg(feature = "full")] - pub fn origin(&self, name: &str) -> &'static str { - match self.data.get(name) { - None => self.parent.map_or("undefined", |p| p.origin(name)), - Some(Macro { - source: ItemSource::Builtin, - .. - }) => "default", - Some(Macro { - source: ItemSource::Environment, - .. - }) => "environment", - // TODO figure out when to return "environment override" - Some(Macro { - source: ItemSource::File { .. }, - .. - }) => "file", - Some(Macro { - source: ItemSource::CommandLineOrMakeflags, - .. - }) => "command line", - // TODO handle override - Some(Macro { - source: ItemSource::FunctionCall, - .. - }) => "automatic", - } - } - - pub fn with_lookup<'l, 's: 'l>(&'s self, lookup: LookupInternal<'l>) -> Set<'s, 'l> { - Set { - parent: Some(self), - data: HashMap::new(), - lookup_internal: Some(lookup), - #[cfg(feature = "full")] - exported: self.exported.same_type(), - warnings: Rc::clone(&self.warnings), - } - } - - pub fn with_overlay<'s>(&'s self) -> Set<'s, 'lookup> { - Set { - parent: Some(self), - data: HashMap::new(), - lookup_internal: None, - #[cfg(feature = "full")] - exported: self.exported.same_type(), - warnings: Rc::clone(&self.warnings), - } - } - #[cfg(feature = "full")] pub fn resolve_exports<R: BufRead>( &self, @@ -370,37 +171,16 @@ impl<'parent, 'lookup> Set<'parent, 'lookup> { .iter() .filter(|(name, _)| self.exported.should_export(name)) .map(|(name, value)| { - self.expand(&value.text, eval_context.as_deref_mut()) + MacroScopeStack::from_scope(self) + .expand(&value.text, eval_context.as_deref_mut()) .map(|text| (name.as_ref(), text)) }) .collect::<Result<Vec<_>>>()?; - Ok(if let Some(parent) = self.parent { - let mut parent_exports = parent.resolve_exports(eval_context)?; - parent_exports.extend(own_exports); - parent_exports - } else { - own_exports - }) - } -} - -impl fmt::Debug for Set<'_, '_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut r#struct = f.debug_struct("Set"); - r#struct.field("parent", &self.parent); - r#struct.field("data", &self.data); - r#struct.field( - "lookup_internal", - &self.lookup_internal.as_ref().map(|_| ()), - ); - #[cfg(feature = "full")] - r#struct.field("exported", &self.exported); - r#struct.field("warnings", &self.warnings); - r#struct.finish() + Ok(own_exports) } } -impl fmt::Display for Set<'_, '_> { +impl fmt::Display for Set { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let pieces = self .data @@ -411,7 +191,7 @@ impl fmt::Display for Set<'_, '_> { } } -impl Default for Set<'_, '_> { +impl Default for Set { fn default() -> Self { Self::new() } @@ -489,7 +269,7 @@ mod test { }, ); assert_eq!( - macros.expand( + MacroScopeStack::from_scope(¯os).expand( &"$(oof:;=?)".parse()?, #[cfg(feature = "full")] NO_EVAL @@ -512,7 +292,7 @@ mod test { }, ); assert_eq!( - macros.expand(&"$(m:%=%-objs)".parse()?, NO_EVAL)?, + MacroScopeStack::from_scope(¯os).expand(&"$(m:%=%-objs)".parse()?, NO_EVAL)?, "conf-objs" ); Ok(()) diff --git a/src/makefile/macro_scope.rs b/src/makefile/macro_scope.rs new file mode 100644 index 0000000..045d6f7 --- /dev/null +++ b/src/makefile/macro_scope.rs @@ -0,0 +1,211 @@ +use super::eval_context::DeferredEvalContext; +use super::functions; +use super::token::Token; +use super::{ItemSource, LookupInternal, Macro, MacroSet, TokenString}; +use eyre::Context; +use lazy_static::lazy_static; +use std::borrow::Cow; +use std::collections::HashSet; +use std::io::BufRead; +use std::iter; +use std::sync::RwLock; + +pub trait MacroScope { + /// Looks up the macro with the given name and returns it if it exists. + /// + /// Uses [Cow] to allow for lazy macro definitions. + fn get(&self, name: &str) -> Option<Cow<Macro>>; +} + +impl MacroScope for MacroSet { + fn get(&self, name: &str) -> Option<Cow<Macro>> { + self.get(name).map(Cow::Borrowed) + } +} + +impl<'a> MacroScope for LookupInternal<'a> { + fn get(&self, name: &str) -> Option<Cow<Macro>> { + self.lookup(name).ok().map(|value| { + Cow::Owned(Macro { + source: ItemSource::Builtin, + text: TokenString::text(value), + #[cfg(feature = "full")] + eagerly_expanded: false, + }) + }) + } +} + +// warning on undefined macros is useful but can get repetitive fast +lazy_static! { + static ref WARNINGS_EMITTED: RwLock<HashSet<String>> = Default::default(); +} + +fn warn(text: String) { + let already_warned = WARNINGS_EMITTED + .read() + .map_or(true, |warnings| warnings.contains(&text)); + if !already_warned { + log::warn!("{}", &text); + if let Ok(mut warnings) = WARNINGS_EMITTED.write() { + warnings.insert(text); + } + } +} + +#[derive(Default)] +pub struct MacroScopeStack<'a> { + scopes: Vec<&'a dyn MacroScope>, +} + +impl<'a> MacroScopeStack<'a> { + pub fn new() -> Self { + Self::default() + } + + pub fn from_scope(scope: &'a dyn MacroScope) -> Self { + Self { + scopes: vec![scope], + } + } + + pub fn with_scope(&self, new_scope: &'a dyn MacroScope) -> Self { + Self { + scopes: iter::once(new_scope).chain(self.scopes.clone()).collect(), + } + } + + fn get(&self, name: &str) -> Option<Cow<Macro>> { + for scope in &self.scopes { + if let Some(r#macro) = scope.get(name) { + return Some(r#macro); + } + } + None + } + + pub fn expand<#[cfg(feature = "full")] R: BufRead>( + &self, + text: &TokenString, + #[cfg(feature = "full")] mut eval_context: Option<&mut DeferredEvalContext<R>>, + ) -> eyre::Result<String> { + let mut result = String::new(); + for token in text.tokens() { + match token { + Token::Text(t) => result.push_str(t), + Token::MacroExpansion { name, replacement } => { + let name = self + .expand( + name, + #[cfg(feature = "full")] + eval_context.as_deref_mut(), + ) + .wrap_err_with(|| format!("while expanding \"{}\"", name))?; + let macro_value = self.get(&name).map_or_else( + || { + warn(format!("undefined macro {}", name)); + Ok(String::new()) + }, + |x| { + self.expand( + &x.text, + #[cfg(feature = "full")] + eval_context.as_deref_mut(), + ) + .wrap_err_with(|| format!("while expanding \"{}\"", &x.text)) + }, + )?; + let macro_value = match replacement { + Some((subst1, subst2)) => { + let subst1 = self.expand( + subst1, + #[cfg(feature = "full")] + eval_context.as_deref_mut(), + )?; + #[cfg(feature = "full")] + { + let (subst1, subst2) = if subst1.contains('%') { + (subst1, subst2.clone()) + } else { + let mut real_subst2 = TokenString::text("%"); + real_subst2.extend(subst2.clone()); + (format!("%{}", subst1), real_subst2) + }; + let args = [ + TokenString::text(subst1), + subst2, + TokenString::text(macro_value), + ]; + functions::expand_call( + "patsubst", + &args, + self, + eval_context.as_deref_mut(), + )? + } + #[cfg(not(feature = "full"))] + { + let subst1_suffix = regex::escape(&subst1); + let subst1_suffix = + Regex::new(&format!(r"{}(\s|$)", subst1_suffix)) + .context("formed invalid regex somehow")?; + let subst2 = self.expand(subst2)?; + subst1_suffix + .replace_all(¯o_value, |c: ®ex::Captures| { + format!("{}{}", subst2, c.get(1).unwrap().as_str()) + }) + .to_string() + } + } + None => macro_value, + }; + log::trace!( + "expanded {} (from {:?}) into \"{}\"", + token, + self.get(&name).map(|x| x.source.clone()), + ¯o_value + ); + result.push_str(¯o_value); + } + #[cfg(feature = "full")] + Token::FunctionCall { name, args } => { + let name = self.expand(name, eval_context.as_deref_mut())?; + let fn_result = + functions::expand_call(&name, args, self, eval_context.as_deref_mut())?; + log::trace!("expanded {} into \"{}\"", token, &fn_result); + result.push_str(&fn_result); + } + } + } + Ok(result) + } + + #[cfg(feature = "full")] + pub fn origin(&self, name: &str) -> &'static str { + match self.get(name).as_deref() { + None => "undefined", + Some(Macro { + source: ItemSource::Builtin, + .. + }) => "default", + Some(Macro { + source: ItemSource::Environment, + .. + }) => "environment", + // TODO figure out when to return "environment override" + Some(Macro { + source: ItemSource::File { .. }, + .. + }) => "file", + Some(Macro { + source: ItemSource::CommandLineOrMakeflags, + .. + }) => "command line", + // TODO handle override + Some(Macro { + source: ItemSource::FunctionCall, + .. + }) => "automatic", + } + } +} diff --git a/src/makefile/mod.rs b/src/makefile/mod.rs index 9255ebf..ddfac6f 100644 --- a/src/makefile/mod.rs +++ b/src/makefile/mod.rs @@ -12,7 +12,9 @@ use inference_rules::InferenceRule; use input::FinishedMakefileReader; pub use input::MakefileReader; use lookup_internal::LookupInternal; -use r#macro::{Macro, Set as MacroSet}; +pub use macro_scope::MacroScopeStack; +use r#macro::Macro; +pub use r#macro::Set as MacroSet; use target::{DynamicTargetSet, Target}; use token::TokenString; @@ -31,6 +33,7 @@ mod inference_rules; mod input; mod lookup_internal; mod r#macro; +mod macro_scope; mod pattern; mod target; mod token; @@ -51,7 +54,7 @@ pub enum ItemSource { pub struct Makefile<'a> { inference_rules: Vec<InferenceRule>, builtin_inference_rules: Vec<InferenceRule>, - pub macros: MacroSet<'static, 'static>, + pub macros: MacroSet, targets: DynamicTargetSet, pub first_non_special_target: Option<String>, args: &'a Args, @@ -157,9 +160,9 @@ impl<'a> Makefile<'a> { self.update_target(&failed_include).wrap_err_with(|| { format!("while building missing included file {}", &failed_include) })?; - let macros = self.macros.with_overlay(); + let stack = MacroScopeStack::default().with_scope(&self.macros); let file = - MakefileReader::read_file(self.args, macros, failed_include, Default::default())? + MakefileReader::read_file(self.args, stack, failed_include, Default::default())? .finish(); self.extend(file)?; } @@ -349,8 +352,9 @@ impl<'a> Makefile<'a> { } fn expand_macros(&self, text: &TokenString, target: Option<&Target>) -> Result<String> { - self.macros - .with_lookup(LookupInternal::new(target, &|name| self.get_target(name))) + MacroScopeStack::default() + .with_scope(&self.macros) + .with_scope(&LookupInternal::new(target, &|name| self.get_target(name))) .expand( text, #[cfg(feature = "full")] |