cplus2ruby 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,212 @@
1
+ require 'facets/annotations'
2
+ require 'facets/orderedhash'
3
+ require 'cplus2ruby/typing'
4
+
5
+ class Cplus2Ruby::Property; end
6
+ class Cplus2Ruby::Method; end
7
+
8
+ class Cplus2Ruby::Model
9
+ attr_reader :typing
10
+ attr_reader :code
11
+ attr_reader :includes
12
+
13
+ def next_order_cnt
14
+ @order_cnt += 1
15
+ end
16
+
17
+ def initialize
18
+ @typing = Cplus2Ruby::Typing.new
19
+ @code = ""
20
+ @includes = []
21
+ @settings = default_settings()
22
+ @order_cnt = 0
23
+ end
24
+
25
+ def finish!
26
+ entities.each do |klass|
27
+ @typing.add_object_type(klass)
28
+ end
29
+ end
30
+
31
+ def entities
32
+ entities = []
33
+ ObjectSpace.each_object(Class) {|o|
34
+ entities << o if o.kind_of?(Cplus2Ruby::Entity)
35
+ }
36
+ entities
37
+ end
38
+
39
+ def entity_usage(klass, other)
40
+ usage_cnt = 0
41
+ klass.local_annotations.each do |name, opts|
42
+ usage_cnt += 1 if opts[:arguments] and opts[:arguments].values.include?(other)
43
+ usage_cnt += 1 if opts[:type] and opts[:type] == other
44
+ end
45
+ usage_cnt
46
+ end
47
+
48
+ def entities_ordered
49
+ entities().sort {|a, b|
50
+ if a.ancestors.include?(b)
51
+ # a 'after' b (a > b)
52
+ 1
53
+ elsif b.ancestors.include?(a)
54
+ -1
55
+ else
56
+ ea = entity_usage(a, b)
57
+ eb = entity_usage(b, a)
58
+
59
+ if ea > 0 and eb == 0
60
+ -1
61
+ elsif eb > 0 and ea == 0
62
+ 1
63
+ else
64
+ ao = (a.heritage(:__options__) || {})[:order] || 0
65
+ bo = (b.heritage(:__options__) || {})[:order] || 0
66
+ ao <=> bo
67
+ end
68
+ end
69
+ }
70
+ end
71
+
72
+ #
73
+ # Update or retrieve the current settings.
74
+ #
75
+ def settings(h={})
76
+ @settings.update(h)
77
+ @settings
78
+ end
79
+
80
+ protected
81
+
82
+ def default_settings
83
+ {
84
+ :substitute_iv_ats => true,
85
+ :default_body_when_nil => ''
86
+ }
87
+ end
88
+
89
+ end
90
+
91
+ module Cplus2Ruby
92
+ #
93
+ # Global code
94
+ #
95
+ def self.<<(code)
96
+ model.code << "\n"
97
+ model.code << code
98
+ model.code << "\n"
99
+ end
100
+
101
+ def self.include(header)
102
+ model.includes << header
103
+ end
104
+
105
+ def self.settings(h={})
106
+ model.settings(h)
107
+ end
108
+
109
+ def self.model
110
+ @model ||= Cplus2Ruby::Model.new
111
+ end
112
+
113
+ def self.add_type_alias(h)
114
+ h.each {|from, to| model.typing.alias_entry(from, to)}
115
+ end
116
+
117
+ def self.startup(*args, &block)
118
+ self.model.finish!
119
+ Cplus2Ruby::Compiler.new(self.model).startup(*args, &block)
120
+ end
121
+
122
+ def self.compile(*args)
123
+ self.model.finish!
124
+ Cplus2Ruby::Compiler.new(self.model).compile(*args)
125
+ end
126
+
127
+ end
128
+
129
+ module Cplus2Ruby::Entity
130
+ def property(name, type=Object, options={})
131
+ raise ArgumentError if options[:type]
132
+ options[:type] = type
133
+ ann! name, Cplus2Ruby::Property, options
134
+ end
135
+
136
+ #
137
+ # method :name, {hash}, {hash}, ..., {hash}, body, hash
138
+ #
139
+ def method(name, *args)
140
+ options = parse_method_args(args)
141
+ ann! name, Cplus2Ruby::Method, options
142
+ end
143
+
144
+ def static_method(name, *args)
145
+ options = parse_method_args(args)
146
+ options[:static] = true
147
+ ann! name, Cplus2Ruby::Method, options
148
+ end
149
+
150
+ def stub_method(name, *args)
151
+ options = parse_method_args(args)
152
+ options[:stub] = true
153
+ ann! name, Cplus2Ruby::Method, options
154
+ end
155
+
156
+ alias method_c method
157
+
158
+ def virtual(*virtuals)
159
+ virtuals.each do |name|
160
+ ann! name, :virtual => true
161
+ end
162
+ end
163
+
164
+ protected
165
+
166
+ def parse_method_args(args)
167
+ params = OrderedHash.new
168
+ body = nil
169
+ options = {}
170
+
171
+ state = :want_param
172
+
173
+ while not args.empty?
174
+ arg = args.shift
175
+
176
+ case state
177
+ when :want_param
178
+ if arg.is_a?(Hash)
179
+ params.update(arg)
180
+ else
181
+ args.unshift(arg)
182
+ state = :want_body
183
+ end
184
+ when :want_body
185
+ body = arg
186
+ state = :want_options
187
+ when :want_options
188
+ raise unless arg.is_a?(Hash)
189
+ raise unless args.empty?
190
+ options = arg
191
+ else
192
+ raise
193
+ end
194
+ end
195
+
196
+ options[:body] ||= body
197
+ options[:arguments] ||= params
198
+
199
+ return options
200
+ end
201
+
202
+
203
+ end
204
+
205
+ class Module
206
+ def cplus2ruby(hash={})
207
+ include Cplus2Ruby::Entity
208
+ extend Cplus2Ruby::Entity
209
+ ann! :__options__, hash
210
+ ann! :__options__, :order => Cplus2Ruby.model.next_order_cnt if hash[:order].nil?
211
+ end
212
+ end
@@ -0,0 +1,152 @@
1
+ class Cplus2Ruby::Typing
2
+ require 'facets/orderedhash'
3
+
4
+ attr_reader :aliases
5
+
6
+ def initialize
7
+ @map = default_map()
8
+ @aliases = OrderedHash.new
9
+ clone_entry Object, 'VALUE'
10
+ end
11
+
12
+ def add_entry(type, entry)
13
+ type = to_key(type)
14
+ raise if @map.include?(type)
15
+ @map[type] = entry
16
+ end
17
+
18
+ def clone_entry(from, to)
19
+ from = to_key(from)
20
+ to = to_key(to)
21
+ raise if @map.include?(from)
22
+ @map[from] = @map[to] # FIXME: .dup?
23
+ end
24
+
25
+ #
26
+ # Add a type alias. Also modifies type map.
27
+ #
28
+ def alias_entry(from, to)
29
+ from = to_key(from)
30
+ to = to_key(to)
31
+ @aliases[from] = to
32
+ clone_entry(from, to)
33
+ end
34
+
35
+ def get_entry(type)
36
+ @map[to_key(type)]
37
+ end
38
+
39
+ #
40
+ # Looks up first in the annotation options, then
41
+ # in the type options.
42
+ #
43
+ def lookup_entry(attribute, options, type)
44
+ (get_entry(type) || {}).dup.update(options)[attribute]
45
+ end
46
+
47
+ #
48
+ # Returns a C++ declaration
49
+ #
50
+ def var_decl(type, name)
51
+ if entry = get_entry(type)
52
+ entry[:ctype].gsub("%s", name.to_s)
53
+ # FIXME type.to_s
54
+ elsif type.to_s.include?("%s")
55
+ type.gsub("%s", name.to_s)
56
+ else
57
+ "#{type} #{name}"
58
+ end
59
+ end
60
+
61
+ def var_assgn(name, value)
62
+ raise ArgumentError if value.nil?
63
+ if value.is_a?(String) and value.include?('%s')
64
+ value.gsub('%s', name)
65
+ else
66
+ "#{name} = #{value}"
67
+ end
68
+ end
69
+
70
+ #
71
+ # Returns true if Ruby <-> C conversion for this type is possible
72
+ #
73
+ def can_convert?(type)
74
+ get_entry(type) ? true : false
75
+ end
76
+
77
+ def convert(type, var, kind)
78
+ (get_entry(type)[kind] || "").gsub('%s', var.to_s)
79
+ end
80
+
81
+ def add_object_type(klass)
82
+ add_entry(klass, object_type(klass.name))
83
+ end
84
+
85
+ protected
86
+
87
+ def to_key(type)
88
+ if type.is_a?(Symbol)
89
+ type.to_s
90
+ else
91
+ type
92
+ end
93
+ end
94
+
95
+ def object_type(type)
96
+ {
97
+ :init => "NULL",
98
+ :mark => "if (%s) rb_gc_mark(%s->__obj__)",
99
+ :ruby2c => "(NIL_P(%s) ? NULL : (#{type}*)DATA_PTR(%s))",
100
+ :c2ruby => "(%s ? %s->__obj__ : Qnil)",
101
+ :ctype => "#{type} *%s",
102
+ :ruby2c_checktype => "if (!NIL_P(%s)) Check_Type(%s, T_DATA)"
103
+ }
104
+ end
105
+
106
+ def default_map
107
+ {
108
+ 'VALUE' => {
109
+ :init => 'Qnil',
110
+ :mark => 'rb_gc_mark(%s)',
111
+ :ruby2c => '%s',
112
+ :c2ruby => '%s',
113
+ :ctype => 'VALUE %s'
114
+ },
115
+ 'float' => {
116
+ :init => 0.0,
117
+ :ruby2c => '(float)NUM2DBL(%s)',
118
+ :c2ruby => 'rb_float_new((double)%s)',
119
+ :ctype => 'float %s'
120
+ },
121
+ 'double' => {
122
+ :init => 0.0,
123
+ :ruby2c => '(double)NUM2DBL(%s)',
124
+ :c2ruby => 'rb_float_new(%s)',
125
+ :ctype => 'double %s'
126
+ },
127
+ 'int' => {
128
+ :init => 0,
129
+ :ruby2c => '(int)NUM2INT(%s)',
130
+ :c2ruby => 'INT2NUM(%s)',
131
+ :ctype => 'int %s'
132
+ },
133
+ 'unsigned int' => {
134
+ :init => 0,
135
+ :ruby2c => '(unsigned int)NUM2INT(%s)',
136
+ :c2ruby => 'INT2NUM(%s)',
137
+ :ctype => 'unsigned int %s'
138
+ },
139
+ 'bool' => {
140
+ :init => false,
141
+ :ruby2c => '(RTEST(%s) ? true : false)',
142
+ :c2ruby => '(%s ? Qtrue : Qfalse)',
143
+ :ctype => 'bool %s'
144
+ },
145
+ 'void' => {
146
+ :c2ruby => 'Qnil',
147
+ :ctype => 'void'
148
+ }
149
+ }
150
+ end
151
+
152
+ end
@@ -0,0 +1,165 @@
1
+ require 'cplus2ruby/code_generator'
2
+
3
+ class Cplus2Ruby::WrapperCodeGenerator < Cplus2Ruby::CodeGenerator
4
+ def gen_allocator(klass)
5
+ %[
6
+ static VALUE
7
+ #{klass.name}_alloc__(VALUE klass)
8
+ {
9
+ #{klass.name} *__cobj__;
10
+ __cobj__ = new #{klass.name}();
11
+ __cobj__->__obj__ = Data_Wrap_Struct(klass, RubyObject::__mark, RubyObject::__free, __cobj__);
12
+ return __cobj__->__obj__;
13
+ }
14
+ ]
15
+ end
16
+
17
+ def gen_method_wrapper(klass, name, options)
18
+ gen_wrapper(klass, name, options, :wrap)
19
+ end
20
+
21
+ def gen_property_getter(klass, name, options)
22
+ opts = options.dup
23
+ opts[:arguments] = {:returns => options[:type]}
24
+ gen_wrapper(klass, name, opts, :get)
25
+ end
26
+
27
+ def gen_property_setter(klass, name, options)
28
+ opts = options.dup
29
+ opts[:arguments] = {:__val__ => options[:type]}
30
+ gen_wrapper(klass, name, opts, :set)
31
+ end
32
+
33
+ def gen_property_accessor(klass, name, options)
34
+ [gen_property_getter(klass, name, options),
35
+ gen_property_setter(klass, name, options)].join
36
+ end
37
+
38
+ #
39
+ # kind is one of :set, :get, :wrap
40
+ #
41
+ def gen_wrapper(klass, name, options, kind)
42
+ args = options[:arguments].dup
43
+ return nil if options[:stub]
44
+ unless args_convertable?(args)
45
+ STDERR.puts "WARN: cannot wrap method #{klass.name}::#{name} (#{kind})"
46
+ return nil
47
+ end
48
+
49
+ returns = args.delete(:returns) || "void"
50
+
51
+ s = ([["__self__", "VALUE"]] + args.to_a).map {|n,_| "VALUE #{n}"}.join(", ")
52
+
53
+ out = ""
54
+ out << "static VALUE\n"
55
+ out << "#{klass.name}_#{kind}__#{name}(#{s})\n"
56
+ out << "{\n"
57
+
58
+ # declare C++ return value
59
+ if returns != 'void'
60
+ out << @model.typing.var_decl(returns, '__res__') + ";\n"
61
+ end
62
+
63
+ # declare C++ object reference
64
+ out << "#{klass.name} *__cobj__;\n"
65
+ #out << @model.var_decl(klass, '__cobj__') + ";\n"
66
+
67
+ # convert __self__ to C++ object reference (FIXME: can remove?)
68
+ out << "Check_Type(__self__, T_DATA);\n"
69
+ out << "__cobj__ = (#{klass.name}*) DATA_PTR(__self__);\n"
70
+
71
+ # check argument types
72
+ out << args.map {|n, t| @model.typing.convert(t, n.to_s, :ruby2c_checktype) + ";\n" }.join
73
+
74
+ # call arguments
75
+ call_args = args.map {|n, t| @model.typing.convert(t, n.to_s, :ruby2c)}
76
+
77
+ # build method call
78
+ out << "__res__ = " if returns != 'void'
79
+ case kind
80
+ when :wrap
81
+ out << "__cobj__->#{name}(#{call_args.join(', ')});\n"
82
+ when :get
83
+ out << "__cobj__->#{name};\n"
84
+ when :set
85
+ raise ArgumentError if call_args.size != 1
86
+ out << "__cobj__->#{name} = #{call_args.first};\n"
87
+ else
88
+ raise ArgumentError
89
+ end
90
+
91
+ # convert return value
92
+ retval = @model.typing.convert(returns, '__res__', :c2ruby)
93
+
94
+ out << "return #{retval};\n"
95
+ out << "}\n"
96
+
97
+ return out
98
+ end
99
+
100
+ def gen_init(mod_name)
101
+ out = ""
102
+ out << %[extern "C" void Init_#{mod_name}()\n]
103
+ out << "{\n"
104
+ out << " VALUE klass;\n"
105
+
106
+ @model.entities_ordered.each do |klass|
107
+ next if no_wrap?(klass)
108
+ n = klass.name
109
+ out << %{ klass = rb_eval_string("#{n}");\n}
110
+ out << %{ rb_define_alloc_func(klass, #{n}_alloc__);\n}
111
+
112
+ all_methods_of(klass) do |name, options|
113
+ args = options[:arguments]
114
+ next unless args_convertable?(args)
115
+ next if options[:stub]
116
+ out << %{ rb_define_method(klass, "#{name}", }
117
+ out << %{(VALUE(*)(...))#{n}_wrap__#{name}, #{arity(args)});\n}
118
+ end
119
+
120
+ all_properties_of(klass) do |name, options|
121
+ next unless @model.typing.can_convert?(options[:type])
122
+
123
+ # getter
124
+ out << %{ rb_define_method(klass, "#{name}", }
125
+ out << %{(VALUE(*)(...))#{n}_get__#{name}, 0);\n}
126
+
127
+ # setter
128
+ out << %{rb_define_method(klass, "#{name}=", }
129
+ out << %{(VALUE(*)(...))#{n}_set__#{name}, 1);\n}
130
+ end
131
+ end
132
+
133
+ out << "}\n"
134
+
135
+ return out
136
+ end
137
+
138
+ def gen_wrapper_file(mod_name)
139
+ out = ""
140
+ out << %{#include "#{mod_name}.h"\n\n}
141
+
142
+ @model.entities_ordered.each do |klass|
143
+ next if no_wrap?(klass)
144
+
145
+ out << gen_allocator(klass)
146
+
147
+ all_methods_of(klass) do |name, options|
148
+ out << (gen_method_wrapper(klass, name, options) || "")
149
+ end
150
+
151
+ all_properties_of(klass) do |name, options|
152
+ out << gen_property_accessor(klass, name, options)
153
+ end
154
+ end
155
+
156
+ out << gen_init(mod_name)
157
+
158
+ return out
159
+ end
160
+
161
+ def write_files(mod_name)
162
+ write_out(mod_name + "_wrap.cc", gen_wrapper_file(mod_name))
163
+ end
164
+
165
+ end
data/lib/cplus2ruby.rb ADDED
@@ -0,0 +1,30 @@
1
+ module Cplus2Ruby; end
2
+
3
+ require 'cplus2ruby/model'
4
+ require 'cplus2ruby/compiler'
5
+
6
+ #
7
+ # Extend facets/annotations
8
+ #
9
+ class Module
10
+ def recursive_annotations
11
+ res = {}
12
+ ancestors.reverse.each { |ancestor|
13
+ ancestor.annotations.each { |ref, hash|
14
+ res[ref] ||= {}
15
+ res[ref].update(hash) if hash
16
+ }
17
+ }
18
+ res
19
+ end
20
+
21
+ def local_annotations
22
+ new_keys = recursive_annotations().keys - self.superclass.recursive_annotations().keys
23
+ all_keys = new_keys + annotations().keys
24
+ h = {}
25
+ all_keys.each do |key|
26
+ h[key] = heritage(key)
27
+ end
28
+ h
29
+ end
30
+ end
@@ -0,0 +1,29 @@
1
+ require 'rubygems'
2
+ $LOAD_PATH.unshift '../src'
3
+ require 'cplus2ruby'
4
+
5
+ module Mixin1; cplus2ruby
6
+ property :a, :int
7
+ end
8
+
9
+ class A; cplus2ruby
10
+ property :y, :int
11
+ end
12
+
13
+ class B < A; cplus2ruby
14
+ include Mixin1
15
+ end
16
+
17
+ class C < B; cplus2ruby
18
+ property :z, :int
19
+ end
20
+
21
+ Cplus2Ruby.startup('work/test_mixin', true)
22
+ t = B.new
23
+ t.y = 2
24
+ p t.y
25
+
26
+ t = C.new
27
+ t.z = 343333
28
+ p t.z
29
+ p t.y