aboutsummaryrefslogtreecommitdiff
path: root/src/makefile/conditional.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/makefile/conditional.rs')
-rw-r--r--src/makefile/conditional.rs164
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,
+ }
+ }
+}