1use crate::{ast, error};
4
5pub fn parse(input: &str) -> Result<ast::ArithmeticExpr, error::WordParseError> {
18 cacheable_parse(input)
19}
20
21#[cached::macros::cached(max_size = 64, key = "String", convert = r#"{ input.to_owned() }"#)]
22fn cacheable_parse(input: &str) -> Result<ast::ArithmeticExpr, error::WordParseError> {
23 tracing::debug!(target: "arithmetic", "parsing arithmetic expression: '{input}'");
24 arithmetic::full_expression(input)
25 .map_err(|e| error::WordParseError::ArithmeticExpression(e.into()))
26}
27
28peg::parser! {
29 grammar arithmetic() for str {
30 pub(crate) rule full_expression() -> ast::ArithmeticExpr =
31 ![_] { ast::ArithmeticExpr::Literal(0) } /
32 _ e:expression() _ { e }
33
34 pub(crate) rule expression() -> ast::ArithmeticExpr = precedence!{
35 x:(@) _ "," _ y:@ { ast::ArithmeticExpr::BinaryOp(ast::BinaryOperator::Comma, Box::new(x), Box::new(y)) }
36 --
37 x:lvalue() _ "*=" _ y:(@) { ast::ArithmeticExpr::BinaryAssignment(ast::BinaryOperator::Multiply, x, Box::new(y)) }
38 x:lvalue() _ "/=" _ y:(@) { ast::ArithmeticExpr::BinaryAssignment(ast::BinaryOperator::Divide, x, Box::new(y)) }
39 x:lvalue() _ "%=" _ y:(@) { ast::ArithmeticExpr::BinaryAssignment(ast::BinaryOperator::Modulo, x, Box::new(y)) }
40 x:lvalue() _ "+=" _ y:(@) { ast::ArithmeticExpr::BinaryAssignment(ast::BinaryOperator::Add, x, Box::new(y)) }
41 x:lvalue() _ "-=" _ y:(@) { ast::ArithmeticExpr::BinaryAssignment(ast::BinaryOperator::Subtract, x, Box::new(y)) }
42 x:lvalue() _ "<<=" _ y:(@) { ast::ArithmeticExpr::BinaryAssignment(ast::BinaryOperator::ShiftLeft, x, Box::new(y)) }
43 x:lvalue() _ ">>=" _ y:(@) { ast::ArithmeticExpr::BinaryAssignment(ast::BinaryOperator::ShiftRight, x, Box::new(y)) }
44 x:lvalue() _ "&=" _ y:(@) { ast::ArithmeticExpr::BinaryAssignment(ast::BinaryOperator::BitwiseAnd, x, Box::new(y)) }
45 x:lvalue() _ "|=" _ y:(@) { ast::ArithmeticExpr::BinaryAssignment(ast::BinaryOperator::BitwiseOr, x, Box::new(y)) }
46 x:lvalue() _ "^=" _ y:(@) { ast::ArithmeticExpr::BinaryAssignment(ast::BinaryOperator::BitwiseXor, x, Box::new(y)) }
47 x:lvalue() _ "=" _ y:(@) { ast::ArithmeticExpr::Assignment(x, Box::new(y)) }
48 --
49 x:@ _ "?" _ y:expression() _ ":" _ z:(@) { ast::ArithmeticExpr::Conditional(Box::new(x), Box::new(y), Box::new(z)) }
50 --
51 x:(@) _ "||" _ y:@ { ast::ArithmeticExpr::BinaryOp(ast::BinaryOperator::LogicalOr, Box::new(x), Box::new(y)) }
52 --
53 x:(@) _ "&&" _ y:@ { ast::ArithmeticExpr::BinaryOp(ast::BinaryOperator::LogicalAnd, Box::new(x), Box::new(y)) }
54 --
55 x:(@) _ "|" _ y:@ { ast::ArithmeticExpr::BinaryOp(ast::BinaryOperator::BitwiseOr, Box::new(x), Box::new(y)) }
56 --
57 x:(@) _ "^" _ y:@ { ast::ArithmeticExpr::BinaryOp(ast::BinaryOperator::BitwiseXor, Box::new(x), Box::new(y)) }
58 --
59 x:(@) _ "&" _ y:@ { ast::ArithmeticExpr::BinaryOp(ast::BinaryOperator::BitwiseAnd, Box::new(x), Box::new(y)) }
60 --
61 x:(@) _ "==" _ y:@ { ast::ArithmeticExpr::BinaryOp(ast::BinaryOperator::Equals, Box::new(x), Box::new(y)) }
62 x:(@) _ "!=" _ y:@ { ast::ArithmeticExpr::BinaryOp(ast::BinaryOperator::NotEquals, Box::new(x), Box::new(y)) }
63 --
64 x:(@) _ "<" _ y:@ { ast::ArithmeticExpr::BinaryOp(ast::BinaryOperator::LessThan, Box::new(x), Box::new(y)) }
65 x:(@) _ ">" _ y:@ { ast::ArithmeticExpr::BinaryOp(ast::BinaryOperator::GreaterThan, Box::new(x), Box::new(y)) }
66 x:(@) _ "<=" _ y:@ { ast::ArithmeticExpr::BinaryOp(ast::BinaryOperator::LessThanOrEqualTo, Box::new(x), Box::new(y)) }
67 x:(@) _ ">=" _ y:@ { ast::ArithmeticExpr::BinaryOp(ast::BinaryOperator::GreaterThanOrEqualTo, Box::new(x), Box::new(y)) }
68 --
69 x:(@) _ "<<" _ y:@ { ast::ArithmeticExpr::BinaryOp(ast::BinaryOperator::ShiftLeft, Box::new(x), Box::new(y)) }
70 x:(@) _ ">>" _ y:@ { ast::ArithmeticExpr::BinaryOp(ast::BinaryOperator::ShiftRight, Box::new(x), Box::new(y)) }
71 --
72 x:(@) _ "+" _ y:@ { ast::ArithmeticExpr::BinaryOp(ast::BinaryOperator::Add, Box::new(x), Box::new(y)) }
73 x:(@) _ "-" _ y:@ { ast::ArithmeticExpr::BinaryOp(ast::BinaryOperator::Subtract, Box::new(x), Box::new(y)) }
74 --
75 x:(@) _ "*" _ y:@ { ast::ArithmeticExpr::BinaryOp(ast::BinaryOperator::Multiply, Box::new(x), Box::new(y)) }
76 x:(@) _ "%" _ y:@ { ast::ArithmeticExpr::BinaryOp(ast::BinaryOperator::Modulo, Box::new(x), Box::new(y)) }
77 x:(@) _ "/" _ y:@ { ast::ArithmeticExpr::BinaryOp(ast::BinaryOperator::Divide, Box::new(x), Box::new(y)) }
78 --
79 x:@ _ "**" _ y:(@) { ast::ArithmeticExpr::BinaryOp(ast::BinaryOperator::Power, Box::new(x), Box::new(y)) }
80 --
81 "!" _ x:(@) { ast::ArithmeticExpr::UnaryOp(ast::UnaryOperator::LogicalNot, Box::new(x)) }
82 "~" _ x:(@) { ast::ArithmeticExpr::UnaryOp(ast::UnaryOperator::BitwiseNot, Box::new(x)) }
83 --
84 "+" !("+" _ lvalue()) _ x:(@) { ast::ArithmeticExpr::UnaryOp(ast::UnaryOperator::UnaryPlus, Box::new(x)) }
88 "-" !("-" _ lvalue()) _ x:(@) { ast::ArithmeticExpr::UnaryOp(ast::UnaryOperator::UnaryMinus, Box::new(x)) }
89 --
90 "++" _ x:lvalue() { ast::ArithmeticExpr::UnaryAssignment(ast::UnaryAssignmentOperator::PrefixIncrement, x) }
91 "--" _ x:lvalue() { ast::ArithmeticExpr::UnaryAssignment(ast::UnaryAssignmentOperator::PrefixDecrement, x) }
92 --
93 x:lvalue() _ "++" { ast::ArithmeticExpr::UnaryAssignment(ast::UnaryAssignmentOperator::PostfixIncrement, x) }
94 x:lvalue() _ "--" { ast::ArithmeticExpr::UnaryAssignment(ast::UnaryAssignmentOperator::PostfixDecrement, x) }
95 --
96 n:literal_number() { ast::ArithmeticExpr::Literal(n) }
97 l:lvalue() { ast::ArithmeticExpr::Reference(l) }
98 "(" _ expr:expression() _ ")" { expr }
99 }
100
101 rule lvalue() -> ast::ArithmeticTarget =
102 name:variable_name() "[" index:expression() "]" {
103 ast::ArithmeticTarget::ArrayElement(name.to_owned(), Box::new(index))
104 } /
105 name:variable_name() {
106 ast::ArithmeticTarget::Variable(name.to_owned())
107 }
108
109 rule variable_name() -> &'input str =
110 $(['a'..='z' | 'A'..='Z' | '_'](['a'..='z' | 'A'..='Z' | '_' | '0'..='9']*))
111
112 rule _() -> () = quiet!{[' ' | '\t' | '\n' | '\r']*} {}
113
114 rule literal_number() -> i64 =
115 radix:decimal_literal() "#" s:$(['0'..='9' | 'a'..='z' | 'A'..='Z' | '@' | '_']+) {?
117 parse_shell_literal_number(s, radix.cast_unsigned())
118 } /
119 "0" ['x' | 'X'] s:$(['0'..='9' | 'a'..='f' | 'A'..='F']*) {?
121 i64::from_str_radix(s, 16).or(Err("i64"))
122 } /
123 s:$("0" ['0'..='8']*) {?
125 i64::from_str_radix(s, 8).or(Err("i64"))
126 } /
127 decimal_literal()
129
130 rule decimal_literal() -> i64 =
131 s:$(['1'..='9'] ['0'..='9']*) {?
132 s.parse::<u64>().map(|v| v.cast_signed()).or(Err("i64"))
136 }
137 }
138}
139
140fn parse_shell_literal_number(s: &str, radix: u64) -> Result<i64, &'static str> {
141 if !(2..=64).contains(&radix) {
142 return Err("invalid base");
143 }
144
145 let mut result: i64 = 0;
149
150 for ch in s.chars() {
151 let digit_val = if radix <= 36 {
152 match ch {
153 '0'..='9' => (ch as u64) - ('0' as u64),
154 'a'..='z' => (ch as u64) - ('a' as u64) + 10,
155 'A'..='Z' => (ch as u64) - ('A' as u64) + 10,
156 _ => return Err("invalid digit"),
157 }
158 } else {
159 match ch {
160 '0'..='9' => (ch as u64) - ('0' as u64),
161 'a'..='z' => (ch as u64) - ('a' as u64) + 10,
162 'A'..='Z' => (ch as u64) - ('A' as u64) + 36,
163 '@' => 62,
164 '_' => 63,
165 _ => return Err("invalid digit"),
166 }
167 };
168
169 if digit_val >= radix {
170 return Err("value too great for base");
171 }
172
173 result = result
174 .wrapping_mul(radix.cast_signed())
175 .wrapping_add(digit_val.cast_signed());
176 }
177
178 Ok(result)
179}