aboutsummaryrefslogtreecommitdiff
path: root/src/makefile/functions.rs
diff options
context:
space:
mode:
authorMelody Horn <melody@boringcactus.com>2021-03-28 14:57:03 -0600
committerMelody Horn <melody@boringcactus.com>2021-03-28 14:57:03 -0600
commitbc5038e6c344803bce76add47b13ceaa61a5bde3 (patch)
treeeec9a8fc36562aa4413cf8be8ef22910fdc75cac /src/makefile/functions.rs
parent82278c3a5aa93204c963a63cc3cfefea1d0fb3fd (diff)
downloadmakers-bc5038e6c344803bce76add47b13ceaa61a5bde3.tar.gz
makers-bc5038e6c344803bce76add47b13ceaa61a5bde3.zip
almost implement all functions
Diffstat (limited to 'src/makefile/functions.rs')
-rw-r--r--src/makefile/functions.rs285
1 files changed, 274 insertions, 11 deletions
diff --git a/src/makefile/functions.rs b/src/makefile/functions.rs
index bc39886..b9a6a6d 100644
--- a/src/makefile/functions.rs
+++ b/src/makefile/functions.rs
@@ -1,26 +1,160 @@
+use std::env;
+use std::path::Path;
+
+use super::pattern::r#match;
+use super::r#macro::{MacroSet, MacroSource};
use super::token::TokenString;
-pub(crate) fn call(name: &str, args: &[TokenString]) -> 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 {
match name {
// Text Functions
- "filter" => todo!(),
- "filter-out" => todo!(),
- "sort" => todo!(),
+ "filter" => {
+ assert_eq!(args.len(), 2);
+ 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(" ")
+ }
+ "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(" ")
+ }
// File Name Functions
- "notdir" => todo!(),
- "basename" => todo!(),
- "addprefix" => todo!(),
- "wildcard" => todo!(),
+ "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(" ")
+ }
+ "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(" ")
+ }
+ "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(" ")
+ }
+ "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(" ")
+ }
// foreach
- "foreach" => todo!(),
+ "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(" ")
+ }
// call
- "call" => todo!(),
+ "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))
+ }
// eval
- "eval" => todo!(),
+ "eval" => {
+ assert_eq!(args.len(), 1);
+ let arg = macros.expand(&args[0]);
+ todo!()
+ }
// shell
"shell" => todo!(),
@@ -29,3 +163,132 @@ pub(crate) fn call(name: &str, args: &[TokenString]) -> TokenString {
_ => panic!("function not implemented: {}", name),
}
}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ fn call(name: &str, args: &[TokenString], macros: &MacroSet) -> String {
+ super::call(name, args, macros, lookup_fail)
+ }
+
+ macro_rules! call {
+ ($func:literal $($arg:literal),+) => {
+ call($func, &[$(TokenString::text($arg)),+], &MacroSet::new())
+ };
+ ($func:ident $($arg:literal),+) => {
+ call(stringify!($func), &[$(TokenString::text($arg)),+], &MacroSet::new())
+ };
+ }
+
+ #[test]
+ fn filter() {
+ let result = call!(filter "word", "this contains a word inside it");
+ assert_eq!(result, "word");
+
+ let result = call!(filter "%.c %.s", "foo.c bar.c baz.s ugh.h");
+ assert_eq!(result, "foo.c bar.c baz.s");
+ }
+
+ #[test]
+ fn filter_out() {
+ let result = call!("filter-out" "main1.o main2.o", "main1.o foo.o main2.o bar.o");
+ assert_eq!(result, "foo.o bar.o");
+ }
+
+ #[test]
+ fn sort() {
+ let result = call!(sort "foo bar lose foo");
+ assert_eq!(result, "bar foo lose");
+ }
+
+ #[test]
+ fn notdir() {
+ let result = call!(notdir "src/foo.c hacks");
+ assert_eq!(result, "foo.c hacks");
+ }
+
+ #[test]
+ fn basename() {
+ let result = call!(basename "src/foo.c src-1.0/bar hacks");
+ assert_eq!(result, "src/foo src-1.0/bar hacks");
+ }
+
+ #[test]
+ fn addprefix() {
+ let result = call!(addprefix "src/", "foo bar");
+ assert_eq!(result, "src/foo src/bar");
+ }
+
+ #[test]
+ fn wildcard() {
+ use std::env::{set_current_dir, set_var};
+ use std::fs::write;
+ use std::path::MAIN_SEPARATOR;
+
+ let tempdir = tempfile::tempdir().unwrap();
+
+ write(tempdir.path().join("foo.c"), "").unwrap();
+ write(tempdir.path().join("bar.h"), "").unwrap();
+ write(tempdir.path().join("baz.txt"), "").unwrap();
+ write(tempdir.path().join("acab.c"), "ACAB").unwrap();
+ write(tempdir.path().join("based.txt"), "☭").unwrap();
+
+ set_current_dir(tempdir.path()).unwrap();
+ set_var("HOME", tempdir.path().to_str().unwrap());
+ let sort = |x: String| call("sort", &[TokenString::text(&x)], &MacroSet::new());
+ assert_eq!(sort(call!(wildcard "*.c")), "acab.c foo.c");
+ assert_eq!(
+ sort(call!(wildcard "~/ba?.*")),
+ format!(
+ "{0}{1}bar.h {0}{1}baz.txt",
+ tempdir.path().to_str().unwrap(),
+ MAIN_SEPARATOR
+ )
+ );
+ }
+
+ #[test]
+ fn foreach() {
+ let mut macros = MacroSet::new();
+ macros.set(
+ "test".to_string(),
+ MacroSource::File,
+ "worked for $(item).".parse().unwrap(),
+ );
+ assert_eq!(
+ call(
+ "foreach",
+ &[
+ TokenString::text("item"),
+ TokenString::text("a b c d"),
+ TokenString::r#macro("test")
+ ],
+ &macros,
+ ),
+ "worked for a. worked for b. worked for c. worked for d."
+ );
+ }
+
+ #[test]
+ fn call_test() {
+ let mut macros = MacroSet::new();
+ macros.set(
+ "reverse".to_string(),
+ MacroSource::File,
+ "$(2) $(1)".parse().unwrap(),
+ );
+ assert_eq!(
+ call(
+ "call",
+ &[
+ TokenString::text("reverse"),
+ TokenString::text("a"),
+ TokenString::text("b")
+ ],
+ &macros,
+ ),
+ "b a"
+ );
+ }
+}