dbc_rs/signal/
serialize.rs

1use super::Signal;
2use crate::Receivers;
3use std::{
4    fmt::{Display, Formatter, Result},
5    string::String,
6};
7
8impl Signal {
9    #[must_use = "return value should be used"]
10    pub fn to_dbc_string(&self) -> String {
11        let mut result = String::with_capacity(100); // Pre-allocate reasonable capacity
12
13        result.push_str(" SG_ ");
14        result.push_str(self.name());
15        result.push_str(" : ");
16        result.push_str(&self.start_bit().to_string());
17        result.push('|');
18        result.push_str(&self.length().to_string());
19        result.push('@');
20
21        // Byte order: 0 for BigEndian (Motorola), 1 for LittleEndian (Intel)
22        // Per Vector DBC spec v1.0.1: "Big endian is stored as '0', little endian is stored as '1'"
23        match self.byte_order() {
24            crate::ByteOrder::BigEndian => result.push('0'), // @0 = Big Endian (Motorola)
25            crate::ByteOrder::LittleEndian => result.push('1'), // @1 = Little Endian (Intel)
26        }
27
28        // Sign: + for unsigned, - for signed
29        if self.is_unsigned() {
30            result.push('+');
31        } else {
32            result.push('-');
33        }
34
35        // Factor and offset: (factor,offset)
36        result.push_str(" (");
37        use core::fmt::Write;
38        write!(result, "{}", self.factor()).unwrap();
39        result.push(',');
40        write!(result, "{}", self.offset()).unwrap();
41        result.push(')');
42
43        // Min and max: [min|max]
44        result.push_str(" [");
45        write!(result, "{}", self.min()).unwrap();
46        result.push('|');
47        write!(result, "{}", self.max()).unwrap();
48        result.push(']');
49
50        // Unit: "unit" or ""
51        result.push(' ');
52        if let Some(unit) = self.unit() {
53            result.push('"');
54            result.push_str(unit);
55            result.push('"');
56        } else {
57            result.push_str("\"\"");
58        }
59
60        // Receivers: * for Broadcast, space-separated list for Nodes, or empty
61        match self.receivers() {
62            Receivers::Broadcast => {
63                result.push(' ');
64                result.push('*');
65            }
66            Receivers::Nodes(nodes) => {
67                if !nodes.is_empty() {
68                    result.push(' ');
69                    for (i, node) in self.receivers().iter().enumerate() {
70                        if i > 0 {
71                            result.push(' ');
72                        }
73                        result.push_str(node);
74                    }
75                }
76            }
77            Receivers::None => {
78                // No receivers specified - nothing to add
79            }
80        }
81
82        result
83    }
84}
85
86impl Display for Signal {
87    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
88        write!(f, "{}", self.to_dbc_string())
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95    use crate::Parser;
96
97    #[test]
98    fn test_signal_to_dbc_string_round_trip() {
99        // Test round-trip: parse -> to_dbc_string -> parse
100        let test_cases = vec![
101            (
102                r#"SG_ RPM : 0|16@0+ (0.25,0) [0|8000] "rpm" *"#,
103                " SG_ RPM : 0|16@0+ (0.25,0) [0|8000] \"rpm\" *",
104            ),
105            (
106                r#"SG_ Temperature : 16|8@1- (1,-40) [-40|215] "°C" TCM BCM"#,
107                " SG_ Temperature : 16|8@1- (1,-40) [-40|215] \"°C\" TCM BCM",
108            ),
109            (
110                r#"SG_ Flag : 24|1@0+ (1,0) [0|1] "" *"#,
111                " SG_ Flag : 24|1@0+ (1,0) [0|1] \"\" *",
112            ),
113        ];
114
115        for (input_line, expected_output) in test_cases {
116            // Parse the signal
117            let mut parser = Parser::new(input_line.as_bytes()).unwrap();
118            let signal = Signal::parse(&mut parser).unwrap();
119
120            // Convert to DBC string
121            let dbc_string = signal.to_dbc_string();
122            assert_eq!(dbc_string, expected_output);
123
124            // Round-trip: parse the output
125            let mut parser2 = Parser::new(dbc_string.as_bytes()).unwrap();
126            // Skip only the leading space, Signal::parse will handle SG_ keyword
127            parser2.skip_newlines_and_spaces();
128            let signal2 = Signal::parse(&mut parser2).unwrap();
129
130            // Verify round-trip
131            assert_eq!(signal.name(), signal2.name());
132            assert_eq!(signal.start_bit(), signal2.start_bit());
133            assert_eq!(signal.length(), signal2.length());
134            assert_eq!(signal.byte_order(), signal2.byte_order());
135            assert_eq!(signal.is_unsigned(), signal2.is_unsigned());
136            assert_eq!(signal.factor(), signal2.factor());
137            assert_eq!(signal.offset(), signal2.offset());
138            assert_eq!(signal.min(), signal2.min());
139            assert_eq!(signal.max(), signal2.max());
140            assert_eq!(signal.unit(), signal2.unit());
141        }
142    }
143}