# Copyright (c) 2023 M.J.N. Corino, The Netherlands # # This software is released under the MIT license. ### # wxRuby3 typemap Parameter class ### module WXRuby3 module Typemap # represents a basic parameter definition class ParameterBase PTR_RE = /\A([\*&]+)\s*([_a-zA-Z]\w*)?\Z/ CONST_RE = /(\A|\W)const(\W|\Z)/ MULTI_WORD_TYPES = %w[char short int long] def initialize(param) # prevent unwanted splitting on whitespaces in template type decls param = param.gsub(/\<\s+/, '<') param.gsub!(/\s+\>/, '>') @array = false if ::Array === param @ctype, @name, arr = param @array = (arr == '[]' || arr == true) if ::String === @name && @name.end_with?('[]') @array = true @name = @name[0..-3] end else # split the param spec on spaces list = param.to_s.split(' ') # name (if provided) is expected at end @name = list.pop if @name == '[]' || @name.end_with?('[]') # name or array indicator (or both)? @array = true # array type specificied # remove indicator from name or pop actual name id from split list @name = (@name == '[]' ? list.pop : @name[0..-3]) end @ctype = list.join(' ') # put type string back together # strip all 'const' modifiers tmp = @ctype.gsub(CONST_RE) { |s| "#{$1}#{$2 ? $2.strip : $2}" } # check if there is still an identifier left if /\w+/ =~ tmp # is this really a name or part of multiword type? if MULTI_WORD_TYPES.include?(@name) @ctype << ' ' << @name @name = nil # nameless else # does the name still contain a pointer/reference indicator? PTR_RE.match(@name) do |md| @ctype << ' ' << md[1] # attach to type @name = md[2].to_s.strip # remove from name end end else # @name is actually the type identifier and this parameter spec is nameless @ctype << (@ctype.empty? ? '' : ' ') << @name # concatenate type @name = nil end end end attr_reader :ctype def array? @array end def name @name.to_s end def to_s "#{ctype}#{name.empty? ? '' : ' '+name}#{array? ? '[]' : ''}" end end # ParameterBase # encapsulates a type mapping argument mask class ParameterMask < ParameterBase def initialize(mask_def) super @type_mask = ctype.tr(' ', '') # remove all spaces end attr_reader :type_mask def match(paramdef) if (name.empty? || name == paramdef.name) && (array? == paramdef.array) # compare unmodified types return true if type_mask == paramdef.type.tr(' ', '') # reduce const modifiers and compare param_type = paramdef.type.dup while param_type.sub!(CONST_RE) { |s| "#{s.start_with?('c') ? '' : s[0]}#{s[-1]}" } return true if type_mask == param_type.tr(' ', '') end end false end private :match def ==(param) case param when ParameterMask type_mask == param.type_mask && name == param.name && array? == param.array? when ArgumentDecl param == self when Extractor::ParamDef match(param) else ArgumentDecl.new(param.to_s) == self end end end # ParameterMask class ParameterSet def initialize(paramlist) @param_masks = if ::Array === paramlist paramlist.collect { |param| ParameterMask.new(param) } else paramlist.split(',').collect { |param| ParameterMask.new(param) } end end attr_reader :param_masks def ==(params) case params when ParameterSet @param_masks == params.param_masks when Extractor::FunctionDef match(params) else @param_masks == ParameterSet.new(params.to_s).param_masks end end def match(funcdef) STDERR.puts "*** matching #{self} to #{funcdef.signature}" if Director.trace? # see if the first parameter mask matches anywhere in the function's argument list if fpix = (0...funcdef.parameters.size).to_a.detect { |pix| @param_masks.first == funcdef.parameters[pix] } STDERR.puts "*** match found at argument #{fpix}" if Director.trace? # if this is the only param mask we're done return true if @param_masks.size == 1 # are there enough arguments to match all masks from the position of the argument we matched first? if (funcdef.parameters.size - fpix) >= @param_masks.size # do the remainder of the parameter masks (if any) all match as well? fpix += 1 # start matching at the next function arg @param_masks[1,@param_masks.size-1].each_with_index do |pm, pix| return false unless pm == funcdef.parameters[fpix+pix] end return true # fully matched end end false end private :match def to_s "(#{@param_masks.join(', ')})" end def inspect to_s end end # encapsulates a method argument declaration class ArgumentDecl < ParameterBase def initialize(argdecl) super # put together a list of possible argument type strings for matching @type_list = [ (ctype_str = ctype.dup).tr(' ', '') ] # first is the unaltered type # next remove const modifiers from front to back (adding type strings for every reduction) while ctype_str.sub!(CONST_RE) { |s| "#{s.start_with?('c') ? '' : s[0]}#{s[-1]}" } @type_list << ctype_str.tr(' ', '') end end def ==(param_mask) unless ParameterMask === param_mask param_mask = ParameterMask.new(param_mask) end if array? == param_mask.array? if @type_list.any? {|typ| typ == param_mask.type_mask } return param_mask.name.nil? || param_mask.name == name end end false end end # ArgumentDecl class ArgumentList def initialize(arglist) @args = if ::Array === arglist arglist.collect { |arg| ArgumentDecl.new(arg) } else arglist.split(',').collect { |arg| ArgumentDecl.new(arg) } end end # match with parameter mask list and return index of first argument matching or nil def match(masks) masks = ParameterSet === masks ? masks.param_masks : [masks].flatten # see if this parameter mask list matches (a part of) this argument list if arg_ix = @args.find_index { |arg| arg == masks.first } # does the head mask match any argument? # anything left to match? return arg_ix unless masks.size>1 # see if the following arguments match the rest of the masks if (@args.size - arg_ix) >= masks.size # matching numbers? return arg_ix if (1...masks.size).to_a.all? { |i| @args[arg_ix+i] == masks[i] } end end nil end def to_s @args.join(', ') end def inspect to_s end end end end