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