module Yoda
module Model
module Parameters
class Binder
# @return [Base]
attr_reader :parameter
# @param parameter [Base]
def initialize(parameter)
@parameter = parameter
end
# @param type [Interface]
# @param generator [Generator]
# @return [Hash{ Symbol => Interface }]
def bind(type:, generator:)
BoundResult.new(type: type, parameter: parameter, generator: generator).type_bindings
end
# @abstract
module TypeInterface
end
# @abstract
module Generator
def any_type; fail NotImplementedError; end
def proc_type; fail NotImplementedError; end
def hash_type; fail NotImplementedError; end
def array_type; fail NotImplementedError; end
end
class BoundResult
# @return [Interface]
attr_reader :type
# @return [Model::Parameters::Base]
attr_reader :parameter
# @return [Generator]
attr_reader :generator
# @param type [Interface]
# @param parameter [Model::Parameters::Multiple] parameter
# @param generator [Types::Generator]
def initialize(type:, parameter:, generator:)
@type = type
@parameter = parameter
@generator = generator
end
# @return [Hash{Symbol => Interface}]
def type_bindings
@type_bindings ||= {}
.merge!(parameter_bindings)
.merge!(keyword_parameter_bindings)
.merge!(block_parameter)
end
# @return [Hash{Symbol => Interface}]
def parameter_bindings
@parameter_bindings ||= begin
dict, parameters_remain, types_remain = bind_params_as_possible(parameter.parameters, type.parameters)
# TODO: bind with rest paremter type
dict = parameters_remain.reduce(dict) { |dict, param| dict.merge(bind_param(param, generator.any_type)) }
postdict, post_parameters_remain, post_types_remain = bind_params_as_possible(parameter.post_parameters.reverse, type.post_parameters.reverse)
dict = dict.merge(postdict)
# TODO: bind with rest paremter type
dict = post_parameters_remain.reduce(dict) { |dict, param| dict.merge(bind_param(param, generator.any_type)) }
if parameter.rest_parameter
if types_remain.empty? && post_types_remain.empty?
# TODO: bind rest things of keyword and block binding
dict = dict.merge(bind_param(parameter.rest_parameter, type.rest_parameter || generator.array_type))
else
# TODO: bind as sequence type
dict = dict.merge(bind_param(parameter.rest_parameter, generator.array_type))
end
end
dict
end
end
# @return [Hash{Symbol => Interface}]
def keyword_parameter_bindings
@keyword_parameter_bindings ||= begin
type_hash = type.keyword_parameters.to_h
dict = parameter.keyword_parameters.reduce({}) do |dict, param|
if name = (param.respond_to?(:name) && param.name)
dict.merge(name => type_hash.delete(param) || generator.any_type)
else
dict
end
end
if parameter.keyword_rest_parameter
if type_hash.empty?
dict = dict.merge(bind_param(parameter.keyword_rest_parameter, type.keyword_rest_parameter || generator.hash_type))
else
# TODO: merge
dict = dict.merge(bind_param(parameter.keyword_rest_parameter, generator.hash_type))
end
end
dict
end
end
# @return [Hash{Symbol => Interface}]
def block_parameter
@block_parameter ||= parameter.block_parameter ? bind_param(parameter.block_parameter, type.block_parameter || generator.proc_type) : {}
end
private
# @param params [Array]
# @param types [Array]
# @return [(Hash{Symbol => Interface}, Array, Array)]
def bind_params_as_possible(params, types)
min_length = [params.length, types.length].min
dict = params.take(min_length).zip(types.take(min_length)).reduce({}) do |memo, (param, type)|
memo.merge(bind_param(param, type))
end
[dict, params.drop(min_length), types.drop(min_length)]
end
# @param param [Base]
# @param type [Interface]
# @return [Hash{Symbol => Interface}]
def bind_param(param, type)
case param.kind
when :named
{ param.name => type }
when :multiple
# @todo
{}
else
{}
end
end
end
end
end
end
end