From bc5038e6c344803bce76add47b13ceaa61a5bde3 Mon Sep 17 00:00:00 2001 From: Melody Horn Date: Sun, 28 Mar 2021 14:57:03 -0600 Subject: almost implement all functions --- src/makefile/functions.rs | 285 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 274 insertions(+), 11 deletions(-) (limited to 'src/makefile/functions.rs') 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::>(); + 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::>(); + 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::>(); + 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::>(); + 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::>(); + 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::>(); + 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::>(); + 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::>(); + results.join(" ") + } // call - "call" => todo!(), + "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)) + } // 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") + ], + ¯os, + ), + "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") + ], + ¯os, + ), + "b a" + ); + } +} -- cgit v1.2.3