rs_teststand/expression/decimal_point.rs
1//! Which decimal separator expression text uses.
2
3/// How a decimal point is written when converting expression text
4/// (`DecimalPointLocalizationOptions`).
5///
6/// Expression text is not locale-neutral. `1.5` and `1,5` are the same number
7/// written for different stations, so converting between the stored form and
8/// the form an operator reads has to say which convention is meant.
9///
10/// There is no zero variant, deliberately: the engine numbers these from one
11/// and rejects zero with `TS_Err_ValueIsInvalidOrOutOfRange`. Passing a raw
12/// integer is what makes that mistake easy, which is why these members take
13/// this type instead.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15#[non_exhaustive]
16#[expect(
17 clippy::enum_variant_names,
18 reason = "the shared Use prefix is the vendor's own: DecimalPoint_UsePeriod, \
19 DecimalPoint_UseComma. Stripping it to satisfy the lint would leave \
20 names that no longer match the reference a reader is holding."
21)]
22pub enum DecimalPointLocalizationOption {
23 /// Follow the station's TestStand preference.
24 UsePreference,
25 /// Follow the operating system's regional setting.
26 UseSystemSetting,
27 /// Always a period, whatever the station is set to.
28 UsePeriod,
29 /// Always a comma, whatever the station is set to.
30 UseComma,
31}
32
33impl DecimalPointLocalizationOption {
34 /// Maps the engine's number onto an option.
35 ///
36 /// # Errors
37 /// The raw value, when it is one this build does not name.
38 pub const fn from_bits(bits: i32) -> Result<Self, i32> {
39 match bits {
40 1 => Ok(Self::UsePreference),
41 2 => Ok(Self::UseSystemSetting),
42 3 => Ok(Self::UsePeriod),
43 4 => Ok(Self::UseComma),
44 other => Err(other),
45 }
46 }
47
48 /// The number the engine expects.
49 #[must_use]
50 pub const fn bits(self) -> i32 {
51 match self {
52 Self::UsePreference => 1,
53 Self::UseSystemSetting => 2,
54 Self::UsePeriod => 3,
55 Self::UseComma => 4,
56 }
57 }
58}
59
60#[cfg(test)]
61mod tests {
62 use super::DecimalPointLocalizationOption;
63
64 #[test]
65 fn every_option_round_trips_through_the_engine_numbering() {
66 for option in [
67 DecimalPointLocalizationOption::UsePreference,
68 DecimalPointLocalizationOption::UseSystemSetting,
69 DecimalPointLocalizationOption::UsePeriod,
70 DecimalPointLocalizationOption::UseComma,
71 ] {
72 assert_eq!(
73 DecimalPointLocalizationOption::from_bits(option.bits()),
74 Ok(option),
75 );
76 }
77 }
78
79 #[test]
80 fn zero_is_rejected_rather_than_silently_accepted() {
81 // The engine numbers these from one. Zero reaching the engine returns
82 // TS_Err_ValueIsInvalidOrOutOfRange, measured, so it must not map to a
83 // variant here either.
84 assert_eq!(DecimalPointLocalizationOption::from_bits(0), Err(0));
85 }
86}