diff --git a/rosidl_adapter/rosidl_adapter/parser.py b/rosidl_adapter/rosidl_adapter/parser.py index 4ecf11def..79c4dbebc 100644 --- a/rosidl_adapter/rosidl_adapter/parser.py +++ b/rosidl_adapter/rosidl_adapter/parser.py @@ -19,18 +19,25 @@ from typing import Final, Iterable, List, Optional, Tuple, TYPE_CHECKING, TypedDict, Union if TYPE_CHECKING: + from typing import Dict, Literal from typing_extensions import TypeAlias PrimitiveType: TypeAlias = Union[bool, float, int, str] + AnnotationType: TypeAlias = Literal['optional', 'key'] + AnnotantionsDict: TypeAlias = Dict[AnnotationType, bool] class Annotations(TypedDict, total=False): comment: List[str] unit: str optional: bool + key: bool PACKAGE_NAME_MESSAGE_TYPE_SEPARATOR: Final = '/' ANNOTATION_DELIMITER: Final = '@' -OPTIONAL_ANNOTATION: Final = ANNOTATION_DELIMITER + 'optional' +OPTIONAL_ANNOTATION_TOKEN: Final['AnnotationType'] = 'optional' +OPTIONAL_ANNOTATION: Final = ANNOTATION_DELIMITER + OPTIONAL_ANNOTATION_TOKEN +KEY_ANNOTATION_TOKEN: Final['AnnotationType'] = 'key' +KEY_ANNOTATION: Final = ANNOTATION_DELIMITER + KEY_ANNOTATION_TOKEN COMMENT_DELIMITER: Final = '#' CONSTANT_SEPARATOR: Final = '=' ARRAY_UPPER_BOUND_TOKEN: Final = '<=' @@ -112,7 +119,7 @@ class UnknownMessageType(InvalidSpecification): pass -class MultipleOptionalAnnotations(InvalidSpecification): +class RepeatedAnnotation(InvalidSpecification): pass @@ -492,7 +499,10 @@ def parse_message_string(pkg_name: str, msg_name: str, fields: List[Field] = [] constants: List[Constant] = [] last_element: Union[Field, Constant, None] = None # either a field or a constant - is_optional = False + with_annotation: 'AnnotantionsDict' = { + OPTIONAL_ANNOTATION_TOKEN: False, + KEY_ANNOTATION_TOKEN: False + } # replace tabs with spaces message_string = message_string.replace('\t', ' ') @@ -530,16 +540,21 @@ def parse_message_string(pkg_name: str, msg_name: str, if not line: continue - if line.startswith(OPTIONAL_ANNOTATION): - if is_optional: - raise MultipleOptionalAnnotations( - f'Already declared @optional. Error detected with {line}.') + # Loop to allow for arbitrary order + while line.startswith(ANNOTATION_DELIMITER): + for annotation, is_annotated in with_annotation.items(): + annotation_token = ANNOTATION_DELIMITER + annotation + if not line.startswith(annotation_token): + continue + if is_annotated: + raise RepeatedAnnotation( + f'Already declared {annotation_token}. Error detected with {line}.') - line = line[len(OPTIONAL_ANNOTATION):].lstrip() - is_optional = True + line = line[len(annotation_token):].lstrip() + with_annotation[annotation] = True - if not line: - continue + if not line: + continue type_string, _, rest = line.partition(' ') rest = rest.lstrip() @@ -574,9 +589,10 @@ def parse_message_string(pkg_name: str, msg_name: str, last_element = constants[-1] # add "unused" comments to the field / constant - if is_optional: - last_element.annotations['optional'] = is_optional - is_optional = False + for annotation, is_annotated in with_annotation.items(): + if is_annotated: + last_element.annotations[annotation] = is_annotated + with_annotation[annotation] = False comment_lines = last_element.annotations.setdefault( 'comment', []) comment_lines += current_comments diff --git a/rosidl_adapter/rosidl_adapter/resource/struct.idl.em b/rosidl_adapter/rosidl_adapter/resource/struct.idl.em index d91b2de8b..e1529421d 100644 --- a/rosidl_adapter/rosidl_adapter/resource/struct.idl.em +++ b/rosidl_adapter/rosidl_adapter/resource/struct.idl.em @@ -52,6 +52,9 @@ else: @[ end if]@ @[ if 'optional' in constant.annotations]@ @@optional +@[ end if]@ +@[ if 'key' in constant.annotations]@ + @@key @[ end if]@ const @(get_idl_type(constant.type)) @(constant.name) = @(to_idl_literal(get_idl_type(constant.type), constant.value)); @[ end for]@ @@ -87,6 +90,9 @@ else: @[ if 'optional' in field.annotations]@ @@optional @[ end if]@ +@[ if 'key' in field.annotations]@ + @@key +@[ end if]@ @[ if field.default_value is not None]@ @@default (value=@(to_idl_literal(get_idl_type(field.type), field.default_value))) @[ end if]@ diff --git a/rosidl_adapter/test/data/action/Test.action b/rosidl_adapter/test/data/action/Test.action index 702167bfa..b5cb29e97 100644 --- a/rosidl_adapter/test/data/action/Test.action +++ b/rosidl_adapter/test/data/action/Test.action @@ -26,6 +26,11 @@ bool ok @optional bool optional_bool +@key +bool key_bool +@optional +@key +bool optional_key_bool --- # feedback definition # more diff --git a/rosidl_adapter/test/data/action/Test.expected.idl b/rosidl_adapter/test/data/action/Test.expected.idl index 6a8249cec..33d24dd5f 100644 --- a/rosidl_adapter/test/data/action/Test.expected.idl +++ b/rosidl_adapter/test/data/action/Test.expected.idl @@ -53,6 +53,13 @@ module test_msgs { @optional boolean optional_bool; + + @key + boolean key_bool; + + @optional + @key + boolean optional_key_bool; }; @verbatim (language="comment", text= "feedback definition" "\n" diff --git a/rosidl_adapter/test/data/msg/Test.expected.idl b/rosidl_adapter/test/data/msg/Test.expected.idl index c9bbaf57e..4175b7e07 100644 --- a/rosidl_adapter/test/data/msg/Test.expected.idl +++ b/rosidl_adapter/test/data/msg/Test.expected.idl @@ -10,6 +10,16 @@ module test_msgs { const float INLINE_CONSTANT = 32.0; @optional const string OPTIONAL_STRING_CONST = "@optional"; + @key + const float INLINE_CONSTANT_KEY = 32.0; + @key + const string KEY_STRING_CONST = "@key"; + @optional + @key + const float INLINE_CONSTANT_OPTIONAL_KEY = 32.0; + @optional + @key + const string KEY_OPTIONAL_STRING_CONST = "@optional @key"; }; @verbatim (language="comment", text= "msg level doc") @@ -60,6 +70,38 @@ module test_msgs { @optional @default (value="@optional") string optional_default_string; + + @key + float multiline_key; + + @key + float inline_key; + + @key + @default (value=32.0) + float inline_default_key; + + @key + @default (value="@key") + string key_default_string; + + @optional + @key + float multiline_optional_key; + + @optional + @key + float inline_optional_key; + + @optional + @key + @default (value=32.0) + float inline_default_optional_key; + + @optional + @key + @default (value="@key @optional") + string optional_key_default_string; }; }; }; diff --git a/rosidl_adapter/test/data/msg/Test.msg b/rosidl_adapter/test/data/msg/Test.msg index 628fbecc6..b8a1342b8 100644 --- a/rosidl_adapter/test/data/msg/Test.msg +++ b/rosidl_adapter/test/data/msg/Test.msg @@ -25,3 +25,26 @@ float32 multiline_optional @optional string optional_default_string @optional @optional string OPTIONAL_STRING_CONST=@optional + +@key +float32 multiline_key + +@key float32 inline_key +@key float32 inline_default_key 32.0 +@key float32 INLINE_CONSTANT_KEY=32.0 + +@key string key_default_string @key +@key string KEY_STRING_CONST=@key + +@key +@optional +float32 multiline_optional_key + +@key @optional float32 inline_optional_key +@optional @key float32 inline_default_optional_key 32.0 +@key @optional float32 INLINE_CONSTANT_OPTIONAL_KEY=32.0 + +@optional +@key string optional_key_default_string @key @optional +@key +@optional string KEY_OPTIONAL_STRING_CONST=@optional @key diff --git a/rosidl_adapter/test/data/srv/Test.expected.idl b/rosidl_adapter/test/data/srv/Test.expected.idl index 02cadceac..d76de48a4 100644 --- a/rosidl_adapter/test/data/srv/Test.expected.idl +++ b/rosidl_adapter/test/data/srv/Test.expected.idl @@ -53,6 +53,13 @@ module test_msgs { @optional boolean optional_bool; + + @key + boolean key_bool; + + @optional + @key + uint8 optional_key_int; }; }; }; diff --git a/rosidl_adapter/test/data/srv/Test.srv b/rosidl_adapter/test/data/srv/Test.srv index 2a24f2a58..9224e29dc 100644 --- a/rosidl_adapter/test/data/srv/Test.srv +++ b/rosidl_adapter/test/data/srv/Test.srv @@ -24,3 +24,5 @@ string string_value bool ok @optional bool optional_bool +@key bool key_bool +@key @optional uint8 optional_key_int