can_dbc/ast/
comment.rs

1use can_dbc_pest::{Pair, Pairs, Rule};
2
3use crate::ast::MessageId;
4use crate::parser::{
5    inner_str, next, next_optional_rule, next_rule, next_string, parse_next_inner_str,
6    single_inner, validated_inner, DbcError,
7};
8
9/// Object comments
10#[derive(Clone, Debug, PartialEq)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12pub enum Comment {
13    Node {
14        name: String,
15        comment: String,
16    },
17    Message {
18        id: MessageId,
19        comment: String,
20    },
21    Signal {
22        message_id: MessageId,
23        name: String,
24        comment: String,
25    },
26    EnvVar {
27        name: String,
28        comment: String,
29    },
30    Plain {
31        comment: String,
32    },
33}
34
35impl TryFrom<Pair<'_, Rule>> for Comment {
36    type Error = DbcError;
37
38    /// Parse comment: `CM_ [BU_|BO_|SG_|EV_] object_name "comment_text";`
39    fn try_from(value: Pair<'_, Rule>) -> Result<Self, Self::Error> {
40        let mut inner_pairs = validated_inner(value, Rule::comment)?;
41
42        let pair = next(&mut inner_pairs)?;
43
44        if pair.as_rule() == Rule::comment_plain {
45            // Parse plain comment: `"comment"`
46            let comment = inner_str(single_inner(pair, Rule::quoted_str)?);
47            Ok(Comment::Plain { comment })
48        } else {
49            let rule = pair.as_rule();
50            let mut inner = pair.into_inner();
51            match rule {
52                Rule::comment_signal => parse_signal_comment(inner),
53                Rule::comment_message | Rule::comment_message_implicit => {
54                    // Parse message comment: `BO_ <message_id> "comment"`
55                    //      implicit comment: `<message_id> "comment"`
56                    Ok(Comment::Message {
57                        id: next_rule(&mut inner, Rule::message_id)?.try_into()?,
58                        comment: parse_next_inner_str(&mut inner, Rule::quoted_str)?,
59                    })
60                }
61                Rule::comment_node => {
62                    // Parse node comment: `BU_ <node_name> "comment"`
63                    Ok(Comment::Node {
64                        name: next_string(&mut inner, Rule::node_name)?,
65                        comment: parse_next_inner_str(&mut inner, Rule::quoted_str)?,
66                    })
67                }
68                Rule::comment_env_var => {
69                    // Parse environment variable comment: `EV_ <env_var_name> "comment"`
70                    Ok(Comment::EnvVar {
71                        name: next_string(&mut inner, Rule::env_var_name)?,
72                        comment: parse_next_inner_str(&mut inner, Rule::quoted_str)?,
73                    })
74                }
75                rule => Err(DbcError::UnknownRule(rule)),
76            }
77        }
78    }
79}
80
81/// Parse signal comment: `SG_ <message_id> [<signal_name>] "comment"`
82/// If `signal_name` is omitted, this is treated as a message comment.
83fn parse_signal_comment(mut pairs: Pairs<Rule>) -> Result<Comment, DbcError> {
84    let message_id = next_rule(&mut pairs, Rule::message_id)?.try_into()?;
85    if let Some(name) = next_optional_rule(&mut pairs, Rule::signal_name) {
86        // This is a proper signal comment with signal name
87        Ok(Comment::Signal {
88            message_id,
89            name: name.as_str().to_string(),
90            comment: parse_next_inner_str(&mut pairs, Rule::quoted_str)?,
91        })
92    } else {
93        // No signal name - treat as message comment
94        Ok(Comment::Message {
95            id: message_id,
96            comment: parse_next_inner_str(&mut pairs, Rule::quoted_str)?,
97        })
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104    use crate::test_helpers::*;
105
106    #[test]
107    fn signal_comment_test() {
108        let def = r#"
109CM_ SG_ 193 KLU_R_X "This is a signal comment test";
110"#;
111        let exp = Comment::Signal {
112            message_id: MessageId::Standard(193),
113            name: "KLU_R_X".to_string(),
114            comment: "This is a signal comment test".to_string(),
115        };
116        let val = test_into::<Comment>(def.trim_start(), Rule::comment);
117        assert_eq!(val, exp);
118    }
119
120    #[test]
121    fn message_definition_comment_test() {
122        let def = r#"
123CM_ BO_ 34544 "Some Message comment";
124"#;
125        let exp = Comment::Message {
126            id: MessageId::Standard(34544),
127            comment: "Some Message comment".to_string(),
128        };
129        let val = test_into::<Comment>(def.trim_start(), Rule::comment);
130        assert_eq!(val, exp);
131    }
132
133    #[test]
134    fn node_comment_test() {
135        let def = r#"
136CM_ BU_ network_node "Some network node comment";
137"#;
138        let exp = Comment::Node {
139            name: "network_node".to_string(),
140            comment: "Some network node comment".to_string(),
141        };
142        let val = test_into::<Comment>(def.trim_start(), Rule::comment);
143        assert_eq!(val, exp);
144    }
145
146    #[test]
147    fn env_var_comment_test() {
148        let def = r#"
149CM_ EV_ ENVXYZ "Some env var name comment";
150"#;
151        let exp = Comment::EnvVar {
152            name: "ENVXYZ".to_string(),
153            comment: "Some env var name comment".to_string(),
154        };
155        let val = test_into::<Comment>(def.trim_start(), Rule::comment);
156        assert_eq!(val, exp);
157    }
158
159    #[test]
160    fn signal_comment_with_escaped_characters_test() {
161        let def = r#"
162CM_ SG_ 2147548912 FooBar "Foo\\ \n \"Bar\"";
163"#;
164        let exp = Comment::Signal {
165            message_id: MessageId::Extended(65264),
166            name: "FooBar".to_string(),
167            comment: r#"Foo\\ \n \"Bar\""#.to_string(),
168        };
169        let val = test_into::<Comment>(def.trim_start(), Rule::comment);
170        assert_eq!(val, exp);
171    }
172
173    #[test]
174    fn empty_signal_comment_test() {
175        let def = r#"
176CM_ SG_ 2147548912 FooBar "";
177"#;
178        let exp = Comment::Signal {
179            message_id: MessageId::Extended(65264),
180            name: "FooBar".to_string(),
181            comment: String::new(),
182        };
183        let val = test_into::<Comment>(def.trim_start(), Rule::comment);
184        assert_eq!(val, exp);
185    }
186}