dbc_rs/nodes/
nodes.rs

1use crate::{
2    Parser,
3    error::{ParseError, ParseResult, messages},
4};
5
6/// Iterator over nodes in a Nodes collection
7struct NodesIter<'a, 'b> {
8    nodes: &'b [Option<&'a str>],
9    count: usize,
10    pos: usize,
11}
12
13impl<'a, 'b> Iterator for NodesIter<'a, 'b> {
14    type Item = &'a str;
15
16    #[inline]
17    fn next(&mut self) -> Option<Self::Item> {
18        while self.pos < self.count {
19            let result = self.nodes[self.pos];
20            self.pos += 1;
21            if let Some(node) = result {
22                return Some(node);
23            }
24        }
25        None
26    }
27}
28
29#[derive(Debug, PartialEq, Eq, Hash)]
30pub struct Nodes<'a> {
31    nodes: [Option<&'a str>; crate::MAX_NODES],
32    count: usize,
33}
34
35impl<'a> Default for Nodes<'a> {
36    fn default() -> Self {
37        Self {
38            nodes: [const { None }; crate::MAX_NODES],
39            count: 0,
40        }
41    }
42}
43
44impl<'a> Nodes<'a> {
45    // Shared validation function
46    pub(crate) fn validate_nodes(nodes: &[&str]) -> ParseResult<()> {
47        use crate::error::lang;
48        // Check for too many nodes (DoS protection)
49        if nodes.len() > crate::MAX_NODES {
50            return Err(ParseError::Version(messages::NODES_TOO_MANY));
51        }
52
53        // Check for duplicate node names (case-sensitive)
54        for (i, node1) in nodes.iter().enumerate() {
55            for node2 in nodes.iter().skip(i + 1) {
56                if *node1 == *node2 {
57                    return Err(ParseError::Version(lang::NODES_DUPLICATE_NAME));
58                }
59            }
60        }
61        Ok(())
62    }
63
64    #[allow(dead_code)] // Only used by builders (std-only)
65    pub(crate) fn new(nodes: &[&'a str]) -> Self {
66        // Validation should have been done prior (by builder)
67        let mut node_array: [Option<&'a str>; crate::MAX_NODES] =
68            [const { None }; crate::MAX_NODES];
69        let count = nodes.len().min(crate::MAX_NODES);
70        for (i, node) in nodes.iter().take(crate::MAX_NODES).enumerate() {
71            node_array[i] = Some(*node);
72        }
73        Self {
74            nodes: node_array,
75            count,
76        }
77    }
78
79    #[must_use = "parse result should be checked"]
80    pub(crate) fn parse<'b: 'a>(parser: &mut Parser<'b>) -> ParseResult<Self> {
81        // Expect "BU_:" keyword
82        // Note: When called from Dbc::parse, find_next_keyword already advanced past "BU_",
83        // so we try to expect "BU_" first, and if that fails, we're already past it and just expect ":"
84        if parser.expect(crate::BU_.as_bytes()).is_ok() {
85            // Successfully consumed "BU_", now expect ":"
86            parser
87                .expect(b":")
88                .map_err(|_| ParseError::Expected("Expected colon after BU_"))?;
89        } else {
90            // Already past "BU_" from find_next_keyword
91            // find_next_keyword advances to right after "BU_", which should be at ":" or whitespace
92            // Try to expect ":" - if it fails, skip whitespace and try again
93            if parser.expect(b":").is_err() {
94                // Not at ":", skip whitespace and try again
95                parser.skip_newlines_and_spaces();
96                parser
97                    .expect(b":")
98                    .map_err(|_| ParseError::Expected("Expected colon after BU_"))?;
99            }
100        }
101
102        // Skip optional whitespace after ":"
103        parser.skip_newlines_and_spaces();
104
105        // Parse node names into fixed-size array
106        let mut node_names: [Option<&'b str>; crate::MAX_NODES] =
107            [const { None }; crate::MAX_NODES];
108        let mut count = 0;
109
110        loop {
111            // Skip whitespace before each node name
112            let _ = parser.skip_whitespace();
113
114            // Try to parse an identifier (node name)
115            // parse_identifier() will fail if we're at EOF
116            match parser.parse_identifier() {
117                Ok(node) => {
118                    if count >= crate::MAX_NODES {
119                        return Err(ParseError::Version(messages::NODES_TOO_MANY));
120                    }
121                    node_names[count] = Some(node);
122                    count += 1;
123                }
124                Err(_) => {
125                    // No more identifiers, break
126                    break;
127                }
128            }
129        }
130
131        if count == 0 {
132            return Ok(Nodes {
133                nodes: [const { None }; crate::MAX_NODES],
134                count: 0,
135            });
136        }
137
138        // Collect valid node names into a slice for validation and construction
139        // We use a stack-allocated array to avoid allocation
140        let mut node_refs: [&'b str; crate::MAX_NODES] = [""; crate::MAX_NODES];
141        for i in 0..count {
142            if let Some(node) = node_names[i] {
143                node_refs[i] = node;
144            }
145        }
146
147        // Validate before construction
148        Self::validate_nodes(&node_refs[..count])?;
149        // Construct directly (validation already done)
150        let mut node_array: [Option<&'a str>; crate::MAX_NODES] =
151            [const { None }; crate::MAX_NODES];
152        for (i, node) in node_refs.iter().take(count).enumerate() {
153            node_array[i] = Some(*node);
154        }
155        Ok(Self {
156            nodes: node_array,
157            count,
158        })
159    }
160
161    /// Get an iterator over the nodes
162    ///
163    /// # Examples
164    ///
165    /// ```
166    /// use dbc_rs::Dbc;
167    ///
168    /// let dbc = Dbc::parse("VERSION \"1.0\"\n\nBU_: ECM TCM BCM")?;
169    /// for node in dbc.nodes().iter() {
170    ///     println!("Node: {}", node);
171    /// }
172    /// # Ok::<(), dbc_rs::Error>(())
173    /// ```
174    #[inline]
175    #[must_use = "iterator is lazy and does nothing unless consumed"]
176    pub fn iter(&self) -> impl Iterator<Item = &'a str> + '_ {
177        NodesIter {
178            nodes: &self.nodes,
179            count: self.count,
180            pos: 0,
181        }
182    }
183
184    /// Check if a node is in the list
185    ///
186    /// # Examples
187    ///
188    /// ```
189    /// use dbc_rs::Dbc;
190    ///
191    /// let dbc = Dbc::parse("VERSION \"1.0\"\n\nBU_: ECM TCM")?;
192    /// assert!(dbc.nodes().contains("ECM"));
193    /// assert!(!dbc.nodes().contains("BCM"));
194    /// # Ok::<(), dbc_rs::Error>(())
195    /// ```
196    #[inline]
197    #[must_use]
198    pub fn contains(&self, node: &str) -> bool {
199        self.iter().any(|n| n == node)
200    }
201
202    /// Get the number of nodes
203    ///
204    /// # Examples
205    ///
206    /// ```
207    /// use dbc_rs::Dbc;
208    ///
209    /// let dbc = Dbc::parse("VERSION \"1.0\"\n\nBU_: ECM TCM BCM")?;
210    /// assert_eq!(dbc.nodes().len(), 3);
211    /// # Ok::<(), dbc_rs::Error>(())
212    /// ```
213    #[inline]
214    #[must_use]
215    pub fn len(&self) -> usize {
216        self.count
217    }
218
219    /// Returns `true` if there are no nodes
220    ///
221    /// # Examples
222    ///
223    /// ```
224    /// use dbc_rs::Dbc;
225    ///
226    /// let dbc = Dbc::parse("VERSION \"1.0\"")?;
227    /// assert!(dbc.nodes().is_empty());
228    /// # Ok::<(), dbc_rs::Error>(())
229    /// ```
230    pub fn is_empty(&self) -> bool {
231        self.count == 0
232    }
233
234    /// Get a node by index, or None if index is out of bounds
235    ///
236    /// # Examples
237    ///
238    /// ```
239    /// use dbc_rs::Dbc;
240    ///
241    /// let dbc = Dbc::parse("VERSION \"1.0\"\n\nBU_: ECM TCM")?;
242    /// if let Some(node) = dbc.nodes().at(0) {
243    ///     assert_eq!(node, "ECM");
244    /// }
245    /// # Ok::<(), dbc_rs::Error>(())
246    /// ```
247    #[inline]
248    #[must_use]
249    pub fn at(&self, index: usize) -> Option<&'a str> {
250        if index >= self.count {
251            return None;
252        }
253        self.nodes[index]
254    }
255
256    #[cfg(feature = "alloc")]
257    #[must_use]
258    pub fn to_dbc_string(&self) -> String {
259        let mut result = String::from(crate::BU_);
260        result.push(':');
261        let nodes_str = alloc::format!("{}", self);
262        if !nodes_str.is_empty() {
263            result.push(' ');
264            result.push_str(&nodes_str);
265        }
266        result
267    }
268}
269
270#[cfg(feature = "alloc")]
271impl<'a> core::fmt::Display for Nodes<'a> {
272    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
273        if self.count == 0 {
274            return Ok(());
275        }
276        for (i, node) in self.iter().enumerate() {
277            if i > 0 {
278                write!(f, " ")?;
279            }
280            write!(f, "{}", node)?;
281        }
282        Ok(())
283    }
284}
285
286#[cfg(test)]
287mod tests {
288    #![allow(clippy::float_cmp)]
289    use super::*;
290    use crate::{
291        Parser,
292        error::{ParseError, lang},
293    };
294
295    #[test]
296    fn test_nodes_from_valid_line() {
297        let line = b"BU_: ECM TCM BCM ABS";
298        let mut parser = Parser::new(line).unwrap();
299        let nodes = Nodes::parse(&mut parser).unwrap();
300        let node_vec: alloc::vec::Vec<&str> = nodes.iter().collect();
301        assert_eq!(node_vec, &["ECM", "TCM", "BCM", "ABS"]);
302    }
303
304    #[test]
305    fn test_nodes_from_single_node() {
306        let line = b"BU_: ONLYONE";
307        let mut parser = Parser::new(line).unwrap();
308        let nodes = Nodes::parse(&mut parser).unwrap();
309        let node_vec: alloc::vec::Vec<&str> = nodes.iter().collect();
310        assert_eq!(node_vec, &["ONLYONE"]);
311    }
312
313    #[test]
314    fn test_nodes_from_with_extra_spaces() {
315        let line = b"BU_:   Node1   Node2   ";
316        let mut parser = Parser::new(line).unwrap();
317        let nodes = Nodes::parse(&mut parser).unwrap();
318        let node_vec: alloc::vec::Vec<&str> = nodes.iter().collect();
319        assert_eq!(node_vec, &["Node1", "Node2"]);
320    }
321
322    #[test]
323    fn test_nodes_from_empty_list() {
324        let line = b"BU_:";
325        let mut parser = Parser::new(line).unwrap();
326        let nodes = Nodes::parse(&mut parser).unwrap();
327        assert!(nodes.is_empty());
328    }
329
330    #[test]
331    #[cfg(feature = "alloc")]
332    fn test_nodes_new() {
333        use crate::nodes::NodesBuilder;
334        let nodes = NodesBuilder::new()
335            .add_node("ECM")
336            .add_node("TCM")
337            .add_node("BCM")
338            .build()
339            .unwrap();
340        assert!(nodes.contains("ECM"));
341        assert!(nodes.contains("TCM"));
342        assert!(nodes.contains("BCM"));
343        assert!(!nodes.contains("ABS"));
344        assert_eq!(nodes.iter().count(), 3);
345    }
346
347    #[test]
348    #[cfg(feature = "alloc")]
349    fn test_nodes_new_from_vec() {
350        use crate::nodes::NodesBuilder;
351        let nodes = NodesBuilder::new()
352            .add_node("Node1")
353            .add_node("Node2")
354            .add_node("Node3")
355            .build()
356            .unwrap();
357        assert!(nodes.contains("Node1"));
358        assert_eq!(nodes.iter().count(), 3);
359    }
360
361    #[test]
362    #[cfg(feature = "alloc")]
363    fn test_nodes_new_from_slice() {
364        use crate::nodes::NodesBuilder;
365        let nodes = NodesBuilder::new().add_node("A").add_node("B").add_node("C").build().unwrap();
366        assert!(nodes.contains("A"));
367        assert_eq!(nodes.iter().count(), 3);
368    }
369
370    #[test]
371    #[cfg(feature = "alloc")]
372    fn test_nodes_new_duplicate() {
373        use crate::nodes::NodesBuilder;
374        let result = NodesBuilder::new().add_node("ECM").add_node("TCM").add_node("ECM").build();
375        assert!(result.is_err());
376        use crate::Error;
377        match result.unwrap_err() {
378            Error::Nodes(msg) => {
379                assert!(msg.contains(lang::NODES_DUPLICATE_NAME))
380            }
381            _ => panic!("Expected Error::Nodes with NODES_DUPLICATE_NAME"),
382        }
383    }
384
385    #[test]
386    #[cfg(feature = "alloc")]
387    fn test_nodes_to_string_single() {
388        use crate::nodes::NodesBuilder;
389        let nodes = NodesBuilder::new().add_node("ECM").build().unwrap();
390        assert_eq!(alloc::format!("{}", nodes), "ECM");
391    }
392
393    #[test]
394    #[cfg(feature = "alloc")]
395    fn test_nodes_to_string_multiple() {
396        use crate::nodes::NodesBuilder;
397        let nodes = NodesBuilder::new()
398            .add_node("ECM")
399            .add_node("TCM")
400            .add_node("BCM")
401            .build()
402            .unwrap();
403        assert_eq!(alloc::format!("{}", nodes), "ECM TCM BCM");
404    }
405
406    #[test]
407    #[cfg(feature = "alloc")]
408    fn test_nodes_to_dbc_string() {
409        use crate::nodes::NodesBuilder;
410        let nodes_single = NodesBuilder::new().add_node("ECM").build().unwrap();
411        assert_eq!(nodes_single.to_dbc_string(), "BU_: ECM");
412
413        let nodes_multiple = NodesBuilder::new()
414            .add_node("ECM")
415            .add_node("TCM")
416            .add_node("BCM")
417            .build()
418            .unwrap();
419        assert_eq!(nodes_multiple.to_dbc_string(), "BU_: ECM TCM BCM");
420    }
421
422    #[test]
423    fn test_nodes_parse_duplicate() {
424        let line = b"BU_: ECM TCM ECM";
425        let mut parser = Parser::new(line).unwrap();
426        let result = Nodes::parse(&mut parser);
427        assert!(result.is_err());
428        match result.unwrap_err() {
429            ParseError::Version(msg) => assert!(msg == lang::NODES_DUPLICATE_NAME),
430            _ => panic!("Expected ParseError::Version"),
431        }
432    }
433
434    #[test]
435    #[cfg(feature = "alloc")]
436    fn test_nodes_too_many() {
437        use crate::nodes::NodesBuilder;
438        // Create a builder with 257 nodes (exceeds limit of 256)
439        let mut builder = NodesBuilder::new();
440        for i in 0..257 {
441            builder = builder.add_node(format!("Node{i}"));
442        }
443        let result = builder.build();
444        assert!(result.is_err());
445        use crate::Error;
446        match result.unwrap_err() {
447            Error::Nodes(msg) => {
448                assert!(msg.contains(lang::NODES_TOO_MANY));
449            }
450            _ => panic!("Expected Error::Nodes"),
451        }
452    }
453
454    #[test]
455    #[cfg(feature = "alloc")]
456    fn test_nodes_at_limit() {
457        use crate::nodes::NodesBuilder;
458        // Create a builder with exactly 256 nodes (at the limit)
459        let mut builder = NodesBuilder::new();
460        for i in 0..256 {
461            builder = builder.add_node(format!("Node{i}"));
462        }
463        let result = builder.build();
464        assert!(result.is_ok());
465        assert_eq!(result.unwrap().len(), 256);
466    }
467}