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