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/mod.rs | 306 ++++++++++++++++++++++------------------------------ 1 file changed, 128 insertions(+), 178 deletions(-) (limited to 'src/makefile/mod.rs') diff --git a/src/makefile/mod.rs b/src/makefile/mod.rs index 1fc8303..7610197 100644 --- a/src/makefile/mod.rs +++ b/src/makefile/mod.rs @@ -7,21 +7,23 @@ use std::io::{BufRead, BufReader, Error as IoError}; use std::path::Path; use std::rc::Rc; +use crate::args::Args; use lazy_static::lazy_static; use regex::Regex; -use crate::args::Args; - mod command_line; mod conditional; mod functions; mod inference_rules; +mod r#macro; +mod pattern; mod target; mod token; use command_line::CommandLine; use conditional::{ConditionalLine, ConditionalState}; use inference_rules::InferenceRule; +use r#macro::{LookupInternal, MacroSet, MacroSource}; use target::Target; use token::{tokenize, Token, TokenString}; @@ -58,13 +60,6 @@ impl LineType { } } -enum MacroSource { - File, - CommandLineOrMakeflags, - Environment, - Builtin, -} - fn inference_match<'a>( targets: &[&'a str], prerequisites: &[String], @@ -91,7 +86,7 @@ fn inference_match<'a>( pub(crate) struct Makefile<'a> { inference_rules: Vec, - macros: HashMap, + macros: MacroSet, targets: RefCell>>>, pub(crate) first_non_special_target: Option, args: &'a Args, @@ -101,15 +96,13 @@ pub(crate) struct Makefile<'a> { impl<'a> Makefile<'a> { pub(crate) fn new(args: &'a Args) -> Self { let mut inference_rules = vec![]; - let mut macros = HashMap::new(); + let mut macros = MacroSet::new(); let mut targets = HashMap::new(); let first_non_special_target = None; if !args.no_builtin_rules { inference_rules.extend(builtin_inference_rules()); - for (k, v) in builtin_macros() { - macros.insert(k.into(), (MacroSource::Builtin, v)); - } + macros.add_builtins(); targets.extend( builtin_targets() .into_iter() @@ -117,20 +110,14 @@ impl<'a> Makefile<'a> { ); } - for (k, v) in env::vars() { - if k != "MAKEFLAGS" && k != "SHELL" { - macros.insert(k, (MacroSource::Environment, TokenString::text(v))); - } - } + macros.add_env(); for r#macro in args.macros() { if let [name, value] = *r#macro.splitn(2, '=').collect::>() { - macros.insert( + macros.set( name.into(), - ( - MacroSource::CommandLineOrMakeflags, - TokenString::text(value), - ), + MacroSource::CommandLineOrMakeflags, + TokenString::text(value), ); } } @@ -197,7 +184,7 @@ impl<'a> Makefile<'a> { { line.action( conditional_stack.last(), - |name| self.macros.contains_key(name), + |name| self.macros.is_defined(name), |t| self.expand_macros(t, None), ) .apply_to(&mut conditional_stack); @@ -375,7 +362,7 @@ impl<'a> Makefile<'a> { _ => {} } - let value = match self.macros.remove(name) { + let value = match self.macros.pop(name) { Some((_, mut old_value)) if append => { // TODO eagerly expand if appending to eagerly-expanded macro old_value.extend(TokenString::text(" ")); @@ -384,7 +371,7 @@ impl<'a> Makefile<'a> { } _ => value, }; - self.macros.insert(name.into(), (MacroSource::File, value)); + self.macros.set(name.into(), MacroSource::File, value); } fn special_target_has_prereq(&self, target: &str, name: &str) -> bool { @@ -501,104 +488,75 @@ impl<'a> Makefile<'a> { } fn expand_macros(&self, text: &TokenString, target: Option<&Target>) -> 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 target = target.expect("internal macro but no current target!"); - let macro_pieces = if name.starts_with('@') { - // The $@ shall evaluate to the full target name of the - // current target. - vec![target.name.clone()] - } else if name.starts_with('?') { - // The $? macro shall evaluate to the list of prerequisites - // that are newer than the current target. - target - .prerequisites - .iter() - .filter(|prereq| { - self.get_target(prereq) - .borrow() - .newer_than(target) - .unwrap_or(false) - }) - .cloned() - .collect() - } else if name.starts_with('<') { - // In an inference rule, the $< macro shall evaluate to the - // filename whose existence allowed the inference rule to be - // chosen for the target. In the .DEFAULT rule, the $< macro - // shall evaluate to the current target name. - target.prerequisites.clone() - } else if name.starts_with('*') { - // The $* macro shall evaluate to the current target name with - // its suffix deleted. - vec![Path::new(name).with_extension("").to_string_lossy().into()] - } else { - unreachable!() - }; + let target = target.cloned(); + let lookup_internal = move |name: &str| { + let target = target + .as_ref() + .expect("internal macro but no current target!"); + let macro_pieces = if name.starts_with('@') { + // The $@ shall evaluate to the full target name of the + // current target. + vec![target.name.clone()] + } else if name.starts_with('?') { + // The $? macro shall evaluate to the list of prerequisites + // that are newer than the current target. + target + .prerequisites + .iter() + .filter(|prereq| { + self.get_target(prereq) + .borrow() + .newer_than(&target) + .unwrap_or(false) + }) + .cloned() + .collect() + } else if name.starts_with('<') { + // In an inference rule, the $< macro shall evaluate to the + // filename whose existence allowed the inference rule to be + // chosen for the target. In the .DEFAULT rule, the $< macro + // shall evaluate to the current target name. + target.prerequisites.clone() + } else if name.starts_with('*') { + // The $* macro shall evaluate to the current target name with + // its suffix deleted. + vec![Path::new(name).with_extension("").to_string_lossy().into()] + } else { + unreachable!() + }; - let macro_pieces = if name.ends_with('D') { - macro_pieces - .into_iter() - .map(|x| { - Path::new(&x) - .parent() - .expect("no parent") - .to_string_lossy() - .into() - }) - .collect() - } else if name.ends_with('F') { - macro_pieces - .into_iter() - .map(|x| { - Path::new(&x) - .file_name() - .expect("no filename") - .to_string_lossy() - .into() - }) - .collect() - } else { - macro_pieces - }; + let macro_pieces = if name.ends_with('D') { + macro_pieces + .into_iter() + .map(|x| { + Path::new(&x) + .parent() + .expect("no parent") + .to_string_lossy() + .into() + }) + .collect() + } else if name.ends_with('F') { + macro_pieces + .into_iter() + .map(|x| { + Path::new(&x) + .file_name() + .expect("no filename") + .to_string_lossy() + .into() + }) + .collect() + } else { + macro_pieces + }; - macro_pieces.join(" ") - } else { - self.macros - .get(name) - .map_or_else(String::new, |(_, macro_value)| { - self.expand_macros(macro_value, target) - }) - }; - let macro_value = match replacement { - Some((subst1, subst2)) => { - let subst1 = self.expand_macros(subst1, target); - let subst1_suffix = regex::escape(&subst1); - let subst1_suffix = - Regex::new(&format!(r"{}\b", subst1_suffix)).unwrap(); - let subst2 = self.expand_macros(subst2, target); - subst1_suffix.replace_all(¯o_value, subst2).to_string() - } - None => macro_value, - }; - result.push_str(¯o_value); - } - Token::FunctionCall { name, args } => { - result.push_str(&self.expand_macros(&functions::call(name, args), None)); - } - } - } - result + macro_pieces.join(" ") + }; + + let lookup_internal: Rc = Rc::new(lookup_internal); + self.macros.lookup(Rc::downgrade(&lookup_internal)); + self.macros.expand(text) } } @@ -614,9 +572,7 @@ impl fmt::Display for Makefile<'_> { writeln!(f)?; header(f, "Macros")?; - for (k, (_, v)) in &self.macros { - writeln!(f, "{}={}", k, v)?; - } + writeln!(f, "{}", &self.macros)?; writeln!(f)?; header(f, "Targets")?; @@ -691,54 +647,6 @@ fn builtin_inference_rules() -> Vec { "rm -f $*.o" } } -fn builtin_macros() -> Vec<(&'static str, TokenString)> { - // Fuck it, might as well. - macro_rules! handle { - ($value:ident) => { - stringify!($value) - }; - ($value:literal) => { - $value - }; - } - macro_rules! make { - ($($name:ident=$value:tt)+) => {vec![$( - (stringify!($name), handle!($value).parse().unwrap()) - ),+]}; - } - - make![ - MAKE=makers - AR=ar - YACC=yacc - YFLAGS="" - LEX=lex - LFLAGS="" - LDFLAGS="" - - AS=as - CC=cc - CXX="g++" - CPP="$(CC) -E" - FC=f77 - PC=pc - CO=co - GET=get - LINT=lint - MAKEINFO=makeinfo - TEX=tex - TEXI2DVI=texi2dvi - WEAVE=weave - CWEAVE=cweave - TANGLE=tangle - CTANGLE=ctangle - RM="rm -f" - - ARFLAGS="rv" - CFLAGS="" - FFLAGS="" - ] -} fn builtin_targets() -> Vec { // even i'm not going to do that just for this vec![Target { @@ -754,14 +662,14 @@ fn builtin_targets() -> Vec { #[cfg(test)] mod test { - use super::*; - use std::io::Cursor; + use super::*; + fn empty_makefile(args: &Args) -> Makefile { Makefile { inference_rules: vec![], - macros: HashMap::new(), + macros: MacroSet::new(), targets: RefCell::new(HashMap::new()), first_non_special_target: None, args, @@ -781,8 +689,50 @@ endif let mut makefile = empty_makefile(&args); makefile.and_read(Cursor::new(file)); assert_eq!( - makefile.expand_macros(&"$(worked)".parse().unwrap(), None), + makefile.expand_macros(&TokenString::r#macro("worked"), None), "yes" ); } + + #[test] + #[ignore = "I still haven't implemented `eval` or `define` or %-based macro substitution."] + fn eval() { + // This, for the record, is a terrible misfeature. + // If you need this, you probably shouldn't be using Make. + // But a lot of people are using this and still use Make anyway, so here we go, + // I guess. + + let file = " +PROGRAMS = server client + +server_OBJS = server.o server_priv.o server_access.o +server_LIBS = priv protocol + +client_OBJS = client.o client_api.o client_mem.o +client_LIBS = protocol + +# Everything after this is generic + +.PHONY: all +all: $(PROGRAMS) + +define PROGRAM_template = + $(1): $$($(1)_OBJS) $$($(1)_LIBS:%=-l%) + ALL_OBJS += $$($(1)_OBJS) +endef + +$(foreach prog,$(PROGRAMS),$(eval $(call PROGRAM_template,$(prog)))) + +$(PROGRAMS): + $(LINK.o) $^ $(LDLIBS) -o $@ + +clean: + rm -f $(ALL_OBJS) $(PROGRAMS) + "; + + let args = Args::empty(); + let mut makefile = empty_makefile(&args); + makefile.and_read(Cursor::new(file)); + assert!(makefile.targets.borrow().contains_key("server")) + } } -- cgit v1.2.3