class BinData::DSLMixin::DSLParser


* params is a hash containing any parameters
* name is the (possible optional) name of the field
* type is the under_scored name of a registered type
where:
type name, params
A DSLParser parses and accumulates field definitions of the form

def append_field(type, name, params)

def append_field(type, name, params)
  fields.add_field(type, name, params)
rescue BinData::UnRegisteredTypeError => e
  raise TypeError, "unknown type '#{e.message}'"
end

def dsl_params

def dsl_params
  abilities = parser_abilities[@parser_type]
  send(abilities.at(0), abilities.at(1))
end

def dsl_raise(exception, msg)

def dsl_raise(exception, msg)
  backtrace = caller
  backtrace.shift while %r{bindata/dsl.rb}.match?(backtrace.first)
  raise exception, "#{msg} in #{@the_class}", backtrace
end

def endian(endian = nil)

def endian(endian = nil)
  if endian
    set_endian(endian)
  elsif @endian.nil?
    set_endian(parent_attribute(:endian))
  end
  @endian
end

def ensure_hints

def ensure_hints
  endian
  search_prefix
end

def fields

def fields
  @fields ||= SanitizedFields.new(hints, parent_fields)
end

def fields?

def fields?
  defined?(@fields) && !@fields.empty?
end

def hide(*args)

def hide(*args)
  if option?(:hidden_fields)
    @hide ||= parent_attribute(:hide, []).dup
    hidden = args.collect(&:to_sym).compact
    @hide.concat(hidden)
    @hide
  end
end

def hints

def hints
  { endian: endian, search_prefix: search_prefix }
end

def initialize(the_class, parser_type)

def initialize(the_class, parser_type)
  raise "unknown parser type #{parser_type}" unless parser_abilities[parser_type]
  @the_class      = the_class
  @parser_type    = parser_type
  @validator      = DSLFieldValidator.new(the_class, self)
  @endian         = nil
end

def method_missing(*args, &block)

def method_missing(*args, &block)
  ensure_hints
  parse_and_append_field(*args, &block)
end

def option?(opt)

def option?(opt)
  parser_abilities[@parser_type].at(2).include?(opt)
end

def parent_attribute(attr, default = nil)

def parent_attribute(attr, default = nil)
  parent = @the_class.superclass
  parser = parent.respond_to?(:dsl_parser) ? parent.dsl_parser : nil
  if parser&.respond_to?(attr)
    parser.send(attr)
  else
    default
  end
end

def parent_fields

def parent_fields
  parent_attribute(:fields)
end

def parse_and_append_field(*args, &block)

def parse_and_append_field(*args, &block)
  parser = DSLFieldParser.new(hints, *args, &block)
  begin
    @validator.validate_field(parser.name)
    append_field(parser.type, parser.name, parser.params)
  rescue Exception => e
    dsl_raise e.class, e.message
  end
end

def parser_abilities

def parser_abilities
  @abilities ||= {
    struct:     [:to_struct_params, :struct,      [:multiple_fields, :optional_fieldnames, :hidden_fields]],
    array:      [:to_object_params, :type,        [:multiple_fields, :optional_fieldnames]],
    buffer:     [:to_object_params, :type,        [:multiple_fields, :optional_fieldnames, :hidden_fields]],
    choice:     [:to_choice_params, :choices,     [:multiple_fields, :all_or_none_fieldnames, :fieldnames_are_values]],
    delayed_io: [:to_object_params, :type,        [:multiple_fields, :optional_fieldnames, :hidden_fields]],
    primitive:  [:to_struct_params, :struct,      [:multiple_fields, :optional_fieldnames]],
    section:    [:to_object_params, :type,        [:multiple_fields, :optional_fieldnames]],
    skip:       [:to_object_params, :until_valid, [:multiple_fields, :optional_fieldnames]]
  }
end

def search_prefix(*args)

def search_prefix(*args)
  @search_prefix ||= parent_attribute(:search_prefix, []).dup
  prefix = args.collect(&:to_sym).compact
  unless prefix.empty?
    if fields?
      dsl_raise SyntaxError, "search_prefix must be called before defining fields"
    end
    @search_prefix = prefix.concat(@search_prefix)
  end
  @search_prefix
end

def set_endian(endian)

def set_endian(endian)
  if endian
    if fields?
      dsl_raise SyntaxError, "endian must be called before defining fields"
    end
    if !valid_endian?(endian)
      dsl_raise ArgumentError, "unknown value for endian '#{endian}'"
    end
    if endian == :big_and_little
      DSLBigAndLittleEndianHandler.handle(@the_class)
    end
    @endian = endian
  end
end

def to_choice_params(key)

def to_choice_params(key)
  if fields.empty?
    {}
  elsif fields.all_field_names_blank?
    { key => fields.collect(&:prototype) }
  else
    choices = {}
    fields.each { |f| choices[f.name] = f.prototype }
    { key => choices }
  end
end

def to_object_params(key)

def to_object_params(key)
  case fields.length
  when 0
    {}
  when 1
    { key => fields[0].prototype }
  else
    { key => [:struct, to_struct_params] }
  end
end

def to_struct_params(*_)

def to_struct_params(*_)
  result = { fields: fields }
  if !endian.nil?
    result[:endian] = endian
  end
  if !search_prefix.empty?
    result[:search_prefix] = search_prefix
  end
  if option?(:hidden_fields) && !hide.empty?
    result[:hide] = hide
  end
  result
end

def valid_endian?(endian)

def valid_endian?(endian)
  [:big, :little, :big_and_little].include?(endian)
end