diff options
Diffstat (limited to 'cpp/rubygen')
-rwxr-xr-x | cpp/rubygen/amqpgen.rb | 65 | ||||
-rwxr-xr-x | cpp/rubygen/cppgen.rb | 31 | ||||
-rwxr-xr-x | cpp/rubygen/generate | 12 | ||||
-rwxr-xr-x | cpp/rubygen/samples/runme | 18 | ||||
-rw-r--r-- | cpp/rubygen/templates/InvocationVisitor.rb | 105 | ||||
-rwxr-xr-x | cpp/rubygen/templates/Operations.rb (renamed from cpp/rubygen/samples/Operations.rb) | 51 | ||||
-rw-r--r-- | cpp/rubygen/templates/Session.rb | 6 | ||||
-rw-r--r-- | cpp/rubygen/templates/structs.rb | 345 |
8 files changed, 593 insertions, 40 deletions
diff --git a/cpp/rubygen/amqpgen.rb b/cpp/rubygen/amqpgen.rb index 7d635c99c2..044a2497e0 100755 --- a/cpp/rubygen/amqpgen.rb +++ b/cpp/rubygen/amqpgen.rb @@ -60,8 +60,7 @@ class AmqpField < AmqpElement # Get AMQP type for a domain name. def domain_type(name) - domain=elements["/amqp/domain[@name='#{name}']"] - (domain and domain.attributes["type"] or name) + @cache_domain_type ||= domain_type_impl(name) end # Get the AMQP type of this field. @@ -70,6 +69,22 @@ class AmqpField < AmqpElement dt=domain_type d if d (dt or attributes["type"]) end + + # determine whether this type is defined as a struct in the spec + def defined_as_struct() + @cache_defined_as_struct ||= defined_as_struct_impl() + end + +private + def domain_type_impl(name) + domain=elements["/amqp/domain[@name='#{name}']"] + (domain and domain.attributes["type"] or name) + end + + def defined_as_struct_impl() + domain_name = attributes["domain"] + elements["/amqp/domain[@name='#{domain_name}']/struct"] + end end # AMQP method element @@ -98,6 +113,18 @@ class AmqpMethod < AmqpElement } end + def has_result?() + @cache_has_result ||= elements["result/struct"] + end + + def result_struct() + @cache_result_struct ||= has_result? ? AmqpStruct.new(elements["result/struct"], self) : nil + end + + def is_server_method?() + @cache_is_server_method ||= elements["chassis[@name='server']"] + end + def request?() responds_to().empty?; end def response?() not request?; end end @@ -116,7 +143,25 @@ class AmqpClass < AmqpElement def amqp_methods_on(chassis) @cache_amqp_methods_on ||= { } @cache_amqp_methods_on[chassis] ||= elements.collect("method/chassis[@name='#{chassis}']/..") { |m| AmqpMethod.new(m,self) }.sort_by_name + end end + + +# AMQP struct element. +class AmqpStruct < AmqpElement + def initialize(xml,amqp) super; end + + def type() attributes["type"]; end + + def size() attributes["size"]; end + + def result?() parent.name == "result"; end + + def domain?() parent.name == "domain"; end + + def fields() + @cache_fields ||= elements.collect("field") { |f| AmqpField.new(f,self); } + end end # AMQP root element. @@ -152,9 +197,23 @@ class AmqpRoot < AmqpElement @cache_amqp_classes ||= elements.collect("class") { |c| AmqpClass.new(c,self) }.sort_by_name end + + def amqp_structs() + @cache_amqp_structs ||= amqp_result_structs + amqp_domain_structs + end + + def amqp_domain_structs() + @cache_amqp_domain_structs ||= elements.collect("domain/struct") { |s| AmqpStruct.new(s, self) } + end + + def amqp_result_structs() + @cache_amqp_result_structs ||= amqp_methods.collect { |m| m.result_struct }.compact + end # Return all methods on all classes. - def amqp_methods() amqp_classes.collect { |c| c.amqp_methods }.flatten; end + def amqp_methods() + @cache_amqp_methods ||= amqp_classes.collect { |c| c.amqp_methods }.flatten; + end # Return all methods on chassis for all classes. def amqp_methods_on(chassis) diff --git a/cpp/rubygen/cppgen.rb b/cpp/rubygen/cppgen.rb index a3314c7e11..109005e743 100755 --- a/cpp/rubygen/cppgen.rb +++ b/cpp/rubygen/cppgen.rb @@ -62,9 +62,11 @@ end # Additional methods for AmqpField. class AmqpField def cppname() @cache_cppname ||= name.lcaps.cppsafe; end - def cpptype() @cache_cpptype ||= amqp_root.param_type(field_type); end + def cpptype() @cache_cpptype ||= defined_as_struct ? "const " + field_type.caps + "&" : amqp_root.param_type(field_type); end + def cpp_member_type() @cache_cpp_member_type ||= defined_as_struct ? field_type.caps : amqp_root.member_type(field_type); end def cppret_type() @cache_cpptype ||= amqp_root.return_type(field_type); end def type_name () @type_name ||= cpptype+" "+cppname; end + def bit?() field_type == "bit" end end # Additional methods for AmqpMethod @@ -82,6 +84,32 @@ class AmqpClass end end +class AmqpStruct + def cppname() + @cache_cppname ||= cppname_impl() + end + +private + + def cppname_impl() + #The name of the struct comes from the context it appears in: + #structs defined in a domain get their name from the domain + #element, structs defined in a result element get their name by + #appending 'Result' to the enclosing method name. + if (domain?) + parent.attributes["name"].caps + else + if (result?) + method = parent.parent + method.parent.attributes["name"].caps + method.attributes["name"].caps + "Result" + else + raise "Bad struct context: expected struct to be child of <domain> or <result>" + end + end + + end +end + # Additional methos for AmqpRoot class AmqpRoot # FIXME aconway 2007-06-20: fix u_int types, should be uint @@ -97,6 +125,7 @@ class AmqpRoot "table"=>["FieldTable", "const FieldTable&", "const FieldTable&"], "content"=>["Content", "const Content&", "const Content&"], "rfc1982-long-set"=>["SequenceNumberSet", "const SequenceNumberSet&", "const SequenceNumberSet&"], + "long-struct"=>["string", "const string&"], "uuid"=>["string", "const string&"] # FIXME should be: ["Uuid", "const Uuid&", "const Uuid&"] } diff --git a/cpp/rubygen/generate b/cpp/rubygen/generate index 14b273b688..52313186ed 100755 --- a/cpp/rubygen/generate +++ b/cpp/rubygen/generate @@ -38,6 +38,10 @@ if makefile generator_files=Dir["**/*.rb"] << File.basename(__FILE__) Dir.chdir dir rgen_generator=generator_files.map{ |f| "$(rgen_dir)/#{f}" } + rgen_srcs=GenFiles.get.map{ |f| "#{Outdir}/#{f}" } + rgen_client_cpp = rgen_srcs.select { |f| f =~ /qpid\/client\/.+\.cpp$/ } + rgen_common_cpp = rgen_srcs.select { |f| f =~ /qpid\/framing\/.+\.cpp$/ } + rgen_h = rgen_srcs.select { |f| f =~ /.+\.h$/ } File.open(makefile, 'w') { |out| out << <<EOS @@ -46,7 +50,13 @@ if makefile rgen_generator=#{rgen_generator.join(" \\\n ")} -rgen_srcs=#{GenFiles.get.join(" \\\n ")} +rgen_client_cpp=#{rgen_client_cpp.join(" \\\n ")} + +rgen_common_cpp=#{rgen_common_cpp.join(" \\\n ")} + +rgen_h=#{rgen_h.join(" \\\n ")} + +rgen_srcs=rgen_h rgen_client_cpp rgen_common_cpp if GENERATE $(rgen_srcs) $(srcdir)/#{File.basename makefile}: $(rgen_generator) $(specs) diff --git a/cpp/rubygen/samples/runme b/cpp/rubygen/samples/runme deleted file mode 100755 index e06d128707..0000000000 --- a/cpp/rubygen/samples/runme +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env ruby -# -# Run this script to generate code for the C++ broker. -# -$: << ".." # Include .. in load path -if ARGV.size < 2 - puts "Usage: <output_directory> <amqp .xml files...>" - puts "Note: passing '-' as the output directory lists generated files." - exit 1 -end - -require 'amqpgen' -Amqp=AmqpRoot.new(*ARGV[1,ARGV.size]) - -require 'Proxy.rb' -require 'Operations.rb' - - diff --git a/cpp/rubygen/templates/InvocationVisitor.rb b/cpp/rubygen/templates/InvocationVisitor.rb new file mode 100644 index 0000000000..f3ac03ece2 --- /dev/null +++ b/cpp/rubygen/templates/InvocationVisitor.rb @@ -0,0 +1,105 @@ +#!/usr/bin/env ruby +$: << ".." # Include .. in load path +require 'cppgen' + +class InvocationVisitor < CppGen + + def initialize(outdir, amqp) + super(outdir, amqp) + @namespace="qpid::framing" + @classname="InvocationVisitor" + @filename="qpid/framing/InvocationVisitor" + end + + def invocation_args(m) + if (m.amqp_parent.name == "message" && (m.name == "transfer" || m.name == "append")) + "body" + else + m.param_names.collect {|p| "body.get" + p.caps + "()" }.join(",\n") + end + end + + def null_visit(m) + genl "void InvocationVisitor::visit(const #{m.body_name}&){}" + end + + def define_visit(m) + if (m.fields.size > 0) + body = "body" + else + body = "/*body*/" + end + gen <<EOS +void InvocationVisitor::visit(const #{m.body_name}& #{body}) +{ + AMQP_ServerOperations::#{m.amqp_parent.cppname}Handler* ptr(0); + if (invocable) { + ptr = dynamic_cast<AMQP_ServerOperations::#{m.amqp_parent.cppname}Handler*>(invocable); + } else { + ptr = ops->get#{m.amqp_parent.cppname}Handler(); + } + + if (ptr) { +EOS + if (m.has_result?) + indent(2) { genl "encode<#{m.result_struct.cppname.caps}>(ptr->#{m.cppname}(#{invocation_args(m)}), result);" } + else + indent(2) { genl "ptr->#{m.cppname}(#{invocation_args(m)});" } + end + + gen <<EOS + succeeded = true; + } else { + succeeded = false; + } +} +EOS + end + + def generate() + h_file("#{@filename}") { + include "AMQP_ServerOperations.h" + include "MethodBodyConstVisitor.h" + include "qpid/framing/StructHelper.h" + namespace(@namespace) { + cpp_class("InvocationVisitor", ["public MethodBodyConstVisitor", "private StructHelper"]) { + indent { + genl "AMQP_ServerOperations* const ops;" + genl "Invocable* const invocable;" + genl "std::string result;" + genl "bool succeeded;" + } + genl "public:" + indent { + genl "InvocationVisitor(AMQP_ServerOperations* _ops) : ops(_ops), invocable(0) {}" + genl "InvocationVisitor(Invocable* _invocable) : ops(0), invocable(_invocable) {}" + genl "const std::string& getResult() const { return result; }" + genl "const bool hasResult() const { return !result.empty(); }" + genl "bool wasHandled() const { return succeeded; }" + genl "void clear();" + genl "virtual ~InvocationVisitor() {}" + } + @amqp.amqp_methods.each { |m| genl "void visit(const #{m.body_name}&);" } + } + } + } + + cpp_file("#{@filename}") { + include "InvocationVisitor.h" + @amqp.amqp_methods.each { |m| include m.body_name } + namespace(@namespace) { + genl "void InvocationVisitor::clear() {" + indent { + genl "succeeded = false;" + genl "result.clear();" + } + genl "}" + genl + @amqp.amqp_methods.each { |m| m.is_server_method? ? define_visit(m) : null_visit(m) } + } + } + end +end + +InvocationVisitor.new(Outdir, Amqp).generate(); + diff --git a/cpp/rubygen/samples/Operations.rb b/cpp/rubygen/templates/Operations.rb index 6b0a24a26c..79b82a9d59 100755 --- a/cpp/rubygen/samples/Operations.rb +++ b/cpp/rubygen/templates/Operations.rb @@ -13,46 +13,69 @@ class OperationsGen < CppGen end def handler_method (m) - gen "\nvirtual void #{m.cppname}(" - gen m.signature.join(",\n") - gen ") = 0;\n" + if (m.has_result?) + return_type = "#{m.amqp_parent.name.caps}#{m.cppname.caps}Result" + else + return_type = "void" + end + if (m.amqp_parent.name == "message" && (m.name == "transfer" || m.name == "append")) + gen "\nvirtual #{return_type} #{m.cppname}(const framing::AMQMethodBody& context) = 0;\n" + else + gen "\nvirtual #{return_type} #{m.cppname}(" + gen m.signature.join(",\n") + gen ") = 0;\n" + end end def handler_classname(c) c.name.caps+"Handler"; end def handler_class(c) - handlerclass=handler_classname c - gen <<EOS + if (!c.amqp_methods_on(@chassis).empty?) + handlerclass=handler_classname c + gen <<EOS // ==================== class #{handlerclass} ==================== -class #{handlerclass} : Invocable { +class #{handlerclass} : public virtual Invocable { // Constructors and destructors public: #{handlerclass}(){}; virtual ~#{handlerclass}() {} // Protocol methods EOS - c.amqp_methods_on(@chassis).each { |m| handler_method(m) } - gen <<EOS + c.amqp_methods_on(@chassis).each { |m| handler_method(m) } + gen <<EOS }; // class #{handlerclass} EOS + end end def handler_get(c) - handlerclass=handler_classname c - gen "virtual #{handlerclass}* get#{handlerclass}() = 0;\n" + if (!c.amqp_methods_on(@chassis).empty?) + handlerclass=handler_classname c + gen "virtual #{handlerclass}* get#{handlerclass}() = 0;\n" + end end def generate() - h_file("#{@classname}.h") { + h_file("qpid/framing/#{@classname}.h") { gen <<EOS #include <sstream> #include "qpid/framing/ProtocolVersion.h" +#include "qpid/framing/amqp_structs.h" namespace qpid { namespace framing { +class AMQMethodBody; + +class Invocable +{ +protected: + Invocable() {} + virtual ~Invocable() {} +}; + class #{@classname} { public: @@ -60,8 +83,8 @@ class #{@classname} { virtual ProtocolVersion getVersion() const = 0; - // Include framing constant declarations - #include "AMQP_Constants.h" + // Include framing constant declarations - why? + //#include "qpid/framing/AMQP_Constants.h" // Inner classes EOS @@ -74,7 +97,7 @@ EOS indent { @amqp.amqp_classes.each { |c| handler_get(c) } } gen <<EOS }; /* class #{@classname} */ -} +}} EOS } end diff --git a/cpp/rubygen/templates/Session.rb b/cpp/rubygen/templates/Session.rb index d5b6d9e8c2..0d5808318a 100644 --- a/cpp/rubygen/templates/Session.rb +++ b/cpp/rubygen/templates/Session.rb @@ -67,9 +67,9 @@ class SessionGen < CppGen #include "qpid/framing/amqp_framing.h" #include "qpid/framing/ProtocolVersion.h" #include "qpid/framing/MethodContent.h" -#include "ConnectionImpl.h" -#include "Response.h" -#include "SessionCore.h" +#include "qpid/client/ConnectionImpl.h" +#include "qpid/client/Response.h" +#include "qpid/client/SessionCore.h" namespace qpid { namespace client { diff --git a/cpp/rubygen/templates/structs.rb b/cpp/rubygen/templates/structs.rb new file mode 100644 index 0000000000..972a0386b9 --- /dev/null +++ b/cpp/rubygen/templates/structs.rb @@ -0,0 +1,345 @@ +#!/usr/bin/env ruby +# Usage: output_directory xml_spec_file [xml_spec_file...] +# +$: << '..' +require 'cppgen' + +class StructGen < CppGen + + def initialize(outdir, amqp) + super(outdir, amqp) + end + + EncodingMap={ + "octet"=>"Octet", + "short"=>"Short", + "long"=>"Long", + "longlong"=>"LongLong", + "longstr"=>"LongString", + "shortstr"=>"ShortString", + "timestamp"=>"LongLong", + "uuid"=>"ShortString",#FIXME + "table"=>"FieldTable", + "content"=>"Content", + "long-struct"=>"LongString" + } + SizeMap={ + "octet"=>"1", + "short"=>"2", + "long"=>"4", + "longlong"=>"8", + "timestamp"=>"8" + } + + ValueTypes=["octet", "short", "long", "longlong", "timestamp"] + + def printable_form(f) + if (f.cpptype == "u_int8_t") + return "(int) " + f.cppname + else + return f.cppname + end + end + + def generate_encode(f, combined) + if (f.field_type == "bit") + genl "uint8_t #{f.cppname}_bits = #{f.cppname};" + count = 0 + combined.each { |c| genl "#{f.cppname}_bits |= #{c.cppname} << #{count += 1};" } + genl "buffer.putOctet(#{f.cppname}_bits);" + else + encoded = EncodingMap[f.field_type] + if (encoded) + genl "buffer.put#{encoded}(#{f.cppname});" + else + genl "#{f.cppname}.encode(buffer);" + end + end + end + + def generate_decode(f, combined) + if (f.field_type == "bit") + genl "uint8_t #{f.cppname}_bits = buffer.getOctet();" + genl "#{f.cppname} = 1 & #{f.cppname}_bits;" + count = 0 + combined.each { |c| genl "#{c.cppname} = (1 << #{count += 1}) & #{f.cppname}_bits;" } + else + encoded = EncodingMap[f.field_type] + if (encoded) + if (ValueTypes.include?(f.field_type)) + genl "#{f.cppname} = buffer.get#{encoded}();" + else + genl "buffer.get#{encoded}(#{f.cppname});" + end + else + genl "#{f.cppname}.decode(buffer);" + end + end + end + + def generate_size(f, combined) + if (f.field_type == "bit") + names = ([f] + combined).collect {|g| g.cppname} + genl "+ 1 //#{names.join(", ")}" + else + size = SizeMap[f.field_type] + if (size) + genl "+ #{size} //#{f.cppname}" + elsif (f.cpp_member_type == "SequenceNumberSet") + genl "+ #{f.cppname}.encodedSize()" + else + encoded = EncodingMap[f.field_type] + gen "+ 4 " if encoded == "LongString" + gen "+ 1 " if encoded == "ShortString" + genl "+ #{f.cppname}.size()" + end + end + end + + def process_fields(s) + last = nil + count = 0 + bits = [] + s.fields.each { + |f| if (last and last.bit? and f.bit? and count < 7) + count += 1 + bits << f + else + if (last and last.bit?) + yield last, bits + count = 0 + bits = [] + end + if (not f.bit?) + yield f + end + last = f + end + } + if (last and last.bit?) + yield last, bits + end + end + + def methodbody_extra_defs(s) + gen <<EOS + using AMQMethodBody::accept; + void accept(MethodBodyConstVisitor& v) const { v.visit(*this); } + + inline ClassId amqpClassId() const { return CLASS_ID; } + inline MethodId amqpMethodId() const { return METHOD_ID; } +EOS + if (s.is_server_method?) + gen <<EOS + void invoke(AMQP_ServerOperations& target) + { + target.get#{s.amqp_parent.cppname}Handler()->#{s.cppname} + ( +EOS + if (s.amqp_parent.name == "message" && (s.name == "transfer" || s.name == "append")) + indent(4) { genl "*this" } + else + indent(4) { genl s.param_names.join(",\n") } + end + + genl <<EOS + ); + } + + bool invoke(Invocable* target) + { + AMQP_ServerOperations::#{s.amqp_parent.cppname}Handler* ptr + = dynamic_cast<AMQP_ServerOperations::#{s.amqp_parent.cppname}Handler*>(target); + if (ptr) { + ptr->#{s.cppname}( +EOS + if (s.amqp_parent.name == "message" && (s.name == "transfer" || s.name == "append")) + indent(5) { genl "*this" } + else + indent(5) { genl s.param_names.join(",\n") } + end + + gen <<EOS + ); + return true; + } else { + return false; + } + } +EOS + end + end + + def define_constructor(name, s) + if (s.fields.size > 0) + genl "#{name}(" + if (s.kind_of? AmqpMethod) + indent {gen "ProtocolVersion, "} + end + indent { gen s.fields.collect { |f| "#{f.cpptype} _#{f.cppname}" }.join(",\n") } + gen ")" + genl ": " if s.fields.size > 0 + indent { gen s.fields.collect { |f| " #{f.cppname}(_#{f.cppname})" }.join(",\n") } + genl " {}" + end + if (s.kind_of? AmqpMethod) + genl "#{name}(ProtocolVersion=ProtocolVersion()) {}" + end + end + + def define_accessors(f) + genl "void set#{f.name.caps}(#{f.cpptype} _#{f.cppname}) { #{f.cppname} = _#{f.cppname}; }" + genl "#{f.cpptype} get#{f.name.caps}() const { return #{f.cppname}; }" + end + + def define_struct(s) + classname = s.cppname + inheritance = "" + if (s.kind_of? AmqpMethod) + classname = s.body_name + inheritance = ": public AMQMethodBody" + end + + h_file("qpid/framing/#{classname}.h") { + if (s.kind_of? AmqpMethod) + gen <<EOS +#include "qpid/framing/AMQMethodBody.h" +#include "qpid/framing/AMQP_ServerOperations.h" +#include "qpid/framing/MethodBodyConstVisitor.h" +EOS + end + + #need to include any nested struct definitions + s.fields.select {|f| f.defined_as_struct }.map {|f| include f.name.caps} + + gen <<EOS + +#include <ostream> +#include "qpid/framing/amqp_types_full.h" + +namespace qpid { +namespace framing { + +class #{classname} #{inheritance} { +EOS + indent { s.fields.each { |f| genl "#{f.cpp_member_type} #{f.cppname};" } } + genl "public:" + if (s.kind_of? AmqpMethod) + indent { genl "static const ClassId CLASS_ID = #{s.amqp_parent.index};" } + indent { genl "static const MethodId METHOD_ID = #{s.index};" } + end + + if (s.kind_of? AmqpStruct) + if (s.type) + if (s.result?) + #as result structs have types that are only unique to the + #class, they have a class dependent qualifier added to them + #(this is inline with current python code but a formal + #solution is expected from the WG + indent { genl "static const uint16_t TYPE = #{s.type} + #{s.amqp_parent.amqp_parent.index} * 256;" } + else + indent { genl "static const uint16_t TYPE = #{s.type};" } + end + end + end + + indent { + define_constructor(classname, s) + genl "" + s.fields.each { |f| define_accessors(f) } + } + if (s.kind_of? AmqpMethod) + methodbody_extra_defs(s) + end + if (s.kind_of? AmqpStruct) + indent {genl "friend std::ostream& operator<<(std::ostream&, const #{classname}&);" } + end + + gen <<EOS + void encode(Buffer&) const; + void decode(Buffer&, uint32_t=0); + uint32_t size() const; + void print(std::ostream& out) const; +}; /* class #{classname} */ + +}} +EOS + } + cpp_file("qpid/framing/#{classname}.cpp") { + if (s.fields.size > 0) + buffer = "buffer" + else + buffer = "/*buffer*/" + end + gen <<EOS +#include "#{classname}.h" + +using namespace qpid::framing; + +void #{classname}::encode(Buffer& #{buffer}) const +{ +EOS + indent { process_fields(s) { |f, combined| generate_encode(f, combined) } } + gen <<EOS +} + +void #{classname}::decode(Buffer& #{buffer}, uint32_t /*size*/) +{ +EOS + indent { process_fields(s) { |f, combined| generate_decode(f, combined) } } + gen <<EOS +} + +uint32_t #{classname}::size() const +{ + return 0 +EOS + indent { process_fields(s) { |f, combined| generate_size(f, combined) } } + gen <<EOS + ; +} + +void #{classname}::print(std::ostream& out) const +{ + out << "#{classname}: "; +EOS + copy = Array.new(s.fields) + f = copy.shift + + indent { + genl "out << \"#{f.name}=\" << #{printable_form(f)};" if f + copy.each { |f| genl "out << \"; #{f.name}=\" << #{printable_form(f)};" } + } + gen <<EOS +} +EOS + + if (s.kind_of? AmqpStruct) + gen <<EOS +namespace qpid{ +namespace framing{ + + std::ostream& operator<<(std::ostream& out, const #{classname}& s) + { + s.print(out); + return out; + } + +} +} +EOS + end + +} + end + + def generate() + @amqp.amqp_structs.each { |s| define_struct(s) } + @amqp.amqp_methods.each { |m| define_struct(m) } + #generate a single include file containing the list of structs for convenience + h_file("qpid/framing/amqp_structs.h") { @amqp.amqp_structs.each { |s| genl "#include \"#{s.cppname}.h\"" } } + end +end + +StructGen.new(ARGV[0], Amqp).generate() + |