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