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/reactor.rb
CHANGED
@@ -1,13 +1,17 @@
|
|
1
1
|
module CrystalRuby
|
2
|
+
require 'json'
|
2
3
|
# The Reactor represents a singleton Thread responsible for running all Ruby/crystal interop code.
|
3
4
|
# Crystal's Fiber scheduler and GC assume all code is run on a single thread.
|
4
|
-
# This class is responsible for multiplexing Ruby and Crystal code
|
5
|
-
# Functions annotated with async: true, are executed using callbacks to allow these to be interleaved
|
6
|
-
|
5
|
+
# This class is responsible for multiplexing Ruby and Crystal code onto a single thread.
|
6
|
+
# Functions annotated with async: true, are executed using callbacks to allow these to be interleaved
|
7
|
+
# without blocking multiple Ruby threads.
|
7
8
|
module Reactor
|
8
9
|
module_function
|
9
10
|
|
10
11
|
class SingleThreadViolation < StandardError; end
|
12
|
+
class StopReactor < StandardError; end
|
13
|
+
|
14
|
+
@single_thread_mode = false
|
11
15
|
|
12
16
|
REACTOR_QUEUE = Queue.new
|
13
17
|
|
@@ -39,13 +43,15 @@ module CrystalRuby
|
|
39
43
|
end
|
40
44
|
end
|
41
45
|
|
42
|
-
ERROR_CALLBACK = FFI::Function.new(:void, %i[string string int]) do |error_type, message, tid|
|
46
|
+
ERROR_CALLBACK = FFI::Function.new(:void, %i[string string string int]) do |error_type, message, backtrace, tid|
|
43
47
|
error_type = error_type.to_sym
|
44
48
|
is_exception_type = Object.const_defined?(error_type) && Object.const_get(error_type).ancestors.include?(Exception)
|
45
49
|
error_type = is_exception_type ? Object.const_get(error_type) : RuntimeError
|
46
|
-
|
50
|
+
error = error_type.new(message)
|
51
|
+
error.set_backtrace(JSON.parse(backtrace))
|
52
|
+
raise error unless THREAD_MAP.key?(tid)
|
47
53
|
|
48
|
-
THREAD_MAP[tid][:error] =
|
54
|
+
THREAD_MAP[tid][:error] = error
|
49
55
|
THREAD_MAP[tid][:result] = nil
|
50
56
|
THREAD_MAP[tid][:cond].signal
|
51
57
|
end
|
@@ -57,18 +63,35 @@ module CrystalRuby
|
|
57
63
|
def await_result!
|
58
64
|
mux, cond = thread_conditions.values_at(:mux, :cond)
|
59
65
|
cond.wait(mux)
|
60
|
-
|
66
|
+
if error = thread_conditions[:error]
|
67
|
+
combined_backtrace = error.backtrace[0..(error.backtrace.index{|m| m.include?('call_blocking_function')} || 2) - 3] + caller[5..-1]
|
68
|
+
error.set_backtrace(combined_backtrace)
|
69
|
+
raise error
|
70
|
+
end
|
71
|
+
|
72
|
+
thread_conditions[:result]
|
73
|
+
end
|
74
|
+
|
75
|
+
def halt_loop!
|
76
|
+
raise StopReactor
|
77
|
+
end
|
61
78
|
|
62
|
-
|
79
|
+
def stop!
|
80
|
+
if @main_loop
|
81
|
+
schedule_work!(self, :halt_loop!, :void, blocking: true, async: false)
|
82
|
+
@main_loop.join
|
83
|
+
@main_loop = nil
|
84
|
+
CrystalRuby.log_info "Reactor loop stopped"
|
85
|
+
end
|
63
86
|
end
|
64
87
|
|
65
88
|
def start!
|
66
89
|
@main_loop ||= Thread.new do
|
90
|
+
@main_thread_id = Thread.current.object_id
|
67
91
|
CrystalRuby.log_debug("Starting reactor")
|
68
92
|
CrystalRuby.log_debug("CrystalRuby initialized")
|
69
|
-
|
70
|
-
|
71
|
-
end
|
93
|
+
REACTOR_QUEUE.pop[] while true
|
94
|
+
rescue StopReactor => e
|
72
95
|
rescue StandardError => e
|
73
96
|
CrystalRuby.log_error "Error: #{e}"
|
74
97
|
CrystalRuby.log_error e.backtrace
|
@@ -80,26 +103,17 @@ module CrystalRuby
|
|
80
103
|
end
|
81
104
|
|
82
105
|
def yield!(lib: nil, time: 0.0)
|
83
|
-
schedule_work!(lib, :yield,
|
106
|
+
schedule_work!(lib, :yield, :int, async: false, blocking: false, lib: lib) if running? && lib
|
84
107
|
nil
|
85
108
|
end
|
86
109
|
|
87
|
-
def current_thread_id=(val)
|
88
|
-
@current_thread_id = val
|
89
|
-
end
|
90
|
-
|
91
|
-
def current_thread_id
|
92
|
-
@current_thread_id
|
93
|
-
end
|
94
|
-
|
95
110
|
def schedule_work!(receiver, op_name, *args, return_type, blocking: true, async: true, lib: nil)
|
96
|
-
if @single_thread_mode
|
111
|
+
if @single_thread_mode || (Thread.current.object_id == @main_thread_id && op_name != :yield)
|
97
112
|
unless Thread.current.object_id == @main_thread_id
|
98
113
|
raise SingleThreadViolation,
|
99
114
|
"Single thread mode is enabled, cannot run in multi-threaded mode. " \
|
100
115
|
"Reactor was started from: #{@main_thread_id}, then called from #{Thread.current.object_id}"
|
101
116
|
end
|
102
|
-
|
103
117
|
return receiver.send(op_name, *args)
|
104
118
|
end
|
105
119
|
|
@@ -118,14 +132,17 @@ module CrystalRuby
|
|
118
132
|
when blocking
|
119
133
|
lambda {
|
120
134
|
tvars[:error] = nil
|
121
|
-
|
135
|
+
should_halt = false
|
122
136
|
begin
|
123
137
|
result = receiver.send(op_name, *args)
|
138
|
+
rescue StopReactor => e
|
139
|
+
should_halt = true
|
124
140
|
rescue StandardError => e
|
125
141
|
tvars[:error] = e
|
126
142
|
end
|
127
143
|
tvars[:result] = result unless tvars[:error]
|
128
144
|
tvars[:cond].signal
|
145
|
+
raise StopReactor if should_halt
|
129
146
|
}
|
130
147
|
else
|
131
148
|
lambda {
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module CrystalRuby
|
2
|
+
module SourceReader
|
3
|
+
module_function
|
4
|
+
|
5
|
+
# Reads code line by line from a given source location and returns the first valid Ruby expression found
|
6
|
+
def extract_expr_from_source_location(source_location)
|
7
|
+
lines = source_location.then{|f,l| IO.readlines(f)[l-1..]}
|
8
|
+
lines[0] = lines[0][/CRType.*/] if lines[0] =~ /<\s+CRType/ || lines[0] =~ /= CRType/
|
9
|
+
lines.each.with_object("") do |line, expr_source|
|
10
|
+
break expr_source if (SyntaxTree.parse(expr_source << line) rescue nil)
|
11
|
+
end
|
12
|
+
rescue
|
13
|
+
raise "Failed to extract expression from source location: #{source_location}. Ensure the file exists and the line number is correct. Extraction from a REPL is not supported"
|
14
|
+
end
|
15
|
+
|
16
|
+
# Given a proc, extracts the source code of the block passed to it
|
17
|
+
# If raw is true, the source is expected to be Raw Crystal code captured
|
18
|
+
# in a string or Heredoc literal. Otherwise the Ruby code (assumed to be valid Crystal)
|
19
|
+
# is extracted.
|
20
|
+
def extract_source_from_proc(block, raw: false)
|
21
|
+
block_source = extract_expr_from_source_location(block.source_location)
|
22
|
+
body_node = SyntaxTree.search(block_source, :Statements).to_a[1]
|
23
|
+
return raw ?
|
24
|
+
SyntaxTree.search(node_to_s(body_node), :TStringContent).map(&method(:node_to_s)).join :
|
25
|
+
node_to_s(body_node)
|
26
|
+
end
|
27
|
+
|
28
|
+
@visitor = SyntaxTree::MutationVisitor.new
|
29
|
+
|
30
|
+
# Specify that it should mutate If nodes with assignments in their predicates
|
31
|
+
@visitor.mutate("ReturnNode") do |node|
|
32
|
+
node
|
33
|
+
end
|
34
|
+
|
35
|
+
# Simple helper function to turn a SyntaxTree node back into a Ruby string
|
36
|
+
# The default formatter will turn a break/return of [1,2,3] into a brackless 1,2,3
|
37
|
+
# Can't have that in Crystal as it turns it into a Tuple
|
38
|
+
def node_to_s(node)
|
39
|
+
@_syxt_transform ||= SyntaxTree::FlowControlFormatter.prepend(Module.new do
|
40
|
+
def format(quer)
|
41
|
+
first_arg = self.node.arguments.child_nodes
|
42
|
+
if first_arg[0].kind_of?(SyntaxTree::ArrayLiteral)
|
43
|
+
return format_arguments(quer, " [", "]")
|
44
|
+
end
|
45
|
+
super(quer)
|
46
|
+
end
|
47
|
+
end)
|
48
|
+
SyntaxTree::Formatter.format("", node.accept(@visitor))
|
49
|
+
end
|
50
|
+
|
51
|
+
# Given a method, extracts the source code of the block passed to it
|
52
|
+
# and also converts any keyword arguments given in the method definition as a
|
53
|
+
# named map of keyword names to Crystal types.
|
54
|
+
# Also supports basic ffi symbol types.
|
55
|
+
#
|
56
|
+
# E.g.
|
57
|
+
#
|
58
|
+
# def add a: Int32 | Int64, b: :int
|
59
|
+
#
|
60
|
+
# The above will be converted to:
|
61
|
+
# {
|
62
|
+
# a: Int32 | Int64, # Int32 | Int64 is a Crystal type
|
63
|
+
# b: :int # :int is an FFI type shorthand
|
64
|
+
# }
|
65
|
+
# If raw is true, the source is expected to be Raw Crystal code captured
|
66
|
+
# in a string or Heredoc literal. Otherwise the Ruby code (assumed to be valid Crystal)
|
67
|
+
# is extracted.
|
68
|
+
def extract_args_and_source_from_method(method, raw: false)
|
69
|
+
method_source = extract_expr_from_source_location(method.source_location)
|
70
|
+
params = SyntaxTree.search(method_source, :Params).first
|
71
|
+
args = params ? params.keywords.map{|k,v| [k.value[0...-1].to_sym, node_to_s(v)] }.to_h : {}
|
72
|
+
body_node = SyntaxTree.search(method_source, :BodyStmt).first || SyntaxTree.search(method_source, :Statements).to_a[1]
|
73
|
+
body = node_to_s(body_node)
|
74
|
+
body = SyntaxTree.search(body, :TStringContent).map(&method(:node_to_s)).join if raw
|
75
|
+
args.transform_values! do |type_exp|
|
76
|
+
if CrystalRuby::Typemaps::CRYSTAL_TYPE_MAP.key?(type_exp[1..-1].to_sym)
|
77
|
+
type_exp[1..-1].to_sym
|
78
|
+
else
|
79
|
+
TypeBuilder.build_from_source(type_exp, context: method.owner)
|
80
|
+
end
|
81
|
+
end.to_h
|
82
|
+
return args, body
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
data/lib/crystalruby/template.rb
CHANGED
@@ -1,12 +1,23 @@
|
|
1
1
|
module CrystalRuby
|
2
2
|
module Template
|
3
|
-
|
3
|
+
class Renderer < Struct.new(:raw_value)
|
4
|
+
require 'erb'
|
5
|
+
def render(context)
|
6
|
+
if context.kind_of?(::Hash)
|
7
|
+
raw_value % context
|
8
|
+
else
|
9
|
+
ERB.new(raw_value, trim_mode: "%").result(context)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
(
|
15
|
+
Dir[File.join(File.dirname(__FILE__), "templates", "**", "*.cr")] +
|
16
|
+
Dir[File.join(File.dirname(__FILE__), "types", "**", "*.cr")]
|
17
|
+
).each do |file|
|
4
18
|
template_name = File.basename(file, File.extname(file)).split("_").map(&:capitalize).join
|
5
19
|
template_value = File.read(file)
|
6
|
-
|
7
|
-
self % context
|
8
|
-
end
|
9
|
-
const_set(template_name, template_value)
|
20
|
+
const_set(template_name, Renderer.new(template_value))
|
10
21
|
end
|
11
22
|
end
|
12
23
|
end
|
@@ -3,8 +3,8 @@
|
|
3
3
|
# Crystal code can simply call this method directly, enabling generated crystal code
|
4
4
|
# to call other generated crystal code without overhead.
|
5
5
|
|
6
|
-
|
7
|
-
def
|
6
|
+
%{module_or_class} %{module_name} %{superclass}
|
7
|
+
def %{fn_scope}%{fn_name}(%{fn_args}) : %{fn_ret_type}
|
8
8
|
%{fn_body}
|
9
9
|
end
|
10
10
|
end
|
@@ -16,13 +16,13 @@ fun %{lib_fn_name}(%{lib_fn_args}): %{lib_fn_ret_type}
|
|
16
16
|
begin
|
17
17
|
%{convert_lib_args}
|
18
18
|
begin
|
19
|
-
return_value = %{
|
19
|
+
return_value = %{receiver}.%{fn_name}(%{arg_names})%{block_converter}
|
20
20
|
return %{convert_return_type}
|
21
21
|
rescue ex
|
22
|
-
CrystalRuby.report_error("RuntimeError", ex.message.to_s, 0)
|
22
|
+
CrystalRuby.report_error("RuntimeError", ex.message.to_s, ex.backtrace.to_json, 0)
|
23
23
|
end
|
24
24
|
rescue ex
|
25
|
-
CrystalRuby.report_error("ArgumentError", ex.message.to_s, 0)
|
25
|
+
CrystalRuby.report_error("ArgumentError", ex.message.to_s, ex.backtrace.to_json, 0)
|
26
26
|
end
|
27
27
|
return %{error_value}
|
28
28
|
end
|
@@ -37,25 +37,26 @@ fun %{lib_fn_name}_async(%{lib_fn_args} thread_id: UInt32, callback : %{callbac
|
|
37
37
|
CrystalRuby.increment_task_counter
|
38
38
|
spawn do
|
39
39
|
begin
|
40
|
-
return_value = %{
|
41
|
-
converted = %{convert_return_type}
|
40
|
+
return_value = %{receiver}.%{fn_name}(%{arg_names})%{block_converter}
|
42
41
|
CrystalRuby.queue_callback(->{
|
42
|
+
converted = %{convert_return_type}
|
43
43
|
%{callback_call}
|
44
44
|
CrystalRuby.decrement_task_counter
|
45
45
|
})
|
46
46
|
rescue ex
|
47
47
|
exception = ex.message.to_s
|
48
|
+
backtrace = ex.backtrace.to_json
|
48
49
|
CrystalRuby.queue_callback(->{
|
49
|
-
CrystalRuby.
|
50
|
+
CrystalRuby.report_error("RuntimeError", exception, backtrace, thread_id)
|
50
51
|
CrystalRuby.decrement_task_counter
|
51
52
|
})
|
52
53
|
end
|
53
54
|
end
|
54
55
|
rescue ex
|
55
|
-
|
56
56
|
exception = ex.message.to_s
|
57
|
+
backtrace = ex.backtrace.to_json
|
57
58
|
CrystalRuby.queue_callback(->{
|
58
|
-
CrystalRuby.
|
59
|
+
CrystalRuby.report_error("RuntimeError", ex.message.to_s, backtrace, thread_id)
|
59
60
|
CrystalRuby.decrement_task_counter
|
60
61
|
})
|
61
62
|
end
|
@@ -1,48 +1,40 @@
|
|
1
1
|
module CrystalRuby
|
2
|
-
|
3
2
|
ARGV1 = "crystalruby"
|
4
|
-
CALLBACK_MUX = Mutex.new
|
5
3
|
|
6
|
-
alias ErrorCallback = (Pointer(UInt8), Pointer(UInt8), UInt32 -> Void)
|
4
|
+
alias ErrorCallback = (Pointer(::UInt8), Pointer(::UInt8), Pointer(::UInt8), ::UInt32 -> Void)
|
5
|
+
|
6
|
+
class_property libname : String = "crystalruby"
|
7
|
+
class_property callbacks : Channel(Proc(Nil)) = Channel(Proc(Nil)).new
|
8
|
+
class_property rc_mux : Pointer(Void) = Pointer(Void).null
|
9
|
+
class_property task_counter : Atomic(Int32) = Atomic(Int32).new(0)
|
7
10
|
|
8
11
|
# Initializing Crystal Ruby invokes init on the Crystal garbage collector.
|
9
12
|
# We need to be sure to only do this once.
|
10
|
-
|
11
|
-
|
12
|
-
@@libname = "crystalruby"
|
13
|
-
# Our Ruby <-> Crystal Reactor uses Fibers, with callbacks to allow
|
14
|
-
# multiple concurrent Crystal operations to be queued
|
15
|
-
@@callbacks = [] of Proc(Nil)
|
16
|
-
|
17
|
-
# We only continue to yield to the Crystal scheduler from Ruby
|
18
|
-
# while there are outstanding tasks.
|
19
|
-
@@task_counter : Atomic(Int32) = Atomic.new(0)
|
13
|
+
class_property initialized : Bool = false
|
20
14
|
|
21
15
|
# We can override the error callback to catch errors in Crystal,
|
22
16
|
# and explicitly expose them to Ruby.
|
23
|
-
@@error_callback : ErrorCallback
|
17
|
+
@@error_callback : ErrorCallback?
|
24
18
|
|
25
19
|
# This is the entry point for instantiating CrystalRuby
|
26
20
|
# We:
|
27
21
|
# 1. Initialize the Crystal garbage collector
|
28
22
|
# 2. Set the error callback
|
29
23
|
# 3. Call the Crystal main function
|
30
|
-
def self.init(libname : Pointer(UInt8), error_callback : ErrorCallback)
|
31
|
-
return if
|
32
|
-
|
24
|
+
def self.init(libname : Pointer(::UInt8), @@error_callback : ErrorCallback, @@rc_mux : Pointer(Void))
|
25
|
+
return if self.initialized
|
26
|
+
self.initialized = true
|
33
27
|
argv_ptr = ARGV1.to_unsafe
|
34
28
|
Crystal.main_user_code(0, pointerof(argv_ptr))
|
35
|
-
|
36
|
-
|
29
|
+
self.libname = String.new(libname)
|
30
|
+
LibGC.set_finalize_on_demand(1)
|
37
31
|
end
|
38
32
|
|
39
33
|
# Explicit error handling (triggers exception within Ruby on the same thread)
|
40
|
-
def self.report_error(error_type : String,
|
41
|
-
@@error_callback
|
42
|
-
|
43
|
-
|
44
|
-
def self.error_callback : ErrorCallback
|
45
|
-
@@error_callback
|
34
|
+
def self.report_error(error_type : String, message : String, backtrace : String, thread_id : UInt32)
|
35
|
+
if error_reporter = @@error_callback
|
36
|
+
error_reporter.call(error_type.to_unsafe, message.to_unsafe, backtrace.to_unsafe, thread_id)
|
37
|
+
end
|
46
38
|
end
|
47
39
|
|
48
40
|
# New async task started
|
@@ -57,51 +49,40 @@ module CrystalRuby
|
|
57
49
|
|
58
50
|
# Get number of outstanding tasks
|
59
51
|
def self.get_task_counter : Int32
|
60
|
-
@@task_counter.get
|
52
|
+
@@task_counter.get
|
61
53
|
end
|
62
54
|
|
63
55
|
# Queue a callback for an async task
|
64
56
|
def self.queue_callback(callback : Proc(Nil))
|
65
|
-
|
66
|
-
@@callbacks << callback
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
# Get number of queued callbacks
|
71
|
-
def self.count_callbacks : Int32
|
72
|
-
@@callbacks.size
|
57
|
+
self.callbacks.send(callback)
|
73
58
|
end
|
74
59
|
|
75
|
-
def self.
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
# Flush all callbacks
|
80
|
-
def self.flush_callbacks : Int32
|
81
|
-
CALLBACK_MUX.synchronize do
|
82
|
-
count = @@callbacks.size
|
83
|
-
@@callbacks.each do |callback|
|
84
|
-
result = callback.call()
|
85
|
-
end
|
86
|
-
@@callbacks.clear
|
87
|
-
end
|
88
|
-
get_task_counter
|
60
|
+
def self.synchronize
|
61
|
+
LibC.pthread_mutex_lock(self.rc_mux)
|
62
|
+
yield
|
63
|
+
LibC.pthread_mutex_unlock(self.rc_mux)
|
89
64
|
end
|
90
65
|
end
|
91
66
|
|
92
67
|
# Initialize CrystalRuby
|
93
|
-
fun init(libname : Pointer(UInt8), cb : CrystalRuby::ErrorCallback): Void
|
94
|
-
CrystalRuby.init(libname, cb)
|
68
|
+
fun init(libname : Pointer(::UInt8), cb : CrystalRuby::ErrorCallback, rc_mux : Pointer(Void)) : Void
|
69
|
+
CrystalRuby.init(libname, cb, rc_mux)
|
95
70
|
end
|
96
71
|
|
97
72
|
fun stop : Void
|
98
|
-
LibGC.deinit
|
73
|
+
LibGC.deinit
|
99
74
|
end
|
100
75
|
|
101
76
|
@[Link("gc")]
|
102
77
|
lib LibGC
|
103
78
|
$stackbottom = GC_stackbottom : Void*
|
104
79
|
fun deinit = GC_deinit
|
80
|
+
fun set_finalize_on_demand = GC_set_finalize_on_demand(Int32)
|
81
|
+
fun invoke_finalizers = GC_invoke_finalizers : Int
|
82
|
+
end
|
83
|
+
|
84
|
+
lib LibC
|
85
|
+
fun calloc = calloc(Int32, Int32) : Void*
|
105
86
|
end
|
106
87
|
|
107
88
|
module GC
|
@@ -112,6 +93,11 @@ module GC
|
|
112
93
|
def self.set_stackbottom(stack_bottom : Void*)
|
113
94
|
LibGC.stackbottom = stack_bottom
|
114
95
|
end
|
96
|
+
|
97
|
+
def self.collect
|
98
|
+
LibGC.collect
|
99
|
+
LibGC.invoke_finalizers
|
100
|
+
end
|
115
101
|
end
|
116
102
|
|
117
103
|
# Yield to the Crystal scheduler from Ruby
|
@@ -119,24 +105,25 @@ end
|
|
119
105
|
# Otherwise, we yield to the Crystal scheduler and let Ruby know
|
120
106
|
# how many outstanding tasks still remain (it will stop yielding to Crystal
|
121
107
|
# once this figure reaches 0).
|
122
|
-
fun yield
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
CrystalRuby.flush_callbacks()
|
108
|
+
fun yield : Int32
|
109
|
+
Fiber.yield
|
110
|
+
loop do
|
111
|
+
select
|
112
|
+
when callback = CrystalRuby.callbacks.receive
|
113
|
+
callback.call
|
114
|
+
else
|
115
|
+
break
|
116
|
+
end
|
132
117
|
end
|
118
|
+
CrystalRuby.get_task_counter
|
133
119
|
end
|
134
120
|
|
121
|
+
class Array(T)
|
122
|
+
def initialize(size : Int32, @buffer : Pointer(T))
|
123
|
+
@size = size.to_i32
|
124
|
+
@capacity = @size
|
125
|
+
end
|
126
|
+
end
|
135
127
|
|
136
|
-
# This is where we define all our Crystal modules and types
|
137
|
-
# derived from their Ruby counterparts.
|
138
|
-
%{type_modules}
|
139
|
-
|
140
|
-
# Require all generated crystal files
|
141
128
|
require "json"
|
142
129
|
%{requires}
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# This is the template used for all CrystalRuby functions
|
2
|
+
# Calls to this method *from ruby* are first transformed through the lib function.
|
3
|
+
# Crystal code can simply call this method directly, enabling generated crystal code
|
4
|
+
# to call other generated crystal code without overhead.
|
5
|
+
|
6
|
+
%{module_or_class} %{module_name} %{superclass}
|
7
|
+
def %{fn_scope}%{fn_name}(%{fn_args}) : %{fn_ret_type}
|
8
|
+
%{convert_lib_args}
|
9
|
+
cb = %{module_name}.%{callback_name}
|
10
|
+
unless cb.nil?
|
11
|
+
callback_done_channel = Channel(Nil).new
|
12
|
+
return_value = nil
|
13
|
+
if Fiber.current == Thread.current.main_fiber
|
14
|
+
return_value = cb.call(%{lib_fn_arg_names})
|
15
|
+
return %{convert_return_type}
|
16
|
+
else
|
17
|
+
CrystalRuby.queue_callback(->{
|
18
|
+
return_value = cb.call(%{lib_fn_arg_names})
|
19
|
+
callback_done_channel.send(nil)
|
20
|
+
})
|
21
|
+
end
|
22
|
+
callback_done_channel.receive
|
23
|
+
return %{convert_return_type}
|
24
|
+
end
|
25
|
+
raise "No callback registered for %{fn_name}"
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
class_property %{callback_name} : Proc(%{lib_fn_types} %{lib_fn_ret_type})?
|
30
|
+
end
|
31
|
+
|
32
|
+
fun register_%{callback_name}(callback : Proc(%{lib_fn_types} %{lib_fn_ret_type})) : Void
|
33
|
+
%{module_name}.%{callback_name} = callback
|
34
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# This is the template used for all CrystalRuby functions
|
2
|
+
# Calls to this method *from ruby* are first transformed through the lib function.
|
3
|
+
# Crystal code can simply call this method directly, enabling generated crystal code
|
4
|
+
# to call other generated crystal code without overhead.
|
5
|
+
|
6
|
+
def %{fn_name}(%{fn_args}) : %{fn_ret_type}
|
7
|
+
%{fn_body}
|
8
|
+
end
|
9
|
+
|
10
|
+
# This function is the entry point for the CrystalRuby code, exposed through FFI.
|
11
|
+
# We apply some basic error handling here, and convert the arguments and return values
|
12
|
+
# to ensure that we are using Crystal native types.
|
13
|
+
fun %{lib_fn_name}(%{lib_fn_args}): %{lib_fn_ret_type}
|
14
|
+
begin
|
15
|
+
%{convert_lib_args}
|
16
|
+
begin
|
17
|
+
return_value = %{fn_name}(%{arg_names})%{block_converter}
|
18
|
+
return %{convert_return_type}
|
19
|
+
rescue ex
|
20
|
+
CrystalRuby.report_error("RuntimeError", ex.message.to_s, ex.backtrace.to_json, 0)
|
21
|
+
end
|
22
|
+
rescue ex
|
23
|
+
CrystalRuby.report_error("ArgumentError", ex.message.to_s, ex.backtrace.to_json, 0)
|
24
|
+
end
|
25
|
+
return %{error_value}
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
# This function is the async entry point for the CrystalRuby code, exposed through FFI.
|
30
|
+
# We apply some basic error handling here, and convert the arguments and return values
|
31
|
+
# to ensure that we are using Crystal native types.
|
32
|
+
fun %{lib_fn_name}_async(%{lib_fn_args} thread_id: UInt32, callback : %{callback_type}): Void
|
33
|
+
begin
|
34
|
+
%{convert_lib_args}
|
35
|
+
CrystalRuby.increment_task_counter
|
36
|
+
spawn do
|
37
|
+
begin
|
38
|
+
return_value = %{fn_name}(%{arg_names})%{block_converter}
|
39
|
+
CrystalRuby.queue_callback(->{
|
40
|
+
converted = %{convert_return_type}
|
41
|
+
%{callback_call}
|
42
|
+
CrystalRuby.decrement_task_counter
|
43
|
+
})
|
44
|
+
rescue ex
|
45
|
+
exception = ex.message.to_s
|
46
|
+
backtrace = ex.backtrace.to_json
|
47
|
+
CrystalRuby.queue_callback(->{
|
48
|
+
CrystalRuby.report_error("RuntimeError", exception, backtrace, thread_id)
|
49
|
+
CrystalRuby.decrement_task_counter
|
50
|
+
})
|
51
|
+
end
|
52
|
+
end
|
53
|
+
rescue ex
|
54
|
+
|
55
|
+
exception = ex.message.to_s
|
56
|
+
backtrace = ex.backtrace.to_json
|
57
|
+
CrystalRuby.queue_callback(->{
|
58
|
+
CrystalRuby.report_error("RuntimeError", ex.message.to_s, backtrace, thread_id)
|
59
|
+
CrystalRuby.decrement_task_counter
|
60
|
+
})
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# This is the template used for all CrystalRuby functions
|
2
|
+
# Calls to this method *from ruby* are first transformed through the lib function.
|
3
|
+
# Crystal code can simply call this method directly, enabling generated crystal code
|
4
|
+
# to call other generated crystal code without overhead.
|
5
|
+
|
6
|
+
module TopLevelCallbacks
|
7
|
+
class_property %{fn_name}_callback : Proc(%{lib_fn_types} %{lib_fn_ret_type})?
|
8
|
+
end
|
9
|
+
|
10
|
+
def %{fn_scope}%{fn_name}(%{fn_args}) : %{fn_ret_type}
|
11
|
+
%{convert_lib_args}
|
12
|
+
cb = TopLevelCallbacks.%{fn_name}_callback
|
13
|
+
unless cb.nil?
|
14
|
+
callback_done_channel = Channel(Nil).new
|
15
|
+
return_value = nil
|
16
|
+
if Fiber.current == Thread.current.main_fiber
|
17
|
+
return_value = cb.call(%{lib_fn_arg_names})
|
18
|
+
return %{convert_return_type}
|
19
|
+
else
|
20
|
+
CrystalRuby.queue_callback(->{
|
21
|
+
return_value = cb.call(%{lib_fn_arg_names})
|
22
|
+
callback_done_channel.send(nil)
|
23
|
+
})
|
24
|
+
end
|
25
|
+
callback_done_channel.receive
|
26
|
+
return %{convert_return_type}
|
27
|
+
end
|
28
|
+
raise "No callback registered for %{fn_name}"
|
29
|
+
end
|
30
|
+
|
31
|
+
fun register_%{fn_name}_callback(callback : Proc(%{lib_fn_types} %{lib_fn_ret_type})) : Void
|
32
|
+
TopLevelCallbacks.%{fn_name}_callback = callback
|
33
|
+
end
|