use crate::ast::{ArithmAssignOp, ArithmBinOp, ArithmExpr, ArithmUnOp, Position, Range, Spanned};
#[cfg(test)]
fn parse_arithm_expr(input: &str) -> ArithmExpr {
normalize_test_arithm_expr(
&parse_arithm_expr_strict(input).unwrap_or(ArithmExpr::literal(0, Range::unknown())),
)
}
#[cfg(test)]
fn normalize_test_arithm_expr(expr: &ArithmExpr) -> ArithmExpr {
match expr {
ArithmExpr::Literal(literal) => ArithmExpr::literal(literal.value(), Range::default()),
ArithmExpr::Variable(variable) => ArithmExpr::variable(variable.name(), Range::default()),
ArithmExpr::Raw(raw) => ArithmExpr::raw(raw.expr(), Range::default()),
ArithmExpr::BinOp(binary) => ArithmExpr::bin_op(
binary.op(),
normalize_test_arithm_expr(binary.left()),
normalize_test_arithm_expr(binary.right()),
Range::default(),
),
ArithmExpr::UnOp(unary) => ArithmExpr::un_op(
unary.op(),
normalize_test_arithm_expr(unary.operand()),
Range::default(),
),
ArithmExpr::Cond(cond) => ArithmExpr::cond(
normalize_test_arithm_expr(cond.cond()),
normalize_test_arithm_expr(cond.then_branch()),
normalize_test_arithm_expr(cond.else_branch()),
Range::default(),
),
ArithmExpr::Assign(assign) => ArithmExpr::assign(
assign.name(),
assign.op(),
normalize_test_arithm_expr(assign.value()),
Range::default(),
),
}
}
pub fn parse_arithm_expr_strict(input: &str) -> Result<ArithmExpr, ArithmParseError> {
let mut p = ArithmParser::new(input);
p.skip_whitespace();
let expr = p.assignment()?;
p.skip_whitespace();
if p.pos < p.input.len() {
return Err(ArithmParseError::new(
p.pos,
"trailing characters in arithmetic expression",
));
}
Ok(expr)
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct ArithmParseError {
pub position: usize,
pub message: String,
}
impl ArithmParseError {
fn new(position: usize, message: &str) -> Self {
Self {
position,
message: message.to_string(),
}
}
}
struct ArithmParser<'a> {
input: &'a [u8],
pos: usize,
}
impl<'a> ArithmParser<'a> {
fn new(input: &'a str) -> Self {
Self {
input: input.as_bytes(),
pos: 0,
}
}
fn peek(&self) -> Option<u8> {
self.input.get(self.pos).copied()
}
fn peek2(&self) -> Option<u8> {
self.input.get(self.pos + 1).copied()
}
fn skip_whitespace(&mut self) {
while let Some(b) = self.peek() {
if b == b' ' || b == b'\t' || b == b'\n' || b == b'\r' {
self.pos += 1;
} else {
break;
}
}
}
fn parse_str(&mut self, s: &str) -> bool {
let bytes = s.as_bytes();
if self.pos + bytes.len() <= self.input.len()
&& &self.input[self.pos..self.pos + bytes.len()] == bytes
{
self.pos += bytes.len();
true
} else {
false
}
}
fn parse_char(&mut self, ch: u8) -> bool {
if self.peek() == Some(ch) {
self.pos += 1;
true
} else {
false
}
}
fn position(&self, offset: usize) -> Position {
let mut line = 1u32;
let mut column = 1u32;
for &b in &self.input[..offset.min(self.input.len())] {
if b == b'\n' {
line += 1;
column = 1;
} else {
column += 1;
}
}
Position {
offset,
line,
column,
}
}
fn range(&self, start: usize, end: usize) -> Range {
Range {
begin: self.position(start),
end: self.position(end),
}
}
fn parse_left_assoc(
&mut self,
parse_operand: fn(&mut Self) -> Result<ArithmExpr, ArithmParseError>,
parse_op: fn(&mut Self) -> Option<ArithmBinOp>,
) -> Result<ArithmExpr, ArithmParseError> {
let mut left = parse_operand(self)?;
loop {
self.skip_whitespace();
let Some(op) = parse_op(self) else { break };
let right = parse_operand(self)?;
let range = left.span().cover(right.span());
left = ArithmExpr::bin_op(op, left, right, range);
}
Ok(left)
}
fn parse_factor_op(&mut self) -> Option<ArithmBinOp> {
let b = self.peek()?;
let op = match b {
b'*' if self.peek2() != Some(b'=') => ArithmBinOp::Mul,
b'/' if self.peek2() != Some(b'=') => ArithmBinOp::Div,
b'%' if self.peek2() != Some(b'=') => ArithmBinOp::Mod,
_ => return None,
};
self.pos += 1;
Some(op)
}
fn parse_addend_op(&mut self) -> Option<ArithmBinOp> {
let b = self.peek()?;
let op = match b {
b'+' if self.peek2() != Some(b'=') && self.peek2() != Some(b'+') => ArithmBinOp::Add,
b'-' if self.peek2() != Some(b'=') && self.peek2() != Some(b'-') => ArithmBinOp::Sub,
_ => return None,
};
self.pos += 1;
Some(op)
}
fn parse_shift_op(&mut self) -> Option<ArithmBinOp> {
if self.pos + 1 >= self.input.len() {
return None;
}
if self.input[self.pos] == b'<'
&& self.input[self.pos + 1] == b'<'
&& self.input.get(self.pos + 2) != Some(&b'=')
{
self.pos += 2;
return Some(ArithmBinOp::Shl);
}
if self.input[self.pos] == b'>'
&& self.input[self.pos + 1] == b'>'
&& self.input.get(self.pos + 2) != Some(&b'=')
{
self.pos += 2;
return Some(ArithmBinOp::Shr);
}
None
}
fn parse_comparison_op(&mut self) -> Option<ArithmBinOp> {
if self.parse_str("<=") {
Some(ArithmBinOp::LessEq)
} else if self.parse_str(">=") {
Some(ArithmBinOp::GreaterEq)
} else if self.parse_char(b'<') {
Some(ArithmBinOp::LessThan)
} else if self.parse_char(b'>') {
Some(ArithmBinOp::GreaterThan)
} else {
None
}
}
fn parse_equality_op(&mut self) -> Option<ArithmBinOp> {
if self.parse_str("==") {
Some(ArithmBinOp::Equal)
} else if self.parse_str("!=") {
Some(ArithmBinOp::NotEqual)
} else {
None
}
}
fn parse_bitwise_and_op(&mut self) -> Option<ArithmBinOp> {
if self.peek() == Some(b'&') && self.peek2() != Some(b'&') && self.peek2() != Some(b'=') {
self.pos += 1;
Some(ArithmBinOp::BitAnd)
} else {
None
}
}
fn parse_bitwise_xor_op(&mut self) -> Option<ArithmBinOp> {
if self.peek() == Some(b'^') && self.peek2() != Some(b'=') {
self.pos += 1;
Some(ArithmBinOp::BitXor)
} else {
None
}
}
fn parse_bitwise_or_op(&mut self) -> Option<ArithmBinOp> {
if self.peek() == Some(b'|') && self.peek2() != Some(b'|') && self.peek2() != Some(b'=') {
self.pos += 1;
Some(ArithmBinOp::BitOr)
} else {
None
}
}
fn parse_logical_and_op(&mut self) -> Option<ArithmBinOp> {
if self.parse_str("&&") {
Some(ArithmBinOp::LogAnd)
} else {
None
}
}
fn parse_logical_or_op(&mut self) -> Option<ArithmBinOp> {
if self.parse_str("||") {
Some(ArithmBinOp::LogOr)
} else {
None
}
}
fn literal(&mut self) -> Result<Option<ArithmExpr>, ArithmParseError> {
self.skip_whitespace();
let start = self.pos;
if self.peek() == Some(b'0') && (self.peek2() == Some(b'x') || self.peek2() == Some(b'X')) {
self.pos += 2;
let digits_start = self.pos;
while let Some(b) = self.peek() {
if b.is_ascii_hexdigit() {
self.pos += 1;
} else {
break;
}
}
if self.pos == digits_start {
return Err(ArithmParseError::new(start, "invalid hexadecimal literal"));
}
let s = std::str::from_utf8(&self.input[start..self.pos]).unwrap_or("");
let val = i64::from_str_radix(&s[2..], 16)
.map_err(|_| ArithmParseError::new(start, "invalid hexadecimal literal"))?;
return Ok(Some(ArithmExpr::literal(val, self.range(start, self.pos))));
}
while let Some(b) = self.peek() {
if b.is_ascii_digit() {
self.pos += 1;
} else {
break;
}
}
if self.pos > start {
let s = std::str::from_utf8(&self.input[start..self.pos]).unwrap_or("");
let val = if s.len() > 1 && s.starts_with('0') {
i64::from_str_radix(s, 8)
.map_err(|_| ArithmParseError::new(start, "invalid octal literal"))?
} else {
s.parse()
.map_err(|_| ArithmParseError::new(start, "invalid integer literal"))?
};
Ok(Some(ArithmExpr::literal(val, self.range(start, self.pos))))
} else {
Ok(None)
}
}
fn variable(&mut self) -> Option<ArithmExpr> {
self.skip_whitespace();
let mut name_start = self.pos;
let token_start = self.pos;
let mut had_dollar = false;
if self.peek() == Some(b'$') {
self.pos += 1;
name_start = self.pos;
had_dollar = true;
}
if let Some(b) = self.peek()
&& (b == b'_' || b.is_ascii_alphabetic())
{
self.pos += 1;
while let Some(b) = self.peek() {
if b == b'_' || b.is_ascii_alphanumeric() {
self.pos += 1;
} else {
break;
}
}
let name = std::str::from_utf8(&self.input[name_start..self.pos]).unwrap_or("");
let full = if had_dollar {
format!("${name}")
} else {
name.to_string()
};
return Some(ArithmExpr::variable(
full,
self.range(token_start, self.pos),
));
}
None
}
fn paren(&mut self) -> Result<ArithmExpr, ArithmParseError> {
self.skip_whitespace();
if self.parse_char(b'(') {
self.skip_whitespace();
let expr = self.assignment()?;
self.skip_whitespace();
if !self.parse_char(b')') {
return Err(ArithmParseError::new(self.pos, "expected ')'"));
}
Ok(expr)
} else {
Err(ArithmParseError::new(self.pos, "expected '('"))
}
}
fn term(&mut self) -> Result<ArithmExpr, ArithmParseError> {
self.skip_whitespace();
if let Some(b) = self.peek() {
match b {
b'+' if self.peek2() != Some(b'+') && self.peek2() != Some(b'=') => {
let start = self.pos;
self.pos += 1;
let operand = self.term()?;
let range = self.range(start, operand.span().end.offset);
return Ok(ArithmExpr::un_op(ArithmUnOp::Plus, operand, range));
}
b'-' if self.peek2() != Some(b'-') && self.peek2() != Some(b'=') => {
let start = self.pos;
self.pos += 1;
let operand = self.term()?;
let range = self.range(start, operand.span().end.offset);
return Ok(ArithmExpr::un_op(ArithmUnOp::Minus, operand, range));
}
b'~' => {
let start = self.pos;
self.pos += 1;
let operand = self.term()?;
let range = self.range(start, operand.span().end.offset);
return Ok(ArithmExpr::un_op(ArithmUnOp::BitNot, operand, range));
}
b'!' if self.peek2() != Some(b'=') => {
let start = self.pos;
self.pos += 1;
let operand = self.term()?;
let range = self.range(start, operand.span().end.offset);
return Ok(ArithmExpr::un_op(ArithmUnOp::LogNot, operand, range));
}
_ => {}
}
}
if self.peek() == Some(b'(') {
return self.paren();
}
if let Some(expr) = self.literal()? {
return Ok(expr);
}
if let Some(expr) = self.variable() {
return Ok(expr);
}
Err(ArithmParseError::new(self.pos, "unexpected token"))
}
fn factor(&mut self) -> Result<ArithmExpr, ArithmParseError> {
self.parse_left_assoc(Self::term, Self::parse_factor_op)
}
fn addend(&mut self) -> Result<ArithmExpr, ArithmParseError> {
self.parse_left_assoc(Self::factor, Self::parse_addend_op)
}
fn shift(&mut self) -> Result<ArithmExpr, ArithmParseError> {
self.parse_left_assoc(Self::addend, Self::parse_shift_op)
}
fn comparison(&mut self) -> Result<ArithmExpr, ArithmParseError> {
self.parse_left_assoc(Self::shift, Self::parse_comparison_op)
}
fn equality(&mut self) -> Result<ArithmExpr, ArithmParseError> {
self.parse_left_assoc(Self::comparison, Self::parse_equality_op)
}
fn bitwise_and(&mut self) -> Result<ArithmExpr, ArithmParseError> {
self.parse_left_assoc(Self::equality, Self::parse_bitwise_and_op)
}
fn bitwise_xor(&mut self) -> Result<ArithmExpr, ArithmParseError> {
self.parse_left_assoc(Self::bitwise_and, Self::parse_bitwise_xor_op)
}
fn bitwise_or(&mut self) -> Result<ArithmExpr, ArithmParseError> {
self.parse_left_assoc(Self::bitwise_xor, Self::parse_bitwise_or_op)
}
fn logical_and(&mut self) -> Result<ArithmExpr, ArithmParseError> {
self.parse_left_assoc(Self::bitwise_or, Self::parse_logical_and_op)
}
fn logical_or(&mut self) -> Result<ArithmExpr, ArithmParseError> {
self.parse_left_assoc(Self::logical_and, Self::parse_logical_or_op)
}
fn ternary(&mut self) -> Result<ArithmExpr, ArithmParseError> {
let cond = self.logical_or()?;
self.skip_whitespace();
if self.parse_char(b'?') {
self.skip_whitespace();
let then_branch = self.assignment()?;
self.skip_whitespace();
if !self.parse_char(b':') {
return Err(ArithmParseError::new(
self.pos,
"expected ':' in ternary expression",
));
}
self.skip_whitespace();
let else_branch = self.assignment()?;
let range = cond
.span()
.cover(then_branch.span())
.cover(else_branch.span());
Ok(ArithmExpr::cond(cond, then_branch, else_branch, range))
} else {
Ok(cond)
}
}
fn peek_assign_op(&self) -> Option<(ArithmAssignOp, usize)> {
let b = *self.input.get(self.pos)?;
let b2 = self.input.get(self.pos + 1).copied();
let b3 = self.input.get(self.pos + 2).copied();
if b == b'<' && b2 == Some(b'<') && b3 == Some(b'=') {
return Some((ArithmAssignOp::ShlEq, 3));
}
if b == b'>' && b2 == Some(b'>') && b3 == Some(b'=') {
return Some((ArithmAssignOp::ShrEq, 3));
}
if b2 == Some(b'=') {
let op = match b {
b'*' => ArithmAssignOp::MulEq,
b'/' => ArithmAssignOp::DivEq,
b'%' => ArithmAssignOp::ModEq,
b'+' => ArithmAssignOp::AddEq,
b'-' => ArithmAssignOp::SubEq,
b'&' => ArithmAssignOp::AndEq,
b'^' => ArithmAssignOp::XorEq,
b'|' => ArithmAssignOp::OrEq,
_ => return None,
};
return Some((op, 2));
}
if b == b'=' && b2 != Some(b'=') {
return Some((ArithmAssignOp::Equal, 1));
}
None
}
fn assignment(&mut self) -> Result<ArithmExpr, ArithmParseError> {
self.skip_whitespace();
let saved = self.pos;
if let Some(ArithmExpr::Variable(name)) = self.variable() {
if name.name.starts_with('$') {
self.pos = saved;
return self.ternary();
}
let name_span = name.span();
self.skip_whitespace();
if let Some((op, len)) = self.peek_assign_op() {
self.pos += len;
self.skip_whitespace();
let value = self.assignment()?;
let range = name_span.cover(value.span());
return Ok(ArithmExpr::assign(name.name, op, value, range));
}
self.pos = saved;
}
self.ternary()
}
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! arithm_bin_op {
(@pat op: $op:pat, left: $left:pat, right: $right:pat $(,)? ) => {
ArithmExpr::BinOp(crate::ast::ArithmBinaryExpr {
op: $op,
left: $left,
right: $right,
..
})
};
(@pat op: $op:pat, $left:ident, $right:ident $(,)? ) => {
ArithmExpr::BinOp(crate::ast::ArithmBinaryExpr {
op: $op,
left: $left,
right: $right,
..
})
};
(@pat op: $op:pat, left: $left:pat, right: $right:pat, ..) => {
ArithmExpr::BinOp(crate::ast::ArithmBinaryExpr {
op: $op,
left: $left,
right: $right,
..
})
};
(@pat op: $op:pat, $left:ident, $right:ident, ..) => {
ArithmExpr::BinOp(crate::ast::ArithmBinaryExpr {
op: $op,
left: $left,
right: $right,
..
})
};
(@pat op: $op:pat, right: $right:pat, ..) => {
ArithmExpr::BinOp(crate::ast::ArithmBinaryExpr {
op: $op,
right: $right,
..
})
};
(@pat op: $op:pat, left: $left:pat, ..) => {
ArithmExpr::BinOp(crate::ast::ArithmBinaryExpr {
op: $op,
left: $left,
..
})
};
(@pat op: $op:pat, ..) => {
ArithmExpr::BinOp(crate::ast::ArithmBinaryExpr { op: $op, .. })
};
(@pat left: $left:pat, right: $right:pat, ..) => {
ArithmExpr::BinOp(crate::ast::ArithmBinaryExpr {
left: $left,
right: $right,
..
})
};
(op: $op:expr, left: $left:expr, right: $right:expr $(,)?) => {
ArithmExpr::BinOp(crate::ast::ArithmBinaryExpr {
op: $op,
left: $left,
right: $right,
range: crate::ast::Range::default(),
})
};
}
macro_rules! arithm_un_op {
(@pat op: $op:pat, operand: $operand:pat $(,)?) => {
ArithmExpr::UnOp(crate::ast::ArithmUnaryExpr {
op: $op,
operand: $operand,
..
})
};
(@pat op: $op:pat, $operand:ident $(,)?) => {
ArithmExpr::UnOp(crate::ast::ArithmUnaryExpr {
op: $op,
operand: $operand,
..
})
};
(@pat op: $op:pat, $operand:ident, ..) => {
ArithmExpr::UnOp(crate::ast::ArithmUnaryExpr {
op: $op,
operand: $operand,
..
})
};
(@pat op: $op:pat, ..) => {
ArithmExpr::UnOp(crate::ast::ArithmUnaryExpr { op: $op, .. })
};
(@pat operand: $operand:pat, ..) => {
ArithmExpr::UnOp(crate::ast::ArithmUnaryExpr {
operand: $operand,
..
})
};
(op: $op:expr, operand: $operand:expr $(,)?) => {
ArithmExpr::UnOp(crate::ast::ArithmUnaryExpr {
op: $op,
operand: $operand,
range: crate::ast::Range::default(),
})
};
}
macro_rules! arithm_cond {
(@pat cond: $cond:pat, then_branch: $then_branch:pat, else_branch: $else_branch:pat $(,)? ) => {
ArithmExpr::Cond(crate::ast::ArithmConditionalExpr {
cond: $cond,
then_branch: $then_branch,
else_branch: $else_branch,
..
})
};
(@pat $cond:ident, $then_branch:ident, $else_branch:ident $(,)?) => {
ArithmExpr::Cond(crate::ast::ArithmConditionalExpr {
cond: $cond,
then_branch: $then_branch,
else_branch: $else_branch,
..
})
};
(@pat $cond:ident, $then_branch:ident, $else_branch:ident, ..) => {
ArithmExpr::Cond(crate::ast::ArithmConditionalExpr {
cond: $cond,
then_branch: $then_branch,
else_branch: $else_branch,
..
})
};
(@pat .. ) => {
ArithmExpr::Cond(crate::ast::ArithmConditionalExpr { .. })
};
(cond: $cond:expr, then_branch: $then_branch:expr, else_branch: $else_branch:expr $(,)?) => {
ArithmExpr::Cond(crate::ast::ArithmConditionalExpr {
cond: $cond,
then_branch: $then_branch,
else_branch: $else_branch,
range: crate::ast::Range::default(),
})
};
}
macro_rules! arithm_assign {
(@pat name: $name:pat, op: $op:pat, value: $value:pat $(,)?) => {
ArithmExpr::Assign(crate::ast::ArithmAssignmentExpr {
name: $name,
op: $op,
value: $value,
..
})
};
(@pat $name:ident, $op:ident, $value:ident $(,)?) => {
ArithmExpr::Assign(crate::ast::ArithmAssignmentExpr {
name: $name,
op: $op,
value: $value,
..
})
};
(@pat name: $name:pat, op: $op:pat, ..) => {
ArithmExpr::Assign(crate::ast::ArithmAssignmentExpr {
name: $name,
op: $op,
..
})
};
(@pat $name:ident, $op:ident, ..) => {
ArithmExpr::Assign(crate::ast::ArithmAssignmentExpr {
name: $name,
op: $op,
..
})
};
(@pat op: $op:pat, ..) => {
ArithmExpr::Assign(crate::ast::ArithmAssignmentExpr { op: $op, .. })
};
(@pat $op:ident, ..) => {
ArithmExpr::Assign(crate::ast::ArithmAssignmentExpr { op: $op, .. })
};
(name: $name:expr, op: $op:expr, value: $value:expr $(,)?) => {
ArithmExpr::Assign(crate::ast::ArithmAssignmentExpr {
name: $name,
op: $op,
value: $value,
range: crate::ast::Range::default(),
})
};
}
fn arithm_literal(value: i64) -> ArithmExpr {
ArithmExpr::literal(value, crate::ast::Range::default())
}
fn arithm_variable(name: &str) -> ArithmExpr {
ArithmExpr::variable(name, crate::ast::Range::default())
}
#[test]
fn simple_literal() {
let e = parse_arithm_expr("42");
assert_eq!(e, arithm_literal(42));
}
#[test]
fn simple_add() {
let e = parse_arithm_expr("1 + 2");
assert_eq!(
e,
arithm_bin_op! {
op: ArithmBinOp::Add,
left: Box::new(arithm_literal(1)),
right: Box::new(arithm_literal(2)),
}
);
}
#[test]
fn precedence_mul_add() {
let e = parse_arithm_expr("1 + 2 * 3");
match e {
arithm_bin_op! { @pat
op: ArithmBinOp::Add,
right: right,
..
} => {
assert!(matches!(
*right,
arithm_bin_op! { @pat
op: ArithmBinOp::Mul,
..
}
));
}
other => panic!("expected Add at top level, got {other:?}"),
}
}
#[test]
fn ternary_expr() {
let e = parse_arithm_expr("1 ? 2 : 3");
assert!(matches!(e, arithm_cond! { @pat .. }));
}
#[test]
fn assignment_expr() {
let e = parse_arithm_expr("x = 5");
match e {
arithm_assign! { @pat name, op, .. } => {
assert_eq!(name, "x");
assert_eq!(op, ArithmAssignOp::Equal);
}
other => panic!("expected Assign, got {other:?}"),
}
}
#[test]
fn compound_assignment() {
let e = parse_arithm_expr("x += 1");
match e {
arithm_assign! { @pat op, .. } => {
assert_eq!(op, ArithmAssignOp::AddEq);
}
other => panic!("expected Assign, got {other:?}"),
}
}
#[test]
fn variable_reference() {
let e = parse_arithm_expr("x");
assert_eq!(e, arithm_variable("x"));
}
#[test]
fn unary_negation() {
let e = parse_arithm_expr("-5");
match e {
arithm_un_op! { @pat
op: ArithmUnOp::Minus,
operand,
} => {
assert_eq!(*operand, arithm_literal(5));
}
other => panic!("expected UnOp Minus, got {other:?}"),
}
}
#[test]
fn hex_literal() {
let e = parse_arithm_expr("0xff");
assert_eq!(e, arithm_literal(255));
}
#[test]
fn logical_operators() {
let e = parse_arithm_expr("1 && 0 || 1");
assert!(matches!(
e,
arithm_bin_op! { @pat
op: ArithmBinOp::LogOr,
..
}
));
}
#[test]
fn shift_operators() {
let e = parse_arithm_expr("1 << 3");
match e {
arithm_bin_op! { @pat
op: ArithmBinOp::Shl,
..
} => {}
other => panic!("expected Shl, got {other:?}"),
}
}
#[test]
fn comparison_operators() {
let e = parse_arithm_expr("a <= b");
assert!(matches!(
e,
arithm_bin_op! { @pat
op: ArithmBinOp::LessEq,
..
}
));
}
#[test]
fn nested_parens() {
let e = parse_arithm_expr("(1 + 2) * 3");
match e {
arithm_bin_op! { @pat
op: ArithmBinOp::Mul,
left: left,
..
} => match *left {
ArithmExpr::BinOp(binary) => assert_eq!(binary.op(), ArithmBinOp::Add),
other => panic!("expected Add under parens, got {other:?}"),
},
other => panic!("expected Mul at top, got {other:?}"),
}
}
#[test]
fn literal_zero() {
let e = parse_arithm_expr("0");
assert_eq!(e, arithm_literal(0));
}
#[test]
fn large_literal() {
let e = parse_arithm_expr("9999999999");
assert_eq!(e, arithm_literal(9999999999));
}
#[test]
fn hex_literal_uppercase_x() {
let e = parse_arithm_expr("0XFF");
assert_eq!(e, arithm_literal(255));
}
#[test]
fn hex_literal_mixed_case_digits() {
let e = parse_arithm_expr("0xAbCd");
assert_eq!(e, arithm_literal(0xABCD));
}
#[test]
fn empty_input() {
let e = parse_arithm_expr("");
assert_eq!(e, arithm_literal(0));
}
#[test]
fn whitespace_only() {
let e = parse_arithm_expr(" \t\n ");
assert_eq!(e, arithm_literal(0));
}
#[test]
fn leading_trailing_whitespace() {
let e = parse_arithm_expr(" 42 ");
assert_eq!(e, arithm_literal(42));
}
#[test]
fn subtraction() {
let e = parse_arithm_expr("10 - 3");
assert_eq!(
e,
arithm_bin_op! {
op: ArithmBinOp::Sub,
left: Box::new(arithm_literal(10)),
right: Box::new(arithm_literal(3)),
}
);
}
#[test]
fn multiplication() {
let e = parse_arithm_expr("4 * 5");
assert_eq!(
e,
arithm_bin_op! {
op: ArithmBinOp::Mul,
left: Box::new(arithm_literal(4)),
right: Box::new(arithm_literal(5)),
}
);
}
#[test]
fn division() {
let e = parse_arithm_expr("10 / 2");
assert_eq!(
e,
arithm_bin_op! {
op: ArithmBinOp::Div,
left: Box::new(arithm_literal(10)),
right: Box::new(arithm_literal(2)),
}
);
}
#[test]
fn modulo() {
let e = parse_arithm_expr("10 % 3");
assert_eq!(
e,
arithm_bin_op! {
op: ArithmBinOp::Mod,
left: Box::new(arithm_literal(10)),
right: Box::new(arithm_literal(3)),
}
);
}
#[test]
fn shift_right() {
let e = parse_arithm_expr("8 >> 2");
assert_eq!(
e,
arithm_bin_op! {
op: ArithmBinOp::Shr,
left: Box::new(arithm_literal(8)),
right: Box::new(arithm_literal(2)),
}
);
}
#[test]
fn less_than() {
let e = parse_arithm_expr("1 < 2");
assert_eq!(
e,
arithm_bin_op! {
op: ArithmBinOp::LessThan,
left: Box::new(arithm_literal(1)),
right: Box::new(arithm_literal(2)),
}
);
}
#[test]
fn greater_than() {
let e = parse_arithm_expr("5 > 3");
assert_eq!(
e,
arithm_bin_op! {
op: ArithmBinOp::GreaterThan,
left: Box::new(arithm_literal(5)),
right: Box::new(arithm_literal(3)),
}
);
}
#[test]
fn greater_equal() {
let e = parse_arithm_expr("5 >= 3");
assert_eq!(
e,
arithm_bin_op! {
op: ArithmBinOp::GreaterEq,
left: Box::new(arithm_literal(5)),
right: Box::new(arithm_literal(3)),
}
);
}
#[test]
fn equality() {
let e = parse_arithm_expr("1 == 1");
assert_eq!(
e,
arithm_bin_op! {
op: ArithmBinOp::Equal,
left: Box::new(arithm_literal(1)),
right: Box::new(arithm_literal(1)),
}
);
}
#[test]
fn not_equal() {
let e = parse_arithm_expr("1 != 2");
assert_eq!(
e,
arithm_bin_op! {
op: ArithmBinOp::NotEqual,
left: Box::new(arithm_literal(1)),
right: Box::new(arithm_literal(2)),
}
);
}
#[test]
fn bitwise_and() {
let e = parse_arithm_expr("5 & 3");
assert_eq!(
e,
arithm_bin_op! {
op: ArithmBinOp::BitAnd,
left: Box::new(arithm_literal(5)),
right: Box::new(arithm_literal(3)),
}
);
}
#[test]
fn bitwise_xor() {
let e = parse_arithm_expr("5 ^ 3");
assert_eq!(
e,
arithm_bin_op! {
op: ArithmBinOp::BitXor,
left: Box::new(arithm_literal(5)),
right: Box::new(arithm_literal(3)),
}
);
}
#[test]
fn bitwise_or() {
let e = parse_arithm_expr("5 | 3");
assert_eq!(
e,
arithm_bin_op! {
op: ArithmBinOp::BitOr,
left: Box::new(arithm_literal(5)),
right: Box::new(arithm_literal(3)),
}
);
}
#[test]
fn logical_and_only() {
let e = parse_arithm_expr("1 && 0");
assert_eq!(
e,
arithm_bin_op! {
op: ArithmBinOp::LogAnd,
left: Box::new(arithm_literal(1)),
right: Box::new(arithm_literal(0)),
}
);
}
#[test]
fn logical_or_only() {
let e = parse_arithm_expr("0 || 1");
assert_eq!(
e,
arithm_bin_op! {
op: ArithmBinOp::LogOr,
left: Box::new(arithm_literal(0)),
right: Box::new(arithm_literal(1)),
}
);
}
#[test]
fn unary_plus() {
let e = parse_arithm_expr("+7");
assert_eq!(
e,
arithm_un_op! {
op: ArithmUnOp::Plus,
operand: Box::new(arithm_literal(7)),
}
);
}
#[test]
fn unary_bitnot() {
let e = parse_arithm_expr("~0");
assert_eq!(
e,
arithm_un_op! {
op: ArithmUnOp::BitNot,
operand: Box::new(arithm_literal(0)),
}
);
}
#[test]
fn unary_lognot() {
let e = parse_arithm_expr("!1");
assert_eq!(
e,
arithm_un_op! {
op: ArithmUnOp::LogNot,
operand: Box::new(arithm_literal(1)),
}
);
}
#[test]
fn double_negation_with_parens() {
let e = parse_arithm_expr("-(-5)");
assert_eq!(
e,
arithm_un_op! {
op: ArithmUnOp::Minus,
operand: Box::new(arithm_un_op! {
op: ArithmUnOp::Minus,
operand: Box::new(arithm_literal(5)),
}),
}
);
}
#[test]
fn double_minus_is_not_double_unary() {
let e = parse_arithm_expr("--5");
assert_eq!(e, arithm_literal(0));
}
#[test]
fn compound_assign_sub() {
let e = parse_arithm_expr("x -= 1");
match e {
arithm_assign! { @pat name, op, value } => {
assert_eq!(name, "x");
assert_eq!(op, ArithmAssignOp::SubEq);
assert_eq!(*value, arithm_literal(1));
}
other => panic!("expected Assign SubEq, got {other:?}"),
}
}
#[test]
fn compound_assign_mul() {
let e = parse_arithm_expr("x *= 2");
match e {
arithm_assign! { @pat name, op, .. } => {
assert_eq!(name, "x");
assert_eq!(op, ArithmAssignOp::MulEq);
}
other => panic!("expected Assign MulEq, got {other:?}"),
}
}
#[test]
fn compound_assign_div() {
let e = parse_arithm_expr("x /= 2");
match e {
arithm_assign! { @pat name, op, .. } => {
assert_eq!(name, "x");
assert_eq!(op, ArithmAssignOp::DivEq);
}
other => panic!("expected Assign DivEq, got {other:?}"),
}
}
#[test]
fn compound_assign_mod() {
let e = parse_arithm_expr("x %= 3");
match e {
arithm_assign! { @pat name, op, .. } => {
assert_eq!(name, "x");
assert_eq!(op, ArithmAssignOp::ModEq);
}
other => panic!("expected Assign ModEq, got {other:?}"),
}
}
#[test]
fn compound_assign_shl() {
let e = parse_arithm_expr("x <<= 2");
match e {
arithm_assign! { @pat name, op, .. } => {
assert_eq!(name, "x");
assert_eq!(op, ArithmAssignOp::ShlEq);
}
other => panic!("expected Assign ShlEq, got {other:?}"),
}
}
#[test]
fn compound_assign_shr() {
let e = parse_arithm_expr("x >>= 2");
match e {
arithm_assign! { @pat name, op, .. } => {
assert_eq!(name, "x");
assert_eq!(op, ArithmAssignOp::ShrEq);
}
other => panic!("expected Assign ShrEq, got {other:?}"),
}
}
#[test]
fn compound_assign_and() {
let e = parse_arithm_expr("x &= 0xf");
match e {
arithm_assign! { @pat name, op, value } => {
assert_eq!(name, "x");
assert_eq!(op, ArithmAssignOp::AndEq);
assert_eq!(*value, arithm_literal(15));
}
other => panic!("expected Assign AndEq, got {other:?}"),
}
}
#[test]
fn compound_assign_xor() {
let e = parse_arithm_expr("x ^= 1");
match e {
arithm_assign! { @pat name, op, .. } => {
assert_eq!(name, "x");
assert_eq!(op, ArithmAssignOp::XorEq);
}
other => panic!("expected Assign XorEq, got {other:?}"),
}
}
#[test]
fn compound_assign_or() {
let e = parse_arithm_expr("x |= 1");
match e {
arithm_assign! { @pat name, op, .. } => {
assert_eq!(name, "x");
assert_eq!(op, ArithmAssignOp::OrEq);
}
other => panic!("expected Assign OrEq, got {other:?}"),
}
}
#[test]
fn dollar_variable() {
let e = parse_arithm_expr("$x");
assert_eq!(e, arithm_variable("$x"));
}
#[test]
fn dollar_variable_not_assignable() {
let e = parse_arithm_expr("$x");
assert_eq!(e, arithm_variable("$x"));
}
#[test]
fn variable_with_underscore() {
let e = parse_arithm_expr("_foo_bar");
assert_eq!(e, arithm_variable("_foo_bar"));
}
#[test]
fn variable_with_digits() {
let e = parse_arithm_expr("var123");
assert_eq!(e, arithm_variable("var123"));
}
#[test]
fn ternary_full_structure() {
let e = parse_arithm_expr("a ? 10 : 20");
assert_eq!(
e,
arithm_cond! {
cond: Box::new(arithm_variable("a")),
then_branch: Box::new(arithm_literal(10)),
else_branch: Box::new(arithm_literal(20)),
}
);
}
#[test]
fn nested_ternary() {
let e = parse_arithm_expr("a ? b ? 1 : 2 : 3");
match e {
arithm_cond! { @pat
cond,
then_branch,
else_branch,
} => {
assert_eq!(*cond, arithm_variable("a"));
assert!(matches!(*then_branch, arithm_cond! { @pat .. }));
assert_eq!(*else_branch, arithm_literal(3));
}
other => panic!("expected Cond, got {other:?}"),
}
}
#[test]
fn left_associativity_addition() {
let e = parse_arithm_expr("1 + 2 + 3");
match e {
arithm_bin_op! { @pat
op: ArithmBinOp::Add,
left,
right,
} => {
assert_eq!(
*left,
arithm_bin_op! {
op: ArithmBinOp::Add,
left: Box::new(arithm_literal(1)),
right: Box::new(arithm_literal(2)),
}
);
assert_eq!(*right, arithm_literal(3));
}
other => panic!("expected left-assoc Add, got {other:?}"),
}
}
#[test]
fn left_associativity_multiplication() {
let e = parse_arithm_expr("2 * 3 * 4");
match e {
arithm_bin_op! { @pat
op: ArithmBinOp::Mul,
left,
right,
} => {
assert_eq!(
*left,
arithm_bin_op! {
op: ArithmBinOp::Mul,
left: Box::new(arithm_literal(2)),
right: Box::new(arithm_literal(3)),
}
);
assert_eq!(*right, arithm_literal(4));
}
other => panic!("expected left-assoc Mul, got {other:?}"),
}
}
#[test]
fn deeply_nested_parens() {
let e = parse_arithm_expr("((((1))))");
assert_eq!(e, arithm_literal(1));
}
#[test]
fn precedence_bitwise_and_vs_or() {
let e = parse_arithm_expr("1 | 2 & 3");
match e {
arithm_bin_op! { @pat
op: ArithmBinOp::BitOr,
left,
right,
} => {
assert_eq!(*left, arithm_literal(1));
assert_eq!(
*right,
arithm_bin_op! {
op: ArithmBinOp::BitAnd,
left: Box::new(arithm_literal(2)),
right: Box::new(arithm_literal(3)),
}
);
}
other => panic!("expected BitOr at top, got {other:?}"),
}
}
#[test]
fn precedence_xor_vs_or() {
let e = parse_arithm_expr("1 | 2 ^ 3");
match e {
arithm_bin_op! { @pat
op: ArithmBinOp::BitOr,
left,
right,
} => {
assert_eq!(*left, arithm_literal(1));
assert_eq!(
*right,
arithm_bin_op! {
op: ArithmBinOp::BitXor,
left: Box::new(arithm_literal(2)),
right: Box::new(arithm_literal(3)),
}
);
}
other => panic!("expected BitOr at top, got {other:?}"),
}
}
#[test]
fn precedence_shift_vs_add() {
let e = parse_arithm_expr("1 + 2 << 3");
match e {
arithm_bin_op! { @pat
op: ArithmBinOp::Shl,
left,
right,
} => {
assert_eq!(
*left,
arithm_bin_op! {
op: ArithmBinOp::Add,
left: Box::new(arithm_literal(1)),
right: Box::new(arithm_literal(2)),
}
);
assert_eq!(*right, arithm_literal(3));
}
other => panic!("expected Shl at top, got {other:?}"),
}
}
#[test]
fn precedence_comparison_vs_shift() {
let e = parse_arithm_expr("1 << 2 < 10");
match e {
arithm_bin_op! { @pat
op: ArithmBinOp::LessThan,
left,
right,
} => {
assert_eq!(
*left,
arithm_bin_op! {
op: ArithmBinOp::Shl,
left: Box::new(arithm_literal(1)),
right: Box::new(arithm_literal(2)),
}
);
assert_eq!(*right, arithm_literal(10));
}
other => panic!("expected LessThan at top, got {other:?}"),
}
}
#[test]
fn precedence_equality_vs_comparison() {
let e = parse_arithm_expr("1 < 2 == 1");
match e {
arithm_bin_op! { @pat
op: ArithmBinOp::Equal,
left,
right,
} => {
assert_eq!(
*left,
arithm_bin_op! {
op: ArithmBinOp::LessThan,
left: Box::new(arithm_literal(1)),
right: Box::new(arithm_literal(2)),
}
);
assert_eq!(*right, arithm_literal(1));
}
other => panic!("expected Equal at top, got {other:?}"),
}
}
#[test]
fn precedence_logical_and_vs_bitwise_or() {
let e = parse_arithm_expr("1 | 0 && 1");
match e {
arithm_bin_op! { @pat
op: ArithmBinOp::LogAnd,
left,
right,
} => {
assert_eq!(
*left,
arithm_bin_op! {
op: ArithmBinOp::BitOr,
left: Box::new(arithm_literal(1)),
right: Box::new(arithm_literal(0)),
}
);
assert_eq!(*right, arithm_literal(1));
}
other => panic!("expected LogAnd at top, got {other:?}"),
}
}
#[test]
fn precedence_logical_or_vs_logical_and() {
let e = parse_arithm_expr("0 || 1 && 1");
match e {
arithm_bin_op! { @pat
op: ArithmBinOp::LogOr,
left,
right,
} => {
assert_eq!(*left, arithm_literal(0));
assert_eq!(
*right,
arithm_bin_op! {
op: ArithmBinOp::LogAnd,
left: Box::new(arithm_literal(1)),
right: Box::new(arithm_literal(1)),
}
);
}
other => panic!("expected LogOr at top, got {other:?}"),
}
}
#[test]
fn chained_assignment() {
let e = parse_arithm_expr("x = y = 5");
match e {
arithm_assign! { @pat name, op, value } => {
assert_eq!(name, "x");
assert_eq!(op, ArithmAssignOp::Equal);
match *value {
arithm_assign! { @pat
name: inner_name,
op: inner_op,
value: inner_value,
} => {
assert_eq!(inner_name, "y");
assert_eq!(inner_op, ArithmAssignOp::Equal);
assert_eq!(*inner_value, arithm_literal(5));
}
other => panic!("expected inner Assign, got {other:?}"),
}
}
other => panic!("expected Assign, got {other:?}"),
}
}
#[test]
fn assignment_with_expression() {
let e = parse_arithm_expr("x = 1 + 2");
match e {
arithm_assign! { @pat name, op, value } => {
assert_eq!(name, "x");
assert_eq!(op, ArithmAssignOp::Equal);
assert_eq!(
*value,
arithm_bin_op! {
op: ArithmBinOp::Add,
left: Box::new(arithm_literal(1)),
right: Box::new(arithm_literal(2)),
}
);
}
other => panic!("expected Assign, got {other:?}"),
}
}
#[test]
fn unary_in_expression() {
let e = parse_arithm_expr("-1 + 2");
match e {
arithm_bin_op! { @pat
op: ArithmBinOp::Add,
left,
right,
} => {
assert_eq!(
*left,
arithm_un_op! {
op: ArithmUnOp::Minus,
operand: Box::new(arithm_literal(1)),
}
);
assert_eq!(*right, arithm_literal(2));
}
other => panic!("expected Add with unary minus left, got {other:?}"),
}
}
#[test]
fn complex_expression() {
let e = parse_arithm_expr("(a + 1) * 2 == 10");
match e {
arithm_bin_op! { @pat
op: ArithmBinOp::Equal,
left,
right,
} => {
assert!(matches!(
*left,
arithm_bin_op! { @pat
op: ArithmBinOp::Mul,
..
}
));
assert_eq!(*right, arithm_literal(10));
}
other => panic!("expected Equal at top, got {other:?}"),
}
}
#[test]
fn no_whitespace() {
let e = parse_arithm_expr("1+2*3");
match e {
arithm_bin_op! { @pat
op: ArithmBinOp::Add,
left,
right,
} => {
assert_eq!(*left, arithm_literal(1));
assert_eq!(
*right,
arithm_bin_op! {
op: ArithmBinOp::Mul,
left: Box::new(arithm_literal(2)),
right: Box::new(arithm_literal(3)),
}
);
}
other => panic!("expected Add at top, got {other:?}"),
}
}
#[test]
fn variable_in_arithmetic() {
let e = parse_arithm_expr("x + y");
assert_eq!(
e,
arithm_bin_op! {
op: ArithmBinOp::Add,
left: Box::new(arithm_variable("x")),
right: Box::new(arithm_variable("y")),
}
);
}
#[test]
fn ternary_with_expressions() {
let e = parse_arithm_expr("x > 0 ? x : -x");
match e {
arithm_cond! { @pat
cond,
then_branch,
else_branch,
} => {
assert!(matches!(
*cond,
arithm_bin_op! { @pat
op: ArithmBinOp::GreaterThan,
..
}
));
assert_eq!(*then_branch, arithm_variable("x"));
assert!(matches!(
*else_branch,
arithm_un_op! { @pat
op: ArithmUnOp::Minus,
..
}
));
}
other => panic!("expected Cond, got {other:?}"),
}
}
#[test]
fn mixed_factor_operators() {
let e = parse_arithm_expr("12 / 3 * 2 % 5");
match e {
arithm_bin_op! { @pat
op: ArithmBinOp::Mod,
left,
right,
} => {
assert_eq!(*right, arithm_literal(5));
match *left {
arithm_bin_op! { @pat
op: ArithmBinOp::Mul,
left: inner_left,
right: inner_right,
} => {
assert_eq!(
*inner_left,
arithm_bin_op! {
op: ArithmBinOp::Div,
left: Box::new(arithm_literal(12)),
right: Box::new(arithm_literal(3)),
}
);
assert_eq!(*inner_right, arithm_literal(2));
}
other => panic!("expected Mul, got {other:?}"),
}
}
other => panic!("expected Mod at top, got {other:?}"),
}
}
#[test]
fn hex_in_expression() {
let e = parse_arithm_expr("0x10 + 0xff");
assert_eq!(
e,
arithm_bin_op! {
op: ArithmBinOp::Add,
left: Box::new(arithm_literal(16)),
right: Box::new(arithm_literal(255)),
}
);
}
#[test]
fn paren_in_assignment() {
let e = parse_arithm_expr("x = (1 + 2)");
match e {
arithm_assign! { @pat name, op, value } => {
assert_eq!(name, "x");
assert_eq!(op, ArithmAssignOp::Equal);
assert_eq!(
*value,
arithm_bin_op! {
op: ArithmBinOp::Add,
left: Box::new(arithm_literal(1)),
right: Box::new(arithm_literal(2)),
}
);
}
other => panic!("expected Assign, got {other:?}"),
}
}
#[test]
fn lognot_of_expression() {
let e = parse_arithm_expr("!(a == b)");
match e {
arithm_un_op! { @pat
op: ArithmUnOp::LogNot,
operand,
} => {
assert!(matches!(
*operand,
arithm_bin_op! { @pat
op: ArithmBinOp::Equal,
..
}
));
}
other => panic!("expected LogNot, got {other:?}"),
}
}
#[test]
fn bitnot_of_variable() {
let e = parse_arithm_expr("~x");
assert_eq!(
e,
arithm_un_op! {
op: ArithmUnOp::BitNot,
operand: Box::new(arithm_variable("x")),
}
);
}
#[test]
fn strict_parse_rejects_trailing_characters() {
let err = parse_arithm_expr_strict("1 + 2x").expect_err("should reject trailing garbage");
assert_eq!(err.message, "trailing characters in arithmetic expression");
assert_eq!(err.position, 5);
}
#[test]
fn strict_parse_rejects_missing_ternary_colon() {
let err = parse_arithm_expr_strict("a ? 1")
.expect_err("should require ternary else-branch separator");
assert_eq!(err.message, "expected ':' in ternary expression");
assert_eq!(err.position, 5);
}
#[test]
fn strict_parse_rejects_unclosed_parens() {
let err =
parse_arithm_expr_strict("(1 + 2").expect_err("should require closing parenthesis");
assert_eq!(err.message, "expected ')'");
}
#[test]
fn strict_parse_rejects_invalid_hex_literal() {
let err = parse_arithm_expr_strict("0x").expect_err("should reject incomplete hex");
assert_eq!(err.message, "invalid hexadecimal literal");
assert_eq!(err.position, 0);
}
#[test]
fn strict_parse_rejects_overflowing_integer_literal() {
let err = parse_arithm_expr_strict("999999999999999999999999999999")
.expect_err("should reject overflowing integer literal");
assert_eq!(err.message, "invalid integer literal");
assert_eq!(err.position, 0);
}
}