Skip to content

Commit

Permalink
Extract Msg and Srv from codegen
Browse files Browse the repository at this point in the history
  • Loading branch information
adnanademovic committed Aug 4, 2021
1 parent d62957b commit ff91ad7
Show file tree
Hide file tree
Showing 42 changed files with 1,408 additions and 1,135 deletions.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 2 additions & 0 deletions ros_msg_parser/Cargo.toml
Expand Up @@ -8,6 +8,8 @@ repository = "https://github.com/adnanademovic/rosrust"
version = "0.1.0"

[dependencies]
hex = "0.4.3"
lazy_static = "1.4.0"
md-5 = "0.9.1"
regex = "1.5.4"
thiserror = "1.0.26"
136 changes: 136 additions & 0 deletions ros_msg_parser/src/data_type.rs
@@ -0,0 +1,136 @@
use crate::{Error, MessagePath, Result};
use std::collections::HashMap;

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum DataType {
Bool,
I8(bool),
I16,
I32,
I64,
U8(bool),
U16,
U32,
U64,
F32,
F64,
String,
Time,
Duration,
LocalStruct(String),
RemoteStruct(MessagePath),
}

const BOOL_KEY: &str = "bool";
const INT8_KEY: &str = "int8";
const BYTE_KEY: &str = "byte";
const INT16_KEY: &str = "int16";
const INT32_KEY: &str = "int32";
const INT64_KEY: &str = "int64";
const UINT8_KEY: &str = "uint8";
const CHAR_KEY: &str = "char";
const UINT16_KEY: &str = "uint16";
const UINT32_KEY: &str = "uint32";
const UINT64_KEY: &str = "uint64";
const FLOAT32_KEY: &str = "float32";
const FLOAT64_KEY: &str = "float64";
const STRING_KEY: &str = "string";
const TIME_KEY: &str = "time";
const DURATION_KEY: &str = "duration";

impl DataType {
pub fn parse(datatype: &str) -> Result<Self> {
Ok(match datatype {
BOOL_KEY => DataType::Bool,
INT8_KEY => DataType::I8(true),
BYTE_KEY => DataType::I8(false),
INT16_KEY => DataType::I16,
INT32_KEY => DataType::I32,
INT64_KEY => DataType::I64,
UINT8_KEY => DataType::U8(true),
CHAR_KEY => DataType::U8(false),
UINT16_KEY => DataType::U16,
UINT32_KEY => DataType::U32,
UINT64_KEY => DataType::U64,
FLOAT32_KEY => DataType::F32,
FLOAT64_KEY => DataType::F64,
STRING_KEY => DataType::String,
TIME_KEY => DataType::Time,
DURATION_KEY => DataType::Duration,
"Header" => DataType::RemoteStruct(MessagePath::new("std_msgs", "Header")?),
_ => {
let parts = datatype.splitn(2, '/').collect::<Vec<&str>>();
match parts[..] {
[name] => DataType::LocalStruct(name.into()),
[package, name] => DataType::RemoteStruct(MessagePath::new(package, name)?),
_ => {
return Err(Error::UnsupportedDataType {
name: datatype.into(),
reason: "string needs to be in `name` or `package/name` format".into(),
})
}
}
}
})
}

pub fn is_builtin(&self) -> bool {
match *self {
DataType::Bool
| DataType::I8(_)
| DataType::I16
| DataType::I32
| DataType::I64
| DataType::U8(_)
| DataType::U16
| DataType::U32
| DataType::U64
| DataType::F32
| DataType::F64
| DataType::String
| DataType::Time
| DataType::Duration => true,
DataType::LocalStruct(_) | DataType::RemoteStruct(_) => false,
}
}

pub fn md5_string(
&self,
package: &str,
hashes: &HashMap<MessagePath, String>,
) -> Result<String> {
Ok(match *self {
DataType::Bool => BOOL_KEY,
DataType::I8(true) => INT8_KEY,
DataType::I8(false) => BYTE_KEY,
DataType::I16 => INT16_KEY,
DataType::I32 => INT32_KEY,
DataType::I64 => INT64_KEY,
DataType::U8(true) => UINT8_KEY,
DataType::U8(false) => CHAR_KEY,
DataType::U16 => UINT16_KEY,
DataType::U32 => UINT32_KEY,
DataType::U64 => UINT64_KEY,
DataType::F32 => FLOAT32_KEY,
DataType::F64 => FLOAT64_KEY,
DataType::String => STRING_KEY,
DataType::Time => TIME_KEY,
DataType::Duration => DURATION_KEY,
DataType::LocalStruct(ref name) => hashes
.get(&MessagePath::new(package, name)?)
.ok_or_else(|| Error::MessageDependencyMissing {
package: package.into(),
name: name.into(),
})?
.as_str(),
DataType::RemoteStruct(ref message) => hashes
.get(message)
.ok_or_else(|| Error::MessageDependencyMissing {
package: message.package().into(),
name: message.name().into(),
})?
.as_str(),
}
.into())
}
}
6 changes: 6 additions & 0 deletions ros_msg_parser/src/error.rs
Expand Up @@ -2,6 +2,12 @@
pub enum Error {
#[error("message path `{name}` is invalid, {reason}")]
InvalidMessagePath { name: String, reason: String },
#[error("data type `{name}` is invalid, {reason}")]
UnsupportedDataType { name: String, reason: String },
#[error("bad content in message: `{0}`")]
BadMessageContent(String),
#[error("message dependency missing: {package}/{name}")]
MessageDependencyMissing { package: String, name: String },
}

pub type Result<T> = std::result::Result<T, Error>;
55 changes: 55 additions & 0 deletions ros_msg_parser/src/field_info.rs
@@ -0,0 +1,55 @@
use crate::{DataType, MessagePath, Result};
use std::collections::HashMap;

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum FieldCase {
Unit,
Vector,
Array(usize),
Const(String),
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FieldInfo {
pub datatype: DataType,
pub name: String,
pub case: FieldCase,
}

impl FieldInfo {
pub fn new(datatype: &str, name: &str, case: FieldCase) -> Result<FieldInfo> {
Ok(FieldInfo {
datatype: DataType::parse(datatype)?,
name: name.into(),
case,
})
}

pub fn is_constant(&self) -> bool {
matches!(self.case, FieldCase::Const(..))
}

pub fn md5_string(
&self,
package: &str,
hashes: &HashMap<MessagePath, String>,
) -> Result<String> {
let datatype = self.datatype.md5_string(package, hashes)?;
Ok(match (self.datatype.is_builtin(), &self.case) {
(_, &FieldCase::Const(ref v)) => format!("{} {}={}", datatype, self.name, v),
(false, _) | (_, &FieldCase::Unit) => format!("{} {}", datatype, self.name),
(true, &FieldCase::Vector) => format!("{}[] {}", datatype, self.name),
(true, &FieldCase::Array(l)) => format!("{}[{}] {}", datatype, l, self.name),
})
}

pub fn is_header(&self) -> bool {
if self.case != FieldCase::Unit || self.name != "header" {
return false;
}
match &self.datatype {
DataType::RemoteStruct(msg) => msg.package() == "std_msgs" && msg.name() == "Header",
_ => false,
}
}
}
11 changes: 11 additions & 0 deletions ros_msg_parser/src/lib.rs
@@ -1,5 +1,16 @@
mod data_type;
mod error;
mod field_info;
mod message_path;
mod msg;
mod parse_msg;
mod srv;
#[cfg(test)]
mod tests;

pub use data_type::DataType;
pub use error::{Error, Result};
pub use field_info::{FieldCase, FieldInfo};
pub use message_path::MessagePath;
pub use msg::Msg;
pub use srv::Srv;
45 changes: 2 additions & 43 deletions ros_msg_parser/src/message_path.rs
Expand Up @@ -11,7 +11,7 @@ pub struct MessagePath {
name: String,
}

fn is_valid_package_name(package: &str) -> bool {
pub fn is_valid_package_name(package: &str) -> bool {
lazy_static! {
static ref RE_PACKAGE_CORRECT_CHAR_SET_AND_LENGTH: Regex =
Regex::new("^[a-z][a-z0-9_]+$").unwrap();
Expand Down Expand Up @@ -43,7 +43,7 @@ impl MessagePath {
[package, name] => Self::new(package, name),
_ => Err(Error::InvalidMessagePath {
name: input.into(),
reason: "string needs to be in package/name format".into(),
reason: "string needs to be in `package/name` format".into(),
}),
}
}
Expand Down Expand Up @@ -74,44 +74,3 @@ impl<'a> TryFrom<&'a str> for MessagePath {
Self::from_combined(value)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn package_names_must_be_at_least_two_characters() {
assert!(is_valid_package_name("foo"));
assert!(is_valid_package_name("fo"));
assert!(!is_valid_package_name("f"));
}

#[test]
fn package_names_must_start_with_lowercase_alphabetic() {
assert!(is_valid_package_name("foo_123"));
assert!(!is_valid_package_name("Foo_123"));
assert!(!is_valid_package_name("1oo_123"));
assert!(!is_valid_package_name("_oo_123"));
}

#[test]
fn package_names_must_not_contain_uppercase_anywhere() {
assert!(is_valid_package_name("foo_123"));
assert!(!is_valid_package_name("foO_123"));
}

#[test]
fn package_names_are_limited_to_lowercase_alphanumeric_and_underscore() {
assert!(is_valid_package_name("foo_123"));
assert!(!is_valid_package_name("foO_123"));
assert!(!is_valid_package_name("foo-123"));
}

#[test]
fn package_names_must_not_contain_multiple_underscores_in_a_row() {
assert!(is_valid_package_name("foo_123_"));
assert!(is_valid_package_name("f_o_o_1_2_3_"));
assert!(!is_valid_package_name("foo__123_"));
assert!(!is_valid_package_name("foo___123_"));
}
}
72 changes: 72 additions & 0 deletions ros_msg_parser/src/msg.rs
@@ -0,0 +1,72 @@
use std::collections::HashMap;

use crate::{parse_msg::match_lines, DataType, FieldInfo, MessagePath, Result};

#[derive(Clone, Debug)]
pub struct Msg {
pub path: MessagePath,
pub fields: Vec<FieldInfo>,
pub source: String,
}

impl Msg {
pub fn new(path: MessagePath, source: &str) -> Result<Msg> {
let fields = match_lines(source)?;
Ok(Msg {
path,
fields,
source: source.trim().into(),
})
}

pub fn get_type(&self) -> String {
format!("{}/{}", self.path.package(), self.path.name())
}

pub fn dependencies(&self) -> Result<Vec<MessagePath>> {
self.fields
.iter()
.filter_map(|field| match field.datatype {
DataType::LocalStruct(ref name) => {
Some(MessagePath::new(self.path.package(), name))
}
DataType::RemoteStruct(ref message) => Some(Ok(message.clone())),
_ => None,
})
.collect()
}

#[cfg(test)]
pub fn calculate_md5(&self, hashes: &HashMap<MessagePath, String>) -> Result<String> {
use md5::{Digest, Md5};

let mut hasher = Md5::new();
hasher.update(&self.get_md5_representation(hashes)?);
Ok(hex::encode(hasher.finalize()))
}

pub fn get_md5_representation(&self, hashes: &HashMap<MessagePath, String>) -> Result<String> {
let constants = self
.fields
.iter()
.filter(|v| v.is_constant())
.map(|v| v.md5_string(self.path.package(), hashes))
.collect::<Result<Vec<String>>>()?;
let fields = self
.fields
.iter()
.filter(|v| !v.is_constant())
.map(|v| v.md5_string(self.path.package(), hashes))
.collect::<Result<Vec<String>>>()?;
let representation = constants
.into_iter()
.chain(fields)
.collect::<Vec<_>>()
.join("\n");
Ok(representation)
}

pub fn has_header(&self) -> bool {
self.fields.iter().any(FieldInfo::is_header)
}
}

0 comments on commit ff91ad7

Please sign in to comment.