From 9bf3c4ad2de1a1bb2168429c6e84c42ed18728e9 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Wed, 20 Mar 2024 17:22:06 -0400 Subject: [PATCH 01/20] Add types to defintions Signed-off-by: Michael Carlstrom --- rosidl_parser/rosidl_parser/definition.py | 191 +++++++++++++++------- 1 file changed, 129 insertions(+), 62 deletions(-) diff --git a/rosidl_parser/rosidl_parser/definition.py b/rosidl_parser/rosidl_parser/definition.py index 689b71384..94b7aa4a1 100644 --- a/rosidl_parser/rosidl_parser/definition.py +++ b/rosidl_parser/rosidl_parser/definition.py @@ -13,8 +13,16 @@ # limitations under the License. import pathlib +from typing import Dict from typing import Iterable +from typing import List +from typing import Literal +from typing import Optional +from typing import Set from typing import Tuple +from typing import Type +from typing import TYPE_CHECKING +from typing import Union # Basic types as defined by the IDL specification @@ -85,6 +93,40 @@ OCTET_TYPE, ) +if TYPE_CHECKING: + SignedNonexplicitIntegerTypeValues = Union[Literal['short'], Literal['long'], + Literal['long long']] + UnsignedNonexplicitIntegerTypeValues = Union[Literal['unsigned short'], + Literal['unsigned long'], + Literal['unsigned long long']] + + NonexplicitIntegerTypeValues = Union[SignedNonexplicitIntegerTypeValues, + UnsignedNonexplicitIntegerTypeValues] + + FloatingPointTypeValues = Union[Literal['float'], Literal['double'], + Literal['long double']] + CharacterTypeValues = Union[Literal['char'], Literal['wchar']] + BooleanValue = Literal['boolean'] + OctetValue = Literal['octet'] + + SignedExplicitIntegerTypeValues = Union[Literal['int8'], Literal['int16'], + Literal['int32'], Literal['int64']] + UnsignedExplicitIntegerTypeValues = Union[Literal['uint8'], Literal['uint16'], + Literal['uint32'], Literal['uint64']] + + ExplicitIntegerTypeValues = Union[SignedExplicitIntegerTypeValues, + UnsignedExplicitIntegerTypeValues] + + SignedIntegerTypeValues = Union[SignedNonexplicitIntegerTypeValues, + SignedExplicitIntegerTypeValues] + UnsignedIntegerTypeValues = Union[UnsignedNonexplicitIntegerTypeValues, + UnsignedExplicitIntegerTypeValues] + IntegerTypeValues = Union[SignedIntegerTypeValues, UnsignedIntegerTypeValues] + + BasicTypeValues = Union[IntegerTypeValues, FloatingPointTypeValues, + CharacterTypeValues, BooleanValue, + OctetValue] + EMPTY_STRUCTURE_REQUIRED_MEMBER_NAME = 'structure_needs_at_least_one_member' CONSTANT_MODULE_SUFFIX = '_Constants' @@ -107,8 +149,8 @@ class AbstractType: __slots__ = () - def __eq__(self, other): - return type(self) == type(other) + def __eq__(self, other: object) -> bool: + return isinstance(other, type(self)) class AbstractNestableType(AbstractType): @@ -137,7 +179,7 @@ class BasicType(AbstractNestableType): __slots__ = ('typename', ) - def __init__(self, typename: str): + def __init__(self, typename: 'BasicTypeValues') -> None: """ Create a BasicType. @@ -147,7 +189,9 @@ def __init__(self, typename: str): assert typename in BASIC_TYPES self.typename = typename - def __eq__(self, other): + def __eq__(self, other: object) -> bool: + if not isinstance(other, BasicType): + return False return super().__eq__(other) and self.typename == other.typename @@ -156,7 +200,7 @@ class NamedType(AbstractNestableType): __slots__ = ('name') - def __init__(self, name: str): + def __init__(self, name: str) -> None: """ Create a NamedType. @@ -165,7 +209,9 @@ def __init__(self, name: str): super().__init__() self.name = name - def __eq__(self, other): + def __eq__(self, other: object) -> bool: + if not isinstance(other, NamedType): + return False return super().__eq__(other) and self.name == other.name @@ -174,7 +220,7 @@ class NamespacedType(AbstractNestableType): __slots__ = ('namespaces', 'name') - def __init__(self, namespaces: Iterable[str], name: str): + def __init__(self, namespaces: Iterable[str], name: str) -> None: """ Create a NamespacedType. @@ -189,7 +235,9 @@ def __init__(self, namespaces: Iterable[str], name: str): def namespaced_name(self) -> Tuple[str, ...]: return (*self.namespaces, self.name) - def __eq__(self, other): + def __eq__(self, other: object) -> bool: + if not isinstance(other, NamespacedType): + return False return super().__eq__(other) and \ self.namespaces == other.namespaces and self.name == other.name @@ -199,7 +247,7 @@ class AbstractGenericString(AbstractNestableType): __slots__ = () - def has_maximum_size(self): + def has_maximum_size(self) -> bool: raise NotImplementedError('Only implemented in subclasses') @@ -214,7 +262,7 @@ class BoundedString(AbstractString): __slots__ = ('maximum_size', ) - def __init__(self, maximum_size: int): + def __init__(self, maximum_size: int) -> None: """ Create a BoundedString. @@ -224,10 +272,12 @@ def __init__(self, maximum_size: int): assert maximum_size >= 0 self.maximum_size = maximum_size - def has_maximum_size(self): + def has_maximum_size(self) -> 'Literal[True]': return True - def __eq__(self, other): + def __eq__(self, other: object) -> bool: + if not isinstance(other, BoundedString): + return False return super().__eq__(other) and \ self.maximum_size == other.maximum_size @@ -237,7 +287,7 @@ class UnboundedString(AbstractString): __slots__ = () - def has_maximum_size(self): + def has_maximum_size(self) -> 'Literal[False]': return False @@ -252,7 +302,7 @@ class BoundedWString(AbstractWString): __slots__ = ('maximum_size', ) - def __init__(self, maximum_size: int): + def __init__(self, maximum_size: int) -> None: """ Create a BoundedWString. @@ -265,10 +315,12 @@ def __init__(self, maximum_size: int): # assert maximum_size > 0 self.maximum_size = maximum_size - def has_maximum_size(self): + def has_maximum_size(self) -> 'Literal[True]': return True - def __eq__(self, other): + def __eq__(self, other: object) -> bool: + if not isinstance(other, BoundedWString): + return False return super().__eq__(other) and \ self.maximum_size == other.maximum_size @@ -278,7 +330,7 @@ class UnboundedWString(AbstractWString): __slots__ = () - def has_maximum_size(self): + def has_maximum_size(self) -> 'Literal[False]': return False @@ -294,7 +346,7 @@ class AbstractNestedType(AbstractType): __slots__ = ('value_type', ) - def __init__(self, value_type: AbstractNestableType): + def __init__(self, value_type: AbstractNestableType) -> None: """ Create an AbstractNestedType. @@ -304,10 +356,12 @@ def __init__(self, value_type: AbstractNestableType): assert isinstance(value_type, AbstractNestableType) self.value_type = value_type - def has_maximum_size(self): + def has_maximum_size(self) -> bool: raise NotImplementedError('Only implemented in subclasses') - def __eq__(self, other): + def __eq__(self, other: object) -> bool: + if not isinstance(other, AbstractNestedType): + return False return super().__eq__(other) and self.value_type == other.value_type @@ -316,7 +370,7 @@ class Array(AbstractNestedType): __slots__ = ('size') - def __init__(self, value_type: AbstractNestableType, size: int): + def __init__(self, value_type: AbstractNestableType, size: int) -> None: """ Create an Array. @@ -328,19 +382,21 @@ def __init__(self, value_type: AbstractNestableType, size: int): assert size > 0 self.size = size - def has_maximum_size(self): + def has_maximum_size(self) -> 'Literal[True]': return True - def __eq__(self, other): + def __eq__(self, other: object) -> bool: + if not isinstance(other, Array): + return False return super().__eq__(other) and self.size == other.size class AbstractSequence(AbstractNestedType): """The abstract base class of sequence types.""" - __slots__ = set() + __slots__: Set[str] = set() - def __init__(self, value_type: AbstractNestableType): + def __init__(self, value_type: AbstractNestableType) -> None: super().__init__(value_type) @@ -349,7 +405,7 @@ class BoundedSequence(AbstractSequence): __slots__ = ('maximum_size', ) - def __init__(self, value_type: AbstractNestableType, maximum_size: int): + def __init__(self, value_type: AbstractNestableType, maximum_size: int) -> None: """ Create a BoundedSequence. @@ -360,10 +416,12 @@ def __init__(self, value_type: AbstractNestableType, maximum_size: int): assert maximum_size > 0 self.maximum_size = maximum_size - def has_maximum_size(self): + def has_maximum_size(self) -> 'Literal[True]': return True - def __eq__(self, other): + def __eq__(self, other: object) -> bool: + if not isinstance(other, BoundedSequence): + return False return super().__eq__(other) and \ self.maximum_size == other.maximum_size @@ -373,7 +431,7 @@ class UnboundedSequence(AbstractSequence): __slots__ = () - def __init__(self, value_type: AbstractNestableType): + def __init__(self, value_type: AbstractNestableType) -> None: """ Create an UnboundedSequence. @@ -381,16 +439,19 @@ def __init__(self, value_type: AbstractNestableType): """ super().__init__(value_type) - def has_maximum_size(self): + def has_maximum_size(self) -> 'Literal[False]': return False +ValueType = Union[str, int, float, bool, Dict[str, Union[str, int, float, bool]], None] + + class Annotation: """An annotation identified by a name with an arbitrary value.""" __slots__ = ('name', 'value') - def __init__(self, name: str, value): + def __init__(self, name: str, value: ValueType) -> None: """ Create an Annotation. @@ -409,14 +470,14 @@ class Annotatable: __slots__ = ('annotations', ) - def __init__(self): - self.annotations = [] + def __init__(self) -> None: + self.annotations: List[Annotation] = [] - def get_annotation_value(self, name): + def get_annotation_value(self, name: str) -> ValueType: """ Get the unique value of an annotation of a specific type. - :param str name: the name of the annotation type + :param name: the name of the annotation type :returns: the annotation value :raises: ValueError if there is no or multiple annotations with the given name @@ -428,16 +489,16 @@ def get_annotation_value(self, name): raise ValueError(f"Multiple '{name}' annotations") return values[0] - def get_annotation_values(self, name): + def get_annotation_values(self, name: str) -> List[ValueType]: """ Get the values of annotations of a specific type. - :param str name: the name of the annotation type + :param name: the name of the annotation type :returns: a list of annotation values """ return [a.value for a in self.annotations if a.name == name] - def get_comment_lines(self): + def get_comment_lines(self) -> List[str]: """ Get the comment lines of the annotatable. @@ -445,28 +506,29 @@ def get_comment_lines(self): """ comments = [ x['text'] for x in self.get_annotation_values('verbatim') if + isinstance(x, Dict) and 'language' in x and 'text' in x and x['language'] == 'comment' ] - lines = [] + lines: List[str] = [] for comment in comments: - lines.extend(comment.splitlines()) + lines.extend(str(comment).splitlines()) return lines - def has_annotation(self, name): + def has_annotation(self, name: str) -> bool: """ Check if there is exactly one annotation of a specific type. - :param str name: the name of the annotation type + :param name: the name of the annotation type :returns: True if there is exactly one annotation, False otherwise """ values = self.get_annotation_values(name) return len(values) == 1 - def has_annotations(self, name): + def has_annotations(self, name: str) -> bool: """ Check if there are any annotations of a specific type. - :param str name: the name of the annotation type + :param name: the name of the annotation type :returns: True if there are any annotations, False otherwise """ annotations = self.get_annotation_values(name) @@ -496,7 +558,8 @@ class Structure(Annotatable): __slots__ = ('namespaced_type', 'members') - def __init__(self, namespaced_type: NamespacedType, members=None): + def __init__(self, namespaced_type: NamespacedType, + members: Optional[List['Member']] = None) -> None: """ Create a Structure. @@ -514,11 +577,11 @@ class Include: __slots__ = ('locator', ) - def __init__(self, locator): + def __init__(self, locator: str) -> None: """ Create an Include. - :param str locator: a URI identifying the included file + :param locator: a URI identifying the included file """ self.locator = locator @@ -528,7 +591,8 @@ class Constant(Annotatable): __slots__ = ('name', 'type', 'value') - def __init__(self, name: str, type_: AbstractType, value): + def __init__(self, name: str, type_: AbstractType, + value: Union[str, int, float, bool]) -> None: """ Create a Constant. @@ -548,7 +612,7 @@ class Message: __slots__ = ('structure', 'constants') - def __init__(self, structure: Structure): + def __init__(self, structure: Structure) -> None: """ Create a Message. @@ -557,7 +621,7 @@ def __init__(self, structure: Structure): super().__init__() assert isinstance(structure, Structure) self.structure = structure - self.constants = [] + self.constants: List[Constant] = [] class Service: @@ -568,7 +632,7 @@ class Service: def __init__( self, namespaced_type: NamespacedType, request: Message, response: Message - ): + ) -> None: """ Create a Service. @@ -622,7 +686,7 @@ class Action: def __init__( self, namespaced_type: NamespacedType, goal: Message, result: Message, feedback: Message - ): + ) -> None: """ Create an Action. @@ -732,18 +796,18 @@ class IdlLocator: __slots__ = ('basepath', 'relative_path') - def __init__(self, basepath, relative_path): + def __init__(self, basepath: str, relative_path: str) -> None: """ Create an IdlLocator. - :param str basepath: the basepath of file - :param str relative_path: the path relative to the basepath of the file + :param basepath: the basepath of file + :param relative_path: the path relative to the basepath of the file """ super().__init__() self.basepath = pathlib.Path(basepath) self.relative_path = pathlib.Path(relative_path) - def get_absolute_path(self): + def get_absolute_path(self) -> pathlib.Path: return self.basepath / self.relative_path @@ -752,11 +816,14 @@ class IdlContent: __slots__ = ('elements', ) - def __init__(self): + def __init__(self) -> None: super().__init__() - self.elements = [] + self.elements: List[Union[Include, Message, Service, Action]] = [] - def get_elements_of_type(self, type_): + def get_elements_of_type( + self, + type_: Type[Union[Include, Message, Service, Action]] + ) -> List[Union[Include, Message, Service, Action]]: return [e for e in self.elements if isinstance(e, type_)] @@ -765,12 +832,12 @@ class IdlFile: __slots__ = ('locator', 'content') - def __init__(self, locator, content): + def __init__(self, locator: IdlLocator, content: IdlContent) -> None: """ Create an IdlFile. - :param IdlLocator locator: the locator of the IDL file - :param IdlContent content: the content of the IDL file + :param locator: the locator of the IDL file + :param content: the content of the IDL file """ super().__init__() assert isinstance(locator, IdlLocator) From 4eecaccdaa56746546bab7248d76898fd5722a07 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Wed, 20 Mar 2024 17:29:48 -0400 Subject: [PATCH 02/20] Move Literal import Signed-off-by: Michael Carlstrom --- rosidl_parser/rosidl_parser/definition.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rosidl_parser/rosidl_parser/definition.py b/rosidl_parser/rosidl_parser/definition.py index 94b7aa4a1..5bf49c414 100644 --- a/rosidl_parser/rosidl_parser/definition.py +++ b/rosidl_parser/rosidl_parser/definition.py @@ -16,7 +16,6 @@ from typing import Dict from typing import Iterable from typing import List -from typing import Literal from typing import Optional from typing import Set from typing import Tuple @@ -94,6 +93,7 @@ ) if TYPE_CHECKING: + from typing import Literal SignedNonexplicitIntegerTypeValues = Union[Literal['short'], Literal['long'], Literal['long long']] UnsignedNonexplicitIntegerTypeValues = Union[Literal['unsigned short'], From 0df8f0fc00d449664c088da8a6d96902cbc20cfd Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Wed, 20 Mar 2024 17:48:44 -0400 Subject: [PATCH 03/20] Cleaner Literals Signed-off-by: Michael Carlstrom --- rosidl_parser/rosidl_parser/definition.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/rosidl_parser/rosidl_parser/definition.py b/rosidl_parser/rosidl_parser/definition.py index 5bf49c414..1894a7165 100644 --- a/rosidl_parser/rosidl_parser/definition.py +++ b/rosidl_parser/rosidl_parser/definition.py @@ -94,25 +94,20 @@ if TYPE_CHECKING: from typing import Literal - SignedNonexplicitIntegerTypeValues = Union[Literal['short'], Literal['long'], - Literal['long long']] - UnsignedNonexplicitIntegerTypeValues = Union[Literal['unsigned short'], - Literal['unsigned long'], - Literal['unsigned long long']] + SignedNonexplicitIntegerTypeValues = Literal['short', 'long', 'long long'] + UnsignedNonexplicitIntegerTypeValues = Literal['unsigned short', 'unsigned long', + 'unsigned long long'] NonexplicitIntegerTypeValues = Union[SignedNonexplicitIntegerTypeValues, UnsignedNonexplicitIntegerTypeValues] - FloatingPointTypeValues = Union[Literal['float'], Literal['double'], - Literal['long double']] - CharacterTypeValues = Union[Literal['char'], Literal['wchar']] + FloatingPointTypeValues = Literal['float', 'double', 'long double'] + CharacterTypeValues = Literal['char', 'wchar'] BooleanValue = Literal['boolean'] OctetValue = Literal['octet'] - SignedExplicitIntegerTypeValues = Union[Literal['int8'], Literal['int16'], - Literal['int32'], Literal['int64']] - UnsignedExplicitIntegerTypeValues = Union[Literal['uint8'], Literal['uint16'], - Literal['uint32'], Literal['uint64']] + SignedExplicitIntegerTypeValues = Literal['int8', 'int16', 'int32' 'int64'] + UnsignedExplicitIntegerTypeValues = Literal['uint8', 'uint16', 'uint32', 'uint64'] ExplicitIntegerTypeValues = Union[SignedExplicitIntegerTypeValues, UnsignedExplicitIntegerTypeValues] From e818297c4bd41386dbeca8572598e3eb8aab42e4 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Wed, 20 Mar 2024 20:13:20 -0400 Subject: [PATCH 04/20] Add py.typed Signed-off-by: Michael Carlstrom --- rosidl_parser/rosidl_parser/py.typed | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 rosidl_parser/rosidl_parser/py.typed diff --git a/rosidl_parser/rosidl_parser/py.typed b/rosidl_parser/rosidl_parser/py.typed new file mode 100644 index 000000000..e69de29bb From 41dbe426eaf412ee904ef86d048eb6714dfa3d60 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sun, 6 Oct 2024 20:28:11 -0400 Subject: [PATCH 05/20] init Signed-off-by: Michael Carlstrom --- rosidl_parser/rosidl_parser/definition.py | 6 +- rosidl_parser/rosidl_parser/parser.py | 153 +++++++++++++++------- rosidl_parser/test/test_parser.py | 27 ++-- 3 files changed, 120 insertions(+), 66 deletions(-) diff --git a/rosidl_parser/rosidl_parser/definition.py b/rosidl_parser/rosidl_parser/definition.py index 26587b9b3..8664ead92 100644 --- a/rosidl_parser/rosidl_parser/definition.py +++ b/rosidl_parser/rosidl_parser/definition.py @@ -108,7 +108,7 @@ BooleanValue = Literal['boolean'] OctetValue = Literal['octet'] - SignedExplicitIntegerTypeValues = Literal['int8', 'int16', 'int32' 'int64'] + SignedExplicitIntegerTypeValues = Literal['int8', 'int16', 'int32', 'int64'] UnsignedExplicitIntegerTypeValues = Literal['uint8', 'uint16', 'uint32', 'uint64'] ExplicitIntegerTypeValues = Union[SignedExplicitIntegerTypeValues, @@ -299,7 +299,7 @@ class BoundedWString(AbstractWString): __slots__ = ('maximum_size', ) - def __init__(self, maximum_size: int) -> None: + def __init__(self, maximum_size: Union[int, str]) -> None: """ Create a BoundedWString. @@ -803,7 +803,7 @@ class IdlLocator: __slots__ = ('basepath', 'relative_path') - def __init__(self, basepath: str, relative_path: str) -> None: + def __init__(self, basepath: pathlib.Path, relative_path: pathlib.Path) -> None: """ Create an IdlLocator. diff --git a/rosidl_parser/rosidl_parser/parser.py b/rosidl_parser/rosidl_parser/parser.py index 8aceb02df..0ae827f6d 100644 --- a/rosidl_parser/rosidl_parser/parser.py +++ b/rosidl_parser/rosidl_parser/parser.py @@ -16,13 +16,24 @@ import os import re import sys +from typing import Any +from typing import Callable +from typing import Dict +from typing import List +from typing import Literal +from typing import Union +from typing import Optional +from typing import TYPE_CHECKING from lark import Lark from lark.lexer import Token +from lark.tree import Branch from lark.tree import pydot__tree_to_png from lark.tree import Tree +from lark.tree import ParseTree from rosidl_parser.definition import AbstractNestedType +from rosidl_parser.definition import AbstractNestableType from rosidl_parser.definition import AbstractType from rosidl_parser.definition import Action from rosidl_parser.definition import ACTION_FEEDBACK_SUFFIX @@ -38,6 +49,7 @@ from rosidl_parser.definition import CONSTANT_MODULE_SUFFIX from rosidl_parser.definition import IdlContent from rosidl_parser.definition import IdlFile +from rosidl_parser.definition import IdlLocator from rosidl_parser.definition import Include from rosidl_parser.definition import Member from rosidl_parser.definition import Message @@ -50,15 +62,22 @@ from rosidl_parser.definition import UnboundedSequence from rosidl_parser.definition import UnboundedString from rosidl_parser.definition import UnboundedWString +from rosidl_parser.definition import ValueType + +if TYPE_CHECKING: + from rosidl_parser.definition import BasicTypeValues + + +AbstractTypeAlias = Union[AbstractNestableType, BoundedSequence, BoundedWString] grammar_file = os.path.join(os.path.dirname(__file__), 'grammar.lark') with open(grammar_file, mode='r', encoding='utf-8') as h: grammar = h.read() -_parser = None +_parser: Optional[Lark] = None -def parse_idl_file(locator, png_file=None): +def parse_idl_file(locator: IdlLocator, png_file: Optional[str] = None) -> IdlFile: string = locator.get_absolute_path().read_text(encoding='utf-8') try: content = parse_idl_string(string, png_file=png_file) @@ -68,7 +87,7 @@ def parse_idl_file(locator, png_file=None): return IdlFile(locator, content) -def parse_idl_string(idl_string, png_file=None): +def parse_idl_string(idl_string: str, png_file: Optional[str] = None) -> IdlContent: tree = get_ast_from_idl_string(idl_string) content = extract_content_from_ast(tree) @@ -82,25 +101,26 @@ def parse_idl_string(idl_string, png_file=None): return content -def get_ast_from_idl_string(idl_string): +def get_ast_from_idl_string(idl_string: str) -> ParseTree: global _parser if _parser is None: _parser = Lark(grammar, start='specification', maybe_placeholders=False) return _parser.parse(idl_string) -def extract_content_from_ast(tree): +def extract_content_from_ast(tree: ParseTree) -> IdlContent: content = IdlContent() include_directives = tree.find_data('include_directive') for include_directive in include_directives: assert len(include_directive.children) == 1 child = include_directive.children[0] + assert isinstance(child, Tree) assert child.data in ('h_char_sequence', 'q_char_sequence') include_token = next(child.scan_values(_find_tokens(None))) content.elements.append(Include(include_token.value)) - constants = {} + constants: Dict[str, List[Constant]] = {} const_dcls = tree.find_data('const_dcl') for const_dcl in const_dcls: annotations = get_annotations(const_dcl) @@ -116,18 +136,21 @@ def extract_content_from_ast(tree): constant.annotations = annotations module_comments.append(constant) - typedefs = {} + typedefs: Dict[Any, Union[Array, AbstractTypeAlias]] = {} typedef_dcls = tree.find_data('typedef_dcl') for typedef_dcl in typedef_dcls: assert len(typedef_dcl.children) == 1 child = typedef_dcl.children[0] + assert isinstance(child, Tree) assert 'type_declarator' == child.data assert len(child.children) == 2 - abstract_type = get_abstract_type(child.children[0]) + abstract_type: Union[Array, AbstractTypeAlias] = get_abstract_type(child.children[0]) child = child.children[1] + assert isinstance(child, Tree) assert 'any_declarators' == child.data assert len(child.children) == 1, 'Only support single typedefs atm' child = child.children[0] + assert isinstance(child, Tree) identifier = get_first_identifier_value(child) abstract_type = get_abstract_type_optionally_as_array( abstract_type, child) @@ -259,7 +282,7 @@ def extract_content_from_ast(tree): return content -def resolve_typedefed_names(structure, typedefs): +def resolve_typedefed_names(structure: Structure, typedefs: Dict[Any, Union[Array, AbstractTypeAlias]]) -> None: for member in structure.members: type_ = member.type if isinstance(type_, AbstractNestedType): @@ -283,13 +306,13 @@ def resolve_typedefed_names(structure, typedefs): member.type = typedefed_type -def get_first_identifier_value(tree): +def get_first_identifier_value(tree: ParseTree) -> Any: """Get the value of the first identifier token for a node.""" identifier_token = next(tree.scan_values(_find_tokens('IDENTIFIER'))) return identifier_token.value -def get_child_identifier_value(tree): +def get_child_identifier_value(tree: ParseTree) -> Any: """Get the value of the first child identifier token for a node.""" for c in tree.children: if not isinstance(c, Token): @@ -299,23 +322,24 @@ def get_child_identifier_value(tree): return None -def _find_tokens(token_type): - def find(t): +def _find_tokens(token_type: Optional[Literal['IDENTIFIER']]) -> Callable[[Branch[Token]], bool]: + def find(t: Branch[Token]) -> bool: if isinstance(t, Token): if token_type is None or t.type == token_type: - return t + return True + return False return find -def get_module_identifier_values(tree, target): +def get_module_identifier_values(tree: ParseTree, target: ParseTree) -> List[Any]: """Get all module names between a tree node and a specific target node.""" path = _find_path(tree, target) - modules = [n for n in path if n.data == 'module_dcl'] + modules = [n for n in path if n is not None and n.data == 'module_dcl'] return [ get_first_identifier_value(n) for n in modules] -def _find_path(node, target): +def _find_path(node: ParseTree, target: ParseTree) -> List[ParseTree]: if node == target: return [node] for c in node.children: @@ -324,14 +348,17 @@ def _find_path(node, target): tail = _find_path(c, target) if tail is not None: return [node] + tail - return None + raise ValueError(f"No path found between {node} and {target}") -def get_abstract_type_from_const_expr(const_expr, value): +def get_abstract_type_from_const_expr(const_expr: ParseTree, value: Union[str, int, float, bool] + ) -> Union[BoundedString, BoundedWString, BasicType]: assert len(const_expr.children) == 1 child = const_expr.children[0] + assert isinstance(child, Tree) if child.data in ('string_type', 'wide_string_type'): + assert isinstance(value, str) if 'string_type' == child.data: return BoundedString(len(value)) if 'wide_string_type' == child.data: @@ -340,12 +367,17 @@ def get_abstract_type_from_const_expr(const_expr, value): while len(child.children) == 1: child = child.children[0] + assert isinstance(child, Tree) return BasicType(BASE_TYPE_SPEC_TO_IDL_TYPE[child.data]) -def get_abstract_type_optionally_as_array(abstract_type, declarator): +def get_abstract_type_optionally_as_array( + abstract_type: AbstractTypeAlias, + declarator: ParseTree +) -> Union[Array, AbstractTypeAlias]: assert len(declarator.children) == 1 child = declarator.children[0] + assert isinstance(child, Tree) if child.data == 'array_declarator': fixed_array_sizes = list(child.find_data('fixed_array_size')) assert len(fixed_array_sizes) == 1, \ @@ -357,7 +389,7 @@ def get_abstract_type_optionally_as_array(abstract_type, declarator): return abstract_type -def add_message_members(msg, tree): +def add_message_members(msg: Message, tree: ParseTree) -> None: members = tree.find_data('member') for member in members: # the find_data methods seems to traverse the tree in post order @@ -370,6 +402,7 @@ def add_message_members(msg, tree): for declarator in declarators: assert len(declarator.children) == 1 child = declarator.children[0] + assert isinstance(child, Tree) if child.data == 'array_declarator': fixed_array_sizes = list(child.find_data('fixed_array_size')) assert len(fixed_array_sizes) == 1, \ @@ -377,13 +410,16 @@ def add_message_members(msg, tree): positive_int_const = next( fixed_array_sizes[0].find_data('positive_int_const')) size = get_positive_int_const(positive_int_const) - abstract_type = Array(abstract_type, size) - m = Member(abstract_type, get_first_identifier_value(declarator)) + member_abstract_type: Union[Array, AbstractNestableType] = \ + Array(abstract_type, size) + else: + member_abstract_type = abstract_type + m = Member(member_abstract_type, get_first_identifier_value(declarator)) m.annotations += annotations msg.structure.members.append(m) -BASE_TYPE_SPEC_TO_IDL_TYPE = { +BASE_TYPE_SPEC_TO_IDL_TYPE: Dict[str, 'BasicTypeValues'] = { 'floating_pt_type_float': 'float', 'floating_pt_type_double': 'double', 'floating_pt_type_long_double': 'long double', @@ -402,20 +438,23 @@ def add_message_members(msg, tree): } -def get_abstract_type_from_type_spec(type_spec): +def get_abstract_type_from_type_spec(type_spec: ParseTree) -> AbstractTypeAlias: assert len(type_spec.children) == 1 child = type_spec.children[0] return get_abstract_type(child) -def get_abstract_type(tree): +def get_abstract_type(tree: Branch[Token]) -> AbstractTypeAlias: + assert isinstance(tree, Tree) if 'simple_type_spec' == tree.data: assert len(tree.children) == 1 child = tree.children[0] + assert isinstance(child, Tree) if 'base_type_spec' == child.data: while len(child.children) == 1: child = child.children[0] + assert isinstance(child, Tree) return BasicType(BASE_TYPE_SPEC_TO_IDL_TYPE[child.data]) if 'scoped_name' == child.data: @@ -432,6 +471,7 @@ def get_abstract_type(tree): if 'template_type_spec' == tree.data: assert len(tree.children) == 1 child = tree.children[0] + assert isinstance(child, Tree) if 'sequence_type' == child.data: # the find_data methods seems to traverse the tree in post order @@ -452,8 +492,10 @@ def get_abstract_type(tree): if child.data in ('string_type', 'wide_string_type'): if len(child.children) == 1: - assert child.children[0].data == 'positive_int_const' - maximum_size = get_positive_int_const(child.children[0]) + child = child.children[0] + assert isinstance(child, Tree) + assert child.data == 'positive_int_const' + maximum_size = get_positive_int_const(child) if 'string_type' == child.data: assert maximum_size > 0 return BoundedString(maximum_size=maximum_size) @@ -473,7 +515,7 @@ def get_abstract_type(tree): assert False, 'Unsupported tree: ' + str(tree) -def get_positive_int_const(positive_int_const): +def get_positive_int_const(positive_int_const: ParseTree) -> int: assert positive_int_const.data == 'positive_int_const' # TODO support arbitrary expressions try: @@ -483,6 +525,7 @@ def get_positive_int_const(positive_int_const): else: digits = '' for child in decimal_literal.children: + assert isinstance(child, Token) digits += child.value return int(digits) @@ -493,13 +536,13 @@ def get_positive_int_const(positive_int_const): pass else: # TODO ensure that identifier resolves to a positive integer - return identifier_token.value + return int(identifier_token.value) assert False, 'Unsupported tree: ' + str(positive_int_const) -def get_annotations(tree): - annotations = [] +def get_annotations(tree: ParseTree) -> List[Annotation]: + annotations: List[Annotation] = [] for c in tree.children: if not isinstance(c, Tree): continue @@ -508,11 +551,12 @@ def get_annotations(tree): annotation_appl = c params = list(annotation_appl.find_data('annotation_appl_param')) if params: - value = {} + value_dict: Dict[Any, Union[str, int, float, bool]] = {} for param in params: const_expr = next(param.find_data('const_expr')) - value[get_first_identifier_value(param)] = \ + value_dict[get_first_identifier_value(param)] = \ get_const_expr_value(const_expr) + value: ValueType = value_dict elif len(annotation_appl.children) == 1: value = None else: @@ -524,13 +568,15 @@ def get_annotations(tree): return annotations -def get_const_expr_value(const_expr): +def get_const_expr_value(const_expr: Branch[Token]) -> Union[str, int, float, bool]: + assert isinstance(const_expr, Tree) # TODO support arbitrary expressions expr = list(const_expr.find_data('primary_expr')) assert len(expr) == 1, str(expr) primary_expr = expr[0] assert len(primary_expr.children) == 1 child = primary_expr.children[0] + assert isinstance(child, Tree) if 'scoped_name' == child.data: return str(child.children[0]) elif 'literal' == child.data: @@ -540,13 +586,15 @@ def get_const_expr_value(const_expr): assert len(literal.children) == 1 child = literal.children[0] + assert isinstance(child, Tree) if child.data == 'integer_literal': assert len(child.children) == 1 child = child.children[0] + assert isinstance(child, Tree) if child.data == 'decimal_literal': - value = get_decimal_literal_value(child) + value: Union[int, float] = get_decimal_literal_value(child) if negate_value: value = -value return value @@ -568,6 +616,7 @@ def get_const_expr_value(const_expr): if child.data == 'boolean_literal': assert len(child.children) == 1 child = child.children[0] + assert isinstance(child, Tree) assert child.data in ('boolean_literal_true', 'boolean_literal_false') return child.data == 'boolean_literal_true' @@ -584,14 +633,17 @@ def get_const_expr_value(const_expr): assert False, 'Unsupported tree: ' + str(const_expr) -def get_decimal_literal_value(decimal_literal): +def get_decimal_literal_value(decimal_literal: ParseTree) -> int: value = '' for child in decimal_literal.children: - value += child.value + if isinstance(child, Token): + value += child.value + else: + assert False, 'Unsupported tree: ' + str(decimal_literal) return int(value) -def get_floating_pt_literal_value(floating_pt_literal): +def get_floating_pt_literal_value(floating_pt_literal: ParseTree) -> float: value = '' for child in floating_pt_literal.children: if isinstance(child, Token): @@ -601,7 +653,7 @@ def get_floating_pt_literal_value(floating_pt_literal): return float(value) -def get_fixed_pt_literal_value(fixed_pt_literal): +def get_fixed_pt_literal_value(fixed_pt_literal: ParseTree) -> float: value = '' for child in fixed_pt_literal.children: if isinstance(child, Token): @@ -611,7 +663,7 @@ def get_fixed_pt_literal_value(fixed_pt_literal): return float(value) -def get_string_literals_value(string_literals, *, allow_unicode=False): +def get_string_literals_value(string_literals: ParseTree, *, allow_unicode: bool = False) -> str: assert len(string_literals.children) > 0 value = '' for string_literal in string_literals.children: @@ -620,7 +672,8 @@ def get_string_literals_value(string_literals, *, allow_unicode=False): return value -def get_string_literal_value(string_literal, *, allow_unicode=False): +def get_string_literal_value(string_literal: Branch[Token], *, allow_unicode: bool = False) -> str: + assert isinstance(string_literal, Tree) if len(string_literal.children) == 0: return '' assert len(string_literal.children) == 1 @@ -639,18 +692,18 @@ def get_string_literal_value(string_literal, *, allow_unicode=False): value = value[1:-1] regex = _get_escape_sequences_regex(allow_unicode=allow_unicode) - value = regex.sub(_decode_escape_sequence, value) + str_value = regex.sub(_decode_escape_sequence, value) # unescape double quote and backslash if preceeded by a backslash i = 0 - while i < len(value): - if value[i] == '\\': - if i + 1 < len(value) and value[i + 1] in ('"', '\\'): - value = value[:i] + value[i + 1:] + while i < len(str_value): + if str_value[i] == '\\': + if i + 1 < len(str_value) and str_value[i + 1] in ('"', '\\'): + str_value = str_value[:i] + str_value[i + 1:] i += 1 - return value + return str_value -def _get_escape_sequences_regex(*, allow_unicode): +def _get_escape_sequences_regex(*, allow_unicode: bool) -> re.Pattern[str]: # IDL Table 7-9: Escape sequences pattern = '(' # newline, horizontal tab, vertical tab, backspace, carriage return, @@ -668,5 +721,5 @@ def _get_escape_sequences_regex(*, allow_unicode): return re.compile(pattern) -def _decode_escape_sequence(match): +def _decode_escape_sequence(match: re.Match[str]) -> str: return codecs.decode(match.group(0), 'unicode-escape') diff --git a/rosidl_parser/test/test_parser.py b/rosidl_parser/test/test_parser.py index b58f55721..1f8a5e059 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 IdlFile from rosidl_parser.definition import IdlLocator from rosidl_parser.definition import Include from rosidl_parser.definition import Message @@ -43,11 +44,11 @@ @pytest.fixture(scope='module') -def message_idl_file(): +def message_idl_file() -> IdlFile: return parse_idl_file(MESSAGE_IDL_LOCATOR) -def test_whitespace_at_start_of_string(): +def test_whitespace_at_start_of_string() -> None: # Repeat to check ros2/rosidl#676 for _ in range(10): ast = get_ast_from_idl_string('const string foo = " e";') @@ -55,7 +56,7 @@ def test_whitespace_at_start_of_string(): assert ' e' == get_string_literals_value(token) -def test_whitespace_at_start_of_wide_string(): +def test_whitespace_at_start_of_wide_string() -> None: # Repeat to check ros2/rosidl#676 for _ in range(10): ast = get_ast_from_idl_string('const wstring foo = L" e";') @@ -63,7 +64,7 @@ def test_whitespace_at_start_of_wide_string(): assert ' e' == get_string_literals_value(token, allow_unicode=True) -def test_whitespace_at_end_of_string(): +def test_whitespace_at_end_of_string() -> None: # Repeat to check ros2/rosidl#676 for _ in range(10): ast = get_ast_from_idl_string('const string foo = "e ";') @@ -71,7 +72,7 @@ def test_whitespace_at_end_of_string(): assert 'e ' == get_string_literals_value(token) -def test_whitespace_at_end_of_wide_string(): +def test_whitespace_at_end_of_wide_string() -> None: # Repeat to check ros2/rosidl#676 for _ in range(10): ast = get_ast_from_idl_string('const wstring foo = L"e ";') @@ -79,19 +80,19 @@ def test_whitespace_at_end_of_wide_string(): assert 'e ' == get_string_literals_value(token, allow_unicode=True) -def test_message_parser(message_idl_file): +def test_message_parser(message_idl_file: IdlFile) -> None: messages = message_idl_file.content.get_elements_of_type(Message) assert len(messages) == 1 -def test_message_parser_includes(message_idl_file): +def test_message_parser_includes(message_idl_file: IdlFile) -> None: includes = message_idl_file.content.get_elements_of_type(Include) assert len(includes) == 2 assert includes[0].locator == 'OtherMessage.idl' assert includes[1].locator == 'pkgname/msg/OtherMessage.idl' -def test_message_parser_structure(message_idl_file): +def test_message_parser_structure(message_idl_file: IdlFile) -> None: messages = message_idl_file.content.get_elements_of_type(Message) assert len(messages) == 1 @@ -185,7 +186,7 @@ def test_message_parser_structure(message_idl_file): assert structure.members[31].name == 'array_short_values' -def test_message_parser_annotations(message_idl_file): +def test_message_parser_annotations(message_idl_file: IdlFile) -> None: messages = message_idl_file.content.get_elements_of_type(Message) assert len(messages) == 1 structure = messages[0].structure @@ -305,11 +306,11 @@ def test_message_parser_annotations(message_idl_file): @pytest.fixture(scope='module') -def service_idl_file(): +def service_idl_file() -> IdlFile: return parse_idl_file(SERVICE_IDL_LOCATOR) -def test_service_parser(service_idl_file): +def test_service_parser(service_idl_file: IdlFile) -> None: services = service_idl_file.content.get_elements_of_type(Service) assert len(services) == 1 @@ -345,11 +346,11 @@ def test_service_parser(service_idl_file): @pytest.fixture(scope='module') -def action_idl_file(): +def action_idl_file() -> IdlFile: return parse_idl_file(ACTION_IDL_LOCATOR) -def test_action_parser(action_idl_file): +def test_action_parser(action_idl_file: IdlFile) -> None: actions = action_idl_file.content.get_elements_of_type(Action) assert len(actions) == 1 From 1c99d3b5ded854af6379c98965891f8a38e2d2ed Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sun, 6 Oct 2024 20:57:23 -0400 Subject: [PATCH 06/20] type complete Signed-off-by: Michael Carlstrom --- rosidl_parser/rosidl_parser/parser.py | 22 +++++++++++++-------- rosidl_parser/test/test_parser.py | 28 +++++++++++++-------------- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/rosidl_parser/rosidl_parser/parser.py b/rosidl_parser/rosidl_parser/parser.py index 0ae827f6d..8b3dfacbf 100644 --- a/rosidl_parser/rosidl_parser/parser.py +++ b/rosidl_parser/rosidl_parser/parser.py @@ -68,7 +68,7 @@ from rosidl_parser.definition import BasicTypeValues -AbstractTypeAlias = Union[AbstractNestableType, BoundedSequence, BoundedWString] +AbstractTypeAlias = Union[AbstractNestableType, BoundedSequence, UnboundedSequence] grammar_file = os.path.join(os.path.dirname(__file__), 'grammar.lark') with open(grammar_file, mode='r', encoding='utf-8') as h: @@ -144,7 +144,7 @@ def extract_content_from_ast(tree: ParseTree) -> IdlContent: assert isinstance(child, Tree) assert 'type_declarator' == child.data assert len(child.children) == 2 - abstract_type: Union[Array, AbstractTypeAlias] = get_abstract_type(child.children[0]) + abstract_type = get_abstract_type(child.children[0]) child = child.children[1] assert isinstance(child, Tree) assert 'any_declarators' == child.data @@ -152,12 +152,12 @@ def extract_content_from_ast(tree: ParseTree) -> IdlContent: child = child.children[0] assert isinstance(child, Tree) identifier = get_first_identifier_value(child) - abstract_type = get_abstract_type_optionally_as_array( + abstract_type_array = get_abstract_type_optionally_as_array( abstract_type, child) if identifier in typedefs: - assert typedefs[identifier] == abstract_type + assert typedefs[identifier] == abstract_type_array else: - typedefs[identifier] = abstract_type + typedefs[identifier] = abstract_type_array struct_defs = list(tree.find_data('struct_def')) if len(struct_defs) == 1: @@ -297,10 +297,13 @@ def resolve_typedefed_names(structure: Structure, typedefs: Dict[Any, Union[Arra if isinstance(typedefed_type.value_type, NamedType): assert typedefed_type.value_type.name in typedefs, \ 'Unknown named type: ' + typedefed_type.value_type.name - typedefed_type.value_type = \ - typedefs[typedefed_type.value_type.name] + + typedef = typedefs[typedefed_type.value_type.name] + assert isinstance(typedef, AbstractNestableType) + typedefed_type.value_type = typedef if isinstance(member.type, AbstractNestedType): + assert isinstance(typedefed_type, AbstractNestableType) member.type.value_type = typedefed_type else: member.type = typedefed_type @@ -379,13 +382,14 @@ def get_abstract_type_optionally_as_array( child = declarator.children[0] assert isinstance(child, Tree) if child.data == 'array_declarator': + assert isinstance(abstract_type, AbstractNestableType) fixed_array_sizes = list(child.find_data('fixed_array_size')) assert len(fixed_array_sizes) == 1, \ 'Unsupported multidimensional array: ' + str(declarator) positive_int_const = next( fixed_array_sizes[0].find_data('positive_int_const')) size = get_positive_int_const(positive_int_const) - abstract_type = Array(abstract_type, size) + return Array(abstract_type, size) return abstract_type @@ -397,6 +401,7 @@ def add_message_members(msg: Message, tree: ParseTree) -> None: type_specs = list(member.find_data('type_spec')) type_spec = type_specs[-1] abstract_type = get_abstract_type_from_type_spec(type_spec) + assert isinstance(abstract_type, AbstractNestableType) declarators = member.find_data('declarator') annotations = get_annotations(member) for declarator in declarators: @@ -479,6 +484,7 @@ def get_abstract_type(tree: Branch[Token]) -> AbstractTypeAlias: type_specs = list(child.find_data('type_spec')) type_spec = type_specs[-1] basetype = get_abstract_type_from_type_spec(type_spec) + assert isinstance(basetype, AbstractNestableType) positive_int_consts = list(child.find_data('positive_int_const')) if positive_int_consts: path = _find_path(child, positive_int_consts[0]) diff --git a/rosidl_parser/test/test_parser.py b/rosidl_parser/test/test_parser.py index 1f8a5e059..61b164742 100644 --- a/rosidl_parser/test/test_parser.py +++ b/rosidl_parser/test/test_parser.py @@ -193,12 +193,12 @@ def test_message_parser_annotations(message_idl_file: IdlFile) -> None: assert len(structure.annotations) == 2 assert structure.annotations[0].name == 'verbatim' - assert len(structure.annotations[0].value) == 2 - assert 'language' in structure.annotations[0].value - assert structure.annotations[0].value['language'] == 'comment' - assert 'text' in structure.annotations[0].value - assert structure.annotations[0].value['text'] == \ - 'Documentation of MyMessage.Adjacent string literal.' + assert len(structure.annotations[0].value) == 2 # type: ignore[arg-type] + assert 'language' in structure.annotations[0].value # type: ignore[operator] + assert structure.annotations[0].value['language'] == 'comment' # type: ignore[index] + assert 'text' in structure.annotations[0].value # type: ignore[operator] + text = structure.annotations[0].value['text'] # type: ignore[index] + assert text == 'Documentation of MyMessage.Adjacent string literal.' assert structure.annotations[1].name == 'transfer_mode' assert structure.annotations[1].value == 'SHMEM_REF' @@ -207,9 +207,9 @@ def test_message_parser_annotations(message_idl_file: IdlFile) -> None: assert structure.has_any_member_with_annotation('autoid') is False assert structure.members[2].annotations[0].name == 'default' - assert len(structure.members[2].annotations[0].value) == 1 - assert 'value' in structure.members[2].annotations[0].value - assert structure.members[2].annotations[0].value['value'] == 123 + assert len(structure.members[2].annotations[0].value) == 1 # type: ignore[arg-type] + assert 'value' in structure.members[2].annotations[0].value # type: ignore[operator] + assert structure.members[2].annotations[0].value['value'] == 123 # type: ignore[index] assert structure.has_any_member_with_annotation('default') assert len(structure.members[3].annotations) == 2 @@ -219,11 +219,11 @@ def test_message_parser_annotations(message_idl_file: IdlFile) -> None: assert structure.has_any_member_with_annotation('key') assert structure.members[3].annotations[1].name == 'range' - assert len(structure.members[3].annotations[1].value) == 2 - assert 'min' in structure.members[3].annotations[1].value - assert structure.members[3].annotations[1].value['min'] == -10 - assert 'max' in structure.members[3].annotations[1].value - assert structure.members[3].annotations[1].value['max'] == 10 + assert len(structure.members[3].annotations[1].value) == 2 # type: ignore[arg-type] + assert 'min' in structure.members[3].annotations[1].value # type: ignore[operator] + assert structure.members[3].annotations[1].value['min'] == -10 # type: ignore[index] + assert 'max' in structure.members[3].annotations[1].value # type: ignore[operator] + assert structure.members[3].annotations[1].value['max'] == 10 # type: ignore[index] assert structure.has_any_member_with_annotation('range') assert isinstance(structure.members[32].type, BasicType) From 6e4303d8886f13385fd209e656a9f775bd6f4286 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sat, 19 Oct 2024 10:34:18 -0400 Subject: [PATCH 07/20] flake8 fixes Signed-off-by: Michael Carlstrom --- rosidl_parser/rosidl_parser/parser.py | 13 +++++++------ rosidl_parser/test/test_mypy.py | 23 +++++++++++++++++++++++ 2 files changed, 30 insertions(+), 6 deletions(-) create mode 100644 rosidl_parser/test/test_mypy.py diff --git a/rosidl_parser/rosidl_parser/parser.py b/rosidl_parser/rosidl_parser/parser.py index 8b3dfacbf..ac591e827 100644 --- a/rosidl_parser/rosidl_parser/parser.py +++ b/rosidl_parser/rosidl_parser/parser.py @@ -21,19 +21,19 @@ from typing import Dict from typing import List from typing import Literal -from typing import Union from typing import Optional from typing import TYPE_CHECKING +from typing import Union from lark import Lark from lark.lexer import Token from lark.tree import Branch +from lark.tree import ParseTree from lark.tree import pydot__tree_to_png from lark.tree import Tree -from lark.tree import ParseTree -from rosidl_parser.definition import AbstractNestedType from rosidl_parser.definition import AbstractNestableType +from rosidl_parser.definition import AbstractNestedType from rosidl_parser.definition import AbstractType from rosidl_parser.definition import Action from rosidl_parser.definition import ACTION_FEEDBACK_SUFFIX @@ -282,7 +282,8 @@ def extract_content_from_ast(tree: ParseTree) -> IdlContent: return content -def resolve_typedefed_names(structure: Structure, typedefs: Dict[Any, Union[Array, AbstractTypeAlias]]) -> None: +def resolve_typedefed_names(structure: Structure, + typedefs: Dict[Any, Union[Array, AbstractTypeAlias]]) -> None: for member in structure.members: type_ = member.type if isinstance(type_, AbstractNestedType): @@ -297,7 +298,7 @@ def resolve_typedefed_names(structure: Structure, typedefs: Dict[Any, Union[Arra if isinstance(typedefed_type.value_type, NamedType): assert typedefed_type.value_type.name in typedefs, \ 'Unknown named type: ' + typedefed_type.value_type.name - + typedef = typedefs[typedefed_type.value_type.name] assert isinstance(typedef, AbstractNestableType) typedefed_type.value_type = typedef @@ -351,7 +352,7 @@ def _find_path(node: ParseTree, target: ParseTree) -> List[ParseTree]: tail = _find_path(c, target) if tail is not None: return [node] + tail - raise ValueError(f"No path found between {node} and {target}") + raise ValueError(f'No path found between {node} and {target}') def get_abstract_type_from_const_expr(const_expr: ParseTree, value: Union[str, int, float, bool] diff --git a/rosidl_parser/test/test_mypy.py b/rosidl_parser/test/test_mypy.py new file mode 100644 index 000000000..97e4f502a --- /dev/null +++ b/rosidl_parser/test/test_mypy.py @@ -0,0 +1,23 @@ +# Copyright 2024 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_mypy.main import main +import pytest + + +@pytest.mark.mypy +@pytest.mark.linter +def test_mypy() -> None: + rc = main(argv=[]) + assert rc == 0, 'Found type errors!' From cece6d471ac69394a12ee898c1e46192db2d55dd Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sat, 19 Oct 2024 10:37:45 -0400 Subject: [PATCH 08/20] add dep to package.xml Signed-off-by: Michael Carlstrom --- rosidl_parser/package.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/rosidl_parser/package.xml b/rosidl_parser/package.xml index 68222fa91..e6a2379d9 100644 --- a/rosidl_parser/package.xml +++ b/rosidl_parser/package.xml @@ -24,6 +24,7 @@ ament_cmake_pytest ament_lint_auto ament_lint_common + ament_mypy python3-pytest From d90aa1a06960320793c86ee2280b828e14731cd7 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sat, 19 Oct 2024 11:19:06 -0400 Subject: [PATCH 09/20] fix None return bug Signed-off-by: Michael Carlstrom --- rosidl_parser/rosidl_parser/parser.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rosidl_parser/rosidl_parser/parser.py b/rosidl_parser/rosidl_parser/parser.py index ac591e827..151231639 100644 --- a/rosidl_parser/rosidl_parser/parser.py +++ b/rosidl_parser/rosidl_parser/parser.py @@ -68,7 +68,7 @@ from rosidl_parser.definition import BasicTypeValues -AbstractTypeAlias = Union[AbstractNestableType, BoundedSequence, UnboundedSequence] +AbstractTypeAlias = Union[AbstractNestableType, BasicType, BoundedSequence, UnboundedSequence] grammar_file = os.path.join(os.path.dirname(__file__), 'grammar.lark') with open(grammar_file, mode='r', encoding='utf-8') as h: @@ -352,6 +352,7 @@ def _find_path(node: ParseTree, target: ParseTree) -> List[ParseTree]: tail = _find_path(c, target) if tail is not None: return [node] + tail + return None raise ValueError(f'No path found between {node} and {target}') @@ -402,7 +403,6 @@ def add_message_members(msg: Message, tree: ParseTree) -> None: type_specs = list(member.find_data('type_spec')) type_spec = type_specs[-1] abstract_type = get_abstract_type_from_type_spec(type_spec) - assert isinstance(abstract_type, AbstractNestableType) declarators = member.find_data('declarator') annotations = get_annotations(member) for declarator in declarators: @@ -410,13 +410,14 @@ def add_message_members(msg: Message, tree: ParseTree) -> None: child = declarator.children[0] assert isinstance(child, Tree) if child.data == 'array_declarator': + assert isinstance(abstract_type, AbstractNestableType) fixed_array_sizes = list(child.find_data('fixed_array_size')) assert len(fixed_array_sizes) == 1, \ 'Unsupported multidimensional array: ' + str(member) positive_int_const = next( fixed_array_sizes[0].find_data('positive_int_const')) size = get_positive_int_const(positive_int_const) - member_abstract_type: Union[Array, AbstractNestableType] = \ + member_abstract_type: Union[Array, AbstractTypeAlias] = \ Array(abstract_type, size) else: member_abstract_type = abstract_type From 356799b46fa1c37a46cbf9ea1079ed667270cf38 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sat, 19 Oct 2024 12:40:48 -0400 Subject: [PATCH 10/20] path recursive Signed-off-by: Michael Carlstrom --- rosidl_parser/rosidl_parser/parser.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/rosidl_parser/rosidl_parser/parser.py b/rosidl_parser/rosidl_parser/parser.py index 151231639..d60415b89 100644 --- a/rosidl_parser/rosidl_parser/parser.py +++ b/rosidl_parser/rosidl_parser/parser.py @@ -338,22 +338,28 @@ def find(t: Branch[Token]) -> bool: def get_module_identifier_values(tree: ParseTree, target: ParseTree) -> List[Any]: """Get all module names between a tree node and a specific target node.""" path = _find_path(tree, target) - modules = [n for n in path if n is not None and n.data == 'module_dcl'] + modules = [n for n in path if n.data == 'module_dcl'] return [ get_first_identifier_value(n) for n in modules] def _find_path(node: ParseTree, target: ParseTree) -> List[ParseTree]: + path = _find_path_recursive(node, target) + if path is None: + raise ValueError(f'No path found between {node} and {target}') + return path + + +def _find_path_recursive(node: ParseTree, target: ParseTree) -> Optional[List[ParseTree]]: if node == target: return [node] for c in node.children: if not isinstance(c, Tree): continue - tail = _find_path(c, target) + tail = _find_path_recursive(c, target) if tail is not None: return [node] + tail return None - raise ValueError(f'No path found between {node} and {target}') def get_abstract_type_from_const_expr(const_expr: ParseTree, value: Union[str, int, float, bool] @@ -544,7 +550,7 @@ def get_positive_int_const(positive_int_const: ParseTree) -> int: pass else: # TODO ensure that identifier resolves to a positive integer - return int(identifier_token.value) + return identifier_token.value assert False, 'Unsupported tree: ' + str(positive_int_const) From 2779c000901536eb5c156881a99ca6a66464824f Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sat, 19 Oct 2024 15:12:56 -0400 Subject: [PATCH 11/20] fix build error Signed-off-by: Michael Carlstrom --- rosidl_parser/rosidl_parser/parser.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rosidl_parser/rosidl_parser/parser.py b/rosidl_parser/rosidl_parser/parser.py index d60415b89..e773da332 100644 --- a/rosidl_parser/rosidl_parser/parser.py +++ b/rosidl_parser/rosidl_parser/parser.py @@ -506,10 +506,10 @@ def get_abstract_type(tree: Branch[Token]) -> AbstractTypeAlias: if child.data in ('string_type', 'wide_string_type'): if len(child.children) == 1: - child = child.children[0] - assert isinstance(child, Tree) - assert child.data == 'positive_int_const' - maximum_size = get_positive_int_const(child) + child_child = child.children[0] + assert isinstance(child_child, Tree) + assert child_child.data == 'positive_int_const' + maximum_size = get_positive_int_const(child_child) if 'string_type' == child.data: assert maximum_size > 0 return BoundedString(maximum_size=maximum_size) From 5e60e59727f882af3b3ee135646ac9c9a84947d2 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sat, 19 Oct 2024 15:16:51 -0400 Subject: [PATCH 12/20] remove no any return Signed-off-by: Michael Carlstrom --- rosidl_parser/rosidl_parser/parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rosidl_parser/rosidl_parser/parser.py b/rosidl_parser/rosidl_parser/parser.py index e773da332..871973ae8 100644 --- a/rosidl_parser/rosidl_parser/parser.py +++ b/rosidl_parser/rosidl_parser/parser.py @@ -550,7 +550,7 @@ def get_positive_int_const(positive_int_const: ParseTree) -> int: pass else: # TODO ensure that identifier resolves to a positive integer - return identifier_token.value + return int(identifier_token.value) assert False, 'Unsupported tree: ' + str(positive_int_const) From 09f69f8dcfa15b1c02a31a33f0d9f1e463932f14 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sat, 19 Oct 2024 15:22:44 -0400 Subject: [PATCH 13/20] cmake mypy Signed-off-by: Michael Carlstrom --- rosidl_parser/CMakeLists.txt | 1 + rosidl_parser/package.xml | 2 +- rosidl_parser/test/test_mypy.py | 23 ----------------------- 3 files changed, 2 insertions(+), 24 deletions(-) delete mode 100644 rosidl_parser/test/test_mypy.py diff --git a/rosidl_parser/CMakeLists.txt b/rosidl_parser/CMakeLists.txt index 9c282bca0..166758c2a 100644 --- a/rosidl_parser/CMakeLists.txt +++ b/rosidl_parser/CMakeLists.txt @@ -9,6 +9,7 @@ ament_python_install_package(${PROJECT_NAME}) if(BUILD_TESTING) find_package(ament_lint_auto REQUIRED) + find_package(ament_cmake_mypy REQUIRED) ament_lint_auto_find_test_dependencies() endif() diff --git a/rosidl_parser/package.xml b/rosidl_parser/package.xml index e6a2379d9..9f258cdcd 100644 --- a/rosidl_parser/package.xml +++ b/rosidl_parser/package.xml @@ -21,10 +21,10 @@ python3-lark-parser rosidl_adapter + ament_cmake_mypy ament_cmake_pytest ament_lint_auto ament_lint_common - ament_mypy python3-pytest diff --git a/rosidl_parser/test/test_mypy.py b/rosidl_parser/test/test_mypy.py deleted file mode 100644 index 97e4f502a..000000000 --- a/rosidl_parser/test/test_mypy.py +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright 2024 Open Source Robotics Foundation, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from ament_mypy.main import main -import pytest - - -@pytest.mark.mypy -@pytest.mark.linter -def test_mypy() -> None: - rc = main(argv=[]) - assert rc == 0, 'Found type errors!' From 3ba595b1e81166c0ca1cc546c685470b799f5afc Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sat, 19 Oct 2024 15:53:24 -0400 Subject: [PATCH 14/20] resolve constant bug Signed-off-by: Michael Carlstrom --- rosidl_parser/rosidl_parser/parser.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/rosidl_parser/rosidl_parser/parser.py b/rosidl_parser/rosidl_parser/parser.py index 871973ae8..1449cca27 100644 --- a/rosidl_parser/rosidl_parser/parser.py +++ b/rosidl_parser/rosidl_parser/parser.py @@ -397,6 +397,8 @@ def get_abstract_type_optionally_as_array( positive_int_const = next( fixed_array_sizes[0].find_data('positive_int_const')) size = get_positive_int_const(positive_int_const) + if isinstance(size, str): + raise ValueError('Arrays only support Literal Sizes not constants') return Array(abstract_type, size) return abstract_type @@ -423,6 +425,8 @@ def add_message_members(msg: Message, tree: ParseTree) -> None: positive_int_const = next( fixed_array_sizes[0].find_data('positive_int_const')) size = get_positive_int_const(positive_int_const) + if isinstance(size, str): + raise ValueError('Arrays only support Literal Sizes not constants') member_abstract_type: Union[Array, AbstractTypeAlias] = \ Array(abstract_type, size) else: @@ -500,6 +504,8 @@ def get_abstract_type(tree: Branch[Token]) -> AbstractTypeAlias: positive_int_consts.pop(0) if positive_int_consts: maximum_size = get_positive_int_const(positive_int_consts[-1]) + if isinstance(maximum_size, str): + raise ValueError('BoundedSequence only support Literal Sizes not constants') return BoundedSequence(basetype, maximum_size) else: return UnboundedSequence(basetype) @@ -511,6 +517,8 @@ def get_abstract_type(tree: Branch[Token]) -> AbstractTypeAlias: assert child_child.data == 'positive_int_const' maximum_size = get_positive_int_const(child_child) if 'string_type' == child.data: + if isinstance(maximum_size, str): + raise ValueError('BoundedString only support Literal Sizes not constants') assert maximum_size > 0 return BoundedString(maximum_size=maximum_size) if 'wide_string_type' == child.data: @@ -529,7 +537,7 @@ def get_abstract_type(tree: Branch[Token]) -> AbstractTypeAlias: assert False, 'Unsupported tree: ' + str(tree) -def get_positive_int_const(positive_int_const: ParseTree) -> int: +def get_positive_int_const(positive_int_const: ParseTree) -> Union[int, str]: assert positive_int_const.data == 'positive_int_const' # TODO support arbitrary expressions try: @@ -550,7 +558,7 @@ def get_positive_int_const(positive_int_const: ParseTree) -> int: pass else: # TODO ensure that identifier resolves to a positive integer - return int(identifier_token.value) + return str(identifier_token.value) assert False, 'Unsupported tree: ' + str(positive_int_const) From 86ec612c0ebb1918594aff5906202ad50cc5262b Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 31 Oct 2024 14:44:45 -0400 Subject: [PATCH 15/20] Add Branch defintion Signed-off-by: Michael Carlstrom --- rosidl_parser/rosidl_parser/definition.py | 3 ++- rosidl_parser/rosidl_parser/parser.py | 24 +++++++++++++++++------ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/rosidl_parser/rosidl_parser/definition.py b/rosidl_parser/rosidl_parser/definition.py index 8664ead92..5873ba470 100644 --- a/rosidl_parser/rosidl_parser/definition.py +++ b/rosidl_parser/rosidl_parser/definition.py @@ -17,6 +17,7 @@ from typing import Final from typing import Iterable from typing import List +from typing import Literal from typing import Optional from typing import Set from typing import Tuple @@ -95,7 +96,7 @@ ) if TYPE_CHECKING: - from typing import Literal, TypeAlias + from typing_extensions import TypeAlias SignedNonexplicitIntegerTypeValues = Literal['short', 'long', 'long long'] UnsignedNonexplicitIntegerTypeValues = Literal['unsigned short', 'unsigned long', 'unsigned long long'] diff --git a/rosidl_parser/rosidl_parser/parser.py b/rosidl_parser/rosidl_parser/parser.py index 1449cca27..432dc781a 100644 --- a/rosidl_parser/rosidl_parser/parser.py +++ b/rosidl_parser/rosidl_parser/parser.py @@ -27,7 +27,6 @@ from lark import Lark from lark.lexer import Token -from lark.tree import Branch from lark.tree import ParseTree from lark.tree import pydot__tree_to_png from lark.tree import Tree @@ -65,8 +64,20 @@ from rosidl_parser.definition import ValueType if TYPE_CHECKING: + from typing_extensions import TypeAlias + from typing import TypeVar + + # Definition taken from lark.tree to here since rhel's lark version does not have Branch. + _Leaf_T = TypeVar('_Leaf_T') + Branch: 'TypeAlias' = Union[_Leaf_T, 'Tree[_Leaf_T]'] + from rosidl_parser.definition import BasicTypeValues +try: + from lark.tree import Branch +except ImportError: + pass + AbstractTypeAlias = Union[AbstractNestableType, BasicType, BoundedSequence, UnboundedSequence] @@ -326,8 +337,8 @@ def get_child_identifier_value(tree: ParseTree) -> Any: return None -def _find_tokens(token_type: Optional[Literal['IDENTIFIER']]) -> Callable[[Branch[Token]], bool]: - def find(t: Branch[Token]) -> bool: +def _find_tokens(token_type: Optional[Literal['IDENTIFIER']]) -> Callable[['Branch[Token]'], bool]: + def find(t: 'Branch[Token]') -> bool: if isinstance(t, Token): if token_type is None or t.type == token_type: return True @@ -461,7 +472,7 @@ def get_abstract_type_from_type_spec(type_spec: ParseTree) -> AbstractTypeAlias: return get_abstract_type(child) -def get_abstract_type(tree: Branch[Token]) -> AbstractTypeAlias: +def get_abstract_type(tree: 'Branch[Token]') -> AbstractTypeAlias: assert isinstance(tree, Tree) if 'simple_type_spec' == tree.data: assert len(tree.children) == 1 @@ -590,7 +601,7 @@ def get_annotations(tree: ParseTree) -> List[Annotation]: return annotations -def get_const_expr_value(const_expr: Branch[Token]) -> Union[str, int, float, bool]: +def get_const_expr_value(const_expr: 'Branch[Token]') -> Union[str, int, float, bool]: assert isinstance(const_expr, Tree) # TODO support arbitrary expressions expr = list(const_expr.find_data('primary_expr')) @@ -694,7 +705,8 @@ def get_string_literals_value(string_literals: ParseTree, *, allow_unicode: bool return value -def get_string_literal_value(string_literal: Branch[Token], *, allow_unicode: bool = False) -> str: +def get_string_literal_value(string_literal: 'Branch[Token]', *, + allow_unicode: bool = False) -> str: assert isinstance(string_literal, Tree) if len(string_literal.children) == 0: return '' From fdf96df992edfedd43358424e184214cc9376f0e Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 31 Oct 2024 15:16:20 -0400 Subject: [PATCH 16/20] Moved ParseTree into try except Signed-off-by: Michael Carlstrom --- rosidl_parser/rosidl_parser/parser.py | 42 ++++++++++++++------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/rosidl_parser/rosidl_parser/parser.py b/rosidl_parser/rosidl_parser/parser.py index 432dc781a..8e3b413d4 100644 --- a/rosidl_parser/rosidl_parser/parser.py +++ b/rosidl_parser/rosidl_parser/parser.py @@ -27,7 +27,6 @@ from lark import Lark from lark.lexer import Token -from lark.tree import ParseTree from lark.tree import pydot__tree_to_png from lark.tree import Tree @@ -67,14 +66,16 @@ from typing_extensions import TypeAlias from typing import TypeVar - # Definition taken from lark.tree to here since rhel's lark version does not have Branch. + # Definitions taken from lark.tree to here since rhel's lark version does not have Branch. _Leaf_T = TypeVar('_Leaf_T') - Branch: 'TypeAlias' = Union[_Leaf_T, 'Tree[_Leaf_T]'] + Branch: TypeAlias = Union[_Leaf_T, 'Tree[_Leaf_T]'] + ParseTree: TypeAlias = Tree[Token] from rosidl_parser.definition import BasicTypeValues try: from lark.tree import Branch + from lark.tree import ParseTree except ImportError: pass @@ -112,14 +113,14 @@ def parse_idl_string(idl_string: str, png_file: Optional[str] = None) -> IdlCont return content -def get_ast_from_idl_string(idl_string: str) -> ParseTree: +def get_ast_from_idl_string(idl_string: str) -> 'ParseTree': global _parser if _parser is None: _parser = Lark(grammar, start='specification', maybe_placeholders=False) return _parser.parse(idl_string) -def extract_content_from_ast(tree: ParseTree) -> IdlContent: +def extract_content_from_ast(tree: 'ParseTree') -> IdlContent: content = IdlContent() include_directives = tree.find_data('include_directive') @@ -321,13 +322,13 @@ def resolve_typedefed_names(structure: Structure, member.type = typedefed_type -def get_first_identifier_value(tree: ParseTree) -> Any: +def get_first_identifier_value(tree: 'ParseTree') -> Any: """Get the value of the first identifier token for a node.""" identifier_token = next(tree.scan_values(_find_tokens('IDENTIFIER'))) return identifier_token.value -def get_child_identifier_value(tree: ParseTree) -> Any: +def get_child_identifier_value(tree: 'ParseTree') -> Any: """Get the value of the first child identifier token for a node.""" for c in tree.children: if not isinstance(c, Token): @@ -346,7 +347,7 @@ def find(t: 'Branch[Token]') -> bool: return find -def get_module_identifier_values(tree: ParseTree, target: ParseTree) -> List[Any]: +def get_module_identifier_values(tree: 'ParseTree', target: 'ParseTree') -> List[Any]: """Get all module names between a tree node and a specific target node.""" path = _find_path(tree, target) modules = [n for n in path if n.data == 'module_dcl'] @@ -354,14 +355,14 @@ def get_module_identifier_values(tree: ParseTree, target: ParseTree) -> List[Any get_first_identifier_value(n) for n in modules] -def _find_path(node: ParseTree, target: ParseTree) -> List[ParseTree]: +def _find_path(node: 'ParseTree', target: 'ParseTree') -> List['ParseTree']: path = _find_path_recursive(node, target) if path is None: raise ValueError(f'No path found between {node} and {target}') return path -def _find_path_recursive(node: ParseTree, target: ParseTree) -> Optional[List[ParseTree]]: +def _find_path_recursive(node: 'ParseTree', target: 'ParseTree') -> Optional[List['ParseTree']]: if node == target: return [node] for c in node.children: @@ -373,7 +374,7 @@ def _find_path_recursive(node: ParseTree, target: ParseTree) -> Optional[List[Pa return None -def get_abstract_type_from_const_expr(const_expr: ParseTree, value: Union[str, int, float, bool] +def get_abstract_type_from_const_expr(const_expr: 'ParseTree', value: Union[str, int, float, bool] ) -> Union[BoundedString, BoundedWString, BasicType]: assert len(const_expr.children) == 1 child = const_expr.children[0] @@ -395,7 +396,7 @@ def get_abstract_type_from_const_expr(const_expr: ParseTree, value: Union[str, i def get_abstract_type_optionally_as_array( abstract_type: AbstractTypeAlias, - declarator: ParseTree + declarator: 'ParseTree' ) -> Union[Array, AbstractTypeAlias]: assert len(declarator.children) == 1 child = declarator.children[0] @@ -414,7 +415,7 @@ def get_abstract_type_optionally_as_array( return abstract_type -def add_message_members(msg: Message, tree: ParseTree) -> None: +def add_message_members(msg: Message, tree: 'ParseTree') -> None: members = tree.find_data('member') for member in members: # the find_data methods seems to traverse the tree in post order @@ -466,7 +467,7 @@ def add_message_members(msg: Message, tree: ParseTree) -> None: } -def get_abstract_type_from_type_spec(type_spec: ParseTree) -> AbstractTypeAlias: +def get_abstract_type_from_type_spec(type_spec: 'ParseTree') -> AbstractTypeAlias: assert len(type_spec.children) == 1 child = type_spec.children[0] return get_abstract_type(child) @@ -548,7 +549,7 @@ def get_abstract_type(tree: 'Branch[Token]') -> AbstractTypeAlias: assert False, 'Unsupported tree: ' + str(tree) -def get_positive_int_const(positive_int_const: ParseTree) -> Union[int, str]: +def get_positive_int_const(positive_int_const: 'ParseTree') -> Union[int, str]: assert positive_int_const.data == 'positive_int_const' # TODO support arbitrary expressions try: @@ -574,7 +575,7 @@ def get_positive_int_const(positive_int_const: ParseTree) -> Union[int, str]: assert False, 'Unsupported tree: ' + str(positive_int_const) -def get_annotations(tree: ParseTree) -> List[Annotation]: +def get_annotations(tree: 'ParseTree') -> List[Annotation]: annotations: List[Annotation] = [] for c in tree.children: if not isinstance(c, Tree): @@ -666,7 +667,7 @@ def get_const_expr_value(const_expr: 'Branch[Token]') -> Union[str, int, float, assert False, 'Unsupported tree: ' + str(const_expr) -def get_decimal_literal_value(decimal_literal: ParseTree) -> int: +def get_decimal_literal_value(decimal_literal: 'ParseTree') -> int: value = '' for child in decimal_literal.children: if isinstance(child, Token): @@ -676,7 +677,7 @@ def get_decimal_literal_value(decimal_literal: ParseTree) -> int: return int(value) -def get_floating_pt_literal_value(floating_pt_literal: ParseTree) -> float: +def get_floating_pt_literal_value(floating_pt_literal: 'ParseTree') -> float: value = '' for child in floating_pt_literal.children: if isinstance(child, Token): @@ -686,7 +687,7 @@ def get_floating_pt_literal_value(floating_pt_literal: ParseTree) -> float: return float(value) -def get_fixed_pt_literal_value(fixed_pt_literal: ParseTree) -> float: +def get_fixed_pt_literal_value(fixed_pt_literal: 'ParseTree') -> float: value = '' for child in fixed_pt_literal.children: if isinstance(child, Token): @@ -696,7 +697,8 @@ def get_fixed_pt_literal_value(fixed_pt_literal: ParseTree) -> float: return float(value) -def get_string_literals_value(string_literals: ParseTree, *, allow_unicode: bool = False) -> str: +def get_string_literals_value(string_literals: 'ParseTree', *, + allow_unicode: bool = False) -> str: assert len(string_literals.children) > 0 value = '' for string_literal in string_literals.children: From b4d4837846d74ed6b980fe9756b9931666e6b8e2 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 31 Oct 2024 21:19:36 -0400 Subject: [PATCH 17/20] remove try except Signed-off-by: Michael Carlstrom --- rosidl_parser/rosidl_parser/parser.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/rosidl_parser/rosidl_parser/parser.py b/rosidl_parser/rosidl_parser/parser.py index 8e3b413d4..862c78461 100644 --- a/rosidl_parser/rosidl_parser/parser.py +++ b/rosidl_parser/rosidl_parser/parser.py @@ -66,20 +66,13 @@ from typing_extensions import TypeAlias from typing import TypeVar - # Definitions taken from lark.tree to here since rhel's lark version does not have Branch. + from rosidl_parser.definition import BasicTypeValues + + # Definitions taken from lark.tree to here since old lark version does not have Branch ot ParseTree. _Leaf_T = TypeVar('_Leaf_T') Branch: TypeAlias = Union[_Leaf_T, 'Tree[_Leaf_T]'] ParseTree: TypeAlias = Tree[Token] - from rosidl_parser.definition import BasicTypeValues - -try: - from lark.tree import Branch - from lark.tree import ParseTree -except ImportError: - pass - - AbstractTypeAlias = Union[AbstractNestableType, BasicType, BoundedSequence, UnboundedSequence] grammar_file = os.path.join(os.path.dirname(__file__), 'grammar.lark') From bfa2194580d8ea1816c1b2dd61af8b4d3d6d9ff2 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Thu, 31 Oct 2024 21:28:23 -0400 Subject: [PATCH 18/20] fix comment Signed-off-by: Michael Carlstrom --- rosidl_parser/rosidl_parser/parser.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rosidl_parser/rosidl_parser/parser.py b/rosidl_parser/rosidl_parser/parser.py index 862c78461..2693af206 100644 --- a/rosidl_parser/rosidl_parser/parser.py +++ b/rosidl_parser/rosidl_parser/parser.py @@ -68,7 +68,8 @@ from rosidl_parser.definition import BasicTypeValues - # Definitions taken from lark.tree to here since old lark version does not have Branch ot ParseTree. + # Definitions taken from lark.tree + # Since lark version's used by Windows and rhel does not have Branch or ParseTree. _Leaf_T = TypeVar('_Leaf_T') Branch: TypeAlias = Union[_Leaf_T, 'Tree[_Leaf_T]'] ParseTree: TypeAlias = Tree[Token] From 3582a1e9c64f9e38f023256071b4df5205d4e4df Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Fri, 1 Nov 2024 13:27:00 -0400 Subject: [PATCH 19/20] use Match and Pattern from typing module Signed-off-by: Michael Carlstrom --- rosidl_parser/rosidl_parser/parser.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rosidl_parser/rosidl_parser/parser.py b/rosidl_parser/rosidl_parser/parser.py index 2693af206..3df9db3be 100644 --- a/rosidl_parser/rosidl_parser/parser.py +++ b/rosidl_parser/rosidl_parser/parser.py @@ -21,7 +21,9 @@ from typing import Dict from typing import List from typing import Literal +from typing import Match from typing import Optional +from typing import Pattern from typing import TYPE_CHECKING from typing import Union @@ -733,7 +735,7 @@ def get_string_literal_value(string_literal: 'Branch[Token]', *, return str_value -def _get_escape_sequences_regex(*, allow_unicode: bool) -> re.Pattern[str]: +def _get_escape_sequences_regex(*, allow_unicode: bool) -> Pattern[str]: # IDL Table 7-9: Escape sequences pattern = '(' # newline, horizontal tab, vertical tab, backspace, carriage return, @@ -751,5 +753,5 @@ def _get_escape_sequences_regex(*, allow_unicode: bool) -> re.Pattern[str]: return re.compile(pattern) -def _decode_escape_sequence(match: re.Match[str]) -> str: +def _decode_escape_sequence(match: Match[str]) -> str: return codecs.decode(match.group(0), 'unicode-escape') From b7e6fab2eabd30765d8bd98db694d84539545a76 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Fri, 1 Nov 2024 16:26:35 -0400 Subject: [PATCH 20/20] old lark version work arounds Signed-off-by: Michael Carlstrom --- rosidl_parser/rosidl_parser/parser.py | 29 ++++++++++++++++----------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/rosidl_parser/rosidl_parser/parser.py b/rosidl_parser/rosidl_parser/parser.py index 3df9db3be..399ceee5d 100644 --- a/rosidl_parser/rosidl_parser/parser.py +++ b/rosidl_parser/rosidl_parser/parser.py @@ -73,8 +73,8 @@ # Definitions taken from lark.tree # Since lark version's used by Windows and rhel does not have Branch or ParseTree. _Leaf_T = TypeVar('_Leaf_T') - Branch: TypeAlias = Union[_Leaf_T, 'Tree[_Leaf_T]'] - ParseTree: TypeAlias = Tree[Token] + Branch: TypeAlias = Union[_Leaf_T, Tree] + ParseTree: TypeAlias = Tree AbstractTypeAlias = Union[AbstractNestableType, BasicType, BoundedSequence, UnboundedSequence] @@ -126,7 +126,8 @@ def extract_content_from_ast(tree: 'ParseTree') -> IdlContent: assert isinstance(child, Tree) assert child.data in ('h_char_sequence', 'q_char_sequence') include_token = next(child.scan_values(_find_tokens(None))) - content.elements.append(Include(include_token.value)) + # Type ignore around lark-parser typing bugging in old version + content.elements.append(Include(include_token.value)) # type: ignore[attr-defined] constants: Dict[str, List[Constant]] = {} const_dcls = tree.find_data('const_dcl') @@ -321,7 +322,8 @@ def resolve_typedefed_names(structure: Structure, def get_first_identifier_value(tree: 'ParseTree') -> Any: """Get the value of the first identifier token for a node.""" identifier_token = next(tree.scan_values(_find_tokens('IDENTIFIER'))) - return identifier_token.value + # Type ignore around lark-parser typing bugging in old version + return identifier_token.value # type: ignore[attr-defined] def get_child_identifier_value(tree: 'ParseTree') -> Any: @@ -334,8 +336,10 @@ def get_child_identifier_value(tree: 'ParseTree') -> Any: return None -def _find_tokens(token_type: Optional[Literal['IDENTIFIER']]) -> Callable[['Branch[Token]'], bool]: - def find(t: 'Branch[Token]') -> bool: +def _find_tokens( + token_type: Optional[Literal['IDENTIFIER']] +) -> Callable[['Branch[Union[str, Token]]'], bool]: + def find(t: 'Branch[Union[str, Token]]') -> bool: if isinstance(t, Token): if token_type is None or t.type == token_type: return True @@ -469,7 +473,7 @@ def get_abstract_type_from_type_spec(type_spec: 'ParseTree') -> AbstractTypeAlia return get_abstract_type(child) -def get_abstract_type(tree: 'Branch[Token]') -> AbstractTypeAlias: +def get_abstract_type(tree: 'Branch[Union[str, Token]]') -> AbstractTypeAlias: assert isinstance(tree, Tree) if 'simple_type_spec' == tree.data: assert len(tree.children) == 1 @@ -566,7 +570,8 @@ def get_positive_int_const(positive_int_const: 'ParseTree') -> Union[int, str]: pass else: # TODO ensure that identifier resolves to a positive integer - return str(identifier_token.value) + # Type ignore around lark-parser typing bugging in old version + return str(identifier_token.value) # type: ignore[attr-defined] assert False, 'Unsupported tree: ' + str(positive_int_const) @@ -598,7 +603,7 @@ def get_annotations(tree: 'ParseTree') -> List[Annotation]: return annotations -def get_const_expr_value(const_expr: 'Branch[Token]') -> Union[str, int, float, bool]: +def get_const_expr_value(const_expr: 'Branch[Union[str, Token]]') -> Union[str, int, float, bool]: assert isinstance(const_expr, Tree) # TODO support arbitrary expressions expr = list(const_expr.find_data('primary_expr')) @@ -703,7 +708,7 @@ def get_string_literals_value(string_literals: 'ParseTree', *, return value -def get_string_literal_value(string_literal: 'Branch[Token]', *, +def get_string_literal_value(string_literal: 'Branch[Union[str, Token]]', *, allow_unicode: bool = False) -> str: assert isinstance(string_literal, Tree) if len(string_literal.children) == 0: @@ -725,7 +730,7 @@ def get_string_literal_value(string_literal: 'Branch[Token]', *, regex = _get_escape_sequences_regex(allow_unicode=allow_unicode) str_value = regex.sub(_decode_escape_sequence, value) - # unescape double quote and backslash if preceeded by a backslash + # unescape double quote and backslash if preceded by a backslash i = 0 while i < len(str_value): if str_value[i] == '\\': @@ -754,4 +759,4 @@ def _get_escape_sequences_regex(*, allow_unicode: bool) -> Pattern[str]: def _decode_escape_sequence(match: Match[str]) -> str: - return codecs.decode(match.group(0), 'unicode-escape') + return codecs.decode(match.group(0), 'unicode-escape') # type: ignore