scpi/parser/
suffix.rs

1//! Handle decimal-data suffixes
2
3#[allow(unused_imports)]
4use {
5    crate::{
6        error::{Error, ErrorCode},
7        parser::{
8            response::{Formatter, ResponseData},
9            tokenizer::Token,
10        },
11    },
12    core::convert::{TryFrom, TryInto},
13};
14
15//use crate::scpi1999::numeric::NumericValueDefaults;
16
17/// A logarithmic or linear unit
18pub enum Db<V, UNIT> {
19    /// No suffix provided, unknown if linear or log
20    None(V),
21    /// Linear suffix provided
22    Linear(UNIT),
23    /// Log suffix provided
24    Logarithmic(V, UNIT),
25}
26
27/// Amplitude specifier
28pub enum Amplitude<UNIT> {
29    /// No amplitude specifier
30    None(UNIT),
31    /// `<UNIT>PK`
32    ///
33    /// Example: `VPK`
34    Peak(UNIT),
35    /// `<UNIT>PP`
36    ///
37    /// Example: `VPP`
38    PeakToPeak(UNIT),
39    /// `<UNIT>RMS`
40    ///
41    /// Example: `VRMS`
42    Rms(UNIT),
43}
44
45impl<'a, UNIT> TryFrom<Token<'a>> for Amplitude<UNIT>
46where
47    UNIT: TryFrom<Token<'a>, Error = Error>,
48{
49    type Error = Error;
50
51    fn try_from(value: Token<'a>) -> Result<Self, Self::Error> {
52        fn ends_with_ignore_ascii(str: &[u8], needle: &[u8]) -> bool {
53            let (m, n) = (str.len(), needle.len());
54            m >= n && needle.eq_ignore_ascii_case(&str[m - n..])
55        }
56
57        match value {
58            Token::DecimalNumericSuffixProgramData(num, s) if ends_with_ignore_ascii(s, b"PK") => {
59                Ok(Self::Peak(<UNIT>::try_from(
60                    Token::DecimalNumericSuffixProgramData(num, &s[..s.len() - 2]),
61                )?))
62            }
63            Token::DecimalNumericSuffixProgramData(num, s) if ends_with_ignore_ascii(s, b"PP") => {
64                Ok(Self::PeakToPeak(<UNIT>::try_from(
65                    Token::DecimalNumericSuffixProgramData(num, &s[..s.len() - 2]),
66                )?))
67            }
68            Token::DecimalNumericSuffixProgramData(num, s) if ends_with_ignore_ascii(s, b"RMS") => {
69                Ok(Self::Rms(<UNIT>::try_from(
70                    Token::DecimalNumericSuffixProgramData(num, &s[..s.len() - 3]),
71                )?))
72            }
73            _ => Ok(Self::None(<UNIT>::try_from(value)?)),
74        }
75    }
76}
77
78#[cfg(feature = "unit-angle")]
79mod angle {
80    use super::*;
81    use uom::si::angle::{degree, gon, minute, radian, revolution, second, Angle};
82
83    impl_unit![uom::si::angle::Conversion<V>, Angle, radian;
84        b"RAD" => radian,
85        b"DEG" => degree,
86        b"MNT" => minute,
87        b"SEC" => second,
88        b"REV" => revolution,
89        b"GON" => gon
90    ];
91}
92
93#[cfg(feature = "unit-capacitance")]
94mod capacitance {
95    use super::*;
96    use uom::si::capacitance::{farad, microfarad, millifarad, nanofarad, picofarad, Capacitance};
97
98    impl_unit![uom::si::capacitance::Conversion<V>, Capacitance, farad;
99        b"F" => farad,
100        b"MF" => millifarad,
101        b"UF" => microfarad,
102        b"NF" => nanofarad,
103        b"PF" => picofarad
104    ];
105}
106
107#[cfg(feature = "unit-electric-charge")]
108mod electric_charge {
109    use super::*;
110    use uom::si::electric_charge::{
111        ampere_hour, coulomb, kilocoulomb, megacoulomb, microcoulomb, milliampere_hour,
112        millicoulomb, ElectricCharge,
113    };
114
115    impl_unit![uom::si::electric_charge::Conversion<V>, ElectricCharge, coulomb;
116        //Coloumb
117        b"MAC" => megacoulomb,
118        b"KC" => kilocoulomb,
119        b"C" => coulomb,
120        b"MC" => millicoulomb,
121        b"UC" => microcoulomb,
122        //Ampere hour
123        b"AH"|b"A.HR" => ampere_hour,
124        b"MAH"|b"MA.HR" => milliampere_hour
125    ];
126}
127
128#[cfg(feature = "unit-electric-current")]
129mod electric_current {
130    use super::*;
131    use uom::si::electric_current::{
132        ampere, kiloampere, microampere, milliampere, nanoampere, ElectricCurrent,
133    };
134
135    impl_unit![uom::si::electric_current::Conversion<V>, ElectricCurrent, ampere;
136        b"KA" => kiloampere,
137        b"A" => ampere,
138        b"MA" => milliampere,
139        b"UA" => microampere,
140        b"NA" => nanoampere
141    ];
142
143    impl_logarithmic_unit![uom::si::electric_current::Conversion<V>, ElectricCurrent;
144        b"DBA" => ampere,
145        b"DBMA" => milliampere,
146        b"DBUA" => microampere
147    ];
148}
149
150#[cfg(feature = "unit-electric-potential")]
151mod electric_potential {
152    use super::*;
153    use uom::si::electric_potential::{kilovolt, microvolt, millivolt, volt, ElectricPotential};
154
155    impl_unit![uom::si::electric_potential::Conversion<V>, ElectricPotential, volt;
156        b"KV" => kilovolt,
157        b"V" => volt,
158        b"MV" => millivolt,
159        b"UV" => microvolt
160    ];
161
162    impl_logarithmic_unit![uom::si::electric_potential::Conversion<V>, ElectricPotential;
163        b"DBV" => volt,
164        b"DBMV" => millivolt,
165        b"DBUV" => microvolt
166    ];
167}
168
169#[cfg(feature = "unit-electrical-conductance")]
170mod electrical_conductance {
171    use super::*;
172    use uom::si::electrical_conductance::{
173        kilosiemens, microsiemens, millisiemens, siemens, ElectricalConductance,
174    };
175
176    impl_unit![uom::si::electrical_conductance::Conversion<V>, ElectricalConductance, siemens;
177        b"KSIE" => kilosiemens,
178        b"SIE" => siemens,
179        b"MSIE" => millisiemens,
180        b"USIE" => microsiemens
181    ];
182}
183
184#[cfg(feature = "unit-electrical-resistance")]
185mod electrical_resistance {
186    use super::*;
187
188    use uom::si::electrical_resistance::{
189        gigaohm, kiloohm, megaohm, microohm, ohm, ElectricalResistance,
190    };
191    impl_unit![uom::si::electrical_resistance::Conversion<V>, ElectricalResistance, ohm;
192        b"GOHM" => gigaohm,
193        b"MOHM" => megaohm,
194        b"KOHM" => kiloohm,
195        b"OHM" => ohm,
196        b"UOHM" => microohm
197    ];
198}
199
200#[cfg(feature = "unit-energy")]
201mod energy {
202    use super::*;
203    use uom::si::energy::{
204        electronvolt, joule, kilojoule, megajoule, megawatt_hour, microjoule, milliwatt_hour,
205        watt_hour, Energy,
206    };
207
208    impl_unit![uom::si::energy::Conversion<V>, Energy, joule;
209        b"MAJ" => megajoule,
210        b"KJ" => kilojoule,
211        b"J" => joule,
212        b"MJ" => megajoule,
213        b"UJ" => microjoule,
214        // Watt-hour
215        b"MAW.HR" => megawatt_hour,
216        b"WH"|b"W.HR" => watt_hour,
217        b"MW.HR" => milliwatt_hour,
218        // Electronvolt
219        b"EV" => electronvolt
220    ];
221}
222
223#[cfg(feature = "unit-inductance")]
224mod inductance {
225    use super::*;
226    use uom::si::inductance::{henry, microhenry, millihenry, nanohenry, picohenry, Inductance};
227
228    impl_unit![uom::si::inductance::Conversion<V>, Inductance, henry;
229        b"H" => henry,
230        b"MH" => millihenry,
231        b"UH" => microhenry,
232        b"NH" => nanohenry,
233        b"PH" => picohenry
234    ];
235}
236
237#[cfg(feature = "unit-power")]
238mod power {
239    use super::*;
240    use uom::si::power::{kilowatt, megawatt, microwatt, milliwatt, watt, Power};
241
242    impl_unit![uom::si::power::Conversion<V>, Power, watt;
243        b"MAW" => megawatt,
244        b"KW" => kilowatt,
245        b"W" => watt,
246        b"MW" => milliwatt,
247        b"UW" => microwatt
248    ];
249
250    impl_logarithmic_unit![uom::si::power::Conversion<V>, Power;
251        b"DBW" => watt,
252        b"DBMW" | b"DBM" => milliwatt,
253        b"DBUW" => microwatt
254    ];
255}
256
257#[cfg(feature = "unit-ratio")]
258mod ratio {
259    use super::*;
260    use uom::si::ratio::{part_per_million, percent, ratio, Ratio};
261
262    impl_unit![uom::si::ratio::Conversion<V>, Ratio, ratio;
263        b"PCT" => percent,
264        b"PPM" => part_per_million
265    ];
266
267    impl_logarithmic_unit![uom::si::ratio::Conversion<V>, Ratio;
268        b"DB" => ratio
269    ];
270}
271
272#[cfg(feature = "unit-thermodynamic-temperature")]
273mod thermodynamic_temperature {
274    use super::*;
275    use uom::si::thermodynamic_temperature::{
276        degree_celsius, degree_fahrenheit, kelvin, ThermodynamicTemperature,
277    };
278
279    impl_unit![uom::si::thermodynamic_temperature::Conversion<V>, ThermodynamicTemperature, degree_celsius;
280        b"CEL" => degree_celsius,
281        b"FAR" => degree_fahrenheit,
282        b"K" => kelvin
283    ];
284}
285
286#[cfg(feature = "unit-time")]
287mod time {
288    use super::*;
289    use uom::si::time::{
290        day, hour, microsecond, millisecond, minute, nanosecond, second, year, Time,
291    };
292
293    impl_unit![uom::si::time::Conversion<V>, Time, second;
294        b"S" => second,
295        b"MS" => millisecond,
296        b"US" => microsecond,
297        b"NS" => nanosecond,
298        b"MIN" => minute,
299        b"HR" => hour,
300        b"D" => day,
301        b"ANN" => year
302    ];
303}
304
305#[cfg(feature = "unit-frequency")]
306mod frequency {
307    use super::*;
308    use uom::si::frequency::{gigahertz, hertz, kilohertz, megahertz, Frequency};
309
310    impl_unit![uom::si::frequency::Conversion<V>, Frequency, hertz;
311        b"GHZ" => gigahertz,
312        b"MHZ" | b"MAHZ" => megahertz,
313        b"KHZ" => kilohertz,
314        b"HZ" => hertz
315    ];
316}
317
318#[cfg(all(feature = "unit-electric-potential", test))]
319mod test_suffix {
320
321    extern crate std;
322
323    use crate::{
324        parser::suffix::{Amplitude, Db},
325        //scpi1999::numeric::NumericValueDefaults,
326        tree::prelude::*,
327    };
328    use core::convert::TryInto;
329    use uom::si::f32::*;
330
331    #[test]
332    fn test_suffix_amplitude() {
333        let none: Amplitude<ElectricPotential> =
334            Token::DecimalNumericSuffixProgramData(b"1.0", b"V")
335                .try_into()
336                .unwrap();
337        assert!(matches!(none, Amplitude::None(_)));
338        let peak: Amplitude<ElectricPotential> =
339            Token::DecimalNumericSuffixProgramData(b"1.0", b"VPK")
340                .try_into()
341                .unwrap();
342        assert!(matches!(peak, Amplitude::Peak(_)));
343        let peak_to_peak: Amplitude<ElectricPotential> =
344            Token::DecimalNumericSuffixProgramData(b"1.0", b"VPP")
345                .try_into()
346                .unwrap();
347        assert!(matches!(peak_to_peak, Amplitude::PeakToPeak(_)));
348        let rms: Amplitude<ElectricPotential> =
349            Token::DecimalNumericSuffixProgramData(b"1.0", b"VRMS")
350                .try_into()
351                .unwrap();
352        assert!(matches!(rms, Amplitude::Rms(_)))
353    }
354
355    #[test]
356    fn test_suffix_logarithmic() {
357        let none: Db<f32, ElectricPotential> =
358            Token::DecimalNumericProgramData(b"1.0").try_into().unwrap();
359        assert!(matches!(none, Db::None(_)));
360        let peak: Db<f32, ElectricPotential> = Token::DecimalNumericSuffixProgramData(b"1.0", b"V")
361            .try_into()
362            .unwrap();
363        assert!(matches!(peak, Db::Linear(_)));
364        let peak_to_peak: Db<f32, ElectricPotential> =
365            Token::DecimalNumericSuffixProgramData(b"1.0", b"DBV")
366                .try_into()
367                .unwrap();
368        assert!(matches!(peak_to_peak, Db::Logarithmic(_, _)));
369    }
370
371    // #[test]
372    // fn test_suffix_numeric_value() {
373    //     let volt_max = ElectricPotential::numeric_value_max();
374    //     assert_eq!(volt_max.value, f32::MAX)
375    // }
376}
377
378#[allow(unused_macros)]
379macro_rules! impl_logarithmic_unit {
380    ($conversion:path, $unit:ident; $($($suffix:literal)|+ => $subunit:ident),+) => {
381        impl<'a, U, V> TryFrom<Token<'a>> for Db<V,$unit<U, V>>
382        where
383            U: Units<V> + ?Sized,
384            V: Num + uom::Conversion<V> + TryFrom<Token<'a>,Error=Error>,
385            $unit<U, V>: TryFrom<Token<'a>,Error=Error>,
386            $(
387            $subunit: $conversion
388            ),+
389        {
390            type Error = Error;
391
392            fn try_from(value: Token<'a>) -> Result<Self, Self::Error> {
393                if let Token::DecimalNumericProgramData(_) = value {
394                    Ok(Self::None(<V>::try_from(value)?))
395                } else if let Token::DecimalNumericSuffixProgramData(num, suffix) = value {
396                    match suffix {
397                        $(
398                        s if $(s.eq_ignore_ascii_case($suffix))||+ => Ok(Self::Logarithmic(
399                            <V>::try_from(Token::DecimalNumericProgramData(num))?,
400                            <$unit<U,V>>::new::<$subunit>(V::one())
401                        ))
402                        ),+,
403                        _ => Ok(Self::Linear(<$unit<U, V>>::try_from(value)?))
404                    }
405                } else {
406                    Err(ErrorCode::DataTypeError.into())
407                }
408            }
409        }
410
411        //TODO: Test log parsing
412    };
413}
414pub(crate) use impl_logarithmic_unit;
415
416#[allow(unused_macros)]
417macro_rules! impl_unit {
418    ($conversion:path, $unit:ident, $base:ident; $($($suffix:literal)|+ => $subunit:ident),+) => {
419        use uom::{num_traits::Num, si::Units, Conversion};
420
421        impl<'a, U, V> TryFrom<Token<'a>> for $unit<U, V>
422        where
423            U: Units<V> + ?Sized,
424            V: Num + uom::Conversion<V> + TryFrom<Token<'a>, Error=Error>,
425            $base: $conversion,
426            $(
427            $subunit: $conversion
428            ),+
429        {
430            type Error = Error;
431
432            fn try_from(value: Token<'a>) -> Result<Self, Self::Error> {
433                if let Token::DecimalNumericProgramData(_) = value {
434                    Ok($unit::new::<$base>(<V>::try_from(value)?))
435                } else if let Token::DecimalNumericSuffixProgramData(num, suffix) = value
436                {
437                    match suffix {
438                        $(
439                        s if $(s.eq_ignore_ascii_case($suffix))||+ => Ok($unit::new::<$subunit>(
440                            <V>::try_from(Token::DecimalNumericProgramData(num))?,
441                        ))
442                        ),+,
443                        _ => Err(ErrorCode::IllegalParameterValue.into()),
444                    }
445                } else {
446                    Err(ErrorCode::DataTypeError.into())
447                }
448            }
449        }
450
451        impl<U, V> ResponseData for $unit<U, V> where U: Units<V> + ?Sized, V: Num + Conversion<V> + ResponseData {
452            fn format_response_data(
453                &self,
454                formatter: &mut dyn Formatter,
455            ) -> core::result::Result<(), Error> {
456                self.value.format_response_data(formatter)
457            }
458        }
459
460        // impl<U, V, T> NumericValueDefaults for $unit<U, V>
461        // where
462        //     U: Units<V> + ?Sized,
463        //     V: Num + Conversion<V, T = T> + NumericValueDefaults,
464        //     $base: Conversion<V, T = T>,
465        // {
466        //     fn numeric_value_max() -> Self {
467        //         Self::new::<$base>(V::numeric_value_max())
468        //     }
469
470        //     fn numeric_value_min() -> Self {
471        //         Self::new::<$base>(V::numeric_value_min())
472        //     }
473        // }
474
475        #[cfg(test)]
476        #[allow(non_snake_case)]
477        mod tests {
478
479            extern crate std;
480
481            use crate::{tree::prelude::*};
482            use core::convert::TryInto;
483            use uom::si::f32::*;
484
485            #[test]
486            fn test_suffix_correct() {
487                $(
488                $(
489                let l: $unit = Token::DecimalNumericSuffixProgramData(b"1.0", $suffix)
490                    .try_into()
491                    .unwrap();
492                assert_eq!(l.get::<super::$subunit>(), 1.0f32);
493                )+
494                )+
495
496            }
497
498            #[test]
499            fn test_suffix_incorrect() {
500                // Do not accept incorrect suffix
501                let l: Result<$unit, Error> = Token::DecimalNumericSuffixProgramData(b"1.0", b"POTATO")
502                    .try_into();
503                assert_eq!(l, Err(Error::from(ErrorCode::IllegalParameterValue)));
504                // Do not accept incorrect datatype
505                let l: Result<$unit, Error> = Token::StringProgramData(b"STRING").try_into();
506                assert_eq!(l, Err(Error::from(ErrorCode::DataTypeError)))
507                // Do not accept
508            }
509
510            #[test]
511            fn test_suffix_default() {
512                let l: $unit = Token::DecimalNumericProgramData(b"1.0").try_into().unwrap();
513                assert_eq!(l.get::< super::$base >(), 1.0f32)
514            }
515        }
516    };
517}
518pub(crate) use impl_unit;