lib/gir_ffi/builders/argument_builder.rb
# frozen_string_literal: true
require "gir_ffi/builders/base_argument_builder"
require "gir_ffi/builders/closure_to_pointer_convertor"
require "gir_ffi/builders/full_c_to_ruby_convertor"
require "gir_ffi/builders/ruby_to_c_convertor"
require "gir_ffi/builders/pointer_value_convertor"
require "gir_ffi/builders/null_convertor"
module GirFFI
module Builders
# Implements building pre- and post-processing statements for arguments.
class ArgumentBuilder < BaseArgumentBuilder
def method_argument_name
name if has_input_value? && !helper_argument?
end
def block_argument?
specialized_type_tag == :callback
end
def allow_none?
arginfo.may_be_null?
end
def post_converted_name
@post_converted_name ||= if has_post_conversion?
new_variable
else
call_argument_name
end
end
def return_value_name
post_converted_name if has_output_value? && !array_length_parameter?
end
def capture_variable_name
nil
end
def pre_conversion
case direction
when :in
pre_conversion_in
when :inout
pre_conversion_inout
when :out
pre_conversion_out
when :error
pre_conversion_error
end
end
def post_conversion
if direction == :error
["GirFFI::ArgHelper.check_error(#{call_argument_name})"]
elsif has_post_conversion?
value = output_value
["#{post_converted_name} = #{value}"]
else
[]
end
end
private
def pre_conversion_in
pr = []
pr << fixed_array_size_check if needs_size_check?
pr << array_length_assignment if array_length_parameter?
pr << "#{call_argument_name} = #{ingoing_convertor.conversion}"
pr
end
def pre_conversion_inout
pr = []
pr << fixed_array_size_check if needs_size_check?
pr << array_length_assignment if array_length_parameter?
pr << out_parameter_preparation
pr << ingoing_value_storage
pr
end
def pre_conversion_out
[out_parameter_preparation]
end
def pre_conversion_error
["#{call_argument_name} = FFI::MemoryPointer.new(:pointer).write_pointer nil"]
end
def ingoing_value_storage
PointerValueConvertor.new(type_spec)
.value_to_pointer(call_argument_name, ingoing_convertor.conversion)
end
def has_post_conversion?
has_output_value? && (!caller_allocated_object? || gvalue?)
end
def output_value
return "#{call_argument_name}.get_value" if caller_allocated_object? && gvalue?
base = pointer_to_value_method_call call_argument_name, type_spec
if needs_out_conversion?
outgoing_convertor(base).conversion
elsif allocated_by_them? && specialized_type_tag != :void
pointer_to_value_method_call base, sub_type_spec
else
base
end
end
def outgoing_convertor(base)
FullCToRubyConvertor.new(type_info, base, length_argument_name,
ownership_transfer: arginfo.ownership_transfer)
end
def sub_type_spec
type_spec[1]
end
def pointer_to_value_method_call(ptr_exp, spec)
PointerValueConvertor.new(spec).pointer_to_value(ptr_exp)
end
def needs_out_conversion?
type_info.needs_c_to_ruby_conversion_for_functions?
end
def gvalue?
type_info.gvalue?
end
# Check if an out argument needs to be allocated by them, the callee. Since
# caller_allocates is false by default, we must also check that the type
# is a pointer. For example, an out parameter of type gint8* will always
# be allocated by the caller (that's us).
def allocated_by_them?
!arginfo.caller_allocates? && type_info.pointer?
end
def length_argument_name
length_arg&.post_converted_name
end
def needs_size_check?
specialized_type_tag == :c && type_info.array_fixed_size > -1
end
def fixed_array_size_check
size = type_info.array_fixed_size
"GirFFI::ArgHelper.check_fixed_array_size #{size}, #{name}, \"#{name}\""
end
def skipped_in?
arginfo.skip?
end
def skipped_out?
arginfo.skip? ||
array_arg && array_arg.specialized_type_tag == :strv
end
def has_output_value?
(direction == :inout || direction == :out) && !skipped_out?
end
def has_input_value?
has_ingoing_component? && !skipped_in?
end
def has_ingoing_component?
direction == :inout || direction == :in
end
def array_length_assignment
arrname = array_arg.name
"#{name} = #{arrname}.nil? ? 0 : #{arrname}.length"
end
def out_parameter_preparation
value = if caller_allocated_object?
if specialized_type_tag == :array
"#{argument_class_name}.new #{type_info.element_type.inspect}"
else
"#{argument_class_name}.new"
end
else
ffi_type_spec = TypeMap.type_specification_to_ffi_type type_spec
"FFI::MemoryPointer.new #{ffi_type_spec.inspect}"
end
"#{call_argument_name} = #{value}"
end
def type_spec
type_info.tag_or_class
end
def caller_allocated_object?
[:struct, :array].include?(specialized_type_tag) &&
arginfo.caller_allocates?
end
DESTROY_NOTIFIER = "GLib::DestroyNotify.default"
def ingoing_convertor
if skipped_in?
NullConvertor.new("0")
elsif destroy_notifier?
NullConvertor.new(DESTROY_NOTIFIER)
elsif user_data?
ClosureToPointerConvertor.new(callback_argument_name)
elsif needs_ruby_to_c_conversion?
RubyToCConvertor.new(type_info, pre_convertor_argument,
ownership_transfer: ownership_transfer)
else
NullConvertor.new(pre_convertor_argument)
end
end
def pre_convertor_argument
if ownership_transfer == :everything && specialized_type_tag == :object
"#{name} && #{name}.ref"
else
name
end
end
def needs_ruby_to_c_conversion?
type_info.needs_ruby_to_c_conversion_for_functions?
end
def callback_argument_name
related_callback_builder.call_argument_name
end
end
end
end