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.
@@ -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