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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +2 -0
- data/Dockerfile +23 -2
- data/README.md +395 -198
- data/Rakefile +4 -3
- data/crystalruby.gemspec +2 -2
- data/examples/adder/adder.rb +1 -1
- data/exe/crystalruby +1 -0
- data/lib/crystalruby/adapter.rb +143 -73
- data/lib/crystalruby/arc_mutex.rb +47 -0
- data/lib/crystalruby/compilation.rb +32 -3
- data/lib/crystalruby/config.rb +41 -37
- data/lib/crystalruby/function.rb +216 -73
- data/lib/crystalruby/library.rb +157 -51
- data/lib/crystalruby/reactor.rb +63 -44
- data/lib/crystalruby/source_reader.rb +92 -0
- data/lib/crystalruby/template.rb +16 -5
- data/lib/crystalruby/templates/function.cr +11 -10
- data/lib/crystalruby/templates/index.cr +53 -66
- data/lib/crystalruby/templates/inline_chunk.cr +1 -1
- data/lib/crystalruby/templates/ruby_interface.cr +34 -0
- data/lib/crystalruby/templates/top_level_function.cr +62 -0
- data/lib/crystalruby/templates/top_level_ruby_interface.cr +33 -0
- data/lib/crystalruby/typebuilder.rb +11 -55
- data/lib/crystalruby/typemaps.rb +92 -67
- data/lib/crystalruby/types/concerns/allocator.rb +80 -0
- data/lib/crystalruby/types/fixed_width/named_tuple.cr +80 -0
- data/lib/crystalruby/types/fixed_width/named_tuple.rb +86 -0
- data/lib/crystalruby/types/fixed_width/proc.cr +45 -0
- data/lib/crystalruby/types/fixed_width/proc.rb +79 -0
- data/lib/crystalruby/types/fixed_width/tagged_union.cr +53 -0
- data/lib/crystalruby/types/fixed_width/tagged_union.rb +113 -0
- data/lib/crystalruby/types/fixed_width/tuple.cr +82 -0
- data/lib/crystalruby/types/fixed_width/tuple.rb +92 -0
- data/lib/crystalruby/types/fixed_width.cr +138 -0
- data/lib/crystalruby/types/fixed_width.rb +205 -0
- data/lib/crystalruby/types/primitive.cr +21 -0
- data/lib/crystalruby/types/primitive.rb +117 -0
- data/lib/crystalruby/types/primitive_types/bool.cr +34 -0
- data/lib/crystalruby/types/primitive_types/bool.rb +11 -0
- data/lib/crystalruby/types/primitive_types/nil.cr +35 -0
- data/lib/crystalruby/types/primitive_types/nil.rb +16 -0
- data/lib/crystalruby/types/primitive_types/numbers.cr +37 -0
- data/lib/crystalruby/types/primitive_types/numbers.rb +28 -0
- data/lib/crystalruby/types/primitive_types/symbol.cr +55 -0
- data/lib/crystalruby/types/primitive_types/symbol.rb +35 -0
- data/lib/crystalruby/types/primitive_types/time.cr +35 -0
- data/lib/crystalruby/types/primitive_types/time.rb +25 -0
- data/lib/crystalruby/types/type.cr +64 -0
- data/lib/crystalruby/types/type.rb +249 -30
- data/lib/crystalruby/types/variable_width/array.cr +74 -0
- data/lib/crystalruby/types/variable_width/array.rb +88 -0
- data/lib/crystalruby/types/variable_width/hash.cr +146 -0
- data/lib/crystalruby/types/variable_width/hash.rb +117 -0
- data/lib/crystalruby/types/variable_width/string.cr +36 -0
- data/lib/crystalruby/types/variable_width/string.rb +18 -0
- data/lib/crystalruby/types/variable_width.cr +23 -0
- data/lib/crystalruby/types/variable_width.rb +46 -0
- data/lib/crystalruby/types.rb +32 -13
- data/lib/crystalruby/version.rb +2 -2
- data/lib/crystalruby.rb +13 -6
- metadata +42 -22
- data/lib/crystalruby/types/array.rb +0 -15
- data/lib/crystalruby/types/bool.rb +0 -3
- data/lib/crystalruby/types/hash.rb +0 -17
- data/lib/crystalruby/types/named_tuple.rb +0 -28
- data/lib/crystalruby/types/nil.rb +0 -3
- data/lib/crystalruby/types/numbers.rb +0 -5
- data/lib/crystalruby/types/string.rb +0 -3
- data/lib/crystalruby/types/symbol.rb +0 -3
- data/lib/crystalruby/types/time.rb +0 -8
- data/lib/crystalruby/types/tuple.rb +0 -17
- data/lib/crystalruby/types/type_serializer/json.rb +0 -41
- data/lib/crystalruby/types/type_serializer.rb +0 -37
- data/lib/crystalruby/types/typedef.rb +0 -57
- data/lib/crystalruby/types/union_type.rb +0 -43
- data/lib/module.rb +0 -3
@@ -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
|