crystalruby 0.2.3 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|