cplus2ruby 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README ADDED
@@ -0,0 +1,210 @@
1
+ ---------------------------------------------------------
2
+ Cplus2Ruby - Gluing C++ and Ruby together in an OO manner
3
+ ---------------------------------------------------------
4
+
5
+ COPYRIGHT
6
+
7
+ Copyright (c) 2007, 2008 by Michael Neumann (mneumann@ntecs.de).
8
+ All rights reserved.
9
+
10
+ LICENSE
11
+
12
+ Ruby License.
13
+
14
+ ABOUT
15
+
16
+ Cplus2Ruby (or "C++Ruby") makes it easy to mix Ruby and C++ in
17
+ a seamless way. You can use the power of the Ruby object model
18
+ and where needed switch to C++ methods for ultimate performance!
19
+
20
+ Cplus2Ruby generates getter and setter methods for C++ properties,
21
+ and wrapper methods so that you can call your C++ methods from
22
+ Ruby without writing a single line of C++ wrapper code. In the same
23
+ way stub methods enable your C++ methods to directly call a Ruby
24
+ method.
25
+
26
+ As mentioned above shortly, the main purpose of Cplus2Ruby is speed.
27
+ Accessing instance variables in Ruby is somewhat slow compared to
28
+ accessing an C++ attribute. Calling a C++ method is as well *a lot*
29
+ faster than calling a Ruby method. Cplus2Ruby now allows you to
30
+ write your performance critical methods in C++, which can call other
31
+ C++ methods and access C++ attributes with native C++ performance.
32
+
33
+ INSTALLATION
34
+
35
+ gem install cplus2ruby
36
+
37
+ DEPENDENCIES
38
+
39
+ * gem install facets
40
+ * C++ compiler and make
41
+
42
+ EXAMPLE
43
+
44
+ Take a look at the following example. You should also take a look
45
+ at the generated C++ source file (work/*.cc). Note that properties
46
+ are actually members of a C++ class, not instance variables, and as
47
+ such, their access from C++ is very fast. As calling a method is
48
+ quite slow in Ruby, a method defined in C++ ("method") can be called
49
+ directly from C++, which again is very fast!
50
+
51
+ $LOAD_PATH.unshift './lib'
52
+ require 'rubygems'
53
+ require 'cplus2ruby'
54
+
55
+ class NeuralEntity; cplus2ruby
56
+ property :id
57
+ end
58
+
59
+ class Neuron < NeuralEntity
60
+ property :potential, :float
61
+ property :last_spike_time, :float
62
+ property :pre_synapses
63
+
64
+ method :stimulate, {:at => :float},{:weight => :float}, %{
65
+ // This is C++ Code
66
+ @potential += at*weight;
67
+
68
+ // call a Ruby method
69
+ log(@potential);
70
+ }
71
+
72
+ stub_method :log, {:pot => :float}
73
+
74
+ def log(pot)
75
+ puts "log(#{pot})"
76
+ end
77
+
78
+ def initialize
79
+ self.pre_synapses = []
80
+ end
81
+ end
82
+
83
+ if __FILE__ == $0
84
+ #
85
+ # Generate C++ code, compile and load shared library.
86
+ #
87
+ Cplus2Ruby.startup('work/neural')
88
+
89
+ n = Neuron.new
90
+ n.id = "n1"
91
+ n.potential = 1.0
92
+ n.stimulate(1.0, 2.0)
93
+ p n.potential # => 3.0
94
+ end
95
+
96
+ FEATURES
97
+
98
+ You can disable the substitution of "@" to "this->" in the generated
99
+ C++ source code with:
100
+
101
+ Cplus2Ruby.settings :substitute_iv_ats => false
102
+
103
+ A method signature to return a value (in our case an integer) looks like:
104
+
105
+ method :abc, {:arg1 => :int}, {:arg2 => :float}, {:returns => :int}, %{
106
+ ...
107
+ }
108
+
109
+ Mixins can be used:
110
+
111
+ module Mixin; cplus2ruby
112
+ property :a
113
+ end
114
+
115
+ class C; cplus2ruby
116
+ include Mixin
117
+ end
118
+
119
+ They don't generate a C++ class, instead get inlined into the class
120
+ into which they are mixed in.
121
+
122
+ You can use type aliases:
123
+
124
+ Cplus2Ruby.add_type_alias 'MyIntegerType' => 'unsigned int'
125
+
126
+ After that Cplus2Ruby knows about this type and how to convert it
127
+ (if it knows how to convert the 'unsigned int' type) and you can
128
+ use it wherever you want.
129
+
130
+ Inline, static and virtual methods can be declared like this:
131
+
132
+ method :abc, {:a1 => :int}, %{
133
+ body
134
+ }, :inline => true, :static => true, :virtual => true
135
+
136
+ There is also a static_method short-cut for static methods, so instead
137
+ of:
138
+
139
+ method :abc, {:a1 => :int}, %{
140
+ ...
141
+ }, :static => true
142
+
143
+ you can write:
144
+
145
+ static_method :abc, {:a1 => :int}, %{
146
+ ...
147
+ }
148
+
149
+ To mark a method in a class hierarchy forever as virtual, you can
150
+ write:
151
+
152
+ virtual :method1, :method2
153
+
154
+ You can also define a class that is purely used from within C++.
155
+ If you don't want to generate wrapper code etc. specify:
156
+
157
+ cplus2ruby :no_wrap => true
158
+
159
+ You can use Strings, Symbols and Classes for types in signatures or
160
+ in property declarations. There is no distinction between Strings and
161
+ Symbols. If you specify a class, it must be known to Cplus2Ruby,
162
+ either explicitly:
163
+
164
+ class A
165
+ cplus2ruby # marks it known to Cplus2Ruby
166
+ end
167
+
168
+ Or using inheritance:
169
+
170
+ class A
171
+ cplus2ruby # marks it known to Cplus2Ruby
172
+ end
173
+
174
+ class B < A # implicit by inheritance
175
+ end
176
+
177
+ Global code (mostly type declarations etc.) can be added as shown
178
+ below:
179
+
180
+ Cplus2Ruby << %q{
181
+ #include <assert.h>
182
+ #include <math.h>
183
+
184
+ #define real_exp expf
185
+ #define real_fabs fabsf
186
+
187
+ #define THROW(str) rb_raise(rb_eRuntimeError, str)
188
+ }
189
+
190
+ Compilation flags etc.:
191
+
192
+ Cplus2Ruby.startup(module_name, force_compilation, cflags, ldflags)
193
+
194
+ For example:
195
+
196
+ #
197
+ # force_compilation => true regenerates and recompiles the
198
+ # C++ code every time.
199
+ #
200
+ Cplus2Ruby.startup("work/mymodule", true, '-DNDEBUG -Winline -Wall', '-lm')
201
+
202
+ BUGS
203
+
204
+ * I get an "illegal instruction" (sig 4) when the C++ code is compiled
205
+ with -pthread. This is the default in the ports on FreeBSD 7.0 even
206
+ when WITH_PTHREAD is defined. It is somehow related to the GC,
207
+ because when I disable the GC everything is fine (except memory
208
+ usage :).
209
+
210
+ END
@@ -0,0 +1,19 @@
1
+ require 'rubygems'
2
+
3
+ spec = Gem::Specification.new do |s|
4
+ s.name = "cplus2ruby"
5
+ s.version = "1.0.0"
6
+ s.summary = "Gluing C++ and Ruby together in an OO manner"
7
+ s.files = Dir['**/*']
8
+ s.add_dependency('facets', '>= 2.3.0')
9
+
10
+ s.author = "Michael Neumann"
11
+ s.email = "mneumann@ntecs.de"
12
+ s.homepage = "http://www.ntecs.de/projects/cplus2ruby/"
13
+ s.rubyforge_project = "cplus2ruby"
14
+ end
15
+
16
+ if __FILE__ == $0
17
+ Gem::manage_gems
18
+ Gem::Builder.new(spec).build
19
+ end
data/example.rb ADDED
@@ -0,0 +1,44 @@
1
+ require 'rubygems'
2
+ $LOAD_PATH.unshift './lib'
3
+ require 'cplus2ruby'
4
+
5
+ class NeuralEntity; cplus2ruby
6
+ property :id
7
+ end
8
+
9
+ class Neuron < NeuralEntity
10
+ property :potential, :float
11
+ property :last_spike_time, :float
12
+ property :pre_synapses
13
+
14
+ method :stimulate, {:at => :float},{:weight => :float}, %{
15
+ // This is C++ Code
16
+ @potential += at*weight;
17
+
18
+ // call a Ruby method
19
+ log(@potential);
20
+ }
21
+
22
+ stub_method :log, {:pot => :float}
23
+
24
+ def log(pot)
25
+ puts "log(#{pot})"
26
+ end
27
+
28
+ def initialize
29
+ self.pre_synapses = []
30
+ end
31
+ end
32
+
33
+ if __FILE__ == $0
34
+ #
35
+ # Generate C++ code, compile and load shared library.
36
+ #
37
+ Cplus2Ruby.startup('work/neural')
38
+
39
+ n = Neuron.new
40
+ n.id = "n1"
41
+ n.potential = 1.0
42
+ n.stimulate(1.0, 2.0)
43
+ p n.potential # => 3.0
44
+ end
@@ -0,0 +1,52 @@
1
+ class Cplus2Ruby::CodeGenerator
2
+ require 'fileutils'
3
+
4
+ def initialize(model=Cplus2Ruby.model)
5
+ @model = model
6
+ end
7
+
8
+ #
9
+ # Allows preprocessing of generated code.
10
+ #
11
+ def write_out(file, str)
12
+ if @model.settings()[:substitute_iv_ats]
13
+ str.gsub!('@', 'this->')
14
+ end
15
+ FileUtils.mkdir_p(File.dirname(file))
16
+ File.open(file, 'w+') {|out| out.puts str}
17
+ end
18
+
19
+ def no_wrap?(klass)
20
+ (klass.local_annotations[:__options__] || {})[:no_wrap]
21
+ end
22
+
23
+ def wrap?(klass)
24
+ not no_wrap?(klass)
25
+ end
26
+
27
+ def all_properties_of(klass)
28
+ klass.local_annotations.each do |name, options|
29
+ next if options[:class] != Cplus2Ruby::Property
30
+ yield name, options
31
+ end
32
+ end
33
+
34
+ def all_methods_of(klass)
35
+ klass.local_annotations.each do |name, options|
36
+ next if options[:class] != Cplus2Ruby::Method
37
+ yield name, options
38
+ end
39
+ end
40
+
41
+ def args_convertable?(args)
42
+ #FIXME: Facets 2.3.0 has a bug in Dictionary#all?
43
+ ##args.all? {|_, type| @model.typing.can_convert?(type) }
44
+ args.each {|_, type| return false unless @model.typing.can_convert?(type) }
45
+ return true
46
+ end
47
+
48
+ def arity(args)
49
+ args.size - (args.include?(:returns) ? 1 : 0)
50
+ end
51
+
52
+ end
@@ -0,0 +1,84 @@
1
+ class Cplus2Ruby::Compiler
2
+ require 'cplus2ruby/cpp_code_generator'
3
+ require 'cplus2ruby/wrapper_code_generator'
4
+
5
+ def initialize(model=Cplus2Ruby.model)
6
+ @model = model
7
+ end
8
+
9
+ def write_files(mod_name)
10
+ cpp_cg = Cplus2Ruby::CppCodeGenerator.new(@model)
11
+ wrap_cg = Cplus2Ruby::WrapperCodeGenerator.new(@model)
12
+ cpp_cg.write_files(mod_name)
13
+ wrap_cg.write_files(mod_name)
14
+ end
15
+
16
+ def startup(file, force_compilation=false, cflags="", libs="", &block)
17
+ n = names(file)
18
+
19
+ if not force_compilation
20
+ begin
21
+ require n[:ld]
22
+ block.call if block
23
+ return
24
+ rescue LoadError
25
+ end
26
+ end
27
+
28
+ compile(file, cflags, libs)
29
+ require n[:ld]
30
+ block.call if block
31
+ end
32
+
33
+ #
34
+ # Compiles +file+. Returns the name of the shared object to
35
+ # use by +require+.
36
+ #
37
+ def compile(file, cflags="", libs="")
38
+ n = names(file)
39
+
40
+ require 'win32/process' if RUBY_PLATFORM.match('mswin')
41
+ require 'fileutils'
42
+
43
+ FileUtils.mkdir_p(n[:dir])
44
+
45
+ make = RUBY_PLATFORM.match('mswin') ? 'nmake' : 'make'
46
+
47
+ Dir.chdir(n[:dir]) do
48
+ system("#{make} clean") if File.exist?('Makefile')
49
+ write_files(n[:mod])
50
+
51
+ pid = fork do
52
+ require 'mkmf'
53
+ $CFLAGS = cflags
54
+ $LIBS << (" -lstdc++ " + libs)
55
+ create_makefile(n[:mod])
56
+ exec "#{make}"
57
+ end
58
+ _, status = Process.waitpid2(pid)
59
+
60
+ if RUBY_PLATFORM.match('mswin')
61
+ raise if status != 0
62
+ else
63
+ raise if status.exitstatus != 0
64
+ end
65
+ end
66
+
67
+ return n[:ld]
68
+ end
69
+
70
+ def names(file)
71
+ require 'rbconfig'
72
+ base = File.basename(file)
73
+ dir = File.dirname(file)
74
+ mod, ext = base.split(".")
75
+ ld = "#{dir}/#{mod}.#{Config::CONFIG['DLEXT']}"
76
+ { :file => file,
77
+ :base => base,
78
+ :dir => dir,
79
+ :mod => mod,
80
+ :ext => ext,
81
+ :ld => ld }
82
+ end
83
+
84
+ end
@@ -0,0 +1,287 @@
1
+ require 'cplus2ruby/code_generator'
2
+
3
+ class Cplus2Ruby::CppCodeGenerator < Cplus2Ruby::CodeGenerator
4
+ DEFAULT_INCLUDES = [:"stdlib.h", "ruby.h"]
5
+
6
+ def gen_rubyObject
7
+ %[
8
+ struct RubyObject {
9
+ VALUE __obj__;
10
+ RubyObject() { __obj__ = Qnil; }
11
+ virtual ~RubyObject() {};
12
+ static void __free(void *ptr) { ((RubyObject*)ptr)->__free__(); }
13
+ static void __mark(void *ptr) { ((RubyObject*)ptr)->__mark__(); }
14
+ virtual void __free__() { delete this; }
15
+ virtual void __mark__() { }
16
+ };
17
+ ]
18
+ end
19
+
20
+ def gen_include(inc)
21
+ "#include " +
22
+ case inc
23
+ when Symbol
24
+ %{<#{inc}>}
25
+ when String
26
+ %{"#{inc}"}
27
+ else
28
+ raise ArgumentError, "invalid header"
29
+ end
30
+ end
31
+
32
+ def gen_includes(includes)
33
+ includes.map {|inc| gen_include(inc) }.join("\n")
34
+ end
35
+
36
+ def gen_type_alias(from, to)
37
+ "typedef #{to} #{from};"
38
+ end
39
+
40
+ #
41
+ # Type aliases is a hash in the form from => to.
42
+ #
43
+ def gen_type_aliases(type_aliases)
44
+ type_aliases.map {|from, to| gen_type_alias(from, to) }.join("\n")
45
+ end
46
+
47
+ #
48
+ # +kind+ is either :free or :mark
49
+ #
50
+ def gen_free_or_mark_method(klass, kind)
51
+ stmts = stmts_for_free_or_mark_method(klass, kind)
52
+ return "" if stmts.empty?
53
+ stmts << "super::__#{kind}__()"
54
+ %[
55
+ void #{klass.name}::__#{kind}__()
56
+ {
57
+ #{stmts.join(";\n")};
58
+ }
59
+ ]
60
+ end
61
+
62
+ def gen_constructor(klass)
63
+ stmts = []
64
+ all_properties_of(klass) do |name, options|
65
+ init = @model.typing.lookup_entry(:init, options, options[:type])
66
+ stmts << @model.typing.var_assgn("this->#{name}", init) unless init.nil?
67
+ end
68
+ #return "" if stmts.empty?
69
+ %[
70
+ #{klass.name}::#{klass.name}()
71
+ {
72
+ #{stmts.join(";\n")};
73
+ }
74
+ ]
75
+ end
76
+
77
+ def gen_property(name, options)
78
+ @model.typing.var_decl(options[:type], name)
79
+ end
80
+
81
+ #
82
+ # If +klassname+ is nil, then it doesn't include the
83
+ # Klassname:: prefix.
84
+ #
85
+ # Doesn't include the semicolon at the end.
86
+ #
87
+ def gen_method_sig(klassname, name, options, is_declaration)
88
+ args = options[:arguments].dup
89
+ returns = args.delete(:returns) || "void"
90
+
91
+ out = ""
92
+ if is_declaration
93
+ out << "static " if options[:static]
94
+ out << "inline " if options[:inline]
95
+ out << "virtual " if options[:virtual]
96
+ end
97
+ out << @model.typing.var_decl(returns, "")
98
+ out << " "
99
+
100
+ s = args.map {|aname, atype| @model.typing.var_decl(atype, aname) }.join(", ")
101
+
102
+ out << "#{klassname}::" if klassname
103
+ out << "#{name}(#{s})"
104
+ return out
105
+ end
106
+
107
+ def gen_method_body(klassname, name, options)
108
+ if options[:stub]
109
+ gen_stub_method(klassname, name, options)
110
+ else
111
+ "{\n" + (options[:body] || @model.settings[:default_body_when_nil]) + "}\n"
112
+ end
113
+ end
114
+
115
+ #
116
+ # Generates a C++ method that forwards the call to the Ruby method
117
+ # of the same name.
118
+ #
119
+ def gen_stub_method(klassname, name, options)
120
+ raise "Stub method with body is invalid!" if options[:body]
121
+
122
+ args = options[:arguments].dup
123
+ unless args_convertable?(args)
124
+ raise "ERROR: Cannot convert stub method #{klassname}::#{name}"
125
+ end
126
+
127
+ returns = args.delete(:returns) || "void"
128
+
129
+ out = ""
130
+ out << "{\n"
131
+ out << "VALUE __res__ = " if returns != 'void'
132
+
133
+ # TODO: move rb_intern out
134
+ call_args = ["@__obj__", %{rb_intern("#{name}")}, args.size] +
135
+ args.map {|n, k| @model.typing.convert(k, n, :c2ruby) }
136
+
137
+ out << %{rb_funcall(#{call_args.join(', ')});}
138
+
139
+ # check return type
140
+ if returns != 'void'
141
+ out << @model.typing.convert(returns, '__res__', :ruby2c_checktype)
142
+ retval = @model.typing.convert(returns, '__res__', :ruby2c)
143
+ out << "return #{retval};\n"
144
+ end
145
+
146
+ out << "}\n"
147
+
148
+ return out
149
+ end
150
+
151
+ def gen_method(klassname, name, options, include_body, is_declaration)
152
+ str = gen_method_sig(klassname, name, options, is_declaration)
153
+ str << gen_method_body(klassname, name, options) if include_body
154
+ str
155
+ end
156
+
157
+ def gen_class_declaration(klass)
158
+ if klass.superclass == Object
159
+ sc = "RubyObject"
160
+ else
161
+ sc = klass.superclass.name
162
+ end
163
+
164
+ #
165
+ # Do we have free or mark methods defined?
166
+ #
167
+ m = {}
168
+ [:free, :mark].each do |kind|
169
+ if not stmts_for_free_or_mark_method(klass, kind).empty?
170
+ m[kind] = "virtual void __#{kind}__();"
171
+ end
172
+ end
173
+
174
+ #
175
+ # Write out property declarations and method signatures.
176
+ #
177
+ stmts = []
178
+
179
+ all_properties_of(klass) {|name, options|
180
+ stmts << gen_property(name, options)
181
+ }
182
+
183
+ all_methods_of(klass) {|name, options|
184
+ stmts << gen_method(nil, name, options, options[:inline], true)
185
+ }
186
+
187
+ if no_wrap?(klass)
188
+ %[
189
+ struct #{klass.name}
190
+ {
191
+ #{stmts.join("; \n")};
192
+ };
193
+ ]
194
+ else
195
+ %[
196
+ struct #{klass.name} : #{sc}
197
+ {
198
+ typedef #{sc} super;
199
+
200
+ #{klass.name}();
201
+
202
+ #{m[:free]}
203
+ #{m[:mark]}
204
+
205
+ #{stmts.join("; \n")};
206
+ };
207
+ ]
208
+ end
209
+ end
210
+
211
+ def gen_class_impl(klass)
212
+ # FIXME: helper_codes
213
+
214
+ stmts = []
215
+
216
+ if wrap?(klass)
217
+ stmts << gen_constructor(klass)
218
+
219
+ [:free, :mark].each {|kind|
220
+ stmts << gen_free_or_mark_method(klass, kind)
221
+ }
222
+ end
223
+
224
+ all_methods_of(klass) do |name, options|
225
+ next if options[:inline]
226
+ stmts << gen_method(klass.name, name, options, true, false)
227
+ end
228
+
229
+ stmts.join("\n")
230
+ end
231
+
232
+ def gen_header_file
233
+ out = ""
234
+ out << gen_includes(DEFAULT_INCLUDES + @model.includes)
235
+ out << gen_rubyObject()
236
+ out << gen_type_aliases(@model.typing.aliases)
237
+ out << @model.code
238
+
239
+ # forward class declarations
240
+ @model.entities_ordered.each do |klass|
241
+ out << "struct #{klass.name};\n"
242
+ end
243
+
244
+ # FIXME: helper_headers
245
+
246
+ #
247
+ # class declarations
248
+ #
249
+ @model.entities_ordered.each do |klass|
250
+ out << gen_class_declaration(klass)
251
+ end
252
+
253
+ return out
254
+ end
255
+
256
+ def gen_impl_file(mod_name)
257
+ out = ""
258
+ out << %{#include "#{mod_name}.h"\n\n}
259
+
260
+ #
261
+ # class declarations
262
+ #
263
+ @model.entities_ordered.each do |klass|
264
+ out << gen_class_impl(klass)
265
+ end
266
+
267
+ return out
268
+ end
269
+
270
+ def write_files(mod_name)
271
+ write_out(mod_name + ".h", gen_header_file())
272
+ write_out(mod_name + ".cc", gen_impl_file(mod_name))
273
+ end
274
+
275
+ protected
276
+
277
+ def stmts_for_free_or_mark_method(klass, kind)
278
+ stmts = []
279
+ all_properties_of(klass) do |name, options|
280
+ if free_mark = @model.typing.lookup_entry(kind, options, options[:type])
281
+ stmts << free_mark.gsub('%s', "this->#{name}") # FIXME: use a common replacement function
282
+ end
283
+ end
284
+ stmts
285
+ end
286
+
287
+ end