diff --git a/redhawk-codegen/redhawk/codegen/jinja/cpp/ports/generic.py b/redhawk-codegen/redhawk/codegen/jinja/cpp/ports/generic.py index 0d815e71e..02a9ba838 100644 --- a/redhawk-codegen/redhawk/codegen/jinja/cpp/ports/generic.py +++ b/redhawk-codegen/redhawk/codegen/jinja/cpp/ports/generic.py @@ -22,14 +22,12 @@ from omniORB import CORBA from redhawk.codegen.lang import cpp -from redhawk.codegen.lang.idl import IDLInterface, IDLStruct +from redhawk.codegen.lang import idl from redhawk.codegen.jinja.ports import PortFactory from redhawk.codegen.jinja.cpp import CppTemplate from generator import CppPortGenerator -from ossie.utils.sca.importIDL import SequenceType, BaseType - if not '__package__' in locals(): # Python 2.4 compatibility __package__ = __name__.rsplit('.', 1)[0] @@ -48,77 +46,68 @@ CORBA.tk_ulonglong: 'CORBA::ULongLong' } -def baseType(typeobj, direction=None): +def cppName(typeobj): + return '::'.join(typeobj.scopedName()) + +def newObject(typeobj): + return 'new %s()' % baseType(typeobj) + +def baseType(typeobj): kind = typeobj.kind() if kind in _baseMap: return _baseMap[kind] elif kind == CORBA.tk_void: return 'void' elif kind == CORBA.tk_string: - if direction == 'out': - return 'CORBA::String_out' - else: - return 'char*' + return 'char*' elif kind == CORBA.tk_any: return 'CORBA::Any' - elif kind == CORBA.tk_alias and \ - typeobj.aliasType().kind() == CORBA.tk_string: - return 'char*' - - name = '::'.join(typeobj.scopedName()) - if kind == CORBA.tk_objref: - if direction == 'out': - return name + '_out' - else: - return name + '_ptr' - elif kind == CORBA.tk_alias and isinstance(typeobj.aliasType(), SequenceType): - if direction == 'out': - return name + '_out' - else: - return name - else: - return name -def doesStructIncludeInterface(scopedName): - repo_id = 'IDL:' - if len(scopedName) == 1: - repo_id += scopedName[0] - else: - for name in scopedName: - repo_id += name + '/' - repo_id = repo_id[:-1] - repo_id += ':1.0' - idl = IDLStruct(repo_id) - _members = idl.members() - for member_key in _members: - if _members[member_key] in ['objref', 'alias', 'struct']: - return True - return False - -def baseReturnType(typeobj): + return cppName(typeobj) + +def isVariableLength(typeobj): + # Remove any aliases + typeobj = unaliasedType(typeobj) kind = typeobj.kind() if kind in _baseMap: - return _baseMap[kind] - elif kind == CORBA.tk_void: - return 'void' - elif kind == CORBA.tk_string: - return 'char*' - elif kind == CORBA.tk_any: - return 'CORBA::Any' - - name = '::'.join(typeobj.scopedName()) + # Basic numeric types are always fixed-length + return False + elif kind in (CORBA.tk_struct, CORBA.tk_union): + # Struct and unions are variable-length if any members are + repo_id = 'IDL:' + '/'.join(typeobj.scopedName()) + ':1.0' + idl_type = idl.idlRepo.getIdlStruct(repo_id) + if kind == CORBA.tk_struct: + # Work around insufficent information in public interface by using + # "private" interface that was added for this usage + members = idl_type._fields + else: + members = idl_type.members() + for member in members: + if isVariableLength(member.memberType()): + return True + return False + else: + # Assume everything else is variable-length + return True + +def returnType(typeobj): + name = baseType(typeobj) + kind = unaliasedType(typeobj).kind() if kind == CORBA.tk_objref: return name + '_ptr' - elif kind == CORBA.tk_struct: - if doesStructIncludeInterface(typeobj.scopedName()): + elif kind in (CORBA.tk_struct, CORBA.tk_union): + if isVariableLength(typeobj): return name + '*' else: return name - elif kind == CORBA.tk_alias and isinstance(typeobj.aliasType(), SequenceType): + elif kind in (CORBA.tk_any, CORBA.tk_sequence): return name + '*' else: return name +# Preserve old name in case other places still reference it +baseReturnType = returnType + def unaliasedType(typeobj): kind = typeobj.kind() if kind != CORBA.tk_alias: @@ -127,20 +116,18 @@ def unaliasedType(typeobj): return unaliasedType(typeobj.aliasType()) def temporaryType(typeobj): - kind = typeobj.kind() - if kind in _baseMap: - return _baseMap[kind] - elif kind == CORBA.tk_void: - return 'void' - elif kind == CORBA.tk_string: - return 'CORBA::String_var' - elif kind == CORBA.tk_any: - return 'CORBA::Any' - - name = '::'.join(typeobj.scopedName()) + name = baseType(typeobj) kind = unaliasedType(typeobj).kind() - if kind in (CORBA.tk_objref, CORBA.tk_sequence) or ((kind == CORBA.tk_struct) and doesStructIncludeInterface(typeobj.scopedName())): + if kind == CORBA.tk_string: + # Use the base type even when aliased + return 'CORBA::String_var' + elif kind in (CORBA.tk_objref, CORBA.tk_sequence, CORBA.tk_any): return name + '_var' + elif kind in (CORBA.tk_struct, CORBA.tk_union): + if isVariableLength(typeobj): + return name + '_var' + else: + return name else: return name @@ -149,67 +136,77 @@ def temporaryValue(typeobj): if kind in _baseMap: return '0' elif kind == CORBA.tk_enum: - return '::'.join(typeobj.enumValues()[0].scopedName()) + enum_values = unaliasedType(typeobj).enumValues() + return cppName(enum_values[0]) elif kind == CORBA.tk_string: return 'CORBA::string_dup("")' - elif kind == CORBA.tk_sequence or ((kind == CORBA.tk_struct) and doesStructIncludeInterface(typeobj.scopedName())): - return 'new %s()' % '::'.join(typeobj.scopedName()) - elif ((kind == CORBA.tk_struct) and not doesStructIncludeInterface(typeobj.scopedName())): - return '%s()' % '::'.join(typeobj.scopedName()) + elif kind in (CORBA.tk_sequence, CORBA.tk_any): + return newObject(typeobj) + elif kind in (CORBA.tk_struct, CORBA.tk_union): + if isVariableLength(typeobj): + return newObject(typeobj) + else: + # The default constructor is fine, no need to generate a temporary + # that does the same thing + return None else: return None -def passByValue(argType,direction): - if direction == 'in': - if not passConst(argType,direction): - return True - else: - kind = argType.kind() - if kind == CORBA.tk_string or \ - (kind == CORBA.tk_alias and argType.aliasType().kind() == CORBA.tk_string): - return True - else: - return False +def inType(typeobj): + kind = unaliasedType(typeobj).kind() + if kind == CORBA.tk_string: + # Strings are always "const char*" even when aliased because for a + # typedef the const applies to the full type (i.e., the pointer), but + # the contract is that the contents (char) cannot be modified + return 'const char*' + + name = baseType(typeobj) + if kind in _baseMap or kind == CORBA.tk_enum: + # Basic types are passed by value + return name + elif kind == CORBA.tk_objref: + return name + '_ptr' else: - kind = argType.kind() - if kind == CORBA.tk_alias and isinstance(argType.aliasType(), SequenceType): - if direction == 'out': - return True - elif direction == 'inout': - return False - elif kind == CORBA.tk_string: - if direction == 'out': - return True - elif direction == 'inout': - return False - elif kind == CORBA.tk_objref: - return True - else: - return False + return 'const '+name+'&' -def passConst(argType,direction): - if direction == 'in': - kind = argType.kind() - if kind in _baseMap or \ - (kind == CORBA.tk_alias and isinstance(argType.aliasType(), BaseType) and argType.aliasType().kind() != CORBA.tk_string) or \ - kind == CORBA.tk_objref or kind == CORBA.tk_enum: - return False - else: - return True +def outType(typeobj): + if typeobj.kind() == CORBA.tk_string: + # Strings (just the base type, not aliases) require special handling in + # that the out type is not derived from the normal mapping (char*) + return 'CORBA::String_out' + + name = baseType(typeobj) + kind = unaliasedType(typeobj).kind() + if kind in _baseMap or kind == CORBA.tk_enum: + # CORBA technically has "_out" typedefs for primitive types, but for + # consistency with prior versions just use a reference + return name + '&' + elif kind in (CORBA.tk_struct, CORBA.tk_union) and not isVariableLength(typeobj): + # Since omniORB directly uses the reference type, copy that here + return name + '&' + else: + return name+'_out' + +def inoutType(typeobj): + kind = unaliasedType(typeobj).kind() + if kind == CORBA.tk_string: + # Just for consistency with omniORB, remap all string types to the base + # type + return 'char*&' + + name = baseType(typeobj) + if kind == CORBA.tk_objref: + return name + '_ptr&' else: - return False + return name + '&' def argumentType(argType, direction): - name = baseType(argType,direction) - if not direction == 'in': - # sequence is special case because arg is different in C++ than the IDL - if argType == 'sequence': - return name + '_out' - if passConst(argType,direction): - name = 'const '+name - if not passByValue(argType,direction): - name = name + '&' - return name + if direction == 'in': + return inType(argType) + elif direction == 'out': + return outType(argType) + else: + return inoutType(argType) class GenericPortFactory(PortFactory): def match(self, port): @@ -247,24 +244,14 @@ def hasInOut(self): def operations(self): for op in self.idl.operations(): - _out = False - for p in op.params: - if p.direction == 'out': - _out = True - break - _inout = False - for p in op.params: - if p.direction == 'inout': - _inout = True - break yield {'name': op.name, 'arglist': ', '.join('%s %s' % (argumentType(p.paramType,p.direction), p.name) for p in op.params), 'argnames': ', '.join(p.name for p in op.params), - 'hasout': _out, - 'hasinout': _inout, + 'hasout': any(p.direction == 'out' for p in op.params), + 'hasinout': any(p.direction == 'inout' for p in op.params), 'temporary': temporaryType(op.returnType), 'initializer': temporaryValue(op.returnType), - 'returns': baseReturnType(op.returnType)} + 'returns': returnType(op.returnType)} # # for attributes of an interface...provide manipulator methods # @@ -278,7 +265,7 @@ def operations(self): 'readwrite_attr': readwrite_attr, 'temporary': temporaryType(attr.attrType), 'initializer': temporaryValue(attr.attrType), - 'returns': baseReturnType(attr.attrType)} + 'returns': returnType(attr.attrType)} if not attr.readonly: yield {'name': attr.name, 'arglist': argumentType(attr.attrType,'in') + ' data', diff --git a/redhawk-codegen/redhawk/codegen/jinja/cpp/ports/templates/generic.uses.cpp b/redhawk-codegen/redhawk/codegen/jinja/cpp/ports/templates/generic.uses.cpp index 6c6baf6a9..da48145d2 100644 --- a/redhawk-codegen/redhawk/codegen/jinja/cpp/ports/templates/generic.uses.cpp +++ b/redhawk-codegen/redhawk/codegen/jinja/cpp/ports/templates/generic.uses.cpp @@ -32,24 +32,8 @@ Port_Uses_base_impl(port_name) { } /*{% for operation in portgen.operations() %}*/ + //% set hasreturn = operation.returns != 'void' -/*{% if hasreturn %}*/ -/*{% set returnstate='true' %}*/ -/*{% else %}*/ -/*{% set returnstate='false' %}*/ -/*{% endif %}*/ -//% set hasout = operation.hasout -/*{% if hasout %}*/ -/*{% set _hasout='true' %}*/ -/*{% else %}*/ -/*{% set _hasout='false' %}*/ -/*{% endif %}*/ -//% set hasinout = operation.hasinout -/*{% if hasinout %}*/ -/*{% set _hasinout='true' %}*/ -/*{% else %}*/ -/*{% set _hasinout='false' %}*/ -/*{% endif %}*/ /*{% if operation.readwrite_attr %}*/ ${operation.returns} ${classname}::${operation.name}() { return _get_${operation.name}(""); @@ -71,7 +55,7 @@ Port_Uses_base_impl(port_name) boost::mutex::scoped_lock lock(updatingPortsLock); // don't want to process while command information is coming in - __evaluateRequestBasedOnConnections(__connection_id__, ${returnstate}, ${_hasinout}, ${_hasout}); + __evaluateRequestBasedOnConnections(__connection_id__, ${cpp.boolLiteral(hasreturn)}, ${cpp.boolLiteral(operation.hasinout)}, ${cpp.boolLiteral(operation.hasout)}); if (this->active) { for (i = this->outConnections.begin(); i != this->outConnections.end(); ++i) { if (not __connection_id__.empty() and __connection_id__ != (*i).second) diff --git a/redhawk-codegen/redhawk/codegen/jinja/cpp/service/mapping.py b/redhawk-codegen/redhawk/codegen/jinja/cpp/service/mapping.py index 8f33f62b1..b0592da3a 100644 --- a/redhawk-codegen/redhawk/codegen/jinja/cpp/service/mapping.py +++ b/redhawk-codegen/redhawk/codegen/jinja/cpp/service/mapping.py @@ -70,12 +70,12 @@ def getOperations(self, idl): operations.append({'name': op.name, 'arglist': ', '.join('%s %s' % (generic.argumentType(p.paramType,p.direction), p.name) for p in op.params), 'argnames': ', '.join(p.name for p in op.params), - 'returns': generic.baseReturnType(op.returnType)}) + 'returns': generic.returnType(op.returnType)}) for attr in idl.attributes(): operations.append({'name': attr.name, 'arglist': '', 'argnames': '', - 'returns': generic.baseReturnType(attr.attrType)}) + 'returns': generic.returnType(attr.attrType)}) if not attr.readonly: operations.append({'name': attr.name, 'arglist': generic.baseType(attr.attrType) + ' data', diff --git a/redhawk-codegen/redhawk/codegen/lang/cpp.py b/redhawk-codegen/redhawk/codegen/lang/cpp.py index 80a473ef0..d8ac62cdd 100644 --- a/redhawk-codegen/redhawk/codegen/lang/cpp.py +++ b/redhawk-codegen/redhawk/codegen/lang/cpp.py @@ -79,6 +79,12 @@ def stringLiteral(string): def charLiteral(string): return "'" + string + "'" +def boolLiteral(value): + if value: + return TRUE + else: + return FALSE + def complexType(typename): return 'std::complex<%s>' % (cppType(typename),) @@ -127,10 +133,7 @@ def literal(value, typename, complex=False): else: return stringLiteral(value) elif typename == CorbaTypes.BOOLEAN: - if parseBoolean(value): - return TRUE - else: - return FALSE + return boolLiteral(parseBoolean(value)) elif typename in (CorbaTypes.LONGLONG, CorbaTypes.ULONGLONG): # Explicitly mark the literal as a 'long long' for 32-bit systems if not isinstance(value, list): diff --git a/redhawk/src/base/framework/python/ossie/utils/idllib.py b/redhawk/src/base/framework/python/ossie/utils/idllib.py index 5931f6bdc..0e0fde31b 100644 --- a/redhawk/src/base/framework/python/ossie/utils/idllib.py +++ b/redhawk/src/base/framework/python/ossie/utils/idllib.py @@ -102,7 +102,7 @@ def _importFile(self, filename): self._interfaces[interface.repoId] = interface # Mark the file that provided this interface as parsed - if not isinstance(interface, importIDL.IdlStruct): + if isinstance(interface, importIDL.Interface): self._parsed.add(interface.fullpath) # Mark the file as parsed in case it didn't contain any interfaces diff --git a/redhawk/src/base/framework/python/ossie/utils/sca/importIDL.py b/redhawk/src/base/framework/python/ossie/utils/sca/importIDL.py index 056be07b6..e09c2dde5 100644 --- a/redhawk/src/base/framework/python/ossie/utils/sca/importIDL.py +++ b/redhawk/src/base/framework/python/ossie/utils/sca/importIDL.py @@ -142,12 +142,36 @@ def __init__(self, scopedName, values): def enumValues(self): return self._enumValues +class Member(object): + def __init__(self, name, memberType): + self._name = name + self._memberType = memberType + + def name(self): + return self._name + + def memberType(self): + return self._memberType + +class Union(NamedType): + def __init__(self, scopedName, repoId, discriminant, members): + super(Union,self).__init__(CORBA.tk_union, scopedName) + self.name = scopedName[-1] + self.repoId = repoId + self._discriminant = discriminant + self._members = members + + def discriminant(self): + return self._discriminant + + def members(self): + return self._members + def ExceptionType(type): return NamedType(CORBA.tk_except, type.scopedName()) -# Internal cache of parsed IDL interfaces -_interfaces = {} -_structs = {} +# Internal cache of parsed IDL types +_cache = {} class Interface: def __init__(self,name,nameSpace="",operations=[],filename="",fullpath="",repoId=""): @@ -185,7 +209,6 @@ def __repr__(self): return retstr - class Operation: def __init__(self,name,returnType,params=[]): self.name = name @@ -293,8 +316,11 @@ def __init__(self, node): self.name = '::'.join(node.scopedName()) self.repoId = node.repoId() self.members = {} + self._fields = [] for member in node.members(): - self.members[member.declarators()[0].scopedName()[-1]] = baseTypes[member.memberType().kind()] + name = member.declarators()[0].scopedName()[-1] + self.members[name] = baseTypes[member.memberType().kind()] + self._fields.append(Member(name, IDLType.instance(member.memberType()))) class ExampleVisitor (idlvisitor.AstVisitor): def __init__(self,*args): @@ -312,20 +338,44 @@ def visitModule(self, node): n.accept(self) def visitStruct(self, node): - # create the Attribute object - _struct = IdlStruct(node) - _structs[node.repoId()] = _struct - self.myStructs.append(_struct) + # Use cached post-processed struct if available + struct = _cache.get(node.repoId(), None) + if not struct: + struct = IdlStruct(node) + _cache[node.repoId()] = struct + self.myStructs.append(struct) + + def visitUnion(self, node): + # Use cached post-processed union if available + union = _cache.get(node.repoId(), None) + if not union: + members = [] + for case in node.cases(): + name = case.declarator().identifier() + idl_type = IDLType.instance(case.caseType()) + # Ignore case labels here, until there is a need to generate code + # based upon them + members.append(Member(name, idl_type)) + + union = Union(node.scopedName(), node.repoId(), IDLType.instance(node.switchType()), members) + _cache[union.repoId] = union + + self.myStructs.append(union) def visitInterface(self, node): # Use cached post-processed interface if available - global _interfaces - interface = _interfaces.get(node.repoId(), None) + interface = _cache.get(node.repoId(), None) if not interface: visitor = InterfaceVisitor() node.accept(visitor) interface = visitor.interface - _interfaces[node.repoId()] = interface + _cache[node.repoId()] = interface + + # Process nested declarations regardless of cache status in case unions + # and structs were not requested last time + for decl in node.declarations(): + decl.accept(self) + self.myInterfaces.append(interface) def run(tree, args): @@ -377,8 +427,7 @@ def getInterfacesFromFile(filename, includepath=None, getStructs=False): x.filename = x.filename[:-4] #remove the .idl suffix if getStructs: - for _struct in structs: - ints = ints + structs + ints = ints + structs return ints diff --git a/redhawk/src/testing/tests/idl/TestIdl.idl b/redhawk/src/testing/tests/idl/TestIdl.idl new file mode 100644 index 000000000..81a09b78e --- /dev/null +++ b/redhawk/src/testing/tests/idl/TestIdl.idl @@ -0,0 +1,50 @@ +/* + * This file is protected by Copyright. Please refer to the COPYRIGHT file + * distributed with this source distribution. + * + * This file is part of REDHAWK core. + * + * REDHAWK core is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * REDHAWK core is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +/** + * This module contains IDL constructs for testing the Python IDL library. + */ +module TestIdl { + interface StructInterface { + struct NestedStruct { + long field; + }; + + NestedStruct func(); + }; + + union BasicUnion switch (short) { + case 0: octet octetValue; + case 1: long longValue; + case 2: + case 3: + float floatValue; + default: string stringValue; + }; + + interface UnionInterface { + union NestedUnion switch (long) { + case 0: string stringValue; + default: any anyValue; + }; + + NestedUnion func(); + }; +}; diff --git a/redhawk/src/testing/tests/test_00_PythonUtils.py b/redhawk/src/testing/tests/test_00_PythonUtils.py index fcc8a88fe..cbd334c88 100644 --- a/redhawk/src/testing/tests/test_00_PythonUtils.py +++ b/redhawk/src/testing/tests/test_00_PythonUtils.py @@ -19,12 +19,15 @@ # along with this program. If not, see http://www.gnu.org/licenses/. # +import os import unittest import weakref from ossie.utils.notify import notification from ossie.utils import weakobj +from ossie.utils import idllib + class TestClass(object): def foo(self): return 'foo' @@ -263,3 +266,45 @@ def test_WeakBoundMethodCallback(self): del obj3 self.assertEqual(len(children), 0) + +class TestIdlLibrary(unittest.TestCase): + def setUp(self): + # Use a local IDL library path to have complete control over tests + idldir = os.path.abspath(os.path.join(os.path.dirname(__file__), 'idl')) + self.lib = idllib.IDLLibrary() + self.lib.addSearchPath(idldir) + + def test_NestedStruct(self): + """ + Tests that the IDL library allows looking up structs nested under + interfaces. + """ + repo_id = 'IDL:TestIdl/StructInterface/NestedStruct:1.0' + try: + structdef = self.lib.getIdlStruct(repo_id) + except idllib.UnknownInterfaceError as exc: + self.fail('Nested IDL struct was not found') + self.assertEqual(structdef.repoId, repo_id) + + def test_Union(self): + repo_id = 'IDL:TestIdl/BasicUnion:1.0' + try: + union = self.lib.getIdlStruct(repo_id) + except idllib.UnknownInterfaceError as exc: + self.fail('IDL union was not found') + self.assertEqual(union.repoId, repo_id) + + # Make sure it has the exactly the expected members + expected = ['octetValue', 'longValue', 'floatValue', 'stringValue'] + expected.sort() + actuals = [m.name() for m in union.members()] + actuals.sort() + self.assertEqual(expected, actuals) + + def test_NestedUnion(self): + repo_id = 'IDL:TestIdl/UnionInterface/NestedUnion:1.0' + try: + union = self.lib.getIdlStruct(repo_id) + except idllib.UnknownInterfaceError as exc: + self.fail('Nested IDL union was not found') + self.assertEqual(union.repoId, repo_id)