RubyInline 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,427 @@
1
+ #!/usr/local/bin/ruby -w
2
+
3
+ # Ruby Inline is a framework for writing ruby extensions in foreign
4
+ # languages
5
+ #
6
+ # = SYNOPSIS
7
+ #
8
+ # require 'inline'
9
+ # class MyClass
10
+ # inline do |builder|
11
+ # builder.include "<math.h>"
12
+ # builder.c %q{
13
+ # long factorial(int max) {
14
+ # int i=max, result=1;
15
+ # while (i >= 2) { result *= i--; }
16
+ # return result;
17
+ # }
18
+ # }
19
+ # end
20
+ # end
21
+ #
22
+ # = DESCRIPTION
23
+ #
24
+ # Inline allows you to write C/C++ code within your ruby code. It
25
+ # automatically determines if the code in question has changed and
26
+ # builds it only when necessary. The extensions are then automatically
27
+ # loaded into the class/module that defines it.
28
+ #
29
+ # You can even write extra builders that will allow you to write
30
+ # inlined code in any language. Use Inline::C as a template and look
31
+ # at Module#inline for the required API.
32
+
33
+ require "rbconfig"
34
+ require "ftools"
35
+
36
+ $TESTING = false unless defined? $TESTING
37
+
38
+ # The Inline module is the top-level module used. It is responsible
39
+ # for instantiating the builder for the right language used,
40
+ # compilation/linking when needed, and loading the inlined code into
41
+ # the current namespace.
42
+
43
+ module Inline
44
+ VERSION = '3.1.0'
45
+
46
+ $stderr.puts "RubyInline v #{VERSION}" if $DEBUG
47
+
48
+ protected
49
+
50
+ def self.rootdir
51
+ unless defined? @@rootdir and test ?d, @@rootdir then
52
+ rootdir = ENV['INLINEDIR'] || ENV['HOME']
53
+ Dir.mkdir rootdir, 0700 unless test ?d, rootdir
54
+ Dir.assert_secure rootdir
55
+ @@rootdir = rootdir
56
+ end
57
+
58
+ @@rootdir
59
+ end
60
+
61
+ def self.directory
62
+ unless defined? @@directory and test ?d, @@directory then
63
+ directory = File.join(rootdir, ".ruby_inline")
64
+ unless File.directory? directory then
65
+ $stderr.puts "NOTE: creating #{directory} for RubyInline" if $DEBUG
66
+ Dir.mkdir directory, 0700
67
+ end
68
+ Dir.assert_secure directory
69
+ @@directory = directory
70
+ end
71
+ @@directory
72
+ end
73
+
74
+ # Inline::C is the default builder used and the only one provided by
75
+ # Inline. It can be used as a template to write builders for other
76
+ # languages. It understands type-conversions for the basic types and
77
+ # can be extended as needed.
78
+
79
+ class C
80
+
81
+ protected unless $TESTING
82
+
83
+ MAGIC_ARITY_THRESHOLD = 2
84
+ MAGIC_ARITY = -1
85
+
86
+ @@type_map = {
87
+ 'char' => [ 'NUM2CHR', 'CHR2FIX' ],
88
+ 'char *' => [ 'STR2CSTR', 'rb_str_new2' ],
89
+ 'int' => [ 'FIX2INT', 'INT2FIX' ],
90
+ 'long' => [ 'NUM2INT', 'INT2NUM' ],
91
+ 'unsigned int' => [ 'NUM2UINT', 'UINT2NUM' ],
92
+ 'unsigned long' => [ 'NUM2UINT', 'UINT2NUM' ],
93
+ 'unsigned' => [ 'NUM2UINT', 'UINT2NUM' ],
94
+ # Can't do these converters because they conflict with the above:
95
+ # ID2SYM(x), SYM2ID(x), NUM2DBL(x), FIX2UINT(x)
96
+ }
97
+
98
+ def ruby2c(type)
99
+ raise "Unknown type #{type}" unless @@type_map.has_key? type
100
+ @@type_map[type].first
101
+ end
102
+
103
+ def c2ruby(type)
104
+ raise "Unknown type #{type}" unless @@type_map.has_key? type
105
+ @@type_map[type].last
106
+ end
107
+
108
+ def strip_comments(src)
109
+ # strip c-comments
110
+ src = src.gsub(/\s*(?:(?:\/\*)(?:(?:(?!\*\/)[\s\S])*)(?:\*\/))/, '')
111
+ # strip cpp-comments
112
+ src.gsub!(/\s*(?:\/\*(?:(?!\*\/)[\s\S])*\*\/|\/\/[^\n]*\n)/, '')
113
+ src
114
+ end
115
+
116
+ def parse_signature(src, raw=false)
117
+
118
+ sig = self.strip_comments(src)
119
+ # strip preprocessor directives
120
+ sig.gsub!(/^\s*\#.*(\\\n.*)*/, '')
121
+ # strip {}s
122
+ sig.gsub!(/\{[^\}]*\}/, '{ }')
123
+ # clean and collapse whitespace
124
+ sig.gsub!(/\s+/, ' ')
125
+
126
+ types = 'void|VALUE|' + @@type_map.keys.map{|x| Regexp.escape(x)}.join('|')
127
+
128
+ if /(#{types})\s*(\w+)\s*\(([^)]*)\)/ =~ sig then
129
+ return_type, function_name, arg_string = $1, $2, $3
130
+ args = []
131
+ arg_string.split(',').each do |arg|
132
+
133
+ # helps normalize into 'char * varname' form
134
+ arg = arg.gsub(/\s*\*\s*/, ' * ').strip
135
+
136
+ # if /(#{types})\s+(\w+)\s*$/ =~ arg
137
+ if /(((#{types})\s*\*?)+)\s+(\w+)\s*$/ =~ arg then
138
+ args.push([$4, $1])
139
+ elsif arg != "void" then
140
+ $stderr.puts "WARNING: '#{arg}' not understood"
141
+ end
142
+ end
143
+
144
+ arity = args.size
145
+ arity = -1 if arity > MAGIC_ARITY_THRESHOLD or raw
146
+
147
+ return {
148
+ 'return' => return_type,
149
+ 'name' => function_name,
150
+ 'args' => args,
151
+ 'arity' => arity
152
+ }
153
+ end
154
+
155
+ raise "Bad parser exception: #{sig}"
156
+ end # def parse_signature
157
+
158
+ def generate(src, expand_types=true)
159
+
160
+ result = self.strip_comments(src)
161
+
162
+ signature = parse_signature(src, !expand_types)
163
+ function_name = signature['name']
164
+ return_type = signature['return']
165
+ arity = signature['arity']
166
+
167
+ if expand_types then
168
+ prefix = "static VALUE #{function_name}("
169
+ if arity == MAGIC_ARITY then
170
+ prefix += "int argc, VALUE *argv, VALUE self"
171
+ else
172
+ prefix += "VALUE self"
173
+ signature['args'].each do |arg, type|
174
+ prefix += ", VALUE _#{arg}"
175
+ end
176
+ end
177
+ prefix += ") {\n"
178
+ if arity == MAGIC_ARITY then
179
+ count = 0
180
+ signature['args'].each do |arg, type|
181
+ prefix += " #{type} #{arg} = #{ruby2c(type)}(argv[#{count}]);\n"
182
+ count += 1
183
+ end
184
+ else
185
+ signature['args'].each do |arg, type|
186
+ prefix += " #{type} #{arg} = #{ruby2c(type)}(_#{arg});\n"
187
+ end
188
+ end
189
+ # replace the function signature (hopefully) with new signature (prefix)
190
+ result.sub!(/[^;\/\"\>]+#{function_name}\s*\([^\{]+\{/, "\n" + prefix)
191
+ result.sub!(/\A\n/, '') # strip off the \n in front in case we added it
192
+ unless return_type == "void" then
193
+ raise "couldn't find return statement for #{function_name}" unless
194
+ result =~ /return/
195
+ result.gsub!(/return\s+([^\;\}]+)/) do
196
+ "return #{c2ruby(return_type)}(#{$1})"
197
+ end
198
+ else
199
+ result.sub!(/\s*\}\s*\Z/, "\nreturn Qnil;\n}")
200
+ end
201
+ else
202
+ prefix = "static #{return_type} #{function_name}("
203
+ result.sub!(/[^;\/\"\>]+#{function_name}\s*\(/, prefix)
204
+ result.sub!(/\A\n/, '') # strip off the \n in front in case we added it
205
+ end
206
+
207
+ @src << result
208
+ @sig[function_name] = arity
209
+
210
+ return result if $TESTING
211
+ end # def generate
212
+
213
+ attr_accessor :mod, :src, :sig, :flags, :libs if $TESTING
214
+
215
+ public
216
+
217
+ def load
218
+ require "#{@so_name}" or raise "require on #{@so_name} failed"
219
+ @mod.class_eval "include #{@mod_name}"
220
+ end
221
+
222
+ def build
223
+ rb_file = File.expand_path(caller[1].split(/:/).first) # [MS]
224
+
225
+ unless File.file? @so_name and File.mtime(rb_file) < File.mtime(@so_name)
226
+
227
+ src_name = "#{Inline.directory}/#{@mod_name}.c"
228
+ old_src_name = "#{src_name}.old"
229
+ should_compare = File.write_with_backup(src_name) do |io|
230
+ io.puts
231
+ io.puts "#include \"ruby.h\""
232
+ io.puts
233
+ io.puts @src.join("\n\n")
234
+ io.puts
235
+ io.puts
236
+ io.puts " VALUE c#{@mod_name};"
237
+ io.puts "#ifdef __cplusplus"
238
+ io.puts "extern \"C\" {"
239
+ io.puts "#endif"
240
+ io.puts " void Init_#{@mod_name}() {"
241
+ io.puts " c#{@mod_name} = rb_define_module(\"#{@mod_name}\");"
242
+ @sig.keys.sort.each do |name|
243
+ arity = @sig[name]
244
+ io.print " rb_define_method(c#{@mod_name}, \"#{name}\", "
245
+ io.puts "(VALUE(*)(ANYARGS))#{name}, #{arity});"
246
+ end
247
+ io.puts
248
+ io.puts " }"
249
+ io.puts "#ifdef __cplusplus"
250
+ io.puts "}"
251
+ io.puts "#endif"
252
+ io.puts
253
+ end
254
+
255
+ # recompile only if the files are different
256
+ recompile = true
257
+ if should_compare and File::compare(old_src_name, src_name, $DEBUG) then
258
+ recompile = false
259
+
260
+ # Updates the timestamps on all the generated/compiled files.
261
+ # Prevents us from entering this conditional unless the source
262
+ # file changes again.
263
+ File.utime(Time.now, Time.now, src_name, old_src_name, @so_name)
264
+ end
265
+
266
+ if recompile then
267
+
268
+ # extracted from mkmf.rb
269
+ srcdir = Config::CONFIG["srcdir"]
270
+ archdir = Config::CONFIG["archdir"]
271
+ if File.exist? archdir + "/ruby.h" then
272
+ hdrdir = archdir
273
+ elsif File.exist? srcdir + "/ruby.h" then
274
+ hdrdir = srcdir
275
+ else
276
+ $stderr.puts "ERROR: Can't find header files for ruby. Exiting..."
277
+ exit 1
278
+ end
279
+
280
+ flags = @flags.join(' ')
281
+ flags += " #{$INLINE_FLAGS}" if defined? $INLINE_FLAGS# DEPRECATE
282
+ libs = @libs.join(' ')
283
+ libs += " #{$INLINE_LIBS}" if defined? $INLINE_LIBS # DEPRECATE
284
+
285
+ cmd = "#{Config::CONFIG['LDSHARED']} #{flags} #{Config::CONFIG['CFLAGS']} -I #{hdrdir} -o #{@so_name} #{src_name} #{libs}"
286
+
287
+ if /mswin32/ =~ RUBY_PLATFORM then
288
+ cmd += " -link /INCREMENTAL:no /EXPORT:Init_#{@mod_name}"
289
+ end
290
+
291
+ $stderr.puts "Building #{@so_name} with '#{cmd}'" if $DEBUG
292
+ `#{cmd}`
293
+ raise "error executing #{cmd}: #{$?}" if $? != 0
294
+ $stderr.puts "Built successfully" if $DEBUG
295
+ end
296
+
297
+ else
298
+ $stderr.puts "#{@so_name} is up to date" if $DEBUG
299
+ end # unless (file is out of date)
300
+ end # def build
301
+
302
+ def initialize(mod)
303
+ @mod = mod
304
+ @mod_name = "Mod_#{@mod}"
305
+ @so_name = "#{Inline.directory}/#{@mod_name}.#{Config::CONFIG["DLEXT"]}"
306
+ @src = []
307
+ @sig = {}
308
+ @flags = []
309
+ @libs = []
310
+ end
311
+
312
+ # Adds compiler options to the compiler command line. No
313
+ # preprocessing is done, so you must have all your dashes and
314
+ # everything.
315
+
316
+ def add_compile_flags(*flags)
317
+ @flags.push(*flags)
318
+ end
319
+
320
+ # Adds linker flags to the link command line. No preprocessing is
321
+ # done, so you must have all your dashes and everything.
322
+
323
+ def add_link_flags(*flags)
324
+ @libs.push(*flags)
325
+ end
326
+
327
+ # Registers C type-casts <tt>r2c</tt> and <tt>c2r</tt> for
328
+ # <tt>type</tt>.
329
+
330
+ def add_type_converter(type, r2c, c2r)
331
+ $stderr.puts "WARNING: overridding #{type}" if @@type_map.has_key? type
332
+ @@type_map[type] = [r2c, c2r]
333
+ end
334
+
335
+ # Adds an include to the top of the file. Don't forget to use
336
+ # quotes or angle brackets.
337
+
338
+ def include(header)
339
+ @src << "#include #{header}"
340
+ end
341
+
342
+ # Adds any amount of text/code to the source
343
+
344
+ def prefix(code)
345
+ @src << code
346
+ end
347
+
348
+ # Adds a C function to the source, including performing automatic
349
+ # type conversion to arguments and the return value. Unknown type
350
+ # conversions can be extended by using +add_type_converter+.
351
+
352
+ def c src
353
+ self.generate(src)
354
+ end
355
+
356
+ # Adds a raw C function to the source. This version does not
357
+ # perform any type conversion and must conform to the ruby/C
358
+ # coding conventions.
359
+
360
+ def c_raw src
361
+ self.generate(src, false)
362
+ end
363
+
364
+ end # class Inline::C
365
+ end # module Inline
366
+
367
+ class Module
368
+
369
+ # Extends the Module class to have an inline method. The default
370
+ # language/builder used is C, but can be specified with the +lang+
371
+ # parameter.
372
+
373
+ def inline(lang = :C, testing=false)
374
+ require "inline/#{lang}" unless lang == :C
375
+ builder = Inline.const_get(lang).new self
376
+
377
+ yield builder
378
+
379
+ unless testing then
380
+ builder.build
381
+ builder.load
382
+ end
383
+ end
384
+ end
385
+
386
+ class File
387
+
388
+ # Equivalent to <tt>File::open</tt> with an associated block, but moves
389
+ # any existing file with the same name to the side first.
390
+
391
+ def self.write_with_backup(path) # returns true if file already existed
392
+
393
+ # move previous version to the side if it exists
394
+ renamed = false
395
+ if test ?f, path then
396
+ renamed = true
397
+ File.rename path, path + ".old"
398
+ end
399
+
400
+ File.open(path, "w") do |io|
401
+ yield(io)
402
+ end
403
+
404
+ return renamed
405
+ end
406
+
407
+ end # class File
408
+
409
+ class Dir
410
+
411
+ # +assert_secure+ checks to see that +path+ exists and has minimally
412
+ # writable permissions. If not, it prints an error and exits. It
413
+ # only works on +POSIX+ systems. Patches for other systems are
414
+ # welcome.
415
+
416
+ def self.assert_secure(path)
417
+ mode = File.stat(path).mode
418
+ unless ((mode % 01000) & 0022) == 0 then
419
+ if $TESTING then
420
+ raise 'InsecureDir'
421
+ else
422
+ $stderr.puts "#{path} is insecure (#{sprintf('%o', mode)}), needs 0700 for perms"
423
+ exit 1
424
+ end
425
+ end
426
+ end
427
+ end
@@ -0,0 +1,481 @@
1
+ #!/usr/bin/ruby -w
2
+
3
+ $TESTING = true
4
+
5
+ require 'inline'
6
+ require 'test/unit'
7
+
8
+ File.umask(0)
9
+
10
+ #class TestFile < Test::Unit::TestCase
11
+ # TODO def test_write_with_backup
12
+ #end
13
+
14
+ class TestDir < Test::Unit::TestCase
15
+
16
+ def setup
17
+ @dir = "/tmp/#{$$}"
18
+ @count = 1
19
+ Dir.mkdir @dir, 0700
20
+ end
21
+
22
+ def teardown
23
+ `rm -rf #{@dir}` unless $DEBUG
24
+ end
25
+
26
+ def util_assert_secure(perms, should_pass)
27
+ path = File.join(@dir, @count.to_s)
28
+ @count += 1
29
+ Dir.mkdir path, perms unless perms.nil?
30
+ if should_pass then
31
+ assert_nothing_raised do
32
+ Dir.assert_secure path
33
+ end
34
+ else
35
+ assert_raises(perms.nil? ? Errno::ENOENT : RuntimeError) do
36
+ Dir.assert_secure path
37
+ end
38
+ end
39
+ end
40
+
41
+ def test_assert_secure
42
+ # existing/good
43
+ util_assert_secure 0700, true
44
+ # existing/bad
45
+ util_assert_secure 0707, false
46
+ util_assert_secure 0770, false
47
+ util_assert_secure 0777, false
48
+ # missing
49
+ util_assert_secure nil, false
50
+ end
51
+ end
52
+
53
+ class TestInline < Test::Unit::TestCase
54
+
55
+ def setup
56
+ @rootdir = "/tmp/#{$$}"
57
+ Dir.mkdir @rootdir, 0700
58
+ ENV['INLINEDIR'] = @rootdir
59
+ end
60
+
61
+ def teardown
62
+ `rm -rf #{@rootdir}` unless $DEBUG
63
+ end
64
+
65
+ def test_rootdir
66
+ assert_equal(@rootdir, Inline.rootdir)
67
+ end
68
+
69
+ def test_directory
70
+ inlinedir = File.join(@rootdir, ".ruby_inline")
71
+ assert_equal(inlinedir, Inline.directory)
72
+ end
73
+
74
+ end
75
+
76
+ class TestInline
77
+ class TestC < Test::Unit::TestCase
78
+
79
+ # quick hack to make tests more readable,
80
+ # does nothing I wouldn't otherwise do...
81
+ def inline(lang=:C)
82
+ self.class.inline(lang, true) do |builder|
83
+ yield(builder)
84
+ end
85
+ end
86
+
87
+ def test_initialize
88
+ x = Inline::C.new(self.class)
89
+ assert_equal TestInline::TestC, x.mod
90
+ assert_equal [], x.src
91
+ assert_equal({}, x.sig)
92
+ assert_equal [], x.flags
93
+ assert_equal [], x.libs
94
+ end
95
+
96
+ def test_ruby2c
97
+ x = Inline::C.new(nil)
98
+ assert_equal 'NUM2CHR', x.ruby2c("char")
99
+ assert_equal 'STR2CSTR', x.ruby2c("char *")
100
+ assert_equal 'FIX2INT', x.ruby2c("int")
101
+ assert_equal 'NUM2INT', x.ruby2c("long")
102
+ assert_equal 'NUM2UINT', x.ruby2c("unsigned int")
103
+ assert_equal 'NUM2UINT', x.ruby2c("unsigned long")
104
+ assert_equal 'NUM2UINT', x.ruby2c("unsigned")
105
+
106
+ assert_raises RuntimeError do
107
+ x.ruby2c('blah')
108
+ end
109
+ end
110
+
111
+ def test_c2ruby
112
+ x = Inline::C.new(nil)
113
+ assert_equal 'CHR2FIX', x.c2ruby("char")
114
+ assert_equal 'rb_str_new2', x.c2ruby("char *")
115
+ assert_equal 'INT2FIX', x.c2ruby("int")
116
+ assert_equal 'INT2NUM', x.c2ruby("long")
117
+ assert_equal 'UINT2NUM', x.c2ruby("unsigned int")
118
+ assert_equal 'UINT2NUM', x.c2ruby("unsigned long")
119
+ assert_equal 'UINT2NUM', x.c2ruby("unsigned")
120
+
121
+ assert_raises RuntimeError do
122
+ x.c2ruby('blah')
123
+ end
124
+ end
125
+
126
+ def util_parse_signature(src, expected, t=nil, a=nil, b=nil)
127
+
128
+ result = nil
129
+ inline do |builder|
130
+ builder.add_type_converter t, a, b unless t.nil?
131
+ result = builder.parse_signature(src)
132
+ end
133
+
134
+ assert_equal(expected, result)
135
+ end
136
+
137
+ def test_parse_signature
138
+ src = "// stupid cpp comment
139
+ #include \"header.h\"
140
+ /* stupid c comment */
141
+ int
142
+ add(int x, int y) {
143
+ int result = x+y;
144
+ return result;
145
+ }
146
+ "
147
+
148
+ expected = {
149
+ 'name' => 'add',
150
+ 'return' => 'int',
151
+ 'arity' => 2,
152
+ 'args' => [
153
+ ['x', 'int'],
154
+ ['y', 'int']
155
+ ]
156
+ }
157
+
158
+ util_parse_signature(src, expected)
159
+ end
160
+
161
+ def test_parse_signature_custom
162
+
163
+ src = "// stupid cpp comment
164
+ #include \"header.h\"
165
+ /* stupid c comment */
166
+ int
167
+ add(fooby x, int y) {
168
+ int result = x+y;
169
+ return result;
170
+ }
171
+ "
172
+
173
+ expected = {
174
+ 'name' => 'add',
175
+ 'return' => 'int',
176
+ 'arity' => 2,
177
+ 'args' => [
178
+ [ 'x', 'fooby' ],
179
+ ['y', 'int']
180
+ ]
181
+ }
182
+
183
+ util_parse_signature(src, expected,
184
+ "fooby", "r2c_fooby", "c2r_fooby")
185
+ end
186
+
187
+ def test_parse_signature_register
188
+
189
+ src = "// stupid cpp comment
190
+ #include \"header.h\"
191
+ /* stupid c comment */
192
+ int
193
+ add(register int x, int y) {
194
+ int result = x+y;
195
+ return result;
196
+ }
197
+ "
198
+
199
+ expected = {
200
+ 'name' => 'add',
201
+ 'return' => 'int',
202
+ 'arity' => 2,
203
+ 'args' => [
204
+ [ 'x', 'register int' ],
205
+ ['y', 'int']
206
+ ]
207
+ }
208
+
209
+
210
+ util_parse_signature(src, expected,
211
+ "register int", 'FIX2INT', 'INT2FIX')
212
+ end
213
+
214
+ def util_generate(src, expected, expand_types=true)
215
+ result = ''
216
+ inline do |builder|
217
+ result = builder.generate src, expand_types
218
+ end
219
+ assert_equal(expected, result)
220
+ end
221
+
222
+ def util_generate_raw(src, expected)
223
+ util_generate(src, expected, false)
224
+ end
225
+
226
+ # Ruby Arity Rules, from the mouth of Matz:
227
+ # -2 = ruby array argv
228
+ # -1 = c array argv
229
+ # 0 = self
230
+ # 1 = self, value
231
+ # 2 = self, value, value
232
+ # ...
233
+ # 16 = self, value * 15
234
+
235
+ def test_generate_raw_arity_0
236
+ src = "VALUE y(VALUE self) {blah;}"
237
+
238
+ expected = "static VALUE y(VALUE self) {blah;}"
239
+
240
+ util_generate_raw(src, expected)
241
+ end
242
+
243
+ def test_generate_arity_0
244
+ src = "int y() { do_something; return 42; }"
245
+
246
+ expected = "static VALUE y(VALUE self) {\n do_something; return INT2FIX(42); }"
247
+
248
+ util_generate(src, expected)
249
+ end
250
+
251
+ def test_generate_arity_0_no_return
252
+ src = "void y() { do_something; }"
253
+
254
+ expected = "static VALUE y(VALUE self) {\n do_something;\nreturn Qnil;\n}"
255
+
256
+ util_generate(src, expected)
257
+ end
258
+
259
+ def test_generate_arity_0_void_return
260
+ src = "void y(void) {go_do_something_external;}"
261
+
262
+ expected = "static VALUE y(VALUE self) {
263
+ go_do_something_external;\nreturn Qnil;\n}"
264
+
265
+ util_generate(src, expected)
266
+ end
267
+
268
+ def test_generate_arity_0_int_return
269
+ src = "int x() {return 42}"
270
+
271
+ expected = "static VALUE x(VALUE self) {
272
+ return INT2FIX(42)}"
273
+
274
+ util_generate(src, expected)
275
+ end
276
+
277
+ def test_generate_raw_arity_1
278
+ src = "VALUE y(VALUE self, VALUE obj) {blah;}"
279
+
280
+ expected = "static VALUE y(VALUE self, VALUE obj) {blah;}"
281
+
282
+ util_generate_raw(src, expected)
283
+ end
284
+
285
+ def test_generate_arity_1
286
+ src = "int y(int x) {blah; return x+1;}"
287
+
288
+ expected = "static VALUE y(VALUE self, VALUE _x) {\n int x = FIX2INT(_x);\nblah; return INT2FIX(x+1);}"
289
+
290
+ util_generate(src, expected)
291
+ end
292
+
293
+ def test_generate_arity_1_no_return
294
+ src = "void y(int x) {blah;}"
295
+
296
+ expected = "static VALUE y(VALUE self, VALUE _x) {\n int x = FIX2INT(_x);\nblah;\nreturn Qnil;\n}"
297
+
298
+ util_generate(src, expected)
299
+ end
300
+
301
+ def test_generate_raw_arity_2
302
+ src = "VALUE func(VALUE self, VALUE obj1, VALUE obj2) {blah;}"
303
+
304
+ expected = "static VALUE func(VALUE self, VALUE obj1, VALUE obj2) {blah;}"
305
+
306
+ util_generate_raw(src, expected)
307
+ end
308
+
309
+ def test_generate_arity_2
310
+ src = "int func(int x, int y) {blah; return x+y;}"
311
+
312
+ expected = "static VALUE func(VALUE self, VALUE _x, VALUE _y) {\n int x = FIX2INT(_x);\n int y = FIX2INT(_y);\nblah; return INT2FIX(x+y);}"
313
+
314
+ util_generate(src, expected)
315
+ end
316
+
317
+ def test_generate_raw_arity_3
318
+ src = "VALUE func(VALUE self, VALUE obj1, VALUE obj2, VALUE obj3) {blah;}"
319
+
320
+ expected = "static VALUE func(VALUE self, VALUE obj1, VALUE obj2, VALUE obj3) {blah;}"
321
+
322
+ util_generate_raw(src, expected)
323
+ end
324
+
325
+ def test_generate_arity_3
326
+ src = "int func(int x, int y, int z) {blah; return x+y+z;}"
327
+
328
+ expected = "static VALUE func(int argc, VALUE *argv, VALUE self) {\n int x = FIX2INT(argv[0]);\n int y = FIX2INT(argv[1]);\n int z = FIX2INT(argv[2]);\nblah; return INT2FIX(x+y+z);}"
329
+
330
+ util_generate(src, expected)
331
+ end
332
+
333
+ def test_generate_comments
334
+ src = "// stupid cpp comment
335
+ /* stupid c comment */
336
+ int
337
+ add(int x, int y) { // add two numbers
338
+ return x+y;
339
+ }
340
+ "
341
+
342
+ expected = "static VALUE add(VALUE self, VALUE _x, VALUE _y) {
343
+ int x = FIX2INT(_x);
344
+ int y = FIX2INT(_y);
345
+ return INT2FIX(x+y);
346
+ }
347
+ "
348
+
349
+ util_generate(src, expected)
350
+ end
351
+
352
+ def test_generate_local_header
353
+ src = "// stupid cpp comment
354
+ #include \"header\"
355
+ /* stupid c comment */
356
+ int
357
+ add(int x, int y) { // add two numbers
358
+ return x+y;
359
+ }
360
+ "
361
+ # FIX: should be 2 spaces before the return. Can't find problem.
362
+ expected = "#include \"header\"
363
+ static VALUE add(VALUE self, VALUE _x, VALUE _y) {
364
+ int x = FIX2INT(_x);
365
+ int y = FIX2INT(_y);
366
+ return INT2FIX(x+y);
367
+ }
368
+ "
369
+ util_generate(src, expected)
370
+ end
371
+
372
+ def test_generate_system_header
373
+ src = "// stupid cpp comment
374
+ #include <header>
375
+ /* stupid c comment */
376
+ int
377
+ add(int x, int y) { // add two numbers
378
+ return x+y;
379
+ }
380
+ "
381
+ expected = "#include <header>
382
+ static VALUE add(VALUE self, VALUE _x, VALUE _y) {
383
+ int x = FIX2INT(_x);
384
+ int y = FIX2INT(_y);
385
+ return INT2FIX(x+y);
386
+ }
387
+ "
388
+ util_generate(src, expected)
389
+ end
390
+
391
+ def test_generate_wonky_return
392
+ src = "unsigned\nlong z(void) {return 42}"
393
+
394
+ expected = "static VALUE z(VALUE self) {
395
+ return UINT2NUM(42)}"
396
+
397
+ util_generate(src, expected)
398
+ end
399
+
400
+ def test_generate_compact
401
+ src = "int add(int x, int y) {return x+y}"
402
+
403
+ expected = "static VALUE add(VALUE self, VALUE _x, VALUE _y) {
404
+ int x = FIX2INT(_x);
405
+ int y = FIX2INT(_y);
406
+ return INT2FIX(x+y)}"
407
+
408
+ util_generate(src, expected)
409
+ end
410
+
411
+ def test_generate_char_star_normalize
412
+ src = "char\n\*\n blah( char*s) {puts(s); return s}"
413
+
414
+ expected = "static VALUE blah(VALUE self, VALUE _s) {
415
+ char * s = STR2CSTR(_s);
416
+ puts(s); return rb_str_new2(s)}"
417
+
418
+ util_generate(src, expected)
419
+ end
420
+
421
+ def test_c
422
+ builder = result = nil
423
+ inline(:C) do |b|
424
+ builder = b
425
+ result = builder.c "int add(int a, int b) { return a + b; }"
426
+ end
427
+
428
+ expected = "static VALUE add(VALUE self, VALUE _a, VALUE _b) {\n int a = FIX2INT(_a);\n int b = FIX2INT(_b);\n return INT2FIX(a + b); }"
429
+
430
+ assert_equal expected, result
431
+ assert_equal [expected], builder.src
432
+ end
433
+
434
+ def test_c_raw
435
+ src = "static VALUE answer_raw(int argc, VALUE *argv, VALUE self) { return INT2NUM(42); }"
436
+ builder = result = nil
437
+ inline(:C) do |b|
438
+ builder = b
439
+ result = builder.c_raw src.dup
440
+ end
441
+
442
+ expected = src
443
+
444
+ assert_equal expected, result
445
+ assert_equal [expected], builder.src
446
+ end
447
+
448
+ # I have _no_ idea how to test these
449
+ # TODO def test_build
450
+ # TODO def test_load
451
+
452
+ end # class TestC
453
+ end # class TestInline
454
+
455
+ class TestModule < Test::Unit::TestCase
456
+
457
+ def setup
458
+ @rootdir = "/tmp/#{$$}"
459
+ ENV['INLINEDIR'] = @rootdir
460
+ end
461
+
462
+ def teardown
463
+ `rm -rf #{@rootdir}` unless $DEBUG
464
+ end
465
+
466
+ def test_inline
467
+ self.class.inline(:C) do |builder|
468
+ builder.c "int add(int a, int b) { return a + b; }"
469
+ end
470
+ assert(test(?d, Inline.directory),
471
+ "inline dir should have been created")
472
+ assert(test(?f, File.join(Inline.directory, "Mod_TestModule.c")),
473
+ "Source should have been created")
474
+ assert(test(?f, File.join(Inline.directory,
475
+ "Mod_TestModule.#{Config::CONFIG["DLEXT"]}")),
476
+ "Source should have been created")
477
+ end
478
+
479
+ end
480
+
481
+ # Number of errors detected: 4