diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/args.rs | 7 | ||||
| -rw-r--r-- | src/makefile/conditional.rs | 164 | ||||
| -rw-r--r-- | src/makefile/mod.rs | 54 | ||||
| -rw-r--r-- | src/makefile/token.rs | 21 | 
4 files changed, 246 insertions, 0 deletions
diff --git a/src/args.rs b/src/args.rs index f96cba4..134f785 100644 --- a/src/args.rs +++ b/src/args.rs @@ -165,6 +165,13 @@ impl Args {          Self::from_given_args_and_given_env(args, env_makeflags)      } +    #[cfg(test)] +    pub(crate) fn empty() -> Self { +        let env_makeflags = String::new(); +        let args = vec![OsString::from("makers")]; +        Self::from_given_args_and_given_env(args.into_iter(), env_makeflags) +    } +      pub(crate) fn targets(&self) -> impl Iterator<Item = &String> {          self.targets_or_macros.iter().filter(|x| !x.contains('='))      } diff --git a/src/makefile/conditional.rs b/src/makefile/conditional.rs new file mode 100644 index 0000000..f9d2d60 --- /dev/null +++ b/src/makefile/conditional.rs @@ -0,0 +1,164 @@ +use super::token::TokenString; + +pub(crate) enum ConditionalLine { +    /// spelled "ifeq" +    IfEqual(TokenString, TokenString), +    /// spelled "ifneq" +    IfNotEqual(TokenString, TokenString), +    /// spelled "ifdef" +    IfDefined(String), +    /// spelled "ifndef" +    IfNotDefined(String), +    /// spelled "else" +    Else, +    /// spelled "else condition" +    ElseIf(Box<ConditionalLine>), +    /// spelled "endif" +    EndIf, +} + +pub(crate) enum ConditionalState { +    /// we saw a conditional, the condition was true, we're executing now +    /// and if we hit an else we will start SkippingUntilEndIf +    Executing, +    /// we saw a conditional, the condition was false, we're ignoring now +    /// and if we hit an else we'll start executing +    /// (or if it's an else if we'll check the condition) +    SkippingUntilElseOrEndIf, +    /// we saw a conditional, the condition was true, we executed, and now we hit an else +    /// so we don't need to stop and evaluate new conditions, because we straight up do +    /// not care +    SkippingUntilEndIf, +} + +impl ConditionalState { +    pub(crate) const fn skipping(&self) -> bool { +        match self { +            Self::Executing => false, +            Self::SkippingUntilElseOrEndIf | Self::SkippingUntilEndIf => true, +        } +    } +} + +pub(crate) enum ConditionalStateAction { +    Push(ConditionalState), +    Replace(ConditionalState), +    Pop, +} + +impl ConditionalStateAction { +    pub(crate) fn apply_to(self, stack: &mut Vec<ConditionalState>) { +        match self { +            Self::Push(state) => stack.push(state), +            Self::Replace(state) => match stack.last_mut() { +                Some(x) => *x = state, +                None => panic!("applying Replace on an empty condition stack"), +            }, +            Self::Pop => { +                stack.pop(); +            } +        } +    } +} + +fn decode_condition_args(line_body: &str) -> Option<(TokenString, TokenString)> { +    let tokens: TokenString = line_body.parse().ok()?; +    let (mut arg1, mut arg2) = if tokens.starts_with("(") && tokens.ends_with(")") { +        let mut tokens = tokens; +        tokens.strip_prefix("("); +        tokens.strip_suffix(")"); +        tokens.split_once(',')? +    } else { +        // TODO see if i really need to implement potentially-mixed-quoted args +        return None; +    }; +    arg1.trim_end(); +    arg2.trim_start(); +    Some((arg1, arg2)) +} + +impl ConditionalLine { +    pub(crate) fn from(line: &str, expand_macro: impl Fn(&TokenString) -> String) -> Option<Self> { +        if let Some(line) = line.strip_prefix("ifeq ") { +            let (arg1, arg2) = decode_condition_args(line)?; +            Some(Self::IfEqual(arg1, arg2)) +        } else if let Some(line) = line.strip_prefix("ifneq ") { +            let (arg1, arg2) = decode_condition_args(line)?; +            Some(Self::IfNotEqual(arg1, arg2)) +        } else if let Some(line) = line.strip_prefix("ifdef ") { +            Some(Self::IfDefined(expand_macro(&line.parse().ok()?))) +        } else if let Some(line) = line.strip_prefix("ifndef ") { +            Some(Self::IfNotDefined(expand_macro(&line.parse().ok()?))) +        } else if line == "else" { +            Some(Self::Else) +        } else if let Some(line) = line.strip_prefix("else ") { +            let sub_condition = Self::from(line, expand_macro)?; +            Some(Self::ElseIf(Box::new(sub_condition))) +        } else if line == "endif" { +            Some(Self::EndIf) +        } else { +            None +        } +    } + +    pub(crate) fn action( +        &self, +        current_state: Option<&ConditionalState>, +        is_macro_defined: impl Fn(&str) -> bool, +        expand_macro: impl Fn(&TokenString) -> String, +    ) -> ConditionalStateAction { +        use ConditionalState as State; +        use ConditionalStateAction as Action; +        match self { +            Self::IfEqual(arg1, arg2) => { +                let arg1 = expand_macro(arg1); +                let arg2 = expand_macro(arg2); +                if arg1 == arg2 { +                    Action::Push(State::Executing) +                } else { +                    Action::Push(State::SkippingUntilElseOrEndIf) +                } +            } +            Self::IfNotEqual(arg1, arg2) => { +                let arg1 = expand_macro(arg1); +                let arg2 = expand_macro(arg2); +                if arg1 == arg2 { +                    Action::Push(State::SkippingUntilElseOrEndIf) +                } else { +                    Action::Push(State::Executing) +                } +            } +            Self::IfDefined(name) => { +                if is_macro_defined(name) { +                    Action::Push(State::Executing) +                } else { +                    Action::Push(State::SkippingUntilElseOrEndIf) +                } +            } +            Self::IfNotDefined(name) => { +                if is_macro_defined(name) { +                    Action::Push(State::SkippingUntilElseOrEndIf) +                } else { +                    Action::Push(State::Executing) +                } +            } +            Self::Else => Action::Replace(match current_state { +                Some(State::Executing) | Some(State::SkippingUntilEndIf) => { +                    State::SkippingUntilEndIf +                } +                Some(State::SkippingUntilElseOrEndIf) => State::Executing, +                None => panic!("got an Else but not in a conditional"), +            }), +            Self::ElseIf(inner_condition) => match current_state { +                Some(State::Executing) | Some(State::SkippingUntilEndIf) => { +                    Action::Replace(State::SkippingUntilEndIf) +                } +                Some(State::SkippingUntilElseOrEndIf) => { +                    inner_condition.action(current_state, is_macro_defined, expand_macro) +                } +                None => panic!("got an ElseIf but not in a conditional"), +            }, +            Self::EndIf => Action::Pop, +        } +    } +} diff --git a/src/makefile/mod.rs b/src/makefile/mod.rs index 2f73c70..0d4cded 100644 --- a/src/makefile/mod.rs +++ b/src/makefile/mod.rs @@ -13,11 +13,13 @@ use regex::Regex;  use crate::args::Args;  mod command_line; +mod conditional;  mod inference_rules;  mod target;  mod token;  use command_line::CommandLine; +use conditional::{ConditionalLine, ConditionalState};  use inference_rules::InferenceRule;  use target::Target;  use token::{tokenize, Token, TokenString}; @@ -151,6 +153,7 @@ impl<'a> Makefile<'a> {      pub(crate) fn and_read(&mut self, source: impl BufRead) {          let mut lines_iter = source.lines().enumerate().peekable(); +        let mut conditional_stack: Vec<ConditionalState> = vec![];          while let Some((line_number, line)) = lines_iter.next() {              // TODO handle I/O errors at all              let mut line = line.expect("failed to read line of makefile!"); @@ -170,6 +173,14 @@ impl<'a> Makefile<'a> {              }              let line = COMMENT.replace(&line, "").into_owned(); +            // skip lines if we need to +            if conditional_stack +                .last() +                .map_or(false, ConditionalState::skipping) +            { +                continue; +            } +              // handle include lines              if let Some(line) = line.strip_prefix("include ") {                  // remove extra leading space @@ -181,6 +192,14 @@ impl<'a> Makefile<'a> {                  for field in fields {                      self.and_read_file(field);                  } +            } else if let Some(line) = ConditionalLine::from(&line, |t| self.expand_macros(t, None)) +            { +                line.action( +                    conditional_stack.last(), +                    |name| self.macros.contains_key(name), +                    |t| self.expand_macros(t, None), +                ) +                .apply_to(&mut conditional_stack);              } else if line.trim().is_empty() {                  // handle blank lines                  continue; @@ -728,3 +747,38 @@ fn builtin_targets() -> Vec<Target> {          already_updated: Cell::new(false),      }]  } + +#[cfg(test)] +mod test { +    use super::*; + +    use std::io::Cursor; + +    fn empty_makefile(args: &Args) -> Makefile { +        Makefile { +            inference_rules: vec![], +            macros: HashMap::new(), +            targets: RefCell::new(HashMap::new()), +            first_non_special_target: None, +            args, +        } +    } + +    #[test] +    fn basic_conditionals() { +        let file = " +ifeq (1,1) +worked = yes +else ifeq (2,2) +worked = no +endif +        "; +        let args = Args::empty(); +        let mut makefile = empty_makefile(&args); +        makefile.and_read(Cursor::new(file)); +        assert_eq!( +            makefile.expand_macros(&"$(worked)".parse().unwrap(), None), +            "yes" +        ); +    } +} diff --git a/src/makefile/token.rs b/src/makefile/token.rs index 811f30f..86c442b 100644 --- a/src/makefile/token.rs +++ b/src/makefile/token.rs @@ -47,6 +47,13 @@ impl TokenString {          None      } +    pub(crate) fn starts_with(&self, pattern: &str) -> bool { +        match self.0.first() { +            Some(Token::Text(t)) => t.starts_with(pattern), +            _ => false, +        } +    } +      pub(crate) fn ends_with(&self, pattern: &str) -> bool {          match self.0.last() {              Some(Token::Text(t)) => t.ends_with(pattern), @@ -54,6 +61,14 @@ impl TokenString {          }      } +    pub(crate) fn strip_prefix(&mut self, suffix: &str) { +        if let Some(Token::Text(t)) = self.0.first_mut() { +            if let Some(x) = t.strip_prefix(suffix) { +                *t = x.into() +            } +        } +    } +      pub(crate) fn strip_suffix(&mut self, suffix: &str) {          if let Some(Token::Text(t)) = self.0.last_mut() {              if let Some(x) = t.strip_suffix(suffix) { @@ -71,6 +86,12 @@ impl TokenString {              *t = t.trim_start().into();          }      } + +    pub(crate) fn trim_end(&mut self) { +        if let Some(Token::Text(t)) = self.0.last_mut() { +            *t = t.trim_end().into(); +        } +    }  }  impl fmt::Display for TokenString {  |