From 5266daec0855b62bebb0c4ffdfb845fb6c47d444 Mon Sep 17 00:00:00 2001 From: "Ryan.Friedman" Date: Mon, 18 Mar 2024 16:11:25 +0000 Subject: [PATCH] Add null support for constants and defaults Signed-off-by: Ryan Friedman --- rosidl_generator_py/resource/_msg.py.em | 7 ++++- .../rosidl_generator_py/generate_py_impl.py | 12 ++++++-- .../test/test_generate_py_impl.py | 28 +++++++++++++++++++ rosidl_generator_py/test/test_interfaces.py | 11 ++++++++ 4 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 rosidl_generator_py/test/test_generate_py_impl.py diff --git a/rosidl_generator_py/resource/_msg.py.em b/rosidl_generator_py/resource/_msg.py.em index 8c21e552..56a1504c 100644 --- a/rosidl_generator_py/resource/_msg.py.em +++ b/rosidl_generator_py/resource/_msg.py.em @@ -46,7 +46,7 @@ for member in message.structure.members: 'import builtins', []) # used for @builtins.property if isinstance(type_, BasicType) and type_.typename in FLOATING_POINT_TYPES: imports.setdefault( - 'import math', []) # used for math.isinf + 'import math', []) # used for math.isinf, math.nan if ( isinstance(member.type, AbstractNestedType) and isinstance(member.type.value_type, BasicType) and @@ -405,6 +405,11 @@ if isinstance(type_, AbstractNestedType): if all(self.@(member.name) != other.@(member.name)): @[ else]@ if self.@(member.name) != other.@(member.name): +@[ if isinstance(member.type, BasicType) and member.type.typename in FLOATING_POINT_TYPES ]@ + # Handle special case for IEE-754 floating point NaN not equaling itself. + if math.isnan(self.@(member.name)) and math.isnan(other.@(member.name)): + return True +@[ end if]@ @[ end if]@ return False @[end for]@ diff --git a/rosidl_generator_py/rosidl_generator_py/generate_py_impl.py b/rosidl_generator_py/rosidl_generator_py/generate_py_impl.py index 0cbaaa10..d45beb89 100644 --- a/rosidl_generator_py/rosidl_generator_py/generate_py_impl.py +++ b/rosidl_generator_py/rosidl_generator_py/generate_py_impl.py @@ -14,6 +14,7 @@ from ast import literal_eval import keyword +import math import os import pathlib import sys @@ -246,7 +247,7 @@ def primitive_value_to_py(type_, value): return repr(bytes([value])) if type_.typename in FLOATING_POINT_TYPES: - return '%s' % value + return floating_point_to_py(value) assert False, "unknown primitive type '%s'" % type_.typename @@ -268,7 +269,7 @@ def constant_value_to_py(type_, value): return repr(bytes([value])) if type_.typename in FLOATING_POINT_TYPES: - return '%s' % value + return floating_point_to_py(value) if isinstance(type_, AbstractGenericString): return quoted_string(value) @@ -276,6 +277,13 @@ def constant_value_to_py(type_, value): assert False, "unknown constant type '%s'" % type_ +def floating_point_to_py(value): + if math.isnan(float(value)): + return 'math.nan' + else: + return '%s' % value + + def quoted_string(s): s = s.replace('\\', '\\\\') # strings containing single quote but no double quotes can be wrapped in diff --git a/rosidl_generator_py/test/test_generate_py_impl.py b/rosidl_generator_py/test/test_generate_py_impl.py new file mode 100644 index 00000000..b1dfcd58 --- /dev/null +++ b/rosidl_generator_py/test/test_generate_py_impl.py @@ -0,0 +1,28 @@ +# Copyright 2016-2019 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 rosidl_generator_py.generate_py_impl import floating_point_to_py + + +def test_floating_point_to_py(): + + # normal floating point values + assert floating_point_to_py("1.0") == "1.0" + assert floating_point_to_py("-42.0") == "-42.0" + + # NaN's + assert floating_point_to_py("NaN") == "math.nan" + assert floating_point_to_py("nan") == "math.nan" + assert floating_point_to_py("NAN") == "math.nan" diff --git a/rosidl_generator_py/test/test_interfaces.py b/rosidl_generator_py/test/test_interfaces.py index 100beed9..90b58105 100644 --- a/rosidl_generator_py/test/test_interfaces.py +++ b/rosidl_generator_py/test/test_interfaces.py @@ -83,8 +83,12 @@ def test_basic_types(): assert 42 == msg.char_value msg.float32_value = 1.125 assert 1.125 == msg.float32_value + msg.float32_value = math.nan + assert math.isnan(msg.float32_value) msg.float64_value = 1.125 assert 1.125 == msg.float64_value + msg.float64_value = math.nan + assert math.isnan(msg.float64_value) msg.int8_value = -50 assert -50 == msg.int8_value msg.uint8_value = 200 @@ -130,22 +134,29 @@ def test_basic_types(): setattr(msg, 'float64_value', float64_ieee_max_next) # NaN + # Test IEEE-754 message equality when values are NaN. setattr(msg, 'float32_value', math.nan) assert math.isnan(msg.float32_value) + assert msg == msg setattr(msg, 'float64_value', math.nan) assert math.isnan(msg.float64_value) + assert msg == msg # -Inf setattr(msg, 'float32_value', -math.inf) assert math.isinf(msg.float32_value) + assert msg == msg setattr(msg, 'float64_value', -math.inf) assert math.isinf(msg.float64_value) + assert msg == msg # +Inf setattr(msg, 'float32_value', math.inf) assert math.isinf(msg.float32_value) + assert msg == msg setattr(msg, 'float64_value', math.inf) assert math.isinf(msg.float64_value) + assert msg == msg def test_strings():