fix: correctly evaluate unary operators with parenthesis

This commit is contained in:
electria 2026-10-20 16:53:26 -07:00
commit 7182e29512
Signed by: electria
SSH key fingerprint: SHA256:8LlB3ucPbBHqozqkhsNbaV5oG3SlzzqUj8FZDL6IPQs
3 changed files with 37 additions and 12 deletions

View file

@ -41,6 +41,31 @@ fn collapse_toplevel(nodes: &mut Vec<Node>, mut end: usize) -> Result<(), Error>
.rposition(|n| *n == Node::ParenthesisStart)
.unwrap_or(0);
let mut i = start;
while i < end {
if let Node::Operator(operator) = nodes[i]
&& matches!(operator, Operator::Plus | Operator::Minus)
&& matches!(
i.checked_sub(1).and_then(|i| nodes.get(i)),
None | Some(Node::ParenthesisStart)
)
{
match nodes.get_mut(i + 1) {
None => return Err(Error::OutOfBounds(Nodes(nodes.clone()), i + 1)),
Some(Node::Number(n)) => {
if operator == Operator::Minus {
n.mul_from(-1);
}
nodes.remove(i);
end -= 1;
}
Some(n) => return Err(Error::InvalidOperand(n.clone())),
}
}
i += 1;
}
while let Some((index, function)) = nodes[start..end]
.iter()
.enumerate()

View file

@ -157,17 +157,9 @@ impl FromStr for Nodes {
} else if c == ')' {
nodes.push(Node::ParenthesisEnd);
} else {
if matches!(
nodes.last(),
Some(Node::Operator(_) | Node::ParenthesisStart) | None
) {
// it must be a unary operator
state = ParsingState::ReadingNumber(i);
} else {
nodes.push(Node::Operator(
c.try_into().map_err(Error::InvalidOperator)?,
));
}
nodes.push(Node::Operator(
c.try_into().map_err(Error::InvalidOperator)?,
));
}
}
}
@ -241,7 +233,8 @@ mod tests {
Node::from(5),
Node::Operator(Operator::Mult),
Node::ParenthesisStart,
Node::from(-5),
Node::Operator(Operator::Minus),
Node::from(5),
Node::ParenthesisEnd,
],
)

View file

@ -38,6 +38,13 @@ fn unary_operators() {
assert_eq!(eval("-10"), -10.);
assert_eq!(eval("+10"), 10.);
}
#[test]
fn unary_operators_with_parenthesis() {
assert_eq!(eval("-(10)"), -10.);
assert_eq!(eval("+(10)"), 10.);
assert_eq!(eval("(-10)"), -10.);
assert_eq!(eval("(+10)"), 10.);
}
#[test]
fn constant_implicit_mult() {