def serializer_generator(msg_context, spec, serialize, is_numpy): """ Python generator that yields un-indented python code for (de)serializing MsgSpec. The code this yields is meant to be included in a class method and cannot be used standalone. serialize_fn_generator and deserialize_fn_generator wrap method to provide appropriate class field initializations. :param serialize: if True, yield serialization code. Otherwise, yield deserialization code. ``bool`` :param is_numpy: if True, generate serializer code for numpy datatypes instead of Python lists. ``bool`` """ # Break spec into chunks of simple (primitives) vs. complex (arrays, etc...) # Simple types are batch serialized using the python struct module. # Complex types are individually serialized if spec is None: raise MsgGenerationException("spec is none") names, types = spec.names, spec.types if serialize and not len(names): #Empty yield "pass" return _max_chunk = 255 # iterate through types. whenever we encounter a non-simple type, # yield serializer for any simple types we've encountered until # then, then yield the complex type serializer curr = 0 for (i, full_type) in enumerate(types): if not is_simple(full_type): if i != curr: #yield chunk of simples for _start in range(curr, i, _max_chunk): _end = min(_start + _max_chunk, i) for y in simple_serializer_generator(msg_context, spec, _start, _end, serialize): yield y curr = i+1 for y in complex_serializer_generator(msg_context, spec.package, full_type, names[i], serialize, is_numpy): yield y if curr < len(types): #yield rest of simples for _start in range(curr, len(types), _max_chunk): _end = min(_start + _max_chunk, len(types)) for y in simple_serializer_generator(msg_context, spec, _start, _end, serialize): yield y
def check_type(field_name, field_type, field_val): """ Dynamic type checker that maps ROS .msg types to python types and verifies the python value. check_type() is not designed to be fast and is targeted at error diagnosis. This type checker is not designed to run fast and is meant only for error diagnosis. :param field_name: ROS .msg field name, ``str`` :param field_type: ROS .msg field type, ``str`` :param field_val: field value, ``Any`` :raises: :exc:`SerializationError` If typecheck fails """ if is_simple(field_type): # check sign and width if field_type in ['byte', 'int8', 'int16', 'int32', 'int64']: if type(field_val) not in [long, int]: raise SerializationError('field %s must be an integer type' % field_name) maxval = int(math.pow(2, _widths[field_type] - 1)) if field_val >= maxval or field_val <= -maxval: raise SerializationError( 'field %s exceeds specified width [%s]' % (field_name, field_type)) elif field_type in ['char', 'uint8', 'uint16', 'uint32', 'uint64']: if type(field_val) not in [long, int] or field_val < 0: raise SerializationError( 'field %s must be unsigned integer type' % field_name) maxval = int(math.pow(2, _widths[field_type])) if field_val >= maxval: raise SerializationError( 'field %s exceeds specified width [%s]' % (field_name, field_type)) elif field_type == 'bool': if field_val not in [True, False, 0, 1]: raise SerializationError('field %s is not a bool' % (field_name)) elif field_type == 'string': if sys.hexversion > 0x03000000: if type(field_val) == str: try: field_val.encode('ascii') except UnicodeEncodeError: raise SerializationError('field %s is a non-ascii string' % field_name) elif not type(field_val) == bytes: raise SerializationError( 'field %s must be of type bytes or an ascii string' % field_name) else: if type(field_val) == unicode: raise SerializationError( 'field %s is a unicode string instead of an ascii string' % field_name) elif not isstring(field_val): raise SerializationError('field %s must be of type str' % field_name) elif field_type == 'time': if not isinstance(field_val, Time): raise SerializationError('field %s must be of type Time' % field_name) elif field_type == 'duration': if not isinstance(field_val, Duration): raise SerializationError('field %s must be of type Duration' % field_name) elif field_type.endswith(']'): # array type # use index to generate error if '[' not present base_type = field_type[:field_type.index('[')] if type(field_val) == str: if not base_type in ['char', 'uint8']: raise SerializationError( 'field %s must be a list or tuple type. Only uint8[] can be a string' % field_name) else: #It's a string so its already in byte format and we #don't need to check the individual bytes in the #string. return if not type(field_val) in [list, tuple]: raise SerializationError('field %s must be a list or tuple type' % field_name) for v in field_val: check_type(field_name + "[]", base_type, v) else: if isinstance(field_val, Message): # roslib/Header is the old location of Header. We check it for backwards compat if field_val._type in ['std_msgs/Header', 'roslib/Header']: if field_type not in [ 'Header', 'std_msgs/Header', 'roslib/Header' ]: raise SerializationError( "field %s must be a Header instead of a %s" % (field_name, field_val._type)) elif field_val._type != field_type: raise SerializationError( "field %s must be of type %s instead of %s" % (field_name, field_type, field_val._type)) for n, t in zip(field_val.__slots__, field_val._get_types()): check_type("%s.%s" % (field_name, n), t, getattr(field_val, n)) else: raise SerializationError("field %s must be of type [%s]" % (field_name, field_type))
def array_serializer_generator(msg_context, package, type_, name, serialize, is_numpy): """ Generator for array types :raises: :exc:`MsgGenerationException` If array spec is invalid """ base_type, is_array, array_len = genmsg.msgs.parse_type(type_) if not is_array: raise MsgGenerationException("Invalid array spec: %s"%type_) var_length = array_len is None # handle fixed-size byte arrays could be slightly more efficient # as we recalculated the length in the generated code. if base_type in ['char', 'uint8']: #treat unsigned int8 arrays as string type for y in string_serializer_generator(package, type_, name, serialize): yield y return var = _serial_context+name # yield length serialization, if necessary if var_length: for y in len_serializer_generator(var, False, serialize): yield y #serialize array length length = None else: length = array_len #optimization for simple arrays if is_simple(base_type): if var_length: pattern = compute_struct_pattern([base_type]) yield "pattern = '<%%s%s'%%length"%pattern if serialize: if is_numpy: yield pack_numpy(var) else: yield pack2('pattern', "*"+var) else: yield "start = end" yield "end += struct.calcsize(pattern)" if is_numpy: dtype = NUMPY_DTYPE[base_type] yield unpack_numpy(var, 'length', dtype, 'str[start:end]') else: yield unpack2(var, 'pattern', 'str[start:end]') else: pattern = "%s%s"%(length, compute_struct_pattern([base_type])) if serialize: if is_numpy: yield pack_numpy(var) else: yield pack(pattern, "*"+var) else: yield "start = end" yield "end += %s"%struct.calcsize('<%s'%pattern) if is_numpy: dtype = NUMPY_DTYPE[base_type] yield unpack_numpy(var, length, dtype, 'str[start:end]') else: yield unpack(var, pattern, 'str[start:end]') if not serialize and base_type == 'bool': # convert uint8 to bool if base_type == 'bool': yield "%s = map(bool, %s)"%(var, var) else: #generic recursive serializer #NOTE: this is functionally equivalent to the is_registered branch of complex_serializer_generator # choose a unique temporary variable for iterating loop_var = 'val%s'%len(_context_stack) # compute the variable context and factory to use if base_type == 'string': push_context('') factory = string_serializer_generator(package, base_type, loop_var, serialize) else: push_context('%s.'%loop_var) factory = serializer_generator(msg_context, get_registered_ex(msg_context, base_type), serialize, is_numpy) if serialize: yield 'for %s in %s:'%(loop_var, var) else: yield '%s = []'%var if var_length: yield 'for i in range(0, length):' else: yield 'for i in range(0, %s):'%length if base_type != 'string': yield INDENT + '%s = %s'%(loop_var, compute_constructor(msg_context, package, base_type)) for y in factory: yield INDENT + y if not serialize: yield INDENT + '%s.append(%s)'%(var, loop_var) pop_context()
def check_type(field_name, field_type, field_val): """ Dynamic type checker that maps ROS .msg types to python types and verifies the python value. check_type() is not designed to be fast and is targeted at error diagnosis. This type checker is not designed to run fast and is meant only for error diagnosis. :param field_name: ROS .msg field name, ``str`` :param field_type: ROS .msg field type, ``str`` :param field_val: field value, ``Any`` :raises: :exc:`SerializationError` If typecheck fails """ if is_simple(field_type): # check sign and width if field_type in ['byte', 'int8', 'int16', 'int32', 'int64']: if type(field_val) not in [long, int]: raise SerializationError('field %s must be an integer type'%field_name) maxval = int(math.pow(2, _widths[field_type]-1)) if field_val >= maxval or field_val <= -maxval: raise SerializationError('field %s exceeds specified width [%s]'%(field_name, field_type)) elif field_type in ['char', 'uint8', 'uint16', 'uint32', 'uint64']: if type(field_val) not in [long, int] or field_val < 0: raise SerializationError('field %s must be unsigned integer type'%field_name) maxval = int(math.pow(2, _widths[field_type])) if field_val >= maxval: raise SerializationError('field %s exceeds specified width [%s]'%(field_name, field_type)) elif field_type == 'bool': if field_val not in [True, False, 0, 1]: raise SerializationError('field %s is not a bool'%(field_name)) elif field_type == 'string': if sys.hexversion > 0x03000000: if type(field_val) == str: try: field_val.encode('ascii') except UnicodeEncodeError: raise SerializationError('field %s is a non-ascii string'%field_name) elif not type(field_val) == bytes: raise SerializationError('field %s must be of type bytes or an ascii string'%field_name) else: if type(field_val) == unicode: raise SerializationError('field %s is a unicode string instead of an ascii string'%field_name) elif not isstring(field_val): raise SerializationError('field %s must be of type str'%field_name) elif field_type == 'time': if not isinstance(field_val, Time): raise SerializationError('field %s must be of type Time'%field_name) elif field_type == 'duration': if not isinstance(field_val, Duration): raise SerializationError('field %s must be of type Duration'%field_name) elif field_type.endswith(']'): # array type # use index to generate error if '[' not present base_type = field_type[:field_type.index('[')] if type(field_val) == str: if not base_type in ['char', 'uint8']: raise SerializationError('field %s must be a list or tuple type. Only uint8[] can be a string' % field_name); else: #It's a string so its already in byte format and we #don't need to check the individual bytes in the #string. return if not type(field_val) in [list, tuple]: raise SerializationError('field %s must be a list or tuple type'%field_name) for v in field_val: check_type(field_name+"[]", base_type, v) else: if isinstance(field_val, Message): # roslib/Header is the old location of Header. We check it for backwards compat if field_val._type in ['std_msgs/Header', 'roslib/Header']: if field_type not in ['Header', 'std_msgs/Header', 'roslib/Header']: raise SerializationError("field %s must be a Header instead of a %s"%(field_name, field_val._type)) elif field_val._type != field_type: raise SerializationError("field %s must be of type %s instead of %s"%(field_name, field_type, field_val._type)) for n, t in zip(field_val.__slots__, field_val._get_types()): check_type("%s.%s"%(field_name,n), t, getattr(field_val, n)) else: raise SerializationError("field %s must be of type [%s]"%(field_name, field_type))