Package roslib :: Module genpy
[frames] | no frames]

Source Code for Module roslib.genpy

   1  # Software License Agreement (BSD License) 
   2  # 
   3  # Copyright (c) 2008, Willow Garage, Inc. 
   4  # All rights reserved. 
   5  # 
   6  # Redistribution and use in source and binary forms, with or without 
   7  # modification, are permitted provided that the following conditions 
   8  # are met: 
   9  # 
  10  #  * Redistributions of source code must retain the above copyright 
  11  #    notice, this list of conditions and the following disclaimer. 
  12  #  * Redistributions in binary form must reproduce the above 
  13  #    copyright notice, this list of conditions and the following 
  14  #    disclaimer in the documentation and/or other materials provided 
  15  #    with the distribution. 
  16  #  * Neither the name of Willow Garage, Inc. nor the names of its 
  17  #    contributors may be used to endorse or promote products derived 
  18  #    from this software without specific prior written permission. 
  19  # 
  20  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
  21  # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
  22  # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
  23  # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
  24  # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
  25  # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
  26  # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
  27  # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
  28  # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
  29  # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
  30  # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
  31  # POSSIBILITY OF SUCH DAMAGE. 
  32  # 
  33  # Revision $Id: genpy.py 15930 2012-01-12 00:38:31Z kwc $ 
  34   
  35  """ 
  36  Library for Python message generation. 
  37   
  38  The structure of the serialization descends several levels of serializers: 
  39   - msg_generator: generator for an individual msg file 
  40    - serialize_fn_generator: generator for msg.serialize() 
  41      - serializer_generator 
  42        - field-type-specific serializers 
  43    - deserialize_fn_generator: generator for msg.deserialize() 
  44      - serializer_generator 
  45        - field-type-specific serializers 
  46  """ 
  47   
  48  # NOTE: genpy must be in the roslib package as placing it in rospy 
  49  # creates circular deps 
  50   
  51  import os 
  52  import keyword 
  53  import shutil 
  54  import atexit 
  55  import itertools 
  56  import sys 
  57  import tempfile 
  58  import traceback 
  59  import struct 
  60   
  61  try: 
  62      from cStringIO import StringIO # Python 2.x 
  63  except ImportError: 
  64      from io import StringIO # Python 3.x 
  65   
  66  import roslib.exceptions 
  67  import roslib.gentools 
  68  import roslib.msgs 
  69  import roslib.packages #for get_pkg_dir 
  70   
  71  # indent width 
  72  INDENT = '  ' 
  73   
74 -class MsgGenerationException(roslib.exceptions.ROSLibException):
75 """ 76 Exception type for errors in roslib.genpy 77 """ 78 pass
79
80 -def get_registered_ex(type_):
81 """ 82 wrapper for roslib.msgs.get_registered that wraps unknown types with a MsgGenerationException 83 @param type_: ROS message type 84 @type type_: str 85 """ 86 try: 87 return roslib.msgs.get_registered(type_) 88 except KeyError: 89 raise MsgGenerationException("Unknown type [%s]. Please check that the manifest.xml correctly declares dependencies."%type_)
90 91 ################################################################################ 92 # Primitive type handling for ROS builtin types 93 94 SIMPLE_TYPES_DICT = { #see python module struct 95 'int8': 'b', 96 'uint8': 'B', 97 # Python 2.6 adds in '?' for C99 _Bool, which appears equivalent to an uint8, 98 # thus, we use uint8 99 'bool': 'B', 100 'int16' : 'h', 101 'uint16' : 'H', 102 'int32' : 'i', 103 'uint32' : 'I', 104 'int64' : 'q', 105 'uint64' : 'Q', 106 'float32': 'f', 107 'float64': 'd', 108 # deprecated 109 'char' : 'B', #unsigned 110 'byte' : 'b', #signed 111 } 112 113 ## Simple types are primitives with fixed-serialization length 114 SIMPLE_TYPES = list(SIMPLE_TYPES_DICT.keys()) #py3k 115
116 -def is_simple(type_):
117 """ 118 @return bool: True if type is a 'simple' type, i.e. is of 119 fixed/known serialization length. This is effectively all primitive 120 types except for string 121 @rtype: bool 122 """ 123 return type_ in SIMPLE_TYPES
124
125 -def is_special(type_):
126 """ 127 @return True if type_ is a special type (i.e. builtin represented as a class instead of a primitive) 128 @rtype: bool 129 """ 130 return type_ in _SPECIAL_TYPES
131
132 -def get_special(type_):
133 """ 134 @return: special type handler for type_ or None 135 @rtype: L{Special} 136 """ 137 return _SPECIAL_TYPES.get(type_, None)
138 139 ################################################################################ 140 # Special type handling for ROS builtin types that are not primitives 141
142 -class Special:
143
144 - def __init__(self, constructor, post_deserialize, import_str):
145 """ 146 @param constructor: expression to instantiate new type instance for deserialization 147 @type constructor: str 148 @param post_Deserialize: format string for expression to evaluate on type instance after deserialization is complete. 149 @type post_Deserialize: str 150 variable name will be passed in as the single argument to format string. 151 @param import_str: import to include if type is present 152 @type import_str: str 153 """ 154 self.constructor = constructor 155 self.post_deserialize = post_deserialize 156 self.import_str = import_str
157
158 - def get_post_deserialize(self, varname):
159 """ 160 @return: Post-deserialization code to executed (unindented) or 161 None if no post-deserialization is required 162 @rtype: str 163 """ 164 if self.post_deserialize: 165 return self.post_deserialize%varname 166 else: 167 return None
168 169 _SPECIAL_TYPES = { 170 roslib.msgs.HEADER: Special('std_msgs.msg._Header.Header()', None, 'import std_msgs.msg'), 171 roslib.msgs.TIME: Special('roslib.rostime.Time()', '%s.canon()', 'import roslib.rostime'), 172 roslib.msgs.DURATION: Special('roslib.rostime.Duration()', '%s.canon()', 'import roslib.rostime'), 173 } 174 175 ################################################################################ 176 # utilities 177 178 # #671
179 -def default_value(field_type, default_package):
180 """ 181 Compute default value for field_type 182 @param default_package: default package 183 @type default_package: str 184 @param field_type str: ROS .msg field type 185 @type field_type: ROS .msg field type 186 @return: default value encoded in Python string representation 187 @rtype: str 188 """ 189 if field_type in ['byte', 'int8', 'int16', 'int32', 'int64',\ 190 'char', 'uint8', 'uint16', 'uint32', 'uint64']: 191 return '0' 192 elif field_type in ['float32', 'float64']: 193 return '0.' 194 elif field_type == 'string': 195 # strings, char[], and uint8s are all optimized to be strings 196 return "''" 197 elif field_type == 'bool': 198 return 'False' 199 elif field_type.endswith(']'): # array type 200 base_type, is_array, array_len = roslib.msgs.parse_type(field_type) 201 if base_type in ['char', 'uint8']: 202 # strings, char[], and uint8s are all optimized to be strings 203 if array_len is not None: 204 return "chr(0)*%s"%array_len 205 else: 206 return "''" 207 elif array_len is None: #var-length 208 return '[]' 209 else: # fixed-length, fill values 210 def_val = default_value(base_type, default_package) 211 return '[' + ','.join(itertools.repeat(def_val, array_len)) + ']' 212 else: 213 return compute_constructor(default_package, field_type)
214
215 -def flatten(msg):
216 """ 217 Flattens the msg spec so that embedded message fields become 218 direct references. The resulting MsgSpec isn't a true/legal 219 L{MsgSpec} and should only be used for serializer generation 220 @param msg: msg to flatten 221 @type msg: L{MsgSpec} 222 @return: flatten message 223 @rtype: L{MsgSpec} 224 """ 225 new_types = [] 226 new_names = [] 227 for t, n in zip(msg.types, msg.names): 228 #flatten embedded types - note: bug #59 229 if roslib.msgs.is_registered(t): 230 msg_spec = flatten(roslib.msgs.get_registered(t)) 231 new_types.extend(msg_spec.types) 232 for n2 in msg_spec.names: 233 new_names.append(n+'.'+n2) 234 else: 235 #I'm not sure if it's a performance win to flatten fixed-length arrays 236 #as you get n __getitems__ method calls vs. a single *array call 237 new_types.append(t) 238 new_names.append(n) 239 return roslib.msgs.MsgSpec(new_types, new_names, msg.constants, msg.text)
240
241 -def make_python_safe(spec):
242 """ 243 Remap field/constant names in spec to avoid collision with Python reserved words. 244 @param spec: msg spec to map to new, python-safe field names 245 @type spec: L{MsgSpec} 246 @return: python-safe message specification 247 @rtype: L{MsgSpec} 248 """ 249 new_c = [roslib.msgs.Constant(c.type, _remap_reserved(c.name), c.val, c.val_text) for c in spec.constants] 250 return roslib.msgs.MsgSpec(spec.types, [_remap_reserved(n) for n in spec.names], new_c, spec.text)
251
252 -def _remap_reserved(field_name):
253 """ 254 Map field_name to a python-safe representation, if necessary 255 @param field_name: msg field name 256 @type field_name: str 257 @return: remapped name 258 @rtype: str 259 """ 260 # include 'self' as well because we are within a class instance 261 if field_name in keyword.kwlist + ['self']: 262 return field_name + "_" 263 return field_name
264 265 ################################################################################ 266 # (de)serialization routines 267
268 -def compute_struct_pattern(types):
269 """ 270 @param types: type names 271 @type types: [str] 272 @return: format string for struct if types are all simple. Otherwise, return None 273 @rtype: str 274 """ 275 if not types: #important to filter None and empty first 276 return None 277 try: 278 return ''.join([SIMPLE_TYPES_DICT[t] for t in types]) 279 except: 280 return None
281
282 -def compute_post_deserialize(type_, varname):
283 """ 284 Compute post-deserialization code for type_, if necessary 285 @return: code to execute post-deserialization (unindented), or None if not necessary. 286 @rtype: str 287 """ 288 s = get_special(type_) 289 if s is not None: 290 return s.get_post_deserialize(varname)
291
292 -def compute_constructor(package, type_):
293 """ 294 Compute python constructor expression for specified message type implementation 295 @param package str: package that type is being imported into. Used 296 to resolve type_ if package is not specified. 297 @type package: str 298 @param type_: message type 299 @type type_: str 300 """ 301 if is_special(type_): 302 return get_special(type_).constructor 303 else: 304 base_pkg, base_type_ = compute_pkg_type(package, type_) 305 if not roslib.msgs.is_registered("%s/%s"%(base_pkg,base_type_)): 306 return None 307 else: 308 return '%s.msg.%s()'%(base_pkg, base_type_)
309
310 -def compute_pkg_type(package, type_):
311 """ 312 @param package: package that type is being imported into 313 @type package: str 314 @param type: message type (package resource name) 315 @type type: str 316 @return (str, str): python package and type name 317 """ 318 splits = type_.split(roslib.msgs.SEP) 319 if len(splits) == 1: 320 return package, splits[0] 321 elif len(splits) == 2: 322 return tuple(splits) 323 else: 324 raise MsgGenerationException("illegal message type: %s"%type_)
325
326 -def compute_import(package, type_):
327 """ 328 Compute python import statement for specified message type implementation 329 @param package: package that type is being imported into 330 @type package: str 331 @param type_: message type (package resource name) 332 @type type_: str 333 @return: list of import statements (no newline) required to use type_ from package 334 @rtype: [str] 335 """ 336 # orig_base_type is the unresolved type 337 orig_base_type = roslib.msgs.base_msg_type(type_) # strip array-suffix 338 # resolve orig_base_type based on the current package context. 339 # base_type is the resolved type stripped of any package name. 340 # pkg is the actual package of type_. 341 pkg, base_type = compute_pkg_type(package, orig_base_type) 342 type_str = "%s/%s"%(pkg, base_type) # compute fully-qualified type 343 # important: have to do is_builtin check first. We do this check 344 # against the unresolved type builtins/specials are never 345 # relative. This requires some special handling for Header, which has 346 # two names (Header and std_msgs/Header). 347 if roslib.msgs.is_builtin(orig_base_type) or \ 348 roslib.msgs.is_header_type(orig_base_type): 349 # of the builtin types, only special types require import 350 # handling. we switch to base_type as special types do not 351 # include package names. 352 if is_special(base_type): 353 retval = [get_special(base_type).import_str] 354 else: 355 retval = [] 356 elif not roslib.msgs.is_registered(type_str): 357 retval = [] 358 else: 359 retval = ['import %s.msg'%pkg] 360 for t in get_registered_ex(type_str).types: 361 sub = compute_import(package, t) 362 retval.extend([x for x in sub if not x in retval]) 363 return retval
364
365 -def compute_full_text_escaped(gen_deps_dict):
366 """ 367 Same as roslib.gentools.compute_full_text, except that the 368 resulting text is escaped to be safe for Python's triple-quote string 369 quoting 370 371 @param get_deps_dict: dictionary returned by get_dependencies call 372 @type get_deps_dict: dict 373 @return: concatenated text for msg/srv file and embedded msg/srv types. Text will be escaped for triple-quote 374 @rtype: str 375 """ 376 msg_definition = roslib.gentools.compute_full_text(gen_deps_dict) 377 msg_definition.replace('"""', r'\"\"\"') 378 return msg_definition
379
380 -def reduce_pattern(pattern):
381 """ 382 Optimize the struct format pattern. 383 @param pattern: struct pattern 384 @type pattern: str 385 @return: optimized struct pattern 386 @rtype: str 387 """ 388 if not pattern or len(pattern) == 1 or '%' in pattern: 389 return pattern 390 prev = pattern[0] 391 count = 1 392 new_pattern = '' 393 nums = [str(i) for i in range(0, 9)] 394 for c in pattern[1:]: 395 if c == prev and not c in nums: 396 count += 1 397 else: 398 if count > 1: 399 new_pattern = new_pattern + str(count) + prev 400 else: 401 new_pattern = new_pattern + prev 402 prev = c 403 count = 1 404 if count > 1: 405 new_pattern = new_pattern + str(count) + c 406 else: 407 new_pattern = new_pattern + prev 408 return new_pattern
409 410 ## @param expr str: string python expression that is evaluated for serialization 411 ## @return str: python call to write value returned by expr to serialization buffer
412 -def serialize(expr):
413 return "buff.write(%s)"%expr
414 415 # int32 is very common due to length serialization, so it is special cased
416 -def int32_pack(var):
417 """ 418 @param var: variable name 419 @type var: str 420 @return: struct packing code for an int32 421 """ 422 return serialize('_struct_I.pack(%s)'%var)
423 424 # int32 is very common due to length serialization, so it is special cased
425 -def int32_unpack(var, buff):
426 """ 427 @param var: variable name 428 @type var: str 429 @return: struct unpacking code for an int32 430 """ 431 return '(%s,) = _struct_I.unpack(%s)'%(var, buff)
432 433 #NOTE: '<' = little endian
434 -def pack(pattern, vars):
435 """ 436 create struct.pack call for when pattern is a string pattern 437 @param pattern: pattern for pack 438 @type pattern: str 439 @param vars: name of variables to pack 440 @type vars: str 441 """ 442 # - store pattern in context 443 pattern = reduce_pattern(pattern) 444 add_pattern(pattern) 445 return serialize("_struct_%s.pack(%s)"%(pattern, vars))
446 -def pack2(pattern, vars):
447 """ 448 create struct.pack call for when pattern is the name of a variable 449 @param pattern: name of variable storing string pattern 450 @type pattern: struct 451 @param vars: name of variables to pack 452 @type vars: str 453 """ 454 return serialize("struct.pack(%s, %s)"%(pattern, vars))
455 -def unpack(var, pattern, buff):
456 """ 457 create struct.unpack call for when pattern is a string pattern 458 @param var: name of variable to unpack 459 @type var: str 460 @param pattern: pattern for pack 461 @type pattern: str 462 @param buff: buffer to unpack from 463 @type buff: str 464 """ 465 # - store pattern in context 466 pattern = reduce_pattern(pattern) 467 add_pattern(pattern) 468 return var + " = _struct_%s.unpack(%s)"%(pattern, buff)
469 -def unpack2(var, pattern, buff):
470 """ 471 Create struct.unpack call for when pattern refers to variable 472 @param var: variable the stores the result of unpack call 473 @type var: str 474 @param pattern: name of variable that unpack will read from 475 @type pattern: str 476 @param buff: buffer that the unpack reads from 477 @type buff: StringIO 478 """ 479 return "%s = struct.unpack(%s, %s)"%(var, pattern, buff)
480 481 ################################################################################ 482 # numpy support 483 484 # this could obviously be directly generated, but it's nice to abstract 485 486 ## maps ros msg types to numpy types 487 _NUMPY_DTYPE = { 488 'float32': 'numpy.float32', 489 'float64': 'numpy.float64', 490 'bool': 'numpy.bool', 491 'int8': 'numpy.int8', 492 'int16': 'numpy.int16', 493 'int32': 'numpy.int32', 494 'int64': 'numpy.int64', 495 'uint8': 'numpy.uint8', 496 'uint16': 'numpy.uint16', 497 'uint32': 'numpy.uint32', 498 'uint64': 'numpy.uint64', 499 # deprecated type 500 'char' : 'numpy.uint8', 501 'byte' : 'numpy.int8', 502 } 503 # TODO: this doesn't explicitly specify little-endian byte order on the numpy data instance
504 -def unpack_numpy(var, count, dtype, buff):
505 """ 506 create numpy deserialization code 507 """ 508 return var + " = numpy.frombuffer(%s, dtype=%s, count=%s)"%(buff, dtype, count)
509
510 -def pack_numpy(var):
511 """ 512 create numpy serialization code 513 @param vars: name of variables to pack 514 """ 515 return serialize("%s.tostring()"%var)
516 517 ################################################################################ 518 # (De)serialization generators 519 520 _serial_context = '' 521 _context_stack = [] 522 523 _counter = 0
524 -def next_var():
525 # we could optimize this by reusing vars once the context is popped 526 global _counter 527 _counter += 1 528 return '_v%s'%_counter
529
530 -def push_context(context):
531 """ 532 Push new variable context onto context stack. The context stack 533 manages field-reference context for serialization, e.g. 'self.foo' 534 vs. 'self.bar.foo' vs. 'var.foo' 535 """ 536 global _serial_context, _context_stack 537 _context_stack.append(_serial_context) 538 _serial_context = context
539
540 -def pop_context():
541 """ 542 Pop variable context from context stack. The context stack manages 543 field-reference context for serialization, e.g. 'self.foo' 544 vs. 'self.bar.foo' vs. 'var.foo' 545 """ 546 global _serial_context 547 _serial_context = _context_stack.pop()
548 549 _context_patterns = []
550 -def add_pattern(p):
551 """ 552 Record struct pattern that's been used for (de)serialization 553 """ 554 _context_patterns.append(p)
555 -def clear_patterns():
556 """ 557 Clear record of struct pattern that have been used for (de)serialization 558 """ 559 del _context_patterns[:]
560 -def get_patterns():
561 """ 562 @return: record of struct pattern that have been used for (de)serialization 563 """ 564 return _context_patterns[:]
565 566 # These are the workhorses of the message generation. The generators 567 # are implemented as iterators, where each iteration value is a line 568 # of Python code. The generators will invoke underlying generators, 569 # using the context stack to manage any changes in variable-naming, so 570 # that code can be reused as much as possible. 571
572 -def len_serializer_generator(var, is_string, serialize):
573 """ 574 Generator for array-length serialization (32-bit, little-endian unsigned integer) 575 @param var: variable name 576 @type var: str 577 @param is_string: if True, variable is a string type 578 @type is_string: bool 579 @param serialize bool: if True, generate code for 580 serialization. Other, generate code for deserialization 581 @type serialize: bool 582 """ 583 if serialize: 584 yield "length = len(%s)"%var 585 # NOTE: it's more difficult to save a call to struct.pack with 586 # the array length as we are already using *array_val to pass 587 # into struct.pack as *args. Although it's possible that 588 # Python is not optimizing it, it is potentially worse for 589 # performance to attempt to combine 590 if not is_string: 591 yield int32_pack("length") 592 else: 593 yield "start = end" 594 yield "end += 4" 595 yield int32_unpack('length', 'str[start:end]') #4 = struct.calcsize('<i')
596
597 -def string_serializer_generator(package, type_, name, serialize):
598 """ 599 Generator for string types. similar to arrays, but with more 600 efficient call to struct.pack. 601 @param name: spec field name 602 @type name: str 603 @param serialize: if True, generate code for 604 serialization. Other, generate code for deserialization 605 @type serialize: bool 606 """ 607 # don't optimize in deserialization case as assignment doesn't 608 # work 609 if _serial_context and serialize: 610 # optimize as string serialization accesses field twice 611 yield "_x = %s%s"%(_serial_context, name) 612 var = "_x" 613 else: 614 var = _serial_context+name 615 616 # the length generator is a noop if serialize is True as we 617 # optimize the serialization call. 618 base_type, is_array, array_len = roslib.msgs.parse_type(type_) 619 # - don't serialize length for fixed-length arrays of bytes 620 if base_type not in ['uint8', 'char'] or array_len is None: 621 for y in len_serializer_generator(var, True, serialize): 622 yield y #serialize string length 623 624 if serialize: 625 #serialize length and string together 626 627 #check to see if its a uint8/char type, in which case we need to convert to string before serializing 628 base_type, is_array, array_len = roslib.msgs.parse_type(type_) 629 if base_type in ['uint8', 'char']: 630 yield "# - if encoded as a list instead, serialize as bytes instead of string" 631 if array_len is None: 632 yield "if type(%s) in [list, tuple]:"%var 633 yield INDENT+pack2("'<I%sB'%length", "length, *%s"%var) 634 yield "else:" 635 yield INDENT+pack2("'<I%ss'%length", "length, %s"%var) 636 else: 637 yield "if type(%s) in [list, tuple]:"%var 638 yield INDENT+pack('%sB'%array_len, "*%s"%var) 639 yield "else:" 640 yield INDENT+pack('%ss'%array_len, var) 641 else: 642 # py3k: struct.pack() now only allows bytes for the s string pack code. 643 # FIXME: for py3k, this needs to be w/ encode, but this interferes with actual byte data 644 #yield pack2("'<I%ss'%length", "length, %s.encode()"%var) #Py3k bugfix (see http://docs.python.org/dev/whatsnew/3.2.html#porting-to-python-3-2) 645 yield pack2("'<I%ss'%length", "length, %s"%var) 646 else: 647 yield "start = end" 648 if array_len is not None: 649 yield "end += %s" % array_len 650 else: 651 yield "end += length" 652 yield "%s = str[start:end]" % var
653
654 -def array_serializer_generator(package, type_, name, serialize, is_numpy):
655 """ 656 Generator for array types 657 @raise MsgGenerationException: if array spec is invalid 658 """ 659 base_type, is_array, array_len = roslib.msgs.parse_type(type_) 660 if not is_array: 661 raise MsgGenerationException("Invalid array spec: %s"%type_) 662 var_length = array_len is None 663 664 # handle fixed-size byte arrays could be slightly more efficient 665 # as we recalculated the length in the generated code. 666 if base_type in ['char', 'uint8']: #treat unsigned int8 arrays as string type 667 for y in string_serializer_generator(package, type_, name, serialize): 668 yield y 669 return 670 671 var = _serial_context+name 672 try: 673 # yield length serialization, if necessary 674 if var_length: 675 for y in len_serializer_generator(var, False, serialize): 676 yield y #serialize array length 677 length = None 678 else: 679 length = array_len 680 681 #optimization for simple arrays 682 if is_simple(base_type): 683 if var_length: 684 pattern = compute_struct_pattern([base_type]) 685 yield "pattern = '<%%s%s'%%length"%pattern 686 if serialize: 687 if is_numpy: 688 yield pack_numpy(var) 689 else: 690 yield pack2('pattern', "*"+var) 691 else: 692 yield "start = end" 693 yield "end += struct.calcsize(pattern)" 694 if is_numpy: 695 dtype = _NUMPY_DTYPE[base_type] 696 yield unpack_numpy(var, 'length', dtype, 'str[start:end]') 697 else: 698 yield unpack2(var, 'pattern', 'str[start:end]') 699 else: 700 pattern = "%s%s"%(length, compute_struct_pattern([base_type])) 701 if serialize: 702 if is_numpy: 703 yield pack_numpy(var) 704 else: 705 yield pack(pattern, "*"+var) 706 else: 707 yield "start = end" 708 yield "end += %s"%struct.calcsize('<%s'%pattern) 709 if is_numpy: 710 dtype = _NUMPY_DTYPE[base_type] 711 yield unpack_numpy(var, length, dtype, 'str[start:end]') 712 else: 713 yield unpack(var, pattern, 'str[start:end]') 714 if not serialize and base_type == 'bool': 715 # convert uint8 to bool 716 if base_type == 'bool': 717 yield "%s = map(bool, %s)"%(var, var) 718 719 else: 720 #generic recursive serializer 721 #NOTE: this is functionally equivalent to the is_registered branch of complex_serializer_generator 722 723 # choose a unique temporary variable for iterating 724 loop_var = 'val%s'%len(_context_stack) 725 726 # compute the variable context and factory to use 727 if base_type == 'string': 728 push_context('') 729 factory = string_serializer_generator(package, base_type, loop_var, serialize) 730 else: 731 push_context('%s.'%loop_var) 732 factory = serializer_generator(package, get_registered_ex(base_type), serialize, is_numpy) 733 734 if serialize: 735 yield 'for %s in %s:'%(loop_var, var) 736 else: 737 yield '%s = []'%var 738 if var_length: 739 yield 'for i in range(0, length):' 740 else: 741 yield 'for i in range(0, %s):'%length 742 if base_type != 'string': 743 yield INDENT + '%s = %s'%(loop_var, compute_constructor(package, base_type)) 744 for y in factory: 745 yield INDENT + y 746 if not serialize: 747 yield INDENT + '%s.append(%s)'%(var, loop_var) 748 pop_context() 749 750 except MsgGenerationException: 751 raise #re-raise 752 except Exception as e: 753 raise MsgGenerationException(e) #wrap
754
755 -def complex_serializer_generator(package, type_, name, serialize, is_numpy):
756 """ 757 Generator for serializing complex type 758 @param serialize: if True, generate serialization 759 code. Otherwise, deserialization code. 760 @type serialize: bool 761 @param is_numpy: if True, generate serializer code for numpy 762 datatypes instead of Python lists 763 @type is_numpy: bool 764 @raise MsgGenerationException: if type is not a valid 765 """ 766 # ordering of these statements is important as we mutate the type 767 # string we are checking throughout. parse_type strips array 768 # brackets, then we check for the 'complex' builtin types (string, 769 # time, duration, Header), then we canonicalize it to an embedded 770 # message type. 771 _, is_array, _ = roslib.msgs.parse_type(type_) 772 773 #Array 774 if is_array: 775 for y in array_serializer_generator(package, type_, name, serialize, is_numpy): 776 yield y 777 #Embedded Message 778 elif type_ == 'string': 779 for y in string_serializer_generator(package, type_, name, serialize): 780 yield y 781 else: 782 if not is_special(type_): 783 # canonicalize type 784 pkg, base_type = compute_pkg_type(package, type_) 785 type_ = "%s/%s"%(pkg, base_type) 786 if roslib.msgs.is_registered(type_): 787 # descend data structure #################### 788 ctx_var = next_var() 789 yield "%s = %s"%(ctx_var, _serial_context+name) 790 push_context(ctx_var+'.') 791 # unoptimized code 792 #push_context(_serial_context+name+'.') 793 for y in serializer_generator(package, get_registered_ex(type_), serialize, is_numpy): 794 yield y #recurs on subtype 795 pop_context() 796 else: 797 #Invalid 798 raise MsgGenerationException("Unknown type: %s. Package context is %s"%(type_, package))
799
800 -def simple_serializer_generator(spec, start, end, serialize): #primitives that can be handled with struct
801 """ 802 Generator (de)serialization code for multiple fields from spec 803 @param spec: MsgSpec 804 @type spec: MsgSpec 805 @param start: first field to serialize 806 @type start: int 807 @param end: last field to serialize 808 @type end: int 809 """ 810 # optimize member var access 811 if end - start > 1 and _serial_context.endswith('.'): 812 yield '_x = '+_serial_context[:-1] 813 vars_ = '_x.' + (', _x.').join(spec.names[start:end]) 814 else: 815 vars_ = _serial_context + (', '+_serial_context).join(spec.names[start:end]) 816 817 pattern = compute_struct_pattern(spec.types[start:end]) 818 if serialize: 819 yield pack(pattern, vars_) 820 else: 821 yield "start = end" 822 yield "end += %s"%struct.calcsize('<%s'%reduce_pattern(pattern)) 823 yield unpack('(%s,)'%vars_, pattern, 'str[start:end]') 824 825 # convert uint8 to bool. this doesn't add much value as Python 826 # equality test on a field will return that True == 1, but I 827 # want to be consistent with bool 828 bool_vars = [(f, t) for f, t in zip(spec.names[start:end], spec.types[start:end]) if t == 'bool'] 829 for f, t in bool_vars: 830 #TODO: could optimize this as well 831 var = _serial_context+f 832 yield "%s = bool(%s)"%(var, var) 833
834 -def serializer_generator(package, spec, serialize, is_numpy):
835 """ 836 Python generator that yields un-indented python code for 837 (de)serializing MsgSpec. The code this yields is meant to be 838 included in a class method and cannot be used 839 standalone. serialize_fn_generator and deserialize_fn_generator 840 wrap method to provide appropriate class field initializations. 841 @param package: name of package the spec is being used in 842 @type package: str 843 @param serialize: if True, yield serialization 844 code. Otherwise, yield deserialization code. 845 @type serialize: bool 846 @param is_numpy: if True, generate serializer code for numpy datatypes instead of Python lists 847 @type is_numpy: bool 848 """ 849 # Break spec into chunks of simple (primitives) vs. complex (arrays, etc...) 850 # Simple types are batch serialized using the python struct module. 851 # Complex types are individually serialized 852 if spec is None: 853 raise MsgGenerationException("spec is none") 854 names, types = spec.names, spec.types 855 if serialize and not len(names): #Empty 856 yield "pass" 857 return 858 859 # iterate through types. whenever we encounter a non-simple type, 860 # yield serializer for any simple types we've encountered until 861 # then, then yield the complex type serializer 862 curr = 0 863 for (i, type_) in enumerate(types): 864 if not is_simple(type_): 865 if i != curr: #yield chunk of simples 866 for y in simple_serializer_generator(spec, curr, i, serialize): 867 yield y 868 curr = i+1 869 for y in complex_serializer_generator(package, type_, names[i], serialize, is_numpy): 870 yield y 871 if curr < len(types): #yield rest of simples 872 for y in simple_serializer_generator(spec, curr, len(types), serialize): 873 yield y
874
875 -def serialize_fn_generator(package, spec, is_numpy=False):
876 """ 877 generator for body of serialize() function 878 @param is_numpy: if True, generate serializer code for numpy 879 datatypes instead of Python lists 880 @type is_numpy: bool 881 """ 882 # method-var context ######### 883 yield "try:" 884 push_context('self.') 885 #NOTE: we flatten the spec for optimal serialization 886 for y in serializer_generator(package, flatten(spec), True, is_numpy): 887 yield " "+y 888 pop_context() 889 yield "except struct.error as se: self._check_types(se)" 890 yield "except TypeError as te: self._check_types(te)"
891 # done w/ method-var context # 892
893 -def deserialize_fn_generator(package, spec, is_numpy=False):
894 """ 895 generator for body of deserialize() function 896 @param is_numpy: if True, generate serializer code for numpy 897 datatypes instead of Python lists 898 @type is_numpy: bool 899 """ 900 yield "try:" 901 902 #Instantiate embedded type classes 903 for type_, name in spec.fields(): 904 if roslib.msgs.is_registered(type_): 905 yield " if self.%s is None:"%name 906 yield " self.%s = %s"%(name, compute_constructor(package, type_)) 907 yield " end = 0" #initialize var 908 909 # method-var context ######### 910 push_context('self.') 911 #NOTE: we flatten the spec for optimal serialization 912 for y in serializer_generator(package, flatten(spec), False, is_numpy): 913 yield " "+y 914 pop_context() 915 # done w/ method-var context # 916 917 # generate post-deserialization code 918 for type_, name in spec.fields(): 919 code = compute_post_deserialize(type_, "self.%s"%name) 920 if code: 921 yield " %s"%code 922 923 yield " return self" 924 yield "except struct.error as e:" 925 yield " raise roslib.message.DeserializationError(e) #most likely buffer underfill"
926
927 -def msg_generator(package, name, spec):
928 """ 929 Python code generator for .msg files. Takes in a package name, 930 message name, and message specification and generates a Python 931 message class. 932 933 @param package: name of package for message 934 @type package: str 935 @param name: base type name of message, e.g. 'Empty', 'String' 936 @type name: str 937 @param spec: parsed .msg specification 938 @type spec: L{MsgSpec} 939 """ 940 941 # #2990: have to compute md5sum before any calls to make_python_safe 942 943 # generate dependencies dictionary. omit files calculation as we 944 # rely on in-memory MsgSpecs instead so that we can generate code 945 # for older versions of msg files 946 try: 947 gendeps_dict = roslib.gentools.get_dependencies(spec, package, compute_files=False) 948 except roslib.msgs.MsgSpecException as e: 949 raise MsgGenerationException("Cannot generate .msg for %s/%s: %s"%(package, name, str(e))) 950 md5sum = roslib.gentools.compute_md5(gendeps_dict) 951 952 # remap spec names to be Python-safe 953 spec = make_python_safe(spec) 954 spec_names = spec.names 955 956 # #1807 : this will be much cleaner when msggenerator library is 957 # rewritten to not use globals 958 clear_patterns() 959 960 yield '"""autogenerated by genmsg_py from %s.msg. Do not edit."""'%name 961 yield 'import roslib.message\nimport struct\n' 962 import_strs = [] 963 for t in spec.types: 964 import_strs.extend(compute_import(package, t)) 965 import_strs = set(import_strs) 966 for i in import_strs: 967 if i: 968 yield i 969 970 yield '' 971 972 fulltype = '%s%s%s'%(package, roslib.msgs.SEP, name) 973 974 #Yield data class first, e.g. Point2D 975 yield 'class %s(roslib.message.Message):'%name 976 yield ' _md5sum = "%s"'%(md5sum) 977 yield ' _type = "%s"'%(fulltype) 978 yield ' _has_header = %s #flag to mark the presence of a Header object'%spec.has_header() 979 # note: we introduce an extra newline to protect the escaping from quotes in the message 980 yield ' _full_text = """%s\n"""'%compute_full_text_escaped(gendeps_dict) 981 982 if spec.constants: 983 yield ' # Pseudo-constants' 984 for c in spec.constants: 985 if c.type == 'string': 986 val = c.val 987 if '"' in val and "'" in val: 988 # crude escaping of \ and " 989 escaped = c.val.replace('\\', '\\\\') 990 escaped = escaped.replace('\"', '\\"') 991 yield ' %s = "%s"'%(c.name, escaped) 992 elif '"' in val: #use raw encoding for prettiness 993 yield " %s = r'%s'"%(c.name, val) 994 elif "'" in val: #use raw encoding for prettiness 995 yield ' %s = r"%s"'%(c.name, val) 996 else: 997 yield " %s = '%s'"%(c.name, val) 998 else: 999 yield ' %s = %s'%(c.name, c.val) 1000 yield '' 1001 1002 if len(spec_names): 1003 yield " __slots__ = ['"+"','".join(spec_names)+"']" 1004 yield " _slot_types = ['"+"','".join(spec.types)+"']" 1005 else: 1006 yield " __slots__ = []" 1007 yield " _slot_types = []" 1008 1009 yield """ 1010 def __init__(self, *args, **kwds): 1011 \"\"\" 1012 Constructor. Any message fields that are implicitly/explicitly 1013 set to None will be assigned a default value. The recommend 1014 use is keyword arguments as this is more robust to future message 1015 changes. You cannot mix in-order arguments and keyword arguments. 1016 1017 The available fields are: 1018 %s 1019 1020 @param args: complete set of field values, in .msg order 1021 @param kwds: use keyword arguments corresponding to message field names 1022 to set specific fields. 1023 \"\"\" 1024 if args or kwds: 1025 super(%s, self).__init__(*args, **kwds)"""%(','.join(spec_names), name) 1026 1027 if len(spec_names): 1028 yield " #message fields cannot be None, assign default values for those that are" 1029 for (t, s) in zip(spec.types, spec_names): 1030 yield " if self.%s is None:"%s 1031 yield " self.%s = %s"%(s, default_value(t, package)) 1032 if len(spec_names) > 0: 1033 yield " else:" 1034 for (t, s) in zip(spec.types, spec_names): 1035 yield " self.%s = %s"%(s, default_value(t, package)) 1036 1037 yield """ 1038 def _get_types(self): 1039 \"\"\" 1040 internal API method 1041 \"\"\" 1042 return self._slot_types 1043 1044 def serialize(self, buff): 1045 \"\"\" 1046 serialize message into buffer 1047 @param buff: buffer 1048 @type buff: StringIO 1049 \"\"\"""" 1050 for y in serialize_fn_generator(package, spec): 1051 yield " "+ y 1052 yield """ 1053 def deserialize(self, str): 1054 \"\"\" 1055 unpack serialized message in str into this message instance 1056 @param str: byte array of serialized message 1057 @type str: str 1058 \"\"\"""" 1059 for y in deserialize_fn_generator(package, spec): 1060 yield " " + y 1061 yield "" 1062 1063 yield """ 1064 def serialize_numpy(self, buff, numpy): 1065 \"\"\" 1066 serialize message with numpy array types into buffer 1067 @param buff: buffer 1068 @type buff: StringIO 1069 @param numpy: numpy python module 1070 @type numpy module 1071 \"\"\"""" 1072 for y in serialize_fn_generator(package, spec, is_numpy=True): 1073 yield " "+ y 1074 yield """ 1075 def deserialize_numpy(self, str, numpy): 1076 \"\"\" 1077 unpack serialized message in str into this message instance using numpy for array types 1078 @param str: byte array of serialized message 1079 @type str: str 1080 @param numpy: numpy python module 1081 @type numpy: module 1082 \"\"\"""" 1083 for y in deserialize_fn_generator(package, spec, is_numpy=True): 1084 yield " " + y 1085 yield "" 1086 1087 1088 # #1807 : this will be much cleaner when msggenerator library is 1089 # rewritten to not use globals 1090 yield '_struct_I = roslib.message.struct_I' 1091 patterns = get_patterns() 1092 for p in set(patterns): 1093 # I patterns are already optimized 1094 if p == 'I': 1095 continue 1096 var_name = '_struct_%s'%(p.replace('<','')) 1097 yield '%s = struct.Struct("<%s")'%(var_name, p) 1098 clear_patterns()
1099 1100 ################################################################################ 1101 # dynamic generation of deserializer 1102
1103 -def _generate_dynamic_specs(specs, dep_msg):
1104 """ 1105 @param dep_msg: text of dependent .msg definition 1106 @type dep_msg: str 1107 @return: type name, message spec 1108 @rtype: str, MsgSpec 1109 @raise MsgGenerationException: if dep_msg is improperly formatted 1110 """ 1111 line1 = dep_msg.find('\n') 1112 msg_line = dep_msg[:line1] 1113 if not msg_line.startswith("MSG: "): 1114 raise MsgGenerationException("invalid input to generate_dynamic: dependent type is missing 'MSG:' type declaration header") 1115 dep_type = msg_line[5:].strip() 1116 dep_pkg, dep_base_type = roslib.names.package_resource_name(dep_type) 1117 dep_spec = roslib.msgs.load_from_string(dep_msg[line1+1:], dep_pkg) 1118 return dep_type, dep_spec
1119
1120 -def _gen_dyn_name(pkg, base_type):
1121 """ 1122 Modify pkg/base_type name so that it can safely co-exist with 1123 statically generated files. 1124 1125 @return: name to use for pkg/base_type for dynamically generated message class. 1126 @rtype: str 1127 """ 1128 return "_%s__%s"%(pkg, base_type)
1129
1130 -def _gen_dyn_modify_references(py_text, types):
1131 """ 1132 Modify the generated code to rewrite names such that the code can 1133 safely co-exist with messages of the same name. 1134 1135 @param py_text: genmsg_py-generated Python source code 1136 @type py_text: str 1137 @return: updated text 1138 @rtype: str 1139 """ 1140 for t in types: 1141 pkg, base_type = roslib.names.package_resource_name(t) 1142 gen_name = _gen_dyn_name(pkg, base_type) 1143 1144 # Several things we have to rewrite: 1145 # - remove any import statements 1146 py_text = py_text.replace("import %s.msg"%pkg, '') 1147 # - rewrite any references to class 1148 py_text = py_text.replace("%s.msg.%s"%(pkg, base_type), gen_name) 1149 # - class declaration 1150 py_text = py_text.replace('class %s('%base_type, 'class %s('%gen_name) 1151 # - super() references for __init__ 1152 py_text = py_text.replace('super(%s,'%base_type, 'super(%s,'%gen_name) 1153 # std_msgs/Header also has to be rewritten to be a local reference 1154 py_text = py_text.replace('std_msgs.msg._Header.Header', _gen_dyn_name('std_msgs', 'Header')) 1155 return py_text
1156
1157 -def generate_dynamic(core_type, msg_cat):
1158 """ 1159 Dymamically generate message classes from msg_cat .msg text 1160 gendeps dump. This method modifies sys.path to include a temp file 1161 directory. 1162 @param core_type str: top-level ROS message type of concatenanted .msg text 1163 @param msg_cat str: concatenation of full message text (output of gendeps --cat) 1164 @raise MsgGenerationException: if dep_msg is improperly formatted 1165 """ 1166 core_pkg, core_base_type = roslib.names.package_resource_name(core_type) 1167 1168 # REP 100: pretty gross hack to deal with the fact that we moved 1169 # Header. Header is 'special' because it can be used w/o a package 1170 # name, so the lookup rules end up failing. We are committed to 1171 # never changing std_msgs/Header, so this is generally fine. 1172 msg_cat = msg_cat.replace('roslib/Header', 'std_msgs/Header') 1173 1174 # separate msg_cat into the core message and dependencies 1175 splits = msg_cat.split('\n'+'='*80+'\n') 1176 core_msg = splits[0] 1177 deps_msgs = splits[1:] 1178 1179 # create MsgSpec representations of .msg text 1180 specs = { core_type: roslib.msgs.load_from_string(core_msg, core_pkg) } 1181 # - dependencies 1182 for dep_msg in deps_msgs: 1183 # dependencies require more handling to determine type name 1184 dep_type, dep_spec = _generate_dynamic_specs(specs, dep_msg) 1185 specs[dep_type] = dep_spec 1186 1187 # clear the message registration table and register loaded 1188 # types. The types have to be registered globally in order for 1189 # message generation of dependents to work correctly. 1190 roslib.msgs.reinit() 1191 for t, spec in specs.items(): 1192 roslib.msgs.register(t, spec) 1193 1194 # process actual MsgSpecs: we accumulate them into a single file, 1195 # rewriting the generated text as needed 1196 buff = StringIO() 1197 for t, spec in specs.items(): 1198 pkg, s_type = roslib.names.package_resource_name(t) 1199 # dynamically generate python message code 1200 for l in msg_generator(pkg, s_type, spec): 1201 l = _gen_dyn_modify_references(l, list(specs.keys())) 1202 buff.write(l + '\n') 1203 full_text = buff.getvalue() 1204 1205 # Create a temporary directory 1206 tmp_dir = tempfile.mkdtemp(prefix='genpy_') 1207 1208 # Afterwards, we are going to remove the directory so that the .pyc file gets cleaned up if it's still around 1209 atexit.register(shutil.rmtree, tmp_dir) 1210 1211 # write the entire text to a file and import it 1212 tmp_file = tempfile.NamedTemporaryFile(suffix=".py",dir=tmp_dir) 1213 tmp_file.file.write(full_text) 1214 tmp_file.file.close() 1215 1216 # import our temporary file as a python module, which requires modifying sys.path 1217 sys.path.append(os.path.dirname(tmp_file.name)) 1218 1219 # - strip the prefix to turn it into the python module name 1220 mod = __import__(os.path.basename(tmp_file.name)[:-3]) 1221 1222 # finally, retrieve the message classes from the dynamic module 1223 messages = {} 1224 for t in specs.keys(): 1225 pkg, s_type = roslib.names.package_resource_name(t) 1226 try: 1227 messages[t] = getattr(mod, _gen_dyn_name(pkg, s_type)) 1228 except AttributeError: 1229 raise MsgGenerationException("cannot retrieve message class for %s/%s"%(pkg, s_type)) 1230 1231 # erase the dirty work we've done 1232 roslib.msgs.reinit() 1233 1234 return messages
1235