crystalruby 0.1.12 → 0.2.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/.dockerignore +10 -0
- data/README.md +109 -25
- data/exe/crystalruby +14 -14
- data/lib/crystalruby/adapter.rb +133 -0
- data/lib/crystalruby/compilation.rb +16 -63
- data/lib/crystalruby/config.rb +27 -11
- data/lib/crystalruby/function.rb +228 -0
- data/lib/crystalruby/library.rb +198 -0
- data/lib/crystalruby/reactor.rb +155 -0
- data/lib/crystalruby/template.rb +5 -5
- data/lib/crystalruby/templates/function.cr +36 -3
- data/lib/crystalruby/templates/index.cr +112 -17
- data/lib/crystalruby/typebuilder.rb +2 -0
- data/lib/crystalruby/typemaps.rb +95 -4
- data/lib/crystalruby/types/type_serializer/json.rb +3 -2
- data/lib/crystalruby/version.rb +1 -1
- data/lib/crystalruby.rb +26 -333
- data/lib/module.rb +1 -1
- metadata +7 -2
@@ -1,43 +1,138 @@
|
|
1
|
-
|
1
|
+
module CrystalRuby
|
2
2
|
|
3
|
-
|
3
|
+
ARGV1 = "crystalruby"
|
4
|
+
CALLBACK_MUX = Mutex.new
|
5
|
+
|
6
|
+
alias ErrorCallback = (Pointer(UInt8), Pointer(UInt8), UInt32 -> Void)
|
4
7
|
|
5
|
-
module CrystalRuby
|
6
8
|
# Initializing Crystal Ruby invokes init on the Crystal garbage collector.
|
7
9
|
# We need to be sure to only do this once.
|
8
10
|
@@initialized = false
|
9
11
|
|
10
|
-
|
11
|
-
#
|
12
|
-
#
|
13
|
-
@@
|
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)
|
20
|
+
|
21
|
+
# We can override the error callback to catch errors in Crystal,
|
22
|
+
# and explicitly expose them to Ruby.
|
23
|
+
@@error_callback : ErrorCallback = ->(t : UInt8* , s : UInt8*, tid : UInt32){ puts "Error: #{t}:#{s}" }
|
14
24
|
|
15
25
|
# This is the entry point for instantiating CrystalRuby
|
16
26
|
# We:
|
17
27
|
# 1. Initialize the Crystal garbage collector
|
18
28
|
# 2. Set the error callback
|
19
29
|
# 3. Call the Crystal main function
|
20
|
-
def self.init(error_callback : ErrorCallback)
|
30
|
+
def self.init(libname : Pointer(UInt8), error_callback : ErrorCallback)
|
21
31
|
return if @@initialized
|
22
|
-
|
23
|
-
|
32
|
+
@@initialized = true
|
33
|
+
argv_ptr = ARGV1.to_unsafe
|
34
|
+
Crystal.main_user_code(0, pointerof(argv_ptr))
|
24
35
|
@@error_callback = error_callback
|
25
|
-
|
26
|
-
LibCrystalMain.__crystal_main(1, pointerof(ptr))
|
36
|
+
@@libname = String.new(libname)
|
27
37
|
end
|
28
38
|
|
29
39
|
# Explicit error handling (triggers exception within Ruby on the same thread)
|
30
|
-
def self.report_error(error_type : String, str : String)
|
31
|
-
|
32
|
-
|
40
|
+
def self.report_error(error_type : String, str : String, thread_id : UInt32, )
|
41
|
+
@@error_callback.call(error_type.to_unsafe, str.to_unsafe, thread_id)
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.error_callback : ErrorCallback
|
45
|
+
@@error_callback
|
46
|
+
end
|
47
|
+
|
48
|
+
# New async task started
|
49
|
+
def self.increment_task_counter
|
50
|
+
@@task_counter.add(1)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Async task finished
|
54
|
+
def self.decrement_task_counter
|
55
|
+
@@task_counter.sub(1)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Get number of outstanding tasks
|
59
|
+
def self.get_task_counter : Int32
|
60
|
+
@@task_counter.get()
|
61
|
+
end
|
62
|
+
|
63
|
+
# Queue a callback for an async task
|
64
|
+
def self.queue_callback(callback : Proc(Nil))
|
65
|
+
CALLBACK_MUX.synchronize do
|
66
|
+
@@callbacks << callback
|
33
67
|
end
|
34
68
|
end
|
69
|
+
|
70
|
+
# Get number of queued callbacks
|
71
|
+
def self.count_callbacks : Int32
|
72
|
+
@@callbacks.size
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.libname : String
|
76
|
+
@@libname
|
77
|
+
end
|
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
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Initialize CrystalRuby
|
93
|
+
fun init(libname : Pointer(UInt8), cb : CrystalRuby::ErrorCallback): Void
|
94
|
+
CrystalRuby.init(libname, cb)
|
95
|
+
end
|
96
|
+
|
97
|
+
fun stop : Void
|
98
|
+
LibGC.deinit()
|
99
|
+
end
|
100
|
+
|
101
|
+
@[Link("gc")]
|
102
|
+
lib LibGC
|
103
|
+
$stackbottom = GC_stackbottom : Void*
|
104
|
+
fun deinit = GC_deinit
|
35
105
|
end
|
36
106
|
|
37
|
-
|
38
|
-
|
107
|
+
module GC
|
108
|
+
def self.current_thread_stack_bottom
|
109
|
+
{Pointer(Void).null, LibGC.stackbottom}
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.set_stackbottom(stack_bottom : Void*)
|
113
|
+
LibGC.stackbottom = stack_bottom
|
114
|
+
end
|
39
115
|
end
|
40
116
|
|
117
|
+
# Yield to the Crystal scheduler from Ruby
|
118
|
+
# If there's callbacks to process, we flush them
|
119
|
+
# Otherwise, we yield to the Crystal scheduler and let Ruby know
|
120
|
+
# how many outstanding tasks still remain (it will stop yielding to Crystal
|
121
|
+
# once this figure reaches 0).
|
122
|
+
fun yield() : Int32
|
123
|
+
if CrystalRuby.count_callbacks == 0
|
124
|
+
Fiber.yield
|
125
|
+
|
126
|
+
# TODO: We should apply backpressure here to prevent busy waiting if the number of outstanding tasks is not decreasing.
|
127
|
+
# Use a simple exponential backoff strategy, to increase the time between each yield up to a maximum of 1 second.
|
128
|
+
|
129
|
+
CrystalRuby.get_task_counter
|
130
|
+
else
|
131
|
+
CrystalRuby.flush_callbacks()
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
|
41
136
|
# This is where we define all our Crystal modules and types
|
42
137
|
# derived from their Ruby counterparts.
|
43
138
|
%{type_modules}
|
@@ -19,6 +19,7 @@ module CrystalRuby
|
|
19
19
|
nil
|
20
20
|
end
|
21
21
|
restores << [context, method_name, old_method]
|
22
|
+
context.singleton_class.undef_method(method_name) if old_method
|
22
23
|
context.define_singleton_method(method_name) do |*args|
|
23
24
|
Types.send(method_name, *args)
|
24
25
|
end
|
@@ -26,6 +27,7 @@ module CrystalRuby
|
|
26
27
|
yield
|
27
28
|
ensure
|
28
29
|
restores.each do |context, method_name, old_method|
|
30
|
+
context.singleton_class.undef_method(method_name)
|
29
31
|
context.define_singleton_method(method_name, old_method) if old_method
|
30
32
|
end
|
31
33
|
end
|
data/lib/crystalruby/typemaps.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module CrystalRuby
|
2
4
|
module Typemaps
|
3
5
|
CRYSTAL_TYPE_MAP = {
|
@@ -52,15 +54,104 @@ module CrystalRuby
|
|
52
54
|
string: '"".to_unsafe' # String type
|
53
55
|
}
|
54
56
|
|
55
|
-
C_TYPE_MAP = CRYSTAL_TYPE_MAP.merge(
|
56
|
-
|
57
|
-
|
57
|
+
C_TYPE_MAP = CRYSTAL_TYPE_MAP.merge(
|
58
|
+
{
|
59
|
+
string: "Pointer(UInt8)"
|
60
|
+
}
|
61
|
+
)
|
58
62
|
|
59
63
|
C_TYPE_CONVERSIONS = {
|
60
64
|
string: {
|
61
65
|
from: "String.new(%s)",
|
62
66
|
to: "%s.to_unsafe"
|
67
|
+
},
|
68
|
+
void: {
|
69
|
+
to: "nil"
|
63
70
|
}
|
64
|
-
|
71
|
+
}.tap do |hash|
|
72
|
+
hash.define_singleton_method(:convert) do |type, dir, expr|
|
73
|
+
if hash.key?(type)
|
74
|
+
conversion_string = hash[type][dir]
|
75
|
+
conversion_string =~ /%/ ? conversion_string % expr : conversion_string
|
76
|
+
else
|
77
|
+
expr
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def build_type_map(crystalruby_type)
|
83
|
+
{
|
84
|
+
ffi_type: ffi_type(crystalruby_type),
|
85
|
+
ffi_ret_type: ffi_type(crystalruby_type),
|
86
|
+
crystal_type: crystal_type(crystalruby_type),
|
87
|
+
lib_type: lib_type(crystalruby_type),
|
88
|
+
error_value: error_value(crystalruby_type),
|
89
|
+
arg_mapper: if crystalruby_type.is_a?(Types::TypeSerializer)
|
90
|
+
lambda { |arg|
|
91
|
+
crystalruby_type.prepare_argument(arg)
|
92
|
+
}
|
93
|
+
end,
|
94
|
+
retval_mapper: if crystalruby_type.is_a?(Types::TypeSerializer)
|
95
|
+
lambda { |arg|
|
96
|
+
crystalruby_type.prepare_retval(arg)
|
97
|
+
}
|
98
|
+
end,
|
99
|
+
convert_crystal_to_lib_type: ->(expr) { convert_crystal_to_lib_type(expr, crystalruby_type) },
|
100
|
+
convert_lib_to_crystal_type: ->(expr) { convert_lib_to_crystal_type(expr, crystalruby_type) }
|
101
|
+
}
|
102
|
+
end
|
103
|
+
|
104
|
+
def ffi_type(type)
|
105
|
+
case type
|
106
|
+
when Symbol then type
|
107
|
+
when Types::TypeSerializer then type.ffi_type
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def lib_type(type)
|
112
|
+
if type.is_a?(Types::TypeSerializer)
|
113
|
+
type.lib_type
|
114
|
+
else
|
115
|
+
C_TYPE_MAP.fetch(type)
|
116
|
+
end
|
117
|
+
rescue StandardError
|
118
|
+
raise "Unsupported type #{type}"
|
119
|
+
end
|
120
|
+
|
121
|
+
def error_value(type)
|
122
|
+
if type.is_a?(Types::TypeSerializer)
|
123
|
+
type.error_value
|
124
|
+
else
|
125
|
+
ERROR_VALUE.fetch(type)
|
126
|
+
end
|
127
|
+
rescue StandardError
|
128
|
+
raise "Unsupported type #{type}"
|
129
|
+
end
|
130
|
+
|
131
|
+
def crystal_type(type)
|
132
|
+
if type.is_a?(Types::TypeSerializer)
|
133
|
+
type.crystal_type
|
134
|
+
else
|
135
|
+
CRYSTAL_TYPE_MAP.fetch(type)
|
136
|
+
end
|
137
|
+
rescue StandardError
|
138
|
+
raise "Unsupported type #{type}"
|
139
|
+
end
|
140
|
+
|
141
|
+
def convert_lib_to_crystal_type(expr, type)
|
142
|
+
if type.is_a?(Types::TypeSerializer)
|
143
|
+
type.lib_to_crystal_type_expr(expr)
|
144
|
+
else
|
145
|
+
C_TYPE_CONVERSIONS.convert(type, :from, expr)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def convert_crystal_to_lib_type(expr, type)
|
150
|
+
if type.is_a?(Types::TypeSerializer)
|
151
|
+
type.crystal_to_lib_type_expr(expr)
|
152
|
+
else
|
153
|
+
C_TYPE_CONVERSIONS.convert(type, :to, expr)
|
154
|
+
end
|
155
|
+
end
|
65
156
|
end
|
66
157
|
end
|
data/lib/crystalruby/version.rb
CHANGED
data/lib/crystalruby.rb
CHANGED
@@ -13,357 +13,50 @@ require_relative "crystalruby/types"
|
|
13
13
|
require_relative "crystalruby/typebuilder"
|
14
14
|
require_relative "crystalruby/template"
|
15
15
|
require_relative "crystalruby/compilation"
|
16
|
+
require_relative "crystalruby/adapter"
|
17
|
+
require_relative "crystalruby/reactor"
|
18
|
+
require_relative "crystalruby/library"
|
19
|
+
require_relative "crystalruby/function"
|
20
|
+
require_relative "module"
|
16
21
|
|
17
22
|
module CrystalRuby
|
18
|
-
CR_SRC_FILES_PATTERN = "./**/*.cr"
|
19
|
-
# Define a method to set the @crystalize proc if it doesn't already exist
|
20
|
-
def crystalize(type = :src, **options, &block)
|
21
|
-
(args,), returns = options.first
|
22
|
-
args ||= {}
|
23
|
-
raise "Arguments should be of the form name: :type. Got #{args}" unless args.is_a?(Hash)
|
24
|
-
|
25
|
-
@crystalize_next = { raw: type.to_sym == :raw, args: args, returns: returns, block: block }
|
26
|
-
end
|
27
|
-
|
28
|
-
def crystal(type = :src, &block)
|
29
|
-
inline_crystal_body = Template.render(
|
30
|
-
Template::InlineChunk,
|
31
|
-
{
|
32
|
-
module_name: name,
|
33
|
-
body: block.source.lines[
|
34
|
-
type == :raw ? 2...-2 : 1...-1
|
35
|
-
].join("\n")
|
36
|
-
}
|
37
|
-
)
|
38
|
-
CrystalRuby.write_chunk(self, body: inline_crystal_body)
|
39
|
-
end
|
40
|
-
|
41
|
-
def crtype(&block)
|
42
|
-
TypeBuilder.with_injected_type_dsl(self) do
|
43
|
-
TypeBuilder.build(&block)
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def json(&block)
|
48
|
-
crtype(&block).serialize_as(:json)
|
49
|
-
end
|
50
|
-
|
51
|
-
def method_added(method_name)
|
52
|
-
if @crystalize_next
|
53
|
-
attach_crystalized_method(method_name)
|
54
|
-
@crystalize_next = nil
|
55
|
-
end
|
56
|
-
super
|
57
|
-
end
|
58
|
-
|
59
|
-
def config
|
60
|
-
CrystalRuby.config
|
61
|
-
end
|
62
|
-
|
63
|
-
def attach_crystalized_method(method_name)
|
64
|
-
CrystalRuby.instantiate_crystal_ruby! unless CrystalRuby.instantiated?
|
65
|
-
|
66
|
-
function_body = instance_method(method_name).source.lines[
|
67
|
-
@crystalize_next[:raw] ? 2...-2 : 1...-1
|
68
|
-
].join("\n")
|
69
|
-
|
70
|
-
fname = "#{name.downcase}_#{method_name}"
|
71
|
-
args, returns, block = @crystalize_next.values_at(:args, :returns, :block)
|
72
|
-
args ||= {}
|
73
|
-
@crystalize_next = nil
|
74
|
-
function = build_function(self, method_name, args, returns, function_body)
|
75
|
-
CrystalRuby.write_chunk(self, name: function[:name], body: function[:body]) do
|
76
|
-
extend FFI::Library
|
77
|
-
ffi_lib config.crystal_lib_dir / config.crystal_lib_name
|
78
|
-
attach_function method_name, fname, function[:ffi_types], function[:return_ffi_type]
|
79
|
-
if block
|
80
|
-
[singleton_class, self].each do |receiver|
|
81
|
-
receiver.prepend(Module.new do
|
82
|
-
define_method(method_name, &block)
|
83
|
-
end)
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
[singleton_class, self].each do |receiver|
|
89
|
-
receiver.prepend(Module.new do
|
90
|
-
define_method(method_name) do |*args|
|
91
|
-
CrystalRuby.build! unless CrystalRuby.compiled?
|
92
|
-
unless CrystalRuby.attached?
|
93
|
-
CrystalRuby.attach!
|
94
|
-
return send(method_name, *args) if block
|
95
|
-
end
|
96
|
-
args.each_with_index do |arg, i|
|
97
|
-
args[i] = function[:arg_maps][i][arg] if function[:arg_maps][i]
|
98
|
-
end
|
99
|
-
result = super(*args)
|
100
|
-
if function[:retval_map]
|
101
|
-
function[:retval_map][result]
|
102
|
-
else
|
103
|
-
result
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end)
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
23
|
module_function
|
111
24
|
|
112
|
-
def
|
113
|
-
|
114
|
-
return_type = build_type_map(returns)
|
115
|
-
function_body = Template.render(
|
116
|
-
Template::Function,
|
117
|
-
{
|
118
|
-
module_name: owner.name,
|
119
|
-
lib_fn_name: "#{owner.name.downcase}_#{name}",
|
120
|
-
fn_name: name,
|
121
|
-
fn_body: body,
|
122
|
-
fn_args: arg_types.map { |k, arg_type| "#{k} : #{arg_type[:crystal_type]}" }.join(","),
|
123
|
-
fn_ret_type: return_type[:crystal_type],
|
124
|
-
lib_fn_args: arg_types.map { |k, arg_type| "_#{k}: #{arg_type[:lib_type]}" }.join(","),
|
125
|
-
lib_fn_ret_type: return_type[:lib_type],
|
126
|
-
convert_lib_args: arg_types.map do |k, arg_type|
|
127
|
-
"#{k} = #{arg_type[:convert_lib_to_crystal_type]["_#{k}"]}"
|
128
|
-
end.join("\n "),
|
129
|
-
arg_names: args.keys.join(","),
|
130
|
-
convert_return_type: return_type[:convert_crystal_to_lib_type]["return_value"],
|
131
|
-
error_value: return_type[:error_value]
|
132
|
-
}
|
133
|
-
)
|
134
|
-
{
|
135
|
-
name: name,
|
136
|
-
body: function_body,
|
137
|
-
retval_map: returns.is_a?(Types::TypeSerializer) ? ->(rv) { returns.prepare_retval(rv) } : nil,
|
138
|
-
ffi_types: arg_types.map { |_k, arg_type| arg_type[:ffi_type] },
|
139
|
-
arg_maps: arg_types.map { |_k, arg_type| arg_type[:mapper] },
|
140
|
-
return_ffi_type: return_type[:return_ffi_type]
|
141
|
-
}
|
142
|
-
end
|
143
|
-
|
144
|
-
def build_type_map(crystalruby_type)
|
145
|
-
if crystalruby_type.is_a?(Types::TypeSerializer) && !crystalruby_type.anonymous?
|
146
|
-
CrystalRuby.register_type!(crystalruby_type)
|
147
|
-
end
|
148
|
-
|
149
|
-
{
|
150
|
-
ffi_type: ffi_type(crystalruby_type),
|
151
|
-
return_ffi_type: ffi_type(crystalruby_type),
|
152
|
-
crystal_type: crystal_type(crystalruby_type),
|
153
|
-
lib_type: lib_type(crystalruby_type),
|
154
|
-
error_value: error_value(crystalruby_type),
|
155
|
-
mapper: crystalruby_type.is_a?(Types::TypeSerializer) ? ->(arg) { crystalruby_type.prepare_argument(arg) } : nil,
|
156
|
-
convert_crystal_to_lib_type: ->(expr) { convert_crystal_to_lib_type(expr, crystalruby_type) },
|
157
|
-
convert_lib_to_crystal_type: ->(expr) { convert_lib_to_crystal_type(expr, crystalruby_type) }
|
158
|
-
}
|
159
|
-
end
|
160
|
-
|
161
|
-
def ffi_type(type)
|
162
|
-
case type
|
163
|
-
when Symbol then type
|
164
|
-
when Types::TypeSerializer then type.ffi_type
|
165
|
-
end
|
166
|
-
end
|
167
|
-
|
168
|
-
def lib_type(type)
|
169
|
-
if type.is_a?(Types::TypeSerializer)
|
170
|
-
type.lib_type
|
171
|
-
else
|
172
|
-
Typemaps::C_TYPE_MAP.fetch(type)
|
173
|
-
end
|
174
|
-
rescue StandardError => e
|
175
|
-
raise "Unsupported type #{type}"
|
176
|
-
end
|
177
|
-
|
178
|
-
def error_value(type)
|
179
|
-
if type.is_a?(Types::TypeSerializer)
|
180
|
-
type.error_value
|
181
|
-
else
|
182
|
-
Typemaps::ERROR_VALUE.fetch(type)
|
183
|
-
end
|
184
|
-
rescue StandardError => e
|
185
|
-
raise "Unsupported type #{type}"
|
186
|
-
end
|
187
|
-
|
188
|
-
def crystal_type(type)
|
189
|
-
if type.is_a?(Types::TypeSerializer)
|
190
|
-
type.crystal_type
|
191
|
-
else
|
192
|
-
Typemaps::CRYSTAL_TYPE_MAP.fetch(type)
|
193
|
-
end
|
194
|
-
rescue StandardError => e
|
195
|
-
raise "Unsupported type #{type}"
|
196
|
-
end
|
197
|
-
|
198
|
-
def convert_lib_to_crystal_type(expr, type)
|
199
|
-
if type.is_a?(Types::TypeSerializer)
|
200
|
-
type.lib_to_crystal_type_expr(expr)
|
201
|
-
else
|
202
|
-
Typemaps::C_TYPE_CONVERSIONS[type] ? Typemaps::C_TYPE_CONVERSIONS[type][:from] % expr : expr
|
203
|
-
end
|
204
|
-
end
|
205
|
-
|
206
|
-
def convert_crystal_to_lib_type(expr, type)
|
207
|
-
if type.is_a?(Types::TypeSerializer)
|
208
|
-
type.crystal_to_lib_type_expr(expr)
|
209
|
-
else
|
210
|
-
Typemaps::C_TYPE_CONVERSIONS[type] ? Typemaps::C_TYPE_CONVERSIONS[type][:to] % expr : expr
|
211
|
-
end
|
212
|
-
end
|
213
|
-
|
214
|
-
def self.instantiate_crystal_ruby!
|
215
|
-
unless system("which crystal > /dev/null 2>&1")
|
216
|
-
raise "Crystal executable not found. Please ensure Crystal is installed and in your PATH."
|
217
|
-
end
|
218
|
-
|
219
|
-
@instantiated = true
|
220
|
-
%w[crystal_lib_dir crystal_main_file crystal_src_dir crystal_lib_name].each do |config_key|
|
221
|
-
unless config.send(config_key)
|
222
|
-
raise "Missing config option `#{config_key}`. \nProvide this inside crystalruby.yaml (run `bundle exec crystalruby init` to generate this file with detaults)"
|
223
|
-
end
|
224
|
-
end
|
225
|
-
FileUtils.mkdir_p config.crystal_codegen_dir_abs
|
226
|
-
FileUtils.mkdir_p config.crystal_lib_dir_abs
|
227
|
-
FileUtils.mkdir_p config.crystal_src_dir_abs
|
228
|
-
unless File.exist?(config.crystal_src_dir_abs / config.crystal_main_file)
|
229
|
-
IO.write(
|
230
|
-
config.crystal_src_dir_abs / config.crystal_main_file,
|
231
|
-
"require \"./#{config.crystal_codegen_dir}/index\"\n"
|
232
|
-
)
|
233
|
-
end
|
234
|
-
|
235
|
-
attach_crystal_ruby_lib! if compiled?
|
236
|
-
|
237
|
-
return if File.exist?(config.crystal_src_dir / "shard.yml")
|
238
|
-
|
239
|
-
IO.write("#{config.crystal_src_dir}/shard.yml", <<~YAML)
|
240
|
-
name: src
|
241
|
-
version: 0.1.0
|
242
|
-
YAML
|
25
|
+
def initialized?
|
26
|
+
!!@initialized
|
243
27
|
end
|
244
28
|
|
245
|
-
def
|
246
|
-
|
247
|
-
ffi_lib config.crystal_lib_dir / config.crystal_lib_name
|
248
|
-
attach_function "init!", :init, [:pointer], :void
|
249
|
-
send(:remove_const, :ErrorCallback) if defined?(ErrorCallback)
|
250
|
-
const_set(:ErrorCallback, FFI::Function.new(:void, %i[string string]) do |error_type, message|
|
251
|
-
error_type = error_type.to_sym
|
252
|
-
is_exception_type = Object.const_defined?(error_type) && Object.const_get(error_type).ancestors.include?(Exception)
|
253
|
-
error_type = is_exception_type ? Object.const_get(error_type) : RuntimeError
|
254
|
-
raise error_type.new(message)
|
255
|
-
end)
|
256
|
-
init!(ErrorCallback)
|
257
|
-
end
|
29
|
+
def initialize_crystal_ruby!
|
30
|
+
return if initialized?
|
258
31
|
|
259
|
-
|
260
|
-
|
32
|
+
check_crystal_ruby!
|
33
|
+
check_config!
|
34
|
+
@initialized = true
|
261
35
|
end
|
262
36
|
|
263
|
-
def
|
264
|
-
|
265
|
-
@compiled
|
266
|
-
end
|
37
|
+
def check_crystal_ruby!
|
38
|
+
return if system("which crystal > /dev/null 2>&1")
|
267
39
|
|
268
|
-
|
269
|
-
|
40
|
+
raise "Crystal executable not found. Please ensure Crystal is installed and in your PATH." \
|
41
|
+
"See https://crystal-lang.org/install/"
|
270
42
|
end
|
271
43
|
|
272
|
-
def
|
273
|
-
|
274
|
-
@types_cache[type.name] = type.type_defn
|
275
|
-
end
|
44
|
+
def check_config!
|
45
|
+
return if config.crystal_src_dir
|
276
46
|
|
277
|
-
|
278
|
-
|
279
|
-
parts = type_name.split("::")
|
280
|
-
typedef = parts[0...-1].each_with_index.reduce("") do |acc, (part, index)|
|
281
|
-
acc + "#{" " * index}module #{part}\n"
|
282
|
-
end
|
283
|
-
typedef += "#{" " * (parts.size - 1)}alias #{parts.last} = #{expr}\n"
|
284
|
-
typedef + parts[0...-1].reverse.each_with_index.reduce("") do |acc, (_part, index)|
|
285
|
-
acc + "#{" " * (parts.size - 2 - index)}end\n"
|
286
|
-
end
|
287
|
-
end.join("\n")
|
47
|
+
raise "Missing config option `crystal_src_dir`. \nProvide this inside crystalruby.yaml "\
|
48
|
+
"(run `bundle exec crystalruby init` to generate this file with detaults)"
|
288
49
|
end
|
289
50
|
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
file_digest = Digest::MD5.hexdigest function_data
|
294
|
-
fname = function[:name]
|
295
|
-
"require \"./#{function[:owner].name}/#{fname}_#{file_digest}.cr\"\n"
|
296
|
-
end.join("\n")
|
297
|
-
end
|
51
|
+
%w[debug info warn error].each do |level|
|
52
|
+
define_method("log_#{level}") do |*msg|
|
53
|
+
prefix = config.colorize_log_output ? "\e[33mcrystalruby\e[0m\e[90m [#{Thread.current.object_id}]\e[0m" : "[crystalruby] #{Thread.current.object_id}"
|
298
54
|
|
299
|
-
|
300
|
-
File.write config.crystal_codegen_dir_abs / "index.cr", Template.render(
|
301
|
-
Template::Index,
|
302
|
-
type_modules: type_modules,
|
303
|
-
requires: requires
|
304
|
-
)
|
305
|
-
if @compiled = CrystalRuby::Compilation.compile!(
|
306
|
-
verbose: config.verbose,
|
307
|
-
debug: config.debug
|
308
|
-
)
|
309
|
-
IO.write(digest_file_name, get_cr_src_files_digest)
|
310
|
-
attach_crystal_ruby_lib!
|
311
|
-
else
|
312
|
-
File.delete(digest_file_name) if File.exist?(digest_file_name)
|
313
|
-
raise "Error compiling crystal code"
|
55
|
+
config.logger.send(level, "#{prefix} #{msg.join(", ")}")
|
314
56
|
end
|
315
57
|
end
|
316
58
|
|
317
|
-
def
|
318
|
-
|
319
|
-
function[:compile_callback]&.call
|
320
|
-
end
|
321
|
-
@attached = true
|
322
|
-
end
|
323
|
-
|
324
|
-
def self.get_cr_src_files_digest
|
325
|
-
file_digests = Dir.glob(CR_SRC_FILES_PATTERN).sort.map do |file_path|
|
326
|
-
content = File.read(file_path)
|
327
|
-
Digest::MD5.hexdigest(content)
|
328
|
-
end.join
|
329
|
-
Digest::MD5.hexdigest(file_digests)
|
330
|
-
end
|
331
|
-
|
332
|
-
def self.digest_file_name
|
333
|
-
@digest_file_name ||= config.crystal_lib_dir_abs / "#{config.crystal_lib_name}.digest"
|
334
|
-
end
|
335
|
-
|
336
|
-
def self.chunk_store
|
337
|
-
@chunk_store ||= []
|
338
|
-
end
|
339
|
-
|
340
|
-
def self.get_current_crystal_lib_digest
|
341
|
-
File.read(digest_file_name) if File.exist?(digest_file_name)
|
342
|
-
end
|
343
|
-
|
344
|
-
def self.write_chunk(owner, body:, name: Digest::MD5.hexdigest(body), &compile_callback)
|
345
|
-
chunk_store << { owner: owner, name: name, body: body, compile_callback: compile_callback }
|
346
|
-
FileUtils.mkdir_p(config.crystal_codegen_dir_abs)
|
347
|
-
existing = Dir.glob("#{config.crystal_codegen_dir_abs}/**/*.cr")
|
348
|
-
chunk_store.each do |function|
|
349
|
-
owner_name = function[:owner].name
|
350
|
-
FileUtils.mkdir_p(config.crystal_codegen_dir_abs / owner_name)
|
351
|
-
function_data = function[:body]
|
352
|
-
fname = function[:name]
|
353
|
-
file_digest = Digest::MD5.hexdigest function_data
|
354
|
-
filename = config.crystal_codegen_dir_abs / owner_name / "#{fname}_#{file_digest}.cr"
|
355
|
-
unless existing.delete(filename.to_s)
|
356
|
-
@compiled = false
|
357
|
-
@attached = false
|
358
|
-
File.write(filename, function_data)
|
359
|
-
end
|
360
|
-
existing.select do |f|
|
361
|
-
f =~ /#{config.crystal_codegen_dir / owner_name / "#{fname}_[a-f0-9]{32}\.cr"}/
|
362
|
-
end.each do |fl|
|
363
|
-
File.delete(fl) unless fl.eql?(filename.to_s)
|
364
|
-
end
|
365
|
-
end
|
59
|
+
def compile!
|
60
|
+
CrystalRuby::Library.all.each(&:build!)
|
366
61
|
end
|
367
62
|
end
|
368
|
-
|
369
|
-
require_relative "module"
|
data/lib/module.rb
CHANGED