use std::collections::HashMap; #[cfg(feature = "full")] use std::collections::HashSet; use std::env; use std::fmt; #[cfg(feature = "full")] use std::io::BufRead; #[cfg(feature = "full")] use super::eval_context::DeferredEvalContext; use super::{ItemSource, TokenString}; #[cfg(feature = "full")] use super::MacroScopeStack; #[cfg(feature = "full")] use eyre::Result; #[derive(Debug, Clone)] pub struct Macro { pub source: ItemSource, pub text: TokenString, #[cfg(feature = "full")] pub eagerly_expanded: bool, } #[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 should_export(&self, x: &str) -> bool { match self { Self::Only(exported) => exported.contains(x), Self::AllBut(not_exported) => !not_exported.contains(x), } } } #[derive(Clone, Debug)] pub struct Set { pub data: HashMap, #[cfg(feature = "full")] pub exported: ExportConfig, } impl Set { pub fn new() -> Self { Self { data: HashMap::new(), #[cfg(feature = "full")] exported: ExportConfig::only(), } } pub fn add_builtins(&mut self) { for (k, v) in builtins() { self.data.insert( k.into(), Macro { source: ItemSource::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: ItemSource::Environment, text: TokenString::text(v), #[cfg(feature = "full")] eagerly_expanded: false, }, ); } } } pub fn get(&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 { // TODO figure out a better way to handle inheritance self.data.remove(name) } 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().filter(|name| !one.contains(*name)).cloned()); } (ExportConfig::AllBut(sne), ExportConfig::Only(oe)) => { sne.extend(other.keys().filter(|name| !oe.contains(*name)).cloned()); } } self.data.extend(other); } #[cfg(feature = "full")] pub fn resolve_exports( &self, mut eval_context: Option<&mut DeferredEvalContext>, ) -> Result> { let own_exports = self .data .iter() .filter(|(name, _)| self.exported.should_export(name)) .map(|(name, value)| { MacroScopeStack::from_scope(self) .expand(&value.text, eval_context.as_deref_mut()) .map(|text| (name.as_ref(), text)) }) .collect::>>()?; Ok(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")) } } impl Default for Set { fn default() -> Self { Self::new() } } 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![ 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::*; #[cfg(feature = "full")] use crate::makefile::functions::NO_EVAL; use crate::MacroScopeStack; use eyre::Result; type R = Result<()>; #[test] fn subst() -> R { let mut macros = Set::new(); macros.set( "oof".to_owned(), Macro { source: ItemSource::Builtin, text: TokenString::text("bruh; swag; yeet;"), #[cfg(feature = "full")] eagerly_expanded: false, }, ); assert_eq!( MacroScopeStack::from_scope(¯os).expand( &"$(oof:;=?)".parse()?, #[cfg(feature = "full")] NO_EVAL )?, "bruh? swag? yeet?" ); Ok(()) } #[test] #[cfg(feature = "full")] fn hell_subst() -> R { let mut macros = Set::new(); macros.set( "m".to_owned(), Macro { source: ItemSource::FunctionCall, text: TokenString::text("conf"), eagerly_expanded: false, }, ); assert_eq!( MacroScopeStack::from_scope(¯os).expand(&"$(m:%=%-objs)".parse()?, NO_EVAL)?, "conf-objs" ); Ok(()) } }