ghazel-ffi_gen 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
File without changes
@@ -0,0 +1,3 @@
1
+ interface NativeEnum {
2
+ public int toNativeInt();
3
+ }
@@ -0,0 +1,324 @@
1
+ class FFIGen
2
+ def generate_java
3
+ writer = Writer.new " ", " * ", "/**", " */"
4
+ writer.puts "// Generated by ffi_gen. Please do not change this file by hand.", "import java.util.*;", "import com.sun.jna.*;", "import java.lang.annotation.*;", "import java.lang.reflect.Method;", "", "public class #{@module_name} {"
5
+ writer.indent do
6
+ writer.puts "private static #{@module_name}Interface INSTANCE = #{@module_name}Interface.JnaInstanceCreator.createInstance();"
7
+ writer.puts "", *IO.readlines(File.join(File.dirname(__FILE__), "java_enum.java")).map(&:rstrip), ""
8
+
9
+ declarations.each do |declaration|
10
+ if declaration.respond_to? :write_static_java
11
+ declaration.write_static_java writer
12
+ else
13
+ declaration.write_java writer
14
+ end
15
+ end
16
+ writer.puts ""
17
+
18
+ writer.puts "private interface #{@module_name}Interface extends Library {"
19
+ writer.indent do
20
+ writer.puts "", *IO.readlines(File.join(File.dirname(__FILE__), "java_pre.java")).map(&:rstrip), ""
21
+ writer.puts "static class JnaInstanceCreator {"
22
+ writer.indent do
23
+ writer.puts "private static #{@module_name}Interface createInstance() {"
24
+ writer.indent do
25
+ writer.puts "DefaultTypeMapper typeMapper = new DefaultTypeMapper();", "typeMapper.addFromNativeConverter(NativeEnum.class, new EnumConverter());", "typeMapper.addToNativeConverter(NativeEnum.class, new EnumConverter());", ""
26
+ writer.puts "Map<String, Object> options = new HashMap<String, Object>();", "options.put(Library.OPTION_FUNCTION_MAPPER, new NativeNameAnnotationFunctionMapper());", "options.put(Library.OPTION_TYPE_MAPPER, typeMapper);", ""
27
+ writer.puts "return (#{@module_name}Interface) Native.loadLibrary(\"#{@ffi_lib}\", #{@module_name}Interface.class, options);"
28
+ end
29
+ writer.puts "}"
30
+ end
31
+ writer.puts "}", ""
32
+ declarations.each do |declaration|
33
+ if declaration.is_a? FunctionOrCallback
34
+ declaration.write_java writer
35
+ end
36
+ end
37
+ end
38
+ writer.puts "}"
39
+ end
40
+ writer.puts "}"
41
+ writer.output
42
+ end
43
+
44
+ class Name
45
+ JAVA_KEYWORDS = %w{abstract assert boolean break byte case catch char class const continue default do double else enum extends final finally float for goto if implements import instanceof int interface long native new package private protected public return short static strictfp super switch synchronized this throw throws transient try void volatile while}
46
+
47
+ def to_java_downcase
48
+ format :camelcase, :initial_downcase, JAVA_KEYWORDS
49
+ end
50
+
51
+ def to_java_classname
52
+ format :camelcase, JAVA_KEYWORDS
53
+ end
54
+
55
+ def to_java_constant
56
+ format :upcase, :underscores, JAVA_KEYWORDS
57
+ end
58
+ end
59
+
60
+ class Type
61
+ def java_description
62
+ java_name
63
+ end
64
+ end
65
+
66
+ class Enum
67
+ def write_java(writer)
68
+ return if @name.nil?
69
+ shorten_names
70
+
71
+ writer.comment do
72
+ writer.write_description @description
73
+ writer.puts "", "<em>This entry is only for documentation and no real method. The FFI::Enum can be accessed via #enum_type(:#{java_name}).</em>"
74
+ writer.puts "", "=== Options:"
75
+ @constants.each do |constant|
76
+ writer.puts "#{constant[:name].to_java_constant} ::"
77
+ writer.write_description constant[:comment], false, " ", " "
78
+ end
79
+ writer.puts "", "@method _enum_#{java_name}_", "@return [Symbol]", "@scope class"
80
+ end
81
+
82
+ writer.puts "public enum #{java_name} implements NativeEnum {"
83
+ writer.indent do
84
+ writer.write_array @constants, "," do |constant|
85
+ "#{constant[:name].to_java_constant}(#{constant[:value]})"
86
+ end
87
+ writer.puts ";"
88
+
89
+ writer.puts "", "private int nativeInt;", "", "private #{java_name}(int nativeInt) {", " this.nativeInt = nativeInt;", "}", "", "@Override", "public int toNativeInt() {", " return nativeInt;", "}"
90
+ end
91
+ writer.puts "}", ""
92
+ end
93
+
94
+ def java_name
95
+ @java_name ||= @name.to_java_classname
96
+ end
97
+
98
+ def java_jna_type
99
+ java_name
100
+ end
101
+
102
+ def java_description
103
+ "Symbol from _enum_#{java_name}_"
104
+ end
105
+ end
106
+
107
+ class StructOrUnion
108
+ def write_java(writer)
109
+ writer.comment do
110
+ writer.write_description @description
111
+ unless @fields.empty?
112
+ writer.puts "", "= Fields:"
113
+ @fields.each do |field|
114
+ writer.puts ":#{field[:name].to_java_downcase} ::"
115
+ writer.write_description field[:comment], false, " (#{field[:type].java_description}) ", " "
116
+ end
117
+ end
118
+ end
119
+
120
+ writer.puts "public static class #{java_name} extends #{@is_union ? 'Union' : (@fields.empty? ? 'PointerType' : 'Structure')} {"
121
+ writer.indent do
122
+ @fields.each do |field|
123
+ writer.puts "public #{field[:type].java_jna_type} #{field[:name].raw};"
124
+ end
125
+ writer.puts "// hidden structure" if @fields.empty?
126
+ end
127
+ writer.indent do
128
+ writer.puts "protected List<String> getFieldOrder() {"
129
+ writer.indent do
130
+ fs = @fields.map{|f| '"' + f[:name].raw + '"'}.join(", ")
131
+ writer.puts "return Arrays.asList(new String[] { #{fs} } );"
132
+ end
133
+ writer.puts "}"
134
+ end
135
+ writer.puts "}", ""
136
+
137
+ @written = true
138
+ end
139
+
140
+ def java_name
141
+ @java_name ||= @name.to_java_classname
142
+ end
143
+
144
+ def java_jna_type
145
+ @written ? java_name : "Pointer"
146
+ end
147
+
148
+ def java_description
149
+ @written ? java_name : "FFI::Pointer(*#{java_name})"
150
+ end
151
+ end
152
+
153
+ class FunctionOrCallback
154
+ def write_java(writer)
155
+ return if @is_callback # not yet supported
156
+
157
+ jna_signature = "#{@parameters.map{ |parameter| "#{parameter[:type].java_jna_type} #{parameter[:name].to_java_downcase}" }.join(', ')}"
158
+ if @is_callback
159
+ writer.puts "callback :#{java_name}, #{jna_signature}", ""
160
+ else
161
+ writer.puts "@NativeName(\"#{@name.raw}\")", "#{@return_type.java_jna_type} #{java_name}(#{jna_signature});", ""
162
+ end
163
+ end
164
+
165
+ def write_static_java(writer)
166
+ return if @is_callback # not yet supported
167
+
168
+ writer.comment do
169
+ writer.write_description @function_description
170
+ writer.puts "", "<em>This entry is only for documentation and no real method.</em>" if @is_callback
171
+ writer.puts "", "@method #{@is_callback ? "_callback_#{java_name}_" : java_name}(#{@parameters.map{ |parameter| parameter[:name].to_java_downcase }.join(', ')})"
172
+ @parameters.each do |parameter|
173
+ writer.write_description parameter[:description], false, "@param [#{parameter[:type].java_description}] #{parameter[:name].to_java_downcase} ", " "
174
+ end
175
+ writer.write_description @return_value_description, false, "@return [#{@return_type.java_description}] ", " "
176
+ writer.puts "@scope class"
177
+ end
178
+
179
+ args = @parameters.map{ |parameter| parameter[:name].to_java_downcase }.join(', ')
180
+ jna_signature = "#{@parameters.map{ |parameter| "#{parameter[:type].java_jna_type} #{parameter[:name].to_java_downcase}" }.join(', ')}"
181
+ writer.puts "static #{@return_type.java_jna_type} #{java_name}(#{jna_signature}) {"
182
+ writer.indent do
183
+ call = "INSTANCE.#{java_name}(#{args});"
184
+ if @return_type.respond_to? :no_return and @return_type.no_return
185
+ writer.puts call
186
+ elsif @return_type.is_a? StructOrUnion
187
+ writer.puts "return (#{@return_type.java_jna_type})#{call}"
188
+ else
189
+ writer.puts "return #{call}"
190
+ end
191
+ end
192
+ writer.puts "}", ""
193
+ end
194
+
195
+ def java_name
196
+ @java_name ||= @name.to_java_downcase
197
+ end
198
+
199
+ def java_jna_type
200
+ java_name
201
+ end
202
+
203
+ def java_description
204
+ "Proc(_callback_#{java_name}_)"
205
+ end
206
+ end
207
+
208
+ class Define
209
+ def write_java(writer)
210
+ parts = @value.map { |v|
211
+ if v.is_a? Array
212
+ case v[0]
213
+ when :method then v[1].to_java_downcase
214
+ when :constant then v[1].to_java_constant
215
+ else raise ArgumentError
216
+ end
217
+ else
218
+ v
219
+ end
220
+ }
221
+ if @parameters
222
+ # not implemented
223
+ else
224
+ writer.puts "public static int #{@name.to_java_constant} = #{parts.join};", ""
225
+ end
226
+ end
227
+ end
228
+
229
+ class PrimitiveType
230
+ def java_name
231
+ case @clang_type
232
+ when :void
233
+ "nil"
234
+ when :bool
235
+ "Boolean"
236
+ when :u_char, :u_short, :u_int, :u_long, :u_long_long, :char_s, :s_char, :short, :int, :long, :long_long
237
+ "Integer"
238
+ when :float, :double
239
+ "Float"
240
+ end
241
+ end
242
+
243
+ def java_jna_type
244
+ case @clang_type
245
+ when :void then "void"
246
+ when :bool then "boolean"
247
+ when :u_char then "byte"
248
+ when :u_short then "short"
249
+ when :u_int then "int"
250
+ when :u_long then "NativeLong"
251
+ when :u_long_long then "long"
252
+ when :char_s, :s_char then "byte"
253
+ when :short then "short"
254
+ when :int then "int"
255
+ when :long then "NativeLong"
256
+ when :long_long then "long"
257
+ when :float then "float"
258
+ when :double then "double"
259
+ end
260
+ end
261
+ end
262
+
263
+ class StringType
264
+ def java_name
265
+ "String"
266
+ end
267
+
268
+ def java_jna_type
269
+ "String"
270
+ end
271
+ end
272
+
273
+ class ByValueType
274
+ def java_name
275
+ @inner_type.java_name
276
+ end
277
+
278
+ def java_jna_type
279
+ @inner_type.java_jna_type
280
+ end
281
+ end
282
+
283
+ class PointerType
284
+ def java_name
285
+ @pointee_name.to_java_downcase
286
+ end
287
+
288
+ def java_jna_type
289
+ "Pointer"
290
+ end
291
+
292
+ def java_description
293
+ "FFI::Pointer(#{'*' * @depth}#{@pointee_name ? @pointee_name.to_java_classname : ''})"
294
+ end
295
+ end
296
+
297
+ class ArrayType
298
+ def java_name
299
+ "array"
300
+ end
301
+
302
+ def java_jna_type
303
+ if @constant_size
304
+ raise
305
+ else
306
+ "#{@element_type.java_jna_type}[]"
307
+ end
308
+ end
309
+
310
+ def java_description
311
+ "Array of #{@element_type.java_description}"
312
+ end
313
+ end
314
+
315
+ class UnknownType
316
+ def java_name
317
+ "unknown"
318
+ end
319
+
320
+ def java_jna_type
321
+ "byte"
322
+ end
323
+ end
324
+ end
@@ -0,0 +1,35 @@
1
+ @Retention(RetentionPolicy.RUNTIME)
2
+ @Target(ElementType.METHOD)
3
+ @interface NativeName {
4
+ String value();
5
+ }
6
+
7
+ class NativeNameAnnotationFunctionMapper implements FunctionMapper {
8
+ @Override
9
+ public String getFunctionName(NativeLibrary library, Method method) {
10
+ return method.getAnnotation(NativeName.class).value();
11
+ }
12
+ }
13
+
14
+ class EnumConverter implements TypeConverter {
15
+ public Class<?> nativeType() {
16
+ return Integer.class;
17
+ }
18
+
19
+ public Object fromNative(Object input, FromNativeContext context) {
20
+ int intValue = (Integer) input;
21
+
22
+ Class<?> targetClass = context.getTargetType();
23
+ for (Object constant : targetClass.getEnumConstants()) {
24
+ if (((NativeEnum) constant).toNativeInt() == intValue) {
25
+ return constant;
26
+ }
27
+ }
28
+
29
+ throw new IllegalArgumentException("No constant with integer value " + intValue + " in enum " + targetClass.getName() + ".");
30
+ }
31
+
32
+ public Object toNative(Object input, ToNativeContext context) {
33
+ return ((NativeEnum) input).toNativeInt();
34
+ }
35
+ }
@@ -0,0 +1,302 @@
1
+ class FFIGen
2
+ def generate_rb
3
+ writer = Writer.new " ", "# "
4
+ writer.puts "# Generated by ffi_gen. Please do not change this file by hand.", "", "require 'ffi'", "", "module #{@module_name}"
5
+ writer.indent do
6
+ writer.puts "extend FFI::Library"
7
+ writer.puts "ffi_lib_flags #{@ffi_lib_flags.map(&:inspect).join(', ')}" if @ffi_lib_flags
8
+ writer.puts "ffi_lib #{@ffi_lib.inspect}", "" if @ffi_lib
9
+ writer.puts "def self.attach_function(name, *_)", " begin; super; rescue FFI::NotFoundError => e", " (class << self; self; end).class_eval { define_method(name) { |*_| raise e } }", " end", "end", ""
10
+ declarations.each do |declaration|
11
+ declaration.write_ruby writer
12
+ end
13
+ end
14
+ writer.puts "end"
15
+ writer.output
16
+ end
17
+
18
+ class Name
19
+ RUBY_KEYWORDS = %w{alias and begin break case class def defined do else elsif end ensure false for if in module next nil not or redo rescue retry return self super then true undef unless until when while yield BEGIN END}
20
+
21
+ def to_ruby_downcase
22
+ format :downcase, :underscores, RUBY_KEYWORDS
23
+ end
24
+
25
+ def to_ruby_classname
26
+ format :camelcase, RUBY_KEYWORDS
27
+ end
28
+
29
+ def to_ruby_constant
30
+ format :upcase, :underscores, RUBY_KEYWORDS
31
+ end
32
+ end
33
+
34
+ class Type
35
+ def ruby_description
36
+ ruby_name
37
+ end
38
+ end
39
+
40
+ class Enum
41
+ def write_ruby(writer)
42
+ return if @name.nil?
43
+ shorten_names
44
+
45
+ @constants.each do |constant|
46
+ constant[:symbol] = ":#{constant[:name].to_ruby_downcase}"
47
+ end
48
+
49
+ writer.comment do
50
+ writer.write_description @description
51
+ writer.puts "", "<em>This entry is only for documentation and no real method. The FFI::Enum can be accessed via #enum_type(:#{ruby_name}).</em>"
52
+ writer.puts "", "=== Options:"
53
+ @constants.each do |constant|
54
+ writer.puts "#{constant[:symbol]} ::"
55
+ writer.write_description constant[:comment], false, " ", " "
56
+ end
57
+ writer.puts "", "@method _enum_#{ruby_name}_", "@return [Symbol]", "@scope class"
58
+ end
59
+
60
+ writer.puts "enum :#{ruby_name}, ["
61
+ writer.indent do
62
+ writer.write_array @constants, "," do |constant|
63
+ "#{constant[:symbol]}, #{constant[:value]}"
64
+ end
65
+ end
66
+ writer.puts "]", ""
67
+ end
68
+
69
+ def ruby_name
70
+ @ruby_name ||= @name.to_ruby_downcase
71
+ end
72
+
73
+ def ruby_ffi_type
74
+ ":#{ruby_name}"
75
+ end
76
+
77
+ def ruby_description
78
+ "Symbol from _enum_#{ruby_name}_"
79
+ end
80
+ end
81
+
82
+ class StructOrUnion
83
+ def write_ruby(writer)
84
+ writer.comment do
85
+ writer.write_description @description
86
+ unless @fields.empty?
87
+ writer.puts "", "= Fields:"
88
+ @fields.each do |field|
89
+ writer.puts ":#{field[:name].to_ruby_downcase} ::"
90
+ writer.write_description field[:comment], false, " (#{field[:type].ruby_description}) ", " "
91
+ end
92
+ end
93
+ end
94
+
95
+ @fields << { name: Name.new(["dummy"]), type: PrimitiveType.new(:char_s) } if @fields.empty?
96
+
97
+ unless @oo_functions.empty?
98
+ writer.puts "module #{ruby_name}Wrappers"
99
+ writer.indent do
100
+ @oo_functions.each_with_index do |(name, function), index|
101
+ parameter_names = function.parameters[1..-1].map { |parameter| parameter[:name].to_ruby_downcase }
102
+ writer.puts "" unless index == 0
103
+ writer.comment do
104
+ function.parameters[1..-1].each do |parameter|
105
+ writer.write_description parameter[:description], false, "@param [#{parameter[:type].ruby_description}] #{parameter[:name].to_ruby_downcase} ", " "
106
+ end
107
+ return_type = function.return_type.is_a?(StructOrUnion) ? function.return_type.ruby_name : function.return_type.ruby_description
108
+ writer.write_description function.return_value_description, false, "@return [#{return_type}] ", " "
109
+ end
110
+ writer.puts "def #{name.to_ruby_downcase}(#{parameter_names.join(', ')})"
111
+ writer.indent do
112
+ cast = function.return_type.is_a?(StructOrUnion) ? "#{function.return_type.ruby_name}.new " : ""
113
+ writer.puts "#{cast}#{@generator.module_name}.#{function.ruby_name}(#{(["self"] + parameter_names).join(', ')})"
114
+ end
115
+ writer.puts "end"
116
+ end
117
+ end
118
+ writer.puts "end", ""
119
+ end
120
+
121
+ writer.puts "class #{ruby_name} < #{@is_union ? 'FFI::Union' : 'FFI::Struct'}"
122
+ writer.indent do
123
+ writer.puts "include #{ruby_name}Wrappers" unless @oo_functions.empty?
124
+ writer.write_array @fields, ",", "layout ", " " do |field|
125
+ ":#{field[:name].to_ruby_downcase}, #{field[:type].ruby_ffi_type}"
126
+ end
127
+ end
128
+ writer.puts "end", ""
129
+
130
+ @written = true
131
+ end
132
+
133
+ def ruby_name
134
+ @ruby_name ||= @name.to_ruby_classname
135
+ end
136
+
137
+ def ruby_ffi_type
138
+ @written ? ruby_name : ":pointer"
139
+ end
140
+
141
+ def ruby_description
142
+ @written ? ruby_name : "FFI::Pointer(*#{ruby_name})"
143
+ end
144
+ end
145
+
146
+ class FunctionOrCallback
147
+ def write_ruby(writer)
148
+ writer.puts "@blocking = true" if @blocking
149
+ writer.comment do
150
+ writer.write_description @function_description
151
+ writer.puts "", "<em>This entry is only for documentation and no real method.</em>" if @is_callback
152
+ writer.puts "", "@method #{@is_callback ? "_callback_#{ruby_name}_" : ruby_name}(#{@parameters.map{ |parameter| parameter[:name].to_ruby_downcase }.join(', ')})"
153
+ @parameters.each do |parameter|
154
+ writer.write_description parameter[:description], false, "@param [#{parameter[:type].ruby_description}] #{parameter[:name].to_ruby_downcase} ", " "
155
+ end
156
+ writer.write_description @return_value_description, false, "@return [#{@return_type.ruby_description}] ", " "
157
+ writer.puts "@scope class"
158
+ end
159
+
160
+ ffi_signature = "[#{@parameters.map{ |parameter| parameter[:type].ruby_ffi_type }.join(', ')}], #{@return_type.ruby_ffi_type}"
161
+ if @is_callback
162
+ writer.puts "callback :#{ruby_name}, #{ffi_signature}", ""
163
+ else
164
+ writer.puts "attach_function :#{ruby_name}, :#{@name.raw}, #{ffi_signature}", ""
165
+ end
166
+ end
167
+
168
+ def ruby_name
169
+ @ruby_name ||= @name.to_ruby_downcase
170
+ end
171
+
172
+ def ruby_ffi_type
173
+ ":#{ruby_name}"
174
+ end
175
+
176
+ def ruby_description
177
+ "Proc(_callback_#{ruby_name}_)"
178
+ end
179
+ end
180
+
181
+ class Define
182
+ def write_ruby(writer)
183
+ parts = @value.map { |v|
184
+ if v.is_a? Array
185
+ case v[0]
186
+ when :method then v[1].to_ruby_downcase
187
+ when :constant then v[1].to_ruby_constant
188
+ else raise ArgumentError
189
+ end
190
+ else
191
+ v
192
+ end
193
+ }
194
+ if @parameters
195
+ writer.puts "def #{@name.to_ruby_downcase}(#{@parameters.join(", ")})"
196
+ writer.indent do
197
+ writer.puts parts.join
198
+ end
199
+ writer.puts "end", ""
200
+ else
201
+ writer.puts "#{@name.to_ruby_constant} = #{parts.join}", ""
202
+ end
203
+ end
204
+ end
205
+
206
+ class PrimitiveType
207
+ def ruby_name
208
+ case @clang_type
209
+ when :void
210
+ "nil"
211
+ when :bool
212
+ "Boolean"
213
+ when :u_char, :u_short, :u_int, :u_long, :u_long_long, :char_s, :s_char, :short, :int, :long, :long_long
214
+ "Integer"
215
+ when :float, :double
216
+ "Float"
217
+ end
218
+ end
219
+
220
+ def ruby_ffi_type
221
+ case @clang_type
222
+ when :void then ":void"
223
+ when :bool then ":bool"
224
+ when :u_char then ":uchar"
225
+ when :u_short then ":ushort"
226
+ when :u_int then ":uint"
227
+ when :u_long then ":ulong"
228
+ when :u_long_long then ":ulong_long"
229
+ when :char_s, :s_char then ":char"
230
+ when :short then ":short"
231
+ when :int then ":int"
232
+ when :long then ":long"
233
+ when :long_long then ":long_long"
234
+ when :float then ":float"
235
+ when :double then ":double"
236
+ end
237
+ end
238
+ end
239
+
240
+ class StringType
241
+ def ruby_name
242
+ "String"
243
+ end
244
+
245
+ def ruby_ffi_type
246
+ ":string"
247
+ end
248
+ end
249
+
250
+ class ByValueType
251
+ def ruby_name
252
+ @inner_type.ruby_name
253
+ end
254
+
255
+ def ruby_ffi_type
256
+ "#{@inner_type.ruby_ffi_type}.by_value"
257
+ end
258
+ end
259
+
260
+ class PointerType
261
+ def ruby_name
262
+ @pointee_name.to_ruby_downcase
263
+ end
264
+
265
+ def ruby_ffi_type
266
+ ":pointer"
267
+ end
268
+
269
+ def ruby_description
270
+ "FFI::Pointer(#{'*' * @depth}#{@pointee_name ? @pointee_name.to_ruby_classname : ''})"
271
+ end
272
+ end
273
+
274
+ class ArrayType
275
+ def ruby_name
276
+ "array"
277
+ end
278
+
279
+ def ruby_ffi_type
280
+ if @constant_size
281
+ "[#{@element_type.ruby_ffi_type}, #{@constant_size}]"
282
+ else
283
+ ":pointer"
284
+ end
285
+ end
286
+
287
+ def ruby_description
288
+ "Array<#{@element_type.ruby_description}>"
289
+ end
290
+ end
291
+
292
+ class UnknownType
293
+ def ruby_name
294
+ "unknown"
295
+ end
296
+
297
+ def ruby_ffi_type
298
+ ":char"
299
+ end
300
+ end
301
+
302
+ end