generator.py
Go to the documentation of this file.
00001 # Software License Agreement (BSD License)
00002 #
00003 # Copyright (c) 2008, Willow Garage, Inc.
00004 # All rights reserved.
00005 #
00006 # Redistribution and use in source and binary forms, with or without
00007 # modification, are permitted provided that the following conditions
00008 # are met:
00009 #
00010 #  * Redistributions of source code must retain the above copyright
00011 #    notice, this list of conditions and the following disclaimer.
00012 #  * Redistributions in binary form must reproduce the above
00013 #    copyright notice, this list of conditions and the following
00014 #    disclaimer in the documentation and/or other materials provided
00015 #    with the distribution.
00016 #  * Neither the name of Willow Garage, Inc. nor the names of its
00017 #    contributors may be used to endorse or promote products derived
00018 #    from this software without specific prior written permission.
00019 #
00020 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00021 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00022 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00023 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00024 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00025 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00026 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00027 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00028 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00029 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00030 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00031 # POSSIBILITY OF SUCH DAMAGE.
00032 
00033 """
00034 Library for Python message generation.
00035 
00036 The structure of the serialization descends several levels of serializers:
00037  - msg_generator: generator for an individual msg file
00038   - serialize_fn_generator: generator for msg.serialize()
00039     - serializer_generator
00040       - field-type-specific serializers
00041                     raise MsgGenerationException("unknown file extension: %s"%f)
00042 
00043   - deserialize_fn_generator: generator for msg.deserialize()
00044     - serializer_generator
00045       - field-type-specific serializers
00046 """
00047 
00048 from __future__ import print_function
00049 
00050 import os
00051 import keyword
00052 import itertools
00053 import sys
00054 import traceback
00055 import struct
00056 
00057 import genmsg
00058 import genmsg.msgs
00059 import genmsg.msg_loader
00060 import genmsg.gentools
00061 
00062 from genmsg import InvalidMsgSpec, MsgContext, MsgSpec, MsgGenerationException
00063 from genmsg.base import log
00064 
00065 from base import is_simple, SIMPLE_TYPES, SIMPLE_TYPES_DICT
00066 from generate_numpy import unpack_numpy, pack_numpy, NUMPY_DTYPE
00067 from generate_struct import reduce_pattern, serialize, \
00068      int32_pack, int32_unpack, pack, pack2, unpack, unpack2, compute_struct_pattern, \
00069      clear_patterns, add_pattern, get_patterns
00070 
00071 # indent width
00072 INDENT = '  '
00073 
00074 def get_registered_ex(msg_context, type_):
00075     """
00076     wrapper for get_registered that wraps unknown types with a MsgGenerationException
00077     :param type_: ROS message type, ``str``
00078     """
00079     try:
00080         return msg_context.get_registered(type_)
00081     except:
00082         raise MsgGenerationException("Unknown type [%s]. Please check that the manifest.xml correctly declares dependencies."%type_)
00083 
00084 ################################################################################
00085 # Special type handling for ROS builtin types that are not primitives
00086 
00087 class Special:
00088 
00089     def __init__(self, constructor, post_deserialize, import_str):
00090         """
00091         :param constructor: expression to instantiate new type instance for deserialization, ``str``
00092         :param post_Deserialize: format string for expression to evaluate on type instance after deserialization is complete., ``str``
00093           variable name will be passed in as the single argument to format string.
00094         :param import_str: import to include if type is present, ``str``
00095         """
00096         self.constructor = constructor
00097         self.post_deserialize = post_deserialize
00098         self.import_str = import_str
00099 
00100     def get_post_deserialize(self, varname):
00101         """
00102         :returns: Post-deserialization code to executed (unindented) or
00103           ``None`` if no post-deserialization is required, ``str``
00104         """
00105         if self.post_deserialize:
00106             return self.post_deserialize%varname
00107         else:
00108             return None
00109 
00110 _SPECIAL_TYPES = {
00111     genmsg.HEADER:   Special('std_msgs.msg._Header.Header()',     None, 'import std_msgs.msg'),
00112     genmsg.TIME:     Special('genpy.Time()',     '%s.canon()', 'import genpy'),
00113     genmsg.DURATION: Special('genpy.Duration()', '%s.canon()', 'import genpy'),
00114     }
00115 
00116 def is_special(type_):
00117     """
00118     :returns: ``True` if *type_* is a special type (i.e. builtin represented as a class instead of a primitive), ``bool``
00119     """
00120     return type_ in _SPECIAL_TYPES
00121 
00122 def get_special(type_):
00123     """
00124     :returns: special type handler for *type_* or ``None``, ``Special``
00125     """
00126     return _SPECIAL_TYPES.get(type_, None)
00127 
00128 ################################################################################
00129 # utilities
00130 
00131 # #671
00132 def default_value(msg_context, field_type, default_package):
00133     """
00134     Compute default value for field_type
00135 
00136     :param default_package: default package, ``str``
00137     :param field_type: ROS .msg field type, ``str``
00138     :returns: default value encoded in Python string representation, ``str``
00139     """
00140     if field_type in ['byte', 'int8', 'int16', 'int32', 'int64',\
00141                           'char', 'uint8', 'uint16', 'uint32', 'uint64']:
00142         return '0'
00143     elif field_type in ['float32', 'float64']:
00144         return '0.'
00145     elif field_type == 'string':
00146         # strings, char[], and uint8s are all optimized to be strings
00147         return "''"
00148     elif field_type == 'bool':
00149         return 'False'
00150     elif field_type.endswith(']'): # array type
00151         base_type, is_array, array_len = genmsg.msgs.parse_type(field_type)
00152         if base_type in ['char', 'uint8']:
00153             # strings, char[], and uint8s are all optimized to be strings
00154             if array_len is not None:
00155                 return "chr(0)*%s"%array_len
00156             else:
00157                 return "''"
00158         elif array_len is None: #var-length
00159             return '[]'
00160         else: # fixed-length, fill values
00161             def_val = default_value(msg_context, base_type, default_package)
00162             return '[' + ','.join(itertools.repeat(def_val, array_len)) + ']'
00163     else:
00164         return compute_constructor(msg_context, default_package, field_type)
00165 
00166 def flatten(msg_context, msg):
00167     """
00168     Flattens the msg spec so that embedded message fields become
00169     direct references. The resulting MsgSpec isn't a true/legal
00170     :class:`MsgSpec` and should only be used for serializer generation.
00171     :param msg: MsgSpec to flatten
00172     :returns: flattened MsgSpec message
00173     """
00174     new_types = []
00175     new_names = []
00176     for t, n in zip(msg.types, msg.names):
00177         # Parse type to make sure we don't flatten an array
00178         msg_type, is_array, _ = genmsg.msgs.parse_type(t)
00179         #flatten embedded types - note: bug #59
00180         if not is_array and msg_context.is_registered(t):
00181             msg_spec = flatten(msg_context, msg_context.get_registered(t))
00182             new_types.extend(msg_spec.types)
00183             for n2 in msg_spec.names:
00184                 new_names.append(n+'.'+n2)
00185         else:
00186             #I'm not sure if it's a performance win to flatten fixed-length arrays
00187             #as you get n __getitems__ method calls vs. a single *array call
00188             new_types.append(t)
00189             new_names.append(n)
00190     return MsgSpec(new_types, new_names, msg.constants, msg.text, msg.full_name)
00191 
00192 def make_python_safe(spec):
00193     """
00194     Remap field/constant names in spec to avoid collision with Python reserved words.
00195 
00196     :param spec: msg spec to map to new, python-safe field names, ``MsgSpec``
00197     :returns: python-safe message specification, ``MsgSpec``
00198     """
00199     new_c = [genmsg.Constant(c.type, _remap_reserved(c.name), c.val, c.val_text) for c in spec.constants]
00200     return MsgSpec(spec.types, [_remap_reserved(n) for n in spec.names], new_c, spec.text, spec.full_name)
00201 
00202 def _remap_reserved(field_name):
00203     """
00204     Map field_name to a python-safe representation, if necessary
00205     :param field_name: msg field name, ``str``
00206     :returns: remapped name, ``str``
00207     """
00208     # include 'self' as well because we are within a class instance
00209     idx = field_name.rfind('.')
00210     if idx > 0:
00211         prefix = field_name[:idx+1]
00212         sub_field_name = field_name[idx+1:]
00213     else:
00214         prefix = ''
00215         sub_field_name = field_name
00216 
00217     if sub_field_name in keyword.kwlist + ['self']:
00218         sub_field_name = sub_field_name + "_"
00219     return prefix + sub_field_name
00220 
00221 ################################################################################
00222 # (de)serialization routines
00223 
00224 def compute_post_deserialize(type_, varname):
00225     """
00226     Compute post-deserialization code for type_, if necessary
00227     :returns: code to execute post-deserialization (unindented), or None if not necessary. ``str``
00228     """
00229     s = get_special(type_)
00230     if s is not None:
00231         return s.get_post_deserialize(varname)
00232 
00233 def compute_constructor(msg_context, package, type_):
00234     """
00235     Compute python constructor expression for specified message type implementation
00236     :param package str: package that type is being imported into. Used
00237         to resolve type_ if package is not specified. ``str``
00238     :param type_: message type, ``str``
00239     """
00240     if is_special(type_):
00241         return get_special(type_).constructor
00242     elif genmsg.msgs.bare_msg_type(type_) != type_:
00243         # array or other weird type
00244         return None
00245     else:
00246         base_pkg, base_type_ = compute_pkg_type(package, type_)
00247         if not msg_context.is_registered("%s/%s"%(base_pkg,base_type_)):
00248             return None
00249         else:
00250             return '%s.msg.%s()'%(base_pkg, base_type_)
00251 
00252 def compute_pkg_type(package, type_):
00253     """
00254     :param package: package that type is being imported into, ``str``
00255     :param type: message type (package resource name), ``str``
00256     :returns: python package and type name, ``(str, str)``
00257     """
00258     splits = type_.split(genmsg.SEP)
00259     if len(splits) == 1:
00260         return package, splits[0]
00261     elif len(splits) == 2:
00262         return tuple(splits)
00263     else:
00264         raise MsgGenerationException("illegal message type: %s"%type_)
00265 
00266 def compute_import(msg_context, package, type_):
00267     """
00268     Compute python import statement for specified message type implementation
00269     :param package: package that type is being imported into, ``str``
00270     :param type_: message type (package resource name), ``str``
00271     :returns: list of import statements (no newline) required to use type_ from package, ``[str]``
00272     """
00273     # orig_base_type is the unresolved type
00274     orig_base_type = genmsg.msgs.bare_msg_type(type_) # strip array-suffix
00275     # resolve orig_base_type based on the current package context.
00276     # base_type is the resolved type stripped of any package name.
00277     # pkg is the actual package of type_.
00278     pkg, base_type = compute_pkg_type(package, orig_base_type)
00279     full_msg_type = "%s/%s"%(pkg, base_type) # compute fully-qualified type
00280     # important: have to do is_builtin check first. We do this check
00281     # against the unresolved type builtins/specials are never
00282     # relative. This requires some special handling for Header, which has
00283     # two names (Header and std_msgs/Header).
00284     if genmsg.msgs.is_builtin(orig_base_type) or \
00285            genmsg.msgs.is_header_type(orig_base_type):
00286         # of the builtin types, only special types require import
00287         # handling. we switch to base_type as special types do not
00288         # include package names.
00289         if is_special(base_type):
00290             retval = [get_special(base_type).import_str]
00291         else:
00292             retval = []
00293     elif not msg_context.is_registered(full_msg_type):
00294         retval = []
00295     else:
00296         retval = ['import %s.msg'%pkg]
00297         iter_types = get_registered_ex(msg_context, full_msg_type).types
00298         for t in iter_types:
00299             assert t != full_msg_type, "msg [%s] has circular self-dependencies"%(full_msg_type)
00300             full_sub_type = "%s/%s"%(package, t)
00301             log("compute_import", full_msg_type, package, t)
00302             sub = compute_import(msg_context, package, t)
00303             retval.extend([x for x in sub if not x in retval])
00304     return retval
00305 
00306 def compute_full_text_escaped(msg_context, spec):
00307     """
00308     Same as genmsg.compute_full_text, except that the
00309     resulting text is escaped to be safe for Python's triple-quote string
00310     quoting
00311 
00312     :param get_deps_dict: dictionary returned by load_dependencies call, ``dict``
00313     :returns: concatenated text for msg/srv file and embedded msg/srv types. Text will be escaped for triple-quote, ``str``
00314     """
00315     msg_definition = genmsg.compute_full_text(msg_context, spec)
00316     msg_definition.replace('"""', r'\"\"\"')
00317     return msg_definition
00318 
00319 ################################################################################
00320 # (De)serialization generators
00321 
00322 _serial_context = ''
00323 _context_stack = []
00324 
00325 _counter = 0
00326 def next_var():
00327     # we could optimize this by reusing vars once the context is popped
00328     global _counter
00329     _counter += 1
00330     return '_v%s'%_counter
00331 
00332 def reset_var():
00333     global _counter
00334     _counter = 0
00335 
00336 def push_context(context):
00337     """
00338     Push new variable context onto context stack.  The context stack
00339     manages field-reference context for serialization, e.g. 'self.foo'
00340     vs. 'self.bar.foo' vs. 'var.foo'
00341     """
00342     global _serial_context, _context_stack
00343     _context_stack.append(_serial_context)
00344     _serial_context = context
00345 
00346 def pop_context():
00347     """
00348     Pop variable context from context stack.  The context stack manages
00349     field-reference context for serialization, e.g. 'self.foo'
00350     vs. 'self.bar.foo' vs. 'var.foo'
00351     """
00352     global _serial_context
00353     _serial_context = _context_stack.pop()
00354 
00355 # These are the workhorses of the message generation. The generators
00356 # are implemented as iterators, where each iteration value is a line
00357 # of Python code. The generators will invoke underlying generators,
00358 # using the context stack to manage any changes in variable-naming, so
00359 # that code can be reused as much as possible.
00360 
00361 def len_serializer_generator(var, is_string, serialize):
00362     """
00363     Generator for array-length serialization (32-bit, little-endian unsigned integer)
00364     :param var: variable name, ``str``
00365     :param is_string: if True, variable is a string type, ``bool``
00366     :param serialize bool: if True, generate code for
00367       serialization. Other, generate code for deserialization, ``bool``
00368     """
00369     if serialize:
00370         yield "length = len(%s)"%var
00371         # NOTE: it's more difficult to save a call to struct.pack with
00372         # the array length as we are already using *array_val to pass
00373         # into struct.pack as *args. Although it's possible that
00374         # Python is not optimizing it, it is potentially worse for
00375         # performance to attempt to combine
00376         if not is_string:
00377             yield int32_pack("length")
00378     else:
00379         yield "start = end"
00380         yield "end += 4"
00381         yield int32_unpack('length', 'str[start:end]') #4 = struct.calcsize('<i')
00382 
00383 def string_serializer_generator(package, type_, name, serialize):
00384     """
00385     Generator for string types. similar to arrays, but with more
00386     efficient call to struct.pack.
00387 
00388     :param name: spec field name, ``str``
00389     :param serialize: if ``True``, generate code for
00390       serialization. Other, generate code for deserialization, ``bool``
00391     """
00392     # don't optimize in deserialization case as assignment doesn't
00393     # work
00394     if _serial_context and serialize:
00395         # optimize as string serialization accesses field twice
00396         yield "_x = %s%s"%(_serial_context, name)
00397         var = "_x"
00398     else:
00399         var = _serial_context+name
00400 
00401     # the length generator is a noop if serialize is True as we
00402     # optimize the serialization call.
00403     base_type, is_array, array_len = genmsg.msgs.parse_type(type_)
00404     # - don't serialize length for fixed-length arrays of bytes
00405     if base_type not in ['uint8', 'char'] or array_len is None:
00406         for y in len_serializer_generator(var, True, serialize):
00407             yield y #serialize string length
00408 
00409     if serialize:
00410         #serialize length and string together
00411 
00412         #check to see if its a uint8/byte type, in which case we need to convert to string before serializing
00413         base_type, is_array, array_len = genmsg.msgs.parse_type(type_)
00414         if base_type in ['uint8', 'char']:
00415             yield "# - if encoded as a list instead, serialize as bytes instead of string"
00416             if array_len is None:
00417                 yield "if type(%s) in [list, tuple]:"%var
00418                 yield INDENT+pack2("'<I%sB'%length", "length, *%s"%var)
00419                 yield "else:"
00420                 yield INDENT+pack2("'<I%ss'%length", "length, %s"%var)
00421             else:
00422                 yield "if type(%s) in [list, tuple]:"%var
00423                 yield INDENT+pack('%sB'%array_len, "*%s"%var)
00424                 yield "else:"
00425                 yield INDENT+pack('%ss'%array_len, var)
00426         else:
00427             # FIXME: for py3k, this needs to be w/ encode(), but this interferes with actual byte data
00428             yield "if python3 or type(%s) == unicode:"%(var)
00429             yield INDENT+"%s = %s.encode('utf-8')"%(var,var) #For unicode-strings in Python2, encode using utf-8
00430             yield INDENT+"length = len(%s)"%(var) # Update the length after utf-8 conversion
00431 
00432             yield "if python3:"
00433             yield INDENT+pack2("'<I%sB'%length", "length, *%s"%var)
00434             yield "else:"
00435             yield INDENT+pack2("'<I%ss'%length", "length, %s"%var)
00436     else:
00437         yield "start = end"
00438         if array_len is not None:
00439             yield "end += %s" % array_len
00440             yield "%s = str[start:end]" % var
00441         else:
00442             yield "end += length"
00443             if base_type in ['uint8', 'char']:
00444                 yield "%s = str[start:end]" % (var)
00445             else:
00446                 yield "if python3:"
00447                 yield INDENT+"%s = str[start:end].decode('utf-8')" % (var) #If messages are python3-decode back to unicode
00448                 yield "else:"
00449                 yield INDENT+"%s = str[start:end]" % (var)
00450 
00451 
00452 def array_serializer_generator(msg_context, package, type_, name, serialize, is_numpy):
00453     """
00454     Generator for array types
00455 
00456     :raises: :exc:`MsgGenerationException` If array spec is invalid
00457     """
00458     base_type, is_array, array_len = genmsg.msgs.parse_type(type_)
00459     if not is_array:
00460         raise MsgGenerationException("Invalid array spec: %s"%type_)
00461     var_length = array_len is None
00462 
00463     # handle fixed-size byte arrays could be slightly more efficient
00464     # as we recalculated the length in the generated code.
00465     if base_type in ['char', 'uint8']: #treat unsigned int8 arrays as string type
00466         for y in string_serializer_generator(package, type_, name, serialize):
00467             yield y
00468         return
00469 
00470     var = _serial_context+name
00471     # yield length serialization, if necessary
00472     if var_length:
00473         for y in len_serializer_generator(var, False, serialize):
00474             yield y #serialize array length
00475         length = None
00476     else:
00477         length = array_len
00478 
00479     #optimization for simple arrays
00480     if is_simple(base_type):
00481         if var_length:
00482             pattern = compute_struct_pattern([base_type])
00483             yield "pattern = '<%%s%s'%%length"%pattern
00484             if serialize:
00485                 if is_numpy:
00486                     yield pack_numpy(var)
00487                 else:
00488                     yield pack2('pattern', "*"+var)
00489             else:
00490                 yield "start = end"
00491                 yield "end += struct.calcsize(pattern)"
00492                 if is_numpy:
00493                     dtype = NUMPY_DTYPE[base_type]
00494                     yield unpack_numpy(var, 'length', dtype, 'str[start:end]')
00495                 else:
00496                     yield unpack2(var, 'pattern', 'str[start:end]')
00497         else:
00498             pattern = "%s%s"%(length, compute_struct_pattern([base_type]))
00499             if serialize:
00500                 if is_numpy:
00501                     yield pack_numpy(var)
00502                 else:
00503                     yield pack(pattern, "*"+var)
00504             else:
00505                 yield "start = end"
00506                 yield "end += %s"%struct.calcsize('<%s'%pattern)
00507                 if is_numpy:
00508                     dtype = NUMPY_DTYPE[base_type]
00509                     yield unpack_numpy(var, length, dtype, 'str[start:end]')
00510                 else:
00511                     yield unpack(var, pattern, 'str[start:end]')
00512         if not serialize and base_type == 'bool':
00513             # convert uint8 to bool
00514             if base_type == 'bool':
00515                 yield "%s = map(bool, %s)"%(var, var)
00516 
00517     else:
00518         #generic recursive serializer
00519         #NOTE: this is functionally equivalent to the is_registered branch of complex_serializer_generator
00520 
00521         # choose a unique temporary variable for iterating
00522         loop_var = 'val%s'%len(_context_stack)
00523 
00524         # compute the variable context and factory to use
00525         if base_type == 'string':
00526             push_context('')
00527             factory = string_serializer_generator(package, base_type, loop_var, serialize)
00528         else:
00529             push_context('%s.'%loop_var)
00530             factory = serializer_generator(msg_context, get_registered_ex(msg_context, base_type), serialize, is_numpy)
00531 
00532         if serialize:
00533             yield 'for %s in %s:'%(loop_var, var)
00534         else:
00535             yield '%s = []'%var
00536             if var_length:
00537                 yield 'for i in range(0, length):'
00538             else:
00539                 yield 'for i in range(0, %s):'%length
00540             if base_type != 'string':
00541                 yield INDENT + '%s = %s'%(loop_var, compute_constructor(msg_context, package, base_type))
00542         for y in factory:
00543             yield INDENT + y
00544         if not serialize:
00545             yield INDENT + '%s.append(%s)'%(var, loop_var)
00546         pop_context()
00547 
00548 def complex_serializer_generator(msg_context, package, type_, name, serialize, is_numpy):
00549     """
00550     Generator for serializing complex type
00551 
00552     :param serialize: if True, generate serialization
00553       code. Otherwise, deserialization code. ``bool``
00554     :param is_numpy: if True, generate serializer code for numpy
00555       datatypes instead of Python lists, ``bool``
00556     :raises: MsgGenerationException If type is not a valid
00557     """
00558     # ordering of these statements is important as we mutate the type
00559     # string we are checking throughout. parse_type strips array
00560     # brackets, then we check for the 'complex' builtin types (string,
00561     # time, duration, Header), then we canonicalize it to an embedded
00562     # message type.
00563     _, is_array, _ = genmsg.msgs.parse_type(type_)
00564 
00565     #Array
00566     if is_array:
00567         for y in array_serializer_generator(msg_context, package, type_, name, serialize, is_numpy):
00568             yield y
00569     #Embedded Message
00570     elif type_ == 'string':
00571         for y in string_serializer_generator(package, type_, name, serialize):
00572             yield y
00573     else:
00574         if not is_special(type_):
00575             # canonicalize type
00576             pkg, base_type = compute_pkg_type(package, type_)
00577             type_ = "%s/%s"%(pkg, base_type)
00578         if msg_context.is_registered(type_):
00579             # descend data structure ####################
00580             ctx_var = next_var()
00581             yield "%s = %s"%(ctx_var, _serial_context+name)
00582             push_context(ctx_var+'.')
00583             # unoptimized code
00584             #push_context(_serial_context+name+'.')
00585             for y in serializer_generator(msg_context, get_registered_ex(msg_context, type_), serialize, is_numpy):
00586                 yield y #recurs on subtype
00587             pop_context()
00588         else:
00589             #Invalid
00590             raise MsgGenerationException("Unknown type: %s. Package context is %s"%(type_, package))
00591 
00592 def simple_serializer_generator(msg_context, spec, start, end, serialize): #primitives that can be handled with struct
00593     """
00594     Generator (de)serialization code for multiple fields from spec
00595 
00596     :param spec: :class:`genmsg.MsgSpec`
00597     :param start: first field to serialize, ``int``
00598     :param end: last field to serialize, ``int``
00599     """
00600     # optimize member var access
00601     if end - start > 1 and _serial_context.endswith('.'):
00602         yield '_x = '+_serial_context[:-1]
00603         vars_ = '_x.' + (', _x.').join(spec.names[start:end])
00604     else:
00605         vars_ = _serial_context + (', '+_serial_context).join(spec.names[start:end])
00606 
00607     pattern = compute_struct_pattern(spec.types[start:end])
00608     if serialize:
00609         yield pack(pattern, vars_)
00610     else:
00611         yield "start = end"
00612         yield "end += %s"%struct.calcsize('<%s'%reduce_pattern(pattern))
00613         yield unpack('(%s,)'%vars_, pattern, 'str[start:end]')
00614 
00615         # convert uint8 to bool. this doesn't add much value as Python
00616         # equality test on a field will return that True == 1, but I
00617         # want to be consistent with bool
00618         bool_vars = [(f, t) for f, t in zip(spec.names[start:end], spec.types[start:end]) if t == 'bool']
00619         for f, t in bool_vars:
00620             #TODO: could optimize this as well
00621             var = _serial_context+f
00622             yield "%s = bool(%s)"%(var, var)
00623 
00624 def serializer_generator(msg_context, spec, serialize, is_numpy):
00625     """
00626     Python generator that yields un-indented python code for
00627     (de)serializing MsgSpec. The code this yields is meant to be
00628     included in a class method and cannot be used
00629     standalone. serialize_fn_generator and deserialize_fn_generator
00630     wrap method to provide appropriate class field initializations.
00631 
00632     :param serialize: if True, yield serialization
00633       code. Otherwise, yield deserialization code. ``bool``
00634     :param is_numpy: if True, generate serializer code for numpy datatypes instead of Python lists. ``bool``
00635     """
00636     # Break spec into chunks of simple (primitives) vs. complex (arrays, etc...)
00637     # Simple types are batch serialized using the python struct module.
00638     # Complex types are individually serialized
00639     if spec is None:
00640         raise MsgGenerationException("spec is none")
00641     names, types = spec.names, spec.types
00642     if serialize and not len(names): #Empty
00643         yield "pass"
00644         return
00645 
00646     _max_chunk = 255
00647     # iterate through types. whenever we encounter a non-simple type,
00648     # yield serializer for any simple types we've encountered until
00649     # then, then yield the complex type serializer
00650     curr = 0
00651     for (i, full_type) in enumerate(types):
00652         if not is_simple(full_type):
00653             if i != curr: #yield chunk of simples
00654                 for _start in range(curr, i, _max_chunk):
00655                     _end = min(_start + _max_chunk, i)
00656                     for y in simple_serializer_generator(msg_context, spec, _start, _end, serialize):
00657                         yield y
00658             curr = i+1
00659             for y in complex_serializer_generator(msg_context, spec.package, full_type, names[i], serialize, is_numpy):
00660                 yield y
00661     if curr < len(types): #yield rest of simples
00662         for _start in range(curr, len(types), _max_chunk):
00663             _end = min(_start + _max_chunk, len(types))
00664             for y in simple_serializer_generator(msg_context, spec, _start, _end, serialize):
00665                 yield y
00666 
00667 def serialize_fn_generator(msg_context, spec, is_numpy=False):
00668     """
00669     generator for body of serialize() function
00670     :param is_numpy: if True, generate serializer code for numpy
00671       datatypes instead of Python lists, ``bool``
00672     """
00673     # method-var context #########
00674     yield "try:"
00675     push_context('self.')
00676     #NOTE: we flatten the spec for optimal serialization
00677     # #3741: make sure to have sub-messages python safe
00678     flattened = make_python_safe(flatten(msg_context, spec))
00679     for y in serializer_generator(msg_context, flattened, True, is_numpy):
00680         yield "  "+y
00681     pop_context()
00682     yield "except struct.error as se: self._check_types(struct.error(\"%s: '%s' when writing '%s'\" % (type(se), str(se), str(_x))))"
00683     yield "except TypeError as te: self._check_types(ValueError(\"%s: '%s' when writing '%s'\" % (type(te), str(te), str(_x))))"
00684     # done w/ method-var context #
00685 
00686 def deserialize_fn_generator(msg_context, spec, is_numpy=False):
00687     """
00688     generator for body of deserialize() function
00689     :param is_numpy: if True, generate serializer code for numpy
00690       datatypes instead of Python lists, ``bool``
00691     """
00692     yield "try:"
00693     package = spec.package
00694     #Instantiate embedded type classes
00695     for type_, name in spec.fields():
00696         if msg_context.is_registered(type_):
00697             yield "  if self.%s is None:"%name
00698             yield "    self.%s = %s"%(name, compute_constructor(msg_context, package, type_))
00699     yield "  end = 0" #initialize var
00700 
00701     # method-var context #########
00702     push_context('self.')
00703     #NOTE: we flatten the spec for optimal serialization
00704     # #3741: make sure to have sub-messages python safe
00705     flattened = make_python_safe(flatten(msg_context, spec))
00706     for y in serializer_generator(msg_context, flattened, False, is_numpy):
00707         yield "  "+y
00708     pop_context()
00709     # done w/ method-var context #
00710 
00711     # generate post-deserialization code
00712     for type_, name in spec.fields():
00713         code = compute_post_deserialize(type_, "self.%s"%name)
00714         if code:
00715             yield "  %s"%code
00716 
00717     yield "  return self"
00718     yield "except struct.error as e:"
00719     yield "  raise genpy.DeserializationError(e) #most likely buffer underfill"
00720 
00721 def msg_generator(msg_context, spec, search_path):
00722     """
00723     Python code generator for .msg files. Generates a Python from a
00724      :class:`genmsg.MsgSpec`.
00725 
00726     :param spec: parsed .msg :class:`genmsg.MsgSpec` instance
00727     :param search_path: dictionary mapping message namespaces to a directory locations
00728     """
00729 
00730     # #2990: have to compute md5sum before any calls to make_python_safe
00731 
00732     # generate dependencies dictionary. omit files calculation as we
00733     # rely on in-memory MsgSpecs instead so that we can generate code
00734     # for older versions of msg files
00735     try:
00736         genmsg.msg_loader.load_depends(msg_context, spec, search_path)
00737     except InvalidMsgSpec as e:
00738         raise MsgGenerationException("Cannot generate .msg for %s/%s: %s"%(package, name, str(e)))
00739     md5sum = genmsg.compute_md5(msg_context, spec)
00740 
00741     # remap spec names to be Python-safe
00742     spec = make_python_safe(spec)
00743     spec_names = spec.names
00744 
00745     # #1807 : this will be much cleaner when msggenerator library is
00746     # rewritten to not use globals
00747     clear_patterns()
00748 
00749     yield '"""autogenerated by genpy from %s.msg. Do not edit."""'%spec.full_name
00750     yield 'import sys'
00751     yield 'python3 = True if sys.hexversion > 0x03000000 else False'
00752     yield 'import genpy\nimport struct\n'
00753     import_strs = []
00754     for t in spec.types:
00755         import_strs.extend(compute_import(msg_context, spec.package, t))
00756     import_strs = set(import_strs)
00757     for i in import_strs:
00758         if i:
00759             yield i
00760 
00761     yield ''
00762 
00763     fulltype = spec.full_name
00764     name = spec.short_name
00765 
00766     #Yield data class first, e.g. Point2D
00767     yield 'class %s(genpy.Message):'%spec.short_name
00768     yield '  _md5sum = "%s"'%(md5sum)
00769     yield '  _type = "%s"'%(fulltype)
00770     yield '  _has_header = %s #flag to mark the presence of a Header object'%spec.has_header()
00771     # note: we introduce an extra newline to protect the escaping from quotes in the message
00772     yield '  _full_text = """%s\n"""'%compute_full_text_escaped(msg_context, spec)
00773 
00774     if spec.constants:
00775         yield '  # Pseudo-constants'
00776         for c in spec.constants:
00777             if c.type == 'string':
00778                 val = c.val
00779                 if '"' in val and "'" in val:
00780                     # crude escaping of \ and "
00781                     escaped = c.val.replace('\\', '\\\\')
00782                     escaped = escaped.replace('\"', '\\"')
00783                     yield '  %s = "%s"'%(c.name, escaped)
00784                 elif '"' in val: #use raw encoding for prettiness
00785                     yield "  %s = r'%s'"%(c.name, val)
00786                 elif "'" in val: #use raw encoding for prettiness
00787                     yield '  %s = r"%s"'%(c.name, val)
00788                 else:
00789                     yield "  %s = '%s'"%(c.name, val)
00790             else:
00791                 yield '  %s = %s'%(c.name, c.val)
00792         yield ''
00793 
00794     if len(spec_names):
00795         yield "  __slots__ = ['"+"','".join(spec_names)+"']"
00796         yield "  _slot_types = ['"+"','".join(spec.types)+"']"
00797     else:
00798         yield "  __slots__ = []"
00799         yield "  _slot_types = []"
00800 
00801     yield """
00802   def __init__(self, *args, **kwds):
00803     \"\"\"
00804     Constructor. Any message fields that are implicitly/explicitly
00805     set to None will be assigned a default value. The recommend
00806     use is keyword arguments as this is more robust to future message
00807     changes.  You cannot mix in-order arguments and keyword arguments.
00808 
00809     The available fields are:
00810        %s
00811 
00812     :param args: complete set of field values, in .msg order
00813     :param kwds: use keyword arguments corresponding to message field names
00814     to set specific fields.
00815     \"\"\"
00816     if args or kwds:
00817       super(%s, self).__init__(*args, **kwds)"""%(','.join(spec_names), name)
00818 
00819     if len(spec_names):
00820         yield "      #message fields cannot be None, assign default values for those that are"
00821         for (t, s) in zip(spec.types, spec_names):
00822             yield "      if self.%s is None:"%s
00823             yield "        self.%s = %s"%(s, default_value(msg_context, t, spec.package))
00824     if len(spec_names) > 0:
00825       yield "    else:"
00826       for (t, s) in zip(spec.types, spec_names):
00827           yield "      self.%s = %s"%(s, default_value(msg_context, t, spec.package))
00828 
00829     yield """
00830   def _get_types(self):
00831     \"\"\"
00832     internal API method
00833     \"\"\"
00834     return self._slot_types
00835 
00836   def serialize(self, buff):
00837     \"\"\"
00838     serialize message into buffer
00839     :param buff: buffer, ``StringIO``
00840     \"\"\""""
00841     for y in serialize_fn_generator(msg_context, spec):
00842         yield "    "+ y
00843     yield """
00844   def deserialize(self, str):
00845     \"\"\"
00846     unpack serialized message in str into this message instance
00847     :param str: byte array of serialized message, ``str``
00848     \"\"\""""
00849     for y in deserialize_fn_generator(msg_context, spec):
00850         yield "    " + y
00851     yield ""
00852 
00853     yield """
00854   def serialize_numpy(self, buff, numpy):
00855     \"\"\"
00856     serialize message with numpy array types into buffer
00857     :param buff: buffer, ``StringIO``
00858     :param numpy: numpy python module
00859     \"\"\""""
00860     for y in serialize_fn_generator(msg_context, spec, is_numpy=True):
00861         yield "    "+ y
00862     yield """
00863   def deserialize_numpy(self, str, numpy):
00864     \"\"\"
00865     unpack serialized message in str into this message instance using numpy for array types
00866     :param str: byte array of serialized message, ``str``
00867     :param numpy: numpy python module
00868     \"\"\""""
00869     for y in deserialize_fn_generator(msg_context, spec, is_numpy=True):
00870         yield "    " + y
00871     yield ""
00872 
00873 
00874     # #1807 : this will be much cleaner when msggenerator library is
00875     # rewritten to not use globals
00876     yield '_struct_I = genpy.struct_I'
00877     patterns = get_patterns()
00878     for p in set(patterns):
00879         # I patterns are already optimized
00880         if p == 'I':
00881             continue
00882         var_name = '_struct_%s'%(p.replace('<',''))
00883         yield '%s = struct.Struct("<%s")'%(var_name, p)
00884     clear_patterns()
00885 
00886 def srv_generator(msg_context, spec, search_path):
00887     for mspec in (spec.request, spec.response):
00888         for l in msg_generator(msg_context, mspec, search_path):
00889             yield l
00890 
00891     name = spec.short_name
00892     req, resp = ["%s%s"%(name, suff) for suff in ['Request', 'Response']]
00893 
00894     fulltype = spec.full_name
00895 
00896     genmsg.msg_loader.load_depends(msg_context, spec, search_path)
00897     md5 = genmsg.compute_md5(msg_context, spec)
00898 
00899     yield "class %s(object):"%name
00900     yield "  _type          = '%s'"%fulltype
00901     yield "  _md5sum = '%s'"%md5
00902     yield "  _request_class  = %s"%req
00903     yield "  _response_class = %s"%resp
00904 
00905 def _module_name(type_name):
00906     """
00907     :param type_name str: Name of message type sans package,
00908       e.g. 'String'
00909     :returns str: name of python module for auto-generated code
00910     """
00911     return "_"+type_name
00912 
00913 def compute_resource_name(filename, ext):
00914     """
00915     Convert resource filename to ROS resource name
00916     :param filename str: path to .msg/.srv file
00917     :returns str: name of ROS resource
00918     """
00919     return os.path.basename(filename)[:-len(ext)]
00920 
00921 def compute_outfile_name(outdir, infile_name, ext):
00922     """
00923     :param outdir str: path to directory that files are generated to
00924     :returns str: output file path based on input file name and output directory
00925     """
00926     # Use leading _ so that module name does not collide with message name. It also
00927     # makes it more clear that the .py file should not be imported directly
00928     return os.path.join(outdir, _module_name(compute_resource_name(infile_name, ext))+".py")
00929 
00930 class Generator(object):
00931 
00932     def __init__(self, what, ext, spec_loader_fn, generator_fn):
00933         self.what = what
00934         self.ext = ext
00935         self.spec_loader_fn = spec_loader_fn
00936         self.generator_fn = generator_fn
00937 
00938     def firos_generate(self, msg_context, full_type, topic_data, outdir, search_path):
00939         # generate message files for request/response
00940         formatted_msg = ""
00941         for data in topic_data['msg']:
00942             formatted_msg += str(topic_data['msg'][data]) + " " + str(data) + "\n"
00943         formatted_msg = formatted_msg[:-1]
00944         spec = self.spec_loader_fn(msg_context, formatted_msg, full_type)
00945         file_name = full_type.replace("/", "")
00946         outfile = os.path.join(outdir, file_name + ".py")
00947 
00948         print("Generating message definition for " + full_type)
00949 
00950         with open(outfile, 'w') as f:
00951             for l in self.generator_fn(msg_context, spec, search_path):
00952                 f.write(l+'\n')
00953             f.close()
00954         return outfile
00955 
00956     def generate(self, msg_context, full_type, f, outdir, search_path):
00957         try:
00958             # you can't just check first... race condition
00959             os.makedirs(outdir)
00960         except OSError as e:
00961             if e.errno != 17: # file exists
00962                 raise
00963         # generate message files for request/response
00964         spec = self.spec_loader_fn(msg_context, f, full_type)
00965         outfile = compute_outfile_name(outdir, os.path.basename(f), self.ext)
00966         with open(outfile, 'w') as f:
00967             for l in self.generator_fn(msg_context, spec, search_path):
00968                 f.write(l+'\n')
00969         return outfile
00970 
00971     def generate_firos_messages(self, package, data, outdir, OUTPUT, search_path):
00972         """
00973         :returns: return code, ``int``
00974         """
00975         if not genmsg.is_legal_resource_base_name(package):
00976             raise MsgGenerationException("\nERROR: package name '%s' is illegal and cannot be used in message generation.\nPlease see http://ros.org/wiki/Names"%(package))
00977 
00978         # package/src/package/msg for messages, packages/src/package/srv for services
00979         msg_context = MsgContext.create_default()
00980         retcode = 0
00981         try:
00982             # you can't just check first... race condition
00983             outdir = outdir + OUTPUT
00984             os.makedirs(outdir)
00985             f = open(os.path.join(outdir, "__init__.py"), 'w')
00986             f.close()
00987         except OSError as e:
00988             if e.errno != 17: # file exists
00989                 raise
00990 
00991         for robotName in data:
00992             try:
00993                 robot = data[robotName]
00994                 for topic_name in robot['topics']:
00995                     topic = robot['topics'][topic_name]
00996                     full_type = str(robotName) + '/' + str(topic_name)
00997                     if type(topic['msg']) is dict:
00998                         self.firos_generate(msg_context, full_type, topic, outdir, search_path) #actual generation
00999             except Exception as e:
01000                 if not isinstance(e, MsgGenerationException) and not isinstance(e, genmsg.msgs.InvalidMsgSpec):
01001                     traceback.print_exc()
01002                 print("\nERROR: Unable to generate %s for package '%s': %s\n"%(self.what, package, e), file=sys.stderr)
01003                 retcode = 1 #flag error
01004         return retcode
01005 
01006     def generate_messages(self, package, package_files, outdir, search_path):
01007         """
01008         :returns: return code, ``int``
01009         """
01010         if not genmsg.is_legal_resource_base_name(package):
01011             raise MsgGenerationException("\nERROR: package name '%s' is illegal and cannot be used in message generation.\nPlease see http://ros.org/wiki/Names"%(package))
01012 
01013         # package/src/package/msg for messages, packages/src/package/srv for services
01014         msg_context = MsgContext.create_default()
01015         retcode = 0
01016         for f in package_files:
01017             try:
01018                 f = os.path.abspath(f)
01019                 infile_name = os.path.basename(f)
01020                 full_type = genmsg.gentools.compute_full_type_name(package, infile_name);
01021                 outfile = self.generate(msg_context, full_type, f, outdir, search_path) #actual generation
01022             except Exception as e:
01023                 if not isinstance(e, MsgGenerationException) and not isinstance(e, genmsg.msgs.InvalidMsgSpec):
01024                     traceback.print_exc()
01025                 print("\nERROR: Unable to generate %s for package '%s': while processing '%s': %s\n"%(self.what, package, f, e), file=sys.stderr)
01026                 retcode = 1 #flag error
01027         return retcode
01028 
01029 class SrvGenerator(Generator):
01030 
01031     def __init__(self):
01032         super(SrvGenerator, self).__init__('services', genmsg.EXT_SRV,
01033                                            genmsg.msg_loader.load_srv_from_file,
01034                                            srv_generator)
01035 
01036 class MsgGenerator(Generator):
01037     """
01038     Generates Python message code for all messages in a
01039     package. See genutil.Generator. In order to generator code for a
01040     single .msg file, see msg_generator.
01041     """
01042     def __init__(self, loader=genmsg.msg_loader.load_msg_from_file):
01043         super(MsgGenerator, self).__init__('messages', genmsg.EXT_MSG,
01044                                            loader,
01045                                            msg_generator)
01046 


firos
Author(s): IƱigo Gonzalez, igonzalez@ikergune.com
autogenerated on Thu Jun 6 2019 17:51:04