ffi-inline 0.0.3 → 0.0.4

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