From 57e4222900de52da5ffe13933fbebe91fbd7bab9 Mon Sep 17 00:00:00 2001 From: Melody Horn Date: Sun, 28 Mar 2021 16:15:42 -0600 Subject: almost finish implementing functions --- src/makefile/functions.rs | 312 ++++++++++++++++++++++++++++------------------ src/makefile/macro.rs | 173 +++++++++++++++---------- src/makefile/mod.rs | 8 +- 3 files changed, 300 insertions(+), 193 deletions(-) diff --git a/src/makefile/functions.rs b/src/makefile/functions.rs index b9a6a6d..12da6c9 100644 --- a/src/makefile/functions.rs +++ b/src/makefile/functions.rs @@ -1,160 +1,53 @@ -use std::env; -use std::path::Path; - use super::pattern::r#match; -use super::r#macro::{MacroSet, MacroSource}; +use super::r#macro::MacroSetLike; use super::token::TokenString; -pub(crate) struct FunctionCall {} - -fn filter(macros: &MacroSet, patterns: &TokenString, text: &TokenString) -> String { - let patterns = macros.expand(patterns); - let patterns = patterns.split_whitespace().collect::>(); - let text = macros.expand(&text); - let text = text.split_whitespace(); - let mut result_pieces = vec![]; - for word in text { - if patterns - .iter() - .any(|pattern| r#match(pattern, word).is_some()) - { - result_pieces.push(word); - } - } - result_pieces.join(" ") -} - -pub(crate) fn call(name: &str, args: &[TokenString], macros: &MacroSet) -> String { +pub(crate) fn expand_call(name: &str, args: &[TokenString], macros: &impl MacroSetLike) -> String { match name { - // Text Functions "filter" => { assert_eq!(args.len(), 2); - filter(macros, &args[0], &args[1]) + text::filter(macros, &args[0], &args[1]) } "filter-out" => { assert_eq!(args.len(), 2); - let patterns = macros.expand(&args[0]); - let patterns = patterns.split_whitespace().collect::>(); - let text = macros.expand(&args[1]); - let text = text.split_whitespace(); - let mut result_pieces = vec![]; - for word in text { - if patterns - .iter() - .all(|pattern| r#match(pattern, word).is_none()) - { - result_pieces.push(word); - } - } - result_pieces.join(" ") + text::filter_out(macros, &args[0], &args[1]) } "sort" => { assert_eq!(args.len(), 1); - let words = macros.expand(&args[0]); - let mut words = words.split_whitespace().collect::>(); - words.sort(); - words.dedup(); - words.join(" ") + text::sort(macros, &args[0]) } - // File Name Functions "notdir" => { assert_eq!(args.len(), 1); - let words = macros.expand(&args[0]); - let words = words - .split_whitespace() - .map(|word| { - Path::new(word) - .file_name() - .and_then(|filename| filename.to_str()) - .unwrap_or("") - }) - .collect::>(); - words.join(" ") + file_name::notdir(macros, &args[0]) } "basename" => { assert_eq!(args.len(), 1); - let words = macros.expand(&args[0]); - let words = words - .split_whitespace() - .map(|word| { - Path::new(word) - .with_extension("") - .to_str() - .map_or_else(String::new, ToString::to_string) - }) - .collect::>(); - words.join(" ") + file_name::basename(macros, &args[0]) } "addprefix" => { assert_eq!(args.len(), 2); - let prefix = macros.expand(&args[0]); - let targets = macros.expand(&args[1]); - let results = targets - .split_whitespace() - .map(|t| format!("{}{}", prefix, t)) - .collect::>(); - results.join(" ") + file_name::addprefix(macros, &args[0], &args[1]) } "wildcard" => { assert_eq!(args.len(), 1); - let pattern = macros.expand(&args[0]); - let home_dir = env::var("HOME") - .ok() - .or(dirs::home_dir().and_then(|p| p.to_str().map(String::from))); - let pattern = if let Some(home_dir) = home_dir { - pattern.replace('~', &home_dir) - } else { - pattern - }; - let results = glob::glob(&pattern) - .expect("invalid glob pattern!") - .filter_map(|path| path.ok()) - .map(|path| path.to_str().map(ToString::to_string).unwrap_or_default()) - .collect::>(); - results.join(" ") + file_name::wildcard(macros, &args[0]) } // foreach "foreach" => { assert_eq!(args.len(), 3); - let var = macros.expand(&args[0]); - let list = macros.expand(&args[1]); - let text = &args[2]; - let words = list.split_whitespace(); - - let mut macros = macros.clone(); - let results = words - .map(|word| { - macros.set(var.clone(), MacroSource::File, TokenString::text(word)); - macros.expand(text) - }) - .collect::>(); - results.join(" ") + foreach::foreach(macros, &args[0], &args[1], &args[2]) } // call "call" => { - assert!(args.len() > 0); - let args = args - .iter() - .map(|arg| macros.expand(arg)) - .collect::>(); - let function = args[0].clone(); - - let mut macros = macros.clone(); - for (i, x) in args.into_iter().enumerate() { - macros.set(i.to_string(), MacroSource::File, TokenString::text(x)); - } - macros.expand(&TokenString::r#macro(function)) + assert!(!args.is_empty()); + call::call(macros, args.iter()) } // eval - "eval" => { - assert_eq!(args.len(), 1); - let arg = macros.expand(&args[0]); - todo!() - } + "eval" => todo!(), // shell "shell" => todo!(), @@ -164,12 +57,189 @@ pub(crate) fn call(name: &str, args: &[TokenString], macros: &MacroSet) -> Strin } } +// Text Functions +mod text { + use super::r#match; + use super::MacroSetLike; + use super::TokenString; + + pub(crate) fn filter( + macros: &impl MacroSetLike, + patterns: &TokenString, + text: &TokenString, + ) -> String { + let patterns = macros.expand(patterns); + let patterns = patterns.split_whitespace().collect::>(); + let text = macros.expand(text); + let text = text.split_whitespace(); + let mut result_pieces = vec![]; + for word in text { + if patterns + .iter() + .any(|pattern| r#match(pattern, word).is_some()) + { + result_pieces.push(word); + } + } + result_pieces.join(" ") + } + + pub(crate) fn filter_out( + macros: &impl MacroSetLike, + patterns: &TokenString, + text: &TokenString, + ) -> String { + let patterns = macros.expand(patterns); + let patterns = patterns.split_whitespace().collect::>(); + let text = macros.expand(text); + let text = text.split_whitespace(); + let mut result_pieces = vec![]; + for word in text { + if patterns + .iter() + .all(|pattern| r#match(pattern, word).is_none()) + { + result_pieces.push(word); + } + } + result_pieces.join(" ") + } + + pub(crate) fn sort(macros: &impl MacroSetLike, words: &TokenString) -> String { + let words = macros.expand(words); + let mut words = words.split_whitespace().collect::>(); + words.sort_unstable(); + words.dedup(); + words.join(" ") + } +} + +// File Name Functions +mod file_name { + use std::env; + use std::ffi::OsStr; + use std::path::Path; + + use super::MacroSetLike; + use super::TokenString; + + pub(crate) fn notdir(macros: &impl MacroSetLike, words: &TokenString) -> String { + let words = macros.expand(words); + let words = words + .split_whitespace() + .map(|word| { + Path::new(word) + .file_name() + .and_then(OsStr::to_str) + .unwrap_or("") + }) + .collect::>(); + words.join(" ") + } + + pub(crate) fn basename(macros: &impl MacroSetLike, words: &TokenString) -> String { + let words = macros.expand(words); + let words = words + .split_whitespace() + .map(|word| { + Path::new(word) + .with_extension("") + .to_str() + .map_or_else(String::new, ToString::to_string) + }) + .collect::>(); + words.join(" ") + } + + pub(crate) fn addprefix( + macros: &impl MacroSetLike, + prefix: &TokenString, + targets: &TokenString, + ) -> String { + let prefix = macros.expand(prefix); + let targets = macros.expand(targets); + let results = targets + .split_whitespace() + .map(|t| format!("{}{}", prefix, t)) + .collect::>(); + results.join(" ") + } + + pub(crate) fn wildcard(macros: &impl MacroSetLike, pattern: &TokenString) -> String { + let pattern = macros.expand(pattern); + let home_dir = env::var("HOME") + .ok() + .or_else(|| dirs::home_dir().and_then(|p| p.to_str().map(String::from))); + let pattern = if let Some(home_dir) = home_dir { + pattern.replace('~', &home_dir) + } else { + pattern + }; + let results = glob::glob(&pattern) + .expect("invalid glob pattern!") + .filter_map(|path| { + path.ok() + .map(|x| x.to_str().map(ToString::to_string).unwrap_or_default()) + }) + .collect::>(); + results.join(" ") + } +} + +// foreach +mod foreach { + use super::MacroSetLike; + use super::TokenString; + + pub(crate) fn foreach( + macros: &impl MacroSetLike, + var: &TokenString, + list: &TokenString, + text: &TokenString, + ) -> String { + let var = macros.expand(var); + let list = macros.expand(list); + let words = list.split_whitespace(); + + let mut macros = macros.with_overlay(); + let results = words + .map(|word| { + macros.set(var.clone(), TokenString::text(word)); + macros.expand(text) + }) + .collect::>(); + results.join(" ") + } +} + +// call +mod call { + use super::MacroSetLike; + use super::TokenString; + + pub(crate) fn call<'a>( + macros: &impl MacroSetLike, + args: impl Iterator, + ) -> String { + let args = args.map(|arg| macros.expand(arg)).collect::>(); + let function = args[0].clone(); + + let mut macros = macros.with_overlay(); + for (i, x) in args.into_iter().enumerate() { + macros.set(i.to_string(), TokenString::text(x)); + } + macros.expand(&TokenString::r#macro(function)) + } +} + #[cfg(test)] mod test { use super::*; + use crate::makefile::r#macro::{MacroSet, MacroSource}; + fn call(name: &str, args: &[TokenString], macros: &MacroSet) -> String { - super::call(name, args, macros, lookup_fail) + super::expand_call(name, args, macros) } macro_rules! call { diff --git a/src/makefile/macro.rs b/src/makefile/macro.rs index 139903f..27cbc14 100644 --- a/src/makefile/macro.rs +++ b/src/makefile/macro.rs @@ -1,8 +1,6 @@ -use std::cell::RefCell; use std::collections::HashMap; use std::env; use std::fmt; -use std::rc::{Rc, Weak}; use regex::Regex; @@ -21,29 +19,125 @@ pub(crate) trait LookupInternal: for<'a> Fn(&'a str) -> String {} impl Fn(&'a str) -> String> LookupInternal for F {} -fn lookup_fail(_: &str) -> String { - panic!("internal variables not available!"); +pub(crate) trait MacroSetLike: Sized { + fn lookup_internal(&self, name: &str) -> String; + fn get(&self, name: &str) -> Option<&(MacroSource, TokenString)>; + + fn expand(&self, text: &TokenString) -> String { + 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(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)).unwrap(); + 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)); + } + } + } + result + } + + fn with_lookup<'a, 's: 'a>( + &'s self, + lookup: &'a dyn LookupInternal, + ) -> MacroSetLikeWithLookup<'a, Self> { + MacroSetLikeWithLookup { + parent: self, + lookup, + } + } + + fn with_overlay(&self) -> MacroSetLikeWithOverlay { + MacroSetLikeWithOverlay { + parent: self, + data: HashMap::new(), + } + } +} + +pub(crate) struct MacroSetLikeWithLookup<'a, Parent: MacroSetLike> { + parent: &'a Parent, + lookup: &'a dyn LookupInternal, +} + +impl<'a, Parent: MacroSetLike> MacroSetLike for MacroSetLikeWithLookup<'a, Parent> { + fn lookup_internal(&self, name: &str) -> String { + (self.lookup)(name) + } + + fn get(&self, name: &str) -> Option<&(MacroSource, TokenString)> { + self.parent.get(name) + } +} + +pub(crate) struct MacroSetLikeWithOverlay<'a, Parent: MacroSetLike> { + parent: &'a Parent, + data: HashMap, +} + +impl<'a, Parent: MacroSetLike> MacroSetLike for MacroSetLikeWithOverlay<'a, Parent> { + fn lookup_internal(&self, name: &str) -> String { + self.parent.lookup_internal(name) + } + + fn get(&self, name: &str) -> Option<&(MacroSource, TokenString)> { + self.data.get(name).or_else(|| self.parent.get(name)) + } } -#[derive(Clone)] +impl<'a, Parent: MacroSetLike> MacroSetLikeWithOverlay<'a, Parent> { + pub(crate) fn set(&mut self, name: String, text: TokenString) { + self.data.insert(name, (MacroSource::File, text)); + } +} + +#[derive(Clone, Debug)] pub(crate) struct MacroSet { data: HashMap, - lookup_internal: RefCell>, +} + +impl MacroSetLike for MacroSet { + fn lookup_internal(&self, _: &str) -> String { + panic!("can't lookup an internal macro value from a plain MacroSet"); + } + + fn get(&self, name: &str) -> Option<&(MacroSource, TokenString)> { + self.data.get(name) + } } impl MacroSet { pub(crate) fn new() -> Self { - let lookup_fail: Rc = Rc::new(lookup_fail); Self { data: HashMap::new(), - lookup_internal: RefCell::new(Rc::downgrade(&lookup_fail)), } } - pub(crate) fn lookup(&self, lookup: Weak) { - self.lookup_internal.replace(lookup); - } - pub(crate) fn add_builtins(&mut self) { for (k, v) in builtins() { self.data.insert(k.into(), (MacroSource::Builtin, v)); @@ -67,65 +161,10 @@ impl MacroSet { self.data.contains_key(name) } - pub(crate) fn get(&self, name: &str) -> Option<&(MacroSource, TokenString)> { - self.data.get(name) - } - // `remove` is fine, but I think for "remove-and-return" `pop` is better pub(crate) fn pop(&mut self, name: &str) -> Option<(MacroSource, TokenString)> { self.data.remove(name) } - - pub(crate) fn expand(&self, text: &TokenString) -> String { - 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 { - let lookup_internal = self.lookup_internal.borrow(); - let lookup_internal = match lookup_internal.upgrade() { - Some(f) => f, - None => Rc::new(lookup_fail), - }; - lookup_internal(name) - } else { - self.data - .get(name) - .map_or_else(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)).unwrap(); - 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::call(name, args, &self)); - } - } - } - result - } -} - -impl fmt::Debug for MacroSet { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}", &self.data) - } } impl fmt::Display for MacroSet { diff --git a/src/makefile/mod.rs b/src/makefile/mod.rs index 7610197..f3b7efa 100644 --- a/src/makefile/mod.rs +++ b/src/makefile/mod.rs @@ -23,7 +23,7 @@ mod token; use command_line::CommandLine; use conditional::{ConditionalLine, ConditionalState}; use inference_rules::InferenceRule; -use r#macro::{LookupInternal, MacroSet, MacroSource}; +use r#macro::{MacroSet, MacroSetLike, MacroSource}; use target::Target; use token::{tokenize, Token, TokenString}; @@ -506,7 +506,7 @@ impl<'a> Makefile<'a> { .filter(|prereq| { self.get_target(prereq) .borrow() - .newer_than(&target) + .newer_than(target) .unwrap_or(false) }) .cloned() @@ -554,9 +554,7 @@ impl<'a> Makefile<'a> { macro_pieces.join(" ") }; - let lookup_internal: Rc = Rc::new(lookup_internal); - self.macros.lookup(Rc::downgrade(&lookup_internal)); - self.macros.expand(text) + self.macros.with_lookup(&lookup_internal).expand(text) } } -- cgit v1.2.3