ffi-inline 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/examples/ex_1.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), '../lib')))
2
- require 'ffi/inliner'
2
+ require 'ffi/inline'
3
3
 
4
4
  class Foo
5
- extend FFI::Inliner
5
+ extend FFI::Inline
6
6
 
7
7
  inline 'void say_hello (char* name) { printf("Hello, %s\n", name); }'
8
8
  end
data/ffi-inline.gemspec CHANGED
@@ -1,8 +1,8 @@
1
- Kernel.load 'lib/ffi/inliner/version.rb'
1
+ Kernel.load 'lib/ffi/inline/version.rb'
2
2
 
3
3
  Gem::Specification.new {|s|
4
4
  s.name = 'ffi-inline'
5
- s.version = FFI::Inliner::Version
5
+ s.version = FFI::Inline::VERSION
6
6
  s.authors = 'meh.'
7
7
  s.email = 'meh@paranoici.org'
8
8
  s.homepage = 'http://github.com/meh/ruby-ffi-inline'
data/lib/ffi/inline.rb ADDED
@@ -0,0 +1,18 @@
1
+ #--
2
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
3
+ # Version 2, December 2004
4
+ #
5
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
6
+ # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
7
+ #
8
+ # 0. You just DO WHAT THE FUCK YOU WANT TO.
9
+ #++
10
+
11
+ require 'digest/sha1'
12
+ require 'fileutils'
13
+ require 'rbconfig'
14
+ require 'shellwords'
15
+
16
+ require 'rubygems'
17
+ require 'ffi'
18
+ require 'ffi/inline/inline'
@@ -0,0 +1,108 @@
1
+ #--
2
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
3
+ # Version 2, December 2004
4
+ #
5
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
6
+ # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
7
+ #
8
+ # 0. You just DO WHAT THE FUCK YOU WANT TO.
9
+ #++
10
+
11
+ module FFI; module Inline
12
+
13
+ Signature = ::Struct.new(:return, :name, :arguments, :arity, :blocking)
14
+
15
+ class Builder
16
+ @builders = []
17
+
18
+ def self.[] (name)
19
+ return name if name.is_a?(Builder)
20
+
21
+ @builders.find {|builder|
22
+ builder.name.downcase == name.downcase ||
23
+ builder.aliases.any? { |ali| ali.downcase == name.downcase }
24
+ }
25
+ end
26
+
27
+ def self.define (name, *aliases, &block)
28
+ inherit_from = self
29
+
30
+ if name.is_a?(Builder)
31
+ name = name.class
32
+ end
33
+
34
+ if name.is_a?(Class)
35
+ inherit_from = name
36
+ name = aliases.shift
37
+ end
38
+
39
+ @builders << Class.new(inherit_from, &block).tap {|k|
40
+ k.instance_eval {
41
+ define_singleton_method :name do name end
42
+ define_singleton_method :aliases do aliases end
43
+ }
44
+ }
45
+ end
46
+
47
+ attr_reader :code, :compiler
48
+
49
+ def initialize (code = '')
50
+ @code = code
51
+ @evals = []
52
+ end
53
+
54
+ def use_compiler (compiler)
55
+ @compiler = Compiler[compiler]
56
+ end
57
+
58
+ def raw (code)
59
+ @code << code
60
+ end
61
+
62
+ def eval (&block)
63
+ @evals << block
64
+ end
65
+
66
+ def to_ffi_type (type)
67
+ raise 'the Builder has not been specialized'
68
+ end
69
+
70
+ def shared_object
71
+ raise 'the Builder has not been specialized'
72
+ end
73
+
74
+ def signatures
75
+ raise 'the Builder has not been specialized'
76
+ end
77
+
78
+ def symbols
79
+ signatures.map { |s| s.name.to_sym }
80
+ end
81
+
82
+ def build
83
+ builder = self
84
+ blocks = @evals
85
+
86
+ mod = Module.new
87
+ mod.instance_eval {
88
+ extend FFI::Library
89
+
90
+ ffi_lib builder.shared_object
91
+
92
+ blocks.each { |block| instance_eval &block }
93
+
94
+ builder.signatures.each {|s|
95
+ attach_function s.name, s.arguments.compact.map {|a|
96
+ builder.to_ffi_type(a, self)
97
+ }, builder.to_ffi_type(s.return, self), :blocking => s.blocking
98
+ }
99
+ }
100
+
101
+ mod
102
+ end
103
+ end
104
+
105
+ end; end
106
+
107
+ require 'ffi/inline/builders/c'
108
+ require 'ffi/inline/builders/cpp'
@@ -0,0 +1,155 @@
1
+ #--
2
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
3
+ # Version 2, December 2004
4
+ #
5
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
6
+ # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
7
+ #
8
+ # 0. You just DO WHAT THE FUCK YOU WANT TO.
9
+ #++
10
+
11
+ require 'ffi/inline/compilers/tcc'
12
+ require 'ffi/inline/compilers/gcc'
13
+
14
+ module FFI; module Inline
15
+
16
+ Builder.define :c do
17
+ ToFFI = {
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
+ attr_reader :code, :compiler, :libraries
30
+
31
+ def initialize (code = '', options = {})
32
+ super(code)
33
+
34
+ @types = ToFFI.dup
35
+ @libraries = options[:libraries] || []
36
+ @signatures = []
37
+
38
+ use_compiler options[:use_compiler] || options[:compiler] || :gcc
39
+
40
+ @signatures << parse_signature(code) if code && !code.empty?
41
+ end
42
+
43
+ def libraries (*libraries)
44
+ @libraries.concat(libraries)
45
+ end
46
+
47
+ def types (map = nil)
48
+ map ? @types.merge!(map) : @types
49
+ end
50
+
51
+ alias map types
52
+
53
+ def raw (code, no_line = false)
54
+ return super(code) if no_line
55
+
56
+ whole, path, line = caller.find { |line| line !~ /ffi-inline/ }.match(/^(.*?):(\d+):in/).to_a
57
+
58
+ super "\n#line #{line.to_i} #{path.inspect}\n" << code
59
+ end
60
+
61
+ alias c_raw raw
62
+
63
+ def include (path, options = {})
64
+ delimiter = (options[:quoted] || options[:local]) ? ['"', '"'] : ['<', '>']
65
+
66
+ raw "#include #{delimiter.first}#{path}#{delimiter.last}\n", true
67
+ end
68
+
69
+ def typedef (from, to)
70
+ raw "typedef #{from} #{to};"
71
+ end
72
+
73
+ def function (code, signature = nil)
74
+ parsed = parse_signature(code)
75
+
76
+ if signature
77
+ parsed[:arguments] = signature[:arguments] if signature[:arguments]
78
+ parsed[:return] = signature[:return] if signature[:return]
79
+ parsed[:blocking] = signature[:blocking] if signature[:blocking]
80
+ end
81
+
82
+ @signatures << parsed
83
+
84
+ raw code
85
+ end; alias c function
86
+
87
+ def struct (ffi_struct)
88
+ raw %{
89
+ typedef struct {#{
90
+ ffi_struct.layout.fields.map {|field|
91
+ "#{field} #{field.name};"
92
+ }.join("\n")
93
+ }} #{ffi_struct.class.name}
94
+ }, true
95
+ end
96
+
97
+ def to_ffi_type (type, mod = nil)
98
+ raise ArgumentError, 'type is nil' if type.nil?
99
+
100
+ if type.is_a?(Symbol) || type.is_a?(FFI::Type) || (type.is_a?(Class) && type.ancestors.include?(FFI::Struct))
101
+ type
102
+ elsif @types[type]
103
+ @types[type]
104
+ elsif type.to_s.include? ?*
105
+ :pointer
106
+ elsif ((mod || FFI).find_type(type.to_sym) rescue false)
107
+ type.to_sym
108
+ else
109
+ raise "type #{type} not supported"
110
+ end
111
+ end
112
+
113
+ def shared_object
114
+ @compiler.compile(@code, @libraries)
115
+ end
116
+
117
+ def signatures
118
+ @signatures
119
+ end
120
+
121
+ private
122
+ def strip_comments (code)
123
+ code.gsub(%r(\s*/\*.*?\*/)m, '').
124
+ gsub(%r(^\s*//.*?\n), '').
125
+ gsub(%r([ \t]*//[^\n]*), '')
126
+ end
127
+
128
+ def parse_signature (code)
129
+ sig = strip_comments(code)
130
+
131
+ sig.gsub!(/^\s*\#.*(\\\n.*)*/, '') # strip preprocessor directives
132
+ sig.gsub!(/\s*\{.*/m, '') # strip function body
133
+ sig.gsub!(/\s+/, ' ') # clean and collapse whitespace
134
+ sig.gsub!(/\s*\*\s*/, ' * ') # clean pointers
135
+ sig.gsub!(/\s*const\s*/, '') # remove const
136
+ sig.strip!
137
+
138
+ whole, return_type, function_name, arg_string = sig.match(/(.*?(?:\ \*)?)\s*(\w+)\s*\(([^)]*)\)/).to_a
139
+
140
+ raise SyntaxError, "cannot parse signature: #{sig}" unless whole
141
+
142
+ args = arg_string.split(',').map {|arg|
143
+ # helps normalize into 'char * varname' form
144
+ arg = arg.gsub(/\s*\*\s*/, ' * ').strip
145
+
146
+ whole, type = arg.gsub(/\s*\*\s*/, ' * ').strip.match(/(((.*?(?:\ \*)?)\s*\*?)+)\s+(\w+)\s*$/).to_a
147
+
148
+ type
149
+ }
150
+
151
+ Signature.new(return_type, function_name, args, args.empty? ? -1 : args.length)
152
+ end
153
+ end
154
+
155
+ end; end
@@ -0,0 +1,38 @@
1
+ #--
2
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
3
+ # Version 2, December 2004
4
+ #
5
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
6
+ # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
7
+ #
8
+ # 0. You just DO WHAT THE FUCK YOU WANT TO.
9
+ #++
10
+
11
+ require 'ffi/inline/builders/c'
12
+ require 'ffi/inline/compilers/gxx'
13
+
14
+ module FFI; module Inline
15
+
16
+ Builder.define Builder[:c], :cplusplus, :cxx, :cpp, 'c++' do
17
+ def initialize (code = '', options = {})
18
+ super(code, options) rescue nil
19
+
20
+ use_compiler options[:use_compiler] || options[:compiler] || :gxx
21
+ end
22
+
23
+ def function (code, signature = nil)
24
+ parsed = parse_signature(code)
25
+
26
+ if signature
27
+ parsed[:arguments] = signature[:arguments] if signature[:arguments]
28
+ parsed[:return] = signature[:return] if signature[:return]
29
+ parsed[:blocking] = signature[:blocking] if signature[:blocking]
30
+ end
31
+
32
+ @signatures << parsed
33
+
34
+ raw %{extern "C" {#{code} }\n}
35
+ end
36
+ end
37
+
38
+ end; end
@@ -0,0 +1,63 @@
1
+ #--
2
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
3
+ # Version 2, December 2004
4
+ #
5
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
6
+ # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
7
+ #
8
+ # 0. You just DO WHAT THE FUCK YOU WANT TO.
9
+ #++
10
+
11
+ module FFI; module Inline
12
+
13
+ class Compiler
14
+ Extension = case RbConfig::CONFIG['target_os']
15
+ when /darwin/ then 'dylib'
16
+ when /mswin|mingw/ then 'dll'
17
+ else 'so'
18
+ end
19
+
20
+ @compilers = []
21
+
22
+ def self.[] (name)
23
+ return name if name.is_a?(Compiler)
24
+
25
+ @compilers.find {|compiler|
26
+ compiler.name.downcase == name.downcase ||
27
+ compiler.aliases.any? { |ali| ali.downcase == name.downcase }
28
+ }
29
+ end
30
+
31
+ def self.define (name, *aliases, &block)
32
+ inherit_from = self
33
+
34
+ if name.is_a?(Compiler)
35
+ name = name.class
36
+ end
37
+
38
+ if name.is_a?(Class)
39
+ inherit_from = name
40
+ name = aliases.shift
41
+ end
42
+
43
+ @compilers << Class.new(inherit_from, &block).new(name, *aliases)
44
+ end
45
+
46
+ attr_reader :name, :aliases
47
+ attr_accessor :options
48
+
49
+ def initialize (name, *aliases)
50
+ @name = name
51
+ @aliases = aliases
52
+ end
53
+
54
+ def exists?
55
+ raise 'the Compiler has not been specialized'
56
+ end
57
+
58
+ def compile
59
+ raise 'the Compiler has not been specialized'
60
+ end
61
+ end
62
+
63
+ end; end
@@ -0,0 +1,67 @@
1
+ #--
2
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
3
+ # Version 2, December 2004
4
+ #
5
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
6
+ # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
7
+ #
8
+ # 0. You just DO WHAT THE FUCK YOU WANT TO.
9
+ #++
10
+
11
+ module FFI; module Inline
12
+
13
+ Compiler.define :gcc do
14
+ def exists?
15
+ `gcc -v 2>&1'`; $?.success?
16
+ end
17
+
18
+ def compile (code, libraries = [])
19
+ @code = code
20
+ @libraries = libraries
21
+
22
+ return output if File.exists?(output)
23
+
24
+ unless system(if RbConfig::CONFIG['target_os'] =~ /mswin|mingw/
25
+ "sh -c '#{ldshared} #{ENV['CFLAGS']} -o #{output.shellescape} #{input.shellescape} #{libs}' 2>#{log.shellescape}"
26
+ else
27
+ "#{ldshared} #{ENV['CFLAGS']} -o #{output.shellescape} #{input.shellescape} #{libs} 2>#{log.shellescape}"
28
+ end)
29
+ raise CompilationError.new(log)
30
+ end
31
+
32
+ output
33
+ end
34
+
35
+ private
36
+ def digest
37
+ Digest::SHA1.hexdigest(@code + @libraries.to_s + @options.to_s)
38
+ end
39
+
40
+ def input
41
+ File.join(Inline.directory, "#{digest}.c").tap {|path|
42
+ File.open(path, 'w') { |f| f.write(@code) } unless File.exists?(path)
43
+ }
44
+ end
45
+
46
+ def output
47
+ File.join(Inline.directory, "#{digest}.#{Compiler::Extension}")
48
+ end
49
+
50
+ def log
51
+ File.join(Inline.directory, "#{digest}.log")
52
+ end
53
+
54
+ def ldshared
55
+ if RbConfig::CONFIG['target_os'] =~ /darwin/
56
+ "gcc -dynamic -bundle -fPIC #{options} #{ENV['LDFLAGS']}"
57
+ else
58
+ "gcc -shared -fPIC #{options} #{ENV['LDFLAGS']}"
59
+ end
60
+ end
61
+
62
+ def libs
63
+ @libraries.map { |lib| "-l#{lib}".shellescape }.join(' ')
64
+ end
65
+ end
66
+
67
+ end; end
@@ -0,0 +1,33 @@
1
+ #--
2
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
3
+ # Version 2, December 2004
4
+ #
5
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
6
+ # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
7
+ #
8
+ # 0. You just DO WHAT THE FUCK YOU WANT TO.
9
+ #++
10
+
11
+ module FFI; module Inline
12
+
13
+ Compiler.define Compiler[:gcc], :gxx, 'g++' do
14
+ def exists?
15
+ `g++ -v 2>&1'`; $?.success?
16
+ end
17
+
18
+ def input
19
+ File.join(Inline.directory, "#{digest}.cpp").tap {|path|
20
+ File.open(path, 'w') { |f| f.write(@code) } unless File.exists?(path)
21
+ }
22
+ end
23
+
24
+ def ldshared
25
+ if RbConfig::CONFIG['target_os'] =~ /darwin/
26
+ "g++ -dynamic -bundle -fPIC #{options} #{ENV['LDFLAGS']}"
27
+ else
28
+ "g++ -shared -fPIC #{options} #{ENV['LDFLAGS']}"
29
+ end
30
+ end
31
+ end
32
+
33
+ end; end
@@ -0,0 +1,67 @@
1
+ #--
2
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
3
+ # Version 2, December 2004
4
+ #
5
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
6
+ # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
7
+ #
8
+ # 0. You just DO WHAT THE FUCK YOU WANT TO.
9
+ #++
10
+
11
+ module FFI; module Inline
12
+
13
+ Compiler.define :tcc do
14
+ def exists?
15
+ `tcc -v 2>&1'`; $?.success?
16
+ end
17
+
18
+ def compile (code, libraries = [])
19
+ @code = code
20
+ @libraries = libraries
21
+
22
+ return output if File.exists?(output)
23
+
24
+ unless system(if RbConfig::CONFIG['target_os'] =~ /mswin|mingw/
25
+ "sh -c '#{ldshared} #{ENV['CFLAGS']} #{libs} -o #{output.shellescape} #{input.shellescape}' 2>#{log.shellescape}"
26
+ else
27
+ "#{ldshared} #{ENV['CFLAGS']} #{libs} -o #{output.shellescape} #{input.shellescape} 2>#{log.shellescape}"
28
+ end)
29
+ raise CompilationError.new(log)
30
+ end
31
+
32
+ output
33
+ end
34
+
35
+ private
36
+ def digest
37
+ Digest::SHA1.hexdigest(@code + @libraries.to_s + @options.to_s)
38
+ end
39
+
40
+ def input
41
+ File.join(Inline.directory, "#{digest}.c").tap {|path|
42
+ File.open(path, 'w') { |f| f.write(@code) } unless File.exists?(path)
43
+ }
44
+ end
45
+
46
+ def output
47
+ File.join(Inline.directory, "#{digest}.#{Compiler::Extension}")
48
+ end
49
+
50
+ def log
51
+ File.join(Inline.directory, "#{digest}.log")
52
+ end
53
+
54
+ def ldshared
55
+ if RbConfig::CONFIG['target_os'] =~ /mswin|mingw/
56
+ "tcc -rdynamic -shared -fPIC #{options} #{ENV['LDFLAGS']}"
57
+ else
58
+ "tcc -shared #{options} #{ENV['LDFLAGS']}"
59
+ end
60
+ end
61
+
62
+ def libs
63
+ @libraries.map { |lib| "-l#{lib}".shellescape }.join(' ')
64
+ end
65
+ end
66
+
67
+ end; end
@@ -0,0 +1,21 @@
1
+ #--
2
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
3
+ # Version 2, December 2004
4
+ #
5
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
6
+ # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
7
+ #
8
+ # 0. You just DO WHAT THE FUCK YOU WANT TO.
9
+ #++
10
+
11
+ class CompilationError < RuntimeError
12
+ def initialize (path)
13
+ @path = path
14
+
15
+ super "compile error: see logs at #{@path}"
16
+ end
17
+
18
+ def log
19
+ File.read(@path)
20
+ end
21
+ end
@@ -0,0 +1,83 @@
1
+ #--
2
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
3
+ # Version 2, December 2004
4
+ #
5
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
6
+ # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
7
+ #
8
+ # 0. You just DO WHAT THE FUCK YOU WANT TO.
9
+ #++
10
+
11
+ require 'ffi/inline/error'
12
+
13
+ module FFI
14
+
15
+ module Inline
16
+ def self.directory
17
+ if ENV['FFI_INLINER_PATH'] && !ENV['FFI_INLINER_PATH'].empty?
18
+ @directory = ENV['FFI_INLINER_PATH']
19
+ else
20
+ require 'tmpdir'
21
+ @directory ||= File.expand_path(File.join(Dir.tmpdir, ".ffi-inline-#{Process.uid}"))
22
+ end
23
+
24
+ if File.exists?(@directory) && !File.directory?(@directory)
25
+ raise 'the FFI_INLINER_PATH exists and is not a directory'
26
+ end
27
+
28
+ if !File.exists?(@directory)
29
+ FileUtils.mkdir(@directory)
30
+ end
31
+
32
+ @directory
33
+ end
34
+
35
+ def inline (*args, &block)
36
+ if self.class == Class
37
+ instance_inline(*args, &block)
38
+ else
39
+ singleton_inline(*args, &block)
40
+ end
41
+ end
42
+
43
+ def singleton_inline (*args)
44
+ options = args.last.is_a?(Hash) ? args.pop : {}
45
+
46
+ language, code = if args.length == 2
47
+ args
48
+ else
49
+ block_given? ? [args.shift || :c, ''] : [:c, args.shift || '']
50
+ end
51
+
52
+ builder = Builder[language].new(code, options)
53
+ yield builder if block_given?
54
+ mod = builder.build
55
+
56
+ builder.symbols.each {|sym|
57
+ define_singleton_method sym, &mod.method(sym)
58
+ }
59
+ end
60
+
61
+ def instance_inline (*args)
62
+ options = args.last.is_a?(Hash) ? args.pop : {}
63
+
64
+ language, code = if args.length == 2
65
+ args
66
+ else
67
+ block_given? ? [args.shift || :c, ''] : [:c, args.shift || '']
68
+ end
69
+
70
+ builder = Builder[language].new(code, options)
71
+ yield builder if block_given?
72
+ mod = builder.build
73
+
74
+ builder.symbols.each {|sym|
75
+ define_method sym, &mod.method(sym)
76
+ }
77
+ end
78
+ end
79
+
80
+ end
81
+
82
+ require 'ffi/inline/compilers'
83
+ require 'ffi/inline/builders'
@@ -0,0 +1,17 @@
1
+ #--
2
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
3
+ # Version 2, December 2004
4
+ #
5
+ # DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
6
+ # TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
7
+ #
8
+ # 0. You just DO WHAT THE FUCK YOU WANT TO.
9
+ #++
10
+
11
+ module FFI
12
+
13
+ module Inline
14
+ VERSION = '0.0.4'
15
+ end
16
+
17
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ffi-inline
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-04-22 00:00:00.000000000 Z
12
+ date: 2012-04-29 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: ffi
@@ -68,17 +68,17 @@ files:
68
68
  - Rakefile
69
69
  - examples/ex_1.rb
70
70
  - ffi-inline.gemspec
71
- - lib/ffi/inliner.rb
72
- - lib/ffi/inliner/builders.rb
73
- - lib/ffi/inliner/builders/c.rb
74
- - lib/ffi/inliner/builders/cpp.rb
75
- - lib/ffi/inliner/compilers.rb
76
- - lib/ffi/inliner/compilers/gcc.rb
77
- - lib/ffi/inliner/compilers/gxx.rb
78
- - lib/ffi/inliner/compilers/tcc.rb
79
- - lib/ffi/inliner/error.rb
80
- - lib/ffi/inliner/inliner.rb
81
- - lib/ffi/inliner/version.rb
71
+ - lib/ffi/inline.rb
72
+ - lib/ffi/inline/builders.rb
73
+ - lib/ffi/inline/builders/c.rb
74
+ - lib/ffi/inline/builders/cpp.rb
75
+ - lib/ffi/inline/compilers.rb
76
+ - lib/ffi/inline/compilers/gcc.rb
77
+ - lib/ffi/inline/compilers/gxx.rb
78
+ - lib/ffi/inline/compilers/tcc.rb
79
+ - lib/ffi/inline/error.rb
80
+ - lib/ffi/inline/inline.rb
81
+ - lib/ffi/inline/version.rb
82
82
  - spec/inliner_spec.rb
83
83
  homepage: http://github.com/meh/ruby-ffi-inline
84
84
  licenses: []
data/lib/ffi/inliner.rb DELETED
@@ -1,8 +0,0 @@
1
- require 'digest/sha1'
2
- require 'fileutils'
3
- require 'rbconfig'
4
- require 'shellwords'
5
-
6
- require 'rubygems'
7
- require 'ffi'
8
- require 'ffi/inliner/inliner'
@@ -1,98 +0,0 @@
1
- module FFI; module Inliner
2
-
3
- Signature = ::Struct.new(:return, :name, :arguments, :arity, :blocking)
4
-
5
- class Builder
6
- @builders = []
7
-
8
- def self.[](name)
9
- return name if name.is_a?(Builder)
10
-
11
- @builders.find {|builder|
12
- builder.name.downcase == name.downcase ||
13
- builder.aliases.any? { |ali| ali.downcase == name.downcase }
14
- }
15
- end
16
-
17
- def self.define (name, *aliases, &block)
18
- inherit_from = self
19
-
20
- if name.is_a?(Builder)
21
- name = name.class
22
- end
23
-
24
- if name.is_a?(Class)
25
- inherit_from = name
26
- name = aliases.shift
27
- end
28
-
29
- @builders << Class.new(inherit_from, &block).tap {|k|
30
- k.instance_eval {
31
- define_singleton_method :name do name end
32
- define_singleton_method :aliases do aliases end
33
- }
34
- }
35
- end
36
-
37
- attr_reader :code, :compiler
38
-
39
- def initialize(code = "")
40
- @code = code
41
- @evals = []
42
- end
43
-
44
- def use_compiler(compiler)
45
- @compiler = Compiler[compiler]
46
- end
47
-
48
- def raw(code)
49
- @code << code
50
- end
51
-
52
- def eval(&block)
53
- @evals << block
54
- end
55
-
56
- def to_ffi_type(type)
57
- raise 'the Builder has not been specialized'
58
- end
59
-
60
- def shared_object
61
- raise 'the Builder has not been specialized'
62
- end
63
-
64
- def signatures
65
- raise 'the Builder has not been specialized'
66
- end
67
-
68
- def symbols
69
- signatures.map { |s| s.name.to_sym }
70
- end
71
-
72
- def build
73
- builder = self
74
- blocks = @evals
75
-
76
- mod = Module.new
77
- mod.instance_eval {
78
- extend FFI::Library
79
-
80
- ffi_lib builder.shared_object
81
-
82
- blocks.each { |block| instance_eval &block }
83
-
84
- builder.signatures.each {|s|
85
- attach_function s.name, s.arguments.compact.map {|a|
86
- builder.to_ffi_type(a, self)
87
- }, builder.to_ffi_type(s.return, self), :blocking => s.blocking
88
- }
89
- }
90
-
91
- mod
92
- end
93
- end
94
-
95
- end; end
96
-
97
- require 'ffi/inliner/builders/c'
98
- require 'ffi/inliner/builders/cpp'
@@ -1,147 +0,0 @@
1
- require 'ffi/inliner/compilers/tcc'
2
- require 'ffi/inliner/compilers/gcc'
3
-
4
- module FFI; module Inliner
5
-
6
- Builder.define :c do
7
- ToFFI = {
8
- 'void' => :void,
9
- 'char' => :char,
10
- 'unsigned char' => :uchar,
11
- 'int' => :int,
12
- 'unsigned int' => :uint,
13
- 'long' => :long,
14
- 'unsigned long' => :ulong,
15
- 'float' => :float,
16
- 'double' => :double,
17
- }
18
-
19
- attr_reader :code, :compiler, :libraries
20
-
21
- def initialize(code = nil, options = {})
22
- super(code)
23
-
24
- @types = ToFFI.dup
25
- @libraries = options[:libraries] || []
26
-
27
- @signatures = []
28
-
29
- use_compiler options[:use_compiler] || options[:compiler] || :gcc
30
-
31
- @signatures << parse_signature(code) if code && !code.empty?
32
- end
33
-
34
- def libraries(*libraries)
35
- @libraries.concat(libraries)
36
- end
37
-
38
- def types(map = nil)
39
- map ? @types.merge!(map) : @types
40
- end; alias map types
41
-
42
- def raw(code, no_line = false)
43
- return super(code) if no_line
44
-
45
- whole, path, line = caller.find { |line| line !~ /ffi-inliner/ }.match(/^(.*?):(\d+):in/).to_a
46
-
47
- super "\n#line #{line.to_i} #{path.inspect}\n" << code
48
- end; alias c_raw raw
49
-
50
- def include(path, options = {})
51
- delimiter = (options[:quoted] || options[:local]) ? ['"', '"'] : ['<', '>']
52
-
53
- raw "#include #{delimiter.first}#{path}#{delimiter.last}\n", true
54
- end
55
-
56
- def typedef (from, to)
57
- raw "typedef #{from} #{to};"
58
- end
59
-
60
- def function(code, signature = nil)
61
- parsed = parse_signature(code)
62
-
63
- if signature
64
- parsed[:arguments] = signature[:arguments] if signature[:arguments]
65
- parsed[:return] = signature[:return] if signature[:return]
66
- parsed[:blocking] = signature[:blocking] if signature[:blocking]
67
- end
68
-
69
- @signatures << parsed
70
-
71
- raw code
72
- end; alias c function
73
-
74
- def struct(ffi_struct)
75
- raw %{
76
- typedef struct {#{
77
- ffi_struct.layout.fields.map {|field|
78
- "#{field} #{field.name};"
79
- }.join("\n")
80
- }} #{ffi_struct.class.name}
81
- }, true
82
- end
83
-
84
- def to_ffi_type(type, mod = nil)
85
- raise ArgumentError, 'type is nil' if type.nil?
86
-
87
- if type.is_a?(Symbol) || type.is_a?(FFI::Type) || (type.is_a?(Class) && type.ancestors.include?(FFI::Struct))
88
- type
89
- elsif @types[type]
90
- @types[type]
91
- elsif type.to_s.include? ?*
92
- :pointer
93
- elsif ((mod || FFI).find_type(type.to_sym) rescue false)
94
- type.to_sym
95
- else
96
- raise "type #{type} not supported"
97
- end
98
- end
99
-
100
- def shared_object
101
- @compiler.compile(@code, @libraries)
102
- end
103
-
104
- def signatures
105
- @signatures
106
- end
107
-
108
- private
109
-
110
- # Based on RubyInline code by Ryan Davis
111
- # Copyright (c) 2001-2007 Ryan Davis, Zen Spider Software
112
- def strip_comments(code)
113
- code.gsub(%r(\s*/\*.*?\*/)m, '').
114
- gsub(%r(^\s*//.*?\n), '').
115
- gsub(%r([ \t]*//[^\n]*), '')
116
- end
117
-
118
- # Based on RubyInline code by Ryan Davis
119
- # Copyright (c) 2001-2007 Ryan Davis, Zen Spider Software
120
- def parse_signature(code)
121
- sig = strip_comments(code)
122
-
123
- sig.gsub!(/^\s*\#.*(\\\n.*)*/, '') # strip preprocessor directives
124
- sig.gsub!(/\s*\{.*/m, '') # strip function body
125
- sig.gsub!(/\s+/, ' ') # clean and collapse whitespace
126
- sig.gsub!(/\s*\*\s*/, ' * ') # clean pointers
127
- sig.gsub!(/\s*const\s*/, '') # remove const
128
- sig.strip!
129
-
130
- whole, return_type, function_name, arg_string = sig.match(/(.*?(?:\ \*)?)\s*(\w+)\s*\(([^)]*)\)/).to_a
131
-
132
- raise SyntaxError, "cannot parse signature: #{sig}" unless whole
133
-
134
- args = arg_string.split(',').map {|arg|
135
- # helps normalize into 'char * varname' form
136
- arg = arg.gsub(/\s*\*\s*/, ' * ').strip
137
-
138
- whole, type = arg.gsub(/\s*\*\s*/, ' * ').strip.match(/(((.*?(?:\ \*)?)\s*\*?)+)\s+(\w+)\s*$/).to_a
139
-
140
- type
141
- }
142
-
143
- Signature.new(return_type, function_name, args, args.empty? ? -1 : args.length)
144
- end
145
- end
146
-
147
- end; end
@@ -1,28 +0,0 @@
1
- require 'ffi/inliner/builders/c'
2
- require 'ffi/inliner/compilers/gxx'
3
-
4
- module FFI; module Inliner
5
-
6
- Builder.define Builder[:c], :cplusplus, :cxx, :cpp, 'c++' do
7
- def initialize(code = "", options = {})
8
- super(code, options) rescue nil
9
-
10
- use_compiler options[:use_compiler] || options[:compiler] || :gxx
11
- end
12
-
13
- def function(code, signature = nil)
14
- parsed = parse_signature(code)
15
-
16
- if signature
17
- parsed[:arguments] = signature[:arguments] if signature[:arguments]
18
- parsed[:return] = signature[:return] if signature[:return]
19
- parsed[:blocking] = signature[:blocking] if signature[:blocking]
20
- end
21
-
22
- @signatures << parsed
23
-
24
- raw 'extern "C" {' << code << '}' << "\n"
25
- end
26
- end
27
-
28
- end; end
@@ -1,53 +0,0 @@
1
- module FFI; module Inliner
2
-
3
- class Compiler
4
- Extension = case RbConfig::CONFIG['target_os']
5
- when /darwin/ then 'dylib'
6
- when /mswin|mingw/ then 'dll'
7
- else 'so'
8
- end
9
-
10
- @compilers = []
11
-
12
- def self.[] (name)
13
- return name if name.is_a?(Compiler)
14
-
15
- @compilers.find {|compiler|
16
- compiler.name.downcase == name.downcase ||
17
- compiler.aliases.any? { |ali| ali.downcase == name.downcase }
18
- }
19
- end
20
-
21
- def self.define (name, *aliases, &block)
22
- inherit_from = self
23
-
24
- if name.is_a?(Compiler)
25
- name = name.class
26
- end
27
-
28
- if name.is_a?(Class)
29
- inherit_from = name
30
- name = aliases.shift
31
- end
32
-
33
- @compilers << Class.new(inherit_from, &block).new(name, *aliases)
34
- end
35
-
36
- attr_reader :name, :aliases
37
- attr_accessor :options
38
-
39
- def initialize(name, *aliases)
40
- @name = name
41
- @aliases = aliases
42
- end
43
-
44
- def exists?
45
- raise 'the Compiler has not been specialized'
46
- end
47
-
48
- def compile
49
- raise 'the Compiler has not been specialized'
50
- end
51
- end
52
-
53
- end; end
@@ -1,57 +0,0 @@
1
- module FFI; module Inliner
2
-
3
- Compiler.define :gcc do
4
- def exists?
5
- `gcc -v 2>&1'`; $?.success?
6
- end
7
-
8
- def compile (code, libraries = [])
9
- @code = code
10
- @libraries = libraries
11
-
12
- return output if File.exists?(output)
13
-
14
- unless system(if RbConfig::CONFIG['target_os'] =~ /mswin|mingw/
15
- "sh -c '#{ldshared} #{ENV['CFLAGS']} -o #{output.shellescape} #{input.shellescape} #{libs}' 2>#{log.shellescape}"
16
- else
17
- "#{ldshared} #{ENV['CFLAGS']} -o #{output.shellescape} #{input.shellescape} #{libs} 2>#{log.shellescape}"
18
- end)
19
- raise CompilationError.new(log)
20
- end
21
-
22
- output
23
- end
24
-
25
- private
26
- def digest
27
- Digest::SHA1.hexdigest(@code + @libraries.to_s + @options.to_s)
28
- end
29
-
30
- def input
31
- File.join(Inliner.directory, "#{digest}.c").tap {|path|
32
- File.open(path, 'w') { |f| f.write(@code) } unless File.exists?(path)
33
- }
34
- end
35
-
36
- def output
37
- File.join(Inliner.directory, "#{digest}.#{Compiler::Extension}")
38
- end
39
-
40
- def log
41
- File.join(Inliner.directory, "#{digest}.log")
42
- end
43
-
44
- def ldshared
45
- if RbConfig::CONFIG['target_os'] =~ /darwin/
46
- "gcc -dynamic -bundle -fPIC #{options} #{ENV['LDFLAGS']}"
47
- else
48
- "gcc -shared -fPIC #{options} #{ENV['LDFLAGS']}"
49
- end
50
- end
51
-
52
- def libs
53
- @libraries.map { |lib| "-l#{lib}".shellescape }.join(' ')
54
- end
55
- end
56
-
57
- end; end
@@ -1,23 +0,0 @@
1
- module FFI; module Inliner
2
-
3
- Compiler.define Compiler[:gcc], :gxx, 'g++' do
4
- def exists?
5
- `g++ -v 2>&1'`; $?.success?
6
- end
7
-
8
- def input
9
- File.join(Inliner.directory, "#{digest}.cpp").tap {|path|
10
- File.open(path, 'w') { |f| f.write(@code) } unless File.exists?(path)
11
- }
12
- end
13
-
14
- def ldshared
15
- if RbConfig::CONFIG['target_os'] =~ /darwin/
16
- "g++ -dynamic -bundle -fPIC #{options} #{ENV['LDFLAGS']}"
17
- else
18
- "g++ -shared -fPIC #{options} #{ENV['LDFLAGS']}"
19
- end
20
- end
21
- end
22
-
23
- end; end
@@ -1,57 +0,0 @@
1
- module FFI; module Inliner
2
-
3
- Compiler.define :tcc do
4
- def exists?
5
- `tcc -v 2>&1'`; $?.success?
6
- end
7
-
8
- def compile (code, libraries = [])
9
- @code = code
10
- @libraries = libraries
11
-
12
- return output if File.exists?(output)
13
-
14
- unless system(if RbConfig::CONFIG['target_os'] =~ /mswin|mingw/
15
- "sh -c '#{ldshared} #{ENV['CFLAGS']} #{libs} -o #{output.shellescape} #{input.shellescape}' 2>#{log.shellescape}"
16
- else
17
- "#{ldshared} #{ENV['CFLAGS']} #{libs} -o #{output.shellescape} #{input.shellescape} 2>#{log.shellescape}"
18
- end)
19
- raise CompilationError.new(log)
20
- end
21
-
22
- output
23
- end
24
-
25
- private
26
- def digest
27
- Digest::SHA1.hexdigest(@code + @libraries.to_s + @options.to_s)
28
- end
29
-
30
- def input
31
- File.join(Inliner.directory, "#{digest}.c").tap {|path|
32
- File.open(path, 'w') { |f| f.write(@code) } unless File.exists?(path)
33
- }
34
- end
35
-
36
- def output
37
- File.join(Inliner.directory, "#{digest}.#{Compiler::Extension}")
38
- end
39
-
40
- def log
41
- File.join(Inliner.directory, "#{digest}.log")
42
- end
43
-
44
- def ldshared
45
- if RbConfig::CONFIG['target_os'] =~ /mswin|mingw/
46
- "tcc -rdynamic -shared -fPIC #{options} #{ENV['LDFLAGS']}"
47
- else
48
- "tcc -shared #{options} #{ENV['LDFLAGS']}"
49
- end
50
- end
51
-
52
- def libs
53
- @libraries.map { |lib| "-l#{lib}".shellescape }.join(' ')
54
- end
55
- end
56
-
57
- end; end
@@ -1,11 +0,0 @@
1
- class CompilationError < RuntimeError
2
- def initialize (path)
3
- @path = path
4
-
5
- super "compile error: see logs at #{@path}"
6
- end
7
-
8
- def log
9
- File.read(@path)
10
- end
11
- end
@@ -1,73 +0,0 @@
1
- require 'ffi/inliner/error'
2
-
3
- module FFI
4
-
5
- module Inliner
6
- def self.directory
7
- if ENV['FFI_INLINER_PATH'] && !ENV['FFI_INLINER_PATH'].empty?
8
- @directory = ENV['FFI_INLINER_PATH']
9
- else
10
- require 'tmpdir'
11
- @directory ||= File.expand_path(File.join(Dir.tmpdir, ".ffi-inliner-#{Process.uid}"))
12
- end
13
-
14
- if File.exists?(@directory) && !File.directory?(@directory)
15
- raise 'the FFI_INLINER_PATH exists and is not a directory'
16
- end
17
-
18
- if !File.exists?(@directory)
19
- FileUtils.mkdir(@directory)
20
- end
21
-
22
- @directory
23
- end
24
-
25
- def inline(*args, &block)
26
- if self.class == Class
27
- instance_inline(*args, &block)
28
- else
29
- singleton_inline(*args, &block)
30
- end
31
- end
32
-
33
- def singleton_inline(*args)
34
- options = args.last.is_a?(Hash) ? args.pop : {}
35
-
36
- language, code = if args.length == 2
37
- args
38
- else
39
- block_given? ? [args.shift || :c, ''] : [:c, args.shift || '']
40
- end
41
-
42
- builder = Builder[language].new(code, options)
43
- yield builder if block_given?
44
- mod = builder.build
45
-
46
- builder.symbols.each {|sym|
47
- define_singleton_method sym, &mod.method(sym)
48
- }
49
- end
50
-
51
- def instance_inline(*args)
52
- options = args.last.is_a?(Hash) ? args.pop : {}
53
-
54
- language, code = if args.length == 2
55
- args
56
- else
57
- block_given? ? [args.shift || :c, ''] : [:c, args.shift || '']
58
- end
59
-
60
- builder = Builder[language].new(code, options)
61
- yield builder if block_given?
62
- mod = builder.build
63
-
64
- builder.symbols.each {|sym|
65
- define_method sym, &mod.method(sym)
66
- }
67
- end
68
- end
69
-
70
- end
71
-
72
- require 'ffi/inliner/compilers'
73
- require 'ffi/inliner/builders'
@@ -1,7 +0,0 @@
1
- module FFI
2
-
3
- module Inliner
4
- Version = '0.0.3'
5
- end
6
-
7
- end