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
@@ -4,66 +4,22 @@ module CrystalRuby
|
|
4
4
|
module TypeBuilder
|
5
5
|
module_function
|
6
6
|
|
7
|
-
def
|
8
|
-
|
9
|
-
|
7
|
+
def build_from_source(src, context: )
|
8
|
+
source_type = Types.with_binding_fallback(context) do |binding|
|
9
|
+
eval(src.is_a?(String) ? src : SourceReader.extract_source_from_proc(src), binding)
|
10
10
|
end
|
11
|
-
end
|
12
11
|
|
13
|
-
|
14
|
-
restores = []
|
15
|
-
%i[Array Hash NamedTuple Tuple].each do |method_name|
|
16
|
-
old_method = begin
|
17
|
-
context.instance_method(method_name)
|
18
|
-
rescue StandardError
|
19
|
-
nil
|
20
|
-
end
|
21
|
-
restores << [context, method_name, old_method]
|
22
|
-
context.singleton_class.undef_method(method_name) if old_method
|
23
|
-
context.define_singleton_method(method_name) do |*args|
|
24
|
-
Types.send(method_name, *args)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
yield
|
28
|
-
ensure
|
29
|
-
restores.each do |context, method_name, old_method|
|
30
|
-
context.singleton_class.undef_method(method_name)
|
31
|
-
context.define_singleton_method(method_name, old_method) if old_method
|
32
|
-
end
|
33
|
-
end
|
12
|
+
return source_type if source_type.is_a?(Types::Root::Symbol)
|
34
13
|
|
35
|
-
|
36
|
-
|
37
|
-
[type, begin
|
38
|
-
context.const_get(type)
|
39
|
-
rescue StandardError
|
40
|
-
nil
|
41
|
-
end]
|
42
|
-
end
|
43
|
-
CrystalRuby::Types.constants.each do |type|
|
44
|
-
begin
|
45
|
-
context.send(:remove_const, type)
|
46
|
-
rescue StandardError
|
47
|
-
nil
|
48
|
-
end
|
49
|
-
context.const_set(type, CrystalRuby::Types.const_get(type))
|
14
|
+
unless source_type.kind_of?(Class) && source_type < Types::Type
|
15
|
+
raise "Invalid type #{source_type.inspect}"
|
50
16
|
end
|
51
|
-
yield
|
52
|
-
ensure
|
53
|
-
previous_const_pairs.each do |const_name, const_value|
|
54
|
-
begin
|
55
|
-
context.send(:remove_const, const_name)
|
56
|
-
rescue StandardError
|
57
|
-
nil
|
58
|
-
end
|
59
|
-
context.const_set(const_name, const_value)
|
60
|
-
end
|
61
|
-
end
|
62
17
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
18
|
+
return source_type unless source_type.anonymous?
|
19
|
+
|
20
|
+
source_type.tap do |new_type|
|
21
|
+
Types::Type.validate!(new_type)
|
22
|
+
end
|
67
23
|
end
|
68
24
|
end
|
69
25
|
end
|
data/lib/crystalruby/typemaps.rb
CHANGED
@@ -3,55 +3,60 @@
|
|
3
3
|
module CrystalRuby
|
4
4
|
module Typemaps
|
5
5
|
CRYSTAL_TYPE_MAP = {
|
6
|
-
char: "Int8",
|
7
|
-
uchar: "UInt8",
|
8
|
-
int8: "Int8",
|
9
|
-
uint8: "UInt8",
|
10
|
-
short: "Int16",
|
11
|
-
ushort: "UInt16",
|
12
|
-
int16: "Int16",
|
13
|
-
uint16: "UInt16",
|
14
|
-
int: "Int32",
|
15
|
-
uint: "UInt32",
|
16
|
-
int32: "Int32",
|
17
|
-
uint32: "UInt32",
|
18
|
-
long: "Int32 | Int64",
|
6
|
+
char: "Int8", # In Crystal, :char is typically represented as Int8
|
7
|
+
uchar: "UInt8", # Unsigned char
|
8
|
+
int8: "Int8", # Same as :char
|
9
|
+
uint8: "UInt8", # Same as :uchar
|
10
|
+
short: "Int16", # Short integer
|
11
|
+
ushort: "UInt16", # Unsigned short integer
|
12
|
+
int16: "Int16", # Same as :short
|
13
|
+
uint16: "UInt16", # Same as :ushort
|
14
|
+
int: "Int32", # Integer, Crystal defaults to 32 bits
|
15
|
+
uint: "UInt32", # Unsigned integer
|
16
|
+
int32: "Int32", # 32-bit integer
|
17
|
+
uint32: "UInt32", # 32-bit unsigned integer
|
18
|
+
long: "Int32 | Int64", # Long integer, size depends on the platform (32 or 64 bits)
|
19
19
|
ulong: "UInt32 | UInt64", # Unsigned long integer, size depends on the platform
|
20
|
-
int64: "Int64",
|
21
|
-
uint64: "UInt64",
|
22
|
-
long_long: "Int64",
|
23
|
-
ulong_long: "UInt64",
|
24
|
-
float: "Float32",
|
25
|
-
double: "Float64",
|
26
|
-
bool: "Bool",
|
27
|
-
void: "Void",
|
28
|
-
string: "String"
|
20
|
+
int64: "Int64", # 64-bit integer
|
21
|
+
uint64: "UInt64", # 64-bit unsigned integer
|
22
|
+
long_long: "Int64", # Same as :int64
|
23
|
+
ulong_long: "UInt64", # Same as :uint64
|
24
|
+
float: "Float32", # Floating point number (single precision)
|
25
|
+
double: "Float64", # Double precision floating point number
|
26
|
+
bool: "Bool", # Boolean type
|
27
|
+
void: "Void", # Void type
|
28
|
+
string: "String", # String type
|
29
|
+
pointer: "Pointer(Void)" # Pointer type
|
30
|
+
|
29
31
|
}
|
30
32
|
|
33
|
+
FFI_TYPE_MAP = CRYSTAL_TYPE_MAP.invert
|
34
|
+
|
31
35
|
ERROR_VALUE = {
|
32
|
-
char: "
|
33
|
-
uchar: "
|
34
|
-
int8: "
|
35
|
-
uint8: "
|
36
|
-
short: "
|
37
|
-
ushort: "
|
38
|
-
int16: "
|
39
|
-
uint16: "
|
40
|
-
int: "
|
41
|
-
uint: "
|
42
|
-
int32: "
|
43
|
-
uint32: "
|
44
|
-
long: "
|
45
|
-
ulong: "
|
46
|
-
int64: "
|
47
|
-
uint64: "
|
48
|
-
long_long: "
|
49
|
-
ulong_long: "
|
50
|
-
float: "0.
|
51
|
-
double: "0.
|
36
|
+
char: "0i8", # In Crystal, :char is typically represented as Int8
|
37
|
+
uchar: "0u8", # Unsigned char
|
38
|
+
int8: "0i8", # Same as :char
|
39
|
+
uint8: "0u8", # Same as :uchar
|
40
|
+
short: "0i16", # Short integer
|
41
|
+
ushort: "0u16", # Unsigned short integer
|
42
|
+
int16: "0i16", # Same as :short
|
43
|
+
uint16: "0u16", # Same as :ushort
|
44
|
+
int: "0i32", # Integer, Crystal defaults to 32 bits
|
45
|
+
uint: "0u32", # Unsigned integer
|
46
|
+
int32: "0i32", # 32-bit integer
|
47
|
+
uint32: "0u32", # 32-bit unsigned integer
|
48
|
+
long: "0i64", # Long integer, size depends on the platform (32 or 64 bits)
|
49
|
+
ulong: "0u64", # Unsigned long integer, size depends on the platform
|
50
|
+
int64: "0_i64", # 64-bit integer
|
51
|
+
uint64: "0_u64", # 64-bit unsigned integer
|
52
|
+
long_long: "0_i64", # Same as :int64
|
53
|
+
ulong_long: "0_u64", # Same as :uint64
|
54
|
+
float: "0.0f32", # Floating point number (single precision)
|
55
|
+
double: "0.0f64", # Double precision floating point number
|
52
56
|
bool: "false", # Boolean type
|
53
57
|
void: "Void", # Void type
|
54
|
-
string: '"".to_unsafe' # String type
|
58
|
+
string: '"".to_unsafe', # String type
|
59
|
+
pointer: "Pointer(Void).null" # Pointer type
|
55
60
|
}
|
56
61
|
|
57
62
|
C_TYPE_MAP = CRYSTAL_TYPE_MAP.merge(
|
@@ -68,32 +73,40 @@ module CrystalRuby
|
|
68
73
|
void: {
|
69
74
|
to: "nil"
|
70
75
|
}
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
end
|
76
|
+
}.tap do |hash|
|
77
|
+
hash.define_singleton_method(:convert) do |type, dir, expr|
|
78
|
+
if hash.key?(type)
|
79
|
+
conversion_string = hash[type][dir]
|
80
|
+
conversion_string =~ /%/ ? conversion_string % expr : conversion_string
|
81
|
+
else
|
82
|
+
expr
|
79
83
|
end
|
80
84
|
end
|
85
|
+
end
|
81
86
|
|
82
87
|
def build_type_map(crystalruby_type)
|
88
|
+
crystalruby_type = CRType(&crystalruby_type) if crystalruby_type.is_a?(Proc)
|
83
89
|
{
|
84
90
|
ffi_type: ffi_type(crystalruby_type),
|
85
91
|
ffi_ret_type: ffi_type(crystalruby_type),
|
86
92
|
crystal_type: crystal_type(crystalruby_type),
|
93
|
+
crystalruby_type: crystalruby_type,
|
87
94
|
lib_type: lib_type(crystalruby_type),
|
88
95
|
error_value: error_value(crystalruby_type),
|
89
|
-
arg_mapper: if crystalruby_type.is_a?(Types::
|
96
|
+
arg_mapper: if crystalruby_type.is_a?(Class) && crystalruby_type < Types::Type
|
90
97
|
lambda { |arg|
|
91
|
-
crystalruby_type.
|
98
|
+
arg = crystalruby_type.new(arg.memory) if arg.is_a?(Types::Type) && !arg.is_a?(crystalruby_type)
|
99
|
+
arg = crystalruby_type.new(arg) unless arg.is_a?(Types::Type)
|
100
|
+
arg
|
92
101
|
}
|
93
102
|
end,
|
94
|
-
retval_mapper: if crystalruby_type.is_a?(Types::
|
103
|
+
retval_mapper: if crystalruby_type.is_a?(Class) && crystalruby_type < Types::Type
|
95
104
|
lambda { |arg|
|
96
|
-
|
105
|
+
if arg.is_a?(Types::Type) && !arg.is_a?(crystalruby_type)
|
106
|
+
arg = crystalruby_type.new(arg.memory)
|
107
|
+
end
|
108
|
+
arg = crystalruby_type.new(arg) unless arg.is_a?(Types::Type)
|
109
|
+
crystalruby_type.anonymous? ? arg.native : arg
|
97
110
|
}
|
98
111
|
end,
|
99
112
|
convert_crystal_to_lib_type: ->(expr) { convert_crystal_to_lib_type(expr, crystalruby_type) },
|
@@ -104,13 +117,20 @@ module CrystalRuby
|
|
104
117
|
def ffi_type(type)
|
105
118
|
case type
|
106
119
|
when Symbol then type
|
107
|
-
when
|
120
|
+
when Class
|
121
|
+
if type < Types::FixedWidth
|
122
|
+
:pointer
|
123
|
+
elsif type < Types::Primitive
|
124
|
+
type.ffi_type
|
125
|
+
end
|
108
126
|
end
|
109
127
|
end
|
110
128
|
|
111
129
|
def lib_type(type)
|
112
|
-
if type.is_a?(Types::
|
113
|
-
|
130
|
+
if type.is_a?(Class) && type < Types::FixedWidth
|
131
|
+
"Pointer(::UInt8)"
|
132
|
+
elsif type.is_a?(Class) && type < Types::Type
|
133
|
+
C_TYPE_MAP.fetch(type.ffi_type)
|
114
134
|
else
|
115
135
|
C_TYPE_MAP.fetch(type)
|
116
136
|
end
|
@@ -119,8 +139,10 @@ module CrystalRuby
|
|
119
139
|
end
|
120
140
|
|
121
141
|
def error_value(type)
|
122
|
-
if type.is_a?(Types::
|
123
|
-
|
142
|
+
if type.is_a?(Class) && type < Types::FixedWidth
|
143
|
+
"Pointer(::UInt8).null"
|
144
|
+
elsif type.is_a?(Class) && type < Types::Type
|
145
|
+
ERROR_VALUE.fetch(type.ffi_type)
|
124
146
|
else
|
125
147
|
ERROR_VALUE.fetch(type)
|
126
148
|
end
|
@@ -129,8 +151,8 @@ module CrystalRuby
|
|
129
151
|
end
|
130
152
|
|
131
153
|
def crystal_type(type)
|
132
|
-
if type.is_a?(Types::
|
133
|
-
type.
|
154
|
+
if type.is_a?(Class) && type < Types::Type
|
155
|
+
type.anonymous? ? type.native_type_expr : type.inspect
|
134
156
|
else
|
135
157
|
CRYSTAL_TYPE_MAP.fetch(type)
|
136
158
|
end
|
@@ -139,16 +161,19 @@ module CrystalRuby
|
|
139
161
|
end
|
140
162
|
|
141
163
|
def convert_lib_to_crystal_type(expr, type)
|
142
|
-
if type.is_a?(Types::
|
143
|
-
type.
|
164
|
+
if type.is_a?(Class) && type < Types::Type
|
165
|
+
expr = "#{expr}.not_nil!" unless type.nil?
|
166
|
+
type.pointer_to_crystal_type_conversion(expr)
|
167
|
+
elsif type == :void
|
168
|
+
"nil"
|
144
169
|
else
|
145
|
-
C_TYPE_CONVERSIONS.convert(type, :from, expr)
|
170
|
+
"#{C_TYPE_CONVERSIONS.convert(type, :from, expr)}.not_nil!"
|
146
171
|
end
|
147
172
|
end
|
148
173
|
|
149
174
|
def convert_crystal_to_lib_type(expr, type)
|
150
|
-
if type.is_a?(Types::
|
151
|
-
type.
|
175
|
+
if type.is_a?(Class) && type < Types::Type
|
176
|
+
type.crystal_type_to_pointer_type_conversion(expr)
|
152
177
|
else
|
153
178
|
C_TYPE_CONVERSIONS.convert(type, :to, expr)
|
154
179
|
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module CrystalRuby
|
2
|
+
module Types
|
3
|
+
# Module for memory allocation and tracking functionality
|
4
|
+
module Allocator
|
5
|
+
|
6
|
+
# Called when module is included in a class
|
7
|
+
# @param base [Class] The class including this module
|
8
|
+
def self.included(base)
|
9
|
+
base.class_eval do
|
10
|
+
# Synchronizes a block using mutex
|
11
|
+
# @yield Block to be synchronized
|
12
|
+
def self.synchronize(&block)
|
13
|
+
Type::ARC_MUTEX.synchronize(&block)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Schedules a block for execution
|
17
|
+
# @yield Block to be scheduled
|
18
|
+
def self.schedule!(&block)
|
19
|
+
Type::ARC_MUTEX.schedule!(&block)
|
20
|
+
end
|
21
|
+
|
22
|
+
extend FFI::Library
|
23
|
+
ffi_lib "c"
|
24
|
+
attach_function :_calloc, :calloc, [:size_t, :size_t], :pointer
|
25
|
+
attach_function :_free, :free, [:pointer], :void
|
26
|
+
define_singleton_method(:ptr, &FFI::Pointer.method(:new))
|
27
|
+
define_method(:ptr, &FFI::Pointer.method(:new))
|
28
|
+
|
29
|
+
extend Forwardable
|
30
|
+
|
31
|
+
# Instance method to allocate memory
|
32
|
+
# @param size [Integer] Size in bytes to allocate
|
33
|
+
# @return [FFI::Pointer] Pointer to allocated memory
|
34
|
+
def malloc(size)
|
35
|
+
self.class.malloc(size)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Class method to allocate memory
|
39
|
+
# @param size [Integer] Size in bytes to allocate
|
40
|
+
# @return [FFI::Pointer] Pointer to allocated memory
|
41
|
+
def self.malloc(size)
|
42
|
+
result = _calloc(size, 1)
|
43
|
+
traced_live_objects[result.address] = result if trace_live_objects?
|
44
|
+
result
|
45
|
+
end
|
46
|
+
|
47
|
+
# Frees allocated memory
|
48
|
+
# @param ptr [FFI::Pointer] Pointer to memory to free
|
49
|
+
def self.free(ptr)
|
50
|
+
traced_live_objects.delete(ptr.address) if trace_live_objects?
|
51
|
+
_free(ptr)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns hash of traced live objects
|
55
|
+
# @return [Hash] Map of addresses to pointers
|
56
|
+
def self.traced_live_objects
|
57
|
+
@traced_live_objects ||= {}
|
58
|
+
end
|
59
|
+
|
60
|
+
# Enables tracing of live objects
|
61
|
+
def self.trace_live_objects!
|
62
|
+
@trace_live_objects = true
|
63
|
+
end
|
64
|
+
|
65
|
+
# Checks if live object tracing is enabled
|
66
|
+
# @return [Boolean] True if tracing is enabled
|
67
|
+
def self.trace_live_objects?
|
68
|
+
!!@trace_live_objects
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns count of live objects being tracked
|
72
|
+
# @return [Integer] Number of live objects
|
73
|
+
def self.live_objects
|
74
|
+
traced_live_objects.count
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
class <%= base_crystal_class_name %> < CrystalRuby::Types::FixedWidth
|
2
|
+
|
3
|
+
def initialize(tuple : ::NamedTuple(<%= inner_keys.zip(inner_types).map{|k,v| "#{k}: #{v.native_type_expr}"}.join(',') %>))
|
4
|
+
@memory = malloc(data_offset + 8)
|
5
|
+
self.value = tuple
|
6
|
+
increment_ref_count!
|
7
|
+
end
|
8
|
+
|
9
|
+
def ==(other : <%= native_type_expr %>)
|
10
|
+
native == other
|
11
|
+
end
|
12
|
+
|
13
|
+
def data_pointer
|
14
|
+
Pointer(::UInt8).new((@memory + data_offset).as(::Pointer(UInt64)).value)
|
15
|
+
end
|
16
|
+
|
17
|
+
def [](key : Symbol | String)
|
18
|
+
<% offset = 0 %>
|
19
|
+
<% inner_types.each_with_index do |type, i| %>
|
20
|
+
<% offset += type.refsize %>
|
21
|
+
return <%= type.crystal_class_name %>.fetch_single(data_pointer + <%= offset - type.refsize %>).value if :<%= inner_keys[i] %> == key || key == "<%= inner_keys[i] %>"
|
22
|
+
<% end %>
|
23
|
+
end
|
24
|
+
|
25
|
+
<% offset = 0 %>
|
26
|
+
<% inner_types.each_with_index do |type, i| %>
|
27
|
+
<% offset += type.refsize %>
|
28
|
+
def <%= inner_keys[i] %>
|
29
|
+
return <%= type.crystal_class_name %>.fetch_single(data_pointer + <%= offset - type.refsize %>)
|
30
|
+
end
|
31
|
+
|
32
|
+
def <%= inner_keys[i] %>=(value : <%= type.native_type_expr %>)
|
33
|
+
return <%= type.crystal_class_name %>.write_single(data_pointer + <%= offset - type.refsize %>, value)
|
34
|
+
end
|
35
|
+
<% end %>
|
36
|
+
|
37
|
+
|
38
|
+
|
39
|
+
def value=(tuple : ::NamedTuple(<%= inner_keys.zip(inner_types).map{|k,v| "#{k}: #{v.native_type_expr}"}.join(',') %>))
|
40
|
+
self.class.copy_to!(tuple, @memory)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.copy_to!(tuple : ::NamedTuple(<%= inner_keys.zip(inner_types).map{|k,v| "#{k}: #{v.native_type_expr}"}.join(',') %>), memory : Pointer(::UInt8))
|
44
|
+
data_pointer = malloc(self.memsize)
|
45
|
+
address = data_pointer.address
|
46
|
+
|
47
|
+
<% inner_types.each_with_index do |type, i| %>
|
48
|
+
<%= type.crystal_class_name %>.write_single(data_pointer, tuple[:<%= inner_keys[i] %>])
|
49
|
+
data_pointer += <%= type.refsize %>
|
50
|
+
<% end %>
|
51
|
+
|
52
|
+
(memory+data_offset).as(Pointer(::UInt64)).value = address
|
53
|
+
end
|
54
|
+
|
55
|
+
def value
|
56
|
+
address = data_pointer
|
57
|
+
<% inner_types.each_with_index do |type, i| %>
|
58
|
+
v<%= i %> = <%= type.crystal_class_name %>.fetch_single(address)
|
59
|
+
address += <%= type.refsize %>
|
60
|
+
<% end %>
|
61
|
+
::NamedTuple.new(<%= inner_types.map.with_index { |_, i| "#{inner_keys[i]}: v#{i}" }.join(", ") %>)
|
62
|
+
end
|
63
|
+
|
64
|
+
def native : ::NamedTuple(<%= inner_types.map.with_index { |type, i| "#{inner_keys[i]}: #{type.native_type_expr}" }.join(", ") %>)
|
65
|
+
address = data_pointer
|
66
|
+
<% inner_types.each_with_index do |type, i| %>
|
67
|
+
v<%= i %> = <%= type.crystal_class_name %>.fetch_single(address).native
|
68
|
+
address += <%= type.refsize %>
|
69
|
+
<% end %>
|
70
|
+
::NamedTuple.new(<%= inner_types.map.with_index { |_, i| "#{inner_keys[i]}: v#{i}" }.join(", ") %>)
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.memsize
|
74
|
+
<%= memsize %>
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.refsize
|
78
|
+
<%= refsize %>
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CrystalRuby::Types
|
4
|
+
NamedTuple = FixedWidth.build(error: "NamedTuple type must contain one or more symbol -> type pairs. E.g. NamedTuple(hello: Int32, world: String)")
|
5
|
+
|
6
|
+
def self.NamedTuple(types_hash)
|
7
|
+
raise "NamedTuple must be instantiated with a hash" unless types_hash.is_a?(Root::Hash)
|
8
|
+
|
9
|
+
types_hash.keys.each do |key|
|
10
|
+
raise "NamedTuple keys must be symbols" unless key.is_a?(Root::Symbol) || key.respond_to?(:to_sym)
|
11
|
+
end
|
12
|
+
keys = types_hash.keys.map(&:to_sym)
|
13
|
+
value_types = types_hash.values
|
14
|
+
|
15
|
+
FixedWidth.build(:NamedTuple, ffi_type: :pointer, inner_types: value_types, inner_keys: keys,
|
16
|
+
convert_if: [Root::Hash]) do
|
17
|
+
@data_offset = 4
|
18
|
+
|
19
|
+
# We only accept Hash-like values, which have all of the required keys
|
20
|
+
# and values of the correct type
|
21
|
+
# can successfully be cast to our inner types
|
22
|
+
def self.cast!(value)
|
23
|
+
value = value.transform_keys(&:to_sym)
|
24
|
+
unless value.is_a?(Hash) || value.is_a?(Root::Hash) && inner_keys.each_with_index.all? do |k, i|
|
25
|
+
value.key?(k) && inner_types[i].valid_cast?(value[k])
|
26
|
+
end
|
27
|
+
raise CrystalRuby::InvalidCastError, "Cannot cast #{value} to #{inspect}"
|
28
|
+
end
|
29
|
+
|
30
|
+
inner_keys.map { |k| value[k] }
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.copy_to!(values, memory:)
|
34
|
+
data_pointer = malloc(memsize)
|
35
|
+
|
36
|
+
memory[data_offset].write_pointer(data_pointer)
|
37
|
+
|
38
|
+
inner_types.each.reduce(0) do |offset, type|
|
39
|
+
type.write_single(data_pointer[offset], values.shift)
|
40
|
+
offset + type.refsize
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.memsize
|
45
|
+
inner_types.map(&:refsize).sum
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.each_child_address(pointer)
|
49
|
+
data_pointer = pointer[data_offset].read_pointer
|
50
|
+
inner_types.each do |type|
|
51
|
+
yield type, data_pointer
|
52
|
+
data_pointer += type.refsize
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.offset_for(key)
|
57
|
+
inner_types[0...inner_keys.index(key)].map(&:refsize).sum
|
58
|
+
end
|
59
|
+
|
60
|
+
def value(native: false)
|
61
|
+
ptr = data_pointer
|
62
|
+
inner_keys.zip(inner_types.map do |type|
|
63
|
+
result = type.fetch_single(ptr, native: native)
|
64
|
+
ptr += type.refsize
|
65
|
+
result
|
66
|
+
end).to_h
|
67
|
+
end
|
68
|
+
|
69
|
+
inner_keys.each.with_index do |key, index|
|
70
|
+
type = inner_types[index]
|
71
|
+
offset = offset_for(key)
|
72
|
+
unless method_defined?(key)
|
73
|
+
define_method(key) do
|
74
|
+
type.fetch_single(data_pointer[offset])
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
unless method_defined?("#{key}=")
|
79
|
+
define_method("#{key}=") do |value|
|
80
|
+
type.write_single(data_pointer[offset], value)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
class <%= base_crystal_class_name %> < CrystalRuby::Types::FixedWidth
|
2
|
+
|
3
|
+
def initialize(inner_proc : Proc(<%= inner_types.map(&:native_type_expr).join(",") %>))
|
4
|
+
@memory = malloc(20) # 4 bytes RC + 2x 8 Byte Pointer
|
5
|
+
self.value = inner_proc
|
6
|
+
increment_ref_count!
|
7
|
+
end
|
8
|
+
|
9
|
+
def ==(other : <%= native_type_expr %>)
|
10
|
+
native == other
|
11
|
+
end
|
12
|
+
|
13
|
+
def value : Proc(<%= inner_types.map(&:crystal_type).join(",") %>)
|
14
|
+
func_ptr = Pointer(Void).new((@memory+4).as(Pointer(::UInt64)).value)
|
15
|
+
Proc(<%= inner_types.map(&:crystal_type).join(",") %>).new(func_ptr, Pointer(Void).null)
|
16
|
+
end
|
17
|
+
|
18
|
+
def native : Proc(<%= inner_types.map(&:crystal_type).join(",") %>)
|
19
|
+
value
|
20
|
+
end
|
21
|
+
|
22
|
+
def value=(inner_proc : Proc(<%= inner_types.map(&:native_type_expr).join(",") %>))
|
23
|
+
# We can't maintain a direct reference to our inner_proc within our callback
|
24
|
+
# as this turns it into a closure, which we cannot pass over FFI.
|
25
|
+
# Instead, we box the inner_proc and pass a pointer to the box to the callback.
|
26
|
+
# and then unbox it within the callback.
|
27
|
+
|
28
|
+
boxed_data = Box.box(inner_proc)
|
29
|
+
|
30
|
+
callback_ptr = Proc(Pointer(::UInt8), <%= inner_types.map(&:crystal_type).join(",") %>).new do |<%= inner_types.size.times.map{|i| "_v#{i}" }.join(",") %>|
|
31
|
+
|
32
|
+
inner_prc = Box(typeof(inner_proc)).unbox(_v0.as(Pointer(Void)))
|
33
|
+
<% inner_types.each.with_index do |inner_type, i| %>
|
34
|
+
<% next if i == inner_types.size - 1 %>
|
35
|
+
v<%= i.succ %> = <%= inner_type.crystal_class_name %>.new(_v<%= i.succ %>)<%= inner_type.anonymous? ? ".value" : "" %>
|
36
|
+
<% end %>
|
37
|
+
|
38
|
+
return_value = inner_prc.call(<%= inner_types.size.-(1).times.map{|i| "v#{i.succ}" }.join(",") %>)
|
39
|
+
<%= inner_types[-1].crystal_class_name %>.new(return_value).return_value
|
40
|
+
end.pointer
|
41
|
+
|
42
|
+
(@memory+4).as(Pointer(::UInt64)).value = callback_ptr.address
|
43
|
+
(@memory+12).as(Pointer(::UInt64)).value = boxed_data.address
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module CrystalRuby::Types
|
2
|
+
PROC_REGISTERY = {}
|
3
|
+
Proc = FixedWidth.build(error: "Proc declarations should contain a list of 0 or more comma separated argument types,"\
|
4
|
+
"and a single return type (or Nil if it does not return a value)")
|
5
|
+
|
6
|
+
def self.Proc(*types)
|
7
|
+
proc_type = FixedWidth.build(:Proc, convert_if: [::Proc], inner_types: types, ffi_type: :pointer) do
|
8
|
+
@data_offset = 4
|
9
|
+
|
10
|
+
def self.cast!(rbval)
|
11
|
+
raise "Value must be a proc" unless rbval.is_a?(::Proc)
|
12
|
+
|
13
|
+
func = FFI::Function.new(FFI::Type.const_get(inner_types[-1].ffi_type.to_s.upcase), inner_types[0...-1].map do |v|
|
14
|
+
FFI::Type.const_get(v.ffi_type.to_s.upcase)
|
15
|
+
end) do |*args|
|
16
|
+
args = args.map.with_index do |arg, i|
|
17
|
+
arg = inner_types[i].new(arg) unless arg.is_a?(inner_types[i])
|
18
|
+
inner_types[i].anonymous? ? arg.native : arg
|
19
|
+
end
|
20
|
+
return_val = rbval.call(*args)
|
21
|
+
return_val = inner_types[-1].new(return_val) unless return_val.is_a?(inner_types[-1])
|
22
|
+
return_val.memory
|
23
|
+
end
|
24
|
+
PROC_REGISTERY[func.address] = func
|
25
|
+
func
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.copy_to!(rbval, memory:)
|
29
|
+
memory[4].write_pointer(rbval)
|
30
|
+
end
|
31
|
+
|
32
|
+
def invoke(*args)
|
33
|
+
invoker = value
|
34
|
+
invoker.call(memory[12].read_pointer, *args)
|
35
|
+
end
|
36
|
+
|
37
|
+
def value(native: false)
|
38
|
+
FFI::VariadicInvoker.new(
|
39
|
+
memory[4].read_pointer,
|
40
|
+
[FFI::Type::POINTER, *(inner_types[0...-1].map { |v| FFI::Type.const_get(v.ffi_type.to_s.upcase) })],
|
41
|
+
FFI::Type.const_get(inner_types[-1].ffi_type.to_s.upcase),
|
42
|
+
{ ffi_convention: :stdcall }
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.block_converter
|
47
|
+
<<~CRYSTAL
|
48
|
+
{ #{
|
49
|
+
inner_types.size > 1 ? "|#{inner_types.size.-(1).times.map { |i| "v#{i}" }.join(",")}|" : ""
|
50
|
+
}
|
51
|
+
#{
|
52
|
+
inner_types[0...-1].map.with_index do |type, i|
|
53
|
+
<<~CRYS
|
54
|
+
v#{i} = #{type.crystal_class_name}.new(v#{i}).return_value
|
55
|
+
|
56
|
+
callback_done_channel = Channel(Nil).new
|
57
|
+
result = nil
|
58
|
+
if Fiber.current == Thread.current.main_fiber
|
59
|
+
block_value = #{inner_types[-1].crystal_class_name}.new(__yield_to.call(#{inner_types.size.-(1).times.map { |i| "v#{i}" }.join(",")}))
|
60
|
+
result = #{inner_types[-1].anonymous? ? "block_value.native" : "block_value"}
|
61
|
+
next #{inner_types.last == CrystalRuby::Types::Nil ? "result" : "result.not_nil!"}
|
62
|
+
else
|
63
|
+
CrystalRuby.queue_callback(->{
|
64
|
+
block_value = #{inner_types[-1].crystal_class_name}.new(__yield_to.call(#{inner_types.size.-(1).times.map { |i| "v#{i}" }.join(",")}))
|
65
|
+
result = #{inner_types[-1].anonymous? ? "block_value.native" : "block_value"}
|
66
|
+
callback_done_channel.send(nil)
|
67
|
+
})
|
68
|
+
end
|
69
|
+
callback_done_channel.receive
|
70
|
+
#{inner_types.last == CrystalRuby::Types::Nil ? "result" : "result.not_nil!"}
|
71
|
+
CRYS
|
72
|
+
end.join("\n")
|
73
|
+
}
|
74
|
+
}
|
75
|
+
CRYSTAL
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|