From 57e4222900de52da5ffe13933fbebe91fbd7bab9 Mon Sep 17 00:00:00 2001
From: Melody Horn <melody@boringcactus.com>
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(-)

(limited to 'src')

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::<Vec<_>>();
-    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::<Vec<_>>();
-            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::<Vec<_>>();
-            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::<Vec<_>>();
-            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::<Vec<_>>();
-            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::<Vec<_>>();
-            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::<Vec<_>>();
-            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::<Vec<_>>();
-            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::<Vec<_>>();
-            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::<Vec<_>>();
+        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::<Vec<_>>();
+        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::<Vec<_>>();
+        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::<Vec<_>>();
+        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::<Vec<_>>();
+        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::<Vec<_>>();
+        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::<Vec<_>>();
+        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::<Vec<_>>();
+        results.join(" ")
+    }
+}
+
+// call
+mod call {
+    use super::MacroSetLike;
+    use super::TokenString;
+
+    pub(crate) fn call<'a>(
+        macros: &impl MacroSetLike,
+        args: impl Iterator<Item = &'a TokenString>,
+    ) -> String {
+        let args = args.map(|arg| macros.expand(arg)).collect::<Vec<_>>();
+        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<F: for<'a> 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(&macro_value, subst2).to_string()
+                        }
+                        None => macro_value,
+                    };
+                    result.push_str(&macro_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<Self> {
+        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<String, (MacroSource, TokenString)>,
+}
+
+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<String, (MacroSource, TokenString)>,
-    lookup_internal: RefCell<Weak<dyn LookupInternal>>,
+}
+
+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<dyn LookupInternal> = Rc::new(lookup_fail);
         Self {
             data: HashMap::new(),
-            lookup_internal: RefCell::new(Rc::downgrade(&lookup_fail)),
         }
     }
 
-    pub(crate) fn lookup(&self, lookup: Weak<dyn LookupInternal>) {
-        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(&macro_value, subst2).to_string()
-                        }
-                        None => macro_value,
-                    };
-                    result.push_str(&macro_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<dyn LookupInternal> = 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