module BinData
module DSLMixin
def self.included(base) #:nodoc:
base.extend ClassMethods
end
module ClassMethods
def dsl_parser(parser_type = nil)
unless defined? @dsl_parser
parser_type = superclass.dsl_parser.parser_type if parser_type.nil?
@dsl_parser = DSLParser.new(self, parser_type)
end
@dsl_parser
end
def method_missing(symbol, *args, &block) #:nodoc:
dsl_parser.__send__(symbol, *args, &block)
end
# Assert object is not an array or string.
def to_ary; nil; end
def to_str; nil; end
end
# A DSLParser parses and accumulates field definitions of the form
#
# type name, params
#
# where:
# * +type+ is the under_scored name of a registered type
# * +name+ is the (possible optional) name of the field
# * +params+ is a hash containing any parameters
#
class DSLParser
def initialize(the_class, parser_type)
@the_class = the_class
@parser_type = parser_type
@endian = parent_attribute(:endian, nil)
end
attr_reader :parser_type
def endian(endian = nil)
if endian.nil?
@endian
elsif endian == :big or endian == :little
@endian = endian
else
dsl_raise ArgumentError, "unknown value for endian '#{endian}'"
end
end
def hide(*args)
if option?(:hidden_fields)
hidden = args.collect do |name|
unless Symbol === name
warn "Hidden field '#{name}' should be provided as a symbol. Using strings is deprecated"
end
name.to_sym
end
unless defined? @hide
@hide = parent_attribute(:hide, []).dup
end
@hide.concat(hidden.compact)
@hide
end
end
def fields
unless defined? @fields
fields = parent_attribute(:fields, nil)
klass = option?(:sanitize_fields) ? SanitizedFields : UnSanitizedFields
@fields = klass.new(endian)
@fields.copy_fields(fields) if fields
end
@fields
end
def dsl_params
case @parser_type
when :struct
to_struct_params
when :array
to_array_params
when :choice
to_choice_params
when :primitive
to_struct_params
when :wrapper
raise "Wrapper is deprecated"
else
raise "unknown parser type #{@parser_type}"
end
end
def method_missing(symbol, *args, &block) #:nodoc:
type = symbol
name = name_from_field_declaration(args)
params = params_from_field_declaration(type, args, &block)
append_field(type, name, params)
end
#-------------
private
def option?(opt)
options.include?(opt)
end
def options
case @parser_type
when :struct
[:multiple_fields, :optional_fieldnames, :sanitize_fields, :hidden_fields]
when :array
[:multiple_fields, :optional_fieldnames, :sanitize_fields]
when :choice
[:multiple_fields, :all_or_none_fieldnames, :sanitize_fields, :fieldnames_are_values]
when :primitive
[:multiple_fields, :optional_fieldnames, :sanitize_fields]
when :wrapper
[:only_one_field, :no_fieldnames]
else
raise "unknown parser type #{parser_type}"
end
end
def parent_attribute(attr, default = nil)
parent = @the_class.superclass.respond_to?(:dsl_parser) ? @the_class.superclass.dsl_parser : nil
if parent and parent.respond_to?(attr)
parent.send(attr)
else
default
end
end
def name_from_field_declaration(args)
name, params = args
if name == "" or name.is_a?(Hash)
nil
else
name
end
end
def params_from_field_declaration(type, args, &block)
params = params_from_args(args)
if block_given?
params.merge(params_from_block(type, &block))
else
params
end
end
def params_from_args(args)
name, params = args
params = name if name.is_a?(Hash)
params || {}
end
def params_from_block(type, &block)
bindata_classes = {
:array => BinData::Array,
:choice => BinData::Choice,
:struct => BinData::Struct
}
if bindata_classes.include?(type)
parser = DSLParser.new(bindata_classes[type], type)
parser.endian(endian)
parser.instance_eval(&block)
parser.dsl_params
else
{}
end
end
def append_field(type, name, params)
ensure_valid_field(name)
fields.add_field(type, name, params)
rescue ArgumentError => err
dsl_raise ArgumentError, err.message
rescue UnRegisteredTypeError => err
dsl_raise TypeError, "unknown type '#{err.message}'"
end
def ensure_valid_field(field_name)
if too_many_fields?
dsl_raise SyntaxError, "attempting to wrap more than one type"
end
if must_not_have_a_name_failed?(field_name)
dsl_raise SyntaxError, "field must not have a name"
end
if all_or_none_names_failed?(field_name)
dsl_raise SyntaxError, "fields must either all have names, or none must have names"
end
if must_have_a_name_failed?(field_name)
dsl_raise SyntaxError, "field must have a name"
end
ensure_valid_name(field_name)
end
def ensure_valid_name(name)
if name and not option?(:fieldnames_are_values)
if malformed_name?(name)
dsl_raise NameError.new("", name), "field '#{name}' is an illegal fieldname"
end
if duplicate_name?(name)
dsl_raise SyntaxError, "duplicate field '#{name}'"
end
if name_shadows_method?(name)
dsl_raise NameError.new("", name), "field '#{name}' shadows an existing method"
end
if name_is_reserved?(name)
dsl_raise NameError.new("", name), "field '#{name}' is a reserved name"
end
end
end
def too_many_fields?
option?(:only_one_field) and not fields.empty?
end
def must_not_have_a_name_failed?(name)
option?(:no_fieldnames) and name != nil
end
def must_have_a_name_failed?(name)
option?(:mandatory_fieldnames) and name.nil?
end
def all_or_none_names_failed?(name)
if option?(:all_or_none_fieldnames) and not fields.empty?
all_names_blank = fields.all_field_names_blank?
no_names_blank = fields.no_field_names_blank?
(name != nil and all_names_blank) or (name == nil and no_names_blank)
else
false
end
end
def malformed_name?(name)
/^[a-z_]\w*$/ !~ name.to_s
end
def duplicate_name?(name)
fields.has_field_name?(name)
end
def name_shadows_method?(name)
@the_class.method_defined?(name)
end
def name_is_reserved?(name)
BinData::Struct::RESERVED.include?(name.to_sym)
end
def dsl_raise(exception, message)
backtrace = caller
backtrace.shift while %r{bindata/dsl.rb} =~ backtrace.first
raise exception, message + " in #{@the_class}", backtrace
end
def to_array_params
case fields.length
when 0
{}
when 1
{:type => fields[0].prototype}
else
{:type => [:struct, to_struct_params]}
end
end
def to_choice_params
if fields.length == 0
{}
elsif fields.all_field_names_blank?
{:choices => fields.collect { |f| f.prototype }}
else
choices = {}
fields.each { |f| choices[f.name] = f.prototype }
{:choices => choices}
end
end
def to_struct_params
result = {:fields => fields}
if not endian.nil?
result[:endian] = endian
end
if option?(:hidden_fields) and not hide.empty?
result[:hide] = hide
end
result
end
end
# An array containing a field definition of the form
# expected by BinData::Struct.
class UnSanitizedField < ::Array
def initialize(type, name, params)
super()
self << type << name << params
end
def type
self[0]
end
def name
self[1]
end
def params
self[2]
end
end
class UnSanitizedFields < ::Array
def initialize(endian)
@endian = endian
end
def add_field(type, name, params)
normalized_endian = @endian.respond_to?(:endian) ? @endian.endian : @endian
normalized_type = RegisteredClasses.normalize_name(type, normalized_endian)
self << UnSanitizedField.new(normalized_type, name, params)
end
def copy_fields(other)
concat(other)
end
end
end
end