aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMelody Horn <melody@boringcactus.com>2021-03-28 16:15:42 -0600
committerMelody Horn <melody@boringcactus.com>2021-03-28 16:15:42 -0600
commit57e4222900de52da5ffe13933fbebe91fbd7bab9 (patch)
tree0130a8b5aa46a2aea78fe2cec46088749e066bef
parentbc5038e6c344803bce76add47b13ceaa61a5bde3 (diff)
downloadmakers-57e4222900de52da5ffe13933fbebe91fbd7bab9.tar.gz
makers-57e4222900de52da5ffe13933fbebe91fbd7bab9.zip
almost finish implementing functions
-rw-r--r--src/makefile/functions.rs312
-rw-r--r--src/makefile/macro.rs173
-rw-r--r--src/makefile/mod.rs8
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::<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)
}
}