Skip to main content

si_dynamic/
lib.rs

1use pest::Parser;
2use pest::iterators::Pair;
3use pest_derive::Parser;
4use serde::{Deserialize, Serialize};
5use std::fmt::{Display, Formatter};
6use std::num::{ParseFloatError, ParseIntError};
7
8#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
9pub enum BaseUnit {
10    Second,
11    Meter,
12    Kilogram,
13    Ampere,
14    Kelvin,
15    Mole,
16    Candela,
17
18    Radian,
19    Steradian,
20    Hertz,
21    Newton,
22    Pascal,
23    Joule,
24    Watt,
25    Coulomb,
26    Volt,
27    Farad,
28    Ohm,
29    Siemens,
30    Weber,
31    Tesla,
32    Henry,
33    DegreeCelsius,
34    Lumen,
35    Lux,
36    Becquerel,
37    Gray,
38    Sievert,
39    Katal,
40    Named {
41        name: String,
42        symbol: String,
43        exp: SIExp,
44    },
45    Unitless,
46    // NamedStatic {
47    //     name: &'static str,
48    //     symbol: &'static str,
49    //     exp: SIExp,
50    // },
51}
52
53#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
54pub struct Unit {
55    pub prefix: Prefix,
56    pub base: BaseUnit,
57    pub exp: i8,
58}
59
60impl Unit {
61    pub fn unit() -> Self {
62        Unit {
63            prefix: Prefix::Unit,
64            base: BaseUnit::Unitless,
65            exp: 1,
66        }
67    }
68
69    pub fn base(base: BaseUnit) -> Self {
70        Unit {
71            prefix: Prefix::Unit,
72            base,
73            exp: 1,
74        }
75    }
76}
77
78impl Display for Unit {
79    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
80        if self.exp == 1 {
81            write!(f, "{}{}", self.prefix, self.base)
82        } else {
83            // TODO: use superscript
84            write!(f, "{}{}^{}", self.prefix, self.base, self.exp)
85        }
86    }
87}
88
89pub const KNOWN_UNITS: [BaseUnit; 7 + 22] = [
90    BaseUnit::Second,
91    BaseUnit::Meter,
92    BaseUnit::Kilogram,
93    BaseUnit::Ampere,
94    BaseUnit::Kelvin,
95    BaseUnit::Mole,
96    BaseUnit::Candela,
97    BaseUnit::Radian,
98    BaseUnit::Steradian,
99    BaseUnit::Hertz,
100    BaseUnit::Newton,
101    BaseUnit::Pascal,
102    BaseUnit::Joule,
103    BaseUnit::Watt,
104    BaseUnit::Coulomb,
105    BaseUnit::Volt,
106    BaseUnit::Farad,
107    BaseUnit::Ohm,
108    BaseUnit::Siemens,
109    BaseUnit::Weber,
110    BaseUnit::Tesla,
111    BaseUnit::Henry,
112    BaseUnit::DegreeCelsius,
113    BaseUnit::Lumen,
114    BaseUnit::Lux,
115    BaseUnit::Becquerel,
116    BaseUnit::Gray,
117    BaseUnit::Sievert,
118    BaseUnit::Katal,
119];
120
121impl BaseUnit {
122    pub fn exp(&self) -> SIExp {
123        match self {
124            BaseUnit::Second => SIExp::new_reduced(0, 0, 1, 0),
125            BaseUnit::Meter => SIExp::new_reduced(1, 0, 0, 0),
126            BaseUnit::Kilogram => SIExp::new_reduced(0, 1, 0, 0),
127            BaseUnit::Ampere => SIExp::new_reduced(0, 0, 0, 1),
128            BaseUnit::Kelvin => SIExp::new(0, 0, 0, 0, 1, 0, 0),
129            BaseUnit::Mole => SIExp::new(0, 0, 0, 0, 0, 1, 0),
130            BaseUnit::Candela => SIExp::new(0, 0, 0, 0, 0, 0, 1),
131            BaseUnit::Radian => SIExp::new_reduced(0, 0, 0, 0),
132            BaseUnit::Steradian => SIExp::new_reduced(0, 0, 0, 0),
133            BaseUnit::Hertz => SIExp::new_reduced(0, 0, -1, 0),
134            BaseUnit::Newton => SIExp::new_reduced(1, 1, -2, 0),
135            BaseUnit::Pascal => SIExp::new_reduced(-1, 1, -2, 0),
136            BaseUnit::Joule => SIExp::new_reduced(2, 1, -2, 0),
137            BaseUnit::Watt => SIExp::new_reduced(2, 1, -3, 0),
138            BaseUnit::Coulomb => SIExp::new_reduced(0, 0, 1, 1),
139            BaseUnit::Volt => SIExp::new_reduced(2, 1, -3, -1),
140            BaseUnit::Farad => SIExp::new_reduced(-2, -1, 4, 2),
141            BaseUnit::Ohm => SIExp::new_reduced(2, 1, -3, -2),
142            BaseUnit::Siemens => SIExp::new_reduced(-2, -1, 3, 2),
143            BaseUnit::Weber => SIExp::new_reduced(2, 1, -2, -1),
144            BaseUnit::Tesla => SIExp::new_reduced(0, 1, -2, -1),
145            BaseUnit::Henry => SIExp::new_reduced(2, 1, -2, -2),
146            BaseUnit::DegreeCelsius => SIExp::new(0, 0, 0, 0, 1, 0, 0),
147            BaseUnit::Lumen => SIExp::new(0, 0, 0, 0, 0, 0, 1),
148            BaseUnit::Lux => SIExp::new(-2, 0, 0, 0, 0, 0, 1),
149            BaseUnit::Becquerel => SIExp::new_reduced(0, 0, -1, 0),
150            BaseUnit::Gray => SIExp::new_reduced(2, 0, -2, 0),
151            BaseUnit::Sievert => SIExp::new_reduced(2, 0, -2, 0),
152            BaseUnit::Katal => SIExp::new(0, 0, -1, 0, 0, 1, 0),
153            BaseUnit::Named { exp, .. } => *exp,
154            // SIUnit::NamedStatic { exp, .. } => *exp,
155            BaseUnit::Unitless => SIExp::new_reduced(0, 0, 0, 0),
156        }
157    }
158
159    pub fn name(&self) -> &str {
160        match self {
161            BaseUnit::Second => "Second",
162            BaseUnit::Meter => "Meter",
163            BaseUnit::Kilogram => "Kilogram",
164            BaseUnit::Ampere => "Ampere",
165            BaseUnit::Kelvin => "Kelvin",
166            BaseUnit::Mole => "Mole",
167            BaseUnit::Candela => "Candela",
168            BaseUnit::Radian => "Radian",
169            BaseUnit::Steradian => "Steradian",
170            BaseUnit::Hertz => "Hertz",
171            BaseUnit::Newton => "Newton",
172            BaseUnit::Pascal => "Pascal",
173            BaseUnit::Joule => "Joule",
174            BaseUnit::Watt => "Watt",
175            BaseUnit::Coulomb => "Coulomb",
176            BaseUnit::Volt => "Volt",
177            BaseUnit::Farad => "Farad",
178            BaseUnit::Ohm => "Ohm",
179            BaseUnit::Siemens => "Siemens",
180            BaseUnit::Weber => "Weber",
181            BaseUnit::Tesla => "Tesla",
182            BaseUnit::Henry => "Henry",
183            BaseUnit::DegreeCelsius => "Degree Celsius",
184            BaseUnit::Lumen => "Lumen",
185            BaseUnit::Lux => "Lux",
186            BaseUnit::Becquerel => "Becquerel",
187            BaseUnit::Gray => "Gray",
188            BaseUnit::Sievert => "Sievert",
189            BaseUnit::Katal => "Katal",
190            BaseUnit::Named { name, .. } => name.as_str(),
191            BaseUnit::Unitless => "Unitless",
192        }
193    }
194
195    pub fn symbol(&self) -> &str {
196        match self {
197            BaseUnit::Second => "s",
198            BaseUnit::Meter => "m",
199            BaseUnit::Kilogram => "kg",
200            BaseUnit::Ampere => "A",
201            BaseUnit::Kelvin => "K",
202            BaseUnit::Mole => "mol",
203            BaseUnit::Candela => "cd",
204            BaseUnit::Radian => "rad",
205            BaseUnit::Steradian => "sr",
206            BaseUnit::Hertz => "Hz",
207            BaseUnit::Newton => "N",
208            BaseUnit::Pascal => "Pa",
209            BaseUnit::Joule => "J",
210            BaseUnit::Watt => "W",
211            BaseUnit::Coulomb => "C",
212            BaseUnit::Volt => "V",
213            BaseUnit::Farad => "F",
214            BaseUnit::Ohm => "Ω",
215            BaseUnit::Siemens => "S",
216            BaseUnit::Weber => "Wb",
217            BaseUnit::Tesla => "T",
218            BaseUnit::Henry => "H",
219            BaseUnit::DegreeCelsius => "°C",
220            BaseUnit::Lumen => "lm",
221            BaseUnit::Lux => "lx",
222            BaseUnit::Becquerel => "Bq",
223            BaseUnit::Gray => "Gy",
224            BaseUnit::Sievert => "Sv",
225            BaseUnit::Katal => "kat",
226            BaseUnit::Named { name, .. } => name.as_str(),
227            // SIUnit::NamedStatic { name, .. } => name,
228            BaseUnit::Unitless => "()",
229        }
230    }
231}
232
233#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
234pub struct SIExp(pub [i8; 7]);
235
236impl SIExp {
237    pub const fn new(
238        meter: i8,
239        kilogram: i8,
240        second: i8,
241        ampere: i8,
242        kelvin: i8,
243        mole: i8,
244        candela: i8,
245    ) -> Self {
246        SIExp([meter, kilogram, second, ampere, kelvin, mole, candela])
247    }
248
249    pub const fn new_reduced(meter: i8, kilogram: i8, second: i8, ampere: i8) -> Self {
250        SIExp([meter, kilogram, second, ampere, 0, 0, 0])
251    }
252
253    pub fn meter(&self) -> i8 {
254        self.0[0]
255    }
256
257    pub fn kilogram(&self) -> i8 {
258        self.0[1]
259    }
260
261    pub fn second(&self) -> i8 {
262        self.0[2]
263    }
264
265    pub fn ampere(&self) -> i8 {
266        self.0[3]
267    }
268
269    pub fn kelvin(&self) -> i8 {
270        self.0[4]
271    }
272
273    pub fn mole(&self) -> i8 {
274        self.0[5]
275    }
276
277    pub fn candela(&self) -> i8 {
278        self.0[6]
279    }
280}
281
282#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
283pub struct Quantity {
284    pub number: String,
285    pub unit: Unit,
286}
287
288impl Quantity {
289    pub fn parse(s: impl AsRef<str>) -> Result<Quantity, Error> {
290        let mut p = SIParser::parse(Rule::si_number, s.as_ref())?
291            .into_iter()
292            .next()
293            .ok_or(Error::Internal("expected si_number".into()))?
294            .into_inner();
295        let number = p
296            .next()
297            .ok_or(Error::Internal("expected number".into()))?
298            .as_str();
299        let unit = if let Some(unit) = p.next() {
300            parse_unit(unit)?
301        } else {
302            Unit::unit()
303        };
304        Ok(Quantity {
305            number: number.to_string(),
306            unit,
307        })
308    }
309
310    pub fn as_f32(&self) -> Result<f32, ParseFloatError> {
311        let number = self.number.parse::<f32>()?;
312        Ok(number * self.unit.prefix.as_f32())
313    }
314}
315
316fn parse_unit(p: Pair<Rule>) -> Result<Unit, Error> {
317    if !matches!(p.as_rule(), Rule::unit) {
318        return Err(Error::Internal("expected unit".into()));
319    }
320    let mut p = p.into_inner();
321    let prefix_or_raw_unit = p
322        .next()
323        .ok_or(Error::Internal("expected prefix or unit".into()))?;
324    let (prefix, raw_unit) = if prefix_or_raw_unit.as_rule() == Rule::prefix {
325        let raw_unit = p
326            .next()
327            .ok_or(Error::Internal("expected raw_unit".into()))?;
328        (parse_prefix(prefix_or_raw_unit)?, raw_unit)
329    } else {
330        (Prefix::Unit, prefix_or_raw_unit)
331    };
332    let base = parse_raw_unit(raw_unit)?;
333    let exp = if let Some(num) = p.next() {
334        parse_integer_number_i64(num)?
335    } else {
336        1
337    };
338    let exp =
339        i8::try_from(exp).map_err(|_| Error::Internal("unit exponent must fit in i8".into()))?;
340    Ok(Unit { prefix, base, exp })
341}
342
343fn parse_raw_unit(p: Pair<Rule>) -> Result<BaseUnit, Error> {
344    if !matches!(p.as_rule(), Rule::raw_unit) {
345        return Err(Error::Internal("expected raw_unit".into()));
346    }
347    let raw_unit = p.into_inner().next().ok_or(Error::Internal("".into()))?;
348    let unit = match raw_unit.as_rule() {
349        Rule::second => BaseUnit::Second,
350        Rule::meter => BaseUnit::Meter,
351        Rule::gram => BaseUnit::Kilogram, // prefix handled separately
352        Rule::ampere => BaseUnit::Ampere,
353        Rule::kelvin => BaseUnit::Kelvin,
354        Rule::mole => BaseUnit::Mole,
355        Rule::candela => BaseUnit::Candela,
356
357        Rule::radian => BaseUnit::Radian,
358        Rule::steradian => BaseUnit::Steradian,
359        Rule::hertz => BaseUnit::Hertz,
360        Rule::newton => BaseUnit::Newton,
361        Rule::pascal => BaseUnit::Pascal,
362        Rule::joule => BaseUnit::Joule,
363        Rule::watt => BaseUnit::Watt,
364        Rule::coulomb => BaseUnit::Coulomb,
365        Rule::volt => BaseUnit::Volt,
366        Rule::farad => BaseUnit::Farad,
367        Rule::ohm => BaseUnit::Ohm,
368        Rule::siemens => BaseUnit::Siemens,
369        Rule::weber => BaseUnit::Weber,
370        Rule::tesla => BaseUnit::Tesla,
371        Rule::henry => BaseUnit::Henry,
372        Rule::celsius => BaseUnit::DegreeCelsius,
373        Rule::lumen => BaseUnit::Lumen,
374        Rule::lux => BaseUnit::Lux,
375        Rule::becquerel => BaseUnit::Becquerel,
376        Rule::gray => BaseUnit::Gray,
377        Rule::sievert => BaseUnit::Sievert,
378        Rule::katal => BaseUnit::Katal,
379
380        Rule::ident => BaseUnit::Named {
381            name: raw_unit.as_str().to_string(),
382            symbol: "".to_string(),
383            exp: SIExp::new_reduced(0, 0, 0, 0),
384        },
385        _ => return Err(Error::Internal("unhandled raw_unit".into())),
386    };
387    Ok(unit)
388}
389
390pub enum SIExpr {
391    Num(String),
392    Unit { prefix: Prefix, unit: Unit },
393    Mul((Box<SIExpr>, Box<SIExpr>)),
394    Div((Box<SIExpr>, Box<SIExpr>)),
395}
396
397impl SIExpr {
398    pub fn parse(s: impl AsRef<str>) -> Result<SIExpr, Error> {
399        let p = SIParser::parse(Rule::si_expr, s.as_ref())?;
400        println!("{p:#?}");
401        // Err(Error::Internal("unimplemented".into()))
402        Ok(SIExpr::Num("".into()))
403    }
404}
405
406#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
407pub enum Prefix {
408    Milli,
409    Micro,
410    Nano,
411    Pico,
412
413    Kilo,
414    Mega,
415    Giga,
416
417    Unit,
418
419    Deca,
420    Hecto,
421    Tera,
422    Peta,
423    Exa,
424    Zetta,
425    Yotta,
426    Ronna,
427    Quetta,
428
429    Deci,
430    Centi,
431    Femto,
432    Atto,
433    Zepto,
434    Yocto,
435    Ronto,
436    Quecto,
437}
438
439pub struct OhmF32(pub f32);
440
441#[derive(Debug)]
442pub enum Error {
443    Pest(pest::error::Error<Rule>),
444    Float(ParseFloatError),
445    Int(ParseIntError),
446    Internal(String),
447}
448
449impl From<pest::error::Error<Rule>> for Error {
450    fn from(e: pest::error::Error<Rule>) -> Self {
451        Error::Pest(e)
452    }
453}
454
455impl From<ParseFloatError> for Error {
456    fn from(e: ParseFloatError) -> Self {
457        Error::Float(e)
458    }
459}
460
461impl From<ParseIntError> for Error {
462    fn from(e: ParseIntError) -> Self {
463        Error::Int(e)
464    }
465}
466
467impl Display for Error {
468    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
469        match self {
470            Error::Pest(e) => write!(f, "{}", e),
471            Error::Float(e) => write!(f, "{}", e),
472            Error::Int(e) => write!(f, "{}", e),
473            Error::Internal(e) => write!(f, "Internal: {}", e),
474        }
475    }
476}
477
478fn expected_rule(name: &'static str) -> Error {
479    Error::Internal(name.to_string())
480}
481
482impl Prefix {
483    pub fn as_f32(&self) -> f32 {
484        match self {
485            Prefix::Milli => 1e-3,
486            Prefix::Micro => 1e-6,
487            Prefix::Nano => 1e-9,
488            Prefix::Pico => 1e-12,
489            Prefix::Kilo => 1e3,
490            Prefix::Mega => 1e6,
491            Prefix::Giga => 1e9,
492            Prefix::Unit => 1e0,
493            Prefix::Deca => 1e1,
494            Prefix::Hecto => 1e2,
495            Prefix::Tera => 1e12,
496            Prefix::Peta => 1e15,
497            Prefix::Exa => 1e18,
498            Prefix::Zetta => 1e21,
499            Prefix::Yotta => 1e24,
500            Prefix::Ronna => 1e27,
501            Prefix::Quetta => 1e30,
502            Prefix::Deci => 1e-1,
503            Prefix::Centi => 1e-2,
504            Prefix::Femto => 1e-15,
505            Prefix::Atto => 1e-18,
506            Prefix::Zepto => 1e-21,
507            Prefix::Yocto => 1e-24,
508            Prefix::Ronto => 1e-27,
509            Prefix::Quecto => 1e-30,
510        }
511    }
512
513    pub fn symbol(&self) -> &'static str {
514        match self {
515            Prefix::Milli => "m",
516            Prefix::Micro => "μ", // U+03BC
517            Prefix::Nano => "n",
518            Prefix::Pico => "p",
519            Prefix::Kilo => "k",
520            Prefix::Mega => "M",
521            Prefix::Giga => "G",
522            Prefix::Unit => "",
523            Prefix::Deca => "da",
524            Prefix::Hecto => "h",
525            Prefix::Tera => "T",
526            Prefix::Peta => "P",
527            Prefix::Exa => "E",
528            Prefix::Zetta => "Z",
529            Prefix::Yotta => "Y",
530            Prefix::Ronna => "R",
531            Prefix::Quetta => "Q",
532            Prefix::Deci => "d",
533            Prefix::Centi => "c",
534            Prefix::Femto => "f",
535            Prefix::Atto => "a",
536            Prefix::Zepto => "z",
537            Prefix::Yocto => "y",
538            Prefix::Ronto => "r",
539            Prefix::Quecto => "q",
540        }
541    }
542}
543
544impl Display for Prefix {
545    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
546        write!(f, "{}", self.symbol())
547    }
548}
549
550#[derive(Parser)]
551#[grammar = "grammar/si.pest"]
552struct SIParser;
553
554impl OhmF32 {
555    pub fn parse(s: impl AsRef<str>) -> Result<OhmF32, Error> {
556        let p = SIParser::parse(Rule::resistance, s.as_ref())?;
557        // println!("{p:#?}");
558        let mut resistance = p
559            .into_iter()
560            .next()
561            .ok_or(expected_rule("resistance"))?
562            .into_inner();
563        let value = resistance
564            .next()
565            .ok_or(expected_rule("number_prefix_allowed"))?;
566        let value = parse_any_number_f32(value)?;
567
568        if resistance.peek().map(|p| p.as_rule()) == Some(Rule::EOI) {
569            return Ok(OhmF32(value));
570        }
571        let p = resistance
572            .next()
573            .ok_or(expected_rule("ohm_unit"))?
574            .into_inner()
575            .next()
576            .ok_or(expected_rule("prefix or ohm_or_r"))?;
577        let prefix = match p.as_rule() {
578            Rule::r_prefix => parse_prefix(p)?,
579            Rule::ohm_or_r => Prefix::Unit,
580            _ => return Err(Error::Internal("wrong resistance rule".to_owned())),
581        };
582
583        Ok(OhmF32(value * prefix.as_f32()))
584    }
585}
586
587fn parse_any_number_f32(num: Pair<Rule>) -> Result<f32, Error> {
588    if !matches!(num.as_rule(), Rule::number | Rule::r_number) {
589        return Err(Error::Internal(
590            "expected number or number_prefix_allowed".into(),
591        ));
592    }
593    let mut p = num.into_inner();
594    let int = parse_integer_number_i64(p.next().ok_or(expected_rule("integer_number"))?)?;
595    let Some(tail) = p.next() else {
596        return Ok(int as f32);
597    };
598    match tail.as_rule() {
599        Rule::exponent => {
600            let exp = parse_integer_number_i64(p.next().ok_or(expected_rule("simple_number"))?)?;
601            let exp: u32 = exp
602                .try_into()
603                .map_err(|_| Error::Internal("exponent is too big".into()))?;
604            Ok(int as f32 * 10u64.pow(exp) as f32)
605        }
606        Rule::raw_number => {
607            let frac: f32 = format!("0.{}", tail.as_str()).parse()?;
608            let exp = if let Some(p) = p.next() {
609                let exp = parse_integer_number_i64(p)?;
610                let exp: u32 = exp
611                    .try_into()
612                    .map_err(|_| Error::Internal("exponent is too big".into()))?;
613                exp
614            } else {
615                0
616            };
617            Ok((int as f32 + frac) * 10u64.pow(exp) as f32)
618        }
619        Rule::prefix | Rule::r_prefix => {
620            let prefix = parse_prefix(tail)?;
621            let frac = if let Some(frac) = p.next() {
622                let frac: f32 = format!("0.{}", frac.as_str()).parse()?;
623                frac
624            } else {
625                0.0
626            };
627            Ok((int as f32 + frac) * prefix.as_f32())
628        }
629        _ => Err(Error::Internal("unexpected rule".into())),
630    }
631}
632
633fn parse_integer_number_i64(rule: Pair<Rule>) -> Result<i64, Error> {
634    if rule.as_rule() != Rule::integer_number {
635        return Err(Error::Internal("expected simple_number".into()));
636    }
637    let mut p = rule.into_inner();
638    let minus_or_raw_number = p.next().ok_or(expected_rule("minus or raw_number"))?;
639    let is_minus = minus_or_raw_number.as_rule() == Rule::minus;
640    let int = if is_minus {
641        let int = p.next().ok_or(expected_rule("integer"))?.into_inner();
642        int.as_str()
643    } else {
644        minus_or_raw_number.as_str()
645    };
646    let v: i64 = int.parse()?;
647    if is_minus { Ok(-v) } else { Ok(v) }
648}
649
650fn parse_prefix(rule: Pair<Rule>) -> Result<Prefix, Error> {
651    let rule = rule.into_inner().next().ok_or(expected_rule("prefix"))?;
652    let prefix = match rule.as_rule() {
653        Rule::quetta => Prefix::Quetta,
654        Rule::ronna => Prefix::Ronna,
655        Rule::yotta => Prefix::Yotta,
656        Rule::zetta => Prefix::Zetta,
657        Rule::exa => Prefix::Exa,
658        Rule::peta => Prefix::Peta,
659        Rule::tera => Prefix::Tera,
660        Rule::tera_unconditional => Prefix::Tera,
661        Rule::giga => Prefix::Giga,
662        Rule::giga_unconditional => Prefix::Giga,
663        Rule::mega => Prefix::Mega,
664        Rule::mega_unconditional => Prefix::Mega,
665        Rule::kilo => Prefix::Kilo,
666        Rule::kilo_unconditional => Prefix::Kilo,
667        Rule::hecto => Prefix::Hecto,
668        Rule::deca => Prefix::Deca,
669        Rule::deci => Prefix::Deci,
670        Rule::centi => Prefix::Centi,
671        Rule::milli => Prefix::Milli,
672        Rule::milli_unconditional => Prefix::Milli,
673        Rule::micro => Prefix::Micro,
674        Rule::micro_unconditional => Prefix::Micro,
675        Rule::nano => Prefix::Nano,
676        Rule::nano_unconditional => Prefix::Nano,
677        Rule::pico => Prefix::Pico,
678        Rule::femto => Prefix::Femto,
679        Rule::atto => Prefix::Atto,
680        Rule::zepto => Prefix::Zepto,
681        Rule::yocto => Prefix::Yocto,
682        Rule::ronto => Prefix::Ronto,
683        Rule::quecto => Prefix::Quecto,
684        _ => return Err(Error::Internal("expected prefix rule".into())),
685    };
686    Ok(prefix)
687}
688
689impl Display for BaseUnit {
690    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
691        write!(f, "{}", self.symbol())
692    }
693}
694
695#[cfg(test)]
696mod tests {
697    use super::*;
698    use float_cmp::approx_eq;
699
700    #[test]
701    fn expr() {
702        // let pairs = SIParser::parse(Rule::si_number, "10kg").unwrap();
703        // println!("{pairs:#?}");
704        // let pairs = SIParser::parse(Rule::si_number, "10 V/s").unwrap();
705        // println!("{pairs:#?}");
706        // let _expr = SIExpr::parse("10*V/us").unwrap();
707        // println!("{pairs:#?}");
708        // let v = SIValueF32::parse("9.80665 m/s²").unwrap();
709        // assert_eq!(v.value, 9.80665);
710        // assert_eq!(v.unit, SIUnit::new_reduced_exp(-2, 1, 0, 0));
711        // let _expr = SIExpr::parse("1 m/s⁻²").unwrap();
712        // println!("{pairs:#?}");
713        // let pairs = SIParser::parse(Rule::si_number, "9.8 m/s^2").unwrap();
714        // println!("{pairs:#?}");
715        // let pairs = SIParser::parse(Rule::si_number, "1 m/s^-2").unwrap();
716        // println!("{pairs:#?}");
717    }
718
719    #[test]
720    fn si_number_voltage() {
721        let num = Quantity::parse("10V").unwrap();
722        assert_eq!(num.number, "10");
723        assert_eq!(num.unit.prefix, Prefix::Unit);
724        assert_eq!(num.unit.base, BaseUnit::Volt);
725        assert_eq!(num.unit.exp, 1);
726        assert!(approx_eq!(f32, num.as_f32().unwrap(), 10.0, ulps = 2));
727
728        let num = Quantity::parse("10mV").unwrap();
729        assert_eq!(num.number, "10");
730        assert_eq!(num.unit.prefix, Prefix::Milli);
731        assert_eq!(num.unit.base, BaseUnit::Volt);
732        assert_eq!(num.unit.exp, 1);
733        assert!(approx_eq!(f32, num.as_f32().unwrap(), 0.01, ulps = 2));
734
735        let num = Quantity::parse("10mVDC").unwrap();
736        assert_eq!(num.number, "10");
737        assert_eq!(num.unit.prefix, Prefix::Milli);
738        assert_eq!(num.unit.base.name(), "VDC");
739    }
740
741    #[test]
742    fn si_number_capacitance() {
743        let num = Quantity::parse("4.7 uF").unwrap();
744        assert_eq!(num.number, "4.7");
745        assert_eq!(num.unit.prefix, Prefix::Micro);
746        assert_eq!(num.unit.base, BaseUnit::Farad);
747        assert_eq!(num.unit.exp, 1);
748        assert!(approx_eq!(f32, num.as_f32().unwrap(), 4.7e-6, ulps = 2));
749
750        let num = Quantity::parse("4.7 µF").unwrap();
751        assert_eq!(num.number, "4.7");
752        assert_eq!(num.unit.prefix, Prefix::Micro);
753        assert_eq!(num.unit.base, BaseUnit::Farad);
754        assert_eq!(num.unit.exp, 1);
755        assert!(approx_eq!(f32, num.as_f32().unwrap(), 4.7e-6, ulps = 2));
756    }
757
758    #[test]
759    fn expr_leading_number() {
760        let _expr = SIExpr::parse("10*V/us").unwrap();
761        let _expr = SIExpr::parse("10 V/us").unwrap();
762        let _expr = SIExpr::parse("  10 V/us").unwrap();
763        let _expr = SIExpr::parse(" 10V ").unwrap();
764        let _expr = SIExpr::parse("10").unwrap();
765    }
766
767    #[test]
768    fn resistance_values() {
769        let v = OhmF32::parse("100").unwrap();
770        assert_eq!(v.0, 100.0);
771
772        let v = OhmF32::parse("5k").unwrap();
773        assert_eq!(v.0, 5000.0);
774
775        let v = OhmF32::parse("5M").unwrap();
776        assert_eq!(v.0, 5_000_000.0);
777
778        let v = OhmF32::parse("5G").unwrap();
779        assert_eq!(v.0, 5_000_000_000.0);
780
781        let v = OhmF32::parse("1R").unwrap();
782        assert_eq!(v.0, 1.0);
783
784        let v = OhmF32::parse("15.5").unwrap();
785        assert_eq!(v.0, 15.5);
786
787        let v = OhmF32::parse("5.55R").unwrap();
788        assert_eq!(v.0, 5.55);
789
790        let v = OhmF32::parse("1.0k").unwrap();
791        assert_eq!(v.0, 1000.0);
792
793        let v = OhmF32::parse("1k2").unwrap();
794        assert_eq!(v.0, 1200.0);
795
796        let v = OhmF32::parse("10 kΩ").unwrap();
797        assert_eq!(v.0, 10_000.0);
798
799        let v = OhmF32::parse("5Ω").unwrap();
800        assert_eq!(v.0, 5.0);
801
802        let v = OhmF32::parse("1 mΩ").unwrap();
803        assert_eq!(v.0, 0.001);
804
805        let v = OhmF32::parse("1μΩ").unwrap();
806        assert_eq!(v.0, 0.000001);
807
808        let v = OhmF32::parse(" 0 ").unwrap();
809        assert_eq!(v.0, 0.0);
810
811        let v = OhmF32::parse(" 0  R ").unwrap();
812        assert_eq!(v.0, 0.0);
813
814        let v = OhmF32::parse("49r").unwrap();
815        assert_eq!(v.0, 49.0);
816
817        let v = OhmF32::parse(" 499kR ").unwrap();
818        assert_eq!(v.0, 499_000.0);
819
820        let v = OhmF32::parse("12 kOhms").unwrap();
821        assert_eq!(v.0, 12000.0);
822
823        let v = OhmF32::parse("2 mOhms").unwrap();
824        assert_eq!(v.0, 0.002);
825    }
826}