diff --git a/rosidl_parser/rosidl_parser/definition.py b/rosidl_parser/rosidl_parser/definition.py index 5873ba470..39170af21 100644 --- a/rosidl_parser/rosidl_parser/definition.py +++ b/rosidl_parser/rosidl_parser/definition.py @@ -533,6 +533,40 @@ def has_annotations(self, name: str) -> bool: return bool(annotations) +class Enumerator(Annotatable): + """An enumerator in an enum.""" + + __slots__ = ('name') + + def __init__(self, name: str): + """ + Create an Enumerator. + + :param name: the name of the enumerator + """ + super().__init__() + self.name = name + + +class Enum(Annotatable): + """An enum type containing a list of enumerators.""" + + __slots__ = ('namespaced_type', 'enumerators') + + def __init__(self, namespaced_type: NamespacedType, + enumerators: Optional[List['Enumerator']] = None) -> None: + """ + Create an Enum. + + :param namespaced_type: the namespaced type identifying the enum + :param list enumerators: the enumerators of the enum + """ + super().__init__() + assert isinstance(namespaced_type, NamespacedType) + self.namespaced_type = namespaced_type + self.enumerators = enumerators or [] + + class Member(Annotatable): """A member of a structure.""" @@ -819,7 +853,7 @@ def get_absolute_path(self) -> pathlib.Path: return self.basepath / self.relative_path -IdlContentElement: 'TypeAlias' = Union[Include, Message, Service, Action] +IdlContentElement: 'TypeAlias' = Union[Include, Message, Service, Action, Enum] IdlContentElementT = TypeVar('IdlContentElementT', bound=IdlContentElement) diff --git a/rosidl_parser/rosidl_parser/parser.py b/rosidl_parser/rosidl_parser/parser.py index 399ceee5d..3b5c6313f 100644 --- a/rosidl_parser/rosidl_parser/parser.py +++ b/rosidl_parser/rosidl_parser/parser.py @@ -47,6 +47,8 @@ from rosidl_parser.definition import BoundedWString from rosidl_parser.definition import Constant from rosidl_parser.definition import CONSTANT_MODULE_SUFFIX +from rosidl_parser.definition import Enum +from rosidl_parser.definition import Enumerator from rosidl_parser.definition import IdlContent from rosidl_parser.definition import IdlFile from rosidl_parser.definition import IdlLocator @@ -146,6 +148,33 @@ def extract_content_from_ast(tree: 'ParseTree') -> IdlContent: module_comments.append(constant) typedefs: Dict[Any, Union[Array, AbstractTypeAlias]] = {} + enum_dcls = tree.find_data('enum_dcl') + for enum_dcl in enum_dcls: + annotations = get_annotations(enum_dcl) + module_identifiers = get_module_identifier_values(tree, enum_dcl) + enum_name = get_child_identifier_value(enum_dcl) + + enumerators = [] + enumerator_nodes = enum_dcl.find_data('enumerator') + for enumerator_node in enumerator_nodes: + enumerator_name = get_child_identifier_value(enumerator_node) + enumerator_annotations = get_annotations(enumerator_node) + + enumerator = Enumerator(enumerator_name) + enumerator.annotations = enumerator_annotations + enumerators.append(enumerator) + + enum_obj = Enum( + NamespacedType( + namespaces=module_identifiers, + name=enum_name + ), + enumerators=enumerators + ) + enum_obj.annotations = annotations + content.elements.append(enum_obj) + typedefs[enum_name] = enum_obj + typedef_dcls = tree.find_data('typedef_dcl') for typedef_dcl in typedef_dcls: assert len(typedef_dcl.children) == 1 diff --git a/rosidl_parser/test/msg/MyMessage.idl b/rosidl_parser/test/msg/MyMessage.idl index 0348c546c..43efb0350 100644 --- a/rosidl_parser/test/msg/MyMessage.idl +++ b/rosidl_parser/test/msg/MyMessage.idl @@ -13,6 +13,13 @@ module rosidl_parser { const string EMPTY_STRING_CONSTANT = ""; }; + @verbatim ( language="comment", text="Documentation of MyEnum." "Define an enum." ) + enum MyEnum { + FIRST_ENUM, + SECOND_ENUM, + THIRD_ENUM + }; + @verbatim ( language="comment", text="Documentation of MyMessage." "Adjacent string literal." ) @transfer_mode(SHMEM_REF) struct MyMessage { @@ -85,6 +92,9 @@ module rosidl_parser { // Optional test @optional int32 optional_int; + + // Enum field usage + MyEnum my_enum; }; }; }; diff --git a/rosidl_parser/test/test_parser.py b/rosidl_parser/test/test_parser.py index e7a66f70c..40326f102 100644 --- a/rosidl_parser/test/test_parser.py +++ b/rosidl_parser/test/test_parser.py @@ -21,6 +21,7 @@ from rosidl_parser.definition import BoundedSequence from rosidl_parser.definition import BoundedString from rosidl_parser.definition import BoundedWString +from rosidl_parser.definition import Enum from rosidl_parser.definition import IdlFile from rosidl_parser.definition import IdlLocator from rosidl_parser.definition import Include @@ -92,6 +93,21 @@ def test_message_parser_includes(message_idl_file: IdlFile) -> None: assert includes[1].locator == 'pkgname/msg/OtherMessage.idl' +def test_message_parser_enums(message_idl_file: IdlFile) -> None: + enums = message_idl_file.content.get_elements_of_type(Enum) + assert len(enums) == 1 + assert enums[0].namespaced_type.name == 'MyEnum' + assert len(enums[0].annotations) == 1 + assert enums[0].annotations[0].name == 'verbatim' + assert enums[0].annotations[0].value['language'] == 'comment' + text = enums[0].annotations[0].value['text'] + assert text == 'Documentation of MyEnum.Define an enum.' + assert len(enums[0].enumerators) == 3 + assert enums[0].enumerators[0].name == 'FIRST_ENUM' + assert enums[0].enumerators[1].name == 'SECOND_ENUM' + assert enums[0].enumerators[2].name == 'THIRD_ENUM' + + def test_message_parser_structure(message_idl_file: IdlFile) -> None: messages = message_idl_file.content.get_elements_of_type(Message) assert len(messages) == 1 @@ -134,7 +150,7 @@ def test_message_parser_structure(message_idl_file: IdlFile) -> None: structure = messages[0].structure assert structure.namespaced_type.namespaces == ['rosidl_parser', 'msg'] assert structure.namespaced_type.name == 'MyMessage' - assert len(structure.members) == 46 + assert len(structure.members) == 47 assert isinstance(structure.members[0].type, BasicType) assert structure.members[0].type.typename == 'int16' @@ -310,6 +326,14 @@ def test_message_parser_annotations(message_idl_file: IdlFile) -> None: assert len(structure.members[45].annotations) == 1 assert structure.members[45].annotations[0].name == 'optional' + assert isinstance(structure.members[46].type, Enum) + assert structure.members[46].name == 'my_enum' + assert structure.members[46].type.namespaced_type.name == 'MyEnum' + assert len(structure.members[46].type.enumerators) == 3 + assert structure.members[46].type.enumerators[0].name == 'FIRST_ENUM' + assert structure.members[46].type.enumerators[1].name == 'SECOND_ENUM' + assert structure.members[46].type.enumerators[2].name == 'THIRD_ENUM' + @pytest.fixture(scope='module') def service_idl_file() -> IdlFile: