lib/bindata/dsl.rb in bindata-1.3.1 vs lib/bindata/dsl.rb in bindata-1.4.0
- old
+ new
@@ -3,24 +3,17 @@
def self.included(base) #:nodoc:
base.extend ClassMethods
end
module ClassMethods
- # Defines the DSL Parser for this BinData object. Allowed +args+ are:
- #
- # [<tt>:only_one_field</tt>] Only one field may be declared.
- # [<tt>:multiple_fields</tt>] Multiple fields may be declared.
- # [<tt>:hidden_fields</tt>] Hidden fields are allowed.
- # [<tt>:sanitize_fields</tt>] Fields are to be sanitized.
- # [<tt>:mandatory_fieldnames</tt>] Fieldnames are mandatory.
- # [<tt>:optional_fieldnames</tt>] Fieldnames are optional.
- # [<tt>:fieldnames_for_choices</tt>] Fieldnames are choice keys.
- # [<tt>:no_fieldnames</tt>] Fieldnames are prohibited.
- # [<tt>:all_or_none_fieldnames</tt>] All fields must have names, or
- # none may have names.
- def dsl_parser(*args)
- @dsl_parser ||= DSLParser.new(self, *args)
+
+ 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
@@ -28,82 +21,33 @@
# Assert object is not an array or string.
def to_ary; nil; end
def to_str; nil; 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
- def to_type_params
- [self.type, self.params]
- end
- end
-
- class UnSanitizedFields < ::Array
- def field_names
- collect { |f| f.name }
- end
-
- def add_field(type, name, params, endian)
- 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
- 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, *options)
- @the_class = the_class
-
- @options = parent_options_plus_these(options)
-
- @endian = parent_attribute(:endian, nil)
-
- if option?(:hidden_fields)
- @hide = parent_attribute(:hide, []).dup
- end
-
- if option?(:sanitize_fields)
- fields = parent_attribute(:fields, nil)
- @fields = Sanitizer.new.create_sanitized_fields(fields)
- else
- fields = parent_attribute(:fields, UnSanitizedFields.new)
- @fields = fields.dup
- end
+ def initialize(the_class, parser_type)
+ @the_class = the_class
+ @parser_type = parser_type
+ @endian = parent_attribute(:endian, nil)
end
- attr_reader :options
+ attr_reader :parser_type
def endian(endian = nil)
if endian.nil?
@endian
- elsif endian.respond_to? :endian
+ elsif endian == :big or endian == :little
@endian = endian
- elsif [:little, :big].include?(endian)
- @endian = Sanitizer.new.create_sanitized_endian(endian)
else
dsl_raise ArgumentError, "unknown value for endian '#{endian}'"
end
end
@@ -113,33 +57,46 @@
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 field
- @fields[0]
- end
-
- def to_struct_params
- result = {:fields => fields}
- if not endian.nil?
- result[:endian] = endian
+ 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
- if option?(:hidden_fields) and not hide.empty?
- result[:hide] = hide
- end
-
- result
end
def method_missing(symbol, *args, &block) #:nodoc:
type = symbol
name = name_from_field_declaration(args)
@@ -149,19 +106,29 @@
end
#-------------
private
- def dsl_raise(exception, message)
- backtrace = caller
- backtrace.shift while %r{bindata/dsl.rb} =~ backtrace.first
-
- raise exception, message + " in #{@the_class}", backtrace
+ def option?(opt)
+ options.include?(opt)
end
- def option?(opt)
- @options.include?(opt)
+ 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)
@@ -169,44 +136,24 @@
else
default
end
end
- def parent_options_plus_these(options)
- result = parent_attribute(:options, []).dup
-
- mutexes = [
- [:only_one_field, :multiple_fields],
- [:mandatory_fieldnames, :optional_fieldnames, :no_fieldnames, :all_or_none_fieldnames]
- ]
-
- options.each do |opt|
- mutexes.each do |mutex|
- if mutex.include?(opt)
- result -= mutex
- end
- end
-
- result << opt
- end
-
- result
- end
-
def name_from_field_declaration(args)
name, params = args
- name = "" if name.nil? or name.is_a?(Hash)
- name = name.to_s if name.is_a?(Symbol)
-
- name
+ 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? and BlockParsers.has_key?(type)
- params.merge(BlockParsers[type].extract_params(endian, &block))
+ if block_given?
+ params.merge(params_from_block(type, &block))
else
params
end
end
@@ -215,36 +162,60 @@
params = name if name.is_a?(Hash)
params || {}
end
- def append_field(type, name, params)
- if too_many_fields?
- dsl_raise SyntaxError, "attempting to wrap more than one type"
+ 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
- ensure_valid_name(name)
+ def append_field(type, name, params)
+ ensure_valid_field(name)
- fields.add_field(type, name, params, endian)
+ 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_name(name)
- if must_not_have_a_name_failed?(name)
+ 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?(name)
+ 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?(name)
+ if must_have_a_name_failed?(field_name)
dsl_raise SyntaxError, "field must have a name"
end
- unless option?(:fieldnames_for_choices)
+ 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)
@@ -264,91 +235,117 @@
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 != ""
+ option?(:no_fieldnames) and name != nil
end
def must_have_a_name_failed?(name)
- option?(:mandatory_fieldnames) and 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.field_names.all? { |n| n == "" }
- no_names_blank = fields.field_names.all? { |n| n != "" }
+ all_names_blank = fields.all_field_names_blank?
+ no_names_blank = fields.no_field_names_blank?
- (name != "" and all_names_blank) or (name == "" and no_names_blank)
+ (name != nil and all_names_blank) or (name == nil and no_names_blank)
else
false
end
end
def malformed_name?(name)
- name != "" and /^[a-z_]\w*$/ !~ name
+ /^[a-z_]\w*$/ !~ name.to_s
end
def duplicate_name?(name)
- name != "" and fields.field_names.include?(name)
+ fields.has_field_name?(name)
end
def name_shadows_method?(name)
- name != "" and @the_class.method_defined?(name)
+ @the_class.method_defined?(name)
end
def name_is_reserved?(name)
- name != "" and BinData::Struct::RESERVED.include?(name)
+ BinData::Struct::RESERVED.include?(name.to_sym)
end
- end
- class StructBlockParser
- def self.extract_params(endian, &block)
- parser = DSLParser.new(BinData::Struct, :multiple_fields, :optional_fieldnames, :hidden_fields)
- parser.endian endian
- parser.instance_eval(&block)
+ def dsl_raise(exception, message)
+ backtrace = caller
+ backtrace.shift while %r{bindata/dsl.rb} =~ backtrace.first
- parser.to_struct_params
+ raise exception, message + " in #{@the_class}", backtrace
end
- end
- class ArrayBlockParser
- def self.extract_params(endian, &block)
- parser = DSLParser.new(BinData::Array, :multiple_fields, :optional_fieldnames)
- parser.endian endian
- parser.instance_eval(&block)
-
- if parser.fields.length == 1
- {:type => parser.field.to_type_params}
+ def to_array_params
+ case fields.length
+ when 0
+ {}
+ when 1
+ {:type => fields[0].prototype}
else
- {:type => [:struct, parser.to_struct_params]}
+ {:type => [:struct, to_struct_params]}
end
end
- end
- class ChoiceBlockParser
- def self.extract_params(endian, &block)
- parser = DSLParser.new(BinData::Choice, :multiple_fields, :all_or_none_fieldnames, :fieldnames_for_choices)
- parser.endian endian
- parser.instance_eval(&block)
-
- if all_blank?(parser.fields.field_names)
- {:choices => parser.fields.collect { |f| f.to_type_params }}
+ def to_choice_params
+ if fields.length == 0
+ {}
+ elsif fields.all_field_names_blank?
+ {:choices => fields.collect { |f| f.prototype }}
else
choices = {}
- parser.fields.each { |f| choices[f.name] = f.to_type_params }
+ fields.each { |f| choices[f.name] = f.prototype }
{:choices => choices}
end
end
- def self.all_blank?(array)
- array.all? { |el| el == "" }
+ 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
- BlockParsers = {
- :struct => StructBlockParser,
- :array => ArrayBlockParser,
- :choice => ChoiceBlockParser,
- }
+ # 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