ros_message/parse_msg/
mod.rs

1use crate::{Error, FieldCase, FieldInfo, Result};
2use lazy_static::lazy_static;
3use regex::Regex;
4
5static IGNORE_WHITESPACE: &str = r"\s*";
6static ANY_WHITESPACE: &str = r"\s+";
7static FIELD_TYPE: &str = r"([a-zA-Z0-9_/]+)";
8static FIELD_NAME: &str = r"([a-zA-Z][a-zA-Z0-9_]*)";
9static EMPTY_BRACKETS: &str = r"\[\s*\]";
10static NUMBER_BRACKETS: &str = r"\[\s*([0-9]+)\s*\]";
11
12#[derive(Debug, PartialEq)]
13struct FieldLine {
14    field_type: String,
15    field_name: String,
16}
17
18#[inline]
19pub fn match_lines(data: &str) -> Result<Vec<FieldInfo>> {
20    data.split('\n')
21        .filter_map(match_line)
22        .collect::<Result<_>>()
23}
24
25fn match_line(data: &str) -> Option<Result<FieldInfo>> {
26    if let Some((info, data)) = match_const_string(data.trim()) {
27        return Some(FieldInfo::new(
28            &info.field_type,
29            &info.field_name,
30            FieldCase::Const(data),
31        ));
32    }
33    let data = match strip_useless(data) {
34        Ok(v) => v,
35        Err(v) => return Some(Err(v)),
36    };
37
38    if data.is_empty() {
39        return None;
40    }
41    if let Some(info) = match_field(data) {
42        return Some(FieldInfo::new(
43            &info.field_type,
44            &info.field_name,
45            FieldCase::Unit,
46        ));
47    }
48    if let Some(info) = match_vector_field(data) {
49        return Some(FieldInfo::new(
50            &info.field_type,
51            &info.field_name,
52            FieldCase::Vector,
53        ));
54    }
55    if let Some((info, count)) = match_array_field(data) {
56        return Some(FieldInfo::new(
57            &info.field_type,
58            &info.field_name,
59            FieldCase::Array(count),
60        ));
61    }
62    if let Some((info, data)) = match_const_numeric(data) {
63        return Some(FieldInfo::new(
64            &info.field_type,
65            &info.field_name,
66            FieldCase::Const(data),
67        ));
68    }
69    Some(Err(Error::BadMessageContent(data.into())))
70}
71
72fn match_const_string(data: &str) -> Option<(FieldLine, String)> {
73    lazy_static! {
74        static ref MATCHER: String = format!(
75            r"^(string){}{}{}={}(.*)$",
76            ANY_WHITESPACE, FIELD_NAME, IGNORE_WHITESPACE, IGNORE_WHITESPACE
77        );
78        static ref RE: Regex = Regex::new(&MATCHER).unwrap();
79    }
80    let captures = match RE.captures(data) {
81        Some(v) => v,
82        None => return None,
83    };
84    Some((
85        FieldLine {
86            field_type: captures.get(1).unwrap().as_str().into(),
87            field_name: captures.get(2).unwrap().as_str().into(),
88        },
89        captures.get(3).unwrap().as_str().into(),
90    ))
91}
92
93fn match_field(data: &str) -> Option<FieldLine> {
94    lazy_static! {
95        static ref MATCHER: String = format!("^{}{}{}$", FIELD_TYPE, ANY_WHITESPACE, FIELD_NAME);
96        static ref RE: Regex = Regex::new(&MATCHER).unwrap();
97    }
98    let captures = match RE.captures(data) {
99        Some(v) => v,
100        None => return None,
101    };
102    Some(FieldLine {
103        field_type: captures.get(1).unwrap().as_str().into(),
104        field_name: captures.get(2).unwrap().as_str().into(),
105    })
106}
107
108fn match_vector_field(data: &str) -> Option<FieldLine> {
109    lazy_static! {
110        static ref MATCHER: String = format!(
111            "^{}{}{}{}{}$",
112            FIELD_TYPE, IGNORE_WHITESPACE, EMPTY_BRACKETS, ANY_WHITESPACE, FIELD_NAME
113        );
114        static ref RE: Regex = Regex::new(&MATCHER).unwrap();
115    }
116    let captures = match RE.captures(data) {
117        Some(v) => v,
118        None => return None,
119    };
120    Some(FieldLine {
121        field_type: captures.get(1).unwrap().as_str().into(),
122        field_name: captures.get(2).unwrap().as_str().into(),
123    })
124}
125
126fn match_array_field(data: &str) -> Option<(FieldLine, usize)> {
127    lazy_static! {
128        static ref MATCHER: String = format!(
129            "^{}{}{}{}{}$",
130            FIELD_TYPE, IGNORE_WHITESPACE, NUMBER_BRACKETS, ANY_WHITESPACE, FIELD_NAME
131        );
132        static ref RE: Regex = Regex::new(&MATCHER).unwrap();
133    }
134    let captures = match RE.captures(data) {
135        Some(v) => v,
136        None => return None,
137    };
138    Some((
139        FieldLine {
140            field_type: captures.get(1).unwrap().as_str().into(),
141            field_name: captures.get(3).unwrap().as_str().into(),
142        },
143        captures.get(2).unwrap().as_str().parse().unwrap(),
144    ))
145}
146
147fn match_const_numeric(data: &str) -> Option<(FieldLine, String)> {
148    lazy_static! {
149        static ref MATCHER: String = format!(
150            r"^{}{}{}{}={}(-?[0-9\.eE\+\-]+)$",
151            FIELD_TYPE, ANY_WHITESPACE, FIELD_NAME, IGNORE_WHITESPACE, IGNORE_WHITESPACE
152        );
153        static ref RE: Regex = Regex::new(&MATCHER).unwrap();
154    }
155    let captures = match RE.captures(data) {
156        Some(v) => v,
157        None => return None,
158    };
159    Some((
160        FieldLine {
161            field_type: captures.get(1).unwrap().as_str().into(),
162            field_name: captures.get(2).unwrap().as_str().into(),
163        },
164        captures.get(3).unwrap().as_str().into(),
165    ))
166}
167
168#[inline]
169fn strip_useless(data: &str) -> Result<&str> {
170    Ok(data
171        .split('#')
172        .next()
173        .ok_or_else(|| Error::BadMessageContent(data.into()))?
174        .trim())
175}
176
177#[cfg(test)]
178mod tests;