crystalruby 0.2.3 → 0.3.1

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