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