crystalruby 0.2.3 → 0.3.0
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.
- 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
|