RubyInline 3.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +120 -0
- data/Makefile +37 -0
- data/Manifest.txt +12 -0
- data/README.txt +118 -0
- data/example.rb +75 -0
- data/example2.rb +29 -0
- data/inline-compat.rb +44 -0
- data/inline.gemspec +33 -0
- data/inline.rb +427 -0
- data/test_inline.rb +481 -0
- data/tutorial/example1.rb +63 -0
- data/tutorial/example2.rb +96 -0
- metadata +52 -0
data/inline.rb
ADDED
@@ -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
|
data/test_inline.rb
ADDED
@@ -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
|