summaryrefslogtreecommitdiff
path: root/cpp
diff options
context:
space:
mode:
authorAlan Conway <aconway@apache.org>2007-07-31 20:00:10 +0000
committerAlan Conway <aconway@apache.org>2007-07-31 20:00:10 +0000
commitc11f9a79eec63da7aa6e6dac248a689a9d461beb (patch)
tree7c89f9804904dd3b99f3b3e564d14278ee3a7044 /cpp
parent82d128b33fac55b8618d593c89283356ee4e192b (diff)
downloadqpid-python-c11f9a79eec63da7aa6e6dac248a689a9d461beb.tar.gz
Ruby code generator for C++.
Not yet in active use yet but two sample templates are provided. Note: same dependency story as java codegen: distribution has pre-generated code so ruby not required to build a distro. Only required for svn working copy builds. git-svn-id: https://svn.apache.org/repos/asf/incubator/qpid/trunk/qpid@561468 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'cpp')
-rw-r--r--cpp/rubygen/README20
-rwxr-xr-xcpp/rubygen/amqpgen.rb208
-rwxr-xr-xcpp/rubygen/cppgen.rb126
-rwxr-xr-xcpp/rubygen/samples/Operations.rb85
-rwxr-xr-xcpp/rubygen/samples/Proxy.rb153
-rwxr-xr-xcpp/rubygen/samples/runme18
6 files changed, 610 insertions, 0 deletions
diff --git a/cpp/rubygen/README b/cpp/rubygen/README
new file mode 100644
index 0000000000..43111d4c4f
--- /dev/null
+++ b/cpp/rubygen/README
@@ -0,0 +1,20 @@
+RUBY CODE GENERATOR
+
+amqpgen.rb: builds an AMQP model from XML files and provides generic code generation functions (e.g. capitalization of names etc)
+
+cppgen.rb: C++ specific code generation functions.
+
+A template is a ruby file that generates one or more source files.
+
+For an example run
+ samples/runme test <amqp.xml files>
+
+The first argument is a directory for generated files, remaining arguments
+are xml files.
+
+
+
+
+
+
+
diff --git a/cpp/rubygen/amqpgen.rb b/cpp/rubygen/amqpgen.rb
new file mode 100755
index 0000000000..3fc66525d8
--- /dev/null
+++ b/cpp/rubygen/amqpgen.rb
@@ -0,0 +1,208 @@
+#
+# Generic AMQP code generation library.
+#
+
+require 'delegate'
+require 'rexml/document'
+require 'pathname'
+include REXML
+
+# Handy String functions for converting names.
+class String
+ # Convert to CapitalizedForm.
+ def caps() gsub( /(^|\W)(\w)/ ) { |m| $2.upcase } end
+
+ # Convert to underbar_separated_form.
+ def bars() tr('- .','_'); end
+
+ # Convert to lowerCaseCapitalizedForm
+ def lcaps() gsub( /\W(\w)/ ) { |m| $1.upcase } end
+end
+
+# Sort an array by name.
+class Array
+ def sort_by_name()
+ sort() { |a,b| a.name <=> b.name }
+ end
+end
+
+# Add collect to Elements
+class Elements
+ def collect(xpath, &block)
+ result=[]
+ each(xpath) { |el| result << yield(el) }
+ result
+ end
+end
+
+# An AmqpElement extends (delegates to) a REXML::Element
+#
+# NB: AmqpElements cache various values, they assume that
+# the XML model does not change after the AmqpElement has
+# been created.
+#
+class AmqpElement < DelegateClass(Element)
+ def initialize(xml, amqp) super(xml); @amqp_parent=amqp; end
+
+ attr_reader :amqp_parent
+
+ # Return the name attribute, not the element name.
+ def name() attributes["name"]; end
+
+ def amqp_root()
+ amqp_parent ? amqp_parent.amqp_root : self;
+ end
+end
+
+# AMQP field element
+class AmqpField < AmqpElement
+ def initialize(xml, amqp) super; end;
+
+ # Get AMQP type for a domain name.
+ def domain_type(name)
+ domain=elements["/amqp/domain[@name='#{name}']"]
+ (domain and domain.attributes["type"] or name)
+ end
+
+ # Get the AMQP type of this field.
+ def field_type()
+ d=attributes["domain"]
+ dt=domain_type d if d
+ (dt or attributes["type"])
+ end
+end
+
+# AMQP method element
+class AmqpMethod < AmqpElement
+ def initialize(xml, amqp) super; end
+
+ def fields()
+ @cache_fields ||= elements.collect("field") { |f| AmqpField.new(f,self); }
+ end
+
+ # Responses to this method (0-9)
+ def responses()
+ @cache_responses ||= elements.collect("response") { |el| new AmqpMethod(el,self) }
+ end
+
+ # Methods this method responds to (0-9)
+ def responds_to()
+ @cache_responds_to ||= elements.collect("../method/response[@name='#{attributes['name']}']") { |el|
+ AmqpMethod.new(el.parent, amqp_parent)
+ }
+ end
+
+ def request?() responds_to().empty?; end
+ def response?() not request?; end
+end
+
+# AMQP class element.
+class AmqpClass < AmqpElement
+ def initialize(xml,amqp) super; end
+
+ def methods()
+ @cache_methods ||= elements.collect("method") { |el|
+ AmqpMethod.new(el,self)
+ }.sort_by_name
+ end
+
+ # chassis should be "client" or "server"
+ def methods_on(chassis)
+ elements.collect("method/chassis[@name='#{chassis}']/..") { |m|
+ AmqpMethod.new(m,self)
+ }.sort_by_name
+ end
+end
+
+# AMQP root element.
+class AmqpRoot < AmqpElement
+
+ # Initialize with output directory and spec files from ARGV.
+ def initialize(*specs)
+ specs.size or raise "No XML spec files."
+ specs.each { |f| File.exists?(f) or raise "Invalid XML file: #{f}"}
+ super Document.new(File.new(specs.shift)).root, nil
+ specs.each { |s| # Merge in additional specs
+ root=Document.new(File.new(s)).root
+ merge(self,root)
+ }
+ end
+
+ def version()
+ attributes["major"]+"-"+attributes["minor"]
+ end
+
+ def classes()
+ @cache_classes ||= elements.collect("class") { |c| AmqpClass.new(c,self) }.sort_by_name
+ end
+
+ # Return all methods on chassis for all classes.
+ def methods_on(chassis)
+ classes.collect { |c|
+ c.methods_on(chassis)
+ }.flatten
+ end
+
+ # Merge contents of elements.
+ def merge(to,from)
+ from.elements.each { |from_child|
+ tag,name = from_child.name, from_child.attributes["name"]
+ to_child=to.elements["./#{tag}[@name='#{name}']"]
+ to_child ? merge(to_child, from_child) : to.add(from_child.clone) }
+ end
+
+ private :merge
+
+end
+
+# Base class for code generators.
+# Supports setting a per-line prefix, useful for e.g. indenting code.
+#
+class Generator
+ # Takes directory for output or "-", meaning print file names that
+ # would be generated.
+ def initialize (outdir, amqp)
+ @amqp=amqp
+ @outdir=outdir
+ @prefix='' # For indentation or comments.
+ @indentstr=' ' # One indent level.
+ raise "Invalid output directory: #{outdir}" unless @outdir=="-" or File.directory?(@outdir)
+ end
+
+ # Create a new file, set @out.
+ def file(file)
+ puts file
+ if (@outdir != "-")
+ path=Pathname.new "#{@outdir}/#{file}"
+ path.parent.mkpath
+ path.open('w') { |@out| yield }
+ end
+ end
+
+ # Append multi-line string to generated code, prefixing each line.
+ def gen (str)
+ str.each_line { |line|
+ @out << @prefix unless @midline
+ @out << line
+ @midline = nil
+ }
+ # Note if we stopped mid-line
+ @midline = /[^\n]\z/ === str
+ end
+
+ # Generate code with added prefix.
+ def prefix(add)
+ save=@prefix
+ @prefix+=add
+ yield
+ @prefix=save
+ end
+
+ # Generate indented code
+ def indent(n=1,&block) prefix(@indentstr * n,&block); end
+
+ attr_accessor :out
+end
+
+
+
diff --git a/cpp/rubygen/cppgen.rb b/cpp/rubygen/cppgen.rb
new file mode 100755
index 0000000000..3e3800c4cd
--- /dev/null
+++ b/cpp/rubygen/cppgen.rb
@@ -0,0 +1,126 @@
+#!/usr/bin/ruby
+#
+# General purpose C++ code generation.
+#
+require 'amqpgen'
+require 'set'
+
+Copyright=<<EOS
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+EOS
+
+CppKeywords = Set.new(["and", "and_eq", "asm", "auto", "bitand",
+ "bitor", "bool", "break", "case", "catch", "char",
+ "class", "compl", "const", "const_cast", "continue",
+ "default", "delete", "do", "DomainInfo", "double",
+ "dynamic_cast", "else", "enum", "explicit", "extern",
+ "false", "float", "for", "friend", "goto", "if",
+ "inline", "int", "long", "mutable", "namespace", "new",
+ "not", "not_eq", "operator", "or", "or_eq", "private",
+ "protected", "public", "register", "reinterpret_cast",
+ "return", "short", "signed", "sizeof", "static",
+ "static_cast", "struct", "switch", "template", "this",
+ "throw", "true", "try", "typedef", "typeid",
+ "typename", "union", "unsigned", "using", "virtual",
+ "void", "volatile", "wchar_t", "while", "xor",
+ "xor_eq"])
+# Names that need a trailing "_" to avoid clashes.
+CppMangle = CppKeywords+Set.new(["string"])
+
+class String
+ def cppsafe()
+ CppMangle.include?(self) ? self+"_" : self
+ end
+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 type_name () @type_name ||= cpptype+" "+cppname; end
+end
+
+# Additional methods for AmqpMethod
+class AmqpMethod
+ def cppname() @cache_cppname ||= name.lcaps.cppsafe; end
+ def param_names() @cache_param_names ||= fields.collect { |f| f.cppname }; end
+ def signature() @cache_signature ||= fields.collect { |f| f.cpptype+" "+f.cppname }; end
+ def body_name() @cache_body_name ||= amqp_parent.name.caps+name.caps+"Body"; end
+end
+
+# Additional methos for AmqpRoot
+class AmqpRoot
+ # FIXME aconway 2007-06-20: fix u_int types, should be uint
+ CppTypeMap={
+ "bit"=> ["bool"],
+ "octet"=>["u_int8_t"],
+ "short"=>["u_int16_t"],
+ "long"=>["u_int32_t"],
+ "longlong"=>["u_int64_t"],
+ "timestamp"=>["u_int64_t"],
+ "longstr"=>["string", "const string&"],
+ "shortstr"=>["string", "const string&"],
+ "table"=>["FieldTable", "const FieldTable&", "const FieldTable&"],
+ "content"=>["Content", "const Content&", "const Content&"]
+ }
+
+ def lookup(amqptype)
+ CppTypeMap[amqptype] or raise "No cpp type for #{amqptype}";
+ end
+
+ def member_type(amqptype) lookup(amqptype)[0]; end
+ def param_type(amqptype) t=lookup(amqptype); t[1] or t[0]; end
+ def return_type(amqptype) t=lookup(amqptype); t[2] or t[0]; end
+end
+
+# Additional methods for AmqpClass
+class AmqpClass
+ def cppname() @cache_cppname ||= name.caps; end
+end
+
+class CppGen < Generator
+ def initialize(outdir, *specs)
+ super(outdir,*specs)
+ end
+
+ # Write a header file.
+ def h_file(path)
+ guard=path.upcase.tr('./-','_')
+ file(path) {
+ gen "#ifndef #{guard}\n"
+ gen "#define #{guard}\n"
+ gen Copyright
+ yield
+ gen "#endif /*!#{guard}*/\n"
+ }
+ end
+
+ # Write a .cpp file.
+ def cpp_file(path)
+ file (path) do
+ gen Copyright
+ yield
+ end
+ end
+end
+
diff --git a/cpp/rubygen/samples/Operations.rb b/cpp/rubygen/samples/Operations.rb
new file mode 100755
index 0000000000..1c245ca188
--- /dev/null
+++ b/cpp/rubygen/samples/Operations.rb
@@ -0,0 +1,85 @@
+#!/usr/bin/env ruby
+# Usage: output_directory xml_spec_file [xml_spec_file...]
+#
+$: << '..'
+require 'cppgen'
+
+class OperationsGen < CppGen
+
+ def initialize(chassis, outdir, amqp)
+ super(outdir, amqp)
+ @chassis=chassis
+ @classname="AMQP_#{@chassis.caps}Operations"
+ end
+
+ def handler_method (m)
+ gen "\nvirtual void #{m.cppname}("
+ gen m.signature.join(",\n")
+ gen ") = 0;\n"
+ end
+
+ def handler_classname(c) c.name.caps+"Handler"; end
+
+ def handler_class(c)
+ handlerclass=handler_classname c
+ gen <<EOS
+// ==================== class #{handlerclass} ====================
+class #{handlerclass} : Invocable {
+ // Constructors and destructors
+ public:
+ #{handlerclass}(){};
+ virtual ~#{handlerclass}() {}
+ // Protocol methods
+EOS
+ c.methods_on(@chassis).each { |m| handler_method(m) }
+ gen <<EOS
+}; // class #{handlerclass}
+
+
+EOS
+ end
+
+ def handler_get(c)
+ handlerclass=handler_classname c
+ gen "virtual #{handlerclass}* get#{handlerclass}() = 0;\n"
+ end
+
+ def generate()
+ h_file("#{@classname}.h") {
+ gen <<EOS
+#include <sstream>
+#include "qpid/framing/ProtocolVersion.h"
+
+namespace qpid {
+namespace framing {
+
+class #{@classname} {
+
+ public:
+ virtual ~#{@classname}() {}
+
+ virtual ProtocolVersion getVersion() const = 0;
+
+ // Include framing constant declarations
+ #include "AMQP_Constants.h"
+
+ // Inner classes
+EOS
+ indent { @amqp.classes.each { |c| handler_class(c) } }
+ gen <<EOS
+
+ // Method handler get methods
+
+EOS
+ indent { @amqp.classes.each { |c| handler_get(c) } }
+ gen <<EOS
+}; /* class #{@classname} */
+}
+EOS
+}
+ end
+end
+
+OperationsGen.new("client",ARGV[0], Amqp).generate()
+OperationsGen.new("server",ARGV[0], Amqp).generate()
+
diff --git a/cpp/rubygen/samples/Proxy.rb b/cpp/rubygen/samples/Proxy.rb
new file mode 100755
index 0000000000..f7765f3729
--- /dev/null
+++ b/cpp/rubygen/samples/Proxy.rb
@@ -0,0 +1,153 @@
+#!/usr/bin/env ruby
+$: << ".." # Include .. in load path
+require 'cppgen'
+
+class ProxyGen < CppGen
+
+ def initialize(chassis, outdir, amqp)
+ super(outdir, amqp)
+ @chassis=chassis
+ @classname="AMQP_#{@chassis.caps}Proxy"
+ end
+
+ def include(m) gen "#include \"#{m.body_name}.h\"\n"; end
+
+ def proxy_member(c) c.name.lcaps+"Proxy"; end
+
+ def inner_class_decl(c)
+ cname=c.name.caps
+ gen <<EOS
+ // ==================== class #{cname} ====================
+ class #{cname}
+ {
+ private:
+ ChannelAdapter& channel;
+
+ public:
+ // Constructors and destructors
+
+ #{cname}(ChannelAdapter& ch) :
+ channel(ch) {}
+ virtual ~#{cname}() {}
+
+ static #{cname}& get(#{@classname}& proxy) { return proxy.get#{cname}();}
+
+ // Protocol methods
+EOS
+ indent(2) { c.methods_on(@chassis).each { |m| inner_method_decl(m) } }
+ gen "\n }; // class #{cname}\n\n"
+ end
+
+ def inner_method_decl(m)
+ gen "virtual void #{m.cppname}(#{m.signature.join(",\n ")})\n\n";
+ end
+
+ def inner_class_defn(c)
+ cname=c.cppname
+ gen "// ==================== class #{cname} ====================\n"
+ c.methods_on(@chassis).each { |m| inner_method_defn(m, cname) }
+ end
+
+ def inner_method_defn(m,cname)
+ if m.response?
+ rettype="void"
+ ret=""
+ sigadd=["RequestId responseTo"]
+ paramadd=["channel.getVersion(), responseTo"]
+ else
+ rettype="RequestId"
+ ret="return "
+ sigadd=[]
+ paramadd=["channel.getVersion()"]
+ end
+ sig=(m.signature+sigadd).join(", ")
+ params=(paramadd+m.param_names).join(",\n ")
+ gen <<EOS
+#{rettype} #{@classname}::#{cname}::#{m.cppname}(#{sig}) {
+ #{ret}channel.send(new #{m.body_name}(#{params}));
+}
+
+EOS
+ end
+
+ def get_decl(c)
+ cname=c.name.caps
+ gen " #{cname}& #{@classname}::get#{cname}();\n"
+ end
+
+ def get_defn(c)
+ cname=c.name.caps
+ gen <<EOS
+#{@classname}::#{c.name.caps}& #{@classname}::get#{c.name.caps}()
+{
+ return #{proxy_member(c)};
+}
+EOS
+ end
+
+ def generate
+ # .h file
+ h_file(@classname+".h") {
+ gen <<EOS
+#include "qpid/framing/Proxy.h"
+
+namespace qpid {
+namespace framing {
+
+class #{@classname} : public Proxy
+{
+public:
+ #{@classname}(ChannelAdapter& ch);
+
+ // Inner class definitions
+EOS
+ @amqp.classes.each{ |c| inner_class_decl(c) }
+ gen " // Inner class instance get methods\n"
+ @amqp.classes.each{ |c| get_decl(c) }
+ gen <<EOS
+ private:
+ // Inner class instances
+EOS
+ indent { @amqp.classes.each{ |c| gen c.cppname+" "+proxy_member(c)+";\n" } }
+ gen <<EOS
+}; /* class #{@classname} */
+
+} /* namespace framing */
+} /* namespace qpid */
+EOS
+ }
+
+ # .cpp file
+ cpp_file(@classname+".cpp") {
+ gen <<EOS
+#include <sstream>
+#include "#{@classname}.h"
+#include "qpid/framing/ChannelAdapter.h"
+#include "qpid/framing/amqp_types_full.h"
+EOS
+ @amqp.methods_on(@chassis).each { |m| include(m) }
+ gen <<EOS
+namespace qpid {
+namespace framing {
+
+#{@classname}::#{@classname}(ChannelAdapter& ch) :
+EOS
+ gen " Proxy(ch)"
+ @amqp.classes.each { |c| gen ",\n "+proxy_member(c)+"(channel)" }
+ gen <<EOS
+ {}
+
+ // Inner class instance get methods
+EOS
+ @amqp.classes.each { |c| get_defn(c) }
+ gen " // Inner class implementation\n\n"
+ @amqp.classes.each { |c| inner_class_defn(c) }
+ gen "}} // namespae qpid::framing"
+ }
+ end
+end
+
+
+ProxyGen.new("client", ARGV[0], Amqp).generate;
+ProxyGen.new("server", ARGV[0], Amqp).generate;
+
diff --git a/cpp/rubygen/samples/runme b/cpp/rubygen/samples/runme
new file mode 100755
index 0000000000..e06d128707
--- /dev/null
+++ b/cpp/rubygen/samples/runme
@@ -0,0 +1,18 @@
+#!/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'
+
+