Skip to content
This repository
tree: 4951e1d95a
Fetching contributors…

Octocat-spinner-32-eaf2f5

Cannot retrieve contributors at this time

file 209 lines (188 sloc) 8.74 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
# a Thrift server generator for OpenZWave
# transform a server skeleton file into a fully operational server
# a.k.a. "fills in the blanks for you"
#
# (c) 2011 Elias Karakoulakis <elias.karakoulakis@gmail.com>
#
require 'rubygems'
require 'rbgccxml'

OverloadedRE = /([^_]*)(?:_(.*))/

MANAGER_INCLUDES = [
    "gen_cpp",
    "/usr/local/include/thrift/",
    "/home/ekarak/ozw/open-zwave-read-only/cpp/tinyxml",
    "/home/ekarak/ozw/open-zwave-read-only/cpp/src",
    "/home/ekarak/ozw/open-zwave-read-only/cpp/src/value_classes",
    "/home/ekarak/ozw/open-zwave-read-only/cpp/src/command_classes",
    "/home/ekarak/ozw/open-zwave-read-only/cpp/src/platform",
]

#
# must load all source files in a single batch (RbGCCXML gets confused otherwise...)
#
files = [
    "/home/ekarak/ozw/thrift4ozw/gen-cpp/RemoteManager_server.skeleton.cpp",
    "/home/ekarak/ozw/open-zwave-read-only/cpp/src/Manager.h"
]
puts "Parsing:" + files.join("\n\t")
RootNode = RbGCCXML.parse(files, :includes => MANAGER_INCLUDES)

# read skeleton file in memory as an array
output = File.open("gen-cpp/RemoteManager_server.skeleton.cpp").readlines

def ValueID_converter(arg)
    return "*g_values[#{arg}]"
end

# fix the constructor
lineno = RootNode.classes("RemoteManagerHandler").constructors[1]['line'].to_i
#~ output[lineno] = Constructor
# add our extra hidden sauce
#lineno = foo.classes("RemoteManagerHandler").constructors[1]['endline'].to_i
#output[lineno] = Converter

a = RootNode.classes("RemoteManagerHandler").methods.find(:access => :public)
b = RootNode.namespaces("OpenZWave").classes("Manager").methods.find(:access => :public)
puts "RemoteManagerHandler: #{a.size} public methods, OpenZWave::Manager: #{b.size} public methods"

RootNode.classes("RemoteManagerHandler").methods.each { |meth|
    # find line number, insert critical section enter code
    lineno = meth['line'].to_i
    #puts "Method #{meth.name} at line #{lineno}-------------------------"
    output[lineno] = "\tManager* mgr = Manager::Get();\n\tg_criticalSection.lock();\n"
    #
    target_method = nil
    target_method_name = nil
    disambiguation_hint = nil
    
    # skeleton function's name has underscore => Overloaded. Needs disambiguation.
    if md = OverloadedRE.match(meth.name) then
        target_method_name = md[1]
        disambiguation_hint = md[2]
    else
        target_method_name = meth.name
    end
    
    #
    # SEARCH FOR MATCHING FUNCTION IN OPENZWAVE::MANAGER
    #
    search_result = RootNode.namespaces("OpenZWave").classes("Manager").methods.find(:name => target_method_name, :access => :public)
    #puts "search result: #{search_result.class.name}"
    case search_result
    when RbGCCXML::QueryResult then
        raise "#{target_method_name}(): no disambiguation hint given!!!" unless disambiguation_hint
        #puts " ...Overloaded method: #{meth.name}"
        search_result.each { |node|
            # last argument's type must match disambiguation_hint
            target_method = node if node.arguments[-1].cpp_type.to_cpp =~ Regexp.new(disambiguation_hint, Regexp::IGNORECASE)
            # FIXME:: ListString => list<string>
        }
    when RbGCCXML::Method then
        #puts " ...exact match for #{meth.name}"
        target_method = search_result
    end
    
    raise "Unable to resolve target method! (#{meth.name})" unless target_method
    
    #
    # TIME TO BOOGEY
    #
    
    puts "CREATING MAPPING for (#{meth.return_type.to_cpp}) #{meth.name}"

    #Thrift transforms methods with complex return types (string, vector<...>, user-defined structs etc)
    # example 1:
    # (C++) string GetLibraryVersion( uint32 const _homeId );
    # (thrift) string GetLibraryVersion( 1:i32 _homeId );
    # (skeleton) void GetLibraryVersion(std::string& _return, const int32_t _homeId)
    #
    # example 2:
    # (C++) uint32 GetNodeNeighbors( uint32 const _homeId, uint8 const _nodeId, uint8** _nodeNeighbors );
    # (thrift) UInt32_NeighborMap GetNodeNeighbors( 1:i32 _homeId, 2:byte _nodeId);
    # (skeleton) void GetNodeNeighbors(UInt32_ListByte& _return, const int32_t _homeId, const int8_t _nodeId)
    # ozw_types.h: class UInt32_ListByte {
    # int32_t retval;
    # std::vector<int8_t> arg; *** notice manual copying needed from C-style pointer to pointers of uint8's (not very C++ish)
    # }
    #
    # example 3:
    # (C++) bool GetValueListItems( ValueID const& _id, vector<string>* o_value );
    # (thrift) Bool_ListString GetValueListItems( 1:RemoteValueID _id );
    # (skeleton) void GetValueListItems(Bool_ListString& _return, const RemoteValueID _id)
    # where the Thrift definition for Bool_ListString is:
    # (ozw_types.h):class Bool_ListString {
    # bool retval;
    # std::vector<std::string> arg;
    # }
    #
    
    #
    # STEP 1. Map arguments from target (OpenZWave::Manager) to source (skeleton server)
    #
    argmap = {}
        # KEY: target argument node
        # VALUE: hash with
        # :descriptor => source argument DESCRIPTOR STRING (eg "_return._className")
        # :node => the actual source argument node (Argument or Field)
    target_method.arguments.each {|a|
        # 1) match directly by name
        if (arg = meth.arguments.find(:name => a.name )).is_a?RbGCCXML::Argument then
            argmap[a] = {}
            argmap[a][:descriptor] = arg.name
            argmap[a][:node] = arg
        # 2) else, match as a member of Thrift's special "_return" argument (class struct)
        elsif (_ret = meth.arguments.find(:name => "_return" )) and (_ret.cpp_type.base_type.is_a?RbGCCXML::Class) and
            (arg = _ret.cpp_type.base_type.variables.find(:name => a.name)).is_a?RbGCCXML::Field then
            argmap[a] = {}
            argmap[a][:descriptor] = "_return.#{a.name}"
            argmap[a][:node] = arg
        else
            raise ("Reverse argument mapping: couldn't resolve argument '#{a.name}' in method '#{target_method.name}'!!!")
        end
    }

    #
    # STEP 2. Resolve the function call's return clause
    #
    function_return_clause = ''
    if (_return = meth.arguments.find(:name => '_return')).is_a?RbGCCXML::Argument then
        puts "Thrift special _return argument detected!" if $DEBUG
        if (_return.cpp_type.base_type.is_a?RbGCCXML::Class) and
            (retval = _return.cpp_type.base_type.variables.find(:name => 'retval')) and
            (retval.is_a?RbGCCXML::Field) then
                function_return_clause = "_return.retval = "
        else
            unless meth.return_type.name == "void" then
                function_return_clause = "_return = "
            end
        end
    end

    #
    # STEP 3. Prepare argument array (ordered by target_method's argument order)
    #
    arg_array = []
    target_method.arguments.each { |tgt_arg|
        if (hsh = argmap[tgt_arg]) then
            src_arg = hsh[:node]
            descriptor = hsh[:descriptor]
            #puts " src=#{descriptor}\ttgt=#{tgt_arg.qualified_name}"
            ampersand = (tgt_arg.cpp_type.to_cpp.include?('*') ? '&' : '')
            case src_arg.to_cpp
                when /ValueID/
                    arg_array << ValueID_converter(descriptor)
                else
                    arg_array << "(#{tgt_arg.cpp_type.to_cpp}) #{ampersand}#{descriptor}"
                    size_src = src_arg.cpp_type.base_type['size'].to_i
                    size_tgt = tgt_arg.cpp_type.base_type['size'].to_i
                    # sanity check
                    puts "WARNING!!! method '#{meth.name}': Argument '#{descriptor}' size mismatch (src=#{size_src} tgt=#{size_tgt}) - CHECK GENERATED CODE!" unless size_src == size_tgt
            end
        end
    }
    
    # Unleash the beast!
    fcall = "#{function_return_clause} mgr->#{target_method.name}(#{arg_array.compact.join(', ')})"
    case meth.return_type.name
    when "void"
        output[lineno+1] = "\t#{fcall};\n"
    else
        output[lineno+1] = "\t#{meth.return_type.to_cpp} function_result = #{fcall};\n"
    end
    
    # unlock the critical section
    output[lineno+1] << "\tg_criticalSection.unlock();\n"
    # output return statement (unless rettype == void)
    unless meth.return_type.name == "void"
        output[lineno+1] << "\treturn(function_result);\n"
    end

}

output[0] = "// Automatically generated OpenZWave::Manager_server wrapper\n"
output[1] = "// (c) 2011 Elias Karakoulakis <elias.karakoulakis@gmail.com>\n"
# write out the generated file
puts "Writing generated server...."
File.new("gen-cpp/RemoteManager_server.cpp", File::CREAT|File::TRUNC|File::RDWR, 0644) << output.join
puts "Done!"
Something went wrong with that request. Please try again.