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 +210 -0
- data/cplus2ruby.gemspec +19 -0
- data/example.rb +44 -0
- data/lib/cplus2ruby/code_generator.rb +52 -0
- data/lib/cplus2ruby/compiler.rb +84 -0
- data/lib/cplus2ruby/cpp_code_generator.rb +287 -0
- data/lib/cplus2ruby/model.rb +212 -0
- data/lib/cplus2ruby/typing.rb +152 -0
- data/lib/cplus2ruby/wrapper_code_generator.rb +165 -0
- data/lib/cplus2ruby.rb +30 -0
- data/test/test_mixin.rb +29 -0
- data/test/work/Makefile +149 -0
- data/test/work/test_mixin.cc +23 -0
- data/test/work/test_mixin.h +51 -0
- data/test/work/test_mixin.o +0 -0
- data/test/work/test_mixin.so +0 -0
- data/test/work/test_mixin_wrap.cc +105 -0
- data/test/work/test_mixin_wrap.o +0 -0
- metadata +82 -0
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
|
data/cplus2ruby.gemspec
ADDED
@@ -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
|