class Wrapture::ClassSpec

A description of a class, including its constants, functions, and other details.

Attributes

struct[R]

The underlying struct of this class.

Public Class Methods

effective_type(spec) click to toggle source

Gives the effective type of the given class spec hash.

# File lib/wrapture/class_spec.rb, line 54
def self.effective_type(spec)
  inferred_pointer_wrapper = spec['constructors'].any? do |func|
    func['wrapped-function']['return']['type'] == EQUIVALENT_POINTER_KEYWORD
  end

  if spec.key?('type')
    valid_types = %w[pointer struct]
    unless valid_types.include?(spec['type'])
      type_message = "#{spec['type']} is not a valid class type"
      raise InvalidSpecKey.new(type_message, valid_keys: valid_types)
    end

    spec['type']
  elsif inferred_pointer_wrapper
    'pointer'
  else
    'struct'
  end
end
new(spec, scope: Scope.new) click to toggle source

Creates a class spec based on the provided hash spec.

The scope can be provided if available.

The hash must have the following keys:

name

the name of the class

namespace

the namespace to put the class into

equivalent-struct

a hash describing the struct this class wraps

The following keys are optional:

doc

a string containing the documentation for this class

constructors

a list of function specs that can create this class

destructor

a function spec for the destructor of the class

functions

a list of function specs

constants

a list of constant specs

# File lib/wrapture/class_spec.rb, line 92
def initialize(spec, scope: Scope.new)
  @spec = Marshal.load(Marshal.dump(spec))
  TemplateSpec.replace_all_uses(@spec, *scope.templates)

  @spec = ClassSpec.normalize_spec_hash(@spec)

  @struct = if @spec.key?(EQUIVALENT_STRUCT_KEYWORD)
              StructSpec.new(@spec[EQUIVALENT_STRUCT_KEYWORD])
            end

  @functions = @spec['constructors'].map do |constructor_spec|
    full_spec = constructor_spec.dup
    full_spec['name'] = @spec['name']
    full_spec['params'] = constructor_spec['wrapped-function']['params']

    FunctionSpec.new(full_spec, self, constructor: true)
  end

  if @spec.key?('destructor')
    destructor_spec = @spec['destructor'].dup
    destructor_spec['name'] = "~#{@spec['name']}"

    @functions << FunctionSpec.new(destructor_spec, self, destructor: true)
  end

  @spec['functions'].each do |function_spec|
    @functions << FunctionSpec.new(function_spec, self)
  end

  @constants = @spec['constants'].map do |constant_spec|
    ConstantSpec.new(constant_spec)
  end

  @doc = @spec.key?('doc') ? Comment.new(@spec['doc']) : nil

  scope << self
  @scope = scope
end
normalize_spec_hash(spec) click to toggle source

Normalizes a hash specification of a class. Normalization will check for things like invalid keys, duplicate entries in include lists, and will set missing keys to their default values (for example, an empty list if no includes are given).

If this spec cannot be normalized, for example because it is invalid or it uses an unsupported version type, then an exception is raised.

# File lib/wrapture/class_spec.rb, line 32
def self.normalize_spec_hash(spec)
  raise NoNamespace unless spec.key?('namespace')
  raise MissingSpecKey, 'name key is required' unless spec.key?('name')

  Comment.validate_doc(spec['doc']) if spec.key?('doc')

  normalized = spec.dup
  normalized.default = []

  normalized['version'] = Wrapture.spec_version(spec)
  normalized['includes'] = Wrapture.normalize_includes(spec['includes'])
  normalized['type'] = ClassSpec.effective_type(normalized)

  if spec.key?('parent')
    includes = Wrapture.normalize_includes(spec['parent']['includes'])
    normalized['parent']['includes'] = includes
  end

  normalized
end

Public Instance Methods

cast(instance, to, from = name) click to toggle source

Returns a cast of an instance of this class with the provided name to the specified type. Optionally the from parameter may hold the type of the instance, either a reference or a pointer.

# File lib/wrapture/class_spec.rb, line 134
def cast(instance, to, from = name)
  member_access = from.pointer? ? '->' : '.'

  struct = "struct #{@struct.name}"

  if [EQUIVALENT_STRUCT_KEYWORD, struct].include?(to)
    "#{'*' if pointer_wrapper?}#{instance}#{member_access}equivalent"
  elsif [EQUIVALENT_POINTER_KEYWORD, "#{struct} *"].include?(to)
    "#{'&' unless pointer_wrapper?}#{instance}#{member_access}equivalent"
  end
end
generate_wrappers() click to toggle source

Generates the wrapper class declaration and definition files.

# File lib/wrapture/class_spec.rb, line 147
def generate_wrappers
  [generate_declaration_file, generate_definition_file]
end
name() click to toggle source

The name of the class

# File lib/wrapture/class_spec.rb, line 152
def name
  @spec['name']
end
overloads?(parent_spec) click to toggle source

True if this class overloads the given one. A class is considered an overload if its parent is the given class, it has the same equivalent struct name, and the equivalent struct has a set of rules. The overloaded class cannot have any rules in its equivalent struct, or it will not be overloaded.

# File lib/wrapture/class_spec.rb, line 161
def overloads?(parent_spec)
  return false unless parent_spec.struct&.rules&.empty?

  parent_spec.struct.name == struct_name &&
    parent_spec.name == parent_name &&
    !@struct.rules.empty?
end
parent_name() click to toggle source

The name of the parent of this class, or nil if there is no parent.

# File lib/wrapture/class_spec.rb, line 170
def parent_name
  @spec['parent']['name'] if child?
end
pointer_wrapper?() click to toggle source

Determines if this class is a wrapper for a struct pointer or not.

# File lib/wrapture/class_spec.rb, line 175
def pointer_wrapper?
  @spec['type'] == 'pointer'
end
struct_name() click to toggle source

The name of the equivalent struct of this class.

# File lib/wrapture/class_spec.rb, line 180
def struct_name
  @struct.name
end
this_struct() click to toggle source

Gives a code snippet that accesses the equivalent struct from within the class using the 'this' keyword.

# File lib/wrapture/class_spec.rb, line 186
def this_struct
  if pointer_wrapper?
    '*(this->equivalent)'
  else
    'this->equivalent'
  end
end
this_struct_pointer() click to toggle source

Gives a code snippet that accesses the equivalent struct pointer from within the class using the 'this' keyword.

# File lib/wrapture/class_spec.rb, line 196
def this_struct_pointer
  "#{'&' unless pointer_wrapper?}this->equivalent"
end
type(type) click to toggle source

Returns the ClassSpec for the given type in this class's scope.

# File lib/wrapture/class_spec.rb, line 201
def type(type)
  @scope.type(type)
end
type?(type) click to toggle source

Returns true if the given type exists in this class's scope.

# File lib/wrapture/class_spec.rb, line 206
def type?(type)
  @scope.type?(type)
end

Private Instance Methods

child?() click to toggle source

True if the class has a parent.

# File lib/wrapture/class_spec.rb, line 213
def child?
  @spec.key?('parent')
end
declaration_contents() { |"#ifndef #{header_guard}"| ... } click to toggle source

Gives the content of the class declaration to a block, line by line.

# File lib/wrapture/class_spec.rb, line 218
def declaration_contents
  yield "#ifndef #{header_guard}"
  yield "#define #{header_guard}"

  yield unless declaration_includes.empty?
  declaration_includes.each do |include_file|
    yield "#include <#{include_file}>"
  end

  yield
  yield "namespace #{@spec['namespace']} {"
  yield

  documentation { |line| yield "  #{line}" }
  parent = if child?
             ": public #{parent_name} "
           else
             ''
           end
  yield "  class #{@spec['name']} #{parent}{"

  yield '  public:'

  yield unless @constants.empty?
  @constants.each do |const|
    const.declaration { |line| yield "    #{line}" }
  end

  yield
  equivalent_member_declaration { |line| yield "    #{line}" }
  yield

  member_constructor_declaration { |line| yield "    #{line}" }

  pointer_constructor_declaration { |line| yield "    #{line}" }

  unless !@struct || pointer_wrapper?
    yield "    #{struct_constructor_signature};"
  end

  overload_declaration { |line| yield "    #{line}" }

  @functions.each do |func|
    func.declaration { |line| yield "    #{line}" }
  end

  yield '  };' # end of class
  yield
  yield '}' # end of namespace
  yield
  yield '#endif' # end of header guard
end
declaration_includes() click to toggle source

A list of includes needed for the declaration of the class.

# File lib/wrapture/class_spec.rb, line 272
def declaration_includes
  includes = @spec['includes'].dup

  includes.concat(@struct.includes) if @struct

  @functions.each do |func|
    includes.concat(func.declaration_includes)
  end

  @constants.each do |const|
    includes.concat(const.declaration_includes)
  end

  includes.concat(@spec['parent']['includes']) if child?

  includes.uniq
end
definition_contents() { |"#include <#{include_file}>"| ... } click to toggle source

Gives the content of the class definition to a block, line by line.

# File lib/wrapture/class_spec.rb, line 291
def definition_contents
  definition_includes.each do |include_file|
    yield "#include <#{include_file}>"
  end

  yield
  yield "namespace #{@spec['namespace']} {"

  yield unless @constants.empty?
  @constants.each do |const|
    yield "  #{const.definition(@spec['name'])};"
  end

  member_constructor_definition { |line| yield "  #{line}" }

  pointer_constructor_definition { |line| yield "  #{line}" }

  unless pointer_wrapper? || !@struct
    yield
    yield "  #{@spec['name']}::#{struct_constructor_signature} {"

    @struct.members.each do |member|
      member_decl = this_member(member['name'])
      yield "    #{member_decl} = equivalent.#{member['name']};"
    end

    yield '  }'
  end

  overload_definition { |line| yield "  #{line}" }

  @functions.each do |func|
    yield

    func.definition do |def_line|
      yield "  #{def_line}"
    end
  end

  yield
  yield '}' # end of namespace
end
definition_includes() click to toggle source

A list of includes needed for the definition of the class.

# File lib/wrapture/class_spec.rb, line 335
def definition_includes
  includes = ["#{@spec['name']}.hpp"]

  includes.concat(@spec['includes'])

  @functions.each do |func|
    includes.concat(func.definition_includes)
  end

  @constants.each do |const|
    includes.concat(const.definition_includes)
  end

  includes.concat(overload_definition_includes)

  includes.uniq
end
documentation(&block) click to toggle source

Calls the given block for each line of the class documentation.

# File lib/wrapture/class_spec.rb, line 354
def documentation(&block)
  @doc&.format_as_doxygen(max_line_length: 78) { |line| block.call(line) }
end
equivalent_member_declaration() { |"#{declaration};"| ... } click to toggle source

Yields the declaration of the equivalent member if this class has one.

A class might not have an equivalent member if it is able to use the parent class's, for example if the child class wraps the same struct.

# File lib/wrapture/class_spec.rb, line 362
def equivalent_member_declaration
  return unless @struct

  if child?
    parent_spec = @scope.type(TypeSpec.new(parent_name))
    member_reusable = !parent_spec.nil? &&
                      parent_spec.struct_name == @struct.name &&
                      parent_spec.pointer_wrapper? == pointer_wrapper?
    return if member_reusable
  end

  yield "#{@struct.declaration(equivalent_name)};"
end
equivalent_name() click to toggle source

Gives the name of the equivalent struct.

# File lib/wrapture/class_spec.rb, line 377
def equivalent_name
  "#{'*' if pointer_wrapper?}equivalent"
end
generate_declaration_file() click to toggle source

Generates the declaration of the class.

# File lib/wrapture/class_spec.rb, line 382
def generate_declaration_file
  filename = "#{@spec['name']}.hpp"

  File.open(filename, 'w') do |file|
    declaration_contents do |line|
      file.puts(line)
    end
  end

  filename
end
generate_definition_file() click to toggle source

Generates the definition of the class.

# File lib/wrapture/class_spec.rb, line 395
def generate_definition_file
  filename = "#{@spec['name']}.cpp"

  File.open(filename, 'w') do |file|
    definition_contents do |line|
      file.puts(line)
    end
  end

  filename
end
header_guard() click to toggle source

The header guard for the class.

# File lib/wrapture/class_spec.rb, line 408
def header_guard
  "__#{@spec['name'].upcase}_HPP"
end
member_constructor_declaration() { |"#{spec}( #{member_list_with_defaults} );"| ... } click to toggle source

Yields the declaration of the member constructor for a class. This will be empty if the wrapped struct is a pointer wrapper.

# File lib/wrapture/class_spec.rb, line 414
def member_constructor_declaration
  return unless @struct&.members?

  yield "#{@spec['name']}( #{@struct.member_list_with_defaults} );"
end
member_constructor_definition() { |"#{spec}::#{spec}( #{member_list} ) {"| ... } click to toggle source

Yields the definition of the member constructor for a class. This will be empty if the wrapped struct is a pointer wrapper.

# File lib/wrapture/class_spec.rb, line 422
def member_constructor_definition
  return unless @struct&.members?

  yield "#{@spec['name']}::#{@spec['name']}( #{@struct.member_list} ) {"

  @struct.members.each do |member|
    member_decl = this_member(member['name'])
    yield "  #{member_decl} = #{member['name']};"
  end

  yield '}'
end
overload_declaration() { |"static #{name} *new#{name}( struct #{name} *equivalent );"| ... } click to toggle source

Yields the declaration of the overload function for this class. If there is no overload function for this class, then nothing is yielded.

# File lib/wrapture/class_spec.rb, line 437
def overload_declaration
  return unless @scope.overloads?(self)

  yield "static #{name} *new#{name}( struct #{@struct.name} *equivalent );"
end
overload_definition() { || ... } click to toggle source

Yields each line of the definition of the overload function, with a leading empty yield. If there is no overload function for this class, then nothing is yielded.

# File lib/wrapture/class_spec.rb, line 446
def overload_definition
  return unless @scope.overloads?(self)

  yield

  parameter = "struct #{@struct.name} *equivalent"
  yield "#{name} *#{name}::new#{name}( #{parameter} ) {"

  line_prefix = '  '
  @scope.overloads(self).each do |overload|
    check = overload.struct.rules_check('equivalent')
    yield "#{line_prefix}if( #{check} ) {"
    yield "    return new #{overload.name}( equivalent );"
    line_prefix = '  } else '
  end

  yield "#{line_prefix}{"
  yield "    return new #{name}( equivalent );"
  yield '  }'
  yield '}'
end
overload_definition_includes() click to toggle source

A list of the includes needed for the overload definitions.

# File lib/wrapture/class_spec.rb, line 469
def overload_definition_includes
  @scope.overloads(self).map { |overload| "#{overload.name}.hpp" }
end
parent_provides_initializer?() click to toggle source

The initializer for the pointer constructor, if one is available, or an empty string if not.

# File lib/wrapture/class_spec.rb, line 475
def parent_provides_initializer?
  return false if !pointer_wrapper? || !child?

  parent_spec = @scope.type(TypeSpec.new(parent_name))

  !parent_spec.nil? &&
    parent_spec.pointer_wrapper? &&
    parent_spec.struct_name == @struct.name
end
pointer_constructor_declaration() { |"#{pointer_constructor_signature};"| ... } click to toggle source

Yields the declaration of the pointer constructor for a class.

If this class does not have an equivalent struct, or if there is already a constructor defined with this signature, then this function will return with no output.

# File lib/wrapture/class_spec.rb, line 490
def pointer_constructor_declaration
  return unless @struct

  signature_prefix = "#{@spec['name']}( #{@struct.pointer_declaration('')}"
  return if @functions.any? do |func|
    func.constructor? && func.signature.start_with?(signature_prefix)
  end

  yield "#{pointer_constructor_signature};"
end
pointer_constructor_definition() { |"#{spec}::#{pointer_constructor_signature} #{initializer}{"| ... } click to toggle source

Yields the definition of the pointer constructor for a class.

If this class has no equivalent struct, or if there is already a constructor provided with this signature, then this function will return with no output.

If this is a pointer wrapper class, then the constructor will simply set the underlying pointer to the provided one, and return the new object.

If this is a struct wrapper class, then a constructor will be created that sets each member of the wrapped struct to the provided value.

# File lib/wrapture/class_spec.rb, line 512
def pointer_constructor_definition
  return unless @struct

  signature_prefix = "#{@spec['name']}( #{@struct.pointer_declaration('')}"
  return if @functions.any? do |func|
    func.constructor? && func.signature.start_with?(signature_prefix)
  end

  initializer = pointer_constructor_initializer
  yield "#{@spec['name']}::#{pointer_constructor_signature} #{initializer}{"

  if pointer_wrapper?
    yield '  this->equivalent = equivalent;'
  else
    @struct.members.each do |member|
      member_decl = this_member(member['name'])
      yield "  #{member_decl} = equivalent->#{member['name']};"
    end
  end

  yield '}'
end
pointer_constructor_initializer() click to toggle source

The initializer for the pointer constructor, if one is available, or an empty string if not.

# File lib/wrapture/class_spec.rb, line 537
def pointer_constructor_initializer
  if parent_provides_initializer?
    ": #{parent_name}( equivalent ) "
  else
    ''
  end
end
pointer_constructor_signature() click to toggle source

The signature of the constructor given an equivalent strucct pointer.

# File lib/wrapture/class_spec.rb, line 546
def pointer_constructor_signature
  "#{@spec['name']}( #{@struct.pointer_declaration 'equivalent'} )"
end
struct_constructor_signature() click to toggle source

The signature of the constructor given an equivalent struct type.

# File lib/wrapture/class_spec.rb, line 551
def struct_constructor_signature
  "#{@spec['name']}( #{@struct.declaration 'equivalent'} )"
end
this_member(member) click to toggle source

Gives a code snippet that accesses a member of the equivalent struct for this class within the class using the 'this' keyword.

# File lib/wrapture/class_spec.rb, line 557
def this_member(member)
  "this->equivalent#{pointer_wrapper? ? '->' : '.'}#{member}"
end