crystalruby 0.2.3 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +2 -0
  3. data/Dockerfile +23 -2
  4. data/README.md +395 -198
  5. data/Rakefile +4 -3
  6. data/crystalruby.gemspec +2 -2
  7. data/examples/adder/adder.rb +1 -1
  8. data/exe/crystalruby +1 -0
  9. data/lib/crystalruby/adapter.rb +143 -73
  10. data/lib/crystalruby/arc_mutex.rb +47 -0
  11. data/lib/crystalruby/compilation.rb +32 -3
  12. data/lib/crystalruby/config.rb +41 -37
  13. data/lib/crystalruby/function.rb +216 -73
  14. data/lib/crystalruby/library.rb +157 -51
  15. data/lib/crystalruby/reactor.rb +63 -44
  16. data/lib/crystalruby/source_reader.rb +92 -0
  17. data/lib/crystalruby/template.rb +16 -5
  18. data/lib/crystalruby/templates/function.cr +11 -10
  19. data/lib/crystalruby/templates/index.cr +53 -66
  20. data/lib/crystalruby/templates/inline_chunk.cr +1 -1
  21. data/lib/crystalruby/templates/ruby_interface.cr +34 -0
  22. data/lib/crystalruby/templates/top_level_function.cr +62 -0
  23. data/lib/crystalruby/templates/top_level_ruby_interface.cr +33 -0
  24. data/lib/crystalruby/typebuilder.rb +11 -55
  25. data/lib/crystalruby/typemaps.rb +92 -67
  26. data/lib/crystalruby/types/concerns/allocator.rb +80 -0
  27. data/lib/crystalruby/types/fixed_width/named_tuple.cr +80 -0
  28. data/lib/crystalruby/types/fixed_width/named_tuple.rb +86 -0
  29. data/lib/crystalruby/types/fixed_width/proc.cr +45 -0
  30. data/lib/crystalruby/types/fixed_width/proc.rb +79 -0
  31. data/lib/crystalruby/types/fixed_width/tagged_union.cr +53 -0
  32. data/lib/crystalruby/types/fixed_width/tagged_union.rb +113 -0
  33. data/lib/crystalruby/types/fixed_width/tuple.cr +82 -0
  34. data/lib/crystalruby/types/fixed_width/tuple.rb +92 -0
  35. data/lib/crystalruby/types/fixed_width.cr +138 -0
  36. data/lib/crystalruby/types/fixed_width.rb +205 -0
  37. data/lib/crystalruby/types/primitive.cr +21 -0
  38. data/lib/crystalruby/types/primitive.rb +117 -0
  39. data/lib/crystalruby/types/primitive_types/bool.cr +34 -0
  40. data/lib/crystalruby/types/primitive_types/bool.rb +11 -0
  41. data/lib/crystalruby/types/primitive_types/nil.cr +35 -0
  42. data/lib/crystalruby/types/primitive_types/nil.rb +16 -0
  43. data/lib/crystalruby/types/primitive_types/numbers.cr +37 -0
  44. data/lib/crystalruby/types/primitive_types/numbers.rb +28 -0
  45. data/lib/crystalruby/types/primitive_types/symbol.cr +55 -0
  46. data/lib/crystalruby/types/primitive_types/symbol.rb +35 -0
  47. data/lib/crystalruby/types/primitive_types/time.cr +35 -0
  48. data/lib/crystalruby/types/primitive_types/time.rb +25 -0
  49. data/lib/crystalruby/types/type.cr +64 -0
  50. data/lib/crystalruby/types/type.rb +249 -30
  51. data/lib/crystalruby/types/variable_width/array.cr +74 -0
  52. data/lib/crystalruby/types/variable_width/array.rb +88 -0
  53. data/lib/crystalruby/types/variable_width/hash.cr +146 -0
  54. data/lib/crystalruby/types/variable_width/hash.rb +117 -0
  55. data/lib/crystalruby/types/variable_width/string.cr +36 -0
  56. data/lib/crystalruby/types/variable_width/string.rb +18 -0
  57. data/lib/crystalruby/types/variable_width.cr +23 -0
  58. data/lib/crystalruby/types/variable_width.rb +46 -0
  59. data/lib/crystalruby/types.rb +32 -13
  60. data/lib/crystalruby/version.rb +2 -2
  61. data/lib/crystalruby.rb +13 -6
  62. metadata +42 -22
  63. data/lib/crystalruby/types/array.rb +0 -15
  64. data/lib/crystalruby/types/bool.rb +0 -3
  65. data/lib/crystalruby/types/hash.rb +0 -17
  66. data/lib/crystalruby/types/named_tuple.rb +0 -28
  67. data/lib/crystalruby/types/nil.rb +0 -3
  68. data/lib/crystalruby/types/numbers.rb +0 -5
  69. data/lib/crystalruby/types/string.rb +0 -3
  70. data/lib/crystalruby/types/symbol.rb +0 -3
  71. data/lib/crystalruby/types/time.rb +0 -8
  72. data/lib/crystalruby/types/tuple.rb +0 -17
  73. data/lib/crystalruby/types/type_serializer/json.rb +0 -41
  74. data/lib/crystalruby/types/type_serializer.rb +0 -37
  75. data/lib/crystalruby/types/typedef.rb +0 -57
  76. data/lib/crystalruby/types/union_type.rb +0 -43
  77. data/lib/module.rb +0 -3
@@ -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
@@ -0,0 +1,53 @@
1
+ class <%= base_crystal_class_name %> < CrystalRuby::Types::FixedWidth
2
+
3
+ def initialize(value : <%= native_type_expr %>)
4
+ @memory = malloc(data_offset + <%= memsize %>_u64)
5
+ self.value = value
6
+ increment_ref_count!
7
+ end
8
+
9
+ def value=(value : <%= native_type_expr %>)
10
+ self.class.copy_to!(value, @memory)
11
+ end
12
+
13
+ def ==(other : <%= native_type_expr %>)
14
+ native == other
15
+ end
16
+
17
+ def value
18
+ data_pointer = @memory + data_offset
19
+ case data_pointer.value
20
+ <% union_types.each_with_index do |type, index| %>
21
+ when <%= index %>
22
+ <%= type.crystal_class_name %>.fetch_single(data_pointer+1)
23
+ <% end %>
24
+ else raise "Invalid union type #{data_pointer.value}"
25
+ end
26
+ end
27
+
28
+ def native
29
+ data_pointer = @memory + data_offset
30
+ case data_pointer.value
31
+ <% union_types.each_with_index do |type, index| %>
32
+ when <%= index %>
33
+ <%= type.crystal_class_name %>.fetch_single(data_pointer+1).native
34
+ <% end %>
35
+ else raise "Invalid union type #{data_pointer.value}"
36
+ end
37
+ end
38
+
39
+ def self.copy_to!(value : <%= native_type_expr %>, ptr : Pointer(::UInt8))
40
+ data_pointer = ptr + data_offset
41
+ case value
42
+ <% union_types.each_with_index do |type, index| %>
43
+ when <%= type.native_type_expr %>
44
+ data_pointer[0] = <%= index %>
45
+ <%= type.crystal_class_name %>.write_single(data_pointer + 1, value)
46
+ <% end %>
47
+ end
48
+ end
49
+
50
+ def self.memsize
51
+ <%= memsize %>
52
+ end
53
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CrystalRuby::Types
4
+ TaggedUnion = Class.new(Type) { @error = "Union type must be instantiated from one or more concrete types" }
5
+
6
+ def self.TaggedUnion(*union_types)
7
+ Class.new(FixedWidth) do
8
+ # We only accept List-like values, which have all of the required keys
9
+ # and values of the correct type
10
+ # can successfully be cast to our inner types
11
+ def self.cast!(value)
12
+ casteable_type_index = union_types.find_index do |type, _index|
13
+ next false unless type.valid_cast?(value)
14
+
15
+ type.cast!(value)
16
+ next true
17
+ rescue StandardError
18
+ nil
19
+ end
20
+ unless casteable_type_index
21
+ raise CrystalRuby::InvalidCastError,
22
+ "Cannot cast #{value}:#{value.class} to #{inspect}"
23
+ end
24
+
25
+ [casteable_type_index, value]
26
+ end
27
+
28
+ def value(native: false)
29
+ type = self.class.union_types[data_pointer.read_uint8]
30
+ type.fetch_single(data_pointer[1], native: native)
31
+ end
32
+
33
+ def nil?
34
+ value.nil?
35
+ end
36
+
37
+ def ==(other)
38
+ value == other
39
+ end
40
+
41
+ def self.copy_to!((type_index, value), memory:)
42
+ memory[data_offset].write_int8(type_index)
43
+ union_types[type_index].write_single(memory[data_offset + 1], value)
44
+ end
45
+
46
+ def data_pointer
47
+ memory[data_offset]
48
+ end
49
+
50
+ def self.each_child_address(pointer)
51
+ pointer += data_offset
52
+ type = self.union_types[pointer.read_uint8]
53
+ yield type, pointer[1]
54
+ end
55
+
56
+ def self.inner_types
57
+ union_types
58
+ end
59
+
60
+ define_singleton_method(:memsize) do
61
+ union_types.map(&:refsize).max + 1
62
+ end
63
+
64
+ def self.refsize
65
+ 8
66
+ end
67
+
68
+ def self.typename
69
+ "TaggedUnion"
70
+ end
71
+
72
+ define_singleton_method(:union_types) do
73
+ union_types
74
+ end
75
+
76
+ define_singleton_method(:valid?) do
77
+ union_types.all?(&:valid?)
78
+ end
79
+
80
+ define_singleton_method(:error) do
81
+ union_types.map(&:error).join(", ") if union_types.any?(&:error)
82
+ end
83
+
84
+ define_singleton_method(:inspect) do
85
+ if anonymous?
86
+ union_types.map(&:inspect).join(" | ")
87
+ else
88
+ crystal_class_name
89
+ end
90
+ end
91
+
92
+ define_singleton_method(:native_type_expr) do
93
+ union_types.map(&:native_type_expr).join(" | ")
94
+ end
95
+
96
+ define_singleton_method(:named_type_expr) do
97
+ union_types.map(&:named_type_expr).join(" | ")
98
+ end
99
+
100
+ define_singleton_method(:type_expr) do
101
+ anonymous? ? native_type_expr : name
102
+ end
103
+
104
+ define_singleton_method(:data_offset) do
105
+ 4
106
+ end
107
+
108
+ define_singleton_method(:valid_cast?) do |raw|
109
+ union_types.any? { |type| type.valid_cast?(raw) }
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,82 @@
1
+ class <%= base_crystal_class_name %> < CrystalRuby::Types::FixedWidth
2
+
3
+ def initialize(tuple : ::Tuple(<%= inner_types.map(&:native_type_expr).join(',') %>))
4
+ @memory = malloc(data_offset + 8)
5
+ self.value = tuple
6
+ increment_ref_count!
7
+ end
8
+
9
+ def data_pointer
10
+ Pointer(::UInt8).new((@memory + data_offset).as(Pointer(::UInt64)).value)
11
+ end
12
+
13
+ def [](index : Int)
14
+ index += size if index < 0
15
+ <% offset = 0 %>
16
+ <% inner_types.each_with_index do |type, i| %>
17
+ <% offset += type.refsize %>
18
+ return <%= type.crystal_class_name %>.fetch_single(data_pointer + <%= offset - type.refsize %>) if <%= i %> == index
19
+ <% end %>
20
+ end
21
+
22
+ <% inner_types.each_with_index.group_by{|t,i| t.native_type_expr }.each do |(native_type_expr, types_width_index)| %>
23
+ def []=(index : Int, value : <%= native_type_expr %>)
24
+ index += size if index < 0
25
+ <% types_width_index.each do |type, i| %>
26
+ return <%= type.crystal_class_name %>.write_single(data_pointer + <%= offset_for(i) %>, value) if <%= i %> == index
27
+ <% end %>
28
+ raise ArgumentError.new("Index out of bounds")
29
+ end
30
+ <% end %>
31
+
32
+ def ==(other : ::Tuple(<%= inner_types.map(&:native_type_expr).join(',') %>))
33
+ value == other
34
+ end
35
+
36
+ def value=(tuple : ::Tuple(<%= inner_types.map(&:native_type_expr).join(',') %>))
37
+ self.class.copy_to!(tuple, @memory)
38
+ end
39
+
40
+ def self.copy_to!(tuple : ::Tuple(<%= inner_types.map(&:native_type_expr).join(',') %>), memory : Pointer(::UInt8))
41
+
42
+ data_pointer = malloc(self.memsize)
43
+ address = data_pointer.address
44
+
45
+ <% inner_types.each_with_index do |type, i| %>
46
+ <%= type.crystal_class_name %>.write_single(data_pointer, tuple[<%= i %>])
47
+ data_pointer += <%= type.refsize %>
48
+ <% end %>
49
+
50
+ (memory+data_offset).as(Pointer(::UInt64)).value = address
51
+ end
52
+
53
+ def ==(other : <%= native_type_expr %>)
54
+ native == other
55
+ end
56
+
57
+ def value : ::Tuple(<%= inner_types.map(&:native_type_expr).join(',') %>)
58
+ address = data_pointer
59
+ <% inner_types.each_with_index do |type, i| %>
60
+ v<%= i %> = <%= type.crystal_class_name %>.fetch_single(address)
61
+ address += <%= type.refsize %>
62
+ <% end %>
63
+ ::Tuple.new(<%= inner_types.map.with_index { |_, i| "v#{i}" }.join(", ") %>)
64
+ end
65
+
66
+ def native : ::Tuple(<%= inner_types.map(&:native_type_expr).join(',') %>)
67
+ address = data_pointer
68
+ <% inner_types.each_with_index do |type, i| %>
69
+ v<%= i %> = <%= type.crystal_class_name %>.fetch_single(address).native
70
+ address += <%= type.refsize %>
71
+ <% end %>
72
+ ::Tuple.new(<%= inner_types.map.with_index { |_, i| "v#{i}" }.join(", ") %>)
73
+ end
74
+
75
+ def self.memsize
76
+ <%= memsize %>
77
+ end
78
+
79
+ def self.refsize
80
+ <%= refsize %>
81
+ end
82
+ end