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