#[cfg(feature = "full")] use std::cell::RefCell; use std::collections::HashMap; use std::env; use std::fmt; #[cfg(feature = "full")] 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 {} #[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>>, } 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())), } } 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.data.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) { self.data.extend(other); } 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( || { log::warn!("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), } } 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), } } } 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(()) } }