ffi-inliner 0.2.1

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/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *~
2
+ *.*~
3
+ ^#
4
+ pkg
5
+ announcement.txt
6
+
data/History.txt ADDED
@@ -0,0 +1,21 @@
1
+ == 0.2.1 / 2009-08-05
2
+
3
+ * Bug fix
4
+ * Add fPIC option to gcc compiler to fix AMD64 compilation issue
5
+
6
+ == 0.2.0 / 2009-07-20
7
+
8
+ * Major enhancements
9
+ * Add support for different compiler backends
10
+ * Improve compatibility with Windows and OSX boxes
11
+ * Minor enhancements
12
+ * Add support for configuration block syntax
13
+ * Remove dependency from ffi-tcc and libtcc
14
+ * Improve compatibility with MRI 1.9.1
15
+ * Cleanup and code refactoring
16
+
17
+ == 0.1.0 / 2009-07-07
18
+
19
+ * 1 major enhancement
20
+ * Birthday!
21
+
data/README.rdoc ADDED
@@ -0,0 +1,85 @@
1
+ ffi-inliner
2
+ by Andrea Fazzi
3
+ http://github.com/remogatto/ffi-inliner
4
+
5
+ == DESCRIPTION:
6
+
7
+ With ffi-inliner you can run C code within your ruby script.
8
+
9
+ == FEATURES/PROBLEMS:
10
+
11
+ * Mix C snippets in your Ruby code and gulp it on the fly!
12
+ * It's based on Ruby-FFI so the C code you inject is portable across
13
+ Ruby implementation!
14
+ * Yep, it means that you can run it on JRuby too!
15
+ * Fast compilation through tcc[http://bellard.org/tcc/]
16
+ * But it can use the system's compiler (e.g. gcc) on those platforms
17
+ that don't support tcc (e.g. OSX)
18
+
19
+ == SYNOPSIS:
20
+
21
+ require 'ffi-inliner'
22
+
23
+ module MyLib
24
+ extend Inliner
25
+ inline 'void say_hello(char* name) { printf("Hello, %s\n", name); }'
26
+ end
27
+
28
+ MyLib.say_hello('boys')
29
+
30
+ class Foo
31
+ include MyLib
32
+ end
33
+
34
+ Foo.new.say_hello('foos')
35
+
36
+ For other hints see the examples/ folder or visit the
37
+ wiki[http://wiki.github.com/remogatto/ffi-inliner/tutorial]
38
+
39
+ == REQUIREMENTS:
40
+
41
+ * ffi >= 0.4.0
42
+ * gcc and/or tcc >= 0.9.25 (optional)
43
+
44
+ == DOWNLOAD/INSTALL:
45
+
46
+ From rubyforge:
47
+
48
+ [sudo] gem install ffi-inliner
49
+
50
+ or from github:
51
+
52
+ git clone git://github.com/remogatto/ffi-inliner
53
+ cd ffi-inliner
54
+ sudo rake gem:install
55
+
56
+ == CREDITS
57
+
58
+ This software is inspired to
59
+ RubyInline[http://www.zenspider.com/ZSS/Products/RubyInline/] by Ryan
60
+ Davis. Thank you Ryan.
61
+
62
+ == LICENSE:
63
+
64
+ (The MIT License)
65
+
66
+ Copyright (c) 2009 Andrea Fazzi
67
+
68
+ Permission is hereby granted, free of charge, to any person obtaining
69
+ a copy of this software and associated documentation files (the
70
+ 'Software'), to deal in the Software without restriction, including
71
+ without limitation the rights to use, copy, modify, merge, publish,
72
+ distribute, sublicense, and/or sell copies of the Software, and to
73
+ permit persons to whom the Software is furnished to do so, subject to
74
+ the following conditions:
75
+
76
+ The above copyright notice and this permission notice shall be
77
+ included in all copies or substantial portions of the Software.
78
+
79
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
80
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
81
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
82
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
83
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
84
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
85
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,44 @@
1
+ # Look in the tasks/setup.rb file for the various options that can be
2
+ # configured in this Rakefile. The .rake files in the tasks directory
3
+ # are where the options are used.
4
+
5
+ $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), 'lib')))
6
+ require 'ffi-inliner/version'
7
+
8
+ begin
9
+ require 'bones'
10
+ Bones.setup
11
+ rescue LoadError
12
+ begin
13
+ load 'tasks/setup.rb'
14
+ rescue LoadError
15
+ raise RuntimeError, '### please install the "bones" gem ###'
16
+ end
17
+ end
18
+
19
+ CLOBBER << '*~' << '*.*~'
20
+
21
+ PROJ.name = 'ffi-inliner'
22
+ PROJ.authors = 'Andrea Fazzi'
23
+ PROJ.email = 'andrea.fazzi@alcacoop.it'
24
+ PROJ.url = 'http://github.com/remogatto/ffi-inliner'
25
+ PROJ.version = Inliner::VERSION
26
+
27
+ PROJ.readme_file = 'README.rdoc'
28
+
29
+ PROJ.rubyforge.name = 'ffi'
30
+
31
+ PROJ.ann.paragraphs << 'FEATURES' << 'SYNOPSIS' << 'REQUIREMENTS' << 'DOWNLOAD/INSTALL' << 'CREDITS'
32
+ PROJ.ann.email[:from] = 'andrea.fazzi@alcacoop.it'
33
+ PROJ.ann.email[:to] << 'dev@ruby-ffi.kenai.com' << 'users@ruby-ffi.kenai.com'
34
+ PROJ.ann.email[:server] = 'smtp.gmail.com'
35
+
36
+ PROJ.spec.opts << '--color' << '-fs'
37
+
38
+ PROJ.ruby_opts = []
39
+
40
+ depend_on 'ffi', '0.3.5'
41
+
42
+ task :default => 'spec'
43
+
44
+ # EOF
data/examples/ex_1.rb ADDED
@@ -0,0 +1,23 @@
1
+ $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), '../lib')))
2
+ require 'ffi-inliner'
3
+
4
+ module MyLib
5
+ extend Inliner
6
+ inline 'void say_hello(char* name) { printf("Hello, %s\n", name); }'
7
+ end
8
+
9
+ MyLib.say_hello('boys')
10
+
11
+ class Foo
12
+ include MyLib
13
+ end
14
+
15
+ Foo.new.say_hello('foos')
16
+
17
+
18
+
19
+
20
+
21
+
22
+
23
+
@@ -0,0 +1,5 @@
1
+ require 'rubygems'
2
+ require 'digest/md5'
3
+ require 'rbconfig'
4
+ require 'ffi'
5
+ require 'ffi-inliner/inliner'
@@ -0,0 +1,257 @@
1
+ module Inliner
2
+
3
+ DEV_NULL = if Config::CONFIG['target_os'] =~ /mswin|mingw/
4
+ 'nul'
5
+ else
6
+ '/dev/null'
7
+ end
8
+
9
+ LIB_EXT = if Config::CONFIG['target_os'] =~ /darwin/
10
+ '.dylib'
11
+ elsif Config::CONFIG['target_os'] =~ /mswin|mingw/
12
+ '.dll'
13
+ else
14
+ '.so'
15
+ end
16
+
17
+ C_TO_FFI = {
18
+ 'void' => :void,
19
+ 'char' => :char,
20
+ 'unsigned char' => :uchar,
21
+ 'int' => :int,
22
+ 'unsigned int' => :uint,
23
+ 'long' => :long,
24
+ 'unsigned long' => :ulong,
25
+ 'float' => :float,
26
+ 'double' => :double,
27
+ }
28
+
29
+ @@__inliner_directory = File.expand_path(File.join('~/', '.ffi-inliner'))
30
+
31
+ class << self
32
+ def directory
33
+ @@__inliner_directory
34
+ end
35
+ end
36
+
37
+ class FilenameManager
38
+ def initialize(mod, code)
39
+ @mod = mod.name.gsub('::', '__')
40
+ @code = code
41
+ end
42
+ def cached?
43
+ exists?
44
+ end
45
+ def exists?
46
+ File.exists?(c_fn)
47
+ end
48
+ def base_fn
49
+ File.join(Inliner.directory, "#{@mod}_#{(Digest::MD5.new << @code).to_s[0, 4]}")
50
+ end
51
+ %w(c rb log).each do |ext|
52
+ define_method("#{ext}_fn") { "#{base_fn}.#{ext}" }
53
+ end
54
+ def so_fn
55
+ "#{base_fn}#{LIB_EXT}"
56
+ end
57
+ end
58
+
59
+ module Compilers
60
+ class Compiler
61
+ attr_reader :progname
62
+ def self.check_and_create(fm = nil)
63
+ compiler = new(fm)
64
+ unless compiler.exists?
65
+ raise "Can't find compiler #{compiler.class}"
66
+ else
67
+ compiler
68
+ end
69
+ end
70
+ def initialize(fm = nil)
71
+ @fm = fm
72
+ @progname = cmd.split.first
73
+ end
74
+ def compile
75
+ raise "Compile error! See #{@fm.log_fn}" unless system(cmd)
76
+ end
77
+ end
78
+
79
+ class GCC < Compiler
80
+ def exists?
81
+ IO.popen("#{@progname} 2>&1") { |f| f.gets } ? true : false
82
+ end
83
+ def ldshared
84
+ if Config::CONFIG['target_os'] =~ /darwin/
85
+ 'gcc -dynamic -bundle -fPIC'
86
+ else
87
+ 'gcc -shared -fPIC'
88
+ end
89
+ end
90
+ def cmd
91
+ "#{ldshared} -o \"#{@fm.so_fn}\" \"#{@fm.c_fn}\" 2>\"#{@fm.log_fn}\""
92
+ end
93
+ end
94
+
95
+ class TCC < Compiler
96
+ def exists?
97
+ IO.popen("#{@progname}") { |f| f.gets } ? true : false
98
+ end
99
+ def cmd
100
+ "tcc -shared -o \"#{@fm.so_fn}\" \"#{@fm.c_fn}\" 2>\"#{@fm.log_fn}\""
101
+ end
102
+ end
103
+ end
104
+
105
+ class Builder
106
+ attr_reader :code
107
+ def initialize(mod, code = "", options = {})
108
+ make_pointer_types
109
+ @mod = mod
110
+ @code = code
111
+ @sig = [parse_signature(@code)] unless @code.empty?
112
+ options = { :compiler => Compilers::GCC }.merge(options)
113
+ @compiler = options[:compiler]
114
+ end
115
+
116
+ def map(type_map)
117
+ @types.merge!(type_map)
118
+ end
119
+
120
+ def c(code)
121
+ (@sig ||= []) << parse_signature(code)
122
+ @code << code
123
+ end
124
+
125
+ def c_raw(code)
126
+ @code << code
127
+ end
128
+
129
+ def use_compiler(compiler)
130
+ @compiler = compiler
131
+ end
132
+
133
+ def build
134
+ @fm = FilenameManager.new(@mod, @code)
135
+ @compiler = @compiler.check_and_create(@fm)
136
+ unless @fm.cached?
137
+ write_files(@code, @sig)
138
+ @compiler.compile
139
+ @mod.instance_eval generate_ffi(@sig)
140
+ else
141
+ @mod.instance_eval(File.read(@fm.rb_fn))
142
+ end
143
+ end
144
+
145
+ private
146
+
147
+ def make_pointer_types
148
+ @types = C_TO_FFI.dup
149
+ C_TO_FFI.each_key do |k|
150
+ @types["#{k} *"] = :pointer
151
+ end
152
+ end
153
+
154
+ def cached?(name, code)
155
+ File.exists?(cname(name, code))
156
+ end
157
+
158
+ def to_ffi_type(c_type)
159
+ @types[c_type]
160
+ end
161
+
162
+ # Based on RubyInline code by Ryan Davis
163
+ # Copyright (c) 2001-2007 Ryan Davis, Zen Spider Software
164
+ def strip_comments(code)
165
+ # strip c-comments
166
+ src = code.gsub(%r%\s*/\*.*?\*/%m, '')
167
+ # strip cpp-comments
168
+ src = src.gsub(%r%^\s*//.*?\n%, '')
169
+ src = src.gsub(%r%[ \t]*//[^\n]*%, '')
170
+ src
171
+ end
172
+
173
+ # Based on RubyInline code by Ryan Davis
174
+ # Copyright (c) 2001-2007 Ryan Davis, Zen Spider Software
175
+ def parse_signature(code)
176
+ sig = strip_comments(code)
177
+ # strip preprocessor directives
178
+ sig.gsub!(/^\s*\#.*(\\\n.*)*/, '')
179
+ # strip {}s
180
+ sig.gsub!(/\{[^\}]*\}/, '{ }')
181
+ # clean and collapse whitespace
182
+ sig.gsub!(/\s+/, ' ')
183
+
184
+ # types = 'void|int|char|char\s\*|void\s\*'
185
+ types = @types.keys.map{|x| Regexp.escape(x)}.join('|')
186
+ sig = sig.gsub(/\s*\*\s*/, ' * ').strip
187
+
188
+ if /(#{types})\s*(\w+)\s*\(([^)]*)\)/ =~ sig then
189
+ return_type, function_name, arg_string = $1, $2, $3
190
+ args = []
191
+ arg_string.split(',').each do |arg|
192
+
193
+ # helps normalize into 'char * varname' form
194
+ arg = arg.gsub(/\s*\*\s*/, ' * ').strip
195
+
196
+ if /(((#{types})\s*\*?)+)\s+(\w+)\s*$/ =~ arg then
197
+ args.push($1)
198
+ elsif arg != "void" then
199
+ warn "WAR\NING: '#{arg}' not understood"
200
+ end
201
+ end
202
+
203
+ arity = args.size
204
+
205
+ return {
206
+ 'return' => return_type,
207
+ 'name' => function_name,
208
+ 'args' => args,
209
+ 'arity' => arity
210
+ }
211
+ end
212
+
213
+ raise SyntaxError, "Can't parse signature: #{sig}"
214
+
215
+ end
216
+
217
+ def generate_ffi(sig)
218
+ ffi_code = <<PREAMBLE
219
+ extend FFI::Library
220
+ ffi_lib '#{@fm.so_fn}'
221
+
222
+ PREAMBLE
223
+ sig.each do |s|
224
+ args = s['args'].map { |arg| ":#{to_ffi_type(arg)}" }.join(',')
225
+ ffi_code << "attach_function '#{s['name']}', [#{args}], :#{to_ffi_type(s['return'])}\n"
226
+ end
227
+ ffi_code
228
+ end
229
+ def write_c(code)
230
+ File.open(@fm.c_fn, 'w') { |f| f << code }
231
+ end
232
+
233
+ def write_ffi(sig)
234
+ File.open(@fm.rb_fn, 'w') { |f| f << generate_ffi(sig) }
235
+ end
236
+
237
+ def write_files(code, sig)
238
+ write_c(code)
239
+ write_ffi(sig)
240
+ end
241
+
242
+ end
243
+
244
+ def inline(code = "", options = {})
245
+ __inliner_make_directory
246
+ builder = Builder.new(self, code, options)
247
+ yield builder if block_given?
248
+ builder.build
249
+ end
250
+
251
+ private
252
+
253
+ def __inliner_make_directory
254
+ FileUtils.mkdir(Inliner.directory) unless (File.exists?(Inliner.directory) && File.directory?(Inliner.directory))
255
+ end
256
+
257
+ end