crystalruby 0.2.3 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +2 -0
- data/README.md +389 -193
- data/Rakefile +4 -3
- data/crystalruby.gemspec +2 -2
- data/exe/crystalruby +1 -0
- data/lib/crystalruby/adapter.rb +131 -65
- data/lib/crystalruby/arc_mutex.rb +47 -0
- data/lib/crystalruby/compilation.rb +32 -3
- data/lib/crystalruby/config.rb +41 -37
- data/lib/crystalruby/function.rb +211 -68
- data/lib/crystalruby/library.rb +153 -48
- data/lib/crystalruby/reactor.rb +40 -23
- data/lib/crystalruby/source_reader.rb +86 -0
- data/lib/crystalruby/template.rb +16 -5
- data/lib/crystalruby/templates/function.cr +11 -10
- data/lib/crystalruby/templates/index.cr +53 -66
- data/lib/crystalruby/templates/inline_chunk.cr +1 -1
- data/lib/crystalruby/templates/ruby_interface.cr +34 -0
- data/lib/crystalruby/templates/top_level_function.cr +62 -0
- data/lib/crystalruby/templates/top_level_ruby_interface.cr +33 -0
- data/lib/crystalruby/typebuilder.rb +11 -55
- data/lib/crystalruby/typemaps.rb +92 -67
- data/lib/crystalruby/types/concerns/allocator.rb +80 -0
- data/lib/crystalruby/types/fixed_width/named_tuple.cr +80 -0
- data/lib/crystalruby/types/fixed_width/named_tuple.rb +86 -0
- data/lib/crystalruby/types/fixed_width/proc.cr +45 -0
- data/lib/crystalruby/types/fixed_width/proc.rb +79 -0
- data/lib/crystalruby/types/fixed_width/tagged_union.cr +53 -0
- data/lib/crystalruby/types/fixed_width/tagged_union.rb +109 -0
- data/lib/crystalruby/types/fixed_width/tuple.cr +82 -0
- data/lib/crystalruby/types/fixed_width/tuple.rb +92 -0
- data/lib/crystalruby/types/fixed_width.cr +138 -0
- data/lib/crystalruby/types/fixed_width.rb +205 -0
- data/lib/crystalruby/types/primitive.cr +21 -0
- data/lib/crystalruby/types/primitive.rb +117 -0
- data/lib/crystalruby/types/primitive_types/bool.cr +34 -0
- data/lib/crystalruby/types/primitive_types/bool.rb +11 -0
- data/lib/crystalruby/types/primitive_types/nil.cr +35 -0
- data/lib/crystalruby/types/primitive_types/nil.rb +16 -0
- data/lib/crystalruby/types/primitive_types/numbers.cr +37 -0
- data/lib/crystalruby/types/primitive_types/numbers.rb +28 -0
- data/lib/crystalruby/types/primitive_types/symbol.cr +55 -0
- data/lib/crystalruby/types/primitive_types/symbol.rb +35 -0
- data/lib/crystalruby/types/primitive_types/time.cr +35 -0
- data/lib/crystalruby/types/primitive_types/time.rb +25 -0
- data/lib/crystalruby/types/type.cr +64 -0
- data/lib/crystalruby/types/type.rb +239 -30
- data/lib/crystalruby/types/variable_width/array.cr +74 -0
- data/lib/crystalruby/types/variable_width/array.rb +88 -0
- data/lib/crystalruby/types/variable_width/hash.cr +146 -0
- data/lib/crystalruby/types/variable_width/hash.rb +117 -0
- data/lib/crystalruby/types/variable_width/string.cr +36 -0
- data/lib/crystalruby/types/variable_width/string.rb +18 -0
- data/lib/crystalruby/types/variable_width.cr +23 -0
- data/lib/crystalruby/types/variable_width.rb +46 -0
- data/lib/crystalruby/types.rb +32 -13
- data/lib/crystalruby/version.rb +2 -2
- data/lib/crystalruby.rb +13 -6
- metadata +41 -19
- data/lib/crystalruby/types/array.rb +0 -15
- data/lib/crystalruby/types/bool.rb +0 -3
- data/lib/crystalruby/types/hash.rb +0 -17
- data/lib/crystalruby/types/named_tuple.rb +0 -28
- data/lib/crystalruby/types/nil.rb +0 -3
- data/lib/crystalruby/types/numbers.rb +0 -5
- data/lib/crystalruby/types/string.rb +0 -3
- data/lib/crystalruby/types/symbol.rb +0 -3
- data/lib/crystalruby/types/time.rb +0 -8
- data/lib/crystalruby/types/tuple.rb +0 -17
- data/lib/crystalruby/types/type_serializer/json.rb +0 -41
- data/lib/crystalruby/types/type_serializer.rb +0 -37
- data/lib/crystalruby/types/typedef.rb +0 -57
- data/lib/crystalruby/types/union_type.rb +0 -43
- data/lib/module.rb +0 -3
data/lib/crystalruby/function.rb
CHANGED
@@ -1,16 +1,22 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
1
3
|
module CrystalRuby
|
2
4
|
# This class represents a single Crystalized function.
|
3
5
|
# Each such function belongs a shared lib (See: CrystalRuby::Library)
|
4
6
|
# and is attached to a single owner (a class or a module).
|
5
7
|
class Function
|
8
|
+
extend Forwardable
|
6
9
|
include Typemaps
|
7
10
|
include Config
|
8
11
|
|
9
|
-
attr_accessor :
|
12
|
+
attr_accessor :original_method, :owner, :args, :returns, :function_body, :arity,
|
13
|
+
:lib, :async, :block, :attached, :ruby, :instance_method, :class_method
|
14
|
+
|
15
|
+
def_delegators :@original_method, :name
|
10
16
|
|
11
|
-
def initialize(method:, args:, returns:,
|
17
|
+
def initialize(method:, args:, returns:, lib:, function_body: nil, async: false, ruby: false, &block)
|
18
|
+
self.original_method = method
|
12
19
|
self.owner = method.owner
|
13
|
-
self.method_name = method.name
|
14
20
|
self.args = args
|
15
21
|
self.returns = returns
|
16
22
|
self.function_body = function_body
|
@@ -18,6 +24,16 @@ module CrystalRuby
|
|
18
24
|
self.async = async
|
19
25
|
self.block = block
|
20
26
|
self.attached = false
|
27
|
+
self.class_method = owner.singleton_class? && owner.attached_object.class == Class
|
28
|
+
self.instance_method = original_method.is_a?(UnboundMethod) && original_method.owner.ancestors.include?(CrystalRuby::Types::Type)
|
29
|
+
self.ruby = ruby
|
30
|
+
self.arity = args.keys.-([:__yield_to]).size
|
31
|
+
end
|
32
|
+
|
33
|
+
def crystal_supertype
|
34
|
+
return nil unless original_method.owner.ancestors.include?(CrystalRuby::Types::Type)
|
35
|
+
|
36
|
+
original_method.owner.crystal_supertype
|
21
37
|
end
|
22
38
|
|
23
39
|
# This is where we write/overwrite the class and instance methods
|
@@ -28,60 +44,69 @@ module CrystalRuby
|
|
28
44
|
# will result in a new digest and the FFI function will be recompiled and reattached.
|
29
45
|
def define_crystalized_methods!(lib)
|
30
46
|
func = self
|
31
|
-
[owner, owner.singleton_class]
|
32
|
-
|
33
|
-
receiver.
|
34
|
-
|
35
|
-
lib.build!
|
36
|
-
return send(func.method_name, *args)
|
37
|
-
end
|
47
|
+
receivers = instance_method ? [owner] : [owner, owner.singleton_class]
|
48
|
+
receivers.each do |receiver|
|
49
|
+
receiver.undef_method(name) if receiver.method_defined?(name)
|
50
|
+
receiver.define_method(name) do |*args, &blk|
|
38
51
|
unless func.attached?
|
39
|
-
should_reenter = func.
|
40
|
-
|
52
|
+
should_reenter = func.unwrapped?
|
53
|
+
lib.build! unless lib.compiled?
|
54
|
+
lib.attach! unless func.attached?
|
55
|
+
return send(func.name, *args, &blk) if should_reenter
|
41
56
|
end
|
42
57
|
# All crystalruby functions are executed on the reactor to ensure Crystal/Ruby interop code is executed
|
43
58
|
# from a single same thread. (Needed to make GC and Fiber scheduler happy)
|
44
59
|
# Type mapping (if required) is applied on arguments and on return values.
|
45
|
-
func.
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
60
|
+
if args.length != func.arity
|
61
|
+
raise ArgumentError,
|
62
|
+
"wrong number of arguments (given #{args.length}, expected #{func.arity})"
|
63
|
+
end
|
64
|
+
|
65
|
+
raise ArgumentError, "block given but function does not accept block" if blk && !func.takes_block?
|
66
|
+
raise ArgumentError, "no block given but function expects block" if !blk && func.takes_block?
|
67
|
+
|
68
|
+
args << blk if blk
|
69
|
+
func.map_args!(args)
|
70
|
+
args.unshift(memory) if func.instance_method
|
71
|
+
|
72
|
+
ret_val = Reactor.schedule_work!(
|
73
|
+
func.owner,
|
74
|
+
func.ffi_name,
|
75
|
+
*args,
|
76
|
+
func.ffi_ret_type,
|
77
|
+
async: func.async,
|
78
|
+
lib: lib
|
54
79
|
)
|
80
|
+
|
81
|
+
func.map_retval(ret_val)
|
55
82
|
end
|
56
83
|
end
|
57
84
|
end
|
58
85
|
|
59
|
-
|
60
|
-
|
61
|
-
# We also initialize the shared object (needed to start the GC) and
|
62
|
-
# start the reactor, unless we are in single-thread mode.
|
63
|
-
def attach_ffi_lib_functions!
|
64
|
-
should_reenter = unwrapped?
|
65
|
-
lib_file = lib.lib_file
|
66
|
-
lib.methods.each_value(&:attach_ffi_func!)
|
67
|
-
lib.singleton_class.class_eval do
|
68
|
-
extend FFI::Library
|
69
|
-
ffi_lib lib_file
|
70
|
-
%i[yield init].each do |method_name|
|
71
|
-
singleton_class.undef_method(method_name) if singleton_class.method_defined?(method_name)
|
72
|
-
undef_method(method_name) if method_defined?(method_name)
|
73
|
-
end
|
74
|
-
attach_function :init, %i[string pointer], :void
|
75
|
-
attach_function :yield, %i[], :int
|
76
|
-
end
|
86
|
+
def register_callback!
|
87
|
+
return unless ruby
|
77
88
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
89
|
+
@callback_func = FFI::Function.new(ffi_ret_type, ffi_types) do |*args|
|
90
|
+
receiver = instance_method ? owner.new(args.shift) : owner
|
91
|
+
ret_val = if takes_block?
|
92
|
+
block_arg = arg_type_map[:__yield_to][:crystalruby_type].new(args.pop)
|
93
|
+
receiver.send(name, *unmap_args(args)) do |*args|
|
94
|
+
args = args.map.with_index do |arg, i|
|
95
|
+
arg = block_arg.inner_types[i].new(arg) unless arg.is_a?(block_arg.inner_types[i])
|
96
|
+
arg.memory
|
97
|
+
end
|
98
|
+
return_val = block_arg.invoke(*args)
|
99
|
+
unless return_val.is_a?(block_arg.inner_types[-1])
|
100
|
+
return_val = block_arg.inner_types[-1].new(return_val)
|
101
|
+
end
|
102
|
+
block_arg.inner_types[-1].anonymous? ? return_val.value : return_val
|
103
|
+
end
|
104
|
+
else
|
105
|
+
receiver.send(name, *unmap_args(args))
|
106
|
+
end
|
107
|
+
unmap_retval(ret_val)
|
82
108
|
end
|
83
|
-
Reactor.schedule_work!(lib, :
|
84
|
-
should_reenter
|
109
|
+
Reactor.schedule_work!(lib, :"register_#{name.to_s.gsub("?", "q").gsub("=", "eq").gsub("!", "bang")}_callback", @callback_func, :void, blocking: true, async: false)
|
85
110
|
end
|
86
111
|
|
87
112
|
# Attaches the crystalized FFI functions to their related Ruby modules and classes.
|
@@ -111,7 +136,7 @@ module CrystalRuby
|
|
111
136
|
|
112
137
|
owner.attach_function ffi_name, argtypes, rettype, blocking: true
|
113
138
|
around_wrapper_block = block
|
114
|
-
method_name =
|
139
|
+
method_name = name
|
115
140
|
@attached = true
|
116
141
|
return unless around_wrapper_block
|
117
142
|
|
@@ -124,10 +149,6 @@ module CrystalRuby
|
|
124
149
|
end
|
125
150
|
@around_wrapper.undef_method(method_name) if @around_wrapper.method_defined?(method_name)
|
126
151
|
@around_wrapper.define_method(method_name, &around_wrapper_block)
|
127
|
-
rescue StandardError => e
|
128
|
-
CrystalRuby.log_error("Error attaching #{method_name} as #{ffi_name} to #{owner.name}")
|
129
|
-
CrystalRuby.log_error(e.message)
|
130
|
-
CrystalRuby.log_error(e.backtrace.join("\n"))
|
131
152
|
end
|
132
153
|
|
133
154
|
def unwrapped?
|
@@ -142,12 +163,22 @@ module CrystalRuby
|
|
142
163
|
@attached = false
|
143
164
|
end
|
144
165
|
|
166
|
+
def owner
|
167
|
+
class_method ? @owner.attached_object : @owner
|
168
|
+
end
|
169
|
+
|
170
|
+
def owner_name
|
171
|
+
owner.name
|
172
|
+
end
|
173
|
+
|
145
174
|
def ffi_name
|
146
175
|
lib_fn_name + (async && !config.single_thread_mode ? "_async" : "")
|
147
176
|
end
|
148
177
|
|
149
178
|
def lib_fn_name
|
150
|
-
@lib_fn_name ||= "#{
|
179
|
+
@lib_fn_name ||= "#{owner_name.downcase.gsub("::",
|
180
|
+
"_")}_#{name.to_s.gsub("?", "query").gsub("!", "bang").gsub("=",
|
181
|
+
"eq")}_#{Digest::MD5.hexdigest(function_body.to_s)}"
|
151
182
|
end
|
152
183
|
|
153
184
|
def arg_type_map
|
@@ -155,15 +186,29 @@ module CrystalRuby
|
|
155
186
|
end
|
156
187
|
|
157
188
|
def lib_fn_args
|
158
|
-
@lib_fn_args ||=
|
159
|
-
|
160
|
-
|
189
|
+
@lib_fn_args ||= begin
|
190
|
+
lib_fn_args = arg_type_map.map do |k, arg_type|
|
191
|
+
"_#{k} : #{arg_type[:lib_type]}"
|
192
|
+
end
|
193
|
+
lib_fn_args.unshift("_self : Pointer(::UInt8)") if instance_method
|
194
|
+
lib_fn_args.join(",") + (lib_fn_args.empty? ? "" : ", ")
|
195
|
+
end
|
161
196
|
end
|
162
197
|
|
163
|
-
def lib_fn_arg_names
|
164
|
-
@lib_fn_arg_names ||=
|
165
|
-
"_#{k}"
|
166
|
-
|
198
|
+
def lib_fn_arg_names(skip_blocks = false)
|
199
|
+
@lib_fn_arg_names ||= begin
|
200
|
+
names = arg_type_map.keys.reject { |k, _v| skip_blocks && is_block_arg?(k) }.map { |k| "_#{k}" }
|
201
|
+
names.unshift("self.memory") if instance_method
|
202
|
+
names.join(",") + (names.empty? ? "" : ", ")
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def lib_fn_types
|
207
|
+
@lib_fn_types ||= begin
|
208
|
+
lib_fn_types = arg_type_map.map { |_k, v| v[:lib_type] }
|
209
|
+
lib_fn_types.unshift("Pointer(::UInt8)") if instance_method
|
210
|
+
lib_fn_types.join(",") + (lib_fn_types.empty? ? "" : ", ")
|
211
|
+
end
|
167
212
|
end
|
168
213
|
|
169
214
|
def return_type_map
|
@@ -171,31 +216,69 @@ module CrystalRuby
|
|
171
216
|
end
|
172
217
|
|
173
218
|
def ffi_types
|
174
|
-
@ffi_types ||=
|
219
|
+
@ffi_types ||= begin
|
220
|
+
ffi_types = arg_type_map.map { |_k, arg_type| arg_type[:ffi_type] }
|
221
|
+
ffi_types.unshift(:pointer) if instance_method
|
222
|
+
ffi_types
|
223
|
+
end
|
175
224
|
end
|
176
225
|
|
177
226
|
def arg_maps
|
178
227
|
@arg_maps ||= arg_type_map.map { |_k, arg_type| arg_type[:arg_mapper] }
|
179
228
|
end
|
180
229
|
|
230
|
+
def arg_unmaps
|
231
|
+
@arg_unmaps ||= arg_type_map.reject { |k, _v| is_block_arg?(k) }.map { |_k, arg_type| arg_type[:retval_mapper] }
|
232
|
+
end
|
233
|
+
|
181
234
|
def ffi_ret_type
|
182
235
|
@ffi_ret_type ||= return_type_map[:ffi_ret_type]
|
183
236
|
end
|
184
237
|
|
238
|
+
def custom_types
|
239
|
+
@custom_types ||= begin
|
240
|
+
types = [*arg_type_map.values, return_type_map].map { |t| t[:crystalruby_type] }
|
241
|
+
types.unshift(owner) if instance_method
|
242
|
+
types
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
185
246
|
def register_custom_types!(lib)
|
186
|
-
|
187
|
-
|
188
|
-
|
247
|
+
custom_types.each do |crystalruby_type|
|
248
|
+
next unless crystalruby_type.is_a?(Class) && crystalruby_type < Types::Type
|
249
|
+
|
250
|
+
[*crystalruby_type.nested_types].uniq.each do |type|
|
251
|
+
lib.register_type!(type)
|
189
252
|
end
|
190
253
|
end
|
191
254
|
end
|
192
255
|
|
193
|
-
def map_args(args)
|
256
|
+
def map_args!(args)
|
194
257
|
return args unless arg_maps.any?
|
195
258
|
|
259
|
+
refs = nil
|
260
|
+
|
196
261
|
arg_maps.each_with_index do |argmap, index|
|
197
262
|
next unless argmap
|
198
263
|
|
264
|
+
mapped = argmap[args[index]]
|
265
|
+
case mapped
|
266
|
+
when CrystalRuby::Types::Type then
|
267
|
+
args[index] = mapped.memory
|
268
|
+
(refs ||= []) << mapped
|
269
|
+
else
|
270
|
+
args[index] = mapped
|
271
|
+
end
|
272
|
+
end
|
273
|
+
refs
|
274
|
+
end
|
275
|
+
|
276
|
+
def unmap_args(args)
|
277
|
+
return args unless args.any?
|
278
|
+
|
279
|
+
arg_unmaps.each_with_index do |argmap, index|
|
280
|
+
next unless argmap
|
281
|
+
|
199
282
|
args[index] = argmap[args[index]]
|
200
283
|
end
|
201
284
|
args
|
@@ -207,16 +290,76 @@ module CrystalRuby
|
|
207
290
|
return_type_map[:retval_mapper][retval]
|
208
291
|
end
|
209
292
|
|
293
|
+
def unmap_retval(retval)
|
294
|
+
return retval unless return_type_map[:arg_mapper]
|
295
|
+
|
296
|
+
retval = return_type_map[:arg_mapper][retval]
|
297
|
+
retval = retval.memory if retval.kind_of?(CrystalRuby::Types::Type)
|
298
|
+
retval
|
299
|
+
end
|
300
|
+
|
301
|
+
def takes_block?
|
302
|
+
is_block_arg?(:__yield_to)
|
303
|
+
end
|
304
|
+
|
305
|
+
def is_block_arg?(arg_name)
|
306
|
+
arg_name == :__yield_to && arg_type_map[arg_name] && arg_type_map[arg_name][:crystalruby_type].ancestors.select do |a|
|
307
|
+
a < Types::Type
|
308
|
+
end.map(&:typename).any?(:Proc)
|
309
|
+
end
|
310
|
+
|
311
|
+
def ruby_interface
|
312
|
+
template = owner == Object ? Template::TopLevelRubyInterface : Template::RubyInterface
|
313
|
+
@ruby_interface ||= template.render(
|
314
|
+
{
|
315
|
+
module_or_class: instance_method || class_method ? "class" : "module",
|
316
|
+
receiver: instance_method ? "#{owner_name}.new(_self)" : owner_name,
|
317
|
+
fn_scope: instance_method ? "" : "self.",
|
318
|
+
superclass: instance_method || class_method ? "< #{crystal_supertype}" : nil,
|
319
|
+
module_name: owner_name,
|
320
|
+
lib_fn_name: lib_fn_name,
|
321
|
+
fn_name: name,
|
322
|
+
callback_name: "#{name.to_s.gsub("?", "q").gsub("=", "eq").gsub("!", "bang")}_callback",
|
323
|
+
fn_body: function_body,
|
324
|
+
block_converter: takes_block? ? arg_type_map[:__yield_to][:crystalruby_type].block_converter : "",
|
325
|
+
callback_call: returns == :void ? "callback.call(thread_id)" : "callback.call(thread_id, converted)",
|
326
|
+
callback_type: return_type_map[:ffi_type] == :void ? "UInt32 -> Void" : " UInt32, #{return_type_map[:lib_type]} -> Void",
|
327
|
+
fn_args: arg_type_map
|
328
|
+
.map { |k, arg_type| "#{is_block_arg?(k) ? "&" : ""}#{k} : #{arg_type[:crystal_type]}" }.join(","),
|
329
|
+
fn_ret_type: return_type_map[:crystal_type],
|
330
|
+
lib_fn_args: lib_fn_args,
|
331
|
+
lib_fn_types: lib_fn_types,
|
332
|
+
lib_fn_arg_names: lib_fn_arg_names,
|
333
|
+
lib_fn_ret_type: return_type_map[:lib_type],
|
334
|
+
convert_lib_args: arg_type_map.map do |k, arg_type|
|
335
|
+
"_#{k} = #{arg_type[:convert_crystal_to_lib_type]["#{k}"]}"
|
336
|
+
end.join("\n "),
|
337
|
+
arg_names: args.keys.reject(&method(:is_block_arg?)).join(", "),
|
338
|
+
convert_return_type: return_type_map[:convert_lib_to_crystal_type]["return_value"],
|
339
|
+
error_value: return_type_map[:error_value]
|
340
|
+
}
|
341
|
+
)
|
342
|
+
end
|
343
|
+
|
210
344
|
def chunk
|
211
|
-
|
345
|
+
template = owner == Object ? Template::TopLevelFunction : Template::Function
|
346
|
+
@chunk ||= template.render(
|
212
347
|
{
|
213
|
-
|
348
|
+
module_or_class: instance_method || class_method ? "class" : "module",
|
349
|
+
receiver: instance_method ? "#{owner_name}.new(_self)" : owner_name,
|
350
|
+
fn_scope: instance_method ? "" : "self.",
|
351
|
+
superclass: instance_method || class_method ? "< #{crystal_supertype}" : nil,
|
352
|
+
module_name: owner_name,
|
214
353
|
lib_fn_name: lib_fn_name,
|
215
|
-
fn_name:
|
354
|
+
fn_name: name,
|
355
|
+
callback_name: "#{name.to_s.gsub("?", "q").gsub("=", "eq").gsub("!", "bang")}_callback",
|
216
356
|
fn_body: function_body,
|
357
|
+
block_converter: takes_block? ? arg_type_map[:__yield_to][:crystalruby_type].block_converter : "",
|
217
358
|
callback_call: returns == :void ? "callback.call(thread_id)" : "callback.call(thread_id, converted)",
|
218
359
|
callback_type: return_type_map[:ffi_type] == :void ? "UInt32 -> Void" : " UInt32, #{return_type_map[:lib_type]} -> Void",
|
219
|
-
fn_args: arg_type_map
|
360
|
+
fn_args: arg_type_map
|
361
|
+
.reject { |k, _v| is_block_arg?(k) }
|
362
|
+
.map { |k, arg_type| "#{k} : #{arg_type[:crystal_type]}" }.join(","),
|
220
363
|
fn_ret_type: return_type_map[:crystal_type],
|
221
364
|
lib_fn_args: lib_fn_args,
|
222
365
|
lib_fn_arg_names: lib_fn_arg_names,
|
@@ -224,7 +367,7 @@ module CrystalRuby
|
|
224
367
|
convert_lib_args: arg_type_map.map do |k, arg_type|
|
225
368
|
"#{k} = #{arg_type[:convert_lib_to_crystal_type]["_#{k}"]}"
|
226
369
|
end.join("\n "),
|
227
|
-
arg_names: args.keys.join(","),
|
370
|
+
arg_names: args.keys.reject(&method(:is_block_arg?)).join(", "),
|
228
371
|
convert_return_type: return_type_map[:convert_crystal_to_lib_type]["return_value"],
|
229
372
|
error_value: return_type_map[:error_value]
|
230
373
|
}
|
data/lib/crystalruby/library.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module CrystalRuby
|
2
4
|
class Library
|
3
5
|
include Typemaps
|
@@ -8,7 +10,8 @@ module CrystalRuby
|
|
8
10
|
CR_COMPILE_MUX = Mutex.new
|
9
11
|
CR_ATTACH_MUX = Mutex.new
|
10
12
|
|
11
|
-
attr_accessor :name, :methods, :
|
13
|
+
attr_accessor :name, :methods, :exposed_methods, :chunks, :root_dir,
|
14
|
+
:lib_dir, :src_dir, :codegen_dir, :shards
|
12
15
|
|
13
16
|
@libs_by_name = {}
|
14
17
|
|
@@ -29,7 +32,9 @@ module CrystalRuby
|
|
29
32
|
def initialize(name)
|
30
33
|
self.name = name
|
31
34
|
self.methods = {}
|
35
|
+
self.exposed_methods = {}
|
32
36
|
self.chunks = []
|
37
|
+
self.shards = {}
|
33
38
|
initialize_library!
|
34
39
|
end
|
35
40
|
|
@@ -44,7 +49,7 @@ module CrystalRuby
|
|
44
49
|
].each do |dir|
|
45
50
|
FileUtils.mkdir_p(dir)
|
46
51
|
end
|
47
|
-
IO.write
|
52
|
+
IO.write main_file, "require \"./#{config.crystal_codegen_dir}/index\"\n" unless File.exist?(main_file)
|
48
53
|
|
49
54
|
return if File.exist?(shard_file)
|
50
55
|
|
@@ -71,7 +76,24 @@ module CrystalRuby
|
|
71
76
|
).tap do |func|
|
72
77
|
func.define_crystalized_methods!(self)
|
73
78
|
func.register_custom_types!(self)
|
74
|
-
write_chunk(
|
79
|
+
write_chunk(func.owner_name, method.name, func.chunk)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def expose_method(method, args, returns)
|
85
|
+
CR_ATTACH_MUX.synchronize do
|
86
|
+
methods.each_value(&:unattach!)
|
87
|
+
method_key = "#{method.owner.name}/#{method.name}"
|
88
|
+
methods[method_key] = Function.new(
|
89
|
+
method: method,
|
90
|
+
args: args,
|
91
|
+
returns: returns,
|
92
|
+
ruby: true,
|
93
|
+
lib: self
|
94
|
+
).tap do |func|
|
95
|
+
func.register_custom_types!(self)
|
96
|
+
write_chunk(func.owner_name, method.name, func.ruby_interface)
|
75
97
|
end
|
76
98
|
end
|
77
99
|
end
|
@@ -89,7 +111,7 @@ module CrystalRuby
|
|
89
111
|
end
|
90
112
|
|
91
113
|
def crystalize_chunk(mod, chunk_name, body)
|
92
|
-
write_chunk(mod.name, chunk_name, body)
|
114
|
+
write_chunk(mod.respond_to?(:name) ? name : "main", chunk_name, body)
|
93
115
|
end
|
94
116
|
|
95
117
|
def instantiated?
|
@@ -102,7 +124,15 @@ module CrystalRuby
|
|
102
124
|
file_digest = Digest::MD5.hexdigest chunk_data
|
103
125
|
fname = chunk[:chunk_name]
|
104
126
|
index_contents.include?("#{chunk[:module_name]}/#{fname}_#{file_digest}.cr")
|
105
|
-
end
|
127
|
+
end && shards_installed?
|
128
|
+
end
|
129
|
+
|
130
|
+
def shards_installed?
|
131
|
+
shard_file_content = nil
|
132
|
+
shards.all? do |k, v|
|
133
|
+
dependencies ||= shard_file_contents["dependencies"]
|
134
|
+
dependencies[k] == v
|
135
|
+
end && CrystalRuby::Compilation.shard_check?(src_dir)
|
106
136
|
end
|
107
137
|
|
108
138
|
def index_contents
|
@@ -112,50 +142,126 @@ module CrystalRuby
|
|
112
142
|
end
|
113
143
|
|
114
144
|
def register_type!(type)
|
115
|
-
|
116
|
-
@types_cache[type.name] = type.type_defn
|
145
|
+
write_chunk("types", type.crystal_class_name, build_type(type.crystal_class_name, type.type_defn))
|
117
146
|
end
|
118
147
|
|
119
|
-
def
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
148
|
+
def build_type(type_name, expr)
|
149
|
+
parts = type_name.split("::")
|
150
|
+
typedef = parts[0...-1].each_with_index.reduce("") do |acc, (part, index)|
|
151
|
+
acc + "#{" " * index}module #{part}\n"
|
152
|
+
end
|
153
|
+
typedef += "#{" " * parts.size}#{expr}\n"
|
154
|
+
typedef + parts[0...-1].reverse.each_with_index.reduce("") do |acc, (_part, index)|
|
155
|
+
acc + "#{" " * (parts.size - 2 - index)}end\n"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def shard_file_contents
|
160
|
+
@shard_file_contents ||= YAML.safe_load(IO.read(shard_file))
|
161
|
+
rescue StandardError
|
162
|
+
@shard_file_contents ||= { "name" => "src", "version" => "0.1.0", "dependencies" => {} }
|
163
|
+
end
|
164
|
+
|
165
|
+
def shard_file_contents=(contents)
|
166
|
+
IO.write(shard_file, JSON.load(contents.to_json).to_yaml)
|
167
|
+
end
|
168
|
+
|
169
|
+
def shard_dependencies
|
170
|
+
shard_file_contents["dependencies"] ||= {}
|
171
|
+
end
|
172
|
+
|
173
|
+
def require_shard(name, opts)
|
174
|
+
@shards[name.to_s] = JSON.parse(opts.merge("_crystalruby_managed" => true).to_json)
|
175
|
+
rewrite_shards_file!
|
176
|
+
end
|
177
|
+
|
178
|
+
def rewrite_shards_file!
|
179
|
+
dependencies = shard_dependencies
|
180
|
+
|
181
|
+
dirty = @shards.any? do |k, v|
|
182
|
+
dependencies[k] != v
|
183
|
+
end || (@shards.empty? && dependencies.any?)
|
184
|
+
|
185
|
+
return unless dirty
|
186
|
+
|
187
|
+
if @shards.empty?
|
188
|
+
shard_file_contents.delete("dependencies")
|
189
|
+
else
|
190
|
+
shard_file_contents["dependencies"] = @shards
|
191
|
+
end
|
192
|
+
|
193
|
+
self.shard_file_contents = shard_file_contents
|
130
194
|
end
|
131
195
|
|
132
196
|
def requires
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
197
|
+
Template::Type.render({}) +
|
198
|
+
Template::Primitive.render({}) +
|
199
|
+
Template::FixedWidth.render({}) +
|
200
|
+
Template::VariableWidth.render({}) +
|
201
|
+
chunks.map do |chunk|
|
202
|
+
chunk_data = chunk[:body]
|
203
|
+
file_digest = Digest::MD5.hexdigest chunk_data
|
204
|
+
fname = chunk[:chunk_name]
|
205
|
+
"require \"./#{chunk[:module_name]}/#{fname}_#{file_digest}.cr\"\n"
|
206
|
+
end.join("\n") + shards.keys.map do |shard_name|
|
207
|
+
"require \"#{shard_name}\"\n"
|
208
|
+
end.join("\n")
|
139
209
|
end
|
140
210
|
|
141
211
|
def build!
|
142
212
|
CR_COMPILE_MUX.synchronize do
|
143
|
-
File.write codegen_dir / "index.cr", Template::Index.render(
|
144
|
-
type_modules: type_modules,
|
145
|
-
requires: requires
|
146
|
-
)
|
147
|
-
|
213
|
+
File.write codegen_dir / "index.cr", Template::Index.render(requires: requires)
|
148
214
|
unless compiled?
|
215
|
+
FileUtils.rm_f(lib_file)
|
216
|
+
|
217
|
+
if shard_dependencies.any? && shards.empty?
|
218
|
+
rewrite_shards_file!
|
219
|
+
end
|
220
|
+
|
221
|
+
CrystalRuby::Compilation.install_shards!(src_dir)
|
149
222
|
CrystalRuby::Compilation.compile!(
|
150
223
|
verbose: config.verbose,
|
151
224
|
debug: config.debug,
|
152
225
|
src: main_file,
|
153
|
-
lib: lib_file
|
226
|
+
lib: "#{lib_file}.part"
|
154
227
|
)
|
228
|
+
FileUtils.mv("#{lib_file}.part", lib_file)
|
229
|
+
attach!
|
155
230
|
end
|
156
231
|
end
|
157
232
|
end
|
158
233
|
|
234
|
+
def attach!
|
235
|
+
CR_ATTACH_MUX.synchronize do
|
236
|
+
lib_file = self.lib_file
|
237
|
+
lib_methods = methods
|
238
|
+
lib_methods.values.reject(&:ruby).each(&:attach_ffi_func!)
|
239
|
+
singleton_class.class_eval do
|
240
|
+
extend FFI::Library
|
241
|
+
ffi_lib lib_file
|
242
|
+
%i[yield init].each do |method_name|
|
243
|
+
singleton_class.undef_method(method_name) if singleton_class.method_defined?(method_name)
|
244
|
+
undef_method(method_name) if method_defined?(method_name)
|
245
|
+
end
|
246
|
+
attach_function :init, %i[string pointer pointer], :void
|
247
|
+
attach_function :yield, %i[], :int
|
248
|
+
lib_methods.each_value.select(&:ruby).each do |method|
|
249
|
+
attach_function :"register_#{method.name.to_s.gsub("?", "q").gsub("=", "eq").gsub("!", "bang")}_callback", %i[pointer], :void
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
if CrystalRuby.config.single_thread_mode
|
254
|
+
Reactor.init_single_thread_mode!
|
255
|
+
else
|
256
|
+
Reactor.start!
|
257
|
+
end
|
258
|
+
|
259
|
+
Reactor.schedule_work!(self, :init, name, Reactor::ERROR_CALLBACK, Types::Type::ARC_MUTEX.to_ptr, :void,
|
260
|
+
blocking: true, async: false)
|
261
|
+
methods.values.select(&:ruby).each(&:register_callback!)
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
159
265
|
def digest
|
160
266
|
Digest::MD5.hexdigest(File.read(codegen_dir / "index.cr")) if File.exist?(codegen_dir / "index.cr")
|
161
267
|
end
|
@@ -166,30 +272,29 @@ module CrystalRuby
|
|
166
272
|
|
167
273
|
def write_chunk(module_name, chunk_name, body)
|
168
274
|
chunks.delete_if { |chnk| chnk[:module_name] == module_name && chnk[:chunk_name] == chunk_name }
|
169
|
-
|
275
|
+
chunk = { module_name: module_name, chunk_name: chunk_name, body: body }
|
276
|
+
chunks << chunk
|
170
277
|
existing = Dir.glob(codegen_dir / "**/*.cr")
|
171
278
|
|
172
279
|
current_index_contents = index_contents
|
173
|
-
|
174
|
-
module_name, chunk_name, body = chunk.values_at(:module_name, :chunk_name, :body)
|
280
|
+
module_name, chunk_name, body = chunk.values_at(:module_name, :chunk_name, :body)
|
175
281
|
|
176
|
-
|
177
|
-
|
282
|
+
file_digest = Digest::MD5.hexdigest body
|
283
|
+
filename = (codegen_dir / module_name / "#{chunk_name}_#{file_digest}.cr").to_s
|
178
284
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
285
|
+
unless current_index_contents.include?("#{module_name}/#{chunk_name}_#{file_digest}.cr")
|
286
|
+
methods.each_value(&:unattach!)
|
287
|
+
@compiled = false
|
288
|
+
end
|
183
289
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
end
|
290
|
+
unless existing.delete(filename)
|
291
|
+
FileUtils.mkdir_p(codegen_dir / module_name)
|
292
|
+
File.write(filename, body)
|
293
|
+
end
|
294
|
+
existing.select do |f|
|
295
|
+
f =~ /#{config.crystal_codegen_dir / module_name / "#{chunk_name}_[a-f0-9]{32}\.cr"}/
|
296
|
+
end.each do |fl|
|
297
|
+
File.delete(fl) unless fl.eql?(filename)
|
193
298
|
end
|
194
299
|
end
|
195
300
|
end
|