RubyInline 3.1.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,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