Module: CrystalRuby::Types
- Defined in:
- lib/crystalruby/types.rb,
lib/crystalruby/types/type.rb,
lib/crystalruby/types/primitive.rb,
lib/crystalruby/types/fixed_width.rb,
lib/crystalruby/types/variable_width.rb,
lib/crystalruby/types/fixed_width/proc.rb,
lib/crystalruby/types/fixed_width/tuple.rb,
lib/crystalruby/types/concerns/allocator.rb,
lib/crystalruby/types/primitive_types/nil.rb,
lib/crystalruby/types/variable_width/hash.rb,
lib/crystalruby/types/primitive_types/bool.rb,
lib/crystalruby/types/primitive_types/time.rb,
lib/crystalruby/types/variable_width/array.rb,
lib/crystalruby/types/variable_width/string.rb,
lib/crystalruby/types/primitive_types/symbol.rb,
lib/crystalruby/types/fixed_width/named_tuple.rb,
lib/crystalruby/types/primitive_types/numbers.rb,
lib/crystalruby/types/fixed_width/tagged_union.rb
Defined Under Namespace
Modules: Allocator, Root Classes: FixedWidth, Primitive, Type, VariableWidth
Constant Summary collapse
- PROC_REGISTERY =
{}
- Proc =
FixedWidth.build(error: "Proc declarations should contain a list of 0 or more comma separated argument types,"\ "and a single return type (or Nil if it does not return a value)")
- Tuple =
FixedWidth.build( :Tuple, error: "Tuple type must contain one or more types E.g. Tuple(Int32, String)" )
- Nil =
Primitive.build(:Nil, convert_if: [::NilClass], memsize: 0) do def initialize(val = nil) super @value = 0 end def nil? true end def value(native: false) nil end end
- Hash =
VariableWidth.build(error: "Hash type must have 2 type parameters. E.g. Hash(Float64, String)")
- Bool =
Primitive.build(:Bool, convert_if: [::TrueClass, ::FalseClass], ffi_type: :uint8, memsize: 1) do def value(native: false) super == 1 end def value=(val) !!val && val != 0 ? super(1) : super(0) end end
- Time =
Primitive.build(:Time, convert_if: [Root::Time, Root::String, DateTime], ffi_type: :double) do def initialize(val = Root::Time.now) super end def value=(val) super( if val.respond_to?(:to_time) val.to_time.to_f else val.respond_to?(:to_f) ? val.to_f : 0 end ) end def value(native: false) ::Time.at(super) end end
- Array =
VariableWidth.build(error: "Array type must have a type parameter. E.g. Array(Float64)")
- String =
VariableWidth.build(:String, convert_if: [String, Root::String]) do def self.cast!(rbval) rbval.to_s end def self.copy_to!(rbval, memory:) data_pointer = malloc(rbval.bytesize) data_pointer.write_string(rbval) memory[size_offset].write_uint32(rbval.size) memory[data_offset].write_pointer(data_pointer) end def value(native: false) data_pointer.read_string(size) end end
- Symbol =
Primitive.build( error: "Symbol CrystalRuby types should indicate a list of possible values shared between Crystal and Ruby. "\ "E.g. Symbol(:green, :blue, :orange). If this list is not known at compile time, you should use a String instead." )
- NamedTuple =
FixedWidth.build(error: "NamedTuple type must contain one or more symbol -> type pairs. E.g. NamedTuple(hello: Int32, world: String)")
- TaggedUnion =
Class.new(Type) { @error = "Union type must be instantiated from one or more concrete types" }
Class Method Summary collapse
-
.Array(type) ⇒ Object
An array-like, reference counted manually managed memory type.
- .const_missing(const_name) ⇒ Object
- .Hash(key_type, value_type) ⇒ Object
- .method_missing(method_name, *args) ⇒ Object
- .NamedTuple(types_hash) ⇒ Object
- .Proc(*types) ⇒ Object
- .Symbol(*allowed_values) ⇒ Object
- .TaggedUnion(*union_types) ⇒ Object
- .Tuple(*types) ⇒ Object
- .with_binding_fallback(fallback) ⇒ Object
Class Method Details
.Array(type) ⇒ Object
An array-like, reference counted manually managed memory type. Shareable between Crystal and Crystal.
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/crystalruby/types/variable_width/array.rb', line 8 def self.Array(type) VariableWidth.build(:Array, inner_types: [type], convert_if: [Array, Root::Array], superclass: Array) do include Enumerable # Implement the Enumerable interface # Helps this object to act like an Array def each size.times { |i| yield self[i] } end # We only accept Array-like values, from which all elements # can successfully be cast to our inner type def self.cast!(value) unless value.is_a?(Array) || value.is_a?(Root::Array) && value.all?(&inner_type.method(:valid_cast?)) raise CrystalRuby::InvalidCastError, "Cannot cast #{value} to #{inspect}" end if inner_type.primitive? value.map(&inner_type.method(:to_ffi_repr)) else value end end def self.copy_to!(rbval, memory:) data_pointer = malloc(rbval.size * inner_type.refsize) memory[size_offset].write_uint32(rbval.size) memory[data_offset].write_pointer(data_pointer) if inner_type.primitive? data_pointer.send("put_array_of_#{inner_type.ffi_type}", 0, rbval) else rbval.each_with_index do |val, i| inner_type.write_single(data_pointer[i * refsize], val) end end end def self.each_child_address(pointer) size = pointer[size_offset].get_int32(0) pointer = pointer[data_offset].read_pointer size.times do |i| yield inner_type, pointer[i * inner_type.refsize] end end def checked_offset!(index, size) raise "Index out of bounds: #{index} >= #{size}" if index >= size if index < 0 raise "Index out of bounds: #{index} < -#{size}" if index < -size index += size end index end # Return the element at the given index. # This will automatically increment # the reference count if not a primitive type. def [](index) inner_type.fetch_single(data_pointer[checked_offset!(index, size) * inner_type.refsize]) end # Overwrite the element at the given index # The replaced element will have # its reference count decremented. def []=(index, value) inner_type.write_single(data_pointer[checked_offset!(index, size) * inner_type.refsize], value) end # Load values stored inside array type. # If it's a primitive type, we can quickly bulk load the values. # Otherwise we need toinstantiate new ref-checked instances. def value(native: false) inner_type.fetch_multi(data_pointer, size, native: native) end end end |
.const_missing(const_name) ⇒ Object
14 15 16 17 |
# File 'lib/crystalruby/types.rb', line 14 def self.const_missing(const_name) return @fallback.const_get(const_name) if @fallback&.const_defined?(const_name) super end |
.Hash(key_type, value_type) ⇒ Object
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/crystalruby/types/variable_width/hash.rb', line 6 def self.Hash(key_type, value_type) VariableWidth.build(:Hash, inner_types: [key_type, value_type], convert_if: [Root::Hash], superclass: Hash) do include Enumerable def_delegators :@class, :value_type, :key_type # Implement the Enumerable interface # Helps this object to act like a true Hash def each if block_given? size.times { |i| yield key_for_index(i), value_for_index(i) } else to_enum(:each) end end def keys each.map { |k, _| k } end def values each.map { |_, v| v } end def self.key_type inner_types.first end def self.value_type inner_types.last end # We only accept Hash-like values, from which all elements # can successfully be cast to our inner types def self.cast!(value) unless (value.is_a?(Hash) || value.is_a?(Root::Hash)) && value.keys.all?(&key_type.method(:valid_cast?)) && value.values.all?(&value_type.method(:valid_cast?)) raise CrystalRuby::InvalidCastError, "Cannot cast #{value} to #{inspect}" end [[key_type, value.keys], [value_type, value.values]].map do |type, values| if type.primitive? values.map(&type.method(:to_ffi_repr)) else values end end end def self.copy_to!((keys, values), memory:) data_pointer = malloc(values.size * (key_type.refsize + value_type.refsize)) memory[size_offset].write_uint32(values.size) memory[data_offset].write_pointer(data_pointer) [ [key_type, data_pointer, keys], [value_type, data_pointer[values.length * key_type.refsize], values] ].each do |type, pointer, list| if type.primitive? pointer.send("put_array_of_#{type.ffi_type}", 0, list) else list.each_with_index do |val, i| type.write_single(pointer[i * type.refsize], val) end end end end def index_for_key(key) size.times { |i| return i if key_for_index(i) == key } nil end def key_for_index(index) key_type.fetch_single(data_pointer[index * key_type.refsize]) end def value_for_index(index) value_type.fetch_single(data_pointer[key_type.refsize * size + index * value_type.refsize]) end def self.each_child_address(pointer) size = pointer[size_offset].read_int32 pointer = pointer[data_offset].read_pointer size.times do |i| yield key_type, pointer[i * key_type.refsize] yield value_type, pointer[size * key_type.refsize + i * value_type.refsize] end end def [](key) return nil unless index = index_for_key(key) value_for_index(index) end def []=(key, value) if index = index_for_key(key) value_type.write_single(data_pointer[key_type.refsize * size + index * value_type.refsize], value) else method_missing(:[]=, key, value) end end def value(native: false) keys = key_type.fetch_multi(data_pointer, size, native: native) values = value_type.fetch_multi(data_pointer[key_type.refsize * size], size, native: native) keys.zip(values).to_h end end end |
.method_missing(method_name, *args) ⇒ Object
19 20 21 22 |
# File 'lib/crystalruby/types.rb', line 19 def self.method_missing(method_name, *args) return @fallback.send(method_name, *args) if @fallback&.method_defined?(method_name) super end |
.NamedTuple(types_hash) ⇒ Object
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/crystalruby/types/fixed_width/named_tuple.rb', line 6 def self.NamedTuple(types_hash) raise "NamedTuple must be instantiated with a hash" unless types_hash.is_a?(Root::Hash) types_hash.keys.each do |key| raise "NamedTuple keys must be symbols" unless key.is_a?(Root::Symbol) || key.respond_to?(:to_sym) end keys = types_hash.keys.map(&:to_sym) value_types = types_hash.values FixedWidth.build(:NamedTuple, ffi_type: :pointer, inner_types: value_types, inner_keys: keys, convert_if: [Root::Hash]) do @data_offset = 4 # We only accept Hash-like values, which have all of the required keys # and values of the correct type # can successfully be cast to our inner types def self.cast!(value) value = value.transform_keys(&:to_sym) unless value.is_a?(Hash) || value.is_a?(Root::Hash) && inner_keys.each_with_index.all? do |k, i| value.key?(k) && inner_types[i].valid_cast?(value[k]) end raise CrystalRuby::InvalidCastError, "Cannot cast #{value} to #{inspect}" end inner_keys.map { |k| value[k] } end def self.copy_to!(values, memory:) data_pointer = malloc(memsize) memory[data_offset].write_pointer(data_pointer) inner_types.each.reduce(0) do |offset, type| type.write_single(data_pointer[offset], values.shift) offset + type.refsize end end def self.memsize inner_types.map(&:refsize).sum end def self.each_child_address(pointer) data_pointer = pointer[data_offset].read_pointer inner_types.each do |type| yield type, data_pointer data_pointer += type.refsize end end def self.offset_for(key) inner_types[0...inner_keys.index(key)].map(&:refsize).sum end def value(native: false) ptr = data_pointer inner_keys.zip(inner_types.map do |type| result = type.fetch_single(ptr, native: native) ptr += type.refsize result end).to_h end inner_keys.each.with_index do |key, index| type = inner_types[index] offset = offset_for(key) unless method_defined?(key) define_method(key) do type.fetch_single(data_pointer[offset]) end end unless method_defined?("#{key}=") define_method("#{key}=") do |value| type.write_single(data_pointer[offset], value) end end end end end |
.Proc(*types) ⇒ Object
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/crystalruby/types/fixed_width/proc.rb', line 6 def self.Proc(*types) proc_type = FixedWidth.build(:Proc, convert_if: [::Proc], inner_types: types, ffi_type: :pointer) do @data_offset = 4 def self.cast!(rbval) raise "Value must be a proc" unless rbval.is_a?(::Proc) func = FFI::Function.new(FFI::Type.const_get(inner_types[-1].ffi_type.to_s.upcase), inner_types[0...-1].map do |v| FFI::Type.const_get(v.ffi_type.to_s.upcase) end) do |*args| args = args.map.with_index do |arg, i| arg = inner_types[i].new(arg) unless arg.is_a?(inner_types[i]) inner_types[i].anonymous? ? arg.native : arg end return_val = rbval.call(*args) return_val = inner_types[-1].new(return_val) unless return_val.is_a?(inner_types[-1]) return_val.memory end PROC_REGISTERY[func.address] = func func end def self.copy_to!(rbval, memory:) memory[4].write_pointer(rbval) end def invoke(*args) invoker = value invoker.call(memory[12].read_pointer, *args) end def value(native: false) FFI::VariadicInvoker.new( memory[4].read_pointer, [FFI::Type::POINTER, *(inner_types[0...-1].map { |v| FFI::Type.const_get(v.ffi_type.to_s.upcase) })], FFI::Type.const_get(inner_types[-1].ffi_type.to_s.upcase), { ffi_convention: :stdcall } ) end def self.block_converter <<~CRYSTAL { #{ inner_types.size > 1 ? "|#{inner_types.size.-(1).times.map { |i| "v#{i}" }.join(",")}|" : "" } #{ inner_types[0...-1].map.with_index do |type, i| <<~CRYS v#{i} = #{type.crystal_class_name}.new(v#{i}).return_value callback_done_channel = Channel(Nil).new result = nil if Fiber.current == Thread.current.main_fiber block_value = #{inner_types[-1].crystal_class_name}.new(__yield_to.call(#{inner_types.size.-(1).times.map { |i| "v#{i}" }.join(",")})) result = #{inner_types[-1].anonymous? ? "block_value.native" : "block_value"} next #{inner_types.last == CrystalRuby::Types::Nil ? "result" : "result.not_nil!"} else CrystalRuby.queue_callback(->{ block_value = #{inner_types[-1].crystal_class_name}.new(__yield_to.call(#{inner_types.size.-(1).times.map { |i| "v#{i}" }.join(",")})) result = #{inner_types[-1].anonymous? ? "block_value.native" : "block_value"} callback_done_channel.send(nil) }) end callback_done_channel.receive #{inner_types.last == CrystalRuby::Types::Nil ? "result" : "result.not_nil!"} CRYS end.join("\n") } } CRYSTAL end end end |
.Symbol(*allowed_values) ⇒ Object
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
# File 'lib/crystalruby/types/primitive_types/symbol.rb', line 7 def self.Symbol(*allowed_values) raise "Symbol must have at least one value" if allowed_values.empty? allowed_values.flatten! raise "Symbol allowed values must all be symbols" unless allowed_values.all? { |v| v.is_a?(::Symbol) } Primitive.build(:Symbol, ffi_type: :uint32, convert_if: [Root::String, Root::Symbol], memsize: 4) do bind_local_vars!(%i[allowed_values], binding) define_method(:value=) do |val| val = allowed_values[val] if val.is_a?(::Integer) && val >= 0 && val < allowed_values.size raise "Symbol must be one of #{allowed_values}" unless allowed_values.include?(val) super(allowed_values.index(val)) end define_singleton_method(:valid_cast?) do |raw| super(raw) && allowed_values.include?(raw) end define_method(:value) do |native: false| allowed_values[super()] end define_singleton_method(:type_digest) do Digest::MD5.hexdigest(native_type_expr.to_s + allowed_values.map(&:to_s).join(",")) end end end |
.TaggedUnion(*union_types) ⇒ Object
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/crystalruby/types/fixed_width/tagged_union.rb', line 6 def self.TaggedUnion(*union_types) Class.new(FixedWidth) do # We only accept List-like values, which have all of the required keys # and values of the correct type # can successfully be cast to our inner types def self.cast!(value) casteable_type_index = union_types.find_index do |type, _index| next false unless type.valid_cast?(value) type.cast!(value) next true rescue StandardError nil end unless casteable_type_index raise CrystalRuby::InvalidCastError, "Cannot cast #{value}:#{value.class} to #{inspect}" end [casteable_type_index, value] end def value(native: false) type = self.class.union_types[data_pointer.read_uint8] type.fetch_single(data_pointer[1], native: native) end def nil? value.nil? end def ==(other) value == other end def self.copy_to!((type_index, value), memory:) memory[data_offset].write_int8(type_index) union_types[type_index].write_single(memory[data_offset + 1], value) end def data_pointer memory[data_offset] end def self.each_child_address(pointer) pointer += data_offset type = self.union_types[pointer.read_uint8] yield type, pointer[1] end def self.inner_types union_types end define_singleton_method(:memsize) do union_types.map(&:refsize).max + 1 end def self.refsize 8 end def self.typename "TaggedUnion" end define_singleton_method(:union_types) do union_types end define_singleton_method(:valid?) do union_types.all?(&:valid?) end define_singleton_method(:error) do union_types.map(&:error).join(", ") if union_types.any?(&:error) end define_singleton_method(:inspect) do if anonymous? union_types.map(&:inspect).join(" | ") else crystal_class_name end end define_singleton_method(:native_type_expr) do union_types.map(&:native_type_expr).join(" | ") end define_singleton_method(:type_expr) do anonymous? ? native_type_expr : name end define_singleton_method(:data_offset) do 4 end define_singleton_method(:valid_cast?) do |raw| union_types.any? { |type| type.valid_cast?(raw) } end end end |
.Tuple(*types) ⇒ Object
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/crystalruby/types/fixed_width/tuple.rb', line 9 def self.Tuple(*types) FixedWidth.build(:Tuple, inner_types: types, convert_if: [Root::Array], superclass: Tuple) do @data_offset = 4 # We only accept List-like values, which have all of the required keys # and values of the correct type # can successfully be cast to our inner types def self.cast!(value) unless (value.is_a?(Array) || value.is_a?(Tuple) || value.is_a?(Root::Array)) && value.zip(inner_types).each do |v, t| t && t.valid_cast?(v) end && value.length == inner_types.length raise CrystalRuby::InvalidCastError, "Cannot cast #{value} to #{inspect}" end value end def self.copy_to!(values, memory:) data_pointer = malloc(memsize) memory[data_offset].write_pointer(data_pointer) inner_types.each.reduce(0) do |offset, type| type.write_single(data_pointer[offset], values.shift) offset + type.refsize end end def self.each_child_address(pointer) data_pointer = pointer[data_offset].read_pointer inner_types.each do |type| yield type, data_pointer data_pointer += type.refsize end end def self.memsize inner_types.map(&:refsize).sum end def size inner_types.size end def checked_offset!(index, size) raise "Index out of bounds: #{index} >= #{size}" if index >= size if index < 0 raise "Index out of bounds: #{index} < -#{size}" if index < -size index += size end self.class.offset_for(index) end def self.offset_for(index) inner_types[0...index].map(&:refsize).sum end # Return the element at the given index. # This will automatically increment # the reference count if not a primitive type. def [](index) inner_types[index].fetch_single(data_pointer[checked_offset!(index, size)]) end # Overwrite the element at the given index # The replaced element will have # its reference count decremented. def []=(index, value) inner_types[index].write_single(data_pointer[checked_offset!(index, size)], value) end def value(native: false) ptr = data_pointer inner_types.map do |type| result = type.fetch_single(ptr, native: native) ptr += type.refsize result end end end end |
.with_binding_fallback(fallback) ⇒ Object
24 25 26 27 28 29 30 |
# File 'lib/crystalruby/types.rb', line 24 def self.with_binding_fallback(fallback) @fallback, previous_fallback = fallback, @fallback @fallback = @fallback.class unless @fallback.kind_of?(Module) yield binding ensure @fallback = previous_fallback end |