use std::collections::HashMap;
use std::path::PathBuf;
use super::errors::{IdlError, IdlResult};
use super::parser_pest;
use super::types::{
ACTION_FEEDBACK_MESSAGE_SUFFIX, ACTION_FEEDBACK_SUFFIX, ACTION_GOAL_SERVICE_SUFFIX,
ACTION_GOAL_SUFFIX, ACTION_RESULT_SERVICE_SUFFIX, ACTION_RESULT_SUFFIX, Action, Annotatable,
Array, Constant, IdlContent, IdlContentElement, IdlFile, IdlLocator, IdlType, Include, Member,
Message, NamespacedType, SERVICE_EVENT_MESSAGE_SUFFIX, SERVICE_REQUEST_MESSAGE_SUFFIX,
SERVICE_RESPONSE_MESSAGE_SUFFIX, Service, Structure,
};
use super::values::IdlValue;
fn convert_annotation_params(params: &[(String, IdlValue)]) -> IdlValue {
if params.is_empty() {
return IdlValue::Null;
}
let mut map = HashMap::new();
for (key, value) in params {
map.insert(key.clone(), value.clone());
}
IdlValue::Object(map)
}
fn collect_typedefs(definitions: &[parser_pest::IdlDefinition]) -> HashMap<String, IdlType> {
let mut typedefs = HashMap::new();
for def in definitions {
if let parser_pest::IdlDefinition::Typedef(typedef) = def {
let resolved_type = if typedef.array_sizes.is_empty() {
typedef.base_type.clone()
} else if typedef.array_sizes.len() == 1 {
IdlType::Array(Array::new(
typedef.base_type.clone(),
typedef.array_sizes[0],
))
} else {
let mut current_type = typedef.base_type.clone();
for &size in typedef.array_sizes.iter().rev() {
current_type = IdlType::Array(Array::new(current_type, size));
}
current_type
};
typedefs.insert(typedef.name.clone(), resolved_type);
}
}
let mut changed = true;
while changed {
changed = false;
let keys: Vec<_> = typedefs.keys().cloned().collect();
for key in keys {
let mut resolved = typedefs.get(&key).cloned().unwrap();
let original = resolved.clone();
resolve_typedef(&mut resolved, &typedefs);
if resolved != original {
typedefs.insert(key, resolved);
changed = true;
}
}
}
typedefs
}
fn resolve_typedef(member_type: &mut IdlType, typedefs: &HashMap<String, IdlType>) {
match member_type {
IdlType::Named(named) => {
if let Some(resolved) = typedefs.get(&named.name) {
*member_type = resolved.clone();
}
}
IdlType::Array(arr) => {
resolve_typedef(&mut arr.value_type, typedefs);
}
IdlType::BoundedSequence(seq) => {
resolve_typedef(&mut seq.value_type, typedefs);
}
IdlType::UnboundedSequence(seq) => {
resolve_typedef(&mut seq.value_type, typedefs);
}
_ => {}
}
}
fn resolve_typedefs_in_structure(structure: &mut Structure, typedefs: &HashMap<String, IdlType>) {
for member in &mut structure.members {
resolve_typedef(&mut member.member_type, typedefs);
}
}
fn convert_struct_to_message(
struct_def: &parser_pest::IdlStruct,
namespaces: &[String],
constants_map: &HashMap<String, Vec<Constant>>,
typedefs: &HashMap<String, IdlType>,
) -> Message {
let namespaced_type = NamespacedType::new(namespaces.to_vec(), struct_def.name.clone());
let members: Vec<Member> = struct_def
.fields
.iter()
.map(|f| {
let mut member = Member::new(f.field_type.clone(), f.name.clone());
member.annotations.annotations = f
.annotations
.iter()
.map(|a| super::types::Annotation {
name: a.name.clone(),
value: convert_annotation_params(&a.params),
})
.collect();
member
})
.collect();
let mut structure = Structure::new(namespaced_type);
structure.members = members;
structure.annotations.annotations = struct_def
.annotations
.iter()
.map(|a| super::types::Annotation {
name: a.name.clone(),
value: convert_annotation_params(&a.params),
})
.collect();
resolve_typedefs_in_structure(&mut structure, typedefs);
let mut message = Message::new(structure);
if let Some(constants) = constants_map.get(&struct_def.name) {
message.constants.clone_from(constants);
}
message
}
fn collect_constants(definitions: &[parser_pest::IdlDefinition]) -> HashMap<String, Vec<Constant>> {
let mut constants_map = HashMap::new();
for def in definitions {
if let parser_pest::IdlDefinition::Module(module) = def
&& let Some(base_name) = module.name.strip_suffix("_Constants")
{
let mut constants = Vec::new();
for const_def in &module.definitions {
if let parser_pest::IdlDefinition::Constant(c) = const_def {
let mut constant =
Constant::new(c.name.clone(), c.const_type.clone(), c.value.clone());
constant.annotations.annotations = c
.annotations
.iter()
.map(|a| super::types::Annotation {
name: a.name.clone(),
value: convert_annotation_params(&a.params),
})
.collect();
constants.push(constant);
}
}
if !constants.is_empty() {
constants_map.insert(base_name.to_string(), constants);
}
}
}
constants_map
}
fn build_message_map(
definitions: &[parser_pest::IdlDefinition],
namespaces: &[String],
constants_map: &HashMap<String, Vec<Constant>>,
typedefs: &HashMap<String, IdlType>,
) -> HashMap<String, Message> {
let mut messages = HashMap::new();
for def in definitions {
if let parser_pest::IdlDefinition::Struct(struct_def) = def {
let message =
convert_struct_to_message(struct_def, namespaces, constants_map, typedefs);
messages.insert(struct_def.name.clone(), message);
}
}
messages
}
fn process_srv_module(
module: &parser_pest::IdlModule,
namespaces: &[String],
) -> Vec<IdlContentElement> {
let mut elements = Vec::new();
let constants_map = collect_constants(&module.definitions);
let typedefs = collect_typedefs(&module.definitions);
let mut srv_namespaces = namespaces.to_vec();
srv_namespaces.push(module.name.clone());
let mut messages = build_message_map(
&module.definitions,
&srv_namespaces,
&constants_map,
&typedefs,
);
let request_names: Vec<String> = messages
.keys()
.filter(|name| name.ends_with(SERVICE_REQUEST_MESSAGE_SUFFIX))
.cloned()
.collect();
for request_name in request_names {
let Some(base) = request_name.strip_suffix(SERVICE_REQUEST_MESSAGE_SUFFIX) else {
continue;
};
let response_name = format!("{base}{SERVICE_RESPONSE_MESSAGE_SUFFIX}");
let event_name = format!("{base}{SERVICE_EVENT_MESSAGE_SUFFIX}");
let (Some(request), Some(response)) = (
messages.remove(&request_name),
messages.remove(&response_name),
) else {
if let Some(req) = messages.remove(&request_name) {
messages.insert(request_name, req);
}
continue;
};
let mut request = request;
let mut response = response;
request.structure.annotations = Annotatable::new();
response.structure.annotations = Annotatable::new();
let mut service = Service::new(
NamespacedType::new(srv_namespaces.clone(), base),
request,
response,
);
if let Some(event) = messages.remove(&event_name) {
service.event_message = event;
}
elements.push(IdlContentElement::Service(service));
}
for message in messages.into_values() {
elements.push(IdlContentElement::Message(message));
}
for def in &module.definitions {
if let parser_pest::IdlDefinition::Module(nested) = def
&& !nested.name.ends_with("_Constants")
{
elements.extend(convert_definitions_with_namespace(
&nested.definitions,
&srv_namespaces,
));
}
}
for def in &module.definitions {
if let parser_pest::IdlDefinition::Constant(c) = def {
elements.push(IdlContentElement::Constant(Constant::new(
c.name.clone(),
c.const_type.clone(),
c.value.clone(),
)));
}
}
elements
}
#[allow(clippy::too_many_lines)]
fn process_action_module(
module: &parser_pest::IdlModule,
namespaces: &[String],
) -> Vec<IdlContentElement> {
let mut elements = Vec::new();
let constants_map = collect_constants(&module.definitions);
let typedefs = collect_typedefs(&module.definitions);
let mut action_namespaces = namespaces.to_vec();
action_namespaces.push(module.name.clone());
let mut messages = build_message_map(
&module.definitions,
&action_namespaces,
&constants_map,
&typedefs,
);
let mut services: HashMap<String, Service> = HashMap::new();
let request_names: Vec<String> = messages
.keys()
.filter(|name| name.ends_with(SERVICE_REQUEST_MESSAGE_SUFFIX))
.cloned()
.collect();
for request_name in request_names {
let Some(base) = request_name.strip_suffix(SERVICE_REQUEST_MESSAGE_SUFFIX) else {
continue;
};
let response_name = format!("{base}{SERVICE_RESPONSE_MESSAGE_SUFFIX}");
let event_name = format!("{base}{SERVICE_EVENT_MESSAGE_SUFFIX}");
let (Some(request), Some(response)) = (
messages.remove(&request_name),
messages.remove(&response_name),
) else {
continue;
};
let mut request = request;
let mut response = response;
request.structure.annotations = Annotatable::new();
response.structure.annotations = Annotatable::new();
let mut service = Service::new(
NamespacedType::new(action_namespaces.clone(), base),
request,
response,
);
if let Some(event) = messages.remove(&event_name) {
service.event_message = event;
}
services.insert(base.to_string(), service);
}
let goal_names: Vec<String> = messages
.keys()
.filter(|name| name.ends_with(ACTION_GOAL_SUFFIX))
.cloned()
.collect();
for goal_name in goal_names {
let Some(base) = goal_name.strip_suffix(ACTION_GOAL_SUFFIX) else {
continue;
};
let result_name = format!("{base}{ACTION_RESULT_SUFFIX}");
let feedback_name = format!("{base}{ACTION_FEEDBACK_SUFFIX}");
let feedback_msg_name = format!("{base}{ACTION_FEEDBACK_MESSAGE_SUFFIX}");
let send_goal_name = format!("{base}{ACTION_GOAL_SERVICE_SUFFIX}");
let get_result_name = format!("{base}{ACTION_RESULT_SERVICE_SUFFIX}");
let (Some(goal), Some(result), Some(feedback)) = (
messages.remove(&goal_name),
messages.remove(&result_name),
messages.remove(&feedback_name),
) else {
continue;
};
let mut goal = goal;
let mut result = result;
let mut feedback = feedback;
goal.structure.annotations = Annotatable::new();
result.structure.annotations = Annotatable::new();
feedback.structure.annotations = Annotatable::new();
let mut action = Action::new(
NamespacedType::new(action_namespaces.clone(), base),
goal,
result,
feedback,
);
if let Some(feedback_msg) = messages.remove(&feedback_msg_name) {
action.feedback_message = feedback_msg;
}
if let Some(send_goal) = services.remove(&send_goal_name) {
action.send_goal_service = send_goal;
}
if let Some(get_result) = services.remove(&get_result_name) {
action.get_result_service = get_result;
}
for include in &action.implicit_includes {
elements.push(IdlContentElement::Include(include.clone()));
}
elements.push(IdlContentElement::Action(action));
}
for service in services.into_values() {
elements.push(IdlContentElement::Service(service));
}
for message in messages.into_values() {
elements.push(IdlContentElement::Message(message));
}
for def in &module.definitions {
if let parser_pest::IdlDefinition::Module(nested) = def
&& !nested.name.ends_with("_Constants")
{
elements.extend(convert_definitions_with_namespace(
&nested.definitions,
&action_namespaces,
));
}
}
for def in &module.definitions {
if let parser_pest::IdlDefinition::Constant(c) = def {
elements.push(IdlContentElement::Constant(Constant::new(
c.name.clone(),
c.const_type.clone(),
c.value.clone(),
)));
}
}
elements
}
fn convert_definitions_with_namespace(
definitions: &[parser_pest::IdlDefinition],
namespaces: &[String],
) -> Vec<IdlContentElement> {
let mut elements = Vec::new();
let constants_map = collect_constants(definitions);
let typedefs = collect_typedefs(definitions);
for def in definitions {
match def {
parser_pest::IdlDefinition::Module(module) => {
if module.name.ends_with("_Constants") {
continue;
}
match module.name.as_str() {
"srv" => {
elements.extend(process_srv_module(module, namespaces));
}
"action" => {
elements.extend(process_action_module(module, namespaces));
}
_ => {
let mut new_namespaces = namespaces.to_vec();
new_namespaces.push(module.name.clone());
elements.extend(convert_definitions_with_namespace(
&module.definitions,
&new_namespaces,
));
}
}
}
parser_pest::IdlDefinition::Struct(struct_def) => {
let message =
convert_struct_to_message(struct_def, namespaces, &constants_map, &typedefs);
elements.push(IdlContentElement::Message(message));
}
parser_pest::IdlDefinition::Constant(const_def) => {
let constant = Constant::new(
const_def.name.clone(),
const_def.const_type.clone(),
const_def.value.clone(),
);
elements.push(IdlContentElement::Constant(constant));
}
_ => {
}
}
}
elements
}
pub fn parse_idl_string(
content: &str,
base_path: PathBuf,
relative_path: PathBuf,
) -> IdlResult<IdlFile> {
let locator = IdlLocator::new(base_path, relative_path);
match parser_pest::parse_idl(content) {
Ok(parsed_file) => {
let mut elements = Vec::new();
for include_path in &parsed_file.includes {
elements.push(IdlContentElement::Include(Include::new(
include_path.clone(),
)));
}
let mut converted = convert_definitions_with_namespace(&parsed_file.definitions, &[]);
let existing_locators: std::collections::HashSet<String> = elements
.iter()
.filter_map(|el| {
if let IdlContentElement::Include(inc) = el {
Some(inc.locator.clone())
} else {
None
}
})
.collect();
let filtered: Vec<_> = converted
.drain(..)
.filter(|el| {
if let IdlContentElement::Include(inc) = el {
!existing_locators.contains(&inc.locator)
} else {
true
}
})
.collect();
elements.extend(filtered);
let content = IdlContent { elements };
Ok(IdlFile::new(locator, content))
}
Err(parse_error) => Err(IdlError::ParseError {
line: 1,
column: 1,
message: format!("Parse error: {parse_error}"),
}),
}
}
pub fn parse_idl_file(locator: &IdlLocator) -> IdlResult<IdlFile> {
let path = locator.get_absolute_path();
let content = std::fs::read_to_string(&path)?;
parse_idl_string(
&content,
locator.basepath.clone(),
locator.relative_path.clone(),
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_placeholder_parse() {
let input = "const int32 MY_CONST = 42;";
let result = parse_idl_string(input, std::env::temp_dir(), PathBuf::from("test.idl"));
assert!(result.is_ok());
let idl_file = result.unwrap();
assert_eq!(idl_file.locator.relative_path, PathBuf::from("test.idl"));
}
}