diff options
-rw-r--r-- | src/makefile/command_line.rs | 2 | ||||
-rw-r--r-- | src/makefile/conditional.rs | 4 | ||||
-rw-r--r-- | src/makefile/eval_context.rs | 52 | ||||
-rw-r--r-- | src/makefile/functions.rs | 284 | ||||
-rw-r--r-- | src/makefile/input.rs | 64 | ||||
-rw-r--r-- | src/makefile/macro.rs | 60 | ||||
-rw-r--r-- | src/makefile/mod.rs | 6 |
7 files changed, 314 insertions, 158 deletions
diff --git a/src/makefile/command_line.rs b/src/makefile/command_line.rs index ae6c9e9..c993a10 100644 --- a/src/makefile/command_line.rs +++ b/src/makefile/command_line.rs @@ -36,7 +36,7 @@ fn execute_command_line( let mut command = Command::new(program); command.args(args); #[cfg(feature = "full")] - command.envs(macros.resolve_exports()?); + command.envs(macros.resolve_exports::<&[u8]>(None)?); Ok(command.status()?) } diff --git a/src/makefile/conditional.rs b/src/makefile/conditional.rs index 98400e6..6eed14a 100644 --- a/src/makefile/conditional.rs +++ b/src/makefile/conditional.rs @@ -99,7 +99,7 @@ fn decode_condition_args(line_body: &str) -> Option<(TokenString, TokenString)> impl Line { pub fn from( line: &str, - expand_macro: impl Fn(&TokenString) -> Result<String>, + mut expand_macro: impl FnMut(&TokenString) -> Result<String>, ) -> Result<Option<Self>> { let line = line.trim_start(); Ok(Some(if let Some(line) = line.strip_prefix("ifeq ") { @@ -134,7 +134,7 @@ impl Line { &self, current_state: Option<&State>, is_macro_defined: impl Fn(&str) -> bool, - expand_macro: impl Fn(&TokenString) -> Result<String>, + mut expand_macro: impl FnMut(&TokenString) -> Result<String>, ) -> Result<StateAction> { Ok(match self { Self::IfEqual(arg1, arg2) => { diff --git a/src/makefile/eval_context.rs b/src/makefile/eval_context.rs new file mode 100644 index 0000000..06f4fbf --- /dev/null +++ b/src/makefile/eval_context.rs @@ -0,0 +1,52 @@ +use eyre::{Result, WrapErr}; +use std::io::{BufRead, Cursor}; +use std::rc::Rc; + +use crate::makefile::input::FinishedMakefileReader; +use crate::makefile::MakefileReader; + +pub struct DeferredEvalContext<'parent, 'args, 'grandparent, R: BufRead> { + parent: &'parent MakefileReader<'args, 'grandparent, R>, + children: Vec<FinishedMakefileReader>, +} + +impl<'parent, 'args, 'grandparent, R: BufRead> + DeferredEvalContext<'parent, 'args, 'grandparent, R> +{ + pub fn new(parent: &'parent MakefileReader<'args, 'grandparent, R>) -> Self { + Self { + parent, + children: Vec::new(), + } + } + + pub fn push(&mut self, child: FinishedMakefileReader) { + self.children.push(child); + } + + pub fn eval(&mut self, to_eval: String) -> Result<()> { + let child_macros = self.parent.macros.with_overlay(); + let child = MakefileReader::read( + self.parent.args, + child_macros, + Cursor::new(to_eval), + "<eval>", + Rc::clone(&self.parent.file_names), + ) + .context("while evaling")? + .finish(); + self.push(child); + Ok(()) + } +} + +impl<'parent, 'args, 'grandparent, R: BufRead> IntoIterator + for DeferredEvalContext<'parent, 'args, 'grandparent, R> +{ + type Item = FinishedMakefileReader; + type IntoIter = std::vec::IntoIter<Self::Item>; + + fn into_iter(self) -> Self::IntoIter { + self.children.into_iter() + } +} diff --git a/src/makefile/functions.rs b/src/makefile/functions.rs index 5d5e222..e76a0d2 100644 --- a/src/makefile/functions.rs +++ b/src/makefile/functions.rs @@ -1,129 +1,131 @@ -use std::cell::RefCell; use std::env; +use std::io::BufRead; use std::process::{Command, Stdio}; -use std::rc::Rc; use eyre::{bail, Result, WrapErr}; +use super::eval_context::DeferredEvalContext; use super::pattern::r#match; use super::r#macro::{Macro, Set as MacroSet}; use super::token::TokenString; use super::ItemSource; +pub const NO_EVAL: Option<&mut DeferredEvalContext<&[u8]>> = None; + #[allow(clippy::cognitive_complexity)] pub fn expand_call( name: &str, args: &[TokenString], macros: &MacroSet, - to_eval: Option<Rc<RefCell<Vec<String>>>>, + mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, ) -> Result<String> { match name { "subst" => { assert_eq!(args.len(), 3); - text::subst(macros, &args[0], &args[1], &args[2]) + text::subst(macros, &args[0], &args[1], &args[2], eval_context) } "patsubst" => { assert_eq!(args.len(), 3); - text::patsubst(macros, &args[0], &args[1], &args[2]) + text::patsubst(macros, &args[0], &args[1], &args[2], eval_context) } "strip" => { assert_eq!(args.len(), 1); - text::strip(macros, &args[0]) + text::strip(macros, &args[0], eval_context) } "findstring" => { assert_eq!(args.len(), 2); - text::findstring(macros, &args[0], &args[1]) + text::findstring(macros, &args[0], &args[1], eval_context) } "filter" => { assert_eq!(args.len(), 2); - text::filter(macros, &args[0], &args[1]) + text::filter(macros, &args[0], &args[1], eval_context) } "filter-out" => { assert_eq!(args.len(), 2); - text::filter_out(macros, &args[0], &args[1]) + text::filter_out(macros, &args[0], &args[1], eval_context) } "sort" => { assert_eq!(args.len(), 1); - text::sort(macros, &args[0]) + text::sort(macros, &args[0], eval_context) } "word" => { assert_eq!(args.len(), 2); - text::word(macros, &args[0], &args[1]) + text::word(macros, &args[0], &args[1], eval_context) } "words" => { assert_eq!(args.len(), 1); - text::words(macros, &args[0]) + text::words(macros, &args[0], eval_context) } "firstword" => { assert_eq!(args.len(), 1); - text::firstword(macros, &args[0]) + text::firstword(macros, &args[0], eval_context) } "lastword" => { assert_eq!(args.len(), 1); - text::lastword(macros, &args[0]) + text::lastword(macros, &args[0], eval_context) } "dir" => { assert_eq!(args.len(), 1); - file_name::dir(macros, &args[0]) + file_name::dir(macros, &args[0], eval_context) } "notdir" => { assert_eq!(args.len(), 1); - file_name::notdir(macros, &args[0]) + file_name::notdir(macros, &args[0], eval_context) } "basename" => { assert_eq!(args.len(), 1); - file_name::basename(macros, &args[0]) + file_name::basename(macros, &args[0], eval_context) } "addsuffix" => { assert_eq!(args.len(), 2); - file_name::addsuffix(macros, &args[0], &args[1]) + file_name::addsuffix(macros, &args[0], &args[1], eval_context) } "addprefix" => { assert_eq!(args.len(), 2); - file_name::addprefix(macros, &args[0], &args[1]) + file_name::addprefix(macros, &args[0], &args[1], eval_context) } "wildcard" => { assert_eq!(args.len(), 1); - file_name::wildcard(macros, &args[0]) + file_name::wildcard(macros, &args[0], eval_context) } "realpath" => { assert_eq!(args.len(), 1); - file_name::realpath(macros, &args[0]) + file_name::realpath(macros, &args[0], eval_context) } "abspath" => { assert_eq!(args.len(), 1); - file_name::abspath(macros, &args[0]) + file_name::abspath(macros, &args[0], eval_context) } "if" => { assert!(args.len() == 2 || args.len() == 3); - conditional::r#if(macros, &args[0], &args[1], args.get(2)) + conditional::r#if(macros, &args[0], &args[1], args.get(2), eval_context) } "or" => { assert!(!args.is_empty()); - conditional::or(macros, args.iter()) + conditional::or(macros, args.iter(), eval_context) } "and" => { assert!(!args.is_empty()); - conditional::and(macros, args.iter()) + conditional::and(macros, args.iter(), eval_context) } "foreach" => { assert_eq!(args.len(), 3); - foreach(macros, &args[0], &args[1], &args[2]) + foreach(macros, &args[0], &args[1], &args[2], eval_context) } "call" => { assert!(!args.is_empty()); - call(macros, args.iter()) + call(macros, args.iter(), eval_context) } "eval" => { assert_eq!(args.len(), 1); - let should_eval = eval(macros, &args[0])?; - if let Some(to_eval) = to_eval { - to_eval.borrow_mut().push(should_eval); + let should_eval = eval(macros, &args[0], eval_context.as_deref_mut())?; + if let Some(eval_context) = eval_context { + eval_context.eval(should_eval)?; } else { bail!("tried to eval something but no eval back-channel was available"); } @@ -132,17 +134,17 @@ pub fn expand_call( "origin" => { assert_eq!(args.len(), 1); - origin(macros, &args[0]) + origin(macros, &args[0], eval_context) } "error" => { assert_eq!(args.len(), 1); - meta::error(macros, &args[0]) + meta::error(macros, &args[0], eval_context) } "shell" => { assert_eq!(args.len(), 1); - shell(macros, &args[0]) + shell(macros, &args[0], eval_context) } // fallback @@ -159,10 +161,11 @@ mod text { from: &TokenString, to: &TokenString, text: &TokenString, + mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, ) -> Result<String> { - let from = macros.expand(from)?; - let to = macros.expand(to)?; - let text = macros.expand(text)?; + let from = macros.expand(from, eval_context.as_deref_mut())?; + let to = macros.expand(to, eval_context.as_deref_mut())?; + let text = macros.expand(text, eval_context)?; Ok(text.replace(&from, &to)) } @@ -171,10 +174,11 @@ mod text { from: &TokenString, to: &TokenString, text: &TokenString, + mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, ) -> Result<String> { - let from = macros.expand(from)?; - let to = macros.expand(to)?; - let text = macros.expand(text)?; + let from = macros.expand(from, eval_context.as_deref_mut())?; + let to = macros.expand(to, eval_context.as_deref_mut())?; + let text = macros.expand(text, eval_context)?; let words = text.split_whitespace() .map(|word| { @@ -186,8 +190,12 @@ mod text { Ok(words.join(" ")) } - pub fn strip(macros: &MacroSet, text: &TokenString) -> Result<String> { - let text = macros.expand(text)?; + pub fn strip( + macros: &MacroSet, + text: &TokenString, + eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, + ) -> Result<String> { + let text = macros.expand(text, eval_context)?; // TODO don't allocate this vec let words = text.split_whitespace().collect::<Vec<_>>(); Ok(words.join(" ")) @@ -197,9 +205,10 @@ mod text { macros: &MacroSet, needle: &TokenString, haystack: &TokenString, + mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, ) -> Result<String> { - let needle = macros.expand(needle)?; - let haystack = macros.expand(haystack)?; + let needle = macros.expand(needle, eval_context.as_deref_mut())?; + let haystack = macros.expand(haystack, eval_context)?; if haystack.contains(&needle) { Ok(needle) } else { @@ -207,10 +216,15 @@ mod text { } } - pub fn filter(macros: &MacroSet, patterns: &TokenString, text: &TokenString) -> Result<String> { - let patterns = macros.expand(patterns)?; + pub fn filter( + macros: &MacroSet, + patterns: &TokenString, + text: &TokenString, + mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, + ) -> Result<String> { + let patterns = macros.expand(patterns, eval_context.as_deref_mut())?; let patterns = patterns.split_whitespace().collect::<Vec<_>>(); - let text = macros.expand(text)?; + let text = macros.expand(text, eval_context)?; let text = text.split_whitespace(); let mut result_pieces = vec![]; for word in text { @@ -228,10 +242,11 @@ mod text { macros: &MacroSet, patterns: &TokenString, text: &TokenString, + mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, ) -> Result<String> { - let patterns = macros.expand(patterns)?; + let patterns = macros.expand(patterns, eval_context.as_deref_mut())?; let patterns = patterns.split_whitespace().collect::<Vec<_>>(); - let text = macros.expand(text)?; + let text = macros.expand(text, eval_context)?; let text = text.split_whitespace(); let mut result_pieces = vec![]; for word in text { @@ -245,18 +260,27 @@ mod text { Ok(result_pieces.join(" ")) } - pub fn sort(macros: &MacroSet, words: &TokenString) -> Result<String> { - let words = macros.expand(words)?; + pub fn sort( + macros: &MacroSet, + words: &TokenString, + eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, + ) -> Result<String> { + let words = macros.expand(words, eval_context)?; let mut words = words.split_whitespace().collect::<Vec<_>>(); words.sort_unstable(); words.dedup(); Ok(words.join(" ")) } - pub fn word(macros: &MacroSet, n: &TokenString, text: &TokenString) -> Result<String> { - let n = macros.expand(n)?; + pub fn word( + macros: &MacroSet, + n: &TokenString, + text: &TokenString, + mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, + ) -> Result<String> { + let n = macros.expand(n, eval_context.as_deref_mut())?; let n: usize = n.parse().wrap_err("while calling `word`")?; - let text = macros.expand(text)?; + let text = macros.expand(text, eval_context)?; Ok(text .split_whitespace() .nth(n.saturating_add(1)) @@ -264,18 +288,30 @@ mod text { .to_owned()) } - pub fn words(macros: &MacroSet, words: &TokenString) -> Result<String> { - let words = macros.expand(words)?; + pub fn words( + macros: &MacroSet, + words: &TokenString, + eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, + ) -> Result<String> { + let words = macros.expand(words, eval_context)?; Ok(words.split_whitespace().count().to_string()) } - pub fn firstword(macros: &MacroSet, words: &TokenString) -> Result<String> { - let words = macros.expand(words)?; + pub fn firstword( + macros: &MacroSet, + words: &TokenString, + eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, + ) -> Result<String> { + let words = macros.expand(words, eval_context)?; Ok(words.split_whitespace().next().unwrap_or("").to_owned()) } - pub fn lastword(macros: &MacroSet, words: &TokenString) -> Result<String> { - let words = macros.expand(words)?; + pub fn lastword( + macros: &MacroSet, + words: &TokenString, + eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, + ) -> Result<String> { + let words = macros.expand(words, eval_context)?; Ok(words.split_whitespace().last().unwrap_or("").to_owned()) } } @@ -285,14 +321,19 @@ mod file_name { use std::env; use std::ffi::OsStr; use std::fs; + use std::io::BufRead; use std::path::{Path, MAIN_SEPARATOR}; - use eyre::WrapErr; - use super::*; + use crate::makefile::eval_context::DeferredEvalContext; + use eyre::WrapErr; - pub fn dir(macros: &MacroSet, words: &TokenString) -> Result<String> { - let words = macros.expand(words)?; + pub fn dir( + macros: &MacroSet, + words: &TokenString, + eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, + ) -> Result<String> { + let words = macros.expand(words, eval_context)?; let words = words .split_whitespace() .map(|word| { @@ -307,8 +348,12 @@ mod file_name { Ok(words.join(" ")) } - pub fn notdir(macros: &MacroSet, words: &TokenString) -> Result<String> { - let words = macros.expand(words)?; + pub fn notdir( + macros: &MacroSet, + words: &TokenString, + eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, + ) -> Result<String> { + let words = macros.expand(words, eval_context)?; let words = words .split_whitespace() .map(|word| { @@ -321,8 +366,12 @@ mod file_name { Ok(words.join(" ")) } - pub fn basename(macros: &MacroSet, words: &TokenString) -> Result<String> { - let words = macros.expand(words)?; + pub fn basename( + macros: &MacroSet, + words: &TokenString, + eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, + ) -> Result<String> { + let words = macros.expand(words, eval_context)?; let words = words .split_whitespace() .map(|word| { @@ -339,9 +388,10 @@ mod file_name { macros: &MacroSet, suffix: &TokenString, targets: &TokenString, + mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, ) -> Result<String> { - let suffix = macros.expand(suffix)?; - let targets = macros.expand(targets)?; + let suffix = macros.expand(suffix, eval_context.as_deref_mut())?; + let targets = macros.expand(targets, eval_context)?; let results = targets .split_whitespace() .map(|t| format!("{}{}", t, suffix)) @@ -353,9 +403,10 @@ mod file_name { macros: &MacroSet, prefix: &TokenString, targets: &TokenString, + mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, ) -> Result<String> { - let prefix = macros.expand(prefix)?; - let targets = macros.expand(targets)?; + let prefix = macros.expand(prefix, eval_context.as_deref_mut())?; + let targets = macros.expand(targets, eval_context)?; let results = targets .split_whitespace() .map(|t| format!("{}{}", prefix, t)) @@ -363,8 +414,12 @@ mod file_name { Ok(results.join(" ")) } - pub fn wildcard(macros: &MacroSet, pattern: &TokenString) -> Result<String> { - let pattern = macros.expand(pattern)?; + pub fn wildcard( + macros: &MacroSet, + pattern: &TokenString, + eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, + ) -> Result<String> { + let pattern = macros.expand(pattern, eval_context)?; let home_dir = env::var("HOME") .ok() .or_else(|| dirs::home_dir().and_then(|p| p.to_str().map(String::from))); @@ -383,8 +438,12 @@ mod file_name { Ok(results.join(" ")) } - pub fn realpath(macros: &MacroSet, targets: &TokenString) -> Result<String> { - let targets = macros.expand(targets)?; + pub fn realpath( + macros: &MacroSet, + targets: &TokenString, + eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, + ) -> Result<String> { + let targets = macros.expand(targets, eval_context)?; let results = targets .split_whitespace() .map(|x| { @@ -396,9 +455,13 @@ mod file_name { Ok(results.join(" ")) } - pub fn abspath(macros: &MacroSet, targets: &TokenString) -> Result<String> { + pub fn abspath( + macros: &MacroSet, + targets: &TokenString, + eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, + ) -> Result<String> { // TODO don't resolve symlinks - realpath(macros, targets) + realpath(macros, targets, eval_context) } } @@ -411,24 +474,29 @@ mod conditional { condition: &TokenString, if_true: &TokenString, if_false: Option<&TokenString>, + mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, ) -> Result<String> { let mut condition = condition.clone(); condition.trim_start(); condition.trim_end(); - let condition = macros.expand(&condition)?; + let condition = macros.expand(&condition, eval_context.as_deref_mut())?; if condition.is_empty() { - if_false.map_or_else(|| Ok(String::new()), |if_false| macros.expand(if_false)) + if_false.map_or_else( + || Ok(String::new()), + |if_false| macros.expand(if_false, eval_context), + ) } else { - macros.expand(if_true) + macros.expand(if_true, eval_context) } } pub fn or<'a>( macros: &MacroSet, args: impl Iterator<Item = &'a TokenString>, + mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, ) -> Result<String> { for arg in args { - let arg = macros.expand(arg)?; + let arg = macros.expand(arg, eval_context.as_deref_mut())?; if !arg.is_empty() { return Ok(arg); } @@ -439,10 +507,11 @@ mod conditional { pub fn and<'a>( macros: &MacroSet, args: impl Iterator<Item = &'a TokenString>, + mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, ) -> Result<String> { let mut last = String::new(); for arg in args { - last = macros.expand(arg)?; + last = macros.expand(arg, eval_context.as_deref_mut())?; if last.is_empty() { return Ok(String::new()); } @@ -456,9 +525,10 @@ pub fn foreach( var: &TokenString, list: &TokenString, text: &TokenString, + mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, ) -> Result<String> { - let var = macros.expand(var)?; - let list = macros.expand(list)?; + let var = macros.expand(var, eval_context.as_deref_mut())?; + let list = macros.expand(list, eval_context.as_deref_mut())?; let words = list.split_whitespace(); let mut macros = macros.with_overlay(); @@ -473,15 +543,19 @@ pub fn foreach( eagerly_expanded: false, }, ); - macros.expand(text) + macros.expand(text, eval_context.as_deref_mut()) }) .collect::<Result<Vec<_>, _>>()?; Ok(results.join(" ")) } -pub fn call<'a>(macros: &MacroSet, args: impl Iterator<Item = &'a TokenString>) -> Result<String> { +pub fn call<'a>( + macros: &MacroSet, + args: impl Iterator<Item = &'a TokenString>, + mut eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, +) -> Result<String> { let args = args - .map(|arg| macros.expand(arg)) + .map(|arg| macros.expand(arg, eval_context.as_deref_mut())) .collect::<Result<Vec<_>, _>>()?; let function = args[0].clone(); // TODO if function is a builtin, call the builtin instead @@ -498,31 +572,47 @@ pub fn call<'a>(macros: &MacroSet, args: impl Iterator<Item = &'a TokenString>) }, ); } - macros.expand(&TokenString::r#macro(function)) + macros.expand(&TokenString::r#macro(function), eval_context) } // TODO consider bringing eval logic in here since we put the Vec in MacroSet IIRC -pub fn eval(macros: &MacroSet, arg: &TokenString) -> Result<String> { - macros.expand(arg) +pub fn eval( + macros: &MacroSet, + arg: &TokenString, + eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, +) -> Result<String> { + macros.expand(arg, eval_context) } -pub fn origin(macros: &MacroSet, variable: &TokenString) -> Result<String> { - let variable = macros.expand(variable)?; +pub fn origin( + macros: &MacroSet, + variable: &TokenString, + eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, +) -> Result<String> { + let variable = macros.expand(variable, eval_context)?; Ok(macros.origin(&variable).to_owned()) } mod meta { use super::*; - pub fn error(macros: &MacroSet, text: &TokenString) -> Result<String> { - let text = macros.expand(text)?; + pub fn error( + macros: &MacroSet, + text: &TokenString, + eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, + ) -> Result<String> { + let text = macros.expand(text, eval_context)?; bail!("{}", text); } } -pub fn shell(macros: &MacroSet, command: &TokenString) -> Result<String> { +pub fn shell( + macros: &MacroSet, + command: &TokenString, + eval_context: Option<&mut DeferredEvalContext<impl BufRead>>, +) -> Result<String> { // TODO bring this in from command_line - let command = macros.expand(command)?; + let command = macros.expand(command, eval_context)?; let (program, args) = if cfg!(windows) { let cmd = env::var("COMSPEC").unwrap_or_else(|_| "cmd.exe".into()); let args = vec!["/c", &command]; @@ -551,7 +641,7 @@ mod test { type R = Result<()>; fn call(name: &str, args: &[TokenString], macros: &MacroSet) -> Result<String> { - super::expand_call(name, args, macros, None) + super::expand_call(name, args, macros, NO_EVAL) } macro_rules! call { diff --git a/src/makefile/input.rs b/src/makefile/input.rs index ed1140e..f1d4f70 100644 --- a/src/makefile/input.rs +++ b/src/makefile/input.rs @@ -2,8 +2,6 @@ use std::cell::{Cell, RefCell}; use std::collections::HashMap; use std::error::Error as StdError; use std::fs::File; -#[cfg(feature = "full")] -use std::io::Cursor; use std::io::{BufRead, BufReader, Error as IoError, ErrorKind as IoErrorKind, Lines}; use std::iter::Peekable; use std::path::Path; @@ -18,6 +16,7 @@ use crate::args::Args; use super::command_line::CommandLine; #[cfg(feature = "full")] use super::conditional::{Line as ConditionalLine, State as ConditionalState}; +use super::eval_context::DeferredEvalContext; use super::inference_rules::InferenceRule; #[cfg(feature = "full")] use super::r#macro::ExportConfig; @@ -97,10 +96,10 @@ fn inference_match<'a>( ) -> Option<InferenceMatch<'a>> { lazy_static! { static ref INFERENCE_RULE: Regex = #[allow(clippy::unwrap_used)] - Regex::new(r"^(?P<s2>(\.[^/.]+)?)(?P<s1>\.[^/.]+)$") + Regex::new(r"^(?P<s2>(\.[^/.]+)?)(?P<s1>\.[^/.]+)$") .unwrap(); static ref SPECIAL_TARGET: Regex = #[allow(clippy::unwrap_used)] - Regex::new(r"^\.[A-Z]+$").unwrap(); + Regex::new(r"^\.[A-Z]+$").unwrap(); } let inference_match = INFERENCE_RULE.captures(targets[0]); @@ -192,13 +191,13 @@ pub struct MakefileReader<'a, 'parent, R: BufRead> { built_in_targets: HashMap<String, Target>, pub first_non_special_target: Option<String>, pub failed_includes: Vec<String>, - args: &'a Args, + pub args: &'a Args, lines_iter: Peekable<LineNumbers<String, IoError, Lines<R>>>, // join with escaped_newline_replacement to get the actual line pending_line: Option<(usize, Vec<String>)>, #[cfg(feature = "full")] conditional_stack: Vec<ConditionalState>, - file_names: Rc<RefCell<Vec<String>>>, + pub file_names: Rc<RefCell<Vec<String>>>, } impl<'a, 'parent> MakefileReader<'a, 'parent, BufReader<File>> { @@ -311,24 +310,6 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> { .wrap_err_with(|| format!("while parsing line {}", line_number))? .trim(), ); - // and let's eval whatever bullshit needs evaling - #[cfg(feature = "full")] - { - let eval = self.macros.to_eval.take(); - for eval in eval { - let child_macros = self.macros.with_overlay(); - let child = MakefileReader::read( - self.args, - child_macros, - Cursor::new(eval), - "<eval>", - Rc::clone(&self.file_names), - ) - .context("while evaling")? - .finish(); - self.extend(child); - } - } let line_type = LineType::of(&line_tokens); (line_tokens, line_type) } else { @@ -471,15 +452,19 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> { Err(err) => return Some((line_number, Err(err))), }; if let Some(line) = cond_line { + let mut deferred_eval_context = DeferredEvalContext::new(self); let action = line .action( self.conditional_stack.last(), |name| self.macros.is_defined(name), - |t| self.expand_macros(t), + |t| self.expand_macros_deferred_eval(t, &mut deferred_eval_context), ) .wrap_err_with(|| { format!("while applying conditional on line {}", line_number) }); + for child in deferred_eval_context { + self.extend(child); + } let action = match action { Ok(x) => x, Err(err) => return Some((line_number, Err(err))), @@ -609,6 +594,7 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> { log::error!("rule-specific macros are not implemented yet"); return Ok(()); } + let mut deferred_eval_context = DeferredEvalContext::new(self); let prerequisites = self .macros .with_lookup(&|macro_name: &str| { @@ -644,7 +630,10 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> { Ok(macro_pieces.join(" ")) }) - .expand(&prerequisites)?; + .expand(&prerequisites, Some(&mut deferred_eval_context))?; + for child in deferred_eval_context { + self.extend(child); + } let prerequisites = prerequisites .split_whitespace() .map(|x| x.into()) @@ -913,9 +902,22 @@ impl<'a, 'parent, R: BufRead> MakefileReader<'a, 'parent, R> { Ok(name.to_owned()) } - fn expand_macros(&self, text: &TokenString) -> Result<String> { + fn expand_macros(&mut self, text: &TokenString) -> Result<String> { + let mut deferred_eval_context = DeferredEvalContext::new(self); + let result = self.expand_macros_deferred_eval(text, &mut deferred_eval_context); + for child in deferred_eval_context { + self.extend(child); + } + result + } + + fn expand_macros_deferred_eval( + &self, + text: &TokenString, + deferred_eval_context: &mut DeferredEvalContext<R>, + ) -> Result<String> { self.macros - .expand(text) + .expand(text, Some(deferred_eval_context)) .wrap_err_with(|| format!("while expanding \"{}\"", text)) } @@ -1006,7 +1008,7 @@ worked = perhaps endif "; let args = Args::empty(); - let makefile = MakefileReader::read( + let mut makefile = MakefileReader::read( &args, MacroSet::new(), Cursor::new(file), @@ -1052,7 +1054,7 @@ baz endef "; let args = Args::empty(); - let makefile = MakefileReader::read( + let mut makefile = MakefileReader::read( &args, MacroSet::new(), Cursor::new(file), @@ -1080,7 +1082,7 @@ endif FOO = bar "; let args = Args::empty(); - let makefile = MakefileReader::read( + let mut makefile = MakefileReader::read( &args, MacroSet::new(), Cursor::new(file), diff --git a/src/makefile/macro.rs b/src/makefile/macro.rs index 7b43b13..beae404 100644 --- a/src/makefile/macro.rs +++ b/src/makefile/macro.rs @@ -3,16 +3,17 @@ use std::collections::HashMap; use std::collections::HashSet; use std::env; use std::fmt; +use std::io::BufRead; use std::rc::Rc; -use eyre::{bail, Result, WrapErr}; -#[cfg(not(feature = "full"))] -use regex::Regex; - #[cfg(feature = "full")] use super::functions; use super::token::{Token, TokenString}; use super::ItemSource; +use crate::makefile::eval_context::DeferredEvalContext; +use eyre::{bail, Result, WrapErr}; +#[cfg(not(feature = "full"))] +use regex::Regex; #[derive(Debug, Clone)] pub struct Macro { @@ -90,8 +91,6 @@ pub struct Set<'parent, 'lookup> { pub data: HashMap<String, Macro>, lookup_internal: Option<&'lookup dyn LookupInternal>, #[cfg(feature = "full")] - pub to_eval: Rc<RefCell<Vec<String>>>, - #[cfg(feature = "full")] pub exported: ExportConfig, warnings: Rc<RefCell<HashSet<String>>>, } @@ -103,8 +102,6 @@ impl<'parent, 'lookup> Set<'parent, 'lookup> { data: HashMap::new(), lookup_internal: None, #[cfg(feature = "full")] - to_eval: Rc::new(RefCell::new(Vec::new())), - #[cfg(feature = "full")] exported: ExportConfig::only(), warnings: Default::default(), } @@ -206,14 +203,18 @@ impl<'parent, 'lookup> Set<'parent, 'lookup> { } } - pub fn expand(&self, text: &TokenString) -> Result<String> { + pub fn expand<R: BufRead>( + &self, + text: &TokenString, + mut eval_context: Option<&mut DeferredEvalContext<R>>, + ) -> Result<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 name = self - .expand(name) + .expand(name, eval_context.as_deref_mut()) .wrap_err_with(|| format!("while expanding \"{}\"", name))?; let internal_macro_names = &['@', '?', '<', '*', '^'][..]; let internal_macro_suffices = &['D', 'F'][..]; @@ -231,14 +232,14 @@ impl<'parent, 'lookup> Set<'parent, 'lookup> { Ok(String::new()) }, |x| { - self.expand(&x.text) + self.expand(&x.text, eval_context.as_deref_mut()) .wrap_err_with(|| format!("while expanding \"{}\"", &x.text)) }, )? }; let macro_value = match replacement { Some((subst1, subst2)) => { - let subst1 = self.expand(subst1)?; + let subst1 = self.expand(subst1, eval_context.as_deref_mut())?; #[cfg(feature = "full")] { let (subst1, subst2) = if subst1.contains('%') { @@ -257,7 +258,7 @@ impl<'parent, 'lookup> Set<'parent, 'lookup> { "patsubst", &args, self, - Some(Rc::clone(&self.to_eval)), + eval_context.as_deref_mut(), )? } #[cfg(not(feature = "full"))] @@ -286,9 +287,9 @@ impl<'parent, 'lookup> Set<'parent, 'lookup> { } #[cfg(feature = "full")] Token::FunctionCall { name, args } => { - let name = self.expand(name)?; + let name = self.expand(name, eval_context.as_deref_mut())?; let fn_result = - functions::expand_call(&name, args, self, Some(Rc::clone(&self.to_eval)))?; + functions::expand_call(&name, args, self, eval_context.as_deref_mut())?; log::trace!("expanded {} into \"{}\"", token, &fn_result); result.push_str(&fn_result); } @@ -332,8 +333,6 @@ impl<'parent, 'lookup> Set<'parent, 'lookup> { data: HashMap::new(), lookup_internal: Some(lookup), #[cfg(feature = "full")] - to_eval: Rc::clone(&self.to_eval), - #[cfg(feature = "full")] exported: self.exported.same_type(), warnings: Rc::clone(&self.warnings), } @@ -345,23 +344,27 @@ impl<'parent, 'lookup> Set<'parent, 'lookup> { data: HashMap::new(), lookup_internal: None, #[cfg(feature = "full")] - to_eval: Rc::clone(&self.to_eval), - #[cfg(feature = "full")] exported: self.exported.same_type(), warnings: Rc::clone(&self.warnings), } } #[cfg(feature = "full")] - pub fn resolve_exports(&self) -> Result<Vec<(&str, String)>> { + pub fn resolve_exports<R: BufRead>( + &self, + mut eval_context: Option<&mut DeferredEvalContext<R>>, + ) -> Result<Vec<(&str, String)>> { let own_exports = self .data .iter() .filter(|(name, _)| self.exported.should_export(name)) - .map(|(name, value)| self.expand(&value.text).map(|text| (name.as_ref(), text))) + .map(|(name, value)| { + self.expand(&value.text, eval_context.as_deref_mut()) + .map(|text| (name.as_ref(), text)) + }) .collect::<Result<Vec<_>>>()?; Ok(if let Some(parent) = self.parent { - let mut parent_exports = parent.resolve_exports()?; + let mut parent_exports = parent.resolve_exports(eval_context)?; parent_exports.extend(own_exports); parent_exports } else { @@ -377,8 +380,6 @@ impl fmt::Debug for Set<'_, '_> { r#struct.field("data", &self.data); r#struct.field("lookup_internal", &self.lookup_internal.map(|_| ())); #[cfg(feature = "full")] - r#struct.field("to_eval", &self.to_eval); - #[cfg(feature = "full")] r#struct.field("exported", &self.exported); r#struct.field("warnings", &self.warnings); r#struct.finish() @@ -456,6 +457,7 @@ fn builtins() -> Vec<(&'static str, TokenString)> { #[cfg(test)] mod test { use super::*; + use crate::makefile::functions::NO_EVAL; type R = Result<()>; @@ -471,7 +473,10 @@ mod test { eagerly_expanded: false, }, ); - assert_eq!(macros.expand(&"$(oof:;=?)".parse()?)?, "bruh? swag? yeet?"); + assert_eq!( + macros.expand(&"$(oof:;=?)".parse()?, NO_EVAL)?, + "bruh? swag? yeet?" + ); Ok(()) } @@ -487,7 +492,10 @@ mod test { eagerly_expanded: false, }, ); - assert_eq!(macros.expand(&"$(m:%=%-objs)".parse()?)?, "conf-objs"); + assert_eq!( + macros.expand(&"$(m:%=%-objs)".parse()?, NO_EVAL)?, + "conf-objs" + ); Ok(()) } } diff --git a/src/makefile/mod.rs b/src/makefile/mod.rs index 62cc416..c6a8c0a 100644 --- a/src/makefile/mod.rs +++ b/src/makefile/mod.rs @@ -16,10 +16,12 @@ use target::{DynamicTargetSet, Target}; use token::TokenString; use crate::args::Args; +use crate::makefile::functions::NO_EVAL; mod command_line; #[cfg(feature = "full")] mod conditional; +mod eval_context; #[cfg(feature = "full")] mod functions; mod inference_rules; @@ -410,7 +412,9 @@ impl<'a> Makefile<'a> { Ok(macro_pieces.join(" ")) }; - self.macros.with_lookup(&lookup_internal).expand(text) + self.macros + .with_lookup(&lookup_internal) + .expand(text, NO_EVAL) } } |