Skip to main content

mcap2arrow_ros2msg/
parser.rs

1//! Conversion from `re_ros_msg`'s AST to the shared [`StructDef`] representation.
2//!
3//! [`parse_msg`] is the single public entry point: it delegates parsing to the
4//! `re_ros_msg` crate and then maps the resulting `MessageSpecification` into
5//! the types understood by `mcap2arrow-ros2-common`.
6
7use mcap2arrow_ros2_common::{ConstDef, FieldDef, PrimitiveType, Ros2Error, StructDef, TypeExpr};
8use re_ros_msg::{
9    MessageSchema,
10    message_spec::{
11        ArraySize, BuiltInType, ComplexType, Constant, Field, MessageSpecification, Type,
12    },
13};
14/// Parse .msg format and generate StructDef
15pub fn parse_msg(schema_name: &str, msg_text: &str) -> Result<StructDef, Ros2Error> {
16    // 1. Parse with re_ros_msg
17    let schema = MessageSchema::parse(schema_name, msg_text)
18        .map_err(|e| Ros2Error(format!("failed to parse msg schema '{schema_name}': {e}")))?;
19
20    // 2. Parse schema_name to get full_name
21    let full_name = parse_schema_name(schema_name)?;
22
23    // 3. Convert MessageSpecification → StructDef
24    convert_to_struct_def(full_name, schema.spec)
25}
26
27fn parse_schema_name(name: &str) -> Result<Vec<String>, Ros2Error> {
28    // "geometry_msgs/msg/Point" → vec!["geometry_msgs", "msg", "Point"]
29    // "std_msgs/String" → vec!["std_msgs", "msg", "String"]
30    let parts: Vec<&str> = name.split('/').collect();
31
32    match parts.len() {
33        3 => Ok(parts.iter().map(|s| s.to_string()).collect()),
34        2 => Ok(vec![
35            parts[0].to_string(),
36            "msg".to_string(),
37            parts[1].to_string(),
38        ]),
39        _ => Err(format!("invalid schema name format: {name}").into()),
40    }
41}
42
43fn convert_to_struct_def(
44    full_name: Vec<String>,
45    spec: MessageSpecification,
46) -> Result<StructDef, Ros2Error> {
47    let fields = spec
48        .fields
49        .into_iter()
50        .map(convert_field)
51        .collect::<Result<Vec<_>, _>>()?;
52
53    let consts = spec
54        .constants
55        .into_iter()
56        .map(convert_constant)
57        .collect::<Result<Vec<_>, _>>()?;
58
59    Ok(StructDef {
60        full_name,
61        fields,
62        consts,
63    })
64}
65
66fn convert_field(field: Field) -> Result<FieldDef, Ros2Error> {
67    let (ty, fixed_len) = convert_type(&field.ty)?;
68
69    Ok(FieldDef {
70        name: field.name,
71        ty,
72        fixed_len,
73    })
74}
75
76fn convert_type(ty: &Type) -> Result<(TypeExpr, Option<usize>), Ros2Error> {
77    match ty {
78        Type::BuiltIn(builtin) => Ok((convert_builtin_type(builtin), None)),
79        Type::Complex(complex) => {
80            let scoped = convert_complex_type(complex);
81            Ok((TypeExpr::Scoped(scoped), None))
82        }
83        Type::Array { ty: elem_ty, size } => {
84            let (elem, elem_fixed) = convert_type(elem_ty)?;
85
86            // If the element itself has fixed_len, that becomes the outer array
87            if elem_fixed.is_some() {
88                return Err("nested fixed arrays are not supported in ROS2".into());
89            }
90
91            match size {
92                ArraySize::Fixed(n) => {
93                    // Fixed array: field has fixed_len, type is the element
94                    Ok((elem, Some(*n)))
95                }
96                ArraySize::Unbounded => {
97                    // Dynamic sequence
98                    Ok((
99                        TypeExpr::Sequence {
100                            elem: Box::new(elem),
101                            max_len: None,
102                        },
103                        None,
104                    ))
105                }
106                ArraySize::Bounded(n) => {
107                    // Bounded sequence
108                    Ok((
109                        TypeExpr::Sequence {
110                            elem: Box::new(elem),
111                            max_len: Some(*n),
112                        },
113                        None,
114                    ))
115                }
116            }
117        }
118    }
119}
120
121fn convert_builtin_type(ty: &BuiltInType) -> TypeExpr {
122    let prim = match ty {
123        BuiltInType::Bool => PrimitiveType::Bool,
124        BuiltInType::Byte => PrimitiveType::U8,
125        BuiltInType::Char => PrimitiveType::U8,
126        BuiltInType::Int8 => PrimitiveType::I8,
127        BuiltInType::UInt8 => PrimitiveType::U8,
128        BuiltInType::Int16 => PrimitiveType::I16,
129        BuiltInType::UInt16 => PrimitiveType::U16,
130        BuiltInType::Int32 => PrimitiveType::I32,
131        BuiltInType::UInt32 => PrimitiveType::U32,
132        BuiltInType::Int64 => PrimitiveType::I64,
133        BuiltInType::UInt64 => PrimitiveType::U64,
134        BuiltInType::Float32 => PrimitiveType::F32,
135        BuiltInType::Float64 => PrimitiveType::F64,
136        BuiltInType::String(None) => PrimitiveType::String,
137        BuiltInType::String(Some(n)) => {
138            return TypeExpr::BoundedString(*n);
139        }
140        BuiltInType::WString(None) => PrimitiveType::WString,
141        BuiltInType::WString(Some(n)) => {
142            return TypeExpr::BoundedWString(*n);
143        }
144    };
145
146    TypeExpr::Primitive(prim)
147}
148
149fn convert_complex_type(ty: &ComplexType) -> Vec<String> {
150    match ty {
151        ComplexType::Absolute { package, name } => {
152            // "pkg/Type" → vec!["pkg", "msg", "Type"]
153            vec![package.clone(), "msg".to_string(), name.clone()]
154        }
155        ComplexType::Relative { name } => {
156            // "Type" → vec!["Type"]
157            vec![name.clone()]
158        }
159    }
160}
161
162fn convert_constant(constant: Constant) -> Result<ConstDef, Ros2Error> {
163    let ty = match &constant.ty {
164        Type::BuiltIn(builtin) => convert_builtin_type(builtin),
165        Type::Array { .. } => {
166            return Err("constants cannot be arrays".into());
167        }
168        Type::Complex(_) => {
169            return Err("constants must be primitive types".into());
170        }
171    };
172
173    Ok(ConstDef {
174        ty,
175        name: constant.name,
176        value: format!("{:?}", constant.value),
177    })
178}