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
@@ -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
|