use super::Ast;
use super::PostfixOperator;
use super::PrefixOperator;
use std::ops::Range;
use thiserror::Error;
#[derive(Clone, Debug, Eq, Error, Hash, PartialEq)]
#[non_exhaustive]
pub enum PortabilityError {
#[error("the increment and decrement operators are not portable")]
IncrementDecrement,
}
#[derive(Clone, Debug, Eq, Error, Hash, PartialEq)]
#[error("{cause}")]
pub struct Error {
pub cause: PortabilityError,
pub location: Range<usize>,
}
pub fn check(ast: &[Ast<'_>]) -> Result<(), Error> {
let location = ast
.iter()
.filter_map(|node| match node {
Ast::Prefix {
operator: PrefixOperator::Increment | PrefixOperator::Decrement,
location,
}
| Ast::Postfix {
operator: PostfixOperator::Increment | PostfixOperator::Decrement,
location,
} => Some(location),
_ => None,
})
.min_by_key(|location| location.start);
match location {
Some(location) => Err(Error {
cause: PortabilityError::IncrementDecrement,
location: location.clone(),
}),
None => Ok(()),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::parse;
use crate::token::PeekableTokens;
fn check_expression(expression: &str) -> Result<(), Error> {
let ast = parse(PeekableTokens::from(expression)).unwrap();
check(&ast)
}
#[test]
fn portable_expression() {
assert_eq!(check_expression("foo = -(1 + 2)"), Ok(()));
}
#[test]
fn prefix_increment_and_decrement() {
for (expression, location) in [(" ++foo", 2..4), ("--bar ", 0..2)] {
assert_eq!(
check_expression(expression),
Err(Error {
cause: PortabilityError::IncrementDecrement,
location,
})
);
}
}
#[test]
fn postfix_increment_and_decrement() {
for (expression, location) in [(" foo++", 5..7), ("bar-- ", 3..5)] {
assert_eq!(
check_expression(expression),
Err(Error {
cause: PortabilityError::IncrementDecrement,
location,
})
);
}
}
#[test]
fn non_portable_operator_in_unevaluated_operand() {
assert_eq!(
check_expression("1 || foo++"),
Err(Error {
cause: PortabilityError::IncrementDecrement,
location: 8..10,
})
);
}
#[test]
fn first_non_portable_operator_in_source_order() {
assert_eq!(
check_expression("++--foo + bar++"),
Err(Error {
cause: PortabilityError::IncrementDecrement,
location: 0..2,
})
);
}
}