Skip to content

Commit 4094371

Browse files
Add Optional Parsing (#883)
* Add optional parsing Signed-off-by: Michael Carlstrom <[email protected]> * Update rosidl_adapter/rosidl_adapter/parser.py Co-authored-by: William Woodall <[email protected]> Signed-off-by: Michael Carlstrom <[email protected]> * remove todo Signed-off-by: Michael Carlstrom <[email protected]> --------- Signed-off-by: Michael Carlstrom <[email protected]> Co-authored-by: William Woodall <[email protected]>
1 parent eedfa01 commit 4094371

File tree

10 files changed

+74
-4
lines changed

10 files changed

+74
-4
lines changed

rosidl_adapter/rosidl_adapter/parser.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
from typing import Final, Iterable, List, Optional, Tuple, TYPE_CHECKING, TypedDict, Union
2020

2121
PACKAGE_NAME_MESSAGE_TYPE_SEPARATOR: Final = '/'
22+
ANNOTATION_DELIMITER: Final = '@'
23+
OPTIONAL_ANNOTATION: Final = ANNOTATION_DELIMITER + 'optional'
2224
COMMENT_DELIMITER: Final = '#'
2325
CONSTANT_SEPARATOR: Final = '='
2426
ARRAY_UPPER_BOUND_TOKEN: Final = '<='
@@ -83,6 +85,7 @@
8385
class Annotations(TypedDict, total=False):
8486
comment: List[str]
8587
unit: str
88+
optional: bool
8689

8790

8891
class InvalidSpecification(Exception):
@@ -109,6 +112,10 @@ class UnknownMessageType(InvalidSpecification):
109112
pass
110113

111114

115+
class MultipleOptionalAnnotations(InvalidSpecification):
116+
pass
117+
118+
112119
class InvalidValue(Exception):
113120

114121
def __init__(self, type_: Union['Type', str], value_string: str,
@@ -408,7 +415,7 @@ def __init__(self, pkg_name: str, msg_name: str, fields: Iterable['Field'],
408415
self.msg_name = msg_name
409416
self.annotations: 'Annotations' = {}
410417

411-
self.fields = []
418+
self.fields: list[Field] = []
412419
for index, field in enumerate(fields):
413420
if not isinstance(field, Field):
414421
raise TypeError("field %u must be a 'Field' instance" % index)
@@ -422,7 +429,7 @@ def __init__(self, pkg_name: str, msg_name: str, fields: Iterable['Field'],
422429
'the fields iterable contains duplicate names: %s' %
423430
', '.join(sorted(duplicate_field_names)))
424431

425-
self.constants = []
432+
self.constants: list[Constant] = []
426433
for index, constant in enumerate(constants):
427434
if not isinstance(constant, Constant):
428435
raise TypeError("constant %u must be a 'Constant' instance" %
@@ -485,10 +492,11 @@ def parse_message_string(pkg_name: str, msg_name: str,
485492
fields: List[Field] = []
486493
constants: List[Constant] = []
487494
last_element: Union[Field, Constant, None] = None # either a field or a constant
495+
is_optional = False
488496
# replace tabs with spaces
489497
message_string = message_string.replace('\t', ' ')
490498

491-
current_comments = []
499+
current_comments: list[str] = []
492500
message_comments, lines = extract_file_level_comments(message_string)
493501
for line in lines:
494502
line = line.rstrip()
@@ -522,6 +530,18 @@ def parse_message_string(pkg_name: str, msg_name: str,
522530
if not line:
523531
continue
524532

533+
annotation_index = line.rfind(OPTIONAL_ANNOTATION)
534+
if annotation_index >= 0:
535+
if is_optional:
536+
raise MultipleOptionalAnnotations(
537+
f'Already declared @optional. Error detected with {line}.')
538+
539+
line = line[len(OPTIONAL_ANNOTATION):].lstrip()
540+
is_optional = True
541+
542+
if not line:
543+
continue
544+
525545
type_string, _, rest = line.partition(' ')
526546
rest = rest.lstrip()
527547
if not rest:
@@ -555,6 +575,9 @@ def parse_message_string(pkg_name: str, msg_name: str,
555575
last_element = constants[-1]
556576

557577
# add "unused" comments to the field / constant
578+
if is_optional:
579+
last_element.annotations['optional'] = is_optional
580+
is_optional = False
558581
comment_lines = last_element.annotations.setdefault(
559582
'comment', [])
560583
comment_lines += current_comments

rosidl_adapter/rosidl_adapter/resource/struct.idl.em

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ else:
4949
@[ end if]@
5050
@[ end for]@
5151
)
52+
@[ end if]@
53+
@[ if 'optional' in constant.annotations]@
54+
@@optional
5255
@[ end if]@
5356
const @(get_idl_type(constant.type)) @(constant.name) = @(to_idl_literal(get_idl_type(constant.type), constant.value));
5457
@[ end for]@
@@ -81,6 +84,9 @@ else:
8184
@[ end for]@
8285
)
8386
@[ end if]@
87+
@[ if 'optional' in field.annotations]@
88+
@@optional
89+
@[ end if]@
8490
@[ if field.default_value is not None]@
8591
@@default (value=@(to_idl_literal(get_idl_type(field.type), field.default_value)))
8692
@[ end if]@

rosidl_adapter/test/data/action/Test.action

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ string string_value
2323

2424
# ok docs
2525
bool ok
26+
27+
@optional
28+
bool optional_bool
2629
---
2730
# feedback definition
2831
# more

rosidl_adapter/test/data/action/Test.expected.idl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ module test_msgs {
5050
@verbatim (language="comment", text=
5151
"ok docs")
5252
boolean ok;
53+
54+
@optional
55+
boolean optional_bool;
5356
};
5457
@verbatim (language="comment", text=
5558
"feedback definition" "\n"

rosidl_adapter/test/data/msg/Test.expected.idl

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55

66
module test_msgs {
77
module msg {
8+
module Test_Constants {
9+
@optional
10+
const float INLINE_CONSTANT = 32.0;
11+
};
812
@verbatim (language="comment", text=
913
"msg level doc")
1014
struct Test {
@@ -40,6 +44,16 @@ module test_msgs {
4044
int64 int64_value;
4145

4246
uint64 uint64_value;
47+
48+
@optional
49+
float multiline_optional;
50+
51+
@optional
52+
float inline_optional;
53+
54+
@optional
55+
@default (value=32.0)
56+
float inline_default_optional;
4357
};
4458
};
4559
};

rosidl_adapter/test/data/msg/Test.msg

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,10 @@ int32 int32_value
1515
uint32 uint32_value
1616
int64 int64_value
1717
uint64 uint64_value
18+
19+
@optional
20+
float32 multiline_optional
21+
22+
@optional float32 inline_optional
23+
@optional float32 inline_default_optional 32.0
24+
@optional float32 INLINE_CONSTANT=32.0

rosidl_adapter/test/data/srv/Test.expected.idl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ module test_msgs {
5050
@verbatim (language="comment", text=
5151
"8")
5252
boolean ok;
53+
54+
@optional
55+
boolean optional_bool;
5356
};
5457
};
5558
};

rosidl_adapter/test/data/srv/Test.srv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,4 @@ string string_value
2323
# 8
2424

2525
bool ok
26+
@optional bool optional_bool

rosidl_parser/test/msg/MyMessage.idl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ module rosidl_parser {
8181
float fixed_frac_only;
8282
@default ( value=7d )
8383
float fixed_int_only;
84+
85+
// Optional test
86+
@optional
87+
int32 optional_int;
8488
};
8589
};
8690
};

rosidl_parser/test/test_parser.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ def test_message_parser_structure(message_idl_file: IdlFile) -> None:
134134
structure = messages[0].structure
135135
assert structure.namespaced_type.namespaces == ['rosidl_parser', 'msg']
136136
assert structure.namespaced_type.name == 'MyMessage'
137-
assert len(structure.members) == 45
137+
assert len(structure.members) == 46
138138

139139
assert isinstance(structure.members[0].type, BasicType)
140140
assert structure.members[0].type.typename == 'int16'
@@ -304,6 +304,12 @@ def test_message_parser_annotations(message_idl_file: IdlFile) -> None:
304304
assert len(structure.members[44].annotations) == 1
305305
assert structure.members[44].annotations[0].name == 'default'
306306

307+
assert isinstance(structure.members[45].type, BasicType)
308+
assert structure.members[45].type.typename == 'int32'
309+
assert structure.members[45].name == 'optional_int'
310+
assert len(structure.members[45].annotations) == 1
311+
assert structure.members[45].annotations[0].name == 'optional'
312+
307313

308314
@pytest.fixture(scope='module')
309315
def service_idl_file() -> IdlFile:

0 commit comments

Comments
 (0)