ripper_ruby_parser 1.0.0 → 1.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +30 -0
- data/README.md +3 -3
- data/Rakefile +4 -4
- data/lib/ripper_ruby_parser/commenting_ripper_parser.rb +5 -7
- data/lib/ripper_ruby_parser/parser.rb +2 -3
- data/lib/ripper_ruby_parser/sexp_handlers/arguments.rb +2 -6
- data/lib/ripper_ruby_parser/sexp_handlers/assignment.rb +16 -17
- data/lib/ripper_ruby_parser/sexp_handlers/blocks.rb +5 -5
- data/lib/ripper_ruby_parser/sexp_handlers/conditionals.rb +6 -7
- data/lib/ripper_ruby_parser/sexp_handlers/hashes.rb +4 -5
- data/lib/ripper_ruby_parser/sexp_handlers/helper_methods.rb +7 -11
- data/lib/ripper_ruby_parser/sexp_handlers/literals.rb +35 -26
- data/lib/ripper_ruby_parser/sexp_handlers/loops.rb +19 -18
- data/lib/ripper_ruby_parser/sexp_handlers/method_calls.rb +15 -15
- data/lib/ripper_ruby_parser/sexp_handlers/methods.rb +9 -18
- data/lib/ripper_ruby_parser/sexp_handlers/operators.rb +10 -10
- data/lib/ripper_ruby_parser/sexp_processor.rb +11 -14
- data/lib/ripper_ruby_parser/syntax_error.rb +1 -3
- data/lib/ripper_ruby_parser/version.rb +1 -1
- data/test/end_to_end/comparison_test.rb +0 -1
- data/test/end_to_end/lib_comparison_test.rb +0 -2
- data/test/end_to_end/line_numbering_test.rb +0 -1
- data/test/end_to_end/samples_comparison_test.rb +1 -1
- data/test/end_to_end/test_comparison_test.rb +0 -3
- data/test/pt_testcase/pt_test.rb +4 -4
- data/test/samples/inline.rb +704 -0
- data/test/test_helper.rb +39 -37
- data/test/unit/commenting_ripper_parser_test.rb +57 -59
- data/test/unit/parser_blocks_test.rb +19 -3
- data/test/unit/parser_conditionals_test.rb +0 -1
- data/test/unit/parser_literals_test.rb +25 -25
- data/test/unit/parser_method_calls_test.rb +19 -15
- data/test/unit/parser_test.rb +31 -24
- data/test/unit/sexp_processor_test.rb +1 -14
- metadata +67 -51
- data/lib/ripper_ruby_parser/sexp_ext.rb +0 -14
@@ -0,0 +1,704 @@
|
|
1
|
+
#!/usr/local/bin/ruby -w
|
2
|
+
|
3
|
+
##
|
4
|
+
# Ruby Inline is a framework for writing ruby extensions in foreign
|
5
|
+
# languages.
|
6
|
+
#
|
7
|
+
# == SYNOPSIS
|
8
|
+
#
|
9
|
+
# require 'inline'
|
10
|
+
# class MyClass
|
11
|
+
# inline do |builder|
|
12
|
+
# builder.include "<math.h>"
|
13
|
+
# builder.c %q{
|
14
|
+
# long factorial(int max) {
|
15
|
+
# int i=max, result=1;
|
16
|
+
# while (i >= 2) { result *= i--; }
|
17
|
+
# return result;
|
18
|
+
# }
|
19
|
+
# }
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# == DESCRIPTION
|
24
|
+
#
|
25
|
+
# Inline allows you to write foreign code within your ruby code. It
|
26
|
+
# automatically determines if the code in question has changed and
|
27
|
+
# builds it only when necessary. The extensions are then automatically
|
28
|
+
# loaded into the class/module that defines it.
|
29
|
+
#
|
30
|
+
# You can even write extra builders that will allow you to write
|
31
|
+
# inlined code in any language. Use Inline::C as a template and look
|
32
|
+
# at Module#inline for the required API.
|
33
|
+
#
|
34
|
+
# == PACKAGING
|
35
|
+
#
|
36
|
+
# To package your binaries into a gem, use hoe's INLINE and
|
37
|
+
# FORCE_PLATFORM env vars.
|
38
|
+
#
|
39
|
+
# Example:
|
40
|
+
#
|
41
|
+
# rake package INLINE=1
|
42
|
+
#
|
43
|
+
# or:
|
44
|
+
#
|
45
|
+
# rake package INLINE=1 FORCE_PLATFORM=mswin32
|
46
|
+
#
|
47
|
+
# See hoe for more details.
|
48
|
+
#
|
49
|
+
|
50
|
+
require "rbconfig"
|
51
|
+
require "digest/md5"
|
52
|
+
require 'fileutils'
|
53
|
+
require 'rubygems'
|
54
|
+
|
55
|
+
$TESTING = false unless defined? $TESTING
|
56
|
+
|
57
|
+
class CompilationError < RuntimeError; end
|
58
|
+
|
59
|
+
##
|
60
|
+
# The Inline module is the top-level module used. It is responsible
|
61
|
+
# for instantiating the builder for the right language used,
|
62
|
+
# compilation/linking when needed, and loading the inlined code into
|
63
|
+
# the current namespace.
|
64
|
+
|
65
|
+
module Inline
|
66
|
+
VERSION = '3.7.0'
|
67
|
+
|
68
|
+
WINDOZE = /win(32|64)/ =~ RUBY_PLATFORM
|
69
|
+
RUBINIUS = defined? RUBY_ENGINE
|
70
|
+
DEV_NULL = (WINDOZE ? 'nul' : '/dev/null')
|
71
|
+
GEM = (WINDOZE ? 'gem.bat' : 'gem')
|
72
|
+
RAKE = if WINDOZE then
|
73
|
+
'rake.bat'
|
74
|
+
elsif RUBINIUS then
|
75
|
+
File.join(Gem.bindir, 'rake')
|
76
|
+
else
|
77
|
+
"#{Gem.ruby} -S rake"
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
warn "RubyInline v #{VERSION}" if $DEBUG
|
82
|
+
|
83
|
+
protected
|
84
|
+
|
85
|
+
def self.rootdir
|
86
|
+
env = ENV['INLINEDIR'] || ENV['HOME']
|
87
|
+
|
88
|
+
# in case both INLINEDIR and HOME aren't defined, and under Windows
|
89
|
+
# default to HOMEDRIVE + HOMEPATH values
|
90
|
+
env = ENV['HOMEDRIVE'] + ENV['HOMEPATH'] if env.nil? and WINDOZE
|
91
|
+
|
92
|
+
if env.nil? then
|
93
|
+
abort "Define INLINEDIR or HOME in your environment and try again"
|
94
|
+
end
|
95
|
+
|
96
|
+
unless defined? @@rootdir and env == @@rootdir and test ?d, @@rootdir then
|
97
|
+
rootdir = env
|
98
|
+
Dir.mkdir rootdir, 0700 unless test ?d, rootdir
|
99
|
+
Dir.assert_secure rootdir
|
100
|
+
@@rootdir = rootdir
|
101
|
+
end
|
102
|
+
|
103
|
+
@@rootdir
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.directory
|
107
|
+
directory = File.join(rootdir, ".ruby_inline")
|
108
|
+
unless defined? @@directory and directory == @@directory
|
109
|
+
@@directory = File.join(self.rootdir, ".ruby_inline")
|
110
|
+
end
|
111
|
+
Dir.assert_secure directory
|
112
|
+
@@directory
|
113
|
+
end
|
114
|
+
|
115
|
+
# Inline::C is the default builder used and the only one provided by
|
116
|
+
# Inline. It can be used as a template to write builders for other
|
117
|
+
# languages. It understands type-conversions for the basic types and
|
118
|
+
# can be extended as needed.
|
119
|
+
|
120
|
+
class C
|
121
|
+
|
122
|
+
protected unless $TESTING
|
123
|
+
|
124
|
+
MAGIC_ARITY_THRESHOLD = 15
|
125
|
+
MAGIC_ARITY = -1
|
126
|
+
|
127
|
+
@@type_map = {
|
128
|
+
'char' => [ 'NUM2CHR', 'CHR2FIX' ],
|
129
|
+
'char *' => [ 'STR2CSTR', 'rb_str_new2' ],
|
130
|
+
'double' => [ 'NUM2DBL', 'rb_float_new' ],
|
131
|
+
'int' => [ 'F'+'IX2INT', 'INT2FIX' ],
|
132
|
+
'long' => [ 'NUM2INT', 'INT2NUM' ],
|
133
|
+
'unsigned int' => [ 'NUM2UINT', 'UINT2NUM' ],
|
134
|
+
'unsigned long' => [ 'NUM2UINT', 'UINT2NUM' ],
|
135
|
+
'unsigned' => [ 'NUM2UINT', 'UINT2NUM' ],
|
136
|
+
'VALUE' => [ '', '' ],
|
137
|
+
# Can't do these converters because they conflict with the above:
|
138
|
+
# ID2SYM(x), SYM2ID(x), NUM2DBL(x), F\IX2UINT(x)
|
139
|
+
}
|
140
|
+
|
141
|
+
def ruby2c(type)
|
142
|
+
raise ArgumentError, "Unknown type #{type.inspect}" unless @@type_map.has_key? type
|
143
|
+
@@type_map[type].first
|
144
|
+
end
|
145
|
+
|
146
|
+
def c2ruby(type)
|
147
|
+
raise ArgumentError, "Unknown type #{type.inspect}" unless @@type_map.has_key? type
|
148
|
+
@@type_map[type].last
|
149
|
+
end
|
150
|
+
|
151
|
+
def strip_comments(src)
|
152
|
+
# strip c-comments
|
153
|
+
src = src.gsub(%r%\s*/\*.*?\*/%m, '')
|
154
|
+
# strip cpp-comments
|
155
|
+
src = src.gsub(%r%^\s*//.*?\n%, '')
|
156
|
+
src = src.gsub(%r%[ \t]*//[^\n]*%, '')
|
157
|
+
src
|
158
|
+
end
|
159
|
+
|
160
|
+
def parse_signature(src, raw=false)
|
161
|
+
|
162
|
+
sig = self.strip_comments(src)
|
163
|
+
# strip preprocessor directives
|
164
|
+
sig.gsub!(/^\s*\#.*(\\\n.*)*/, '')
|
165
|
+
# strip {}s
|
166
|
+
sig.gsub!(/\{[^\}]*\}/, '{ }')
|
167
|
+
# clean and collapse whitespace
|
168
|
+
sig.gsub!(/\s+/, ' ')
|
169
|
+
|
170
|
+
unless defined? @types then
|
171
|
+
@types = 'void|' + @@type_map.keys.map{|x| Regexp.escape(x)}.join('|')
|
172
|
+
end
|
173
|
+
|
174
|
+
if /(#{@types})\s*(\w+)\s*\(([^)]*)\)/ =~ sig then
|
175
|
+
return_type, function_name, arg_string = $1, $2, $3
|
176
|
+
args = []
|
177
|
+
arg_string.split(',').each do |arg|
|
178
|
+
|
179
|
+
# helps normalize into 'char * varname' form
|
180
|
+
arg = arg.gsub(/\s*\*\s*/, ' * ').strip
|
181
|
+
|
182
|
+
if /(((#{@types})\s*\*?)+)\s+(\w+)\s*$/ =~ arg then
|
183
|
+
args.push([$4, $1])
|
184
|
+
elsif arg != "void" then
|
185
|
+
warn "WAR\NING: '#{arg}' not understood"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
arity = args.size
|
190
|
+
arity = MAGIC_ARITY if raw
|
191
|
+
|
192
|
+
return {
|
193
|
+
'return' => return_type,
|
194
|
+
'name' => function_name,
|
195
|
+
'args' => args,
|
196
|
+
'arity' => arity
|
197
|
+
}
|
198
|
+
end
|
199
|
+
|
200
|
+
raise SyntaxError, "Can't parse signature: #{sig}"
|
201
|
+
end # def parse_signature
|
202
|
+
|
203
|
+
def generate(src, options={})
|
204
|
+
options = {:expand_types=>options} unless Hash === options
|
205
|
+
|
206
|
+
expand_types = options[:expand_types]
|
207
|
+
singleton = options[:singleton]
|
208
|
+
result = self.strip_comments(src)
|
209
|
+
|
210
|
+
signature = parse_signature(src, !expand_types)
|
211
|
+
function_name = signature['name']
|
212
|
+
method_name = options[:method_name] || function_name
|
213
|
+
return_type = signature['return']
|
214
|
+
arity = signature['arity']
|
215
|
+
|
216
|
+
raise ArgumentError, "too many arguments" if arity > MAGIC_ARITY_THRESHOLD
|
217
|
+
|
218
|
+
if expand_types then
|
219
|
+
prefix = "static VALUE #{function_name}("
|
220
|
+
if arity == MAGIC_ARITY then
|
221
|
+
prefix += "int argc, VALUE *argv, VALUE self"
|
222
|
+
else
|
223
|
+
prefix += "VALUE self"
|
224
|
+
prefix += signature['args'].map { |arg, type| ", VALUE _#{arg}"}.join
|
225
|
+
end
|
226
|
+
prefix += ") {\n"
|
227
|
+
prefix += signature['args'].map { |arg, type|
|
228
|
+
" #{type} #{arg} = #{ruby2c(type)}(_#{arg});\n"
|
229
|
+
}.join
|
230
|
+
|
231
|
+
# replace the function signature (hopefully) with new sig (prefix)
|
232
|
+
result.sub!(/[^;\/\"\>]+#{function_name}\s*\([^\{]+\{/, "\n" + prefix)
|
233
|
+
result.sub!(/\A\n/, '') # strip off the \n in front in case we added it
|
234
|
+
unless return_type == "void" then
|
235
|
+
raise SyntaxError, "Couldn't find return statement for #{function_name}" unless
|
236
|
+
result =~ /return/
|
237
|
+
result.gsub!(/return\s+([^\;\}]+)/) do
|
238
|
+
"return #{c2ruby(return_type)}(#{$1})"
|
239
|
+
end
|
240
|
+
else
|
241
|
+
result.sub!(/\s*\}\s*\Z/, "\nreturn Qnil;\n}")
|
242
|
+
end
|
243
|
+
else
|
244
|
+
prefix = "static #{return_type} #{function_name}("
|
245
|
+
result.sub!(/[^;\/\"\>]+#{function_name}\s*\(/, prefix)
|
246
|
+
result.sub!(/\A\n/, '') # strip off the \n in front in case we added it
|
247
|
+
end
|
248
|
+
|
249
|
+
delta = if result =~ /\A(static.*?\{)/m then
|
250
|
+
$1.split(/\n/).size
|
251
|
+
else
|
252
|
+
warn "WAR\NING: Can't find signature in #{result.inspect}\n" unless $TESTING
|
253
|
+
0
|
254
|
+
end
|
255
|
+
|
256
|
+
file, line = caller[1].split(/:/)
|
257
|
+
result = "# line #{line.to_i + delta} \"#{file}\"\n" + result unless $DEBUG and not $TESTING
|
258
|
+
|
259
|
+
@src << result
|
260
|
+
@sig[function_name] = [arity,singleton,method_name]
|
261
|
+
|
262
|
+
return result if $TESTING
|
263
|
+
end # def generate
|
264
|
+
|
265
|
+
def module_name
|
266
|
+
unless defined? @module_name then
|
267
|
+
module_name = @mod.name.gsub('::','__')
|
268
|
+
md5 = Digest::MD5.new
|
269
|
+
@sig.keys.sort_by { |x| x.to_s }.each { |m| md5 << m.to_s }
|
270
|
+
@module_name = "Inline_#{module_name}_#{md5.to_s[0,4]}"
|
271
|
+
end
|
272
|
+
@module_name
|
273
|
+
end
|
274
|
+
|
275
|
+
def so_name
|
276
|
+
unless defined? @so_name then
|
277
|
+
@so_name = "#{Inline.directory}/#{module_name}.#{Config::CONFIG["DLEXT"]}"
|
278
|
+
end
|
279
|
+
@so_name
|
280
|
+
end
|
281
|
+
|
282
|
+
attr_reader :rb_file, :mod
|
283
|
+
if $TESTING then
|
284
|
+
attr_writer :mod
|
285
|
+
attr_accessor :src, :sig, :flags, :libs
|
286
|
+
end
|
287
|
+
|
288
|
+
public
|
289
|
+
|
290
|
+
def initialize(mod)
|
291
|
+
raise ArgumentError, "Class/Module arg is required" unless Module === mod
|
292
|
+
# new (but not on some 1.8s) -> inline -> real_caller|eval
|
293
|
+
stack = caller
|
294
|
+
meth = stack.shift until meth =~ /in .(inline|test_|setup)/ or stack.empty?
|
295
|
+
raise "Couldn't discover caller" if stack.empty?
|
296
|
+
real_caller = stack.first
|
297
|
+
real_caller = stack[3] if real_caller =~ /\(eval\)/
|
298
|
+
real_caller = real_caller.split(/:/, 3)[0..1]
|
299
|
+
@real_caller = real_caller.join ':'
|
300
|
+
@rb_file = File.expand_path real_caller.first
|
301
|
+
|
302
|
+
@mod = mod
|
303
|
+
@src = []
|
304
|
+
@inc = []
|
305
|
+
@sig = {}
|
306
|
+
@flags = []
|
307
|
+
@libs = []
|
308
|
+
@init_extra = []
|
309
|
+
@include_ruby_first = true
|
310
|
+
end
|
311
|
+
|
312
|
+
##
|
313
|
+
# Attempts to load pre-generated code returning true if it succeeds.
|
314
|
+
|
315
|
+
def load_cache
|
316
|
+
begin
|
317
|
+
file = File.join("inline", File.basename(so_name))
|
318
|
+
if require file then
|
319
|
+
dir = Inline.directory
|
320
|
+
warn "WAR\NING: #{dir} exists but is not being used" if test ?d, dir and $VERBOSE
|
321
|
+
return true
|
322
|
+
end
|
323
|
+
rescue LoadError
|
324
|
+
end
|
325
|
+
return false
|
326
|
+
end
|
327
|
+
|
328
|
+
##
|
329
|
+
# Loads the generated code back into ruby
|
330
|
+
|
331
|
+
def load
|
332
|
+
require "#{so_name}" or raise LoadError, "require on #{so_name} failed"
|
333
|
+
end
|
334
|
+
|
335
|
+
##
|
336
|
+
# Builds the source file, if needed, and attempts to compile it.
|
337
|
+
|
338
|
+
def build
|
339
|
+
so_name = self.so_name
|
340
|
+
so_exists = File.file? so_name
|
341
|
+
unless so_exists and File.mtime(rb_file) < File.mtime(so_name) then
|
342
|
+
|
343
|
+
unless File.directory? Inline.directory then
|
344
|
+
warn "NOTE: creating #{Inline.directory} for RubyInline" if $DEBUG
|
345
|
+
Dir.mkdir Inline.directory, 0700
|
346
|
+
end
|
347
|
+
|
348
|
+
src_name = "#{Inline.directory}/#{module_name}.c"
|
349
|
+
old_src_name = "#{src_name}.old"
|
350
|
+
should_compare = File.write_with_backup(src_name) do |io|
|
351
|
+
if @include_ruby_first
|
352
|
+
@inc.unshift "#include \"ruby.h\""
|
353
|
+
else
|
354
|
+
@inc.push "#include \"ruby.h\""
|
355
|
+
end
|
356
|
+
|
357
|
+
io.puts
|
358
|
+
io.puts @inc.join("\n")
|
359
|
+
io.puts
|
360
|
+
io.puts @src.join("\n\n")
|
361
|
+
io.puts
|
362
|
+
io.puts
|
363
|
+
io.puts "#ifdef __cplusplus"
|
364
|
+
io.puts "extern \"C\" {"
|
365
|
+
io.puts "#endif"
|
366
|
+
io.puts " __declspec(dllexport)" if WINDOZE
|
367
|
+
io.puts " void Init_#{module_name}() {"
|
368
|
+
io.puts " VALUE c = rb_cObject;"
|
369
|
+
|
370
|
+
# TODO: use rb_class2path
|
371
|
+
# io.puts " VALUE c = rb_path2class(#{@mod.name.inspect});"
|
372
|
+
io.puts @mod.name.split("::").map { |n|
|
373
|
+
" c = rb_const_get(c,rb_intern(\"#{n}\"));"
|
374
|
+
}.join("\n")
|
375
|
+
|
376
|
+
@sig.keys.sort.each do |name|
|
377
|
+
arity, singleton, method_name = @sig[name]
|
378
|
+
if singleton then
|
379
|
+
io.print " rb_define_singleton_method(c, \"#{method_name}\", "
|
380
|
+
else
|
381
|
+
io.print " rb_define_method(c, \"#{method_name}\", "
|
382
|
+
end
|
383
|
+
io.puts "(VALUE(*)(ANYARGS))#{name}, #{arity});"
|
384
|
+
end
|
385
|
+
io.puts @init_extra.join("\n") unless @init_extra.empty?
|
386
|
+
|
387
|
+
io.puts
|
388
|
+
io.puts " }"
|
389
|
+
io.puts "#ifdef __cplusplus"
|
390
|
+
io.puts "}"
|
391
|
+
io.puts "#endif"
|
392
|
+
io.puts
|
393
|
+
end
|
394
|
+
|
395
|
+
# recompile only if the files are different
|
396
|
+
recompile = true
|
397
|
+
if so_exists and should_compare and
|
398
|
+
FileUtils.compare_file(old_src_name, src_name) then
|
399
|
+
recompile = false
|
400
|
+
|
401
|
+
# Updates the timestamps on all the generated/compiled files.
|
402
|
+
# Prevents us from entering this conditional unless the source
|
403
|
+
# file changes again.
|
404
|
+
t = Time.now
|
405
|
+
File.utime(t, t, src_name, old_src_name, so_name)
|
406
|
+
end
|
407
|
+
|
408
|
+
if recompile then
|
409
|
+
|
410
|
+
hdrdir = %w(srcdir archdir rubyhdrdir).map { |name|
|
411
|
+
Config::CONFIG[name]
|
412
|
+
}.find { |dir|
|
413
|
+
dir and File.exist? File.join(dir, "/ruby.h")
|
414
|
+
} or abort "ERROR: Can't find header dir for ruby. Exiting..."
|
415
|
+
|
416
|
+
flags = @flags.join(' ')
|
417
|
+
libs = @libs.join(' ')
|
418
|
+
|
419
|
+
config_hdrdir = if RUBY_VERSION > '1.9' then
|
420
|
+
"-I #{File.join hdrdir, RbConfig::CONFIG['arch']}"
|
421
|
+
else
|
422
|
+
nil
|
423
|
+
end
|
424
|
+
|
425
|
+
cmd = [ Config::CONFIG['LDSHARED'],
|
426
|
+
flags,
|
427
|
+
Config::CONFIG['CCDLFLAGS'],
|
428
|
+
Config::CONFIG['CFLAGS'],
|
429
|
+
'-I', hdrdir,
|
430
|
+
config_hdrdir,
|
431
|
+
'-I', Config::CONFIG['includedir'],
|
432
|
+
"-L#{Config::CONFIG['libdir']}",
|
433
|
+
'-o', so_name.inspect,
|
434
|
+
File.expand_path(src_name).inspect,
|
435
|
+
libs,
|
436
|
+
crap_for_windoze ].join(' ')
|
437
|
+
|
438
|
+
# TODO: remove after osx 10.5.2
|
439
|
+
cmd += ' -flat_namespace -undefined suppress' if
|
440
|
+
RUBY_PLATFORM =~ /darwin9\.[01]/
|
441
|
+
cmd += " 2> #{DEV_NULL}" if $TESTING and not $DEBUG
|
442
|
+
|
443
|
+
warn "Building #{so_name} with '#{cmd}'" if $DEBUG
|
444
|
+
result = `#{cmd}`
|
445
|
+
warn "Output:\n#{result}" if $DEBUG
|
446
|
+
if $? != 0 then
|
447
|
+
bad_src_name = src_name + ".bad"
|
448
|
+
File.rename src_name, bad_src_name
|
449
|
+
raise CompilationError, "error executing #{cmd.inspect}: #{$?}\nRenamed #{src_name} to #{bad_src_name}"
|
450
|
+
end
|
451
|
+
|
452
|
+
# NOTE: manifest embedding is only required when using VC8 ruby
|
453
|
+
# build or compiler.
|
454
|
+
# Errors from this point should be ignored if Config::CONFIG['arch']
|
455
|
+
# (RUBY_PLATFORM) matches 'i386-mswin32_80'
|
456
|
+
if WINDOZE and RUBY_PLATFORM =~ /_80$/ then
|
457
|
+
Dir.chdir Inline.directory do
|
458
|
+
cmd = "mt /manifest lib.so.manifest /outputresource:so.dll;#2"
|
459
|
+
warn "Embedding manifest with '#{cmd}'" if $DEBUG
|
460
|
+
result = `#{cmd}`
|
461
|
+
warn "Output:\n#{result}" if $DEBUG
|
462
|
+
if $? != 0 then
|
463
|
+
raise CompilationError, "error executing #{cmd}: #{$?}"
|
464
|
+
end
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
warn "Built successfully" if $DEBUG
|
469
|
+
end
|
470
|
+
|
471
|
+
else
|
472
|
+
warn "#{so_name} is up to date" if $DEBUG
|
473
|
+
end # unless (file is out of date)
|
474
|
+
end # def build
|
475
|
+
|
476
|
+
##
|
477
|
+
# Returns extra compilation flags for windoze platforms. Ugh.
|
478
|
+
|
479
|
+
def crap_for_windoze
|
480
|
+
# gawd windoze land sucks
|
481
|
+
case RUBY_PLATFORM
|
482
|
+
when /mswin32/ then
|
483
|
+
" -link /LIBPATH:\"#{Config::CONFIG['libdir']}\" /DEFAULTLIB:\"#{Config::CONFIG['LIBRUBY']}\" /INCREMENTAL:no /EXPORT:Init_#{module_name}"
|
484
|
+
when /mingw32/ then
|
485
|
+
" -Wl,--enable-auto-import -L#{Config::CONFIG['libdir']} -lmsvcrt-ruby18"
|
486
|
+
when /i386-cygwin/ then
|
487
|
+
' -L/usr/local/lib -lruby.dll'
|
488
|
+
else
|
489
|
+
''
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
493
|
+
##
|
494
|
+
# Adds compiler options to the compiler command line. No
|
495
|
+
# preprocessing is done, so you must have all your dashes and
|
496
|
+
# everything.
|
497
|
+
|
498
|
+
def add_compile_flags(*flags)
|
499
|
+
@flags.push(*flags)
|
500
|
+
end
|
501
|
+
|
502
|
+
##
|
503
|
+
# Adds linker flags to the link command line. No preprocessing is
|
504
|
+
# done, so you must have all your dashes and everything.
|
505
|
+
|
506
|
+
def add_link_flags(*flags)
|
507
|
+
@libs.push(*flags)
|
508
|
+
end
|
509
|
+
|
510
|
+
##
|
511
|
+
# Adds custom content to the end of the init function.
|
512
|
+
|
513
|
+
def add_to_init(*src)
|
514
|
+
@init_extra.push(*src)
|
515
|
+
end
|
516
|
+
|
517
|
+
##
|
518
|
+
# Registers C type-casts +r2c+ and +c2r+ for +type+.
|
519
|
+
|
520
|
+
def add_type_converter(type, r2c, c2r)
|
521
|
+
warn "WAR\NING: overridding #{type} on #{caller[0]}" if @@type_map.has_key? type
|
522
|
+
@@type_map[type] = [r2c, c2r]
|
523
|
+
end
|
524
|
+
|
525
|
+
##
|
526
|
+
# Maps a ruby constant to C (with the same name)
|
527
|
+
|
528
|
+
def map_ruby_const(*names)
|
529
|
+
names.each do |name|
|
530
|
+
self.prefix "static VALUE #{name};"
|
531
|
+
self.add_to_init " #{name} = rb_const_get(c, rb_intern(#{name.to_s.inspect}));"
|
532
|
+
end
|
533
|
+
end
|
534
|
+
|
535
|
+
##
|
536
|
+
# Maps a C constant to ruby (with the same
|
537
|
+
# name). +names_and_types+ is a hash that maps the name of the
|
538
|
+
# constant to its C type.
|
539
|
+
|
540
|
+
def map_c_const(names_and_types)
|
541
|
+
names_and_types.each do |name, typ|
|
542
|
+
self.add_to_init " rb_define_const(c, #{name.to_s.inspect}, #{c2ruby(typ.to_s)}(#{name}));"
|
543
|
+
end
|
544
|
+
end
|
545
|
+
|
546
|
+
##
|
547
|
+
# Adds an include to the top of the file. Don't forget to use
|
548
|
+
# quotes or angle brackets.
|
549
|
+
|
550
|
+
def include(header)
|
551
|
+
@inc << "#include #{header}"
|
552
|
+
end
|
553
|
+
|
554
|
+
##
|
555
|
+
# Specifies that the the ruby.h header should be included *after* custom
|
556
|
+
# header(s) instead of before them.
|
557
|
+
|
558
|
+
def include_ruby_last
|
559
|
+
@include_ruby_first = false
|
560
|
+
end
|
561
|
+
|
562
|
+
##
|
563
|
+
# Adds any amount of text/code to the source
|
564
|
+
|
565
|
+
def prefix(code)
|
566
|
+
@src << code
|
567
|
+
end
|
568
|
+
|
569
|
+
##
|
570
|
+
# Adds a C function to the source, including performing automatic
|
571
|
+
# type conversion to arguments and the return value. The Ruby
|
572
|
+
# method name can be overridden by providing method_name. Unknown
|
573
|
+
# type conversions can be extended by using +add_type_converter+.
|
574
|
+
|
575
|
+
def c src, options = {}
|
576
|
+
options = {
|
577
|
+
:expand_types => true,
|
578
|
+
}.merge options
|
579
|
+
self.generate src, options
|
580
|
+
end
|
581
|
+
|
582
|
+
##
|
583
|
+
# Same as +c+, but adds a class function.
|
584
|
+
|
585
|
+
def c_singleton src, options = {}
|
586
|
+
options = {
|
587
|
+
:expand_types => true,
|
588
|
+
:singleton => true,
|
589
|
+
}.merge options
|
590
|
+
self.generate src, options
|
591
|
+
end
|
592
|
+
|
593
|
+
##
|
594
|
+
# Adds a raw C function to the source. This version does not
|
595
|
+
# perform any type conversion and must conform to the ruby/C
|
596
|
+
# coding conventions. The Ruby method name can be overridden
|
597
|
+
# by providing method_name.
|
598
|
+
|
599
|
+
def c_raw src, options = {}
|
600
|
+
self.generate src, options
|
601
|
+
end
|
602
|
+
|
603
|
+
##
|
604
|
+
# Same as +c_raw+, but adds a class function.
|
605
|
+
|
606
|
+
def c_raw_singleton src, options = {}
|
607
|
+
options = {
|
608
|
+
:singleton => true,
|
609
|
+
}.merge options
|
610
|
+
self.generate src, options
|
611
|
+
end
|
612
|
+
|
613
|
+
end # class Inline::C
|
614
|
+
end # module Inline
|
615
|
+
|
616
|
+
class Module
|
617
|
+
|
618
|
+
##
|
619
|
+
# options is a hash that allows you to pass extra data to your
|
620
|
+
# builder. The only key that is guaranteed to exist is :testing.
|
621
|
+
|
622
|
+
attr_reader :options
|
623
|
+
|
624
|
+
##
|
625
|
+
# Extends the Module class to have an inline method. The default
|
626
|
+
# language/builder used is C, but can be specified with the +lang+
|
627
|
+
# parameter.
|
628
|
+
|
629
|
+
def inline(lang = :C, options={})
|
630
|
+
case options
|
631
|
+
when TrueClass, FalseClass then
|
632
|
+
warn "WAR\NING: 2nd argument to inline is now a hash, changing to {:testing=>#{options}}" unless options
|
633
|
+
options = { :testing => options }
|
634
|
+
when Hash
|
635
|
+
options[:testing] ||= false
|
636
|
+
else
|
637
|
+
raise ArgumentError, "BLAH"
|
638
|
+
end
|
639
|
+
|
640
|
+
builder_class = begin
|
641
|
+
Inline.const_get(lang)
|
642
|
+
rescue NameError
|
643
|
+
require "inline/#{lang}"
|
644
|
+
Inline.const_get(lang)
|
645
|
+
end
|
646
|
+
|
647
|
+
@options = options
|
648
|
+
builder = builder_class.new self
|
649
|
+
|
650
|
+
yield builder
|
651
|
+
|
652
|
+
unless options[:testing] then
|
653
|
+
unless builder.load_cache then
|
654
|
+
builder.build
|
655
|
+
builder.load
|
656
|
+
end
|
657
|
+
end
|
658
|
+
end
|
659
|
+
end
|
660
|
+
|
661
|
+
class File
|
662
|
+
|
663
|
+
##
|
664
|
+
# Equivalent to +File::open+ with an associated block, but moves
|
665
|
+
# any existing file with the same name to the side first.
|
666
|
+
|
667
|
+
def self.write_with_backup(path) # returns true if file already existed
|
668
|
+
|
669
|
+
# move previous version to the side if it exists
|
670
|
+
renamed = false
|
671
|
+
if test ?f, path then
|
672
|
+
renamed = true
|
673
|
+
File.rename path, path + ".old"
|
674
|
+
end
|
675
|
+
|
676
|
+
File.open(path, "w") do |io|
|
677
|
+
yield(io)
|
678
|
+
end
|
679
|
+
|
680
|
+
return renamed
|
681
|
+
end
|
682
|
+
end # class File
|
683
|
+
|
684
|
+
class Dir
|
685
|
+
|
686
|
+
##
|
687
|
+
# +assert_secure+ checks that if a +path+ exists it has minimally
|
688
|
+
# writable permissions. If not, it prints an error and exits. It
|
689
|
+
# only works on +POSIX+ systems. Patches for other systems are
|
690
|
+
# welcome.
|
691
|
+
|
692
|
+
def self.assert_secure(path)
|
693
|
+
mode = File.stat(path).mode
|
694
|
+
unless ((mode % 01000) & 0022) == 0 then
|
695
|
+
if $TESTING then
|
696
|
+
raise SecurityError, "Directory #{path} is insecure"
|
697
|
+
else
|
698
|
+
abort "#{path} is insecure (#{'%o' % mode}). It may not be group or world writable. Exiting."
|
699
|
+
end
|
700
|
+
end
|
701
|
+
rescue Errno::ENOENT
|
702
|
+
# If it ain't there, it's certainly secure
|
703
|
+
end
|
704
|
+
end
|