[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.

Ambiguous units resolution

Kasper (Jul 1, 2026, 5:47 AM +0200) de8493f0 f5571d28

+1040 -95
+1
CHANGELOG.md
··· 1 1 # Changelog 2 2 3 3 ## Next 4 + - Add ambiguous unit resolution depending on context and user locale. `pound` can now become `GBP` or `lbs` 4 5 - Add currency symbols like `€` 5 6 - Add `as` and `into` keywords for unit conversion 6 7 - Fix some units not being simplified
+106
Cargo.lock
··· 168 168 "regex", 169 169 "reqwest", 170 170 "serde", 171 + "serde-wasm-bindgen", 171 172 "serde_json", 173 + "serial_test", 174 + "sys-locale", 172 175 "unicode-segmentation", 173 176 "wasm-bindgen", 174 177 "web-time", ··· 260 263 version = "0.3.32" 261 264 source = "registry+https://github.com/rust-lang/crates.io-index" 262 265 checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" 266 + 267 + [[package]] 268 + name = "futures-executor" 269 + version = "0.3.32" 270 + source = "registry+https://github.com/rust-lang/crates.io-index" 271 + checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" 272 + dependencies = [ 273 + "futures-core", 274 + "futures-task", 275 + "futures-util", 276 + ] 263 277 264 278 [[package]] 265 279 name = "futures-io" ··· 654 668 checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" 655 669 656 670 [[package]] 671 + name = "lock_api" 672 + version = "0.4.14" 673 + source = "registry+https://github.com/rust-lang/crates.io-index" 674 + checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" 675 + dependencies = [ 676 + "scopeguard", 677 + ] 678 + 679 + [[package]] 657 680 name = "log" 658 681 version = "0.4.33" 659 682 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 717 740 version = "0.2.1" 718 741 source = "registry+https://github.com/rust-lang/crates.io-index" 719 742 checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" 743 + 744 + [[package]] 745 + name = "parking_lot" 746 + version = "0.12.5" 747 + source = "registry+https://github.com/rust-lang/crates.io-index" 748 + checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" 749 + dependencies = [ 750 + "lock_api", 751 + "parking_lot_core", 752 + ] 753 + 754 + [[package]] 755 + name = "parking_lot_core" 756 + version = "0.9.12" 757 + source = "registry+https://github.com/rust-lang/crates.io-index" 758 + checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" 759 + dependencies = [ 760 + "cfg-if", 761 + "libc", 762 + "redox_syscall", 763 + "smallvec", 764 + "windows-link", 765 + ] 720 766 721 767 [[package]] 722 768 name = "percent-encoding" ··· 858 904 ] 859 905 860 906 [[package]] 907 + name = "redox_syscall" 908 + version = "0.5.18" 909 + source = "registry+https://github.com/rust-lang/crates.io-index" 910 + checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" 911 + dependencies = [ 912 + "bitflags", 913 + ] 914 + 915 + [[package]] 861 916 name = "regex" 862 917 version = "1.12.4" 863 918 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1057 1112 ] 1058 1113 1059 1114 [[package]] 1115 + name = "scopeguard" 1116 + version = "1.2.0" 1117 + source = "registry+https://github.com/rust-lang/crates.io-index" 1118 + checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1119 + 1120 + [[package]] 1060 1121 name = "security-framework" 1061 1122 version = "3.7.0" 1062 1123 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1105 1166 ] 1106 1167 1107 1168 [[package]] 1169 + name = "serde-wasm-bindgen" 1170 + version = "0.6.5" 1171 + source = "registry+https://github.com/rust-lang/crates.io-index" 1172 + checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" 1173 + dependencies = [ 1174 + "js-sys", 1175 + "serde", 1176 + "wasm-bindgen", 1177 + ] 1178 + 1179 + [[package]] 1108 1180 name = "serde_core" 1109 1181 version = "1.0.228" 1110 1182 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1138 1210 ] 1139 1211 1140 1212 [[package]] 1213 + name = "serial_test" 1214 + version = "3.5.0" 1215 + source = "registry+https://github.com/rust-lang/crates.io-index" 1216 + checksum = "699f4197115b8a7e7ff19c9a315a4bd6fffec26cc4626ef45ecaea389e081c6d" 1217 + dependencies = [ 1218 + "futures-executor", 1219 + "futures-util", 1220 + "log", 1221 + "once_cell", 1222 + "parking_lot", 1223 + "serial_test_derive", 1224 + ] 1225 + 1226 + [[package]] 1227 + name = "serial_test_derive" 1228 + version = "3.5.0" 1229 + source = "registry+https://github.com/rust-lang/crates.io-index" 1230 + checksum = "94e153fc76e1c6a068703d6d29c508a0b15c061c4b7e43da59cc097bc342673c" 1231 + dependencies = [ 1232 + "proc-macro2", 1233 + "quote", 1234 + "syn", 1235 + ] 1236 + 1237 + [[package]] 1141 1238 name = "shlex" 1142 1239 version = "2.0.1" 1143 1240 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1222 1319 "proc-macro2", 1223 1320 "quote", 1224 1321 "syn", 1322 + ] 1323 + 1324 + [[package]] 1325 + name = "sys-locale" 1326 + version = "0.3.2" 1327 + source = "registry+https://github.com/rust-lang/crates.io-index" 1328 + checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" 1329 + dependencies = [ 1330 + "libc", 1225 1331 ] 1226 1332 1227 1333 [[package]]
+6 -1
Cargo.toml
··· 25 25 fastnum = { version = "0.7", features = ["serde"] } 26 26 unicode-segmentation = "1.13" 27 27 web-time = "1.1.0" 28 - reqwest = { version = "0.13", features = ["blocking", "json"] } 29 28 serde = { version = "1.0", features = ["derive"] } 30 29 serde_json = "1.0" 31 30 31 + [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 32 + reqwest = { version = "0.13", features = ["blocking", "json"] } 33 + sys-locale = "0.3.2" 34 + 32 35 [target.'cfg(target_arch = "wasm32")'.dependencies] 33 36 console_error_panic_hook = "0.1.7" 34 37 wasm-bindgen = "0.2" 35 38 js-sys = "0.3" 39 + serde-wasm-bindgen = "0.6.5" 36 40 37 41 [dev-dependencies] 38 42 regex = "1.11" 43 + serial_test = "3.5.0" 39 44 40 45 [lints.clippy] 41 46 comparison_chain = "allow"
+51 -4
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::UnitType; 9 10 use crate::units::multiply_any; 10 11 use crate::units::to_ideal_unit; 11 12 use crate::units::{add, convert, divide, modulo, multiply, pow, subtract}; ··· 14 15 use fastnum::{D128, dec128 as d, decimal::RoundingMode}; 15 16 16 17 /// Evaluate an [`AstNode`] into a [`Number`] 17 - pub fn evaluate(ast: &AstNode) -> Result<Number, String> { 18 - let answer = evaluate_node(ast)?; 18 + pub fn evaluate(ast: &mut AstNode) -> Result<Number, String> { 19 + resolve_ambiguities(ast, None); 20 + let answer = evaluate_node(&ast)?; 19 21 Ok(answer) 20 22 } 21 23 ··· 54 56 fn replace_without_updating_signals(old: D128, new: D128) -> D128 { 55 57 let new_without_signal = D128::parse_str(&new.to_string(), Context::default()); 56 58 old - old + new_without_signal 59 + } 60 + 61 + fn find_concrete_category(ast: &AstNode) -> Option<UnitType> { 62 + if let Token::Unit(units) = &ast.token { 63 + if let Some((u, _)) = units.iter().find(|(u, _)| !matches!(u, Unit::Ambiguity(_))) { 64 + return Some(u.category()); 65 + } 66 + } 67 + ast.children.iter().find_map(find_concrete_category) 68 + } 69 + 70 + fn resolve_ambiguities(ast: &mut AstNode, hint: Option<UnitType>) { 71 + let mut child_hints = vec![hint; ast.children.len()]; 72 + 73 + if let Token::TextOperator(To) = &ast.token { 74 + if ast.children.len() == 2 { 75 + let left_cat = find_concrete_category(&ast.children[0]); 76 + let right_cat = find_concrete_category(&ast.children[1]); 77 + child_hints[0] = right_cat.or(hint); // left side hinted by right 78 + child_hints[1] = left_cat.or(hint); // right side hinted by left 79 + } 80 + } 81 + 82 + for (child, h) in ast.children.iter_mut().zip(child_hints) { 83 + resolve_ambiguities(child, h); 84 + } 85 + 86 + if let Token::Unit(units) = &mut ast.token { 87 + for (unit, _) in units.iter_mut() { 88 + if let Unit::Ambiguity(amb) = unit { 89 + *unit = hint 90 + .and_then(|cat| amb.candidates.iter().find(|c| c.category() == cat).copied()) 91 + .unwrap_or(*amb.fallback); 92 + } 93 + } 94 + } 57 95 } 58 96 59 97 fn evaluate_unit(ast: &AstNode) -> Result<Vec<(Unit, isize)>, String> { ··· 315 353 316 354 #[cfg(test)] 317 355 mod tests { 356 + use crate::{Settings, eval}; 357 + use serial_test::serial; 318 358 use std::str::FromStr; 319 - 320 - use crate::eval; 321 359 322 360 #[track_caller] 323 361 fn eval_test(input: &str, expected: &str) { ··· 410 448 "1 EUR/gallon to NOK/liter", 411 449 "≈ 2.98089102160411090430525272544562882356 NOK / liter", 412 450 ); 451 + } 452 + 453 + #[test] 454 + #[serial] 455 + fn test_ambiguous_evals() { 456 + Settings::write().locale = "en-GB".to_string(); 457 + results_eq("1pound", "1gbp"); 458 + Settings::write().locale = "nb-NO".to_string(); 459 + results_eq("1pound", "1lbs"); 413 460 } 414 461 415 462 #[test]
+788 -87
src/lexer.rs
··· 7 7 use crate::Token; 8 8 use crate::UnaryOperator::*; 9 9 use crate::currency::currency_code_to_unit; 10 + use crate::get_region; 11 + use crate::units::Ambiguity; 10 12 use crate::units::Unit::*; 11 13 use fastnum::D128; 12 14 use fastnum::decimal::Context; ··· 404 406 return Ok(()); 405 407 } 406 408 }, 407 - false => Token::unit(Pound), 409 + false => Token::unit(Ambiguity(Ambiguity { 410 + string: "pound", 411 + candidates: &[Pound, GBP], 412 + fallback: match get_region().as_str() { 413 + "GB" => &GBP, 414 + _ => &Pound, 415 + }, 416 + })), 408 417 }, 409 418 "stone" | "stones" => Token::unit(Stone), 410 419 "st" | "ton" | "tons" => Token::unit(ShortTon), ··· 816 825 #[cfg(test)] 817 826 mod tests { 818 827 use super::*; 819 - use crate::numtok; 828 + use crate::{numtok, units::Ambiguity}; 820 829 use regex::Regex; 821 830 822 831 #[test] ··· 825 834 let strip_afterdigit_spacing = Regex::new(r"(\d) ").unwrap(); 826 835 let nonplural_data_units = Regex::new(r"(bit|byte)s").unwrap(); 827 836 828 - let run_lex = |input: &str, expected_tokens: Vec<Token>| { 837 + #[track_caller] 838 + fn run_lex( 839 + input: &str, 840 + expected_tokens: Vec<Token>, 841 + strip_operator_spacing: &Regex, 842 + strip_afterdigit_spacing: &Regex, 843 + ) { 829 844 let tokens = match lex(input, false) { 830 845 Ok(tokens) => tokens, 831 846 Err(e) => { ··· 836 851 "run_lex assertion failed.\n input: {}\n left: {:?}\n right: {:?}", 837 852 input, expected_tokens, tokens 838 853 ); 839 - assert!(tokens == expected_tokens, "{info_msg}"); 854 + assert_eq!(tokens, expected_tokens); 840 855 841 856 // Prove we can handle multiple spaces wherever we handle a single space 842 857 let input_extra_spaces = input.replace(" ", " "); 843 858 let tokens_extra_spaces = lex(&input_extra_spaces, false).unwrap(); 844 - assert!(tokens_extra_spaces == expected_tokens, "{info_msg}"); 859 + assert_eq!(tokens_extra_spaces, expected_tokens, "{info_msg}"); 845 860 846 861 // Prove we don't need spaces around operators 847 862 let input_stripped_spaces = strip_operator_spacing.replace_all(input, "$1"); 848 863 let tokens_stripped_spaces = lex(&input_stripped_spaces, false).unwrap(); 849 - assert!(tokens_stripped_spaces == expected_tokens, "{info_msg}"); 864 + assert_eq!(tokens_stripped_spaces, expected_tokens, "{info_msg}"); 850 865 851 866 // Prove we don't need a space after a digit 852 867 let input_afterdigit_stripped_spaces = 853 868 strip_afterdigit_spacing.replace_all(input, "$1"); 854 869 let tokens_afterdigit_stripped_spaces = 855 870 lex(&input_afterdigit_stripped_spaces, false).unwrap(); 856 - assert!( 857 - tokens_afterdigit_stripped_spaces == expected_tokens, 871 + assert_eq!( 872 + tokens_afterdigit_stripped_spaces, expected_tokens, 858 873 "{info_msg}" 859 874 ); 860 - }; 875 + } 861 876 862 877 let run_datarate_lex = |input: &str, expected_tokens: Vec<Token>| { 863 - run_lex(input, (*expected_tokens).to_vec()); 878 + run_lex( 879 + input, 880 + (*expected_tokens).to_vec(), 881 + &strip_operator_spacing, 882 + &strip_afterdigit_spacing, 883 + ); 864 884 865 885 // Prove plural and non-plural data units behave identically 866 886 let input_nonplural_units = nonplural_data_units.replace_all(input, "$1"); ··· 880 900 Token::Operator(Multiply), 881 901 numtok!(2), 882 902 ], 903 + &strip_operator_spacing, 904 + &strip_afterdigit_spacing, 883 905 ); 884 - run_lex("0.5 marathon", vec![numtok!(0.5), Token::unit(Marathon)]); 885 - run_lex("100 nmi", vec![numtok!(100), Token::unit(NauticalMile)]); 906 + run_lex( 907 + "0.5 marathon", 908 + vec![numtok!(0.5), Token::unit(Marathon)], 909 + &strip_operator_spacing, 910 + &strip_afterdigit_spacing, 911 + ); 912 + run_lex( 913 + "100 nmi", 914 + vec![numtok!(100), Token::unit(NauticalMile)], 915 + &strip_operator_spacing, 916 + &strip_afterdigit_spacing, 917 + ); 886 918 run_lex( 887 919 "101 nautical miles", 888 920 vec![numtok!(101), Token::unit(NauticalMile)], 921 + &strip_operator_spacing, 922 + &strip_afterdigit_spacing, 889 923 ); 890 - run_lex("2 lightyears", vec![numtok!(2), Token::unit(LightYear)]); 891 - run_lex("1 light year", vec![numtok!(1), Token::unit(LightYear)]); 892 - run_lex("10 lightsec", vec![numtok!(10), Token::unit(LightSecond)]); 893 - run_lex("12 light secs", vec![numtok!(12), Token::unit(LightSecond)]); 924 + run_lex( 925 + "2 lightyears", 926 + vec![numtok!(2), Token::unit(LightYear)], 927 + &strip_operator_spacing, 928 + &strip_afterdigit_spacing, 929 + ); 930 + run_lex( 931 + "1 light year", 932 + vec![numtok!(1), Token::unit(LightYear)], 933 + &strip_operator_spacing, 934 + &strip_afterdigit_spacing, 935 + ); 936 + run_lex( 937 + "10 lightsec", 938 + vec![numtok!(10), Token::unit(LightSecond)], 939 + &strip_operator_spacing, 940 + &strip_afterdigit_spacing, 941 + ); 942 + run_lex( 943 + "12 light secs", 944 + vec![numtok!(12), Token::unit(LightSecond)], 945 + &strip_operator_spacing, 946 + &strip_afterdigit_spacing, 947 + ); 894 948 run_lex( 895 949 "33.3 square meters", 896 950 vec![numtok!(33.3), Token::unit(SquareMeter)], 951 + &strip_operator_spacing, 952 + &strip_afterdigit_spacing, 897 953 ); 898 - run_lex("54 m2", vec![numtok!(54), Token::unit(SquareMeter)]); 899 - run_lex("87 sq miles", vec![numtok!(87), Token::unit(SquareMile)]); 900 - run_lex("500 feet2", vec![numtok!(500), Token::unit(SquareFoot)]); 901 - run_lex("500 feet²", vec![numtok!(500), Token::unit(SquareFoot)]); 902 - run_lex("4 cubic metres", vec![numtok!(4), Token::unit(CubicMeter)]); 954 + run_lex( 955 + "54 m2", 956 + vec![numtok!(54), Token::unit(SquareMeter)], 957 + &strip_operator_spacing, 958 + &strip_afterdigit_spacing, 959 + ); 960 + run_lex( 961 + "87 sq miles", 962 + vec![numtok!(87), Token::unit(SquareMile)], 963 + &strip_operator_spacing, 964 + &strip_afterdigit_spacing, 965 + ); 966 + run_lex( 967 + "500 feet2", 968 + vec![numtok!(500), Token::unit(SquareFoot)], 969 + &strip_operator_spacing, 970 + &strip_afterdigit_spacing, 971 + ); 972 + run_lex( 973 + "500 feet²", 974 + vec![numtok!(500), Token::unit(SquareFoot)], 975 + &strip_operator_spacing, 976 + &strip_afterdigit_spacing, 977 + ); 978 + run_lex( 979 + "4 cubic metres", 980 + vec![numtok!(4), Token::unit(CubicMeter)], 981 + &strip_operator_spacing, 982 + &strip_afterdigit_spacing, 983 + ); 903 984 run_lex( 904 985 "34 cubic feet + 23 cubic yards", 905 986 vec![ ··· 909 990 numtok!(23), 910 991 Token::unit(CubicYard), 911 992 ], 993 + &strip_operator_spacing, 994 + &strip_afterdigit_spacing, 912 995 ); 913 996 run_lex( 914 997 "66 inches3 + 65 millimetre³", ··· 919 1002 numtok!(65), 920 1003 Token::unit(CubicMillimeter), 921 1004 ], 1005 + &strip_operator_spacing, 1006 + &strip_afterdigit_spacing, 922 1007 ); 923 1008 run_lex( 924 1009 "66 inches³ + 65 millimetre3", ··· 929 1014 numtok!(65), 930 1015 Token::unit(CubicMillimeter), 931 1016 ], 1017 + &strip_operator_spacing, 1018 + &strip_afterdigit_spacing, 1019 + ); 1020 + run_lex( 1021 + "42 millilitres", 1022 + vec![numtok!(42), Token::unit(Milliliter)], 1023 + &strip_operator_spacing, 1024 + &strip_afterdigit_spacing, 1025 + ); 1026 + run_lex( 1027 + "3 tbs", 1028 + vec![numtok!(3), Token::unit(Tablespoon)], 1029 + &strip_operator_spacing, 1030 + &strip_afterdigit_spacing, 1031 + ); 1032 + run_lex( 1033 + "6 floz", 1034 + vec![numtok!(6), Token::unit(FluidOunce)], 1035 + &strip_operator_spacing, 1036 + &strip_afterdigit_spacing, 1037 + ); 1038 + run_lex( 1039 + "6 fl oz", 1040 + vec![numtok!(6), Token::unit(FluidOunce)], 1041 + &strip_operator_spacing, 1042 + &strip_afterdigit_spacing, 1043 + ); 1044 + run_lex( 1045 + "6 fluid ounces", 1046 + vec![numtok!(6), Token::unit(FluidOunce)], 1047 + &strip_operator_spacing, 1048 + &strip_afterdigit_spacing, 1049 + ); 1050 + run_lex( 1051 + "3 oil barrels", 1052 + vec![numtok!(3), Token::unit(OilBarrel)], 1053 + &strip_operator_spacing, 1054 + &strip_afterdigit_spacing, 932 1055 ); 933 - run_lex("42 millilitres", vec![numtok!(42), Token::unit(Milliliter)]); 934 - run_lex("3 tbs", vec![numtok!(3), Token::unit(Tablespoon)]); 935 - run_lex("6 floz", vec![numtok!(6), Token::unit(FluidOunce)]); 936 - run_lex("6 fl oz", vec![numtok!(6), Token::unit(FluidOunce)]); 937 - run_lex("6 fluid ounces", vec![numtok!(6), Token::unit(FluidOunce)]); 938 - run_lex("3 oil barrels", vec![numtok!(3), Token::unit(OilBarrel)]); 939 - run_lex("67 kg", vec![numtok!(67), Token::unit(Kilogram)]); 940 - run_lex("34 oz", vec![numtok!(34), Token::unit(Ounce)]); 941 - run_lex("34 ounces", vec![numtok!(34), Token::unit(Ounce)]); 942 - run_lex("210 lb", vec![numtok!(210), Token::unit(Pound)]); 943 - run_lex("210 lbs", vec![numtok!(210), Token::unit(Pound)]); 944 - run_lex("210 pound", vec![numtok!(210), Token::unit(Pound)]); 945 - run_lex("210 pounds", vec![numtok!(210), Token::unit(Pound)]); 1056 + run_lex( 1057 + "67 kg", 1058 + vec![numtok!(67), Token::unit(Kilogram)], 1059 + &strip_operator_spacing, 1060 + &strip_afterdigit_spacing, 1061 + ); 1062 + run_lex( 1063 + "34 oz", 1064 + vec![numtok!(34), Token::unit(Ounce)], 1065 + &strip_operator_spacing, 1066 + &strip_afterdigit_spacing, 1067 + ); 1068 + run_lex( 1069 + "34 ounces", 1070 + vec![numtok!(34), Token::unit(Ounce)], 1071 + &strip_operator_spacing, 1072 + &strip_afterdigit_spacing, 1073 + ); 1074 + run_lex( 1075 + "210 lb", 1076 + vec![numtok!(210), Token::unit(Pound)], 1077 + &strip_operator_spacing, 1078 + &strip_afterdigit_spacing, 1079 + ); 1080 + run_lex( 1081 + "210 lbs", 1082 + vec![numtok!(210), Token::unit(Pound)], 1083 + &strip_operator_spacing, 1084 + &strip_afterdigit_spacing, 1085 + ); 1086 + run_lex( 1087 + "210 pound", 1088 + vec![ 1089 + numtok!(210), 1090 + Token::unit(Ambiguity(Ambiguity { 1091 + candidates: &[Pound, GBP], 1092 + string: "pound", 1093 + fallback: &Pound, 1094 + })), 1095 + ], 1096 + &strip_operator_spacing, 1097 + &strip_afterdigit_spacing, 1098 + ); 1099 + run_lex( 1100 + "210 pounds", 1101 + vec![ 1102 + numtok!(210), 1103 + Token::unit(Ambiguity(Ambiguity { 1104 + candidates: &[Pound, GBP], 1105 + string: "pound", 1106 + fallback: &Pound, 1107 + })), 1108 + ], 1109 + &strip_operator_spacing, 1110 + &strip_afterdigit_spacing, 1111 + ); 946 1112 run_lex( 947 1113 "210 pounds-force", 948 1114 vec![numtok!(210), Token::LexerKeyword(PoundForce)], 1115 + &strip_operator_spacing, 1116 + &strip_afterdigit_spacing, 949 1117 ); 950 - run_lex("3 ton", vec![numtok!(3), Token::unit(ShortTon)]); 951 - run_lex("3 short tons", vec![numtok!(3), Token::unit(ShortTon)]); 952 - run_lex("4 lt", vec![numtok!(4), Token::unit(LongTon)]); 953 - run_lex("4 long tonnes", vec![numtok!(4), Token::unit(LongTon)]); 1118 + run_lex( 1119 + "3 ton", 1120 + vec![numtok!(3), Token::unit(ShortTon)], 1121 + &strip_operator_spacing, 1122 + &strip_afterdigit_spacing, 1123 + ); 1124 + run_lex( 1125 + "3 short tons", 1126 + vec![numtok!(3), Token::unit(ShortTon)], 1127 + &strip_operator_spacing, 1128 + &strip_afterdigit_spacing, 1129 + ); 1130 + run_lex( 1131 + "4 lt", 1132 + vec![numtok!(4), Token::unit(LongTon)], 1133 + &strip_operator_spacing, 1134 + &strip_afterdigit_spacing, 1135 + ); 1136 + run_lex( 1137 + "4 long tonnes", 1138 + vec![numtok!(4), Token::unit(LongTon)], 1139 + &strip_operator_spacing, 1140 + &strip_afterdigit_spacing, 1141 + ); 954 1142 run_datarate_lex("1 bit", vec![numtok!(1), Token::unit(Bit)]); 955 1143 run_datarate_lex("8 bits", vec![numtok!(8), Token::unit(Bit)]); 956 1144 run_datarate_lex("63 kilobits", vec![numtok!(63), Token::unit(Kilobit)]); ··· 993 1181 run_datarate_lex("0.73 exbibytes", vec![numtok!(0.73), Token::unit(Exbibyte)]); 994 1182 run_datarate_lex("0.49 zebibytes", vec![numtok!(0.49), Token::unit(Zebibyte)]); 995 1183 run_datarate_lex("0.23 yobibytes", vec![numtok!(0.23), Token::unit(Yobibyte)]); 996 - run_lex("432 Bps", vec![numtok!(432), Token::unit(BytesPerSecond)]); 1184 + run_lex( 1185 + "432 Bps", 1186 + vec![numtok!(432), Token::unit(BytesPerSecond)], 1187 + &strip_operator_spacing, 1188 + &strip_afterdigit_spacing, 1189 + ); 997 1190 run_lex( 998 1191 "56 kBps", 999 1192 vec![numtok!(56), Token::unit(KilobytesPerSecond)], 1193 + &strip_operator_spacing, 1194 + &strip_afterdigit_spacing, 1195 + ); 1196 + run_lex( 1197 + "432 bps", 1198 + vec![numtok!(432), Token::unit(BitsPerSecond)], 1199 + &strip_operator_spacing, 1200 + &strip_afterdigit_spacing, 1201 + ); 1202 + run_lex( 1203 + "56 kbps", 1204 + vec![numtok!(56), Token::unit(KilobitsPerSecond)], 1205 + &strip_operator_spacing, 1206 + &strip_afterdigit_spacing, 1000 1207 ); 1001 - run_lex("432 bps", vec![numtok!(432), Token::unit(BitsPerSecond)]); 1002 - run_lex("56 kbps", vec![numtok!(56), Token::unit(KilobitsPerSecond)]); 1003 - run_lex("12 mbps", vec![numtok!(12), Token::unit(MegabitsPerSecond)]); 1208 + run_lex( 1209 + "12 mbps", 1210 + vec![numtok!(12), Token::unit(MegabitsPerSecond)], 1211 + &strip_operator_spacing, 1212 + &strip_afterdigit_spacing, 1213 + ); 1004 1214 run_lex( 1005 1215 "4.2 gbps", 1006 1216 vec![numtok!(4.2), Token::unit(GigabitsPerSecond)], 1217 + &strip_operator_spacing, 1218 + &strip_afterdigit_spacing, 1007 1219 ); 1008 1220 run_lex( 1009 1221 "2.2 tbps", 1010 1222 vec![numtok!(2.2), Token::unit(TerabitsPerSecond)], 1223 + &strip_operator_spacing, 1224 + &strip_afterdigit_spacing, 1011 1225 ); 1012 1226 run_lex( 1013 1227 "1.7 pbps", 1014 1228 vec![numtok!(1.7), Token::unit(PetabitsPerSecond)], 1229 + &strip_operator_spacing, 1230 + &strip_afterdigit_spacing, 1015 1231 ); 1016 1232 run_lex( 1017 1233 "0.99 ebps", 1018 1234 vec![numtok!(0.99), Token::unit(ExabitsPerSecond)], 1235 + &strip_operator_spacing, 1236 + &strip_afterdigit_spacing, 1019 1237 ); 1020 1238 run_lex( 1021 1239 "0.64 zbps", 1022 1240 vec![numtok!(0.64), Token::unit(ZettabitsPerSecond)], 1241 + &strip_operator_spacing, 1242 + &strip_afterdigit_spacing, 1023 1243 ); 1024 1244 run_lex( 1025 1245 "0.278 ybps", 1026 1246 vec![numtok!(0.278), Token::unit(YottabitsPerSecond)], 1247 + &strip_operator_spacing, 1248 + &strip_afterdigit_spacing, 1027 1249 ); 1028 1250 run_datarate_lex( 1029 1251 "4 bits per second", ··· 1331 1553 Token::unit(Second), 1332 1554 ], 1333 1555 ); 1334 - run_lex("1 flop", vec![numtok!(1), Token::unit(Flop)]); 1335 - run_lex("2 kflop", vec![numtok!(2), Token::unit(KiloFlop)]); 1336 - run_lex("3 mflop", vec![numtok!(3), Token::unit(MegaFlop)]); 1337 - run_lex("4 gflop", vec![numtok!(4), Token::unit(GigaFlop)]); 1338 - run_lex("5 tflop", vec![numtok!(5), Token::unit(TeraFlop)]); 1339 - run_lex("6 pflop", vec![numtok!(6), Token::unit(PetaFlop)]); 1340 - run_lex("7 eflop", vec![numtok!(7), Token::unit(ExaFlop)]); 1341 - run_lex("8 zflop", vec![numtok!(8), Token::unit(ZettaFlop)]); 1342 - run_lex("9 yflop", vec![numtok!(9), Token::unit(YottaFlop)]); 1343 - run_lex("10 rflop", vec![numtok!(10), Token::unit(RonnaFlop)]); 1344 - run_lex("11 qflop", vec![numtok!(11), Token::unit(QuettaFlop)]); 1556 + run_lex( 1557 + "1 flop", 1558 + vec![numtok!(1), Token::unit(Flop)], 1559 + &strip_operator_spacing, 1560 + &strip_afterdigit_spacing, 1561 + ); 1562 + run_lex( 1563 + "2 kflop", 1564 + vec![numtok!(2), Token::unit(KiloFlop)], 1565 + &strip_operator_spacing, 1566 + &strip_afterdigit_spacing, 1567 + ); 1568 + run_lex( 1569 + "3 mflop", 1570 + vec![numtok!(3), Token::unit(MegaFlop)], 1571 + &strip_operator_spacing, 1572 + &strip_afterdigit_spacing, 1573 + ); 1574 + run_lex( 1575 + "4 gflop", 1576 + vec![numtok!(4), Token::unit(GigaFlop)], 1577 + &strip_operator_spacing, 1578 + &strip_afterdigit_spacing, 1579 + ); 1580 + run_lex( 1581 + "5 tflop", 1582 + vec![numtok!(5), Token::unit(TeraFlop)], 1583 + &strip_operator_spacing, 1584 + &strip_afterdigit_spacing, 1585 + ); 1586 + run_lex( 1587 + "6 pflop", 1588 + vec![numtok!(6), Token::unit(PetaFlop)], 1589 + &strip_operator_spacing, 1590 + &strip_afterdigit_spacing, 1591 + ); 1592 + run_lex( 1593 + "7 eflop", 1594 + vec![numtok!(7), Token::unit(ExaFlop)], 1595 + &strip_operator_spacing, 1596 + &strip_afterdigit_spacing, 1597 + ); 1598 + run_lex( 1599 + "8 zflop", 1600 + vec![numtok!(8), Token::unit(ZettaFlop)], 1601 + &strip_operator_spacing, 1602 + &strip_afterdigit_spacing, 1603 + ); 1604 + run_lex( 1605 + "9 yflop", 1606 + vec![numtok!(9), Token::unit(YottaFlop)], 1607 + &strip_operator_spacing, 1608 + &strip_afterdigit_spacing, 1609 + ); 1610 + run_lex( 1611 + "10 rflop", 1612 + vec![numtok!(10), Token::unit(RonnaFlop)], 1613 + &strip_operator_spacing, 1614 + &strip_afterdigit_spacing, 1615 + ); 1616 + run_lex( 1617 + "11 qflop", 1618 + vec![numtok!(11), Token::unit(QuettaFlop)], 1619 + &strip_operator_spacing, 1620 + &strip_afterdigit_spacing, 1621 + ); 1345 1622 run_lex( 1346 1623 "1 flop/s", 1347 1624 vec![ ··· 1350 1627 Token::Operator(Divide), 1351 1628 Token::unit(Second), 1352 1629 ], 1630 + &strip_operator_spacing, 1631 + &strip_afterdigit_spacing, 1353 1632 ); 1354 1633 run_lex( 1355 1634 "2 kflop/s", ··· 1359 1638 Token::Operator(Divide), 1360 1639 Token::unit(Second), 1361 1640 ], 1641 + &strip_operator_spacing, 1642 + &strip_afterdigit_spacing, 1362 1643 ); 1363 1644 run_lex( 1364 1645 "3 mflop/s", ··· 1368 1649 Token::Operator(Divide), 1369 1650 Token::unit(Second), 1370 1651 ], 1652 + &strip_operator_spacing, 1653 + &strip_afterdigit_spacing, 1371 1654 ); 1372 1655 run_lex( 1373 1656 "4 gflop/s", ··· 1377 1660 Token::Operator(Divide), 1378 1661 Token::unit(Second), 1379 1662 ], 1663 + &strip_operator_spacing, 1664 + &strip_afterdigit_spacing, 1380 1665 ); 1381 1666 run_lex( 1382 1667 "5 tflop/s", ··· 1386 1671 Token::Operator(Divide), 1387 1672 Token::unit(Second), 1388 1673 ], 1674 + &strip_operator_spacing, 1675 + &strip_afterdigit_spacing, 1389 1676 ); 1390 1677 run_lex( 1391 1678 "6 pflop/s", ··· 1395 1682 Token::Operator(Divide), 1396 1683 Token::unit(Second), 1397 1684 ], 1685 + &strip_operator_spacing, 1686 + &strip_afterdigit_spacing, 1398 1687 ); 1399 1688 run_lex( 1400 1689 "7 eflop/s", ··· 1404 1693 Token::Operator(Divide), 1405 1694 Token::unit(Second), 1406 1695 ], 1696 + &strip_operator_spacing, 1697 + &strip_afterdigit_spacing, 1407 1698 ); 1408 1699 run_lex( 1409 1700 "8 zflop/s", ··· 1413 1704 Token::Operator(Divide), 1414 1705 Token::unit(Second), 1415 1706 ], 1707 + &strip_operator_spacing, 1708 + &strip_afterdigit_spacing, 1416 1709 ); 1417 1710 run_lex( 1418 1711 "9 yflop/s", ··· 1422 1715 Token::Operator(Divide), 1423 1716 Token::unit(Second), 1424 1717 ], 1718 + &strip_operator_spacing, 1719 + &strip_afterdigit_spacing, 1425 1720 ); 1426 1721 run_lex( 1427 1722 "10 rflop/s", ··· 1431 1726 Token::Operator(Divide), 1432 1727 Token::unit(Second), 1433 1728 ], 1729 + &strip_operator_spacing, 1730 + &strip_afterdigit_spacing, 1434 1731 ); 1435 1732 run_lex( 1436 1733 "11 qflop/s", ··· 1440 1737 Token::Operator(Divide), 1441 1738 Token::unit(Second), 1442 1739 ], 1740 + &strip_operator_spacing, 1741 + &strip_afterdigit_spacing, 1443 1742 ); 1444 1743 run_lex( 1445 1744 "1 flop per second", ··· 1449 1748 Token::TextOperator(Per), 1450 1749 Token::unit(Second), 1451 1750 ], 1751 + &strip_operator_spacing, 1752 + &strip_afterdigit_spacing, 1452 1753 ); 1453 1754 run_lex( 1454 1755 "2 kflop per second", ··· 1458 1759 Token::TextOperator(Per), 1459 1760 Token::unit(Second), 1460 1761 ], 1762 + &strip_operator_spacing, 1763 + &strip_afterdigit_spacing, 1461 1764 ); 1462 1765 run_lex( 1463 1766 "3 mflop per second", ··· 1467 1770 Token::TextOperator(Per), 1468 1771 Token::unit(Second), 1469 1772 ], 1773 + &strip_operator_spacing, 1774 + &strip_afterdigit_spacing, 1470 1775 ); 1471 1776 run_lex( 1472 1777 "4 gflop per second", ··· 1476 1781 Token::TextOperator(Per), 1477 1782 Token::unit(Second), 1478 1783 ], 1784 + &strip_operator_spacing, 1785 + &strip_afterdigit_spacing, 1479 1786 ); 1480 1787 run_lex( 1481 1788 "5 tflop per second", ··· 1485 1792 Token::TextOperator(Per), 1486 1793 Token::unit(Second), 1487 1794 ], 1795 + &strip_operator_spacing, 1796 + &strip_afterdigit_spacing, 1488 1797 ); 1489 1798 run_lex( 1490 1799 "6 pflop per second", ··· 1494 1803 Token::TextOperator(Per), 1495 1804 Token::unit(Second), 1496 1805 ], 1806 + &strip_operator_spacing, 1807 + &strip_afterdigit_spacing, 1497 1808 ); 1498 1809 run_lex( 1499 1810 "7 eflop per second", ··· 1503 1814 Token::TextOperator(Per), 1504 1815 Token::unit(Second), 1505 1816 ], 1817 + &strip_operator_spacing, 1818 + &strip_afterdigit_spacing, 1506 1819 ); 1507 1820 run_lex( 1508 1821 "8 zflop per second", ··· 1512 1825 Token::TextOperator(Per), 1513 1826 Token::unit(Second), 1514 1827 ], 1828 + &strip_operator_spacing, 1829 + &strip_afterdigit_spacing, 1515 1830 ); 1516 1831 run_lex( 1517 1832 "9 yflop per second", ··· 1521 1836 Token::TextOperator(Per), 1522 1837 Token::unit(Second), 1523 1838 ], 1839 + &strip_operator_spacing, 1840 + &strip_afterdigit_spacing, 1524 1841 ); 1525 1842 run_lex( 1526 1843 "10 rflop per second", ··· 1530 1847 Token::TextOperator(Per), 1531 1848 Token::unit(Second), 1532 1849 ], 1850 + &strip_operator_spacing, 1851 + &strip_afterdigit_spacing, 1533 1852 ); 1534 1853 run_lex( 1535 1854 "11 qflop per second", ··· 1539 1858 Token::TextOperator(Per), 1540 1859 Token::unit(Second), 1541 1860 ], 1861 + &strip_operator_spacing, 1862 + &strip_afterdigit_spacing, 1542 1863 ); 1543 - run_lex("1 flops", vec![numtok!(1), Token::unit(FlopPerSecond)]); 1544 - run_lex("2 kflops", vec![numtok!(2), Token::unit(KiloFlopPerSecond)]); 1545 - run_lex("3 mflops", vec![numtok!(3), Token::unit(MegaFlopPerSecond)]); 1546 - run_lex("4 gflops", vec![numtok!(4), Token::unit(GigaFlopPerSecond)]); 1547 - run_lex("5 tflops", vec![numtok!(5), Token::unit(TeraFlopPerSecond)]); 1548 - run_lex("6 pflops", vec![numtok!(6), Token::unit(PetaFlopPerSecond)]); 1549 - run_lex("7 eflops", vec![numtok!(7), Token::unit(ExaFlopPerSecond)]); 1864 + run_lex( 1865 + "1 flops", 1866 + vec![numtok!(1), Token::unit(FlopPerSecond)], 1867 + &strip_operator_spacing, 1868 + &strip_afterdigit_spacing, 1869 + ); 1870 + run_lex( 1871 + "2 kflops", 1872 + vec![numtok!(2), Token::unit(KiloFlopPerSecond)], 1873 + &strip_operator_spacing, 1874 + &strip_afterdigit_spacing, 1875 + ); 1876 + run_lex( 1877 + "3 mflops", 1878 + vec![numtok!(3), Token::unit(MegaFlopPerSecond)], 1879 + &strip_operator_spacing, 1880 + &strip_afterdigit_spacing, 1881 + ); 1882 + run_lex( 1883 + "4 gflops", 1884 + vec![numtok!(4), Token::unit(GigaFlopPerSecond)], 1885 + &strip_operator_spacing, 1886 + &strip_afterdigit_spacing, 1887 + ); 1888 + run_lex( 1889 + "5 tflops", 1890 + vec![numtok!(5), Token::unit(TeraFlopPerSecond)], 1891 + &strip_operator_spacing, 1892 + &strip_afterdigit_spacing, 1893 + ); 1894 + run_lex( 1895 + "6 pflops", 1896 + vec![numtok!(6), Token::unit(PetaFlopPerSecond)], 1897 + &strip_operator_spacing, 1898 + &strip_afterdigit_spacing, 1899 + ); 1900 + run_lex( 1901 + "7 eflops", 1902 + vec![numtok!(7), Token::unit(ExaFlopPerSecond)], 1903 + &strip_operator_spacing, 1904 + &strip_afterdigit_spacing, 1905 + ); 1550 1906 run_lex( 1551 1907 "8 zflops", 1552 1908 vec![numtok!(8), Token::unit(ZettaFlopPerSecond)], 1909 + &strip_operator_spacing, 1910 + &strip_afterdigit_spacing, 1553 1911 ); 1554 1912 run_lex( 1555 1913 "9 yflops", 1556 1914 vec![numtok!(9), Token::unit(YottaFlopPerSecond)], 1915 + &strip_operator_spacing, 1916 + &strip_afterdigit_spacing, 1557 1917 ); 1558 1918 run_lex( 1559 1919 "10 rflops", 1560 1920 vec![numtok!(10), Token::unit(RonnaFlopPerSecond)], 1921 + &strip_operator_spacing, 1922 + &strip_afterdigit_spacing, 1561 1923 ); 1562 1924 run_lex( 1563 1925 "11 qflops", 1564 1926 vec![numtok!(11), Token::unit(QuettaFlopPerSecond)], 1927 + &strip_operator_spacing, 1928 + &strip_afterdigit_spacing, 1565 1929 ); 1566 - run_lex("234 wh", vec![numtok!(234), Token::unit(WattHour)]); 1567 - run_lex("1 w", vec![numtok!(1), Token::unit(Watt)]); 1568 - run_lex("1 watt", vec![numtok!(1), Token::unit(Watt)]); 1569 - run_lex("1 watts", vec![numtok!(1), Token::unit(Watt)]); 1570 - run_lex("1 watt hour", vec![numtok!(1), Token::unit(WattHour)]); 1930 + run_lex( 1931 + "234 wh", 1932 + vec![numtok!(234), Token::unit(WattHour)], 1933 + &strip_operator_spacing, 1934 + &strip_afterdigit_spacing, 1935 + ); 1936 + run_lex( 1937 + "1 w", 1938 + vec![numtok!(1), Token::unit(Watt)], 1939 + &strip_operator_spacing, 1940 + &strip_afterdigit_spacing, 1941 + ); 1942 + run_lex( 1943 + "1 watt", 1944 + vec![numtok!(1), Token::unit(Watt)], 1945 + &strip_operator_spacing, 1946 + &strip_afterdigit_spacing, 1947 + ); 1948 + run_lex( 1949 + "1 watts", 1950 + vec![numtok!(1), Token::unit(Watt)], 1951 + &strip_operator_spacing, 1952 + &strip_afterdigit_spacing, 1953 + ); 1954 + run_lex( 1955 + "1 watt hour", 1956 + vec![numtok!(1), Token::unit(WattHour)], 1957 + &strip_operator_spacing, 1958 + &strip_afterdigit_spacing, 1959 + ); 1571 1960 run_lex( 1572 1961 "0 watt + 1 watts", 1573 1962 vec![ ··· 1577 1966 numtok!(1), 1578 1967 Token::unit(Watt), 1579 1968 ], 1969 + &strip_operator_spacing, 1970 + &strip_afterdigit_spacing, 1580 1971 ); 1581 1972 run_lex( 1582 1973 "0 watt * 1", ··· 1586 1977 Token::Operator(Multiply), 1587 1978 numtok!(1), 1588 1979 ], 1980 + &strip_operator_spacing, 1981 + &strip_afterdigit_spacing, 1589 1982 ); 1590 1983 run_lex( 1591 1984 "2 watts + 3 watts", ··· 1596 1989 numtok!(3), 1597 1990 Token::unit(Watt), 1598 1991 ], 1992 + &strip_operator_spacing, 1993 + &strip_afterdigit_spacing, 1599 1994 ); 1600 1995 run_lex( 1601 1996 "2 watts * 3", ··· 1605 2000 Token::Operator(Multiply), 1606 2001 numtok!(3), 1607 2002 ], 2003 + &strip_operator_spacing, 2004 + &strip_afterdigit_spacing, 1608 2005 ); 1609 2006 run_lex( 1610 2007 "4 watt plus 5 watts", ··· 1615 2012 numtok!(5), 1616 2013 Token::unit(Watt), 1617 2014 ], 2015 + &strip_operator_spacing, 2016 + &strip_afterdigit_spacing, 1618 2017 ); 1619 2018 run_lex( 1620 2019 "4 watt times 5", ··· 1624 2023 Token::Operator(Multiply), 1625 2024 numtok!(5), 1626 2025 ], 2026 + &strip_operator_spacing, 2027 + &strip_afterdigit_spacing, 1627 2028 ); 1628 2029 run_lex( 1629 2030 "6 watts plus 7 watts", ··· 1634 2035 numtok!(7), 1635 2036 Token::unit(Watt), 1636 2037 ], 2038 + &strip_operator_spacing, 2039 + &strip_afterdigit_spacing, 1637 2040 ); 1638 2041 run_lex( 1639 2042 "6 watts times 7", ··· 1643 2046 Token::Operator(Multiply), 1644 2047 numtok!(7), 1645 2048 ], 2049 + &strip_operator_spacing, 2050 + &strip_afterdigit_spacing, 1646 2051 ); 1647 - run_lex("2.3 kwh", vec![numtok!(2.3), Token::unit(KilowattHour)]); 1648 - run_lex("1 kw", vec![numtok!(1), Token::unit(Kilowatt)]); 1649 - run_lex("1 kilowatt", vec![numtok!(1), Token::unit(Kilowatt)]); 1650 - run_lex("1 kilowatts", vec![numtok!(1), Token::unit(Kilowatt)]); 2052 + run_lex( 2053 + "2.3 kwh", 2054 + vec![numtok!(2.3), Token::unit(KilowattHour)], 2055 + &strip_operator_spacing, 2056 + &strip_afterdigit_spacing, 2057 + ); 2058 + run_lex( 2059 + "1 kw", 2060 + vec![numtok!(1), Token::unit(Kilowatt)], 2061 + &strip_operator_spacing, 2062 + &strip_afterdigit_spacing, 2063 + ); 2064 + run_lex( 2065 + "1 kilowatt", 2066 + vec![numtok!(1), Token::unit(Kilowatt)], 2067 + &strip_operator_spacing, 2068 + &strip_afterdigit_spacing, 2069 + ); 2070 + run_lex( 2071 + "1 kilowatts", 2072 + vec![numtok!(1), Token::unit(Kilowatt)], 2073 + &strip_operator_spacing, 2074 + &strip_afterdigit_spacing, 2075 + ); 1651 2076 run_lex( 1652 2077 "1 kilowatt hour", 1653 2078 vec![numtok!(1), Token::unit(KilowattHour)], 2079 + &strip_operator_spacing, 2080 + &strip_afterdigit_spacing, 1654 2081 ); 1655 2082 run_lex( 1656 2083 "2 kilowatt + 3 watt", ··· 1661 2088 numtok!(3), 1662 2089 Token::unit(Watt), 1663 2090 ], 2091 + &strip_operator_spacing, 2092 + &strip_afterdigit_spacing, 1664 2093 ); 1665 2094 run_lex( 1666 2095 "2 kilowatt * 4", ··· 1670 2099 Token::Operator(Multiply), 1671 2100 numtok!(4), 1672 2101 ], 2102 + &strip_operator_spacing, 2103 + &strip_afterdigit_spacing, 1673 2104 ); 1674 2105 run_lex( 1675 2106 "2 kilowatt times 4", ··· 1679 2110 Token::Operator(Multiply), 1680 2111 numtok!(4), 1681 2112 ], 2113 + &strip_operator_spacing, 2114 + &strip_afterdigit_spacing, 1682 2115 ); 1683 2116 run_lex( 1684 2117 "2 kilowatt + 3 watts", ··· 1689 2122 numtok!(3), 1690 2123 Token::unit(Watt), 1691 2124 ], 2125 + &strip_operator_spacing, 2126 + &strip_afterdigit_spacing, 1692 2127 ); 1693 2128 run_lex( 1694 2129 "2 kilowatts + 3 watt", ··· 1699 2134 numtok!(3), 1700 2135 Token::unit(Watt), 1701 2136 ], 2137 + &strip_operator_spacing, 2138 + &strip_afterdigit_spacing, 1702 2139 ); 1703 2140 run_lex( 1704 2141 "2 kilowatts + 3 watts", ··· 1709 2146 numtok!(3), 1710 2147 Token::unit(Watt), 1711 2148 ], 2149 + &strip_operator_spacing, 2150 + &strip_afterdigit_spacing, 1712 2151 ); 1713 2152 run_lex( 1714 2153 "2 kilowatt plus 3 watt", ··· 1719 2158 numtok!(3), 1720 2159 Token::unit(Watt), 1721 2160 ], 2161 + &strip_operator_spacing, 2162 + &strip_afterdigit_spacing, 1722 2163 ); 1723 2164 run_lex( 1724 2165 "2 kilowatt plus 3 watts", ··· 1729 2170 numtok!(3), 1730 2171 Token::unit(Watt), 1731 2172 ], 2173 + &strip_operator_spacing, 2174 + &strip_afterdigit_spacing, 1732 2175 ); 1733 2176 run_lex( 1734 2177 "2 kilowatts plus 3 watt", ··· 1739 2182 numtok!(3), 1740 2183 Token::unit(Watt), 1741 2184 ], 2185 + &strip_operator_spacing, 2186 + &strip_afterdigit_spacing, 1742 2187 ); 1743 2188 run_lex( 1744 2189 "2 kilowatts plus 3 watts", ··· 1749 2194 numtok!(3), 1750 2195 Token::unit(Watt), 1751 2196 ], 2197 + &strip_operator_spacing, 2198 + &strip_afterdigit_spacing, 1752 2199 ); 1753 2200 run_lex( 1754 2201 "6.6 watts + 4 kilowatts", ··· 1759 2206 numtok!(4), 1760 2207 Token::unit(Kilowatt), 1761 2208 ], 2209 + &strip_operator_spacing, 2210 + &strip_afterdigit_spacing, 1762 2211 ); 1763 2212 run_lex( 1764 2213 "6.6 watts plus 4 kilowatts", ··· 1769 2218 numtok!(4), 1770 2219 Token::unit(Kilowatt), 1771 2220 ], 2221 + &strip_operator_spacing, 2222 + &strip_afterdigit_spacing, 1772 2223 ); 1773 - run_lex("2.3 mwh", vec![numtok!(2.3), Token::unit(MegawattHour)]); 1774 - run_lex("1 mw", vec![numtok!(1), Token::unit(Megawatt)]); 1775 - run_lex("1 megawatt", vec![numtok!(1), Token::unit(Megawatt)]); 2224 + run_lex( 2225 + "2.3 mwh", 2226 + vec![numtok!(2.3), Token::unit(MegawattHour)], 2227 + &strip_operator_spacing, 2228 + &strip_afterdigit_spacing, 2229 + ); 2230 + run_lex( 2231 + "1 mw", 2232 + vec![numtok!(1), Token::unit(Megawatt)], 2233 + &strip_operator_spacing, 2234 + &strip_afterdigit_spacing, 2235 + ); 2236 + run_lex( 2237 + "1 megawatt", 2238 + vec![numtok!(1), Token::unit(Megawatt)], 2239 + &strip_operator_spacing, 2240 + &strip_afterdigit_spacing, 2241 + ); 1776 2242 run_lex( 1777 2243 "1 megawatt hour", 1778 2244 vec![numtok!(1), Token::unit(MegawattHour)], 2245 + &strip_operator_spacing, 2246 + &strip_afterdigit_spacing, 1779 2247 ); 1780 2248 run_lex( 1781 2249 "2 megawatt + 3 watt", ··· 1786 2254 numtok!(3), 1787 2255 Token::unit(Watt), 1788 2256 ], 2257 + &strip_operator_spacing, 2258 + &strip_afterdigit_spacing, 1789 2259 ); 1790 2260 run_lex( 1791 2261 "2 megawatt * 6", ··· 1795 2265 Token::Operator(Multiply), 1796 2266 numtok!(6), 1797 2267 ], 2268 + &strip_operator_spacing, 2269 + &strip_afterdigit_spacing, 1798 2270 ); 1799 2271 run_lex( 1800 2272 "2 megawatt times 6", ··· 1804 2276 Token::Operator(Multiply), 1805 2277 numtok!(6), 1806 2278 ], 2279 + &strip_operator_spacing, 2280 + &strip_afterdigit_spacing, 1807 2281 ); 1808 2282 run_lex( 1809 2283 "2 megawatt + 3 watts", ··· 1814 2288 numtok!(3), 1815 2289 Token::unit(Watt), 1816 2290 ], 2291 + &strip_operator_spacing, 2292 + &strip_afterdigit_spacing, 1817 2293 ); 1818 2294 run_lex( 1819 2295 "2 megawatts + 3 watt", ··· 1824 2300 numtok!(3), 1825 2301 Token::unit(Watt), 1826 2302 ], 2303 + &strip_operator_spacing, 2304 + &strip_afterdigit_spacing, 1827 2305 ); 1828 2306 run_lex( 1829 2307 "2 megawatts + 3 watts", ··· 1834 2312 numtok!(3), 1835 2313 Token::unit(Watt), 1836 2314 ], 2315 + &strip_operator_spacing, 2316 + &strip_afterdigit_spacing, 1837 2317 ); 1838 2318 run_lex( 1839 2319 "2 megawatt plus 3 watt", ··· 1844 2324 numtok!(3), 1845 2325 Token::unit(Watt), 1846 2326 ], 2327 + &strip_operator_spacing, 2328 + &strip_afterdigit_spacing, 1847 2329 ); 1848 2330 run_lex( 1849 2331 "2 megawatt plus 3 watts", ··· 1854 2336 numtok!(3), 1855 2337 Token::unit(Watt), 1856 2338 ], 2339 + &strip_operator_spacing, 2340 + &strip_afterdigit_spacing, 1857 2341 ); 1858 2342 run_lex( 1859 2343 "2 megawatts plus 3 watt", ··· 1864 2348 numtok!(3), 1865 2349 Token::unit(Watt), 1866 2350 ], 2351 + &strip_operator_spacing, 2352 + &strip_afterdigit_spacing, 1867 2353 ); 1868 2354 run_lex( 1869 2355 "2 megawatts plus 3 watts", ··· 1874 2360 numtok!(3), 1875 2361 Token::unit(Watt), 1876 2362 ], 2363 + &strip_operator_spacing, 2364 + &strip_afterdigit_spacing, 1877 2365 ); 1878 2366 run_lex( 1879 2367 "6.6 watts + 4 megawatts", ··· 1884 2372 numtok!(4), 1885 2373 Token::unit(Megawatt), 1886 2374 ], 2375 + &strip_operator_spacing, 2376 + &strip_afterdigit_spacing, 1887 2377 ); 1888 2378 run_lex( 1889 2379 "6.6 watts plus 4 megawatts", ··· 1894 2384 numtok!(4), 1895 2385 Token::unit(Megawatt), 1896 2386 ], 2387 + &strip_operator_spacing, 2388 + &strip_afterdigit_spacing, 1897 2389 ); 1898 - run_lex("234 gwh", vec![numtok!(234), Token::unit(GigawattHour)]); 1899 - run_lex("1 gw", vec![numtok!(1), Token::unit(Gigawatt)]); 1900 - run_lex("1 gigawatt", vec![numtok!(1), Token::unit(Gigawatt)]); 1901 - run_lex("1 gigawatts", vec![numtok!(1), Token::unit(Gigawatt)]); 2390 + run_lex( 2391 + "234 gwh", 2392 + vec![numtok!(234), Token::unit(GigawattHour)], 2393 + &strip_operator_spacing, 2394 + &strip_afterdigit_spacing, 2395 + ); 2396 + run_lex( 2397 + "1 gw", 2398 + vec![numtok!(1), Token::unit(Gigawatt)], 2399 + &strip_operator_spacing, 2400 + &strip_afterdigit_spacing, 2401 + ); 2402 + run_lex( 2403 + "1 gigawatt", 2404 + vec![numtok!(1), Token::unit(Gigawatt)], 2405 + &strip_operator_spacing, 2406 + &strip_afterdigit_spacing, 2407 + ); 2408 + run_lex( 2409 + "1 gigawatts", 2410 + vec![numtok!(1), Token::unit(Gigawatt)], 2411 + &strip_operator_spacing, 2412 + &strip_afterdigit_spacing, 2413 + ); 1902 2414 run_lex( 1903 2415 "1 gigawatt hour", 1904 2416 vec![numtok!(1), Token::unit(GigawattHour)], 2417 + &strip_operator_spacing, 2418 + &strip_afterdigit_spacing, 1905 2419 ); 1906 2420 run_lex( 1907 2421 "0 gigawatt + 1 gigawatts", ··· 1912 2426 numtok!(1), 1913 2427 Token::unit(Gigawatt), 1914 2428 ], 2429 + &strip_operator_spacing, 2430 + &strip_afterdigit_spacing, 1915 2431 ); 1916 2432 run_lex( 1917 2433 "0 gigawatt * 1", ··· 1921 2437 Token::Operator(Multiply), 1922 2438 numtok!(1), 1923 2439 ], 2440 + &strip_operator_spacing, 2441 + &strip_afterdigit_spacing, 1924 2442 ); 1925 2443 run_lex( 1926 2444 "2 gigawatts + 3 gigawatts", ··· 1931 2449 numtok!(3), 1932 2450 Token::unit(Gigawatt), 1933 2451 ], 2452 + &strip_operator_spacing, 2453 + &strip_afterdigit_spacing, 1934 2454 ); 1935 2455 run_lex( 1936 2456 "2 gigawatts * 3", ··· 1940 2460 Token::Operator(Multiply), 1941 2461 numtok!(3), 1942 2462 ], 2463 + &strip_operator_spacing, 2464 + &strip_afterdigit_spacing, 1943 2465 ); 1944 2466 run_lex( 1945 2467 "4 gigawatt plus 5 watt", ··· 1950 2472 numtok!(5), 1951 2473 Token::unit(Watt), 1952 2474 ], 2475 + &strip_operator_spacing, 2476 + &strip_afterdigit_spacing, 1953 2477 ); 1954 2478 run_lex( 1955 2479 "4 gigawatt plus 5 megawatt", ··· 1960 2484 numtok!(5), 1961 2485 Token::unit(Megawatt), 1962 2486 ], 2487 + &strip_operator_spacing, 2488 + &strip_afterdigit_spacing, 1963 2489 ); 1964 2490 run_lex( 1965 2491 "4 gigawatt plus 5 gigawatt", ··· 1970 2496 numtok!(5), 1971 2497 Token::unit(Gigawatt), 1972 2498 ], 2499 + &strip_operator_spacing, 2500 + &strip_afterdigit_spacing, 1973 2501 ); 1974 2502 run_lex( 1975 2503 "4 gigawatt plus 5 watts", ··· 1980 2508 numtok!(5), 1981 2509 Token::unit(Watt), 1982 2510 ], 2511 + &strip_operator_spacing, 2512 + &strip_afterdigit_spacing, 1983 2513 ); 1984 2514 run_lex( 1985 2515 "4 gigawatt plus 5 megawatts", ··· 1990 2520 numtok!(5), 1991 2521 Token::unit(Megawatt), 1992 2522 ], 2523 + &strip_operator_spacing, 2524 + &strip_afterdigit_spacing, 1993 2525 ); 1994 2526 run_lex( 1995 2527 "4 gigawatt plus 5 gigawatts", ··· 2000 2532 numtok!(5), 2001 2533 Token::unit(Gigawatt), 2002 2534 ], 2535 + &strip_operator_spacing, 2536 + &strip_afterdigit_spacing, 2003 2537 ); 2004 2538 run_lex( 2005 2539 "4 gigawatt times 5", ··· 2009 2543 Token::Operator(Multiply), 2010 2544 numtok!(5), 2011 2545 ], 2546 + &strip_operator_spacing, 2547 + &strip_afterdigit_spacing, 2012 2548 ); 2013 2549 run_lex( 2014 2550 "6 gigawatts plus 7 watt", ··· 2019 2555 numtok!(7), 2020 2556 Token::unit(Watt), 2021 2557 ], 2558 + &strip_operator_spacing, 2559 + &strip_afterdigit_spacing, 2022 2560 ); 2023 2561 run_lex( 2024 2562 "6 gigawatts plus 7 megawatt", ··· 2029 2567 numtok!(7), 2030 2568 Token::unit(Megawatt), 2031 2569 ], 2570 + &strip_operator_spacing, 2571 + &strip_afterdigit_spacing, 2032 2572 ); 2033 2573 run_lex( 2034 2574 "6 gigawatts plus 7 gigawatt", ··· 2039 2579 numtok!(7), 2040 2580 Token::unit(Gigawatt), 2041 2581 ], 2582 + &strip_operator_spacing, 2583 + &strip_afterdigit_spacing, 2042 2584 ); 2043 2585 run_lex( 2044 2586 "6 gigawatts plus 7 watts", ··· 2049 2591 numtok!(7), 2050 2592 Token::unit(Watt), 2051 2593 ], 2594 + &strip_operator_spacing, 2595 + &strip_afterdigit_spacing, 2052 2596 ); 2053 2597 run_lex( 2054 2598 "6 gigawatts plus 7 megawatts", ··· 2059 2603 numtok!(7), 2060 2604 Token::unit(Megawatt), 2061 2605 ], 2606 + &strip_operator_spacing, 2607 + &strip_afterdigit_spacing, 2062 2608 ); 2063 2609 run_lex( 2064 2610 "6 gigawatts plus 7 gigawatts", ··· 2069 2615 numtok!(7), 2070 2616 Token::unit(Gigawatt), 2071 2617 ], 2618 + &strip_operator_spacing, 2619 + &strip_afterdigit_spacing, 2072 2620 ); 2073 2621 run_lex( 2074 2622 "6 gigawatts times 7", ··· 2078 2626 Token::Operator(Multiply), 2079 2627 numtok!(7), 2080 2628 ], 2629 + &strip_operator_spacing, 2630 + &strip_afterdigit_spacing, 2081 2631 ); 2082 2632 run_lex( 2083 2633 "88 mw * 3", ··· 2087 2637 Token::Operator(Multiply), 2088 2638 numtok!(3), 2089 2639 ], 2640 + &strip_operator_spacing, 2641 + &strip_afterdigit_spacing, 2090 2642 ); 2091 2643 run_lex( 2092 2644 "88 mw times 3", ··· 2096 2648 Token::Operator(Multiply), 2097 2649 numtok!(3), 2098 2650 ], 2651 + &strip_operator_spacing, 2652 + &strip_afterdigit_spacing, 2653 + ); 2654 + run_lex( 2655 + "999 kb", 2656 + vec![numtok!(999), Token::unit(Kilobyte)], 2657 + &strip_operator_spacing, 2658 + &strip_afterdigit_spacing, 2099 2659 ); 2100 - run_lex("999 kb", vec![numtok!(999), Token::unit(Kilobyte)]); 2101 2660 run_lex( 2102 2661 "200 gb - 100 mb", 2103 2662 vec![ ··· 2107 2666 numtok!(100), 2108 2667 Token::unit(Megabyte), 2109 2668 ], 2669 + &strip_operator_spacing, 2670 + &strip_afterdigit_spacing, 2110 2671 ); 2111 - run_lex("999 kib", vec![numtok!(999), Token::unit(Kibibyte)]); 2672 + run_lex( 2673 + "999 kib", 2674 + vec![numtok!(999), Token::unit(Kibibyte)], 2675 + &strip_operator_spacing, 2676 + &strip_afterdigit_spacing, 2677 + ); 2112 2678 run_lex( 2113 2679 "200 gib - 100 mib", 2114 2680 vec![ ··· 2118 2684 numtok!(100), 2119 2685 Token::unit(Mebibyte), 2120 2686 ], 2687 + &strip_operator_spacing, 2688 + &strip_afterdigit_spacing, 2121 2689 ); 2122 - run_lex("45 btu", vec![numtok!(45), Token::unit(BritishThermalUnit)]); 2690 + run_lex( 2691 + "45 btu", 2692 + vec![numtok!(45), Token::unit(BritishThermalUnit)], 2693 + &strip_operator_spacing, 2694 + &strip_afterdigit_spacing, 2695 + ); 2123 2696 run_lex( 2124 2697 "45.5 british thermal unit", 2125 2698 vec![numtok!(45.5), Token::unit(BritishThermalUnit)], 2699 + &strip_operator_spacing, 2700 + &strip_afterdigit_spacing, 2126 2701 ); 2127 2702 run_lex( 2128 2703 "46 british thermal units", 2129 2704 vec![numtok!(46), Token::unit(BritishThermalUnit)], 2705 + &strip_operator_spacing, 2706 + &strip_afterdigit_spacing, 2130 2707 ); 2131 2708 run_lex( 2132 2709 "5432 newton metres", 2133 2710 vec![numtok!(5432), Token::unit(NewtonMeter)], 2711 + &strip_operator_spacing, 2712 + &strip_afterdigit_spacing, 2134 2713 ); 2135 2714 run_lex( 2136 2715 "2345 newton-meters", 2137 2716 vec![numtok!(2345), Token::unit(NewtonMeter)], 2717 + &strip_operator_spacing, 2718 + &strip_afterdigit_spacing, 2138 2719 ); 2139 - run_lex("20 lbf", vec![numtok!(20), Token::LexerKeyword(PoundForce)]); 2140 - run_lex("60 hz", vec![numtok!(60), Token::unit(Hertz)]); 2720 + run_lex( 2721 + "20 lbf", 2722 + vec![numtok!(20), Token::LexerKeyword(PoundForce)], 2723 + &strip_operator_spacing, 2724 + &strip_afterdigit_spacing, 2725 + ); 2726 + run_lex( 2727 + "60 hz", 2728 + vec![numtok!(60), Token::unit(Hertz)], 2729 + &strip_operator_spacing, 2730 + &strip_afterdigit_spacing, 2731 + ); 2141 2732 run_lex( 2142 2733 "1100 rpm", 2143 2734 vec![numtok!(1100), Token::unit(RevolutionsPerMinute)], 2735 + &strip_operator_spacing, 2736 + &strip_afterdigit_spacing, 2144 2737 ); 2145 2738 run_lex( 2146 2739 "1150 revolutions per minute", ··· 2150 2743 Token::TextOperator(Per), 2151 2744 Token::unit(Minute), 2152 2745 ], 2746 + &strip_operator_spacing, 2747 + &strip_afterdigit_spacing, 2153 2748 ); 2154 2749 run_lex( 2155 2750 "1 revolution per min", ··· 2159 2754 Token::TextOperator(Per), 2160 2755 Token::unit(Minute), 2161 2756 ], 2757 + &strip_operator_spacing, 2758 + &strip_afterdigit_spacing, 2162 2759 ); 2163 2760 run_lex( 2164 2761 "4 revolution / mins", ··· 2168 2765 Token::Operator(Divide), 2169 2766 Token::unit(Minute), 2170 2767 ], 2768 + &strip_operator_spacing, 2769 + &strip_afterdigit_spacing, 2171 2770 ); 2172 2771 run_lex( 2173 2772 "1250 r / min", ··· 2177 2776 Token::Operator(Divide), 2178 2777 Token::unit(Minute), 2179 2778 ], 2779 + &strip_operator_spacing, 2780 + &strip_afterdigit_spacing, 2180 2781 ); 2181 2782 run_lex( 2182 2783 "1300 rev / min", ··· 2186 2787 Token::Operator(Divide), 2187 2788 Token::unit(Minute), 2188 2789 ], 2790 + &strip_operator_spacing, 2791 + &strip_afterdigit_spacing, 2189 2792 ); 2190 2793 run_lex( 2191 2794 "1350 rev / minute", ··· 2195 2798 Token::Operator(Divide), 2196 2799 Token::unit(Minute), 2197 2800 ], 2801 + &strip_operator_spacing, 2802 + &strip_afterdigit_spacing, 2198 2803 ); 2199 2804 run_lex( 2200 2805 "1250 r per min", ··· 2204 2809 Token::TextOperator(Per), 2205 2810 Token::unit(Minute), 2206 2811 ], 2812 + &strip_operator_spacing, 2813 + &strip_afterdigit_spacing, 2207 2814 ); 2208 2815 run_lex( 2209 2816 "1300 rev per min", ··· 2213 2820 Token::TextOperator(Per), 2214 2821 Token::unit(Minute), 2215 2822 ], 2823 + &strip_operator_spacing, 2824 + &strip_afterdigit_spacing, 2216 2825 ); 2217 2826 run_lex( 2218 2827 "1350 rev per minute", ··· 2222 2831 Token::TextOperator(Per), 2223 2832 Token::unit(Minute), 2224 2833 ], 2834 + &strip_operator_spacing, 2835 + &strip_afterdigit_spacing, 2225 2836 ); 2226 2837 run_lex( 2227 2838 "100 kph", 2228 2839 vec![numtok!(100), Token::unit(KilometersPerHour)], 2840 + &strip_operator_spacing, 2841 + &strip_afterdigit_spacing, 2229 2842 ); 2230 2843 run_lex( 2231 2844 "100 kmh", 2232 2845 vec![numtok!(100), Token::unit(KilometersPerHour)], 2846 + &strip_operator_spacing, 2847 + &strip_afterdigit_spacing, 2233 2848 ); 2234 2849 run_lex( 2235 2850 "100 kilometers per hour", ··· 2239 2854 Token::TextOperator(Per), 2240 2855 Token::unit(Hour), 2241 2856 ], 2857 + &strip_operator_spacing, 2858 + &strip_afterdigit_spacing, 2242 2859 ); 2243 2860 run_lex( 2244 2861 "100 kilometre / hrs", ··· 2248 2865 Token::Operator(Divide), 2249 2866 Token::unit(Hour), 2250 2867 ], 2868 + &strip_operator_spacing, 2869 + &strip_afterdigit_spacing, 2251 2870 ); 2252 - run_lex("3.6 mps", vec![numtok!(3.6), Token::unit(MetersPerSecond)]); 2871 + run_lex( 2872 + "3.6 mps", 2873 + vec![numtok!(3.6), Token::unit(MetersPerSecond)], 2874 + &strip_operator_spacing, 2875 + &strip_afterdigit_spacing, 2876 + ); 2253 2877 run_lex( 2254 2878 "3.6 meters per second", 2255 2879 vec![ ··· 2258 2882 Token::TextOperator(Per), 2259 2883 Token::unit(Second), 2260 2884 ], 2885 + &strip_operator_spacing, 2886 + &strip_afterdigit_spacing, 2261 2887 ); 2262 2888 run_lex( 2263 2889 "3.6 metre / secs", ··· 2267 2893 Token::Operator(Divide), 2268 2894 Token::unit(Second), 2269 2895 ], 2896 + &strip_operator_spacing, 2897 + &strip_afterdigit_spacing, 2270 2898 ); 2271 - run_lex("60 mph", vec![numtok!(60), Token::unit(MilesPerHour)]); 2899 + run_lex( 2900 + "60 mph", 2901 + vec![numtok!(60), Token::unit(MilesPerHour)], 2902 + &strip_operator_spacing, 2903 + &strip_afterdigit_spacing, 2904 + ); 2272 2905 run_lex( 2273 2906 "60 miles per hour", 2274 2907 vec![ ··· 2277 2910 Token::TextOperator(Per), 2278 2911 Token::unit(Hour), 2279 2912 ], 2913 + &strip_operator_spacing, 2914 + &strip_afterdigit_spacing, 2280 2915 ); 2281 2916 run_lex( 2282 2917 "60 mile / hr", ··· 2286 2921 Token::Operator(Divide), 2287 2922 Token::unit(Hour), 2288 2923 ], 2924 + &strip_operator_spacing, 2925 + &strip_afterdigit_spacing, 2289 2926 ); 2290 - run_lex("35 fps", vec![numtok!(35), Token::unit(FeetPerSecond)]); 2927 + run_lex( 2928 + "35 fps", 2929 + vec![numtok!(35), Token::unit(FeetPerSecond)], 2930 + &strip_operator_spacing, 2931 + &strip_afterdigit_spacing, 2932 + ); 2291 2933 run_lex( 2292 2934 "35 ft / sec", 2293 2935 vec![ ··· 2296 2938 Token::Operator(Divide), 2297 2939 Token::unit(Second), 2298 2940 ], 2941 + &strip_operator_spacing, 2942 + &strip_afterdigit_spacing, 2299 2943 ); 2300 2944 run_lex( 2301 2945 "35 ft per seconds", ··· 2305 2949 Token::TextOperator(Per), 2306 2950 Token::unit(Second), 2307 2951 ], 2952 + &strip_operator_spacing, 2953 + &strip_afterdigit_spacing, 2308 2954 ); 2309 2955 run_lex( 2310 2956 "35 foot / secs", ··· 2314 2960 Token::Operator(Divide), 2315 2961 Token::unit(Second), 2316 2962 ], 2963 + &strip_operator_spacing, 2964 + &strip_afterdigit_spacing, 2317 2965 ); 2318 2966 run_lex( 2319 2967 "35 foot per seconds", ··· 2323 2971 Token::TextOperator(Per), 2324 2972 Token::unit(Second), 2325 2973 ], 2974 + &strip_operator_spacing, 2975 + &strip_afterdigit_spacing, 2326 2976 ); 2327 2977 run_lex( 2328 2978 "35 feet / sec", ··· 2332 2982 Token::Operator(Divide), 2333 2983 Token::unit(Second), 2334 2984 ], 2985 + &strip_operator_spacing, 2986 + &strip_afterdigit_spacing, 2335 2987 ); 2336 2988 run_lex( 2337 2989 "35 feet per second", ··· 2341 2993 Token::TextOperator(Per), 2342 2994 Token::unit(Second), 2343 2995 ], 2996 + &strip_operator_spacing, 2997 + &strip_afterdigit_spacing, 2344 2998 ); 2345 - run_lex("30 pa", vec![numtok!(30), Token::unit(Pascal)]); 2999 + run_lex( 3000 + "30 pa", 3001 + vec![numtok!(30), Token::unit(Pascal)], 3002 + &strip_operator_spacing, 3003 + &strip_afterdigit_spacing, 3004 + ); 2346 3005 run_lex( 2347 3006 "23 celsius + 4 celsius", 2348 3007 vec![ ··· 2352 3011 numtok!(4), 2353 3012 Token::unit(Celsius), 2354 3013 ], 3014 + &strip_operator_spacing, 3015 + &strip_afterdigit_spacing, 2355 3016 ); 2356 3017 run_lex( 2357 3018 "54 f - 1.5 fahrenheit", ··· 2362 3023 numtok!(1.5), 2363 3024 Token::unit(Fahrenheit), 2364 3025 ], 3026 + &strip_operator_spacing, 3027 + &strip_afterdigit_spacing, 2365 3028 ); 2366 3029 run_lex( 2367 3030 "50 metric tonnes", 2368 3031 vec![numtok!(50), Token::unit(MetricTon)], 3032 + &strip_operator_spacing, 3033 + &strip_afterdigit_spacing, 2369 3034 ); 2370 3035 run_lex( 2371 3036 "77 metric hps", 2372 3037 vec![numtok!(77), Token::unit(MetricHorsepower)], 3038 + &strip_operator_spacing, 3039 + &strip_afterdigit_spacing, 2373 3040 ); 2374 3041 2375 3042 run_lex( 2376 3043 "100 + 99", 2377 3044 vec![numtok!(100), Token::Operator(Plus), numtok!(99)], 3045 + &strip_operator_spacing, 3046 + &strip_afterdigit_spacing, 2378 3047 ); 2379 3048 run_lex( 2380 3049 "100 plus 99", 2381 3050 vec![numtok!(100), Token::Operator(Plus), numtok!(99)], 3051 + &strip_operator_spacing, 3052 + &strip_afterdigit_spacing, 2382 3053 ); 2383 3054 run_lex( 2384 3055 "12 - 4", 2385 3056 vec![numtok!(12), Token::Operator(Minus), numtok!(4)], 3057 + &strip_operator_spacing, 3058 + &strip_afterdigit_spacing, 2386 3059 ); 2387 3060 run_lex( 2388 3061 "12 minus 4", 2389 3062 vec![numtok!(12), Token::Operator(Minus), numtok!(4)], 3063 + &strip_operator_spacing, 3064 + &strip_afterdigit_spacing, 2390 3065 ); 2391 3066 run_lex( 2392 3067 "50.5 * 2", 2393 3068 vec![numtok!(50.5), Token::Operator(Multiply), numtok!(2)], 3069 + &strip_operator_spacing, 3070 + &strip_afterdigit_spacing, 2394 3071 ); 2395 3072 run_lex( 2396 3073 "50.5 times 2", 2397 3074 vec![numtok!(50.5), Token::Operator(Multiply), numtok!(2)], 3075 + &strip_operator_spacing, 3076 + &strip_afterdigit_spacing, 2398 3077 ); 2399 3078 run_lex( 2400 3079 "50.5 multiplied by 2", 2401 3080 vec![numtok!(50.5), Token::Operator(Multiply), numtok!(2)], 3081 + &strip_operator_spacing, 3082 + &strip_afterdigit_spacing, 2402 3083 ); 2403 3084 run_lex( 2404 3085 "6 / 3", 2405 3086 vec![numtok!(6), Token::Operator(Divide), numtok!(3)], 3087 + &strip_operator_spacing, 3088 + &strip_afterdigit_spacing, 2406 3089 ); 2407 3090 run_lex( 2408 3091 "50 / 10", 2409 3092 vec![numtok!(50), Token::Operator(Divide), numtok!(10)], 3093 + &strip_operator_spacing, 3094 + &strip_afterdigit_spacing, 2410 3095 ); 2411 3096 run_lex( 2412 3097 "52 ÷ 12", 2413 3098 vec![numtok!(52), Token::Operator(Divide), numtok!(12)], 3099 + &strip_operator_spacing, 3100 + &strip_afterdigit_spacing, 2414 3101 ); 2415 3102 run_lex( 2416 3103 "6 divided by 3", 2417 3104 vec![numtok!(6), Token::Operator(Divide), numtok!(3)], 3105 + &strip_operator_spacing, 3106 + &strip_afterdigit_spacing, 2418 3107 ); 2419 3108 run_lex( 2420 3109 "7 mod 5", 2421 3110 vec![numtok!(7), Token::Operator(Modulo), numtok!(5)], 3111 + &strip_operator_spacing, 3112 + &strip_afterdigit_spacing, 2422 3113 ); 2423 3114 2424 3115 run_lex( ··· 2432 3123 Token::Operator(Multiply), 2433 3124 numtok!(4), 2434 3125 ], 3126 + &strip_operator_spacing, 3127 + &strip_afterdigit_spacing, 2435 3128 ); 2436 3129 run_lex( 2437 3130 "52 weeks * (12 hrs + 12 hours)", ··· 2447 3140 Token::unit(Hour), 2448 3141 Token::Operator(RightParen), 2449 3142 ], 3143 + &strip_operator_spacing, 3144 + &strip_afterdigit_spacing, 2450 3145 ); 2451 3146 run_lex( 2452 - "12 pound+", 2453 - vec![numtok!(12), Token::unit(Pound), Token::Operator(Plus)], 3147 + "12 cm+", 3148 + vec![numtok!(12), Token::unit(Centimeter), Token::Operator(Plus)], 3149 + &strip_operator_spacing, 3150 + &strip_afterdigit_spacing, 2454 3151 ); 2455 3152 2456 3153 run_lex( 2457 3154 "5 π m", 2458 3155 vec![numtok!(5), Token::Constant(Pi), Token::unit(Meter)], 3156 + &strip_operator_spacing, 3157 + &strip_afterdigit_spacing, 2459 3158 ); 2460 3159 run_lex( 2461 3160 "5 Ω + 2 mΩ", ··· 2466 3165 numtok!(2), 2467 3166 Token::unit(Milliohm), 2468 3167 ], 3168 + &strip_operator_spacing, 3169 + &strip_afterdigit_spacing, 2469 3170 ); 2470 3171 } 2471 3172 }
+70 -3
src/lib.rs
··· 24 24 25 25 use crate::units::{Unit, UnitType, primitive_unit, sort_units}; 26 26 use fastnum::{D128, dec128 as d}; 27 - use std::fmt::{self, Debug, Display}; 27 + use serde::{Deserialize, Serialize}; 28 + use std::{ 29 + fmt::{self, Debug, Display}, 30 + sync::{OnceLock, RwLock, RwLockReadGuard, RwLockWriteGuard}, 31 + }; 28 32 use web_time::Instant; 29 33 30 34 /// Currency exchange rates ··· 325 329 }; 326 330 } 327 331 332 + static SETTINGS: OnceLock<RwLock<Settings>> = OnceLock::new(); 333 + 334 + #[derive(Serialize, Deserialize)] 335 + pub struct Settings { 336 + pub locale: String, 337 + } 338 + 339 + impl Settings { 340 + pub fn region(&self) -> String { 341 + let region = self.locale.split('-').last().unwrap().to_string(); 342 + region 343 + } 344 + pub fn get() -> &'static RwLock<Settings> { 345 + SETTINGS.get_or_init(|| RwLock::new(Settings::default())) 346 + } 347 + pub fn read() -> RwLockReadGuard<'static, Settings> { 348 + Self::get().read().unwrap() 349 + } 350 + pub fn write() -> RwLockWriteGuard<'static, Settings> { 351 + Self::get().write().unwrap() 352 + } 353 + #[cfg(test)] 354 + pub fn reset_to_default() { 355 + *Self::write() = Settings::default(); 356 + } 357 + } 358 + impl Default for Settings { 359 + fn default() -> Self { 360 + #[cfg(test)] 361 + { 362 + Settings { 363 + locale: "nb-NO".to_string(), 364 + } 365 + } 366 + #[cfg(not(test))] 367 + { 368 + #[cfg(target_arch = "wasm32")] 369 + { 370 + Settings { 371 + locale: "".to_string(), 372 + } 373 + } 374 + #[cfg(not(target_arch = "wasm32"))] 375 + Settings { 376 + locale: sys_locale::get_locale().unwrap_or("".to_string()), 377 + } 378 + } 379 + } 380 + } 381 + 382 + #[cfg(target_arch = "wasm32")] 383 + #[wasm_bindgen] 384 + pub fn set_settings(value: JsValue) -> Result<(), JsValue> { 385 + let settings: Settings = 386 + serde_wasm_bindgen::from_value(value).map_err(|e| JsValue::from_str(&e.to_string()))?; 387 + *Settings::write() = settings; 388 + Ok(()) 389 + } 390 + 391 + pub fn get_region() -> String { 392 + Settings::read().region() 393 + } 394 + 328 395 /// Evaluates a string into a resulting [`Number`]. 329 396 /// 330 397 /// Example: ··· 354 421 355 422 let parse_start = Instant::now(); 356 423 match parser::parse(&tokens) { 357 - Ok(ast) => { 424 + Ok(mut ast) => { 358 425 let parse_time = Instant::now().duration_since(parse_start).as_nanos() as f32; 359 426 if verbose { 360 427 println!("Parsed AstNode: {:#?}", ast); 361 428 } 362 429 363 430 let eval_start = Instant::now(); 364 - match evaluator::evaluate(&ast) { 431 + match evaluator::evaluate(&mut ast) { 365 432 Ok(answer) => { 366 433 let eval_time = 367 434 Instant::now().duration_since(eval_start).as_nanos() as f32;
+15
src/units.rs
··· 6 6 #[derive(Clone, Copy, PartialEq, Debug)] 7 7 /// An enum of all possible unit types, like [`Length`], [`DigitalStorage`] etc. 8 8 pub enum UnitType { 9 + /// May be one of several units depending on context, such as `pound` or `kr`. 10 + Unknown, 9 11 /// A unit of time, for example [`Hour`] 10 12 Time, 11 13 /// A unit of length, for example [`Mile`] ··· 48 50 impl UnitType { 49 51 fn primitive(&self) -> Vec<(Unit, isize)> { 50 52 let units = match self { 53 + UnitType::Unknown => panic!("Unknown unit type for ambiguous unit"), 51 54 Time => vec![(Second, 1)], 52 55 Length => vec![(Meter, 1)], 53 56 Area => vec![(Meter, 2)], ··· 80 83 } 81 84 use UnitType::*; 82 85 86 + #[derive(Clone, Copy, PartialEq, Debug, Eq, PartialOrd, Ord, Hash)] 87 + pub struct Ambiguity { 88 + pub string: &'static str, 89 + pub candidates: &'static [Unit], 90 + pub fallback: &'static Unit, 91 + } 92 + 83 93 /// Sort for display and comparison purposes. 84 94 pub fn sort_units(primitives: &mut Vec<(Unit, isize)>) { 85 95 primitives.sort_by_key(|u| { ··· 134 144 #[derive(Clone, Copy, PartialEq, Debug, Eq, PartialOrd, Ord, Hash)] 135 145 /// A Unit enum. Note that it can also be [`NoUnit`]. 136 146 pub enum Unit { 147 + Ambiguity(Ambiguity), 137 148 $($variant),* 138 149 } 139 150 use Unit::*; ··· 141 152 impl Unit { 142 153 pub fn category(&self) -> UnitType { 143 154 match self { 155 + Unit::Ambiguity(_) => UnitType::Unknown, 144 156 $( 145 157 Unit::$variant => $properties.0 146 158 ),* ··· 148 160 } 149 161 pub fn weight(&self) -> D128 { 150 162 match self { 163 + Unit::Ambiguity(ambiguity) => panic!("Ambiguous unit {}", ambiguity.string), 151 164 $( 152 165 Unit::$variant => $properties.1 153 166 ),* ··· 155 168 } 156 169 pub(crate) fn singular(&self) -> &str { 157 170 match self { 171 + Unit::Ambiguity(ambiguity) => panic!("Ambiguous unit {}", ambiguity.string), 158 172 $( 159 173 Unit::$variant => $properties.2 160 174 ),* ··· 162 176 } 163 177 pub(crate) fn plural(&self) -> &str { 164 178 match self { 179 + Unit::Ambiguity(ambiguity) => panic!("Ambiguous unit {}", ambiguity.string), 165 180 $( 166 181 Unit::$variant => $properties.3 167 182 ),*
+3
web/src/routes/+page.svelte
··· 11 11 12 12 // Initialize currency cache when cpc module is loaded 13 13 cpc_promise.then(async (mod) => { 14 + mod.set_settings({ 15 + locale: Intl?.DateTimeFormat()?.resolvedOptions()?.locale ?? navigator.language 16 + }) 14 17 cpc = mod; 15 18 16 19 // Fetch exchange rates and initialize currency cache