use std::cell::RefCell; use std::collections::HashMap; use std::collections::HashSet; use std::env; use std::fmt; use std::rc::Rc; use eyre::{bail, Result, WrapErr}; use regex::Regex; #[cfg(feature = "full")] use super::functions; use super::token::{Token, TokenString}; #[derive(Debug, Clone)] pub enum Source { File, CommandLineOrMakeflags, Environment, Builtin, } #[derive(Debug, Clone)] pub struct Macro { pub source: Source, pub text: TokenString, #[cfg(feature = "full")] pub eagerly_expanded: bool, } pub trait LookupInternal: for<'a> Fn(&'a str) -> Result {} impl Fn(&'a str) -> Result> LookupInternal for F {} #[cfg(feature = "full")] #[derive(Clone, Debug)] pub enum ExportConfig { Only(HashSet), AllBut(HashSet), } #[cfg(feature = "full")] impl ExportConfig { pub fn all_but() -> Self { Self::AllBut(HashSet::new()) } pub fn only() -> Self { Self::Only(HashSet::new()) } pub fn add_all<'a, I: IntoIterator>(&mut self, iter: I) { match self { Self::Only(exported) => { exported.extend(iter.into_iter().map(|x| x.to_owned())); } Self::AllBut(not_exported) => { for added in iter { not_exported.remove(added); } } } } pub fn remove_all<'a, I: IntoIterator>(&mut self, iter: I) { match self { Self::Only(exported) => { for removed in iter { exported.remove(removed); } } Self::AllBut(not_exported) => { not_exported.extend(iter.into_iter().map(|x| x.into())); } } } 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), Self::AllBut(not_exported) => !not_exported.contains(x), } } } #[derive(Clone)] pub struct Set<'parent, 'lookup> { parent: Option<&'parent Set<'parent, 'lookup>>, pub data: HashMap, lookup_internal: Option<&'lookup dyn LookupInternal>, #[cfg(feature = "full")] pub to_eval: Rc>>, #[cfg(feature = "full")] pub exported: ExportConfig, warnings: Rc>>, } impl<'parent, 'lookup> Set<'parent, 'lookup> { 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(), } } pub fn add_builtins(&mut self) { for (k, v) in builtins() { self.data.insert( k.into(), Macro { source: Source::Builtin, text: v, #[cfg(feature = "full")] eagerly_expanded: false, }, ); } } pub fn add_env(&mut self) { for (k, v) in env::vars() { if k != "MAKEFLAGS" && k != "SHELL" { self.data.insert( k, Macro { source: Source::Environment, text: TokenString::text(v), #[cfg(feature = "full")] eagerly_expanded: false, }, ); } } } fn lookup_internal(&self, name: &str) -> Result { 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))) } 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 { // 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, #[cfg(feature = "full")] other_exports: ExportConfig, ) { #[cfg(feature = "full")] match (&mut self.exported, other_exports) { (ExportConfig::Only(se), ExportConfig::Only(oe)) => { se.extend(oe); } (ExportConfig::AllBut(sne), ExportConfig::AllBut(one)) => { sne.extend(one); } (ExportConfig::Only(se), ExportConfig::AllBut(one)) => { se.extend(other.keys().cloned().filter(|name| !one.contains(name))); } (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 { 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)?; 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)? } else { self.get(&name).map_or_else( || { self.warn(format!("undefined macro {}", name)); Ok(String::new()) }, |x| self.expand(&x.text), )? }; let macro_value = match replacement { Some((subst1, subst2)) => { let subst1 = self.expand(subst1)?; 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, }; 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); } } } 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: Source::Builtin, .. }) => "default", Some(Macro { source: Source::Environment, .. }) => "environment", // TODO figure out when to return "environment override" Some(Macro { source: Source::File, .. }) => "file", Some(Macro { source: Source::CommandLineOrMakeflags, .. }) => "command line", // TODO handle override and 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> { 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))) .collect::>>()?; Ok(if let Some(parent) = self.parent { let mut parent_exports = parent.resolve_exports()?; parent_exports.extend(own_exports); parent_exports } else { own_exports }) } } impl fmt::Display for Set<'_, '_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let pieces = self .data .iter() .map(|(k, x)| format!("{}={}", k, &x.text)) .collect::>(); write!(f, "{}", pieces.join("\n")) } } fn builtins() -> Vec<(&'static str, TokenString)> { // Fuck it, might as well. macro_rules! handle { ($value:ident) => { stringify!($value).parse().unwrap() }; ($value:literal) => { $value.parse().unwrap() }; ($value:expr) => { $value }; } macro_rules! make { ($($name:ident=$value:tt)+) => {vec![$( (stringify!($name), handle!($value)) ),+]}; } make![ MAKE=(std::env::current_exe().map_or_else(|_| TokenString::text("makers"), |x| TokenString::text(x.to_string_lossy()))) AR=ar YACC=yacc YFLAGS="" LEX=lex LFLAGS="" LDFLAGS="" AS=as CC=cc CXX="g++" CPP="$(CC) -E" FC=f77 PC=pc CO=co GET=get LINT=lint MAKEINFO=makeinfo TEX=tex TEXI2DVI=texi2dvi WEAVE=weave CWEAVE=cweave TANGLE=tangle CTANGLE=ctangle RM="rm -f" ARFLAGS="rv" CFLAGS="" FFLAGS="" ] } #[cfg(test)] mod test { use super::*; type R = Result<()>; #[test] fn subst() -> R { let mut macros = Set::new(); macros.set( "oof".to_owned(), Macro { source: Source::File, text: TokenString::text("bruh; swag; yeet;"), #[cfg(feature = "full")] eagerly_expanded: false, }, ); assert_eq!(macros.expand(&"$(oof:;=?)".parse()?)?, "bruh? swag? yeet?"); Ok(()) } }