diff options
Diffstat (limited to 'src/makefile/macro.rs')
-rw-r--r-- | src/makefile/macro.rs | 323 |
1 files changed, 81 insertions, 242 deletions
diff --git a/src/makefile/macro.rs b/src/makefile/macro.rs index d77557b..d27cbfa 100644 --- a/src/makefile/macro.rs +++ b/src/makefile/macro.rs @@ -1,20 +1,20 @@ -use std::cell::RefCell; use std::collections::HashMap; +#[cfg(feature = "full")] use std::collections::HashSet; use std::env; use std::fmt; -use std::rc::Rc; - -use eyre::{bail, Result, WrapErr}; -#[cfg(not(feature = "full"))] -use regex::Regex; +#[cfg(feature = "full")] +use std::io::BufRead; #[cfg(feature = "full")] -use super::functions; -use super::token::{Token, TokenString}; -use super::ItemSource; +use super::eval_context::DeferredEvalContext; +#[cfg(feature = "full")] +use super::MacroScopeStack; +use super::{ItemSource, TokenString}; +#[cfg(feature = "full")] +use eyre::Result; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Macro { pub source: ItemSource, pub text: TokenString, @@ -22,12 +22,8 @@ pub struct Macro { pub eagerly_expanded: bool, } -pub trait LookupInternal: for<'a> Fn(&'a str) -> Result<String> {} - -impl<F: for<'a> Fn(&'a str) -> Result<String>> LookupInternal for F {} - #[cfg(feature = "full")] -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum ExportConfig { Only(HashSet<String>), AllBut(HashSet<String>), @@ -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,29 +73,19 @@ impl ExportConfig { } } -#[derive(Clone)] -pub struct Set<'parent, 'lookup> { - parent: Option<&'parent Set<'parent, 'lookup>>, +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Set { pub data: HashMap<String, Macro>, - lookup_internal: Option<&'lookup dyn LookupInternal>, - #[cfg(feature = "full")] - pub to_eval: Rc<RefCell<Vec<String>>>, #[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")] - to_eval: Rc::new(RefCell::new(Vec::new())), #[cfg(feature = "full")] exported: ExportConfig::only(), - warnings: Default::default(), } } @@ -140,49 +119,18 @@ impl<'parent, 'lookup> Set<'parent, 'lookup> { } } - fn lookup_internal(&self, name: &str) -> Result<String> { - if let Some(lookup) = self.lookup_internal { - 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))) + /// To properly process inherited macros, use [MacroScopeStack::get]. + pub fn get_non_recursive(&self, name: &str) -> Option<&Macro> { + self.data.get(name) } pub fn set(&mut self, name: String, r#macro: Macro) { self.data.insert(name, r#macro); } - #[cfg(feature = "full")] - pub fn is_defined(&self, name: &str) -> bool { - self.get(name).map_or(false, |x| !x.text.is_empty()) - } - - // `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())) - } - - pub fn extend( - &mut self, - other: HashMap<String, Macro>, - #[cfg(feature = "full")] other_exports: ExportConfig, - ) { + pub fn extend(&mut self, other: Self) { #[cfg(feature = "full")] - match (&mut self.exported, other_exports) { + match (&mut self.exported, other.exported) { (ExportConfig::Only(se), ExportConfig::Only(oe)) => { se.extend(oe); } @@ -190,187 +138,47 @@ impl<'parent, 'lookup> Set<'parent, 'lookup> { sne.extend(one); } (ExportConfig::Only(se), ExportConfig::AllBut(one)) => { - se.extend(other.keys().cloned().filter(|name| !one.contains(name))); + se.extend( + other + .data + .keys() + .filter(|name| !one.contains(*name)) + .cloned(), + ); } (ExportConfig::AllBut(sne), ExportConfig::Only(oe)) => { - sne.extend(other.keys().cloned().filter(|name| !oe.contains(name))); - } - } - 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(&self, text: &TokenString) -> 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) - .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) - .wrap_err_with(|| format!("while expanding \"{}\"", &x.text)) - }, - )? - }; - let macro_value = match replacement { - Some((subst1, subst2)) => { - let subst1 = self.expand(subst1)?; - #[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, - Some(Rc::clone(&self.to_eval)), - )? - } - #[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)?; - let fn_result = - functions::expand_call(&name, args, self, Some(Rc::clone(&self.to_eval)))?; - log::trace!("expanded {} into \"{}\"", token, &fn_result); - result.push_str(&fn_result); - } + sne.extend( + other + .data + .keys() + .filter(|name| !oe.contains(*name)) + .cloned(), + ); } } - Ok(result) + self.data.extend(other.data); } #[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: &'l dyn LookupInternal) -> Set<'s, 'l> { - Set { - parent: Some(self), - data: HashMap::new(), - lookup_internal: Some(lookup), - #[cfg(feature = "full")] - to_eval: Rc::clone(&self.to_eval), - #[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")] - to_eval: Rc::clone(&self.to_eval), - #[cfg(feature = "full")] - exported: self.exported.same_type(), - warnings: Rc::clone(&self.warnings), - } - } - - #[cfg(feature = "full")] - pub fn resolve_exports(&self) -> Result<Vec<(&str, String)>> { + pub fn resolve_exports<R: BufRead>( + &self, + mut eval_context: Option<&mut DeferredEvalContext<R>>, + ) -> Result<Vec<(&str, String)>> { let own_exports = self .data .iter() .filter(|(name, _)| self.exported.should_export(name)) - .map(|(name, value)| self.expand(&value.text).map(|text| (name.as_ref(), text))) + .map(|(name, value)| { + 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()?; - parent_exports.extend(own_exports); - parent_exports - } else { - own_exports - }) + 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 @@ -381,9 +189,23 @@ impl fmt::Display for Set<'_, '_> { } } +impl Default for Set { + fn default() -> Self { + Self::new() + } +} + fn builtins() -> Vec<(&'static str, TokenString)> { // Fuck it, might as well. - macro_rules! handle { + macro_rules! handle_key { + ($key:ident) => { + stringify!($key) + }; + ($key:literal) => { + $key + }; + } + macro_rules! handle_value { ($value:ident) => { stringify!($value).parse().unwrap() }; @@ -395,8 +217,8 @@ fn builtins() -> Vec<(&'static str, TokenString)> { }; } macro_rules! make { - ($($name:ident=$value:tt)+) => {vec![$( - (stringify!($name), handle!($value)) + ($($name:tt=$value:tt)+) => {vec![$( + (handle_key!($name), handle_value!($value)) ),+]}; } @@ -429,12 +251,19 @@ fn builtins() -> Vec<(&'static str, TokenString)> { ARFLAGS="rv" CFLAGS="" FFLAGS="" + + // yes, Linux, this is definitely GNU Make 4.0+ + ".FEATURES"="output-sync" ] } #[cfg(test)] mod test { use super::*; + #[cfg(feature = "full")] + use crate::makefile::functions::NO_EVAL; + use crate::MacroScopeStack; + use eyre::Result; type R = Result<()>; @@ -450,7 +279,14 @@ mod test { eagerly_expanded: false, }, ); - assert_eq!(macros.expand(&"$(oof:;=?)".parse()?)?, "bruh? swag? yeet?"); + assert_eq!( + MacroScopeStack::from_scope(¯os).expand( + &"$(oof:;=?)".parse()?, + #[cfg(feature = "full")] + NO_EVAL + )?, + "bruh? swag? yeet?" + ); Ok(()) } @@ -466,7 +302,10 @@ mod test { eagerly_expanded: false, }, ); - assert_eq!(macros.expand(&"$(m:%=%-objs)".parse()?)?, "conf-objs"); + assert_eq!( + MacroScopeStack::from_scope(¯os).expand(&"$(m:%=%-objs)".parse()?, NO_EVAL)?, + "conf-objs" + ); Ok(()) } } |