[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 unit parsing precedence

Kasper (Jun 27, 2026, 9:12 AM +0200) d7b53185 8438ad2f

+138 -45
+7 -5
README.md
··· 2 2 3 3 calculation + conversion 4 4 5 - cpc parses and evaluates strings of math, with support for units and conversion. 128-bit decimal floating points are used for high accuracy. 5 + cpc calculates complex strings of math, with support for units and conversion. 128-bit decimal floating points are used for high accuracy. 6 6 7 - It also lets you mix units, so for example `1 km - 1m` results in `999 Meter`. 7 + For example `1tonne * 1sqm / 2second^3 / 5ampere` results in `100 volts`. 8 8 9 9 [![Crates.io](https://img.shields.io/crates/v/cpc.svg)](https://crates.io/crates/cpc) 10 10 [![Documentation](https://docs.rs/cpc/badge.svg)](https://docs.rs/cpc) ··· 32 32 ``` 33 33 3 + 4 * 2 34 34 35 - 8 % 3 35 + 6'3" to cm 36 36 37 - (4 + 1)km to light years 37 + 1tonne * 1sqm / 2s^3 / 5a 38 + 39 + (7 % 4)km to light years 38 40 39 41 10m/2s * 5 trillion s 40 42 41 43 1 lightyear * 0.001mm in km2 42 44 43 - 1m/s + 1mi/h in kilometers per h 45 + 1m/s + 1mi/5h in kilometers per h 44 46 45 47 round(sqrt(2)^4)! liters 46 48
+30 -5
src/evaluator.rs
··· 6 6 use crate::lookup::{lookup_factorial, lookup_named_number}; 7 7 use crate::parser::AstNode; 8 8 use crate::units::Unit; 9 + use crate::units::multiply_any; 10 + use crate::units::to_ideal_unit; 9 11 use crate::units::{add, convert, divide, modulo, multiply, pow, subtract}; 10 12 use crate::{Number, Token}; 11 13 use fastnum::decimal::Context; ··· 259 261 } 260 262 } 261 263 Per => { 262 - let left = evaluate_node(left_child)?; 263 - let right = evaluate_node(right_child)?; 264 - Ok(divide(left, right)?) 264 + let mut node = AstNode::new(Token::Operator(Divide)); 265 + node.children = children.to_vec(); 266 + Ok(evaluate_node(&node)?) 265 267 } 266 268 } 267 269 } ··· 273 275 .get(1) 274 276 .ok_or(format!("Token {:?} has no child[1]", token))?; 275 277 let left = evaluate_node(left_child)?; 278 + if matches!(operator, Multiply | Divide) 279 + && let Ok(right) = evaluate_unit(right_child) 280 + { 281 + // multiply/divide by unit, for example `2km`, `2ft/s`, `2*m` 282 + let raw_result = match operator { 283 + Multiply => multiply_any(left, Number::with_unit(d!(1), right))?, 284 + Divide => divide(left, Number::with_unit(d!(1), right))?, 285 + _ => panic!(), 286 + }; 287 + let ideal_result = to_ideal_unit(raw_result.clone()); 288 + // If the ideal unit has a different category/exponent, then we use it. 289 + // For example, we want to keep `v*a` -> `watt`, but not `ft/s` -> `cm/s`. 290 + if raw_result.unit.len() != ideal_result.unit.len() { 291 + return Ok(ideal_result); 292 + } 293 + for (ideal, raw) in ideal_result.unit.iter().zip(raw_result.unit.iter()) { 294 + if ideal.0.category() != raw.0.category() || ideal.1 != raw.1 { 295 + return Ok(ideal_result); 296 + } 297 + } 298 + return Ok(raw_result); 299 + } 300 + 276 301 let right = evaluate_node(right_child)?; 277 302 match operator { 278 303 Plus => Ok(add(left, right)?), 279 304 Minus => Ok(subtract(left, right)?), 280 - Multiply => Ok(multiply(left, right)?), 281 - Divide => Ok(divide(left, right)?), 305 + Multiply => Ok(to_ideal_unit(multiply(left, right)?)), 306 + Divide => Ok(to_ideal_unit(divide(left, right)?)), 282 307 Modulo => Ok(modulo(left, right)?), 283 308 Caret => Ok(pow(left, right)?), 284 309 _ => Err(format!("Unexpected operator {:?}", operator)),
+74 -15
src/parser.rs
··· 4 4 use crate::UnaryOperator::*; 5 5 use crate::units::Unit; 6 6 use crate::units::Unit::{Foot, Inch}; 7 + use core::fmt; 7 8 8 - #[derive(Debug)] 9 9 /// A struct with a [`Token`](AstNode::token) and [`AstNode`] [`children`](AstNode::children) 10 + #[derive(Clone)] 10 11 pub struct AstNode { 11 12 /// The children of the [`AstNode`] 12 13 pub children: Vec<AstNode>, 13 14 /// The token of the [`AstNode`] 14 15 pub token: Token, 15 16 } 16 - 17 17 impl AstNode { 18 18 pub const fn new(token: Token) -> AstNode { 19 19 AstNode { 20 20 children: Vec::new(), 21 21 token, 22 22 } 23 + } 24 + } 25 + impl fmt::Debug for AstNode { 26 + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 27 + let mut d = f.debug_struct("AstNode"); 28 + d.field("token", &self.token); 29 + if self.children.is_empty() { 30 + d.field("children", &self.children); 31 + } else { 32 + for (i, child) in self.children.iter().enumerate() { 33 + d.field(&i.to_string(), child); 34 + } 35 + } 36 + d.finish() 23 37 } 24 38 } 25 39 ··· 140 154 } 141 155 } 142 156 143 - let (mut node, mut pos) = parse_caret(tokens, pos)?; 157 + let (mut node, mut pos) = parse_implicit_unit_mult(tokens, pos)?; 144 158 145 159 loop { 146 160 let token = tokens.get(pos); ··· 149 163 | Some(&Token::Operator(Divide)) 150 164 | Some(&Token::TextOperator(Per)) 151 165 | Some(&Token::Operator(Modulo)) => { 152 - let (right_node, next_pos) = parse_caret(tokens, pos + 1)?; 166 + let (right_node, next_pos) = parse_implicit_unit_mult(tokens, pos + 1)?; 153 167 let mut new_node = AstNode::new(token.unwrap().clone()); 154 168 new_node.children.push(node); 155 169 new_node.children.push(right_node); ··· 169 183 let last_token = tokens.get(pos - 1); 170 184 match last_token { 171 185 Some(&Token::Constant(_)) | Some(&Token::Operator(RightParen)) => { 172 - let (right_node, next_pos) = parse_caret(tokens, pos)?; 186 + let (right_node, next_pos) = parse_implicit_unit_mult(tokens, pos)?; 173 187 let mut new_node = AstNode::new(Token::Operator(Multiply)); 174 188 new_node.children.push(node); 175 189 new_node.children.push(right_node); ··· 186 200 let last_token = tokens.get(pos - 1); 187 201 match last_token { 188 202 Some(&Token::Number(_)) | Some(&Token::Operator(RightParen)) => { 189 - let (right_node, next_pos) = parse_caret(tokens, pos)?; 203 + let (right_node, next_pos) = parse_implicit_unit_mult(tokens, pos)?; 190 204 let mut new_node = AstNode::new(Token::Operator(Multiply)); 191 205 new_node.children.push(node); 192 206 new_node.children.push(right_node); ··· 203 217 let last_token = tokens.get(pos - 1); 204 218 match last_token { 205 219 Some(&Token::Number(_)) | Some(&Token::Operator(RightParen)) => { 206 - let (right_node, next_pos) = parse_caret(tokens, pos)?; 220 + let (right_node, next_pos) = parse_implicit_unit_mult(tokens, pos)?; 207 221 let mut new_node = AstNode::new(Token::Operator(Multiply)); 208 222 new_node.children.push(node); 209 223 new_node.children.push(right_node); ··· 222 236 Some(&Token::Number(_)) 223 237 | Some(&Token::Constant(_)) 224 238 | Some(&Token::Operator(RightParen)) => { 225 - let (right_node, next_pos) = parse_caret(tokens, pos)?; 239 + let (right_node, next_pos) = parse_implicit_unit_mult(tokens, pos)?; 226 240 let mut new_node = AstNode::new(Token::Operator(Multiply)); 227 241 new_node.children.push(node); 228 242 new_node.children.push(right_node); ··· 241 255 } 242 256 } 243 257 244 - /// Parse [`^`](crate::Operator::Caret) 258 + /// Parse numbers with units (for example `2km`) 259 + pub fn parse_implicit_unit_mult(tokens: &[Token], pos: usize) -> Result<(AstNode, usize), String> { 260 + let (mut node, mut pos) = parse_unit_mult_level(tokens, pos)?; 261 + loop { 262 + let token = tokens.get(pos); 263 + match token { 264 + // maybe this can be in parse_mult_level instead? 265 + Some(&Token::Unit(_)) => { 266 + // implicit unit multiplication, e.g. `ft s` 267 + let (right_node, next_pos) = parse_unit_mult_level(tokens, pos)?; 268 + let mut new_node = AstNode::new(Token::Operator(Multiply)); 269 + new_node.children.push(node); 270 + new_node.children.push(right_node); 271 + node = new_node; 272 + pos = next_pos; 273 + } 274 + _ => { 275 + return Ok((node, pos)); 276 + } 277 + } 278 + } 279 + } 280 + 281 + /// Parse multiplication and division with units (for example `km/h` or `km*km`) 282 + pub fn parse_unit_mult_level(tokens: &[Token], pos: usize) -> Result<(AstNode, usize), String> { 283 + let (mut node, mut pos) = parse_caret(tokens, pos)?; 284 + loop { 285 + let token = tokens.get(pos); 286 + match token { 287 + Some(&Token::Operator(Multiply)) 288 + | Some(&Token::Operator(Divide)) 289 + | Some(&Token::TextOperator(Per)) => { 290 + // only consume if the right side is a unit 291 + if let Some(&Token::Unit(_)) = tokens.get(pos + 1) { 292 + let (right_node, next_pos) = parse_caret(tokens, pos + 1)?; 293 + let mut new_node = AstNode::new(token.unwrap().clone()); 294 + new_node.children.push(node); 295 + new_node.children.push(right_node); 296 + node = new_node; 297 + pos = next_pos; 298 + } else { 299 + return Ok((node, pos)); 300 + } 301 + } 302 + _ => { 303 + return Ok((node, pos)); 304 + } 305 + } 306 + } 307 + } 308 + 309 + /// Parse [`^`](crate::Operator::Caret), unit suffix (for example `2km` or `2km^2`) 245 310 pub fn parse_caret(tokens: &[Token], pos: usize) -> Result<(AstNode, usize), String> { 246 311 let (mut node, mut pos) = parse_unary_high(tokens, pos)?; 247 312 loop { ··· 292 357 new_node.children.push(node); 293 358 node = new_node; 294 359 pos += 1; 295 - } 296 - Some(Token::Unit(_unit)) => { 297 - // We won't allow units to repeat, like "1min min", so we end the loop if it's found. 298 - let mut new_node = AstNode::new(token.unwrap().clone()); 299 - new_node.children.push(node); 300 - return Ok((new_node, pos + 1)); 301 360 } 302 361 _ => { 303 362 // let's say we parse 1+2. parse_level_7 then returns 1, and token
+27 -20
src/units.rs
··· 703 703 if left.contains_primitive(Temperature) || right.contains_primitive(Temperature) { 704 704 Err(format!("Cannot multiply {} and {}", left, right)) 705 705 } else { 706 - let mut new_number = left; 707 - new_number.value *= right.value; 708 - for (r_unit, r_exp) in right.unit { 709 - let existing = new_number.unit.iter_mut().find(|(u, _)| u == &r_unit); 710 - match existing { 711 - Some(existing) => existing.1 += r_exp, 712 - None => new_number.unit.push((r_unit, r_exp)), 713 - } 706 + multiply_any(left, right) 707 + } 708 + } 709 + 710 + pub(crate) fn multiply_any(left: Number, right: Number) -> Result<Number, String> { 711 + let mut new_number = left; 712 + new_number.value *= right.value; 713 + for (r_unit, r_exp) in right.unit { 714 + let existing = new_number.unit.iter_mut().find(|(u, _)| u == &r_unit); 715 + match existing { 716 + Some(existing) => existing.1 += r_exp, 717 + None => new_number.unit.push((r_unit, r_exp)), 714 718 } 715 - let new_number = to_ideal_unit(new_number); 716 - Ok(new_number) 717 719 } 720 + Ok(new_number) 718 721 } 719 722 720 723 /// Divide a [`Number`] by another [`Number`]. ··· 726 729 if left.contains_primitive(Temperature) || right.contains_primitive(Temperature) { 727 730 Err(format!("Cannot divide {} by {}", left, right)) 728 731 } else { 729 - let mut new_number = left; 730 - new_number.value = new_number.value / right.value; 731 - for (r_unit, r_exp) in right.unit { 732 - let existing = new_number.unit.iter_mut().find(|(u, _)| u == &r_unit); 733 - match existing { 734 - Some(existing) => existing.1 -= r_exp, 735 - None => new_number.unit.push((r_unit, -r_exp)), 736 - } 732 + divide_any(left, right) 733 + } 734 + } 735 + 736 + pub fn divide_any(left: Number, right: Number) -> Result<Number, String> { 737 + let mut new_number = left; 738 + new_number.value = new_number.value / right.value; 739 + for (r_unit, r_exp) in right.unit { 740 + let existing = new_number.unit.iter_mut().find(|(u, _)| u == &r_unit); 741 + match existing { 742 + Some(existing) => existing.1 -= r_exp, 743 + None => new_number.unit.push((r_unit, -r_exp)), 737 744 } 738 - let new_number = to_ideal_unit(new_number); 739 - Ok(new_number) 740 745 } 746 + Ok(new_number) 741 747 } 742 748 743 749 /// Modulo a [`Number`] by another [`Number`]. ··· 1199 1205 1200 1206 #[test] 1201 1207 fn test_unit_evals() { 1208 + eval_test("100kg*sqm / 2s^2", "50j"); 1202 1209 eval_test("3.6km/1h", "3.6 kph"); 1203 1210 eval_test("0.3048 m/s to ft/s", "1 ft/s"); 1204 1211 eval_test("1.609344 km/1h to mph", "1 mph");