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), /// 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) { 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 { 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, } } }