From ab82647c839e3aaa8b5c14d75d3e4a95c9075091 Mon Sep 17 00:00:00 2001 From: Alan Conway Date: Tue, 31 Jul 2007 20:00:10 +0000 Subject: 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@561468 13f79535-47bb-0310-9956-ffa450edef68 --- qpid/cpp/rubygen/README | 20 ++++ qpid/cpp/rubygen/amqpgen.rb | 208 +++++++++++++++++++++++++++++++++ qpid/cpp/rubygen/cppgen.rb | 126 ++++++++++++++++++++ qpid/cpp/rubygen/samples/Operations.rb | 85 ++++++++++++++ qpid/cpp/rubygen/samples/Proxy.rb | 153 ++++++++++++++++++++++++ qpid/cpp/rubygen/samples/runme | 18 +++ 6 files changed, 610 insertions(+) create mode 100644 qpid/cpp/rubygen/README create mode 100755 qpid/cpp/rubygen/amqpgen.rb create mode 100755 qpid/cpp/rubygen/cppgen.rb create mode 100755 qpid/cpp/rubygen/samples/Operations.rb create mode 100755 qpid/cpp/rubygen/samples/Proxy.rb create mode 100755 qpid/cpp/rubygen/samples/runme (limited to 'qpid/cpp/rubygen') diff --git a/qpid/cpp/rubygen/README b/qpid/cpp/rubygen/README new file mode 100644 index 0000000000..43111d4c4f --- /dev/null +++ b/qpid/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 + +The first argument is a directory for generated files, remaining arguments +are xml files. + + + + + + + diff --git a/qpid/cpp/rubygen/amqpgen.rb b/qpid/cpp/rubygen/amqpgen.rb new file mode 100755 index 0000000000..3fc66525d8 --- /dev/null +++ b/qpid/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/qpid/cpp/rubygen/cppgen.rb b/qpid/cpp/rubygen/cppgen.rb new file mode 100755 index 0000000000..3e3800c4cd --- /dev/null +++ b/qpid/cpp/rubygen/cppgen.rb @@ -0,0 +1,126 @@ +#!/usr/bin/ruby +# +# General purpose C++ code generation. +# +require 'amqpgen' +require 'set' + +Copyright=< ["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/qpid/cpp/rubygen/samples/Operations.rb b/qpid/cpp/rubygen/samples/Operations.rb new file mode 100755 index 0000000000..1c245ca188 --- /dev/null +++ b/qpid/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 < +#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 < +#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 < " + 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' + + -- cgit v1.2.1