use eyre::{bail, Result};
use super::token::TokenString;
#[derive(Debug)]
pub enum Line {
IfEqual(TokenString, TokenString),
IfNotEqual(TokenString, TokenString),
IfDefined(String),
IfNotDefined(String),
Else,
ElseIf(Box<Line>),
EndIf,
}
#[derive(Debug)]
pub enum State {
Executing,
SkippingUntilElseOrEndIf,
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<State>) {
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 {
return None;
};
arg1.trim_end();
arg2.trim_start();
Some((arg1, arg2))
}
impl Line {
pub fn from(
line: &str,
expand_macro: impl Fn(&TokenString) -> Result<String>,
) -> Result<Option<Self>> {
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<String>,
) -> Result<StateAction> {
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"),
},
})
}
}