diff options
author | Melody Horn <melody@boringcactus.com> | 2021-03-28 00:36:23 -0600 |
---|---|---|
committer | Melody Horn <melody@boringcactus.com> | 2021-03-28 00:36:23 -0600 |
commit | 40920ea9d255f704116064d0b831666c7416caf2 (patch) | |
tree | bd0021cec78eb94cb2cb998c638d6b4cb52e3484 /src/makefile/conditional.rs | |
parent | 2cd4d0f968c8097566edc0c6fbee10baf4b2647a (diff) | |
download | makers-40920ea9d255f704116064d0b831666c7416caf2.tar.gz makers-40920ea9d255f704116064d0b831666c7416caf2.zip |
add (& somewhat test!) GNUful conditionals
Diffstat (limited to 'src/makefile/conditional.rs')
-rw-r--r-- | src/makefile/conditional.rs | 164 |
1 files changed, 164 insertions, 0 deletions
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, + } + } +} |