#[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, } 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(), (Source::Builtin, v)); } } pub fn add_env(&mut self) { for (k, v) in env::vars() { if k != "MAKEFLAGS" && k != "SHELL" { self.data .insert(k, (Source::Environment, TokenString::text(v))); } } } 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<&(Source, TokenString)> { self.data .get(name) .or_else(|| self.parent.and_then(|parent| parent.get(name))) } pub fn set(&mut self, name: String, source: Source, text: TokenString) { self.data.insert(name, (source, text)); } #[cfg(feature = "full")] 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<(Source, TokenString)> { self.data.remove(name) } 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 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()) }, |(_, 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"{}(\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 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((Source::Builtin, _)) => "default", Some((Source::Environment, _)) => "environment", // TODO figure out when to return "environment override" Some((Source::File, _)) => "file", Some((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, (_, 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).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(), Source::File, TokenString::text("bruh; swag; yeet;"), ); assert_eq!( macros.expand(&"$(oof:;=?)".parse().unwrap())?, "bruh? swag? yeet?" ); Ok(()) } }