[READ-ONLY] Mirror of https://github.com/probablykasper/cpc. Text calculator with support for units and conversion cpc.kasper.space
calculator cli conversion converter library math package units units-converter wasm website
0

Configure Feed

Select the types of activity you want to include in your feed.

Fix unary minus precedence being higher than ^

Closes #37

Kasper (Sep 20, 2023, 6:14 AM +0200) 7d836f07 e1e78267

+71 -54
+3
CHANGELOG.md
··· 1 1 # Changelog 2 2 3 + ## Next 4 + - Fix negative unary `-` always having higher precedence than `^`. This resulted in `-3^2` returning `9` instead of `-9` 5 + 3 6 ## 1.9.2 - 2023 Jul 11 4 7 - Fix automatic light year unit not chosen for large distances (@gcomte) 5 8
+21 -1
src/lib.rs
··· 48 48 #[rustfmt::skip] 49 49 pub mod units; 50 50 51 - #[derive(Clone, Debug)] 51 + #[derive(Clone, Debug, PartialEq)] 52 52 /// A number with a `Unit`. 53 53 /// 54 54 /// Example: ··· 290 290 Err(e) => Err(format!("Lexing error: {}", e)), 291 291 } 292 292 } 293 + 294 + #[cfg(test)] 295 + mod tests { 296 + use super::*; 297 + 298 + fn default_eval(input: &str) -> Number { 299 + eval(input, true, units::Unit::Celsius, false).unwrap() 300 + } 301 + 302 + #[test] 303 + fn test_evaluations() { 304 + assert_eq!(default_eval("-2(-3)"), Number::new(d128!(6), Unit::NoUnit)); 305 + assert_eq!(default_eval("-2(3)"), Number::new(d128!(-6), Unit::NoUnit)); 306 + assert_eq!(default_eval("(3)-2"), Number::new(d128!(1), Unit::NoUnit)); 307 + assert_eq!(default_eval("-1km to m"), Number::new(d128!(-1000), Unit::Meter)); 308 + assert_eq!(default_eval("2*-3*0.5"), Number::new(d128!(-3), Unit::NoUnit)); 309 + assert_eq!(default_eval("-3^2"), Number::new(d128!(-9), Unit::NoUnit)); 310 + assert_eq!(default_eval("-1+2"), Number::new(d128!(1), Unit::NoUnit)); 311 + } 312 + }
+47 -53
src/parser.rs
··· 24 24 25 25 /// Parse [`Token`]s into an Abstract Syntax Tree ([`AstNode`]) 26 26 pub fn parse(tokens: &[Token]) -> Result<AstNode, String> { 27 - parse_level_1(tokens, 0).and_then(|(ast, next_pos)| { 27 + parse_text_operators(tokens, 0).and_then(|(ast, next_pos)| { 28 28 if next_pos == tokens.len() { 29 29 Ok(ast) 30 30 } else { ··· 38 38 39 39 // level 1 precedence (lowest): to, of 40 40 /// Parse [`To`](crate::TextOperator::To) and [`Of`](crate::TextOperator::Of) 41 - pub fn parse_level_1(tokens: &[Token], pos: usize) -> Result<(AstNode, usize), String> { 41 + pub fn parse_text_operators(tokens: &[Token], pos: usize) -> Result<(AstNode, usize), String> { 42 42 // do higher precedences first, then come back down 43 - let (mut node, mut pos) = parse_level_2(tokens, pos)?; 43 + let (mut node, mut pos) = parse_plus(tokens, pos)?; 44 44 // now we loop through the next tokens 45 45 loop { 46 46 let token = tokens.get(pos); ··· 48 48 // if there's a match, we once again do higher precedences, then come 49 49 // back down again and continue the loop 50 50 Some(&Token::TextOperator(To)) | Some(&Token::TextOperator(Of)) => { 51 - let (right_node, next_pos) = parse_level_2(tokens, pos + 1)?; 51 + let (right_node, next_pos) = parse_plus(tokens, pos + 1)?; 52 52 let mut new_node = AstNode::new(token.unwrap().clone()); 53 53 new_node.children.push(node); 54 54 new_node.children.push(right_node); 55 55 node = new_node; 56 56 pos = next_pos; 57 57 } 58 - // if there's no match, we go down to a lower precedence (or, in this 59 - // case, we're done) 58 + // if there's no match, we go down to a lower precedence 60 59 _ => { 61 60 return Ok((node, pos)); 62 61 } ··· 64 63 } 65 64 } 66 65 67 - // level 2 precedence: +, - 68 - /// Parse [`Plus`](crate::Operator::Plus) and [`Minus`](crate::Operator::Minus) 69 - pub fn parse_level_2(tokens: &[Token], pos: usize) -> Result<(AstNode, usize), String> { 70 - let (mut node, mut pos) = parse_level_3(tokens, pos)?; 66 + /// Parse [`+`](crate::Operator::Plus), [`-`](crate::Operator::Minus) 67 + pub fn parse_plus(tokens: &[Token], pos: usize) -> Result<(AstNode, usize), String> { 68 + let (mut node, mut pos) = parse_unary(tokens, pos)?; 71 69 loop { 72 70 let token = tokens.get(pos); 73 71 match token { 74 72 Some(&Token::Operator(Plus)) | Some(&Token::Operator(Minus)) => { 75 - let (right_node, next_pos) = parse_level_3(tokens, pos + 1)?; 73 + let (right_node, next_pos) = parse_unary(tokens, pos + 1)?; 76 74 let mut new_node = AstNode::new(token.unwrap().clone()); 77 75 new_node.children.push(node); 78 76 new_node.children.push(right_node); ··· 86 84 } 87 85 } 88 86 89 - // level 3 precedence: *, /, modulo, implicative multiplication, foot-inch 6'4" 90 - /// Parse [`Multiply`](crate::Operator::Multiply), [`Divide`](crate::Operator::Divide), [`Modulo`](crate::Operator::Modulo) and implicative multiplication (for example`2pi`) 91 - pub fn parse_level_3(tokens: &[Token], pos: usize) -> Result<(AstNode, usize), String> { 87 + /// Parse [`unary -`](Token::Negative) (for example -5) 88 + pub fn parse_unary(tokens: &[Token], pos: usize) -> Result<(AstNode, usize), String> { 89 + // Since a unary operator has no left side, we parse the the unary operator immediately 90 + let token = tokens.get(pos); 91 + match token { 92 + Some(&Token::Operator(Minus)) => { 93 + let (right_node, next_pos) = parse_mult_level(tokens, pos + 1)?; 94 + let mut new_node = AstNode::new(Token::Negative); 95 + new_node.children.push(right_node); 96 + Ok((new_node, next_pos)) 97 + } 98 + _ => parse_mult_level(tokens, pos), 99 + } 100 + } 101 + 102 + /// Parse [`*`](crate::Operator::Multiply), [`/`](crate::Operator::Divide), [`Modulo`](crate::Operator::Modulo), implicative multiplication (for example`2pi`), foot-inch syntax (for example `6'4"`) 103 + pub fn parse_mult_level(tokens: &[Token], pos: usize) -> Result<(AstNode, usize), String> { 92 104 // parse foot-inch syntax 6'4" 93 105 let token0 = tokens.get(pos); 94 106 if let Some(Token::Number(_number)) = token0 { ··· 117 129 } 118 130 } 119 131 120 - let (mut node, mut pos) = parse_level_4(tokens, pos)?; 132 + let (mut node, mut pos) = parse_caret(tokens, pos)?; 121 133 122 134 loop { 123 135 let token = tokens.get(pos); ··· 125 137 Some(&Token::Operator(Multiply)) 126 138 | Some(&Token::Operator(Divide)) 127 139 | Some(&Token::Operator(Modulo)) => { 128 - let (right_node, next_pos) = parse_level_4(tokens, pos + 1)?; 140 + let (right_node, next_pos) = parse_caret(tokens, pos + 1)?; 129 141 let mut new_node = AstNode::new(token.unwrap().clone()); 130 142 new_node.children.push(node); 131 143 new_node.children.push(right_node); ··· 145 157 let last_token = tokens.get(pos - 1); 146 158 match last_token { 147 159 Some(&Token::Constant(_)) | Some(&Token::Operator(RightParen)) => { 148 - let (right_node, next_pos) = parse_level_4(tokens, pos)?; 160 + let (right_node, next_pos) = parse_caret(tokens, pos)?; 149 161 let mut new_node = AstNode::new(Token::Operator(Multiply)); 150 162 new_node.children.push(node); 151 163 new_node.children.push(right_node); ··· 162 174 let last_token = tokens.get(pos - 1); 163 175 match last_token { 164 176 Some(&Token::Number(_)) | Some(&Token::Operator(RightParen)) => { 165 - let (right_node, next_pos) = parse_level_4(tokens, pos)?; 177 + let (right_node, next_pos) = parse_caret(tokens, pos)?; 166 178 let mut new_node = AstNode::new(Token::Operator(Multiply)); 167 179 new_node.children.push(node); 168 180 new_node.children.push(right_node); ··· 179 191 let last_token = tokens.get(pos - 1); 180 192 match last_token { 181 193 Some(&Token::Number(_)) | Some(&Token::Operator(RightParen)) => { 182 - let (right_node, next_pos) = parse_level_4(tokens, pos)?; 194 + let (right_node, next_pos) = parse_caret(tokens, pos)?; 183 195 let mut new_node = AstNode::new(Token::Operator(Multiply)); 184 196 new_node.children.push(node); 185 197 new_node.children.push(right_node); ··· 198 210 Some(&Token::Number(_)) 199 211 | Some(&Token::Constant(_)) 200 212 | Some(&Token::Operator(RightParen)) => { 201 - let (right_node, next_pos) = parse_level_4(tokens, pos)?; 213 + let (right_node, next_pos) = parse_caret(tokens, pos)?; 202 214 let mut new_node = AstNode::new(Token::Operator(Multiply)); 203 215 new_node.children.push(node); 204 216 new_node.children.push(right_node); ··· 217 229 } 218 230 } 219 231 220 - // level 4 precedence: ^ 221 - /// Parse [`Caret`](crate::Operator::Caret) 222 - pub fn parse_level_4(tokens: &[Token], pos: usize) -> Result<(AstNode, usize), String> { 223 - let (mut node, mut pos) = parse_level_5(tokens, pos)?; 232 + /// Parse [`^`](crate::Operator::Caret) 233 + pub fn parse_caret(tokens: &[Token], pos: usize) -> Result<(AstNode, usize), String> { 234 + let (mut node, mut pos) = parse_unary_high(tokens, pos)?; 224 235 loop { 225 236 let token = tokens.get(pos); 226 237 match token { 227 238 Some(&Token::Operator(Caret)) => { 228 - let (right_node, next_pos) = parse_level_5(tokens, pos + 1)?; 239 + let (right_node, next_pos) = parse_unary_high(tokens, pos + 1)?; 229 240 let mut new_node = AstNode::new(token.unwrap().clone()); 230 241 new_node.children.push(node); 231 242 new_node.children.push(right_node); ··· 239 250 } 240 251 } 241 252 242 - // level 5 precedence: - (as in -5, but not 4-5) 243 - /// Parse [`Negative`](Token::Negative) 244 - pub fn parse_level_5(tokens: &[Token], pos: usize) -> Result<(AstNode, usize), String> { 245 - // Here we parse the negative unary operator. If the current token 246 - // is a minus, we wrap the right_node inside a Negative AstNode. 247 - // 248 - // Why doesn't this parse 4-5? First, we will first get a 4. In which case, 249 - // we just return the result of parse_level_6(), which will include the pos 250 - // of +. This will then go down to level 2 and be parsed as a normal minus 251 - // operator. 252 - // The difference is that in other levels, we parse higher priorities 253 - // immediately, while in this one we instead check if the current token 254 - // is a minus, and if not, we then return the higher priority as normal. 253 + /// Parse [`unary -`](Token::Negative) at high precedence (for example in 3^-2) 254 + pub fn parse_unary_high(tokens: &[Token], pos: usize) -> Result<(AstNode, usize), String> { 255 255 let token = tokens.get(pos); 256 256 match token { 257 257 Some(&Token::Operator(Minus)) => { 258 - let (right_node, next_pos) = parse_level_6(tokens, pos + 1)?; 258 + let (right_node, next_pos) = parse_suffix(tokens, pos + 1)?; 259 259 let mut new_node = AstNode::new(Token::Negative); 260 260 new_node.children.push(right_node); 261 261 Ok((new_node, next_pos)) 262 262 } 263 - _ => parse_level_6(tokens, pos), 263 + _ => parse_suffix(tokens, pos), 264 264 } 265 265 } 266 266 267 - // level 6 precedence: !, percent, units attached to values 268 - /// Parse [`Factorial`](crate::UnaryOperator::Factorial) and [`Percent`](crate::UnaryOperator::Percent) 269 - pub fn parse_level_6(tokens: &[Token], pos: usize) -> Result<(AstNode, usize), String> { 270 - let (mut node, mut pos) = parse_level_7(tokens, pos)?; 267 + /// Parse [`!!`](crate::UnaryOperator::Factorial), [`Percent`](crate::UnaryOperator::Percent), units attached to values 268 + pub fn parse_suffix(tokens: &[Token], pos: usize) -> Result<(AstNode, usize), String> { 269 + let (mut node, mut pos) = parse_highest(tokens, pos)?; 271 270 loop { 272 271 let token = tokens.get(pos); 273 272 match token { ··· 298 297 } 299 298 } 300 299 301 - // level 7 precedence: numbers, standalone units, constants, functions, parens 302 - /// Parse [`Number`](Token::Number), 303 - /// [`Unit`](Token::Unit), 304 - /// [`Constant`](Token::Constant), 305 - /// [`FunctionIdentifier`](Token::FunctionIdentifier), 306 - /// [`Paren`](Token::Paren) 307 - pub fn parse_level_7(tokens: &[Token], pos: usize) -> Result<(AstNode, usize), String> { 300 + /// Parse [`Number`](Token::Number), standalone [`Unit`](Token::Unit), [`Constant`](Token::Constant), [`FunctionIdentifier`](Token::FunctionIdentifier), [`Paren`](Token::Paren) 301 + pub fn parse_highest(tokens: &[Token], pos: usize) -> Result<(AstNode, usize), String> { 308 302 let token: &Token = tokens 309 303 .get(pos) 310 304 .ok_or(format!("Unexpected end of input at {}", pos))?; ··· 329 323 Some(&Token::Operator(LeftParen)) => { 330 324 // parse everything inside as you would with normal parentheses, 331 325 // then put it inside an ast node. 332 - parse_level_1(tokens, left_paren_pos + 1).and_then(|(node, next_pos)| { 326 + parse_text_operators(tokens, left_paren_pos + 1).and_then(|(node, next_pos)| { 333 327 if let Some(&Token::Operator(RightParen)) = tokens.get(next_pos) { 334 328 let mut function_node = AstNode::new(token.clone()); 335 329 function_node.children.push(node); ··· 350 344 } 351 345 } 352 346 Token::Operator(LeftParen) => { 353 - parse_level_1(tokens, pos + 1).and_then(|(node, next_pos)| { 347 + parse_text_operators(tokens, pos + 1).and_then(|(node, next_pos)| { 354 348 if let Some(&Token::Operator(RightParen)) = tokens.get(next_pos) { 355 349 let mut paren_node = AstNode::new(Token::Paren); 356 350 paren_node.children.push(node);