yash_arith/ast/
portability.rs1use super::Ast;
20use super::PostfixOperator;
21use super::PrefixOperator;
22use std::ops::Range;
23use thiserror::Error;
24
25#[derive(Clone, Debug, Eq, Error, Hash, PartialEq)]
27#[non_exhaustive]
28pub enum PortabilityError {
29 #[error("the increment and decrement operators are not portable")]
32 IncrementDecrement,
33}
34
35#[derive(Clone, Debug, Eq, Error, Hash, PartialEq)]
37#[error("{cause}")]
38pub struct Error {
39 pub cause: PortabilityError,
41 pub location: Range<usize>,
43}
44
45pub fn check(ast: &[Ast<'_>]) -> Result<(), Error> {
47 let location = ast
48 .iter()
49 .filter_map(|node| match node {
50 Ast::Prefix {
51 operator: PrefixOperator::Increment | PrefixOperator::Decrement,
52 location,
53 }
54 | Ast::Postfix {
55 operator: PostfixOperator::Increment | PostfixOperator::Decrement,
56 location,
57 } => Some(location),
58 _ => None,
59 })
60 .min_by_key(|location| location.start);
61
62 match location {
63 Some(location) => Err(Error {
64 cause: PortabilityError::IncrementDecrement,
65 location: location.clone(),
66 }),
67 None => Ok(()),
68 }
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74 use crate::ast::parse;
75 use crate::token::PeekableTokens;
76
77 fn check_expression(expression: &str) -> Result<(), Error> {
78 let ast = parse(PeekableTokens::from(expression)).unwrap();
79 check(&ast)
80 }
81
82 #[test]
83 fn portable_expression() {
84 assert_eq!(check_expression("foo = -(1 + 2)"), Ok(()));
85 }
86
87 #[test]
88 fn prefix_increment_and_decrement() {
89 for (expression, location) in [(" ++foo", 2..4), ("--bar ", 0..2)] {
90 assert_eq!(
91 check_expression(expression),
92 Err(Error {
93 cause: PortabilityError::IncrementDecrement,
94 location,
95 })
96 );
97 }
98 }
99
100 #[test]
101 fn postfix_increment_and_decrement() {
102 for (expression, location) in [(" foo++", 5..7), ("bar-- ", 3..5)] {
103 assert_eq!(
104 check_expression(expression),
105 Err(Error {
106 cause: PortabilityError::IncrementDecrement,
107 location,
108 })
109 );
110 }
111 }
112
113 #[test]
114 fn non_portable_operator_in_unevaluated_operand() {
115 assert_eq!(
116 check_expression("1 || foo++"),
117 Err(Error {
118 cause: PortabilityError::IncrementDecrement,
119 location: 8..10,
120 })
121 );
122 }
123
124 #[test]
125 fn first_non_portable_operator_in_source_order() {
126 assert_eq!(
129 check_expression("++--foo + bar++"),
130 Err(Error {
131 cause: PortabilityError::IncrementDecrement,
132 location: 0..2,
133 })
134 );
135 }
136}