1 use std::borrow::Cow;
2 
3 #[derive(Eq, PartialEq, PartialOrd, Ord, Clone, Copy)]
4 struct UnitDef {
5 	singular: &'static str,
6 	plural: &'static str,
7 	definition: &'static str,
8 }
9 
10 // singular, plural (or empty), definition, description
11 type UnitTuple = (&'static str, &'static str, &'static str, &'static str);
12 
13 const BASE_UNITS: &[UnitTuple] = &[
14 	("second", "seconds", "l@!", ""),
15 	("meter", "meters", "l@!", ""),
16 	("kilogram", "kilograms", "l@!", ""),
17 	("kelvin", "", "l@!", ""),
18 	("ampere", "amperes", "l@!", ""),
19 	("mole", "moles", "l@!", ""),
20 	("candela", "candelas", "l@!", ""),
21 	("neper", "nepers", "l@!", ""),
22 ];
23 
24 const BASE_UNIT_ABBREVIATIONS: &[UnitTuple] = &[
25 	("s", "", "s@second", ""),
26 	("metre", "metres", "l@meter", ""),
27 	("m", "", "s@meter", ""),
28 	("gram", "grams", "l@1/1000 kilogram", ""),
29 	("jin", "jin", "l@1/2 kilogram", ""),
30 	("g", "", "s@gram", ""),
31 	("K", "", "s@kelvin", ""),
32 	("\u{b0}K", "", "=K", ""),
33 	("amp", "amps", "l@ampere", ""),
34 	("A", "", "s@ampere", ""),
35 	("mol", "", "s@mole", ""),
36 	("cd", "", "s@candela", ""),
37 	("Np", "", "s@neper", ""),
38 ];
39 
40 // some temperature scales have special support for conversions
41 const TEMPERATURE_SCALES: &[UnitTuple] = &[
42 	("celsius", "", "l@!", ""),
43 	("\u{b0}C", "", "celsius", ""), // degree symbol
44 	("oC", "", "=\u{b0}C", ""),
45 	("rankine", "", "l@5/9 K", ""),
46 	("\u{b0}R", "", "rankine", ""),
47 	("fahrenheit", "", "l@!", ""),
48 	("\u{b0}F", "", "fahrenheit", ""),
49 	("oF", "", "=\u{b0}F", ""),
50 ];
51 
52 const BITS_AND_BYTES: &[UnitTuple] = &[
53 	("bit", "bits", "l@!", ""),
54 	("bps", "", "s@bits/second", ""),
55 	("byte", "bytes", "l@8 bits", ""),
56 	("b", "", "s@bit", ""),
57 	("B", "", "s@byte", ""),
58 	("octet", "octets", "l@8 bits", ""),
59 	("nibble", "nibbles", "l@4 bits", ""),
60 ];
61 
62 const STANDARD_PREFIXES: &[UnitTuple] = &[
63 	("quecca", "", "lp@1e30", ""),
64 	("ronna", "", "lp@1e27", ""),
65 	("yotta", "", "lp@1e24", ""),
66 	("zetta", "", "lp@1e21", ""),
67 	("exa", "", "lp@1e18", ""),
68 	("peta", "", "lp@1e15", ""),
69 	("tera", "", "lp@1e12", ""),
70 	("giga", "", "lp@1e9", ""),
71 	("mega", "", "lp@1e6", ""),
72 	("myria", "", "lp@1e4", ""),
73 	("kilo", "", "lp@1e3", ""),
74 	("hecto", "", "lp@1e2", ""),
75 	("deca", "", "lp@1e1", ""),
76 	("deka", "", "lp@deca", ""),
77 	("deci", "", "lp@1e-1", ""),
78 	("centi", "", "lp@1e-2", ""),
79 	("milli", "", "lp@1e-3", ""),
80 	("micro", "", "lp@1e-6", ""),
81 	("nano", "", "lp@1e-9", ""),
82 	("pico", "", "lp@1e-12", ""),
83 	("femto", "", "lp@1e-15", ""),
84 	("atto", "", "lp@1e-18", ""),
85 	("zepto", "", "lp@1e-21", ""),
86 	("yocto", "", "lp@1e-24", ""),
87 	("ronto", "", "lp@1e-27", ""),
88 	("quecto", "", "lp@1e-30", ""),
89 	("k", "", "=1000", ""),
90 	("M", "", "=1,000,000", ""),
91 	("G", "", "=1,000,000,000", ""),
92 	("T", "", "=1,000,000,000,000", ""),
93 ];
94 
95 const NON_STANDARD_PREFIXES: &[UnitTuple] = &[
96 	("quarter", "", "lp@1/4", ""),
97 	("semi", "", "[email protected]", ""),
98 	("demi", "", "[email protected]", ""),
99 	("hemi", "", "[email protected]", ""),
100 	("half", "", "[email protected]", ""),
101 	("double", "", "lp@2", ""),
102 	("triple", "", "lp@3", ""),
103 	("treble", "", "lp@3", ""),
104 ];
105 
106 const BINARY_PREFIXES: &[UnitTuple] = &[
107 	("kibi", "", "lp@2^10", ""),
108 	("mebi", "", "lp@2^20", ""),
109 	("gibi", "", "lp@2^30", ""),
110 	("tebi", "", "lp@2^40", ""),
111 	("pebi", "", "lp@2^50", ""),
112 	("exbi", "", "lp@2^60", ""),
113 	("zebi", "", "lp@2^70", ""),
114 	("yobi", "", "lp@2^80", ""),
115 	("Ki", "", "=2^10", ""),
116 	("Mi", "", "=2^20", ""),
117 	("Gi", "", "=2^30", ""),
118 	("Ti", "", "=2^40", ""),
119 ];
120 
121 const NUMBER_WORDS: &[UnitTuple] = &[
122 	("tithe", "", "=1/10", ""),
123 	("one", "", "=1", ""),
124 	("two", "", "=2", ""),
125 	("couple", "", "=2", ""),
126 	("three", "", "=3", ""),
127 	("four", "", "=4", ""),
128 	("quadruple", "", "=4", ""),
129 	("five", "", "=5", ""),
130 	("quintuple", "", "=5", ""),
131 	("six", "", "=6", ""),
132 	("seven", "", "=7", ""),
133 	("eight", "", "=8", ""),
134 	("nine", "", "=9", ""),
135 	("ten", "", "=10", ""),
136 	("eleven", "", "=11", ""),
137 	("twelve", "", "=12", ""),
138 	("dozen", "", "=12", ""),
139 	("thirteen", "", "=13", ""),
140 	("bakersdozen", "", "=13", ""),
141 	("fourteen", "", "=14", ""),
142 	("fifteen", "", "=15", ""),
143 	("sixteen", "", "=16", ""),
144 	("seventeen", "", "=17", ""),
145 	("eighteen", "", "=18", ""),
146 	("nineteen", "", "=19", ""),
147 	("twenty", "", "=20", ""),
148 	("score", "", "=20", ""),
149 	("thirty", "", "=30", ""),
150 	("forty", "", "=40", ""),
151 	("fifty", "", "=50", ""),
152 	("sixty", "", "=60", ""),
153 	("seventy", "", "=70", ""),
154 	("eighty", "", "=80", ""),
155 	("ninety", "", "=90", ""),
156 	("hundred", "", "=100", ""),
157 	("gross", "", "=144", ""),
158 	("greatgross", "", "=12 gross", ""),
159 	("thousand", "", "=1000", ""),
160 	("million", "", "=1e6", ""),
161 	("billion", "", "=1e9", ""),
162 	("trillion", "", "=1e12", ""),
163 	("quadrillion", "", "=1e15", ""),
164 	("quintillion", "", "=1e18", ""),
165 	("sextillion", "", "=1e21", ""),
166 	("septillion", "", "=1e24", ""),
167 	("octillion", "", "=1e27", ""),
168 	("nonillion", "", "=1e30", ""),
169 	("decillion", "", "=1e33", ""),
170 	("undecillion", "", "=1e36", ""),
171 	("duodecillion", "", "=1e39", ""),
172 	("tredecillion", "", "=1e42", ""),
173 	("quattuordecillion", "", "=1e45", ""),
174 	("quindecillion", "", "=1e48", ""),
175 	("sexdecillion", "", "=1e51", ""),
176 	("septendecillion", "", "=1e54", ""),
177 	("octodecillion", "", "=1e57", ""),
178 	("novemdecillion", "", "=1e60", ""),
179 	("vigintillion", "", "=1e63", ""),
180 	("unvigintillion", "", "=1e66", ""),
181 	("duovigintillion", "", "=1e69", ""),
182 	("trevigintillion", "", "=1e72", ""),
183 	("quattuorvigintillion", "", "=1e75", ""),
184 	("quinvigintillion", "", "=1e78", ""),
185 	("sexvigintillion", "", "=1e81", ""),
186 	("septenvigintillion", "", "=1e84", ""),
187 	("octovigintillion", "", "=1e87", ""),
188 	("novemvigintillion", "", "=1e90", ""),
189 	("trigintillion", "", "=1e93", ""),
190 	("untrigintillion", "", "=1e96", ""),
191 	("duotrigintillion", "", "=1e99", ""),
192 	("googol", "", "=1e100", ""),
193 	("tretrigintillion", "", "=1e102", ""),
194 	("quattuortrigintillion", "", "=1e105", ""),
195 	("quintrigintillion", "", "=1e108", ""),
196 	("sextrigintillion", "", "=1e111", ""),
197 	("septentrigintillion", "", "=1e114", ""),
198 	("octotrigintillion", "", "=1e117", ""),
199 	("novemtrigintillion", "", "=1e120", ""),
200 	("centillion", "", "=1e303", ""),
201 ];
202 
203 const CONSTANTS: &[UnitTuple] = &[
204 	(
205 		"c",
206 		"",
207 		"=299792458 m/s",
208 		"speed of light in vacuum (exact)",
209 	),
210 	(
211 		"planck",
212 		"",
213 		"=6.62607015e-34 J s",
214 		"Planck constant (exact)",
215 	),
216 	(
217 		"boltzmann",
218 		"",
219 		"=1.380649e-23 J/K",
220 		"Boltzmann constant (exact)",
221 	),
222 	(
223 		"electron_charge",
224 		"",
225 		"=1.602176634e-19 coulomb",
226 		"electron charge (exact)",
227 	),
228 	(
229 		"avogadro",
230 		"",
231 		"=6.02214076e23 / mol",
232 		"size of a mole (exact)",
233 	),
234 	("N_A", "", "=avogadro", ""),
235 	(
236 		"gravitational_constant",
237 		"",
238 		"=6.67430e-11 N m^2 / kg^2",
239 		"gravitational constant",
240 	),
241 	("gravity", "", "=9.80665 m/s^2", ""),
242 	("force", "", "gravity", ""), // used to convert some units
243 ];
244 
245 const ANGLES: &[UnitTuple] = &[
246 	("radian", "radians", "l@1", ""),
247 	("rad", "", "radian", ""),
248 	("circle", "circles", "l@2 pi radian", ""),
249 	("degree", "degrees", "l@1/360 circle", ""),
250 	("deg", "degs", "l@degree", ""),
251 	("\u{b0}", "", "degree", ""), // degree symbol
252 	("arcdeg", "arcdegs", "degree", ""),
253 	("arcmin", "arcmins", "l@1/60 degree", ""),
254 	("arcminute", "arcminutes", "l@arcmin", ""),
255 	("arcsec", "arcsecs", "l@1/60 arcmin", ""),
256 	("arcsecond", "arcseconds", "l@arcsec", ""),
257 	("rightangle", "rightangles", "l@90 degrees", ""),
258 	("quadrant", "quadrants", "l@1/4 circle", ""),
259 	("quintant", "quintants", "l@1/5 circle", ""),
260 	("sextant", "sextants", "l@1/6 circle", ""),
261 	(
262 		"zodiac_sign",
263 		"zodiac_signs",
264 		"l@1/12 circle",
265 		"Angular extent of one sign of the zodiac",
266 	),
267 	("turn", "turns", "l@circle", ""),
268 	("revolution", "revolutions", "l@circle", ""),
269 	("rev", "revs", "l@circle", ""),
270 	("gradian", "gradians", "l@1/100 rightangle", ""),
271 	("gon", "gons", "l@gradian", ""),
272 	("grad", "", "l@gradian", ""),
273 	("mas", "", "milliarcsec", ""),
274 ];
275 
276 const SOLID_ANGLES: &[UnitTuple] = &[
277 	("steradian", "steradians", "l@1", ""),
278 	("sr", "sr", "s@steradian", ""),
279 	("sphere", "spheres", "4 pi steradians", ""),
280 	(
281 		"squaredegree",
282 		"squaredegrees",
283 		"(1/180)^2 pi^2 steradians",
284 		"",
285 	),
286 	("squareminute", "squareminutes", "(1/60)^2 squaredegree", ""),
287 	("squaresecond", "squareseconds", "(1/60)^2 squareminute", ""),
288 	("squarearcmin", "squarearcmins", "squareminute", ""),
289 	("squarearcsec", "squarearcsecs", "squaresecond", ""),
290 	(
291 		"sphericalrightangle",
292 		"sphericalrightangles",
293 		"0.5 pi steradians",
294 		"",
295 	),
296 	("octant", "octants", "0.5 pi steradians", ""),
297 ];
298 
299 const COMMON_SI_DERIVED_UNITS: &[UnitTuple] = &[
300 	("newton", "newtons", "l@kg m / s^2", "force"),
301 	("N", "", "s@newton", ""),
302 	("pascal", "pascals", "l@N/m^2", "pressure or stress"),
303 	("Pa", "", "s@pascal", ""),
304 	("joule", "joules", "l@N m", "energy"),
305 	("J", "", "s@joule", ""),
306 	("watt", "watts", "l@J/s", "power"),
307 	("W", "", "s@watt", ""),
308 	(
309 		"horsepower",
310 		"horsepowers",
311 		"[email protected] watts",
312 		"",
313 	),
314 	("hp", "", "s@horsepower", ""),
315 	("coulomb", "", "l@A s", "charge"),
316 	("C", "", "s@coulomb", ""),
317 	("volt", "volts", "l@W/A", "potential difference"),
318 	("V", "", "s@volt", ""),
319 	("Ah", "", "s@ampere hour", ""),
320 	("ohm", "ohms", "l@V/A", "electrical resistance"),
321 	("siemens", "", "l@A/V", "electrical conductance"),
322 	("S", "", "s@siemens", ""),
323 	("farad", "", "l@coulomb/V", "capacitance"),
324 	("F", "", "s@farad", ""),
325 	("weber", "", "l@V s", "magnetic flux"),
326 	("Wb", "", "s@weber", ""),
327 	("henry", "", "l@V s / A", "inductance"),
328 	("H", "", "s@henry", ""),
329 	("tesla", "", "l@Wb/m^2", "magnetic flux density"),
330 	("T", "", "s@tesla", ""),
331 	("hertz", "", "l@/s", "frequency"),
332 	("Hz", "", "s@hertz", ""),
333 	("nit", "nits", "l@candela / meter^2", "luminance"),
334 	("nt", "", "nit", ""),
335 	("lumen", "lumens", "l@cd sr", "luminous flux"),
336 	("lm", "", "s@lumen", ""),
337 	("lux", "", "l@lm/m^2", "illuminance"),
338 	("lx", "", "lux", "illuminance"),
339 	("phot", "phots", "l@1e4 lx", ""),
340 	("ph", "", "s@phot", ""),
341 	("becquerel", "becquerels", "l@/s", "radioactivity"),
342 	("Bq", "", "s@becquerel", ""),
343 	("curie", "curies", "[email protected] Bq", ""),
344 	("Ci", "", "s@curie", ""),
345 	("rutherford", "rutherfords", "l@1e6 Bq", ""),
346 	("Rd", "", "s@rutherford", ""),
347 	(
348 		"gray",
349 		"grays",
350 		"l@J/kg",
351 		"absorbed dose of ionising radiation",
352 	),
353 	("Gy", "", "s@gray", ""),
354 	("rad_radiation", "", "l@1/100 Gy", ""),
355 	(
356 		"sievert",
357 		"sieverts",
358 		"l@J / kg",
359 		"equivalent dose of ionising radiation",
360 	),
361 	("Sv", "", "s@sievert", ""),
362 	("rem", "", "l@1/100 Sv", ""),
363 	("roentgen", "roentgens", "[email protected] coulomb/kg", ""),
364 	("R", "", "s@roentgen", ""),
365 ];
366 
367 const TIME_UNITS: &[UnitTuple] = &[
368 	("sec", "secs", "s@second", ""),
369 	("minute", "minutes", "l@60 seconds", ""),
370 	("min", "mins", "s@minute", ""),
371 	("hour", "hours", "l@60 minutes", ""),
372 	("hr", "hrs", "s@hour", ""),
373 	("h", "h", "s@hour", ""),
374 	("day", "days", "l@24 hours", ""),
375 	("d", "", "s@day", ""),
376 	("da", "", "s@day", ""),
377 	("week", "weeks", "l@7 days", ""),
378 	("wk", "", "s@week", ""),
379 	("fortnight", "fortnights", "l@14 day", ""),
380 	(
381 		"sidereal_year",
382 		"sidereal_years",
383 		"365.256363004 days",
384 		concat!(
385 			"the time taken for the Earth to complete one revolution of its orbit, ",
386 			"as measured against a fixed frame of reference (such as the fixed stars, ",
387 			"Latin sidera, singular sidus)"
388 		),
389 	),
390 	(
391 		"tropical_year",
392 		"tropical_years",
393 		"365.242198781 days",
394 		"the period of time for the mean ecliptic longitude of the Sun to increase by 360 degrees",
395 	),
396 	(
397 		"anomalistic_year",
398 		"anomalistic_years",
399 		"365.259636 days",
400 		"the time taken for the Earth to complete one revolution with respect to its apsides",
401 	),
402 	("year", "years", "l@tropical_year", ""),
403 	("yr", "", "year", ""),
404 	("month", "months", "l@1/12 year", ""),
405 	("mo", "", "month", ""),
406 	("decade", "decades", "10 years", ""),
407 	("century", "centuries", "100 years", ""),
408 	("millennium", "millennia", "1000 years", ""),
409 	("solar_year", "solar_years", "year", ""),
410 	("calendar_year", "calendar_years", "365 days", ""),
411 	("common_year", "common_years", "365 days", ""),
412 	("leap_year", "leap_years", "366 days", ""),
413 	("julian_year", "julian_years", "365.25 days", ""),
414 	("gregorian_year", "gregorian_years", "365.2425 days", ""),
415 	// french revolutionary time
416 	("decimal_hour", "decimal_hours", "l@1/10 day", ""),
417 	(
418 		"decimal_minute",
419 		"decimal_minutes",
420 		"l@1/100 decimal_hour",
421 		"",
422 	),
423 	(
424 		"decimal_second",
425 		"decimal_seconds",
426 		"l@1/100 decimal_minute",
427 		"",
428 	),
429 	("beat", "beats", "l@decimal_minute", "Swatch Internet Time"),
430 	("scaramucci", "scaramuccis", "11 days", ""),
431 	("mooch", "mooches", "scaramucci", ""),
432 ];
433 
434 const RATIOS: &[UnitTuple] = &[
435 	("\u{2030}", "", "=0.001", ""), // per mille
436 	("percent", "", "=0.01", ""),
437 	("%", "", "=percent", ""),
438 	("bel", "bels", "0.5 * ln(10) neper", ""),
439 	("decibel", "decibels", "1/10 bel", ""),
440 	("dB", "", "decibel", ""),
441 	("mill", "mills", "0.001", ""),
442 	("ppm", "", "1e-6", ""),
443 	("parts_per_million", "", "ppm", ""),
444 	("ppb", "", "1e-9", ""),
445 	("parts_per_billion", "", "ppb", ""),
446 	("ppt", "", "1e-12", ""),
447 	("parts_per_trillion", "", "ppt", ""),
448 	("karat", "", "1/24", "measure of gold purity"),
449 	("basispoint", "", "0.01 %", ""),
450 ];
451 
452 const COMMON_PHYSICAL_UNITS: &[UnitTuple] = &[
453 	("electron_volt", "electron_volts", "l@electron_charge V", ""),
454 	("eV", "", "s@electron_volt", ""),
455 	("light_year", "light_years", "c julian_year", ""),
456 	("ly", "", "lightyear", ""),
457 	("light_second", "light_seconds", "c second", ""),
458 	("light_minute", "light_minutes", "c minute", ""),
459 	("light_hour", "light_hours", "c hour", ""),
460 	("light_day", "light_days", "c day", ""),
461 	("parsec", "parsecs", "l@au / tan(arcsec)", ""),
462 	("pc", "", "s@parsec", ""),
463 	(
464 		"astronomical_unit",
465 		"astronomical_units",
466 		"149597870700 m",
467 		"",
468 	),
469 	("au", "", "astronomical_unit", ""),
470 	("AU", "", "astronomical_unit", ""),
471 	("barn", "", "l@1e-28 m^2", ""),
472 	("shed", "", "l@1e-24 barn", ""),
473 	("cc", "", "cm^3", ""),
474 	("are", "ares", "l@100 meter^2", ""),
475 	("liter", "liters", "l@1000 cc", ""),
476 	("litre", "litres", "liter", ""),
477 	("l", "", "s@liter", ""),
478 	("L", "", "s@liter", ""),
479 	("micron", "microns", "l@micrometer", ""),
480 	("bicron", "bicrons", "l@picometer", ""),
481 	("gsm", "", "grams / meter^2", ""),
482 	("hectare", "hectares", "hectoare", ""),
483 	("ha", "", "s@hectare", ""),
484 	("decare", "decares", "l@decaare", ""),
485 	("da", "", "s@decare", ""),
486 	("calorie", "calories", "[email protected] J", ""),
487 	("cal", "", "s@calorie", ""),
488 	(
489 		"british_thermal_unit",
490 		"british_thermal_units",
491 		"1055.05585 J",
492 		"",
493 	),
494 	("btu", "", "british_thermal_unit", ""),
495 	("Wh", "", "s@W hour", ""),
496 	("atmosphere", "atmospheres", "l@101325 Pa", ""),
497 	("atm", "", "s@atmosphere", ""),
498 	("mmHg", "", "l@1/760 atm", "millimeter of mercury"),
499 	("inHg", "", "[email protected] mmHg", "inch of mercury"),
500 	("bar", "", "l@1e5 Pa", "about 1 atmosphere"),
501 	("diopter", "", "l@/m", "reciprocal of focal length"),
502 	("sqm", "", "=m^2", ""),
503 	("sqmm", "", "=mm^2", ""),
504 	("gongjin", "", "l@1 kilogram", ""),
505 	// TODO remove these compatibility units
506 	("lightyear", "lightyears", "light_year", ""),
507 	("light", "", "c", ""),
508 ];
509 
510 const CGS_UNITS: &[UnitTuple] = &[
511 	("gal", "gals", "cm/s^2", "acceleration"),
512 	("dyne", "dynes", "g*gal", "force"),
513 	("erg", "ergs", "g*cm^2/s^2", "work, energy"),
514 	("barye", "baryes", "g/(cm*s^2)", "pressure"),
515 	("poise", "poises", "g/(cm*s)", ""),
516 	("stokes", "", "cm^2/s", ""),
517 	("kayser", "kaysers", "cm^-1", ""),
518 	("biot", "biots", "10 amperes", ""),
519 	("emu", "emus", "0.001 A m^2", ""),
520 	("franklin", "franklins", "dyn^1/2*cm", ""),
521 	("gauss", "", "10^-4 tesla", ""),
522 	("maxwell", "maxwells", "10^-8 weber", ""),
523 	("phot", "phots", "10000 lux", ""),
524 	("stilb", "stilbs", "10000 candela/m^2", ""),
525 	// abbrevations
526 	("gallileo", "gallileos", "gal", ""),
527 	("dyn", "dyns", "dyne", ""),
528 	("Ba", "", "barye", ""),
529 	("P", "", "poise", ""),
530 	("St", "", "stokes", ""),
531 	("K", "", "kayser", ""),
532 	("Bi", "", "biot", ""),
533 	("Fr", "", "franklin", ""),
534 	("G", "", "gauss", ""),
535 	("Mx", "", "maxwell", ""),
536 	("ph", "", "phot", ""),
537 ];
538 
539 const IMPERIAL_UNITS: &[UnitTuple] = &[
540 	("inch", "inches", "2.54 cm", ""),
541 	("mil", "mils", "1/1000 inch", ""),
542 	("\u{2019}", "", "foot", ""), // unicode single quote
543 	("\u{201d}", "", "inch", ""), // unicode double quote
544 	("'", "", "foot", ""),
545 	("\"", "", "inch", ""),
546 	("foot", "feet", "l@12 inch", ""),
547 	("sqft", "", "=ft^2", ""),
548 	("yard", "yards", "l@3 ft", ""),
549 	("mile", "miles", "l@5280 ft", ""),
550 	("line", "lines", "1/12 inch", ""),
551 	("rod", "rods", "5.5 yard", ""),
552 	("pole", "poles", "rod", ""),
553 	("perch", "perches", "rod", ""),
554 	("firkin", "firkins", "90 lb", ""),
555 	("furlong", "furlongs", "40 rod", ""),
556 	("statute_mile", "statute_miles", "mile", ""),
557 	("league", "leagues", "3 mile", ""),
558 	("chain", "chains", "66 feet", ""),
559 	("link", "links", "1/100 chain", ""),
560 	("thou", "", "1/1000 inch", "thousandth of an inch"),
561 	("acre", "acres", "10 chain^2", ""),
562 	("section", "sections", "mile^2", ""),
563 	("township", "townships", "36 sections", ""),
564 	("homestead", "homesteads", "160 acres", ""),
565 	("point", "points", "l@1/72 inch", ""),
566 	("twip", "twips", "l@1/20 point", ""),
567 	("poppyseed", "poppyseeds", "l@line", ""),
568 	("pica", "picas", "l@12 points", ""),
569 	("barleycorn", "barleycorns", "l@4 poppyseed", ""),
570 	("finger", "fingers", "l@63 points", ""),
571 	("stick", "sticks", "l@2 inches", ""),
572 	("palm", "palms", "l@3 inches", ""),
573 	("digit", "digits", "l@1/4 palms", ""),
574 	("nail", "nails", "l@3 digits", ""),
575 	("span", "spans", "l@4 nails", ""),
576 	("hand", "hands", "l@2 sticks", ""),
577 	("shaftment", "shaftments", "l@2 palm", ""),
578 	("cubit", "cubits", "l@2 span", ""),
579 	("ell", "ells", "l@5 span", ""),
580 	("skein", "skeins", "l@96 ell", ""),
581 	("spindle", "spindles", "l@120 skein", ""),
582 	("link", "links", "l@1/25 rod", ""),
583 	("fathom", "fathoms", "l@2 yard", ""),
584 	("shackle", "shackles", "l@15 yard", ""),
585 	("pace", "paces", "l@5 shaftments", ""),
586 	("step", "steps", "l@2 paces", ""),
587 	("grade", "grades", "l@pace", ""),
588 	("rope", "ropes", "l@4 steps", ""),
589 	("ramsdens_chain", "", "l@5 rope", ""),
590 	("roman_mile", "roman_miles", "l@50 ramsdens_chain", ""),
591 	("gunters_chain", "gunters_chains", "l@4 rod", ""),
592 	("rack_unit", "rack_units", "[email protected] inches", ""),
593 	("U", "", "rack_unit", ""),
594 ];
595 
596 const LIQUID_UNITS: &[UnitTuple] = &[
597 	("gallon", "gallons", "231 inch^3", ""),
598 	("gal", "", "gallon", ""),
599 	("quart", "quarts", "1/4 gallon", ""),
600 	("pint", "pints", "1/2 quart", ""),
601 	("cup", "cups", "1/2 pint", ""),
602 	("gill", "", "1/4 pint", ""),
603 	("fluid_ounce", "", "1/16 pint", ""),
604 	("tablespoon", "tablespoons", "1/2 floz", ""),
605 	("teaspoon", "teaspoons", "1/3 tablespoon", ""),
606 	("fluid_dram", "", "1/8 floz", ""),
607 	("qt", "", "quart", ""),
608 	("pt", "", "pint", ""),
609 	("floz", "", "fluid_ounce", ""),
610 	("tbsp", "", "tablespoon", ""),
611 	("tbs", "", "tablespoon", ""),
612 	("tsp", "", "teaspoon", ""),
613 ];
614 
615 const AVOIRDUPOIS_WEIGHT: &[UnitTuple] = &[
616 	("pound", "pounds", "0.45359237 kg", ""),
617 	("lb", "lbs", "pound", ""),
618 	("grain", "grains", "1/7000 pound", ""),
619 	("ounce", "ounces", "1/16 pound", ""),
620 	("oz", "", "ounce", ""),
621 	("dram", "drams", "1/16 ounce", ""),
622 	("dr", "", "dram", ""),
623 	("hundredweight", "hundredweights", "100 pounds", ""),
624 	("cwt", "", "hundredweight", ""),
625 	("short_ton", "short_tons", "2000 pounds", ""),
626 	("quarterweight", "quarterweights", "1/4 short_ton", ""),
627 	("stone", "stones", "14 pounds", ""),
628 	("st", "", "stone", ""),
629 ];
630 
631 const TROY_WEIGHT: &[UnitTuple] = &[
632 	("troy_pound", "troy_pounds", "5760 grains", ""),
633 	("troy_ounce", "troy_ounces", "1/12 troy_pound", ""),
634 	("ozt", "", "troy_ounce", ""),
635 	("pennyweight", "pennyweights", "1/20 troy_ounce", ""),
636 	("dwt", "", "pennyweight", ""),
637 ];
638 
639 const OTHER_WEIGHTS: &[UnitTuple] = &[
640 	("metric_grain", "metric_grains", "50 mg", ""),
641 	("carat", "carats", "0.2 grams", ""),
642 	("ct", "", "carat", ""),
643 	("jewellers_point", "jewellers_points", "1/100 carat", ""),
644 	("tonne", "tonnes", "l@1000 kg", ""),
645 	("t", "", "tonne", ""),
646 ];
647 
648 const IMPERIAL_ABBREVIATIONS: &[UnitTuple] = &[
649 	("yd", "", "yard", ""),
650 	("ch", "", "chain", ""),
651 	("ft", "", "foot", ""),
652 	("mph", "", "mile/hr", ""),
653 	("mpg", "", "mile/gal", ""),
654 	("kph", "", "km/hr", ""),
655 	("kmh", "", "km/hr", ""),
656 	("fpm", "", "ft/min", ""),
657 	("fps", "", "ft/s", ""),
658 	("rpm", "", "rev/min", ""),
659 	("rps", "", "rev/sec", ""),
660 	("mi", "", "mile", ""),
661 	("smi", "", "mile", ""),
662 	("nmi", "", "nautical_mile", ""),
663 	("mbh", "", "1e3 btu/hour", ""),
664 	("ipy", "", "inch/year", ""),
665 	("ccf", "", "100 ft^3", ""),
666 	("Mcf", "", "1000 ft^3", ""),
667 	("plf", "", "lb / foot", "pounds per linear foot"),
668 	("lbf", "", "lb force", ""),
669 	("psi", "", "pound force / inch^2", ""),
670 	("fur", "furs", "furlong", ""),
671 	("fir", "firs", "firkin", ""),
672 	("ftn", "ftns", "fortnight", ""),
673 ];
674 
675 const NAUTICAL_UNITS: &[UnitTuple] = &[
676 	("fathom", "fathoms", "6 ft", ""),
677 	("nautical_mile", "nautical_miles", "1852 m", ""),
678 	("cable", "cables", "1/10 nautical_mile", ""),
679 	("marine_league", "marine_leagues", "3 nautical_mile", ""),
680 	("knot", "knots", "nautical_mile / hr", ""),
681 	("kn", "", "=knots", ""),
682 	("click", "clicks", "km", ""),
683 	("NM", "", "nautical_mile", ""),
684 ];
685 
686 const CURRENCIES: &[UnitTuple] = &[
687 	("BASE_CURRENCY", "BASE_CURRENCY", "!", ""),
688 	("dollar", "dollars", "USD", ""),
689 	("cent", "cents", "0.01 USD", ""),
690 	("US$", "US$", "USD", ""),
691 	("$", "$", "USD", ""),
692 	("euro", "euros", "EUR", ""),
693 	("\u{20ac}", "\u{20ac}", "EUR", ""), // Euro symbol
694 	("\u{a3}", "\u{a3}", "GBP", ""),     // £
695 	("\u{a5}", "\u{a5}", "JPY", ""),     // ¥
696 	("AU$", "AU$", "AUD", ""),
697 	("HK$", "HK$", "HKD", ""),
698 	("NZ$", "NZ$", "NZD", ""),
699 	("zł", "zł", "PLN", ""), // the local abbreviation for PLN, see https://en.wikipedia.org/wiki/Polish_z%C5%82oty
700 	("zl", "zl", "PLN", ""),
701 ];
702 
703 // from https://en.wikipedia.org/wiki/ISO_4217
704 const CURRENCY_IDENTIFIERS: &[&str] = &[
705 	"AED", "AFN", "ALL", "AMD", "ANG", "AOA", "ARS", "AUD", "AWG", "AZN", "BAM", "BBD", "BDT",
706 	"BGN", "BHD", "BIF", "BMD", "BND", "BOB", "BOV", "BRL", "BSD", "BTN", "BWP", "BYN", "BZD",
707 	"CAD", "CDF", "CHE", "CHF", "CHW", "CLF", "CLP", "CNY", "COP", "COU", "CRC", "CUC", "CUP",
708 	"CVE", "CZK", "DJF", "DKK", "DOP", "DZD", "EGP", "ERN", "ETB", "EUR", "FJD", "FKP", "GBP",
709 	"GEL", "GHS", "GIP", "GMD", "GNF", "GTQ", "GYD", "HKD", "HNL", "HRK", "HTG", "HUF", "IDR",
710 	"ILS", "INR", "IQD", "IRR", "ISK", "JMD", "JOD", "JPY", "KES", "KGS", "KHR", "KMF", "KPW",
711 	"KRW", "KWD", "KYD", "KZT", "LAK", "LBP", "LKR", "LRD", "LSL", "LYD", "MAD", "MDL", "MGA",
712 	"MKD", "MMK", "MNT", "MOP", "MRU", "MUR", "MVR", "MWK", "MXN", "MXV", "MYR", "MZN", "NAD",
713 	"NGN", "NIO", "NOK", "NPR", "NZD", "OMR", "PAB", "PEN", "PGK", "PHP", "PKR", "PLN", "PYG",
714 	"QAR", "RON", "RSD", "RUB", "RWF", "SAR", "SBD", "SCR", "SDG", "SEK", "SGD", "SHP", "SLE",
715 	"SLL", "SOS", "SRD", "SSP", "STN", "SVC", "SYP", "SZL", "THB", "TJS", "TMT", "TND", "TOP",
716 	"TRY", "TTD", "TWD", "TZS", "UAH", "UGX", "USD", "USN", "UYI", "UYU", "UYW", "UZS", "VED",
717 	"VES", "VND", "VUV", "WST", "XAF", "XAG", "XAU", "XBA", "XBB", "XBC", "XBD", "XCD", "XDR",
718 	"XOF", "XPD", "XPF", "XPT", "XSU", "XTS", "XUA", "XXX", "YER", "ZAR", "ZMW", "ZWL",
719 ];
720 
721 pub(crate) const ALL_UNIT_DEFS: &[&[UnitTuple]] = &[
722 	BASE_UNITS,
723 	BASE_UNIT_ABBREVIATIONS,
724 	TEMPERATURE_SCALES,
725 	BITS_AND_BYTES,
726 	STANDARD_PREFIXES,
727 	NON_STANDARD_PREFIXES,
728 	BINARY_PREFIXES,
729 	NUMBER_WORDS,
730 	CONSTANTS,
731 	ANGLES,
732 	SOLID_ANGLES,
733 	COMMON_SI_DERIVED_UNITS,
734 	TIME_UNITS,
735 	RATIOS,
736 	COMMON_PHYSICAL_UNITS,
737 	IMPERIAL_UNITS,
738 	LIQUID_UNITS,
739 	AVOIRDUPOIS_WEIGHT,
740 	TROY_WEIGHT,
741 	OTHER_WEIGHTS,
742 	IMPERIAL_ABBREVIATIONS,
743 	NAUTICAL_UNITS,
744 	CURRENCIES,
745 	CGS_UNITS,
746 ];
747 
748 const SHORT_PREFIXES: &[(&str, &str)] = &[
749 	("Ki", "sp@kibi"),
750 	("Mi", "sp@mebi"),
751 	("Gi", "sp@gibi"),
752 	("Ti", "sp@tebi"),
753 	("Pi", "sp@pebi"),
754 	("Ei", "sp@exbi"),
755 	("Zi", "sp@zebi"),
756 	("Yi", "sp@yobi"),
757 	("Y", "sp@yotta"),
758 	("Z", "sp@zetta"),
759 	("E", "sp@exa"),
760 	("P", "sp@peta"),
761 	("T", "sp@tera"),
762 	("G", "sp@giga"),
763 	("M", "sp@mega"),
764 	("k", "sp@kilo"),
765 	("h", "sp@hecto"),
766 	("da", "sp@deka"),
767 	("d", "sp@deci"),
768 	("c", "sp@centi"),
769 	("m", "sp@milli"),
770 	("u", "sp@micro"),       // alternative to µ
771 	("\u{b5}", "sp@micro"),  // U+00B5 (micro sign)
772 	("\u{3bc}", "sp@micro"), // U+03BC (lowercase µ)
773 	("n", "sp@nano"),
774 	("p", "sp@pico"),
775 	("f", "sp@femto"),
776 	("a", "sp@atto"),
777 	("z", "sp@zepto"),
778 	("y", "sp@yocto"),
779 ];
780 
781 #[allow(clippy::too_many_lines)]
query_unit( ident: &str, short_prefixes: bool, case_sensitive: bool, ) -> Option<(Cow<'static, str>, Cow<'static, str>, Cow<'static, str>)>782 pub(crate) fn query_unit(
783 	ident: &str,
784 	short_prefixes: bool,
785 	case_sensitive: bool,
786 ) -> Option<(Cow<'static, str>, Cow<'static, str>, Cow<'static, str>)> {
787 	if short_prefixes {
788 		for (name, def) in SHORT_PREFIXES {
789 			if *name == ident || (!case_sensitive && name.eq_ignore_ascii_case(ident)) {
790 				return Some((Cow::Borrowed(name), Cow::Borrowed(name), Cow::Borrowed(def)));
791 			}
792 		}
793 	}
794 	if let Ok(idx) = CURRENCY_IDENTIFIERS.binary_search(
795 		&if case_sensitive {
796 			ident.to_string()
797 		} else {
798 			ident.to_uppercase()
799 		}
800 		.as_str(),
801 	) {
802 		let name = CURRENCY_IDENTIFIERS[idx];
803 		return Some((
804 			Cow::Borrowed(name),
805 			Cow::Borrowed(name),
806 			Cow::Borrowed("$CURRENCY"),
807 		));
808 	}
809 	let mut candidates = vec![];
810 	for group in ALL_UNIT_DEFS {
811 		for def in *group {
812 			let def = UnitDef {
813 				singular: def.0,
814 				plural: if def.1.is_empty() { def.0 } else { def.1 },
815 				definition: def.2,
816 			};
817 			if def.singular == ident || def.plural == ident {
818 				return Some((
819 					Cow::Borrowed(def.singular),
820 					Cow::Borrowed(def.plural),
821 					Cow::Borrowed(def.definition),
822 				));
823 			}
824 			if !case_sensitive
825 				&& (def.singular.eq_ignore_ascii_case(ident)
826 					|| def.plural.eq_ignore_ascii_case(ident))
827 			{
828 				candidates.push(Some((
829 					Cow::Borrowed(def.singular),
830 					Cow::Borrowed(def.plural),
831 					Cow::Borrowed(def.definition),
832 				)));
833 			}
834 		}
835 	}
836 	if candidates.len() == 1 {
837 		return candidates.into_iter().next().unwrap();
838 	}
839 	None
840 }
841 
842 const DEFAULT_UNITS: &[(&str, &str)] = &[
843 	("hertz", "second^-1"),
844 	("newton", "kilogram^1 meter^1 second^-2"),
845 	("pascal", "kilogram^1 meter^-1 second^-2"),
846 	("joule", "kilogram^1 meter^2 second^-2"),
847 	("watt", "kilogram^1 meter^2 second^-3"),
848 	("ohm", "ampere^-2 kilogram^1 meter^2 second^-3"),
849 	("volt", "ampere^-1 kilogram^1 meter^2 second^-3"),
850 	("liter", "meter^3"),
851 ];
852 
lookup_default_unit(base_units: &str) -> Option<&str>853 pub(crate) fn lookup_default_unit(base_units: &str) -> Option<&str> {
854 	if let Some((unit_name, _)) = DEFAULT_UNITS.iter().find(|(_, base)| *base == base_units) {
855 		return Some(unit_name);
856 	}
857 	if let Some((singular, _, _, _)) = BASE_UNITS
858 		.iter()
859 		.find(|(singular, _, _, _)| format!("{singular}^1") == base_units)
860 	{
861 		return Some(singular);
862 	}
863 	None
864 }
865 
866 /// used for implicit unit addition, e.g. 5'5 -> 5'5"
867 pub(crate) const IMPLICIT_UNIT_MAP: &[(&str, &str)] = &[("'", "\""), ("foot", "inches")];
868 
869 #[cfg(test)]
870 mod tests {
871 	use super::*;
872 
test_str(s: &str, ctx: &mut crate::Context)873 	fn test_str(s: &str, ctx: &mut crate::Context) {
874 		if s.is_empty() || s == "'" || s == "\"" {
875 			return;
876 		}
877 		//eprintln!("Testing '{s}'");
878 		crate::evaluate(s, ctx).unwrap();
879 	}
880 
test_group(group: &[UnitTuple])881 	fn test_group(group: &[UnitTuple]) {
882 		let mut ctx = crate::Context::new();
883 		ctx.set_exchange_rate_handler_v1(crate::test_utils::dummy_currency_handler);
884 		for (s, p, _, _) in group {
885 			test_str(s, &mut ctx);
886 			test_str(p, &mut ctx);
887 		}
888 	}
889 
890 	#[test]
test_all_units()891 	fn test_all_units() {
892 		for &group in ALL_UNIT_DEFS {
893 			test_group(group);
894 		}
895 	}
896 
897 	#[test]
currencies_sorted()898 	fn currencies_sorted() {
899 		let currencies = CURRENCY_IDENTIFIERS.to_vec();
900 		let mut sorted = currencies.clone();
901 		sorted.sort_unstable();
902 		assert_eq!(currencies, sorted, "currencies are not sorted");
903 	}
904 
905 	#[test]
lowercase_currency()906 	fn lowercase_currency() {
907 		assert!(query_unit("usd", true, true).is_none());
908 		assert!(query_unit("usd", true, false).is_some());
909 		assert!(query_unit("USD", true, false).is_some());
910 	}
911 }
912