use std::collections::HashMap; use std::env; use std::fmt; use anyhow::Context; use regex::Regex; use super::functions; use super::token::{Token, TokenString}; #[derive(Debug, Clone)] pub enum MacroSource { File, CommandLineOrMakeflags, Environment, Builtin, } pub trait LookupInternal: for<'a> Fn(&'a str) -> anyhow::Result {} impl Fn(&'a str) -> anyhow::Result> LookupInternal for F {} #[derive(Clone)] pub struct MacroSet<'parent, 'lookup> { parent: Option<&'parent MacroSet<'parent, 'lookup>>, data: HashMap, lookup_internal: Option<&'lookup dyn LookupInternal>, } impl<'parent, 'lookup> MacroSet<'parent, 'lookup> { pub fn new() -> Self { Self { parent: None, data: HashMap::new(), lookup_internal: None, } } pub fn add_builtins(&mut self) { for (k, v) in builtins() { self.data.insert(k.into(), (MacroSource::Builtin, v)); } } pub fn add_env(&mut self) { for (k, v) in env::vars() { if k != "MAKEFLAGS" && k != "SHELL" { self.data .insert(k, (MacroSource::Environment, TokenString::text(v))); } } } fn lookup_internal(&self, name: &str) -> anyhow::Result { if let Some(lookup) = self.lookup_internal { lookup(name) } else if let Some(parent) = self.parent { parent.lookup_internal(name) } else { anyhow::bail!("no lookup possible") } } pub fn get(&self, name: &str) -> Option<&(MacroSource, TokenString)> { self.data .get(name) .or_else(|| self.parent.and_then(|parent| parent.get(name))) } pub fn set(&mut self, name: String, source: MacroSource, text: TokenString) { self.data.insert(name, (source, text)); } pub fn is_defined(&self, name: &str) -> bool { self.data.contains_key(name) } // `remove` is fine, but I think for "remove-and-return" `pop` is better pub fn pop(&mut self, name: &str) -> Option<(MacroSource, TokenString)> { self.data.remove(name) } pub fn expand(&self, text: &TokenString) -> anyhow::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 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( || Ok(String::new()), |(_, macro_value)| self.expand(macro_value), )? }; 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"{}\b", subst1_suffix)) .context("formed invalid regex somehow")?; let subst2 = self.expand(subst2)?; subst1_suffix.replace_all(¯o_value, subst2).to_string() } None => macro_value, }; result.push_str(¯o_value); } Token::FunctionCall { name, args } => { result.push_str(&functions::expand_call(name, args, self)?); } } } Ok(result) } pub fn with_lookup<'l, 's: 'l>(&'s self, lookup: &'l dyn LookupInternal) -> MacroSet<'s, 'l> { MacroSet { parent: Some(self), data: HashMap::new(), lookup_internal: Some(lookup), } } pub fn with_overlay<'s>(&'s self) -> MacroSet<'s, 'lookup> { MacroSet { parent: Some(self), data: HashMap::new(), lookup_internal: None, } } } impl fmt::Display for MacroSet<'_, '_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let pieces = self .data .iter() .map(|(k, (_, v))| format!("{}={}", k, v)) .collect::>(); write!(f, "{}", pieces.join("\n")) } } fn builtins() -> Vec<(&'static str, TokenString)> { // Fuck it, might as well. macro_rules! handle { ($value:ident) => { stringify!($value) }; ($value:literal) => { $value }; } macro_rules! make { ($($name:ident=$value:tt)+) => {vec![$( (stringify!($name), handle!($value).parse().unwrap()) ),+]}; } make![ MAKE=makers 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="" ] }