Skip to content

Commit

Permalink
add support to parse actions from idl files and create derived types
Browse files Browse the repository at this point in the history
  • Loading branch information
dirk-thomas committed Nov 21, 2018
1 parent 234e657 commit cf3d10f
Show file tree
Hide file tree
Showing 4 changed files with 298 additions and 4 deletions.
110 changes: 108 additions & 2 deletions rosidl_parser/rosidl_parser/definition.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@
SERVICE_REQUEST_MESSAGE_SUFFIX = '_Request'
SERVICE_RESPONSE_MESSAGE_SUFFIX = '_Response'

ACTION_GOAL_SERVICE_SUFFIX = '_Goal'
ACTION_RESULT_SERVICE_SUFFIX = '_Result'
ACTION_FEEDBACK_MESSAGE_SUFFIX = '_Feedback'
ACTION_WRAPPER_TYPE_SUFFIX = '_Action'


class AbstractType:
"""The base class for all types."""
Expand Down Expand Up @@ -347,17 +352,18 @@ class Structure(Annotatable):

__slots__ = ('type', 'members')

def __init__(self, type_):
def __init__(self, type_, members=None):
"""
Constructor.
:param NamespacedType type_: the namespaced type identifying the
structure
:param list members: the members of the structure
"""
super().__init__()
assert isinstance(type_, NamespacedType)
self.type = type_
self.members = []
self.members = members or []


class Include:
Expand Down Expand Up @@ -444,6 +450,106 @@ def __init__(self, type_, request, response):
self.response_message = response


class Action:
"""A namespaced type of an action including the derived types."""

__slots__ = (
'structure_type', 'goal_request', 'result_response', 'feedback',
'goal_service', 'result_service', 'feedback_message')

def __init__(self, type_, goal_request, result_response, feedback):
"""
Constructor.
From the provide type the actually used services and message are
derived.
:param NamespacedType type_: the namespaced type identifying the action
:param Message goal_request: the goal request message
:param Message result_response: the result response message
:param Message feedback: the feedback message
"""
super().__init__()

assert isinstance(type_, NamespacedType)
self.structure_type = type_

assert isinstance(goal_request, Message)
assert goal_request.structure.type.namespaces == type_.namespaces
assert goal_request.structure.type.name == type_.name + \
ACTION_GOAL_SERVICE_SUFFIX + SERVICE_REQUEST_MESSAGE_SUFFIX
self.goal_request = goal_request

assert isinstance(result_response, Message)
assert result_response.structure.type.namespaces == type_.namespaces
assert result_response.structure.type.name == type_.name + \
ACTION_RESULT_SERVICE_SUFFIX + SERVICE_RESPONSE_MESSAGE_SUFFIX
self.result_response = result_response

assert isinstance(feedback, Message)
assert feedback.structure.type.namespaces == type_.namespaces
assert feedback.structure.type.name == type_.name + \
ACTION_FEEDBACK_MESSAGE_SUFFIX
self.feedback = feedback

# derived types
goal_service_name = type_.name + ACTION_WRAPPER_TYPE_SUFFIX + \
ACTION_GOAL_SERVICE_SUFFIX
self.goal_service = Service(
NamespacedType(
namespaces=type_.namespaces, name=goal_service_name),
request=Message(Structure(
NamespacedType(
namespaces=type_.namespaces,
name=goal_service_name + SERVICE_REQUEST_MESSAGE_SUFFIX),
members=[
Member(Array(BasicType('uint8'), 16), 'uuid'),
Member(goal_request.structure.type, 'request')]
)),
response=Message(Structure(
NamespacedType(
namespaces=type_.namespaces,
name=goal_service_name + SERVICE_RESPONSE_MESSAGE_SUFFIX),
members=[
Member(BasicType('boolean'), 'accepted'),
Member(
NamespacedType(['builtin_interfaces', 'msg'], 'Time'),
'stamp')]
)),
)

result_service_name = type_.name + ACTION_WRAPPER_TYPE_SUFFIX + \
ACTION_RESULT_SERVICE_SUFFIX
self.result_service = Service(
NamespacedType(
namespaces=type_.namespaces, name=result_service_name),
request=Message(Structure(
NamespacedType(
namespaces=type_.namespaces,
name=result_service_name + SERVICE_REQUEST_MESSAGE_SUFFIX),
members=[Member(Array(BasicType('uint8'), 16), 'uuid')]
)),
response=Message(Structure(
NamespacedType(
namespaces=type_.namespaces,
name=result_service_name + SERVICE_RESPONSE_MESSAGE_SUFFIX),
members=[
Member(BasicType('int8'), 'status'),
Member(result_response.structure.type, 'response')]
)),
)

self.feedback_message = Message(Structure(
NamespacedType(
namespaces=type_.namespaces,
name=type_.name + ACTION_WRAPPER_TYPE_SUFFIX +
ACTION_FEEDBACK_MESSAGE_SUFFIX),
members=[
Member(Array(BasicType('uint8'), 16), 'uuid'),
Member(feedback.structure.type, 'feedback')]
))


class IdlLocator:
"""A URL of an IDL file."""

Expand Down
56 changes: 54 additions & 2 deletions rosidl_parser/rosidl_parser/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
from lark.tree import Tree

from rosidl_parser.definition import AbstractType
from rosidl_parser.definition import Action
from rosidl_parser.definition import ACTION_FEEDBACK_MESSAGE_SUFFIX
from rosidl_parser.definition import ACTION_GOAL_SERVICE_SUFFIX
from rosidl_parser.definition import ACTION_RESULT_SERVICE_SUFFIX
from rosidl_parser.definition import Annotation
from rosidl_parser.definition import Array
from rosidl_parser.definition import BasicType
Expand Down Expand Up @@ -162,10 +166,58 @@ def extract_content_from_ast(tree):
request, response)
content.elements.append(srv)

elif len(struct_defs) == 3:
goal_request = Message(Structure(NamespacedType(
namespaces=get_module_identifier_values(tree, struct_defs[0]),
name=get_first_identifier_value(struct_defs[0]))))
assert goal_request.structure.type.name.endswith(
ACTION_GOAL_SERVICE_SUFFIX + SERVICE_REQUEST_MESSAGE_SUFFIX)
add_message_members(goal_request, struct_defs[0])
resolve_typedefed_names(goal_request.structure, typedefs)
# TODO move "global" constants/enums within a "matching" namespace into
# the goal request message

result_response = Message(Structure(NamespacedType(
namespaces=get_module_identifier_values(tree, struct_defs[1]),
name=get_first_identifier_value(struct_defs[1]))))
assert result_response.structure.type.name.endswith(
ACTION_RESULT_SERVICE_SUFFIX + SERVICE_RESPONSE_MESSAGE_SUFFIX)
add_message_members(result_response, struct_defs[1])
resolve_typedefed_names(result_response.structure, typedefs)
# TODO move "global" constants/enums within a "matching" namespace into
# the result response message

assert goal_request.structure.type.namespaces == \
result_response.structure.type.namespaces
goal_request_basename = goal_request.structure.type.name[
:-len(ACTION_GOAL_SERVICE_SUFFIX +
SERVICE_REQUEST_MESSAGE_SUFFIX)]
result_response_basename = result_response.structure.type.name[
:-len(ACTION_RESULT_SERVICE_SUFFIX +
SERVICE_RESPONSE_MESSAGE_SUFFIX)]
assert goal_request_basename == result_response_basename

feedback_message = Message(Structure(NamespacedType(
namespaces=get_module_identifier_values(tree, struct_defs[2]),
name=get_first_identifier_value(struct_defs[2]))))
assert feedback_message.structure.type.name.endswith(
ACTION_FEEDBACK_MESSAGE_SUFFIX)
add_message_members(feedback_message, struct_defs[2])
resolve_typedefed_names(feedback_message.structure, typedefs)
# TODO move "global" constants/enums within a "matching" namespace into
# the feedback message

action = Action(
NamespacedType(
namespaces=goal_request.structure.type.namespaces,
name=goal_request_basename),
goal_request, result_response, feedback_message)
content.elements.append(action)

else:
assert False, \
'Currently only .idl files with 1 (a message) or 2 (a service) ' \
'structures are supported'
'Currently only .idl files with 1 (a message), 2 (a service) ' \
'and 3 (an action) structures are supported'

return content

Expand Down
13 changes: 13 additions & 0 deletions rosidl_parser/test/action/MyAction.idl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module rosidl_parser {
module action {
struct MyAction_Goal_Request {
int32 input_value;
};
struct MyAction_Result_Response {
uint32 output_value;
};
struct MyAction_Feedback {
float progress_value;
};
};
};
123 changes: 123 additions & 0 deletions rosidl_parser/test/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@

import pytest

from rosidl_parser.definition import Action
from rosidl_parser.definition import Array
from rosidl_parser.definition import BasicType
from rosidl_parser.definition import BoundedSequence
from rosidl_parser.definition import Constant
from rosidl_parser.definition import IdlLocator
from rosidl_parser.definition import Include
from rosidl_parser.definition import Message
from rosidl_parser.definition import NamespacedType
from rosidl_parser.definition import Service
from rosidl_parser.definition import String
from rosidl_parser.definition import UnboundedSequence
Expand All @@ -33,6 +35,8 @@
pathlib.Path(__file__).parent, pathlib.Path('msg') / 'MyMessage.idl')
SERVICE_IDL_LOCATOR = IdlLocator(
pathlib.Path(__file__).parent, pathlib.Path('srv') / 'MyService.idl')
ACTION_IDL_LOCATOR = IdlLocator(
pathlib.Path(__file__).parent, pathlib.Path('action') / 'MyAction.idl')


@pytest.fixture(scope='module')
Expand Down Expand Up @@ -180,3 +184,122 @@ def test_service_parser(service_idl_file):
assert srv.structure_type.name == 'MyService'
assert len(srv.request_message.structure.members) == 2
assert len(srv.response_message.structure.members) == 1


@pytest.fixture(scope='module')
def action_idl_file():
return parse_idl_file(ACTION_IDL_LOCATOR)


def test_action_parser(action_idl_file):
actions = action_idl_file.content.get_elements_of_type(Action)
assert len(actions) == 1

action = actions[0]
assert isinstance(action, Action)
assert action.structure_type.namespaces == ['rosidl_parser', 'action']
assert action.structure_type.name == 'MyAction'

# check messages defined in the idl file
structure = action.goal_request.structure
assert structure.type.namespaces == ['rosidl_parser', 'action']
assert structure.type.name == 'MyAction_Goal_Request'
assert len(structure.members) == 1
assert isinstance(structure.members[0].type, BasicType)
assert structure.members[0].type.type == 'int32'
assert structure.members[0].name == 'input_value'

structure = action.result_response.structure
assert structure.type.namespaces == ['rosidl_parser', 'action']
assert structure.type.name == 'MyAction_Result_Response'
assert len(structure.members) == 1
assert isinstance(structure.members[0].type, BasicType)
assert structure.members[0].type.type == 'uint32'
assert structure.members[0].name == 'output_value'

structure = action.feedback.structure
assert structure.type.namespaces == ['rosidl_parser', 'action']
assert structure.type.name == 'MyAction_Feedback'
assert len(structure.members) == 1
assert isinstance(structure.members[0].type, BasicType)
assert structure.members[0].type.type == 'float'
assert structure.members[0].name == 'progress_value'

# check derived goal service
structure_type = action.goal_service.structure_type
assert structure_type.namespaces == ['rosidl_parser', 'action']
assert structure_type.name == 'MyAction_Action_Goal'

structure = action.goal_service.request_message.structure
assert len(structure.members) == 2

assert isinstance(structure.members[0].type, Array)
assert structure.members[0].type.size == 16
assert isinstance(structure.members[0].type.basetype, BasicType)
assert structure.members[0].type.basetype.type == 'uint8'
assert structure.members[0].name == 'uuid'

assert isinstance(structure.members[1].type, NamespacedType)
assert structure.members[1].type.namespaces == \
action.goal_request.structure.type.namespaces
assert structure.members[1].type.name == \
action.goal_request.structure.type.name

structure = action.goal_service.response_message.structure
assert len(structure.members) == 2

assert isinstance(structure.members[0].type, BasicType)
assert structure.members[0].type.type == 'boolean'
assert structure.members[0].name == 'accepted'

assert isinstance(structure.members[1].type, NamespacedType)
assert structure.members[1].type.namespaces == [
'builtin_interfaces', 'msg']
assert structure.members[1].type.name == 'Time'
assert structure.members[1].name == 'stamp'

# check derived result service
structure_type = action.result_service.structure_type
assert structure_type.namespaces == ['rosidl_parser', 'action']
assert structure_type.name == 'MyAction_Action_Result'

structure = action.result_service.request_message.structure
assert len(structure.members) == 1

assert isinstance(structure.members[0].type, Array)
assert structure.members[0].type.size == 16
assert isinstance(structure.members[0].type.basetype, BasicType)
assert structure.members[0].type.basetype.type == 'uint8'
assert structure.members[0].name == 'uuid'

structure = action.result_service.response_message.structure
assert len(structure.members) == 2

assert isinstance(structure.members[0].type, BasicType)
assert structure.members[0].type.type == 'int8'
assert structure.members[0].name == 'status'

assert isinstance(structure.members[1].type, NamespacedType)
assert structure.members[1].type.namespaces == \
action.result_response.structure.type.namespaces
assert structure.members[1].type.name == \
action.result_response.structure.type.name

# check derived feedback message
structure = action.feedback_message.structure
assert structure.type.namespaces == ['rosidl_parser', 'action']
assert structure.type.name == 'MyAction_Action_Feedback'

assert len(structure.members) == 2

assert isinstance(structure.members[0].type, Array)
assert structure.members[0].type.size == 16
assert isinstance(structure.members[0].type.basetype, BasicType)
assert structure.members[0].type.basetype.type == 'uint8'
assert structure.members[0].name == 'uuid'

assert isinstance(structure.members[1].type, NamespacedType)
assert structure.members[1].type.namespaces == \
action.feedback.structure.type.namespaces
assert structure.members[1].type.name == \
action.feedback.structure.type.name

0 comments on commit cf3d10f

Please sign in to comment.