brush_parser/
arithmetic.rs

1//! Parser for shell arithmetic expressions.
2
3use crate::ast;
4use crate::error;
5
6/// Parses a shell arithmetic expression.
7///
8/// # Arguments
9///
10/// * `input` - The arithmetic expression to parse, in string form.
11pub fn parse(input: &str) -> Result<ast::ArithmeticExpr, error::WordParseError> {
12    cacheable_parse(input.to_owned())
13}
14
15#[cached::proc_macro::cached(size = 64, result = true)]
16fn cacheable_parse(input: String) -> Result<ast::ArithmeticExpr, error::WordParseError> {
17    tracing::debug!(target: "arithmetic", "parsing arithmetic expression: '{input}'");
18    arithmetic::full_expression(input.as_str())
19        .map_err(|e| error::WordParseError::ArithmeticExpression(e.into()))
20}
21
22peg::parser! {
23    grammar arithmetic() for str {
24        pub(crate) rule full_expression() -> ast::ArithmeticExpr =
25            ![_] { ast::ArithmeticExpr::Literal(0) } /
26            _ e:expression() _ { e }
27
28        pub(crate) rule expression() -> ast::ArithmeticExpr = precedence!{
29            x:(@) _ "," _ y:@ { ast::ArithmeticExpr::BinaryOp(ast::BinaryOperator::Comma, Box::new(x), Box::new(y)) }
30            --
31            x:lvalue() _ "*=" _ y:(@) { ast::ArithmeticExpr::BinaryAssignment(ast::BinaryOperator::Multiply, x, Box::new(y)) }
32            x:lvalue() _ "/=" _ y:(@) { ast::ArithmeticExpr::BinaryAssignment(ast::BinaryOperator::Divide, x, Box::new(y)) }
33            x:lvalue() _ "%=" _ y:(@) { ast::ArithmeticExpr::BinaryAssignment(ast::BinaryOperator::Modulo, x, Box::new(y)) }
34            x:lvalue() _ "+=" _ y:(@) { ast::ArithmeticExpr::BinaryAssignment(ast::BinaryOperator::Add, x, Box::new(y)) }
35            x:lvalue() _ "-=" _ y:(@) { ast::ArithmeticExpr::BinaryAssignment(ast::BinaryOperator::Subtract, x, Box::new(y)) }
36            x:lvalue() _ "<<=" _ y:(@) { ast::ArithmeticExpr::BinaryAssignment(ast::BinaryOperator::ShiftLeft, x, Box::new(y)) }
37            x:lvalue() _ ">>=" _ y:(@) { ast::ArithmeticExpr::BinaryAssignment(ast::BinaryOperator::ShiftRight, x, Box::new(y)) }
38            x:lvalue() _ "&=" _ y:(@) { ast::ArithmeticExpr::BinaryAssignment(ast::BinaryOperator::BitwiseAnd, x, Box::new(y)) }
39            --
40            x:lvalue() _ "=" _ y:(@) { ast::ArithmeticExpr::Assignment(x, Box::new(y)) }
41            --
42            x:@ _ "?" _ y:expression() _ ":" _ z:(@) { ast::ArithmeticExpr::Conditional(Box::new(x), Box::new(y), Box::new(z)) }
43            --
44            x:(@) _ "||" _ y:@ { ast::ArithmeticExpr::BinaryOp(ast::BinaryOperator::LogicalOr, Box::new(x), Box::new(y)) }
45            --
46            x:(@) _ "&&" _ y:@ { ast::ArithmeticExpr::BinaryOp(ast::BinaryOperator::LogicalAnd, Box::new(x), Box::new(y)) }
47            --
48            x:(@) _ "|" _ y:@ { ast::ArithmeticExpr::BinaryOp(ast::BinaryOperator::BitwiseOr, Box::new(x), Box::new(y)) }
49            --
50            x:(@) _ "^" _ y:@ { ast::ArithmeticExpr::BinaryOp(ast::BinaryOperator::BitwiseXor, Box::new(x), Box::new(y)) }
51            --
52            x:(@) _ "&" _ y:@ { ast::ArithmeticExpr::BinaryOp(ast::BinaryOperator::BitwiseAnd, Box::new(x), Box::new(y)) }
53            --
54            x:(@) _ "==" _ y:@ { ast::ArithmeticExpr::BinaryOp(ast::BinaryOperator::Equals, Box::new(x), Box::new(y)) }
55            x:(@) _ "!=" _ y:@ { ast::ArithmeticExpr::BinaryOp(ast::BinaryOperator::NotEquals, Box::new(x), Box::new(y)) }
56            --
57            x:(@) _ "<" _ y:@ { ast::ArithmeticExpr::BinaryOp(ast::BinaryOperator::LessThan, Box::new(x), Box::new(y)) }
58            x:(@) _ ">" _ y:@ { ast::ArithmeticExpr::BinaryOp(ast::BinaryOperator::GreaterThan, Box::new(x), Box::new(y)) }
59            x:(@) _ "<=" _ y:@ { ast::ArithmeticExpr::BinaryOp(ast::BinaryOperator::LessThanOrEqualTo, Box::new(x), Box::new(y)) }
60            x:(@) _ ">=" _ y:@ { ast::ArithmeticExpr::BinaryOp(ast::BinaryOperator::GreaterThanOrEqualTo, Box::new(x), Box::new(y)) }
61            --
62            x:(@) _ "<<" _ y:@ { ast::ArithmeticExpr::BinaryOp(ast::BinaryOperator::ShiftLeft, Box::new(x), Box::new(y)) }
63            x:(@) _ ">>" _ y:@ { ast::ArithmeticExpr::BinaryOp(ast::BinaryOperator::ShiftRight, Box::new(x), Box::new(y)) }
64            --
65            x:(@) _ "+" _ y:@ { ast::ArithmeticExpr::BinaryOp(ast::BinaryOperator::Add, Box::new(x), Box::new(y)) }
66            x:(@) _ "-" _ y:@ { ast::ArithmeticExpr::BinaryOp(ast::BinaryOperator::Subtract, Box::new(x), Box::new(y)) }
67            --
68            x:(@) _ "*" _ y:@ { ast::ArithmeticExpr::BinaryOp(ast::BinaryOperator::Multiply, Box::new(x), Box::new(y)) }
69            x:(@) _ "%" _ y:@ { ast::ArithmeticExpr::BinaryOp(ast::BinaryOperator::Modulo, Box::new(x), Box::new(y)) }
70            x:(@) _ "/" _ y:@ { ast::ArithmeticExpr::BinaryOp(ast::BinaryOperator::Divide, Box::new(x), Box::new(y)) }
71            --
72            x:@ _ "**" _ y:(@) { ast::ArithmeticExpr::BinaryOp(ast::BinaryOperator::Power, Box::new(x), Box::new(y)) }
73            --
74            "!" _ x:(@) { ast::ArithmeticExpr::UnaryOp(ast::UnaryOperator::LogicalNot, Box::new(x)) }
75            "~" _ x:(@) { ast::ArithmeticExpr::UnaryOp(ast::UnaryOperator::BitwiseNot, Box::new(x)) }
76            --
77            "++" x:lvalue() { ast::ArithmeticExpr::UnaryAssignment(ast::UnaryAssignmentOperator::PrefixIncrement, x) }
78            "--" x:lvalue() { ast::ArithmeticExpr::UnaryAssignment(ast::UnaryAssignmentOperator::PrefixDecrement, x) }
79            --
80            x:lvalue() _ "++" { ast::ArithmeticExpr::UnaryAssignment(ast::UnaryAssignmentOperator::PostfixIncrement, x) }
81            x:lvalue() _ "--" { ast::ArithmeticExpr::UnaryAssignment(ast::UnaryAssignmentOperator::PostfixDecrement, x) }
82            --
83            "+" _ x:(@) { ast::ArithmeticExpr::UnaryOp(ast::UnaryOperator::UnaryPlus, Box::new(x)) }
84            "-" _ x:(@) { ast::ArithmeticExpr::UnaryOp(ast::UnaryOperator::UnaryMinus, Box::new(x)) }
85            --
86            n:literal_number() { ast::ArithmeticExpr::Literal(n) }
87            l:lvalue() { ast::ArithmeticExpr::Reference(l) }
88            "(" _ expr:expression() _ ")" { expr }
89        }
90
91        rule lvalue() -> ast::ArithmeticTarget =
92            name:variable_name() "[" index:expression() "]" {
93                ast::ArithmeticTarget::ArrayElement(name.to_owned(), Box::new(index))
94            } /
95            name:variable_name() {
96                ast::ArithmeticTarget::Variable(name.to_owned())
97            }
98
99        rule variable_name() -> &'input str =
100            $(['a'..='z' | 'A'..='Z' | '_'](['a'..='z' | 'A'..='Z' | '_' | '0'..='9']*))
101
102        rule _() -> () = quiet!{[' ' | '\t' | '\n' | '\r']*} {}
103
104        rule literal_number() -> i64 =
105            // Literal with explicit radix (format: <base>#<literal>)
106            radix:decimal_literal() "#" s:$(['0'..='9' | 'a'..='z' | 'A'..='Z']+) {?
107                // TODO: Support bases larger than 36. from_str_radix can't handle that.
108                if !(2..=36).contains(&radix) {
109                    return Err("invalid base");
110                }
111
112                // Okay to ignore these warnings; we've already checked that the radix is valid.
113                #[expect(clippy::cast_possible_truncation)]
114                #[expect(clippy::cast_sign_loss)]
115                i64::from_str_radix(s, radix as u32).or(Err("i64"))
116            } /
117            // Hex literal
118            "0" ['x' | 'X'] s:$(['0'..='9' | 'a'..='f' | 'A'..='F']*) {?
119                i64::from_str_radix(s, 16).or(Err("i64"))
120            } /
121            // Octal literal
122            s:$("0" ['0'..='8']*) {?
123                i64::from_str_radix(s, 8).or(Err("i64"))
124            } /
125            // Decimal literal
126            decimal_literal()
127
128        rule decimal_literal() -> i64 =
129            s:$(['1'..='9'] ['0'..='9']*) {?
130                s.parse().or(Err("i64"))
131            }
132    }
133}