dbc_rs/signal/
signal.rs

1use crate::{
2    ByteOrder, Parser, Receivers,
3    error::{ParseError, ParseResult},
4};
5
6#[derive(Debug, Clone, PartialEq)]
7pub struct Signal<'a> {
8    name: &'a str,
9    start_bit: u16,
10    length: u16,
11    byte_order: ByteOrder,
12    unsigned: bool,
13    factor: f64,
14    offset: f64,
15    min: f64,
16    max: f64,
17    unit: Option<&'a str>,
18    receivers: Receivers<'a>,
19}
20
21impl<'a> Signal<'a> {
22    pub(crate) fn validate(name: &str, length: u16, min: f64, max: f64) -> ParseResult<()> {
23        if name.trim().is_empty() {
24            return Err(ParseError::Signal(crate::error::lang::SIGNAL_NAME_EMPTY));
25        }
26
27        // Validate length: must be between 1 and 512 bits
28        // - Classic CAN (2.0A/2.0B): DLC up to 8 bytes (64 bits)
29        // - CAN FD: DLC up to 64 bytes (512 bits)
30        // Signal length is validated against message DLC in Message::validate
31        // Note: name is parsed before this validation, so we can include it in error messages
32        if length == 0 {
33            return Err(ParseError::Signal(
34                crate::error::lang::SIGNAL_LENGTH_TOO_SMALL,
35            ));
36        }
37        if length > 512 {
38            return Err(ParseError::Signal(
39                crate::error::lang::SIGNAL_LENGTH_TOO_LARGE,
40            ));
41        }
42
43        // Note: start_bit validation (boundary checks and overlap detection) is done in
44        // Message::validate, not here, because:
45        // 1. The actual message size depends on DLC (1-64 bytes for CAN FD)
46        // 2. Overlap detection requires comparing multiple signals
47        // 3. This allows signals to be created independently and validated when added to a message
48
49        // Validate min <= max
50        if min > max {
51            return Err(ParseError::Signal(crate::error::lang::INVALID_RANGE));
52        }
53
54        Ok(())
55    }
56
57    #[cfg(any(feature = "alloc", feature = "kernel"))]
58    #[allow(clippy::too_many_arguments)] // Internal method, builder pattern is the public API
59    pub(crate) fn new(
60        name: &'a str,
61        start_bit: u16,
62        length: u16,
63        byte_order: ByteOrder,
64        unsigned: bool,
65        factor: f64,
66        offset: f64,
67        min: f64,
68        max: f64,
69        unit: Option<&'a str>,
70        receivers: Receivers<'a>,
71    ) -> Self {
72        // Validation should have been done prior (by builder or parse)
73        Self {
74            name,
75            start_bit,
76            length,
77            byte_order,
78            unsigned,
79            factor,
80            offset,
81            min,
82            max,
83            unit,
84            receivers,
85        }
86    }
87
88    fn parse_position<'b>(parser: &mut Parser<'b>) -> ParseResult<(u16, u16, ByteOrder, bool)> {
89        // Parse start_bit
90        let start_bit = match parser.parse_u32() {
91            Ok(v) => v as u16,
92            Err(_) => {
93                return Err(ParseError::Signal(
94                    crate::error::lang::SIGNAL_PARSE_INVALID_START_BIT,
95                ));
96            }
97        };
98
99        // Validate start_bit range
100        if start_bit > 511 {
101            return Err(ParseError::Signal(
102                crate::error::lang::SIGNAL_PARSE_INVALID_START_BIT,
103            ));
104        }
105
106        // Expect pipe
107        parser.expect(b"|").map_err(|_| ParseError::Expected("Expected pipe"))?;
108
109        // Parse length
110        let length = parser
111            .parse_u32()
112            .map_err(|_| ParseError::Signal(crate::error::lang::SIGNAL_PARSE_INVALID_LENGTH))?
113            as u16;
114
115        // Expect @
116        parser.expect(b"@").map_err(|_| ParseError::Expected("Expected @"))?;
117
118        // Parse byte order (0 or 1)
119        // Try to expect '0' or '1' directly
120        let bo_byte = if parser.expect(b"0").is_ok() {
121            b'0'
122        } else if parser.expect(b"1").is_ok() {
123            b'1'
124        } else {
125            return Err(ParseError::Expected("Expected byte order"));
126        };
127
128        let byte_order = match bo_byte {
129            b'0' => ByteOrder::BigEndian,    // 0 = Motorola (big-endian)
130            b'1' => ByteOrder::LittleEndian, // 1 = Intel (little-endian)
131            _ => return Err(ParseError::InvalidChar(bo_byte as char)),
132        };
133
134        // Parse sign (+ or -)
135        let sign_byte = if parser.expect(b"+").is_ok() {
136            b'+'
137        } else if parser.expect(b"-").is_ok() {
138            b'-'
139        } else {
140            return Err(ParseError::Expected("Expected sign (+ or -)"));
141        };
142
143        let unsigned = match sign_byte {
144            b'+' => true,
145            b'-' => false,
146            _ => return Err(ParseError::InvalidChar(sign_byte as char)),
147        };
148
149        Ok((start_bit, length, byte_order, unsigned))
150    }
151
152    fn parse_factor_offset<'b>(parser: &mut Parser<'b>) -> ParseResult<(f64, f64)> {
153        // Expect opening parenthesis
154        parser
155            .expect(b"(")
156            .map_err(|_| ParseError::Expected("Expected opening parenthesis"))?;
157
158        // Skip whitespace
159        parser.skip_newlines_and_spaces();
160
161        // Parse factor (may be empty, default to 0.0)
162        // parse_f64() stops at comma/paren without consuming them
163        // If parsing fails immediately (pos unchanged), we're at a delimiter (empty factor)
164        let pos_before = parser.pos();
165        let factor = match parser.parse_f64() {
166            Ok(val) => val,
167            Err(_) => {
168                // Check if position didn't change (we're at delimiter)
169                if parser.pos() == pos_before {
170                    0.0 // Empty factor
171                } else {
172                    // Position changed but parsing failed - invalid format
173                    return Err(ParseError::Signal(
174                        crate::error::lang::SIGNAL_PARSE_INVALID_FACTOR,
175                    ));
176                }
177            }
178        };
179
180        // Expect comma
181        parser.expect(b",").map_err(|_| ParseError::Expected("Expected comma"))?;
182
183        // Skip whitespace
184        parser.skip_newlines_and_spaces();
185
186        // Parse offset (may be empty, default to 0.0)
187        let pos_before = parser.pos();
188        let offset = match parser.parse_f64() {
189            Ok(val) => val,
190            Err(_) => {
191                // Check if position didn't change (we're at closing paren)
192                if parser.pos() == pos_before {
193                    0.0 // Empty offset
194                } else {
195                    return Err(ParseError::Signal(
196                        crate::error::lang::SIGNAL_PARSE_INVALID_OFFSET,
197                    ));
198                }
199            }
200        };
201
202        // Skip whitespace
203        parser.skip_newlines_and_spaces();
204
205        // Expect closing parenthesis
206        parser
207            .expect(b")")
208            .map_err(|_| ParseError::Expected("Expected closing parenthesis"))?;
209
210        Ok((factor, offset))
211    }
212
213    fn parse_range<'b>(parser: &mut Parser<'b>) -> ParseResult<(f64, f64)> {
214        // Expect opening bracket
215        parser
216            .expect(b"[")
217            .map_err(|_| ParseError::Expected("Expected opening bracket"))?;
218
219        // Skip whitespace
220        parser.skip_newlines_and_spaces();
221
222        // Parse min (may be empty, default to 0.0)
223        let pos_before = parser.pos();
224        let min = match parser.parse_f64() {
225            Ok(val) => val,
226            Err(_) => {
227                // Check if position didn't change (we're at pipe or closing bracket)
228                if parser.pos() == pos_before {
229                    0.0 // Empty min
230                } else {
231                    return Err(ParseError::Signal(
232                        crate::error::lang::SIGNAL_PARSE_INVALID_MIN,
233                    ));
234                }
235            }
236        };
237
238        // Expect pipe
239        parser.expect(b"|").map_err(|_| ParseError::Expected("Expected pipe"))?;
240
241        // Skip whitespace
242        parser.skip_newlines_and_spaces();
243
244        // Parse max (may be empty, default to 0.0)
245        let pos_before = parser.pos();
246        let max = match parser.parse_f64() {
247            Ok(val) => val,
248            Err(_) => {
249                // Check if position didn't change (we're at closing bracket)
250                if parser.pos() == pos_before {
251                    0.0 // Empty max
252                } else {
253                    return Err(ParseError::Signal(
254                        crate::error::lang::SIGNAL_PARSE_INVALID_MAX,
255                    ));
256                }
257            }
258        };
259
260        // Skip whitespace
261        parser.skip_newlines_and_spaces();
262
263        // Expect closing bracket
264        parser
265            .expect(b"]")
266            .map_err(|_| ParseError::Expected("Expected closing bracket"))?;
267
268        Ok((min, max))
269    }
270
271    fn parse_unit<'b: 'a>(parser: &mut Parser<'b>) -> ParseResult<Option<&'a str>> {
272        const MAX_UNIT_LENGTH: u16 = 256;
273
274        // Expect opening quote
275        parser
276            .expect(b"\"")
277            .map_err(|_| ParseError::Expected("Expected opening quote"))?;
278
279        // Use take_until_quote to read the unit (allow any printable characters)
280        let unit_bytes = parser.take_until_quote(false, MAX_UNIT_LENGTH).map_err(|e| match e {
281            ParseError::MaxStrLength(_) => {
282                ParseError::Signal(crate::error::lang::SIGNAL_PARSE_UNIT_TOO_LONG)
283            }
284            _ => ParseError::Expected("Expected closing quote"),
285        })?;
286
287        // Convert bytes to string slice
288        let unit_str = core::str::from_utf8(unit_bytes)
289            .map_err(|_| ParseError::Expected("Invalid UTF-8 in unit"))?;
290
291        let unit = if unit_str.is_empty() {
292            None
293        } else {
294            Some(unit_str)
295        };
296
297        Ok(unit)
298    }
299
300    pub(crate) fn parse<'b: 'a>(parser: &mut Parser<'b>) -> ParseResult<Self> {
301        // Signal parsing must always start with "SG_" keyword
302        parser
303            .expect(crate::SG_.as_bytes())
304            .map_err(|_| ParseError::Expected("Expected SG_ keyword"))?;
305
306        // Skip whitespace after "SG_"
307        parser.skip_newlines_and_spaces();
308
309        // Parse signal name (identifier)
310        let name = parser
311            .parse_identifier()
312            .map_err(|_| ParseError::Signal(crate::error::lang::SIGNAL_NAME_EMPTY))?;
313
314        // Skip whitespace (optional before colon) - handle multiplexer indicator
315        // According to spec: multiplexer_indicator = ' ' | [m multiplexer_switch_value] [M]
316        // For now, we just skip whitespace and any potential multiplexer indicator
317        parser.skip_newlines_and_spaces();
318
319        // Skip potential multiplexer indicator (m followed by number, or M)
320        // For simplicity, skip any 'm' or 'M' followed by digits
321        if parser.expect(b"m").is_ok() || parser.expect(b"M").is_ok() {
322            // Skip any digits that follow
323            loop {
324                let _pos_before = parser.pos();
325                // Try to consume a digit
326                if parser.expect(b"0").is_ok()
327                    || parser.expect(b"1").is_ok()
328                    || parser.expect(b"2").is_ok()
329                    || parser.expect(b"3").is_ok()
330                    || parser.expect(b"4").is_ok()
331                    || parser.expect(b"5").is_ok()
332                    || parser.expect(b"6").is_ok()
333                    || parser.expect(b"7").is_ok()
334                    || parser.expect(b"8").is_ok()
335                    || parser.expect(b"9").is_ok()
336                {
337                    // Consumed a digit, continue
338                } else {
339                    // Not a digit, stop
340                    break;
341                }
342            }
343            // Skip whitespace after multiplexer indicator
344            parser.skip_newlines_and_spaces();
345        }
346
347        // Expect colon
348        parser.expect(b":").map_err(|_| ParseError::Expected("Expected colon"))?;
349
350        // Skip whitespace after colon
351        parser.skip_newlines_and_spaces();
352
353        // Parse position: start_bit|length@byteOrderSign
354        let (start_bit, length, byte_order, unsigned) = Self::parse_position(parser)?;
355
356        // Skip whitespace
357        parser.skip_newlines_and_spaces();
358
359        // Parse factor and offset: (factor,offset)
360        let (factor, offset) = Self::parse_factor_offset(parser)?;
361
362        // Skip whitespace
363        parser.skip_newlines_and_spaces();
364
365        // Parse range: [min|max]
366        let (min, max) = Self::parse_range(parser)?;
367
368        // Skip whitespace
369        parser.skip_newlines_and_spaces();
370
371        // Parse unit: "unit" or ""
372        let unit = Self::parse_unit(parser)?;
373
374        // Skip whitespace (but not newlines) before parsing receivers
375        // Newlines indicate end of signal line, so we need to preserve them for Receivers::parse
376        let _ = parser.skip_whitespace().ok(); // Ignore error if no whitespace
377
378        // Parse receivers (may be empty/None if at end of line)
379        let receivers = Receivers::parse(parser)?;
380
381        // Validate before construction
382        Self::validate(name, length, min, max)?;
383        // Construct directly (validation already done)
384        // Value descriptions are stored in Dbc, not in Signal
385        Ok(Self {
386            name,
387            start_bit,
388            length,
389            byte_order,
390            unsigned,
391            factor,
392            offset,
393            min,
394            max,
395            unit,
396            receivers,
397        })
398    }
399
400    #[inline]
401    #[must_use = "return value should be checked"]
402    pub fn name(&self) -> &str {
403        self.name
404    }
405
406    #[inline]
407    #[must_use]
408    pub fn start_bit(&self) -> u16 {
409        self.start_bit
410    }
411
412    #[inline]
413    #[must_use]
414    pub fn length(&self) -> u16 {
415        self.length
416    }
417
418    #[inline]
419    #[must_use]
420    pub fn byte_order(&self) -> ByteOrder {
421        self.byte_order
422    }
423
424    #[inline]
425    #[must_use]
426    pub fn is_unsigned(&self) -> bool {
427        self.unsigned
428    }
429
430    #[inline]
431    #[must_use]
432    pub fn factor(&self) -> f64 {
433        self.factor
434    }
435
436    #[inline]
437    #[must_use]
438    pub fn offset(&self) -> f64 {
439        self.offset
440    }
441
442    #[inline]
443    #[must_use]
444    pub fn min(&self) -> f64 {
445        self.min
446    }
447
448    #[inline]
449    #[must_use]
450    pub fn max(&self) -> f64 {
451        self.max
452    }
453
454    #[inline]
455    #[must_use]
456    pub fn unit(&self) -> Option<&'a str> {
457        self.unit
458    }
459
460    #[inline]
461    #[must_use]
462    pub fn receivers(&self) -> &Receivers<'a> {
463        &self.receivers
464    }
465
466    #[cfg(feature = "alloc")]
467    #[must_use]
468    pub fn to_dbc_string(&self) -> alloc::string::String {
469        use alloc::{
470            format,
471            string::{String, ToString},
472        };
473        let mut result = String::with_capacity(100); // Pre-allocate reasonable capacity
474
475        result.push_str(" SG_ ");
476        result.push_str(self.name());
477        result.push_str(" : ");
478        result.push_str(&self.start_bit().to_string());
479        result.push('|');
480        result.push_str(&self.length().to_string());
481        result.push('@');
482
483        // Byte order: 0 for BigEndian (Motorola), 1 for LittleEndian (Intel)
484        // Per Vector DBC spec v1.0.1: "Big endian is stored as '0', little endian is stored as '1'"
485        match self.byte_order() {
486            ByteOrder::BigEndian => result.push('0'), // @0 = Big Endian (Motorola)
487            ByteOrder::LittleEndian => result.push('1'), // @1 = Little Endian (Intel)
488        }
489
490        // Sign: + for unsigned, - for signed
491        if self.is_unsigned() {
492            result.push('+');
493        } else {
494            result.push('-');
495        }
496
497        // Factor and offset: (factor,offset)
498        result.push_str(" (");
499        result.push_str(&format!("{}", self.factor()));
500        result.push(',');
501        result.push_str(&format!("{}", self.offset()));
502        result.push(')');
503
504        // Min and max: [min|max]
505        result.push_str(" [");
506        result.push_str(&format!("{}", self.min()));
507        result.push('|');
508        result.push_str(&format!("{}", self.max()));
509        result.push(']');
510
511        // Unit: "unit" or ""
512        result.push(' ');
513        if let Some(unit) = self.unit() {
514            result.push('"');
515            result.push_str(unit);
516            result.push('"');
517        } else {
518            result.push_str("\"\"");
519        }
520
521        // Receivers: * for Broadcast, space-separated list for Nodes, or empty
522        match self.receivers() {
523            Receivers::Broadcast => {
524                result.push(' ');
525                result.push('*');
526            }
527            Receivers::Nodes(_, count) => {
528                if *count > 0 {
529                    result.push(' ');
530                    for (i, node) in self.receivers().iter().enumerate() {
531                        if i > 0 {
532                            result.push(' ');
533                        }
534                        result.push_str(node);
535                    }
536                }
537            }
538            Receivers::None => {
539                // No receivers specified - nothing to add
540            }
541        }
542
543        result
544    }
545}
546
547// Custom Eq implementation that handles f64 (treats NaN as equal to NaN)
548impl<'a> Eq for Signal<'a> {}
549
550// Custom Hash implementation that handles f64 (treats NaN consistently)
551impl<'a> core::hash::Hash for Signal<'a> {
552    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
553        self.name.hash(state);
554        self.start_bit.hash(state);
555        self.length.hash(state);
556        self.byte_order.hash(state);
557        self.unsigned.hash(state);
558        // Handle f64: convert to bits for hashing (NaN will have consistent representation)
559        self.factor.to_bits().hash(state);
560        self.offset.to_bits().hash(state);
561        self.min.to_bits().hash(state);
562        self.max.to_bits().hash(state);
563        self.unit.hash(state);
564        self.receivers.hash(state);
565    }
566}
567
568#[cfg(feature = "alloc")]
569impl<'a> core::fmt::Display for Signal<'a> {
570    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
571        write!(f, "{}", self.to_dbc_string())
572    }
573}
574
575#[cfg(test)]
576mod tests {
577    #![allow(clippy::float_cmp)]
578    use super::*;
579    use crate::{
580        Parser,
581        error::{ParseError, lang},
582    };
583
584    // Note: Builder tests have been moved to signal_builder.rs
585    // This module only tests Signal parsing and direct API usage
586
587    #[test]
588    fn test_parse_valid_signal() {
589        let line = r#"SG_ RPM : 0|16@0+ (0.25,0) [0|8000] "rpm" TCM"#;
590        let mut parser = Parser::new(line.as_bytes()).unwrap();
591        let sig = Signal::parse(&mut parser).unwrap();
592        assert_eq!(sig.name(), "RPM");
593        assert_eq!(sig.start_bit(), 0);
594        assert_eq!(sig.length(), 16);
595        assert_eq!(sig.byte_order(), ByteOrder::BigEndian); // @0 = BigEndian (Motorola)
596        assert!(sig.is_unsigned());
597        assert_eq!(sig.factor(), 0.25);
598        assert_eq!(sig.offset(), 0.);
599        assert_eq!(sig.min(), 0.);
600        assert_eq!(sig.max(), 8000.);
601        assert_eq!(sig.unit(), Some("rpm"));
602        // Check receivers using iter_nodes
603        let mut receivers_iter = sig.receivers().iter();
604        assert_eq!(receivers_iter.next(), Some("TCM"));
605        assert_eq!(receivers_iter.next(), None);
606    }
607
608    #[test]
609    fn test_parse_signal_with_empty_unit_and_broadcast() {
610        let line = r#"SG_ ABSActive : 16|1@0+ (1,0) [0|1] "" *"#;
611        let mut parser = Parser::new(line.as_bytes()).unwrap();
612        let sig = Signal::parse(&mut parser).unwrap();
613        assert_eq!(sig.name(), "ABSActive");
614        assert_eq!(sig.start_bit(), 16);
615        assert_eq!(sig.length(), 1);
616        assert_eq!(sig.byte_order(), ByteOrder::BigEndian); // @0 = BigEndian (Motorola)
617        assert!(sig.is_unsigned());
618        assert_eq!(sig.factor(), 1.);
619        assert_eq!(sig.offset(), 0.);
620        assert_eq!(sig.min(), 0.);
621        assert_eq!(sig.max(), 1.);
622        assert_eq!(sig.unit(), None);
623        assert_eq!(sig.receivers(), &Receivers::Broadcast);
624    }
625
626    #[test]
627    fn test_parse_signal_with_negative_offset_and_min() {
628        let line = r#"SG_ Temperature : 16|8@0- (1,-40) [-40|215] "°C" TCM BCM"#;
629        let mut parser = Parser::new(line.as_bytes()).unwrap();
630        let sig = Signal::parse(&mut parser).unwrap();
631        assert_eq!(sig.name(), "Temperature");
632        assert_eq!(sig.start_bit(), 16);
633        assert_eq!(sig.length(), 8);
634        assert_eq!(sig.byte_order(), ByteOrder::BigEndian); // @0 = BigEndian (Motorola)
635        assert!(!sig.is_unsigned());
636        assert_eq!(sig.factor(), 1.);
637        assert_eq!(sig.offset(), -40.);
638        assert_eq!(sig.min(), -40.);
639        assert_eq!(sig.max(), 215.);
640        assert_eq!(sig.unit(), Some("°C"));
641        // Check receivers using iter_nodes
642        let mut receivers_iter = sig.receivers().iter();
643        assert_eq!(receivers_iter.next(), Some("TCM"));
644        assert_eq!(receivers_iter.next(), Some("BCM"));
645        assert_eq!(receivers_iter.next(), None);
646    }
647
648    #[test]
649    fn test_parse_signal_with_percent_unit() {
650        let line = r#"SG_ ThrottlePosition : 24|8@0+ (0.392157,0) [0|100] "%" *"#;
651        let mut parser = Parser::new(line.as_bytes()).unwrap();
652        let sig = Signal::parse(&mut parser).unwrap();
653        assert_eq!(sig.name(), "ThrottlePosition");
654        assert_eq!(sig.start_bit(), 24);
655        assert_eq!(sig.length(), 8);
656        assert_eq!(sig.byte_order(), ByteOrder::BigEndian); // @0 = BigEndian (Motorola)
657        assert!(sig.is_unsigned());
658        assert_eq!(sig.factor(), 0.392_157);
659        assert_eq!(sig.offset(), 0.);
660        assert_eq!(sig.min(), 0.);
661        assert_eq!(sig.max(), 100.);
662        assert_eq!(sig.unit(), Some("%"));
663        assert_eq!(sig.receivers(), &Receivers::Broadcast);
664    }
665
666    #[test]
667    fn test_parse_signal_missing_factors_and_limits() {
668        // Should use default values where missing
669        let line = r#"SG_ Simple : 10|4@0+ ( , ) [ | ] "" *"#;
670        let mut parser = Parser::new(line.as_bytes()).unwrap();
671        let sig = Signal::parse(&mut parser).unwrap();
672        assert_eq!(sig.name(), "Simple");
673        assert_eq!(sig.start_bit(), 10);
674        assert_eq!(sig.length(), 4);
675        assert_eq!(sig.byte_order(), ByteOrder::BigEndian); // @0 = BigEndian (Motorola)
676        assert!(sig.is_unsigned());
677        assert_eq!(sig.factor(), 0.);
678        assert_eq!(sig.offset(), 0.);
679        assert_eq!(sig.min(), 0.);
680        assert_eq!(sig.max(), 0.);
681        assert_eq!(sig.unit(), None);
682        assert_eq!(sig.receivers(), &Receivers::Broadcast);
683    }
684
685    #[test]
686    fn test_parse_signal_missing_start_bit() {
687        let line = r#"SG_ RPM : |16@0+ (0.25,0) [0|8000] "rpm" TCM"#;
688        let mut parser = Parser::new(line.as_bytes()).unwrap();
689        let err = Signal::parse(&mut parser).unwrap_err();
690        match err {
691            ParseError::Signal(msg) => {
692                // Check for either the old constant or the new formatted message
693                assert!(
694                    msg.contains(lang::SIGNAL_PARSE_INVALID_START_BIT)
695                        || msg.contains("Signal 'RPM'")
696                );
697            }
698            _ => panic!("Expected ParseError::Signal"),
699        }
700    }
701
702    #[test]
703    fn test_parse_signal_invalid_range() {
704        // min > max should fail validation
705        let line = r#"SG_ Test : 0|8@0+ (1,0) [100|50] "unit" *"#;
706        let mut parser = Parser::new(line.as_bytes()).unwrap();
707        let err = Signal::parse(&mut parser).unwrap_err();
708        match err {
709            ParseError::Signal(msg) => {
710                assert!(msg.contains(lang::INVALID_RANGE));
711            }
712            e => panic!("Expected ParseError::Signal, got: {:?}", e),
713        }
714    }
715
716    #[test]
717    fn test_parse_signal_overflow() {
718        // Signal with start_bit + length > 64 should parse successfully
719        // (validation against message DLC happens in Message::validate)
720        // This signal would fit in a CAN FD message (64 bytes = 512 bits)
721        let line = r#"SG_ Test : 60|10@0+ (1,0) [0|100] "unit" *"#;
722        let mut parser = Parser::new(line.as_bytes()).unwrap();
723        let signal = Signal::parse(&mut parser).unwrap();
724        assert_eq!(signal.start_bit(), 60);
725        assert_eq!(signal.length(), 10);
726        // Note: Message validation tests are in message.rs and message_builder.rs
727    }
728
729    #[test]
730    fn test_parse_signal_length_too_large() {
731        // length > 512 should fail validation (CAN FD maximum is 512 bits)
732        let line = r#"SG_ Test : 0|513@0+ (1,0) [0|100] "unit" *"#;
733        let mut parser = Parser::new(line.as_bytes()).unwrap();
734        let err = Signal::parse(&mut parser).unwrap_err();
735        match err {
736            ParseError::Signal(msg) => {
737                // Check for either the old constant or the new formatted message
738                assert!(
739                    msg.contains(lang::SIGNAL_LENGTH_TOO_LARGE)
740                        || msg.contains("Signal 'Test'")
741                        || msg.contains("513")
742                );
743            }
744            e => panic!("Expected ParseError::Signal, got: {:?}", e),
745        }
746    }
747
748    #[test]
749    fn test_parse_signal_zero_length() {
750        // length = 0 should fail validation
751        let line = r#"SG_ Test : 0|0@0+ (1,0) [0|100] "unit" *"#;
752        let mut parser = Parser::new(line.as_bytes()).unwrap();
753        let err = Signal::parse(&mut parser).unwrap_err();
754        match err {
755            ParseError::Signal(msg) => {
756                // Check for either the old constant or the new formatted message
757                assert!(
758                    msg.contains(lang::SIGNAL_LENGTH_TOO_SMALL)
759                        || msg.contains("Signal 'Test'")
760                        || msg.contains("0 bits")
761                );
762            }
763            e => panic!("Expected ParseError::Signal, got: {:?}", e),
764        }
765    }
766
767    #[test]
768    fn test_parse_signal_missing_length() {
769        let line = r#"SG_ RPM : 0|@0+ (0.25,0) [0|8000] "rpm" TCM"#;
770        let mut parser = Parser::new(line.as_bytes()).unwrap();
771        let err = Signal::parse(&mut parser).unwrap_err();
772        match err {
773            ParseError::Signal(msg) => assert!(msg.contains(lang::SIGNAL_PARSE_INVALID_LENGTH)),
774            e => panic!("Expected ParseError::Signal, got: {:?}", e),
775        }
776    }
777
778    // Tests that require alloc or kernel (for to_dbc_string)
779    #[cfg(any(feature = "alloc", feature = "kernel"))]
780    mod tests_with_alloc {
781        #[cfg(feature = "alloc")]
782        use super::*;
783
784        #[test]
785        #[cfg(feature = "alloc")] // to_dbc_string is only available with alloc
786        fn test_signal_to_dbc_string_round_trip() {
787            use alloc::vec;
788            // Test round-trip: parse -> to_dbc_string -> parse
789            let test_cases = vec![
790                (
791                    r#"SG_ RPM : 0|16@0+ (0.25,0) [0|8000] "rpm" *"#,
792                    " SG_ RPM : 0|16@0+ (0.25,0) [0|8000] \"rpm\" *",
793                ),
794                (
795                    r#"SG_ Temperature : 16|8@1- (1,-40) [-40|215] "°C" TCM BCM"#,
796                    " SG_ Temperature : 16|8@1- (1,-40) [-40|215] \"°C\" TCM BCM",
797                ),
798                (
799                    r#"SG_ Flag : 24|1@0+ (1,0) [0|1] "" *"#,
800                    " SG_ Flag : 24|1@0+ (1,0) [0|1] \"\" *",
801                ),
802            ];
803
804            for (input_line, expected_output) in test_cases {
805                // Parse the signal
806                let mut parser = Parser::new(input_line.as_bytes()).unwrap();
807                let signal = Signal::parse(&mut parser).unwrap();
808
809                // Convert to DBC string
810                let dbc_string = signal.to_dbc_string();
811                assert_eq!(dbc_string, expected_output);
812
813                // Round-trip: parse the output
814                let mut parser2 = Parser::new(dbc_string.as_bytes()).unwrap();
815                // Skip only the leading space, Signal::parse will handle SG_ keyword
816                parser2.skip_newlines_and_spaces();
817                let signal2 = Signal::parse(&mut parser2).unwrap();
818
819                // Verify round-trip
820                assert_eq!(signal.name(), signal2.name());
821                assert_eq!(signal.start_bit(), signal2.start_bit());
822                assert_eq!(signal.length(), signal2.length());
823                assert_eq!(signal.byte_order(), signal2.byte_order());
824                assert_eq!(signal.is_unsigned(), signal2.is_unsigned());
825                assert_eq!(signal.factor(), signal2.factor());
826                assert_eq!(signal.offset(), signal2.offset());
827                assert_eq!(signal.min(), signal2.min());
828                assert_eq!(signal.max(), signal2.max());
829                assert_eq!(signal.unit(), signal2.unit());
830            }
831        }
832    }
833
834    // Note: Helper parsing functions (parse_name_and_prefix, parse_position, etc.) are now internal
835    // and use the Parser directly. Their functionality is tested through Signal::parse tests above.
836    // All tests for these helper methods have been removed as they are implementation details.
837}