crystalruby 0.2.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2 -0
  3. data/README.md +389 -193
  4. data/Rakefile +4 -3
  5. data/crystalruby.gemspec +2 -2
  6. data/exe/crystalruby +1 -0
  7. data/lib/crystalruby/adapter.rb +131 -65
  8. data/lib/crystalruby/arc_mutex.rb +47 -0
  9. data/lib/crystalruby/compilation.rb +32 -3
  10. data/lib/crystalruby/config.rb +41 -37
  11. data/lib/crystalruby/function.rb +211 -68
  12. data/lib/crystalruby/library.rb +153 -48
  13. data/lib/crystalruby/reactor.rb +40 -23
  14. data/lib/crystalruby/source_reader.rb +86 -0
  15. data/lib/crystalruby/template.rb +16 -5
  16. data/lib/crystalruby/templates/function.cr +11 -10
  17. data/lib/crystalruby/templates/index.cr +53 -66
  18. data/lib/crystalruby/templates/inline_chunk.cr +1 -1
  19. data/lib/crystalruby/templates/ruby_interface.cr +34 -0
  20. data/lib/crystalruby/templates/top_level_function.cr +62 -0
  21. data/lib/crystalruby/templates/top_level_ruby_interface.cr +33 -0
  22. data/lib/crystalruby/typebuilder.rb +11 -55
  23. data/lib/crystalruby/typemaps.rb +92 -67
  24. data/lib/crystalruby/types/concerns/allocator.rb +80 -0
  25. data/lib/crystalruby/types/fixed_width/named_tuple.cr +80 -0
  26. data/lib/crystalruby/types/fixed_width/named_tuple.rb +86 -0
  27. data/lib/crystalruby/types/fixed_width/proc.cr +45 -0
  28. data/lib/crystalruby/types/fixed_width/proc.rb +79 -0
  29. data/lib/crystalruby/types/fixed_width/tagged_union.cr +53 -0
  30. data/lib/crystalruby/types/fixed_width/tagged_union.rb +109 -0
  31. data/lib/crystalruby/types/fixed_width/tuple.cr +82 -0
  32. data/lib/crystalruby/types/fixed_width/tuple.rb +92 -0
  33. data/lib/crystalruby/types/fixed_width.cr +138 -0
  34. data/lib/crystalruby/types/fixed_width.rb +205 -0
  35. data/lib/crystalruby/types/primitive.cr +21 -0
  36. data/lib/crystalruby/types/primitive.rb +117 -0
  37. data/lib/crystalruby/types/primitive_types/bool.cr +34 -0
  38. data/lib/crystalruby/types/primitive_types/bool.rb +11 -0
  39. data/lib/crystalruby/types/primitive_types/nil.cr +35 -0
  40. data/lib/crystalruby/types/primitive_types/nil.rb +16 -0
  41. data/lib/crystalruby/types/primitive_types/numbers.cr +37 -0
  42. data/lib/crystalruby/types/primitive_types/numbers.rb +28 -0
  43. data/lib/crystalruby/types/primitive_types/symbol.cr +55 -0
  44. data/lib/crystalruby/types/primitive_types/symbol.rb +35 -0
  45. data/lib/crystalruby/types/primitive_types/time.cr +35 -0
  46. data/lib/crystalruby/types/primitive_types/time.rb +25 -0
  47. data/lib/crystalruby/types/type.cr +64 -0
  48. data/lib/crystalruby/types/type.rb +239 -30
  49. data/lib/crystalruby/types/variable_width/array.cr +74 -0
  50. data/lib/crystalruby/types/variable_width/array.rb +88 -0
  51. data/lib/crystalruby/types/variable_width/hash.cr +146 -0
  52. data/lib/crystalruby/types/variable_width/hash.rb +117 -0
  53. data/lib/crystalruby/types/variable_width/string.cr +36 -0
  54. data/lib/crystalruby/types/variable_width/string.rb +18 -0
  55. data/lib/crystalruby/types/variable_width.cr +23 -0
  56. data/lib/crystalruby/types/variable_width.rb +46 -0
  57. data/lib/crystalruby/types.rb +32 -13
  58. data/lib/crystalruby/version.rb +2 -2
  59. data/lib/crystalruby.rb +13 -6
  60. metadata +41 -19
  61. data/lib/crystalruby/types/array.rb +0 -15
  62. data/lib/crystalruby/types/bool.rb +0 -3
  63. data/lib/crystalruby/types/hash.rb +0 -17
  64. data/lib/crystalruby/types/named_tuple.rb +0 -28
  65. data/lib/crystalruby/types/nil.rb +0 -3
  66. data/lib/crystalruby/types/numbers.rb +0 -5
  67. data/lib/crystalruby/types/string.rb +0 -3
  68. data/lib/crystalruby/types/symbol.rb +0 -3
  69. data/lib/crystalruby/types/time.rb +0 -8
  70. data/lib/crystalruby/types/tuple.rb +0 -17
  71. data/lib/crystalruby/types/type_serializer/json.rb +0 -41
  72. data/lib/crystalruby/types/type_serializer.rb +0 -37
  73. data/lib/crystalruby/types/typedef.rb +0 -57
  74. data/lib/crystalruby/types/union_type.rb +0 -43
  75. data/lib/module.rb +0 -3
@@ -4,66 +4,22 @@ module CrystalRuby
4
4
  module TypeBuilder
5
5
  module_function
6
6
 
7
- def with_injected_type_dsl(context, &block)
8
- with_constants(context) do
9
- with_methods(context, &block)
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
- def with_methods(context)
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
- def with_constants(context)
36
- previous_const_pairs = CrystalRuby::Types.constants.map do |type|
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
- def build
64
- result = yield
65
- Types::Type.validate!(result)
66
- Types::Typedef(result)
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
@@ -3,55 +3,60 @@
3
3
  module CrystalRuby
4
4
  module Typemaps
5
5
  CRYSTAL_TYPE_MAP = {
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)
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", # 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
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: "0", # In Crystal, :char is typically represented as Int8
33
- uchar: "0", # Unsigned char
34
- int8: "0", # Same as :char
35
- uint8: "0", # Same as :uchar
36
- short: "0", # Short integer
37
- ushort: "0", # Unsigned short integer
38
- int16: "0", # Same as :short
39
- uint16: "0", # Same as :ushort
40
- int: "0", # Integer, Crystal defaults to 32 bits
41
- uint: "0", # Unsigned integer
42
- int32: "0", # 32-bit integer
43
- uint32: "0", # 32-bit unsigned integer
44
- long: "0", # Long integer, size depends on the platform (32 or 64 bits)
45
- ulong: "0", # Unsigned long integer, size depends on the platform
46
- int64: "0", # 64-bit integer
47
- uint64: "0", # 64-bit unsigned integer
48
- long_long: "0", # Same as :int64
49
- ulong_long: "0", # Same as :uint64
50
- float: "0.0", # Floating point number (single precision)
51
- double: "0.0", # Double precision floating point number
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
- }.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
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::TypeSerializer)
96
+ arg_mapper: if crystalruby_type.is_a?(Class) && crystalruby_type < Types::Type
90
97
  lambda { |arg|
91
- crystalruby_type.prepare_argument(arg)
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::TypeSerializer)
103
+ retval_mapper: if crystalruby_type.is_a?(Class) && crystalruby_type < Types::Type
95
104
  lambda { |arg|
96
- crystalruby_type.prepare_retval(arg)
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 Types::TypeSerializer then type.ffi_type
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::TypeSerializer)
113
- type.lib_type
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::TypeSerializer)
123
- type.error_value
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::TypeSerializer)
133
- type.crystal_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::TypeSerializer)
143
- type.lib_to_crystal_type_expr(expr)
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::TypeSerializer)
151
- type.crystal_to_lib_type_expr(expr)
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