use eyre::{bail, Result}; use super::token::TokenString; #[derive(Debug)] pub enum Line { /// 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, } #[derive(Debug)] pub enum State { /// 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 State { pub const fn skipping(&self) -> bool { match self { Self::Executing => false, Self::SkippingUntilElseOrEndIf | Self::SkippingUntilEndIf => true, } } } #[derive(Debug)] pub enum StateAction { Push(State), Replace(State), Pop, } impl StateAction { #[allow(clippy::panic)] pub 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!("internal error: 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 Line { pub fn from( line: &str, expand_macro: impl Fn(&TokenString) -> Result, ) -> Result> { let line = line.trim_start(); Ok(Some(if let Some(line) = line.strip_prefix("ifeq ") { match decode_condition_args(line) { Some((arg1, arg2)) => Self::IfEqual(arg1, arg2), None => return Ok(None), } } else if let Some(line) = line.strip_prefix("ifneq ") { match decode_condition_args(line) { Some((arg1, arg2)) => Self::IfNotEqual(arg1, arg2), None => return Ok(None), } } else if let Some(line) = line.strip_prefix("ifdef ") { Self::IfDefined(expand_macro(&line.parse()?)?) } else if let Some(line) = line.strip_prefix("ifndef ") { Self::IfNotDefined(expand_macro(&line.parse()?)?) } else if line == "else" { Self::Else } else if let Some(line) = line.strip_prefix("else ") { match Self::from(line, expand_macro)? { Some(sub_condition) => Self::ElseIf(Box::new(sub_condition)), None => return Ok(None), } } else if line == "endif" { Self::EndIf } else { return Ok(None); })) } pub fn action( &self, current_state: Option<&State>, is_macro_defined: impl Fn(&str) -> bool, expand_macro: impl Fn(&TokenString) -> Result, ) -> Result { Ok(match self { Self::IfEqual(arg1, arg2) => { let arg1 = expand_macro(arg1)?; let arg2 = expand_macro(arg2)?; if arg1 == arg2 { StateAction::Push(State::Executing) } else { StateAction::Push(State::SkippingUntilElseOrEndIf) } } Self::IfNotEqual(arg1, arg2) => { let arg1 = expand_macro(arg1)?; let arg2 = expand_macro(arg2)?; if arg1 == arg2 { StateAction::Push(State::SkippingUntilElseOrEndIf) } else { StateAction::Push(State::Executing) } } Self::IfDefined(name) => { if is_macro_defined(name) { StateAction::Push(State::Executing) } else { StateAction::Push(State::SkippingUntilElseOrEndIf) } } Self::IfNotDefined(name) => { if is_macro_defined(name) { StateAction::Push(State::SkippingUntilElseOrEndIf) } else { StateAction::Push(State::Executing) } } Self::Else => StateAction::Replace(match current_state { Some(State::Executing) | Some(State::SkippingUntilEndIf) => { State::SkippingUntilEndIf } Some(State::SkippingUntilElseOrEndIf) => State::Executing, None => bail!("got an Else but not in a conditional"), }), Self::ElseIf(inner_condition) => match current_state { Some(State::Executing) | Some(State::SkippingUntilEndIf) => { StateAction::Replace(State::SkippingUntilEndIf) } Some(State::SkippingUntilElseOrEndIf) => { match inner_condition.action(current_state, is_macro_defined, expand_macro)? { StateAction::Push(x) => StateAction::Replace(x), x => x, } } None => bail!("got an ElseIf but not in a conditional"), }, Self::EndIf => match current_state { Some(_) => StateAction::Pop, None => bail!("got an EndIf but not in a conditional"), }, }) } }