Beispiel #1
0
    def generate_firos_messages(self, package, data, outdir, OUTPUT, search_path):
        """
        :returns: return code, ``int``
        """
        if not genmsg.is_legal_resource_base_name(package):
            raise MsgGenerationException("\nERROR: package name '%s' is illegal and cannot be used in message generation.\nPlease see http://ros.org/wiki/Names"%(package))

        # package/src/package/msg for messages, packages/src/package/srv for services
        msg_context = MsgContext.create_default()
        retcode = 0
        try:
            # you can't just check first... race condition
            outdir = outdir + OUTPUT
            os.makedirs(outdir)
            f = open(os.path.join(outdir, "__init__.py"), 'w')
            f.close()
        except OSError as e:
            if e.errno != 17: # file exists
                raise

        for robotName in data:
            try:
                robot = data[robotName]
                for topic_name in robot['topics']:
                    topic = robot['topics'][topic_name]
                    full_type = str(robotName) + '/' + str(topic_name)
                    if type(topic['msg']) is dict:
                        self.firos_generate(msg_context, full_type, topic, outdir, search_path) #actual generation
            except Exception as e:
                if not isinstance(e, MsgGenerationException) and not isinstance(e, genmsg.msgs.InvalidMsgSpec):
                    traceback.print_exc()
                print("\nERROR: Unable to generate %s for package '%s': %s\n"%(self.what, package, e), file=sys.stderr)
                retcode = 1 #flag error
        return retcode
Beispiel #2
0
    def generate_messages(self, package, package_files, outdir, search_path):
        """
        :returns: return code, ``int``
        """
        if not genmsg.is_legal_resource_base_name(package):
            raise MsgGenerationException(
                "\nERROR: package name '%s' is illegal and cannot be used in message generation.\nPlease see http://ros.org/wiki/Names"
                % (package))

        # package/src/package/msg for messages, packages/src/package/srv for services
        msg_context = MsgContext.create_default()
        retcode = 0
        for f in package_files:
            try:
                f = os.path.abspath(f)
                infile_name = os.path.basename(f)
                full_type = genmsg.gentools.compute_full_type_name(
                    package, infile_name)
                outfile = self.generate(msg_context, full_type, f, outdir,
                                        search_path)  #actual generation
            except Exception as e:
                if not isinstance(e,
                                  MsgGenerationException) and not isinstance(
                                      e, genmsg.msgs.InvalidMsgSpec):
                    traceback.print_exc()
                print(
                    "\nERROR: Unable to generate %s for package '%s': while processing '%s': %s\n"
                    % (self.what, package, f, e),
                    file=sys.stderr)
                retcode = 1  #flag error
        return retcode
Beispiel #3
0
 def generator_fn(self, msg_context, spec, search_path):
     try:
         genmsg.msg_loader.load_depends(msg_context, spec, search_path)
     except InvalidMsgSpec as e:
         raise MsgGenerationException("Cannot generate .msg for %s: %s" %
                                      (spec.full_name, str(e)))
     self.add_msg(MsgGeneratorContex(msg_context, spec, search_path))
Beispiel #4
0
def get_registered_ex(msg_context, type_):
    """
    wrapper for get_registered that wraps unknown types with a MsgGenerationException
    :param type_: ROS message type, ``str``
    """
    try:
        return msg_context.get_registered(type_)
    except:
        raise MsgGenerationException("Unknown type [%s]. Please check that the manifest.xml correctly declares dependencies."%type_)
def gen_serializable_xml(spec, pkg):
    # TODO(mereweth) spec.constants

    # TODO(mereweth) - is there ever a difference between spec full and short names?
    root = etree.Element("serializable",
                         name=spec.full_name,
                         namespace="%s::%s"%(BASE_NAMESPACE, pkg))

    members = etree.SubElement(root, "members")

    if len(spec.parsed_fields()) == 0:
        # NOTE(mereweth) Empty.msg, possibly others
        member = etree.SubElement(members,
                                  "member",
                                  name="unused",
                                  type='U8')

    # TODO(mereweth) - what is field.type vs field.base_type?
    for field in spec.parsed_fields():
        # TODO(mereweth) - decide how to handle arrays besides just capping size
        type_, size_, header_includes, serial_imports, countName, countType = compute_type(pkg, field)

        for header in header_includes:
            include_header = etree.SubElement(root, "include_header")
            include_header.text = header

        for serial in serial_imports:
            import_serial = etree.SubElement(root, "import_serializable_type")
            import_serial.text = serial

        if field.is_array:
            if size_ is None:
                raise MsgGenerationException("No calc size for array field: %s, msg: %s, pkg: %s"%(
                                             field.name, spec.full_name, pkg))

            member = etree.SubElement(members,
                                      "member",
                                      name=field.name,
                                      type=type_,
                                      size=str(size_))

            if countName is not None:
                member = etree.SubElement(members,
                                          "member",
                                          name=countName,
                                          type=countType)
        else:
            member = etree.SubElement(members,
                                      "member",
                                      name=field.name,
                                      type=type_)

    return root.getroottree()
Beispiel #6
0
def compute_pkg_type(package, type_):
    """
    :param package: package that type is being imported into, ``str``
    :param type: message type (package resource name), ``str``
    :returns: python package and type name, ``(str, str)``
    """
    splits = type_.split(genmsg.SEP)
    if len(splits) == 1:
        return package, splits[0]
    elif len(splits) == 2:
        return tuple(splits)
    else:
        raise MsgGenerationException("illegal message type: %s"%type_)
Beispiel #7
0
def _write_modmk(outdir, generated_xml):
    if not os.path.exists(outdir):
        os.makedirs(outdir)
    elif not os.path.isdir(outdir):
        raise MsgGenerationException("file preventing the creating of Fprime directory: %s"%dir)
    p = os.path.join(outdir, 'mod.mk')
    with open(p, 'w') as f:
        f.write('SRC = \\\n')
        if len(generated_xml) != 0:
            for xml in generated_xml[:-1]:
                f.write('%s \\\n'%xml)
            f.write('%s\n'%generated_xml[-1])
    return 0
Beispiel #8
0
def _generate_dynamic_specs(msg_context, specs, dep_msg):
    """
    :param dep_msg: text of dependent .msg definition, ``str``
    :returns: type name, message spec, ``str, MsgSpec``
    :raises: MsgGenerationException If dep_msg is improperly formatted
    """
    line1 = dep_msg.find('\n')
    msg_line = dep_msg[:line1]
    if not msg_line.startswith("MSG: "):
        raise MsgGenerationException("invalid input to generate_dynamic: dependent type is missing 'MSG:' type declaration header")
    dep_type = msg_line[5:].strip()
    dep_pkg, dep_base_type = genmsg.package_resource_name(dep_type)
    dep_spec = genmsg.msg_loader.load_msg_from_string(msg_context, dep_msg[line1+1:], dep_type)
    return dep_type, dep_spec
Beispiel #9
0
def complex_serializer_generator(msg_context, package, type_, name, serialize,
                                 is_numpy):  # noqa: D401
    """
    Generator for serializing complex type.

    :param serialize: if True, generate serialization
      code. Otherwise, deserialization code. ``bool``
    :param is_numpy: if True, generate serializer code for numpy
      datatypes instead of Python lists, ``bool``
    :raises: MsgGenerationException If type is not a valid
    """
    # ordering of these statements is important as we mutate the type
    # string we are checking throughout. parse_type strips array
    # brackets, then we check for the 'complex' builtin types (string,
    # time, duration, Header), then we canonicalize it to an embedded
    # message type.
    _, is_array, _ = genmsg.msgs.parse_type(type_)

    # Array
    if is_array:
        for y in array_serializer_generator(msg_context, package, type_, name,
                                            serialize, is_numpy):
            yield y
    # Embedded Message
    elif type_ == 'string':
        for y in string_serializer_generator(package, type_, name, serialize):
            yield y
    else:
        if not is_special(type_):
            # canonicalize type
            pkg, base_type = compute_pkg_type(package, type_)
            type_ = '%s/%s' % (pkg, base_type)
        if msg_context.is_registered(type_):
            # descend data structure ####################
            ctx_var = next_var()
            yield '%s = %s' % (ctx_var, _serial_context + name)
            push_context(ctx_var + '.')
            # unoptimized code
            # push_context(_serial_context+name+'.')
            for y in serializer_generator(
                    msg_context,
                    make_python_safe(get_registered_ex(msg_context, type_)),
                    serialize, is_numpy):
                yield y  # recurs on subtype
            pop_context()
        else:
            # Invalid
            raise MsgGenerationException(
                'Unknown type: %s. Package context is %s' % (type_, package))
Beispiel #10
0
def serializer_generator(msg_context, spec, serialize, is_numpy):
    """
    Python generator that yields un-indented python code for
    (de)serializing MsgSpec. The code this yields is meant to be
    included in a class method and cannot be used
    standalone. serialize_fn_generator and deserialize_fn_generator
    wrap method to provide appropriate class field initializations.

    :param serialize: if True, yield serialization
      code. Otherwise, yield deserialization code. ``bool``
    :param is_numpy: if True, generate serializer code for numpy datatypes instead of Python lists. ``bool``
    """
    # Break spec into chunks of simple (primitives) vs. complex (arrays, etc...)
    # Simple types are batch serialized using the python struct module.
    # Complex types are individually serialized
    if spec is None:
        raise MsgGenerationException("spec is none")
    names, types = spec.names, spec.types
    if serialize and not len(names):  #Empty
        yield "pass"
        return

    _max_chunk = 255
    # iterate through types. whenever we encounter a non-simple type,
    # yield serializer for any simple types we've encountered until
    # then, then yield the complex type serializer
    curr = 0
    for (i, full_type) in enumerate(types):
        if not is_simple(full_type):
            if i != curr:  #yield chunk of simples
                for _start in range(curr, i, _max_chunk):
                    _end = min(_start + _max_chunk, i)
                    for y in simple_serializer_generator(
                            msg_context, spec, _start, _end, serialize):
                        yield y
            curr = i + 1
            for y in complex_serializer_generator(msg_context, spec.package,
                                                  full_type, names[i],
                                                  serialize, is_numpy):
                yield y
    if curr < len(types):  #yield rest of simples
        for _start in range(curr, len(types), _max_chunk):
            _end = min(_start + _max_chunk, len(types))
            for y in simple_serializer_generator(msg_context, spec, _start,
                                                 _end, serialize):
                yield y
Beispiel #11
0
def write_module(basedir, generated_modules):
    """
    Create a module file to mark directory for python

    :param base_dir: path to package, ``str``
    :param package: name of package to write module for, ``str``
    :param generated_modules: list of generated message modules,
      i.e. the names of the .py files that were generated for each
      .msg file. ``[str]``
    """
    if not os.path.exists(basedir):
        os.makedirs(basedir)
    elif not os.path.isdir(basedir):
        raise MsgGenerationException("file preventing the creating of module directory: %s"%dir)
    p = os.path.join(basedir, '__init__.py')
    with open(p, 'w') as f:
        for mod in generated_modules:
            f.write('from %s import *\n'%mod)

    parent_init = os.path.dirname(basedir)
Beispiel #12
0
def generate_dynamic(core_type, msg_cat):
    """
    Dymamically generate message classes from msg_cat .msg text gendeps dump.

    This method modifies sys.path to include a temp file directory.
    :param core_type str: top-level ROS message type of concatenated .msg text
    :param msg_cat str: concatenation of full message text (output of gendeps --cat)
    :raises: MsgGenerationException If dep_msg is improperly formatted
    """
    msg_context = MsgContext.create_default()
    core_pkg, core_base_type = genmsg.package_resource_name(core_type)

    # REP 100: pretty gross hack to deal with the fact that we moved
    # Header. Header is 'special' because it can be used w/o a package
    # name, so the lookup rules end up failing. We are committed to
    # never changing std_msgs/Header, so this is generally fine.
    msg_cat = msg_cat.replace('roslib/Header', 'std_msgs/Header')

    # separate msg_cat into the core message and dependencies
    splits = msg_cat.split('\n' + '=' * 80 + '\n')
    core_msg = splits[0]
    deps_msgs = splits[1:]

    # create MsgSpec representations of .msg text
    specs = {
        core_type:
        msg_loader.load_msg_from_string(msg_context, core_msg, core_type)
    }
    # - dependencies
    for dep_msg in deps_msgs:
        # dependencies require more handling to determine type name
        dep_type, dep_spec = _generate_dynamic_specs(msg_context, specs,
                                                     dep_msg)
        specs[dep_type] = dep_spec

    # clear the message registration table and register loaded
    # types. The types have to be registered globally in order for
    # message generation of dependents to work correctly.
    msg_context = msg_loader.MsgContext.create_default()
    search_path = {}  # no ability to dynamically load
    for t, spec in specs.items():
        msg_context.register(t, spec)

    # process actual MsgSpecs: we accumulate them into a single file,
    # rewriting the generated text as needed
    buff = StringIO()
    for t, spec in specs.items():
        pkg, s_type = genmsg.package_resource_name(t)
        # dynamically generate python message code
        for line in msg_generator(msg_context, spec, search_path):
            line = _gen_dyn_modify_references(line, t, list(specs.keys()))
            buff.write(line + '\n')
    full_text = buff.getvalue()

    # Create a temporary directory
    tmp_dir = tempfile.mkdtemp(prefix='genpy_')

    # Afterwards, we are going to remove the directory so that the .pyc file gets cleaned up if it's still around
    atexit.register(shutil.rmtree, tmp_dir)

    # write the entire text to a file and import it (it will get deleted when tmp_dir goes - above)
    tmp_file = tempfile.NamedTemporaryFile(mode='w',
                                           suffix='.py',
                                           dir=tmp_dir,
                                           delete=False)
    tmp_file.file.write(full_text)
    tmp_file.file.close()

    # import our temporary file as a python module, which requires modifying sys.path
    sys.path.append(os.path.dirname(tmp_file.name))

    # - strip the prefix to turn it into the python module name
    try:
        mod = __import__(os.path.basename(tmp_file.name)[:-3])
    except Exception:
        # TODOXXX:REMOVE
        with open(tmp_file.name) as f:
            text = f.read()
            with open('/tmp/foo', 'w') as f2:
                f2.write(text)
        raise

    # finally, retrieve the message classes from the dynamic module
    messages = {}
    for t in specs.keys():
        pkg, s_type = genmsg.package_resource_name(t)
        try:
            messages[t] = getattr(mod, _gen_dyn_name(pkg, s_type))
        except AttributeError:
            raise MsgGenerationException(
                'cannot retrieve message class for %s/%s: %s' %
                (pkg, s_type, _gen_dyn_name(pkg, s_type)))
        messages[t]._spec = specs[t]

    return messages
Beispiel #13
0
def msg_generator(msg_context, spec, search_path):
    """
    Python code generator for .msg files. Generates a Python from a
     :class:`genmsg.MsgSpec`.

    :param spec: parsed .msg :class:`genmsg.MsgSpec` instance
    :param search_path: dictionary mapping message namespaces to a directory locations
    """

    # #2990: have to compute md5sum before any calls to make_python_safe

    # generate dependencies dictionary. omit files calculation as we
    # rely on in-memory MsgSpecs instead so that we can generate code
    # for older versions of msg files
    try:
        genmsg.msg_loader.load_depends(msg_context, spec, search_path)
    except InvalidMsgSpec as e:
        raise MsgGenerationException("Cannot generate .msg for %s/%s: %s"%(package, name, str(e)))
    md5sum = genmsg.compute_md5(msg_context, spec)

    # remap spec names to be Python-safe
    spec = make_python_safe(spec)
    spec_names = spec.names

    # #1807 : this will be much cleaner when msggenerator library is
    # rewritten to not use globals
    clear_patterns()

    yield '# This Python file uses the following encoding: utf-8'
    yield '"""autogenerated by genpy from %s.msg. Do not edit."""'%spec.full_name
    yield 'import sys'
    yield 'python3 = True if sys.hexversion > 0x03000000 else False'
    yield 'import genpy\nimport struct\n'
    import_strs = []
    for t in spec.types:
        import_strs.extend(compute_import(msg_context, spec.package, t))
    import_strs = set(import_strs)
    for i in import_strs:
        if i:
            yield i

    yield ''

    fulltype = spec.full_name
    name = spec.short_name

    #Yield data class first, e.g. Point2D
    yield 'class %s(genpy.Message):'%spec.short_name
    yield '  _md5sum = "%s"'%(md5sum)
    yield '  _type = "%s"'%(fulltype)
    yield '  _has_header = %s #flag to mark the presence of a Header object'%spec.has_header()
    
    full_text = compute_full_text_escaped(msg_context, spec)
    # escape trailing double-quote, unless already escaped, before wrapping in """
    if full_text.endswith('"') and not full_text.endswith(r'\"'):
        full_text = full_text[:-1] + r'\"'
    yield '  _full_text = """%s"""'%full_text

    if spec.constants:
        yield '  # Pseudo-constants'
        for c in spec.constants:
            if c.type == 'string':
                val = c.val
                if '"' in val and "'" in val:
                    # crude escaping of \ and "
                    escaped = c.val.replace('\\', '\\\\')
                    escaped = escaped.replace('\"', '\\"')
                    yield '  %s = "%s"'%(c.name, escaped)
                elif '"' in val: #use raw encoding for prettiness
                    yield "  %s = r'%s'"%(c.name, val)
                elif "'" in val: #use raw encoding for prettiness
                    yield '  %s = r"%s"'%(c.name, val)
                else:
                    yield "  %s = '%s'"%(c.name, val)
            else:
                yield '  %s = %s'%(c.name, c.val)
        yield ''

    if len(spec_names):
        yield "  __slots__ = ['"+"','".join(spec_names)+"']"
        yield "  _slot_types = ['"+"','".join(spec.types)+"']"
    else:
        yield "  __slots__ = []"
        yield "  _slot_types = []"

    yield """
  def __init__(self, *args, **kwds):
    \"\"\"
    Constructor. Any message fields that are implicitly/explicitly
    set to None will be assigned a default value. The recommend
    use is keyword arguments as this is more robust to future message
    changes.  You cannot mix in-order arguments and keyword arguments.

    The available fields are:
       %s

    :param args: complete set of field values, in .msg order
    :param kwds: use keyword arguments corresponding to message field names
    to set specific fields.
    \"\"\"
    if args or kwds:
      super(%s, self).__init__(*args, **kwds)"""%(','.join(spec_names), name)

    if len(spec_names):
        yield "      #message fields cannot be None, assign default values for those that are"
        for (t, s) in zip(spec.types, spec_names):
            yield "      if self.%s is None:"%s
            yield "        self.%s = %s"%(s, default_value(msg_context, t, spec.package))
    if len(spec_names) > 0:
      yield "    else:"
      for (t, s) in zip(spec.types, spec_names):
          yield "      self.%s = %s"%(s, default_value(msg_context, t, spec.package))

    yield """
  def _get_types(self):
    \"\"\"
    internal API method
    \"\"\"
    return self._slot_types

  def serialize(self, buff):
    \"\"\"
    serialize message into buffer
    :param buff: buffer, ``StringIO``
    \"\"\""""
    for y in serialize_fn_generator(msg_context, spec):
        yield "    "+ y
    yield """
  def deserialize(self, str):
    \"\"\"
    unpack serialized message in str into this message instance
    :param str: byte array of serialized message, ``str``
    \"\"\""""
    for y in deserialize_fn_generator(msg_context, spec):
        yield "    " + y
    yield ""

    yield """
  def serialize_numpy(self, buff, numpy):
    \"\"\"
    serialize message with numpy array types into buffer
    :param buff: buffer, ``StringIO``
    :param numpy: numpy python module
    \"\"\""""
    for y in serialize_fn_generator(msg_context, spec, is_numpy=True):
        yield "    "+ y
    yield """
  def deserialize_numpy(self, str, numpy):
    \"\"\"
    unpack serialized message in str into this message instance using numpy for array types
    :param str: byte array of serialized message, ``str``
    :param numpy: numpy python module
    \"\"\""""
    for y in deserialize_fn_generator(msg_context, spec, is_numpy=True):
        yield "    " + y
    yield ""


    # #1807 : this will be much cleaner when msggenerator library is
    # rewritten to not use globals
    yield '_struct_I = genpy.struct_I'
    yield 'def _get_struct_I():'
    yield '    global _struct_I'
    yield '    return _struct_I'
    patterns = get_patterns()
    for p in set(patterns):
        # I patterns are already optimized
        if p == 'I':
            continue
        var_name = '_struct_%s'%(p.replace('<',''))
        yield '%s = None' % var_name
        yield 'def _get%s():' % var_name
        yield '    global %s' % var_name
        yield '    if %s is None:' % var_name
        yield '        %s = struct.Struct("<%s")' % (var_name, p)
        yield '    return %s' % var_name
    clear_patterns()
Beispiel #14
0
def array_serializer_generator(msg_context, package, type_, name, serialize, is_numpy):
    """
    Generator for array types

    :raises: :exc:`MsgGenerationException` If array spec is invalid
    """
    base_type, is_array, array_len = genmsg.msgs.parse_type(type_)
    if not is_array:
        raise MsgGenerationException("Invalid array spec: %s"%type_)
    var_length = array_len is None

    # handle fixed-size byte arrays could be slightly more efficient
    # as we recalculated the length in the generated code.
    if base_type in ['char', 'uint8']: #treat unsigned int8 arrays as string type
        for y in string_serializer_generator(package, type_, name, serialize):
            yield y
        return

    var = _serial_context+name
    # yield length serialization, if necessary
    if var_length:
        for y in len_serializer_generator(var, False, serialize):
            yield y #serialize array length
        length = None
    else:
        length = array_len

    #optimization for simple arrays
    if is_simple(base_type):
        if var_length:
            pattern = compute_struct_pattern([base_type])
            yield "pattern = '<%%s%s'%%length"%pattern
            if serialize:
                if is_numpy:
                    yield pack_numpy(var)
                else:
                    yield pack2('pattern', "*"+var)
            else:
                yield "start = end"
                yield "end += struct.calcsize(pattern)"
                if is_numpy:
                    dtype = NUMPY_DTYPE[base_type]
                    yield unpack_numpy(var, 'length', dtype, 'str[start:end]')
                else:
                    yield unpack2(var, 'pattern', 'str[start:end]')
        else:
            pattern = "%s%s"%(length, compute_struct_pattern([base_type]))
            if serialize:
                if is_numpy:
                    yield pack_numpy(var)
                else:
                    yield pack(pattern, "*"+var)
            else:
                yield "start = end"
                yield "end += %s"%struct.calcsize('<%s'%pattern)
                if is_numpy:
                    dtype = NUMPY_DTYPE[base_type]
                    yield unpack_numpy(var, length, dtype, 'str[start:end]')
                else:
                    yield unpack(var, pattern, 'str[start:end]')
        if not serialize and base_type == 'bool':
            # convert uint8 to bool
            if base_type == 'bool':
                yield "%s = map(bool, %s)"%(var, var)

    else:
        #generic recursive serializer
        #NOTE: this is functionally equivalent to the is_registered branch of complex_serializer_generator

        # choose a unique temporary variable for iterating
        loop_var = 'val%s'%len(_context_stack)

        # compute the variable context and factory to use
        if base_type == 'string':
            push_context('')
            factory = string_serializer_generator(package, base_type, loop_var, serialize)
        else:
            push_context('%s.'%loop_var)
            factory = serializer_generator(msg_context, make_python_safe(get_registered_ex(msg_context, base_type)), serialize, is_numpy)

        if serialize:
            if array_len is not None:
                yield 'if len(%s) != %s:'%(var, array_len)
                yield INDENT + "self._check_types(ValueError(\"Expecting %%s items but found %%s when writing '%%s'\" %% (%s, len(%s), '%s')))"%(array_len, var, var)
            yield 'for %s in %s:'%(loop_var, var)
        else:
            yield '%s = []'%var
            if var_length:
                yield 'for i in range(0, length):'
            else:
                yield 'for i in range(0, %s):'%length
            if base_type != 'string':
                yield INDENT + '%s = %s'%(loop_var, compute_constructor(msg_context, package, base_type))
        for y in factory:
            yield INDENT + y
        if not serialize:
            yield INDENT + '%s.append(%s)'%(var, loop_var)
        pop_context()
Beispiel #15
0
def array_serializer_generator(msg_context, package, type_, name, serialize,
                               is_numpy):
    """
    Generator for array types
    @raise MsgGenerationException: if array spec is invalid
    """
    base_type, is_array, array_len = genmsg.msgs.parse_type(type_)
    if not is_array:
        raise MsgGenerationException("Invalid array spec: %s" % type_)
    var_length = array_len is None

    # handle fixed-size byte arrays could be slightly more efficient
    # as we recalculated the length in the generated code.
    if base_type in ['char',
                     'uint8']:  #treat unsigned int8 arrays as string type
        for y in string_serializer_generator(package, type_, name, serialize):
            yield y
        return

    var = _serial_context + name
    try:
        # yield length serialization, if necessary
        if var_length:
            for y in len_serializer_generator(var, False, serialize):
                yield y  #serialize array length
            length = None
        else:
            length = array_len

        #optimization for simple arrays
        if is_simple(base_type):
            if var_length:
                pattern = compute_struct_pattern([base_type])
                yield 'pattern = "%s#{length}"' % convert_to_ruby_pattern(
                    pattern)
                if serialize:
                    if is_numpy:
                        yield pack_numpy(var)
                    else:
                        yield pack2('pattern', "*" + var)
                else:
                    yield "start = end_point"
                    yield 'end_point += ROS::Struct::calc_size("#{pattern}")'
                    if is_numpy:
                        dtype = _NUMPY_DTYPE[base_type]
                        yield unpack_numpy(var, 'length', dtype,
                                           'str[start..(end_point-1)]')
                    else:
                        yield unpack2(var, 'pattern',
                                      'str[start..(end_point-1)]')
            else:
                pattern = "%s%s" % (length, compute_struct_pattern([base_type
                                                                    ]))
                if serialize:
                    if is_numpy:
                        yield pack_numpy(var)
                    else:
                        yield pack(pattern, "*" + var)
                else:
                    yield "start = end_point"
                    yield "end_point += ROS::Struct::calc_size('%s')" % convert_to_ruby_pattern(
                        pattern)
                    if is_numpy:
                        dtype = _NUMPY_DTYPE[base_type]
                        yield unpack_numpy(var, length, dtype,
                                           'str[start..(end_point-1)]')
                    else:
                        yield unpack(var, pattern, 'str[start..(end_point-1)]')
            if not serialize and base_type == 'bool':
                # convert uint8 to bool
                if base_type == 'bool':
                    yield "%s = map(bool, %s)" % (var, var)

        else:
            #generic recursive serializer
            #NOTE: this is functionally equivalent to the is_registered branch of complex_serializer_generator

            # choose a unique temporary variable for iterating
            loop_var = 'val%s' % len(_context_stack)

            # compute the variable context and factory to use
            if base_type == 'string':
                push_context('')
                factory = string_serializer_generator(package, base_type,
                                                      loop_var, serialize)
            else:
                push_context('%s.' % loop_var)
                factory = serializer_generator(
                    msg_context, get_registered_ex(msg_context, base_type),
                    serialize, is_numpy)

            if serialize:
                yield 'for %s in %s' % (loop_var, var)
            else:
                yield '%s = []' % var
                if var_length:
                    yield 'length.times do'
                else:
                    yield '%s.times do' % length
                if base_type != 'string':
                    yield INDENT + '%s = %s' % (
                        loop_var,
                        compute_constructor(msg_context, package, base_type))
            for y in factory:
                yield INDENT + y
            if not serialize:
                yield INDENT + '%s.push(%s)' % (var, loop_var)
            pop_context()
            yield 'end'

    except MsgGenerationException:
        raise  #re-raise
    except Exception as e:
        raise MsgGenerationException(e)  #wrap
Beispiel #16
0
def msg_generator_internal(msg_context, spec, search_path):
    """
    Python code generator for .msg files. Takes in a package name,
    message name, and message specification and generates a Python
    message class.

    @param package: name of package for message
    @type  package: str
    @param name: base type name of message, e.g. 'Empty', 'String'
    @type  name: str
    @param spec: parsed .msg specification
    @type  spec: L{MsgSpec}
    """

    # #2990: have to compute md5sum before any calls to make_python_safe

    # generate dependencies dictionary. omit files calculation as we
    # rely on in-memory MsgSpecs instead so that we can generate code
    # for older versions of msg files
    try:
        genmsg.msg_loader.load_depends(msg_context, spec, search_path)
    except InvalidMsgSpec as e:
        raise MsgGenerationException("Cannot generate .msg for %s/%s: %s" %
                                     (package, name, str(e)))
    md5sum = genmsg.compute_md5(msg_context, spec)

    # remap spec names to be Python-safe
    spec = make_ruby_safe(spec)
    spec_names = spec.names

    # for not capital class like tfMessages
    name = spec.short_name
    capitalized_name = name[0].upper() + name[1:]

    # #1807 : this will be much cleaner when msggenerator library is
    # rewritten to not use globals
    clear_patterns()

    yield '# autogenerated by genrb from %s.msg. Do not edit.' % name
    yield "require 'ros/message'\n"
    import_strs = []
    for t in spec.types:
        import_strs.extend(compute_import(msg_context, spec.package, t))
    import_strs = set(import_strs)
    for i in import_strs:
        if i:
            yield i

    yield ''

    yield "module %s\n" % spec.package.capitalize()

    fulltype = '%s%s%s' % (spec.package, genmsg.SEP, name)

    #Yield data class first, e.g. Point2D
    yield 'class %s <::ROS::Message' % capitalized_name

    yield """  def self.md5sum
    \"%s\"
  end
""" % (md5sum)
    yield """  def self.type
    \"%s\"
  end
""" % (fulltype)
    if spec.has_header():
        bool_val = 'true'
    else:
        bool_val = 'false'
    yield """  def has_header?
    %s
  end
""" % bool_val
    # note: we introduce an extra newline to protect the escaping from quotes in the message
    yield """  def message_definition
    \"%s\n\"
  end""" % compute_full_text_escaped(msg_context, spec)

    if spec.constants:
        yield '  # Pseudo-constants'
        for c in spec.constants:
            if c.type == 'string':
                val = c.val
                if '"' in val and "'" in val:
                    # crude escaping of \ and "
                    escaped = c.val.replace('\\', '\\\\')
                    escaped = escaped.replace('\"', '\\"')
                    yield '  %s = "%s"' % (c.name, escaped)
                elif '"' in val:  #use raw encoding for prettiness
                    yield "  %s = r'%s'" % (c.name, val)
                elif "'" in val:  #use raw encoding for prettiness
                    yield '  %s = r"%s"' % (c.name, val)
                else:
                    yield "  %s = '%s'" % (c.name, val)
            else:
                yield '  %s = %s' % (c.name, c.val)
        yield ''
    yield "  attr_accessor " + ", ".join([":" + x for x in spec_names]) + "\n"

    yield '_REPLACE_FOR_STRUCT_'
    if len(spec_names):
        yield "  @@struct_L = ::ROS::Struct.new(\"L\")"
        yield "  @@slot_types = ['" + "','".join(spec.types) + "']"
    else:
        yield "  @@struct_L = ::ROS::Struct.new(\"L\")"
        yield "  @@slot_types = []"

    yield """
  # Constructor. You can set the default values using keyword operators.
  #
  # @param [Hash] args keyword for initializing values"""
    for (t, s) in zip(spec.types, spec_names):
        yield "  # @option args [%s] :%s initialize value" % (t, s)
    yield "  def initialize(args={})"
    if len(spec_names):
        yield "    # message fields cannot be None, assign default values for those that are"
    if len(spec_names) > 0:
        for (t, s) in zip(spec.types, spec_names):
            yield """    if args[:%s]
      @%s = args[:%s]
    else""" % (s, s, s)
            yield "      @%s = %s" % (
                s, default_value(msg_context, t, spec.package))
            yield "    end"
    yield "  end"  # end of initialize

    yield """
  # internal API method
  # @return [String] Message type string.
  def _get_types
    @slot_types
  end

  # serialize message into buffer
  # @param [IO] buff buffer
  def serialize(buff)"""
    for y in serialize_fn_generator(msg_context, spec):
        yield "    " + y
    yield "  end"
    yield """
  #  unpack serialized message in str into this message instance
  #  @param [String] str: byte array of serialized message
  def deserialize(str)
"""
    for y in deserialize_fn_generator(msg_context, spec):
        yield "    " + y
    yield "  end"
    yield "end # end of class"
    yield "end # end of module"