lib/bindata/dsl.rb in bindata-2.1.0 vs lib/bindata/dsl.rb in bindata-2.2.0

- old
+ new

@@ -65,26 +65,41 @@ 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_handler = DSLBigAndLittleEndianHandler.new(the_class) @endian = nil end attr_reader :parser_type def endian(endian = nil) if endian set_endian(endian) elsif @endian.nil? - pendian = parent_attribute(:endian, nil) - set_endian(pendian) if pendian + set_endian(parent_attribute(:endian)) end @endian end + def search_prefix(*args) + unless defined? @search_prefix + @search_prefix = parent_attribute(:search_prefix, []).dup + end + + prefix = args.collect { |name| name.to_sym }.compact + if prefix.size > 0 + if has_fields? + dsl_raise SyntaxError, "search_prefix must be called before defining fields" + end + + @search_prefix = prefix.concat(@search_prefix) + end + + @search_prefix + end + def hide(*args) if option?(:hidden_fields) hidden = args.collect { |name| name.to_sym } unless defined? @hide @@ -96,12 +111,12 @@ end end def fields unless defined? @fields - fields = @endian_handler.ancestor_fields || parent_attribute(:fields) - @fields = SanitizedFields.new(endian) + fields = parent_fields + @fields = SanitizedFields.new(hints) @fields.copy_fields(fields) if fields end @fields end @@ -109,69 +124,74 @@ def dsl_params send(parser_abilities[@parser_type].at(0)) end def method_missing(*args, &block) - if endian == :big_and_little - @endian_handler.forward_field_definition(*args, &block) - else - parse_and_append_field(*args, &block) - end + ensure_hints + parse_and_append_field(*args, &block) end #------------- private def parser_abilities @abilities ||= { :struct => [:to_struct_params, [:multiple_fields, :optional_fieldnames, :hidden_fields]], :array => [:to_array_params, [:multiple_fields, :optional_fieldnames]], - :buffer => [:to_array_params, [:multiple_fields, :optional_fieldnames]], + :buffer => [:to_array_params, [:multiple_fields, :optional_fieldnames, :hidden_fields]], :choice => [:to_choice_params, [:multiple_fields, :all_or_none_fieldnames, :fieldnames_are_values]], :primitive => [:to_struct_params, [:multiple_fields, :optional_fieldnames]] } end def option?(opt) parser_abilities[@parser_type].at(1).include?(opt) end + def ensure_hints + endian + search_prefix + end + + def hints + { + :endian => endian, + :search_prefix => search_prefix, + } + end + def set_endian(endian) - if has_fields? - dsl_raise SyntaxError, "endian must not be called after defining fields" - end - if not valid_endian?(endian) - dsl_raise ArgumentError, "unknown value for endian '#{endian}'" - end + if endian + if has_fields? + dsl_raise SyntaxError, "endian must be called before defining fields" + end + if not valid_endian?(endian) + dsl_raise ArgumentError, "unknown value for endian '#{endian}'" + end - if endian == :big_and_little - @endian_handler.prepare_subclasses - end + if endian == :big_and_little + DSLBigAndLittleEndianHandler.handle(@the_class) + end - @endian = endian + @endian = endian + end end - def has_fields? - @fields && @fields.length > 0 - end - def valid_endian?(endian) [:big, :little, :big_and_little].include?(endian) end - def parent_attribute(attr, default = nil) - parent = @the_class.superclass - parser = parent.respond_to?(:dsl_parser) ? parent.dsl_parser : nil - if parser and parser.respond_to?(attr) - parser.send(attr) - else - default - end + def parent_fields + parent_attribute(:fields) end + def has_fields? + @fields && @fields.length > 0 + end + def parse_and_append_field(*args, &block) - parser = DSLFieldParser.new(endian, *args, &block) + parser = DSLFieldParser.new(hints, *args, &block) begin @validator.validate_field(parser.name) append_field(parser.type, parser.name, parser.params) rescue Exception => err dsl_raise err.class, err.message @@ -182,10 +202,20 @@ fields.add_field(type, name, params) rescue BinData::UnRegisteredTypeError => err raise TypeError, "unknown type '#{err.message}'" end + def parent_attribute(attr, default = nil) + parent = @the_class.superclass + parser = parent.respond_to?(:dsl_parser) ? parent.dsl_parser : nil + if parser and parser.respond_to?(attr) + parser.send(attr) + else + default + end + end + def dsl_raise(exception, message) backtrace = caller backtrace.shift while %r{bindata/dsl.rb} =~ backtrace.first raise exception, message + " in #{@the_class}", backtrace @@ -217,10 +247,13 @@ def to_struct_params result = {:fields => fields} if not endian.nil? result[:endian] = endian end + if not search_prefix.empty? + result[:search_prefix] = search_prefix + end if option?(:hidden_fields) and not hide.empty? result[:hide] = hide end result @@ -229,91 +262,100 @@ # Handles the :big_and_little endian option. # This option creates two subclasses, each handling # :big or :little endian. class DSLBigAndLittleEndianHandler - def initialize(the_class) - @the_class = the_class - end + class << self + def handle(bnl_class) + make_class_abstract(bnl_class) + create_subclasses_with_endian(bnl_class) + override_new_in_class(bnl_class) + delegate_field_creation(bnl_class) + fixup_subclass_hierarchy(bnl_class) + end - def prepare_subclasses - create_subclasses_with_endian - make_class_abstract - override_new_in_class - end + def make_class_abstract(bnl_class) + bnl_class.send(:unregister_self) + end - def forward_field_definition(*args, &block) - class_with_endian(@the_class, :big).send(*args, &block) - class_with_endian(@the_class, :little).send(*args, &block) - end - - def ancestor_fields - if subclass_of_big_and_little_endian? - pparent = @the_class.superclass.superclass - ancestor_with_endian = class_with_endian(pparent, @the_class.endian) - obj_attribute(ancestor_with_endian, :fields) - else - nil + def create_subclasses_with_endian(bnl_class) + instance_eval "class ::#{bnl_class}Be < ::#{bnl_class}; endian :big; end" + instance_eval "class ::#{bnl_class}Le < ::#{bnl_class}; endian :little; end" end - end - #------------- - private + def override_new_in_class(bnl_class) + endian_classes = { + :big => class_with_endian(bnl_class, :big), + :little => class_with_endian(bnl_class, :little), + } + bnl_class.define_singleton_method(:new) do |*args| + if self == bnl_class + value, options, parent = arg_processor.separate_args(self, args) + delegate = endian_classes[options[:endian]] + return delegate.new(*args) if delegate + end - def create_subclasses_with_endian - instance_eval "class ::#{@the_class}Be < ::#{@the_class}; endian :big; end" - instance_eval "class ::#{@the_class}Le < ::#{@the_class}; endian :little; end" - end + super(*args) + end + end - def make_class_abstract - @the_class.send(:unregister_self) - end + def delegate_field_creation(bnl_class) + endian_classes = { + :big => class_with_endian(bnl_class, :big), + :little => class_with_endian(bnl_class, :little), + } - def override_new_in_class - saved_class = @the_class - endian_classes = { - :big => class_with_endian(saved_class, :big), - :little => class_with_endian(saved_class, :little), - } - @the_class.define_singleton_method(:new) do |*args| - if self == saved_class - value, options, parent = arg_processor.separate_args(self, args) - delegate = endian_classes[options[:endian]] - return delegate.new(*args) if delegate + parser = bnl_class.dsl_parser + parser.define_singleton_method(:parse_and_append_field) do |*args, &block| + endian_classes[:big].send(*args, &block) + endian_classes[:little].send(*args, &block) end - - super(*args) end - end - def subclass_of_big_and_little_endian? - parent = @the_class.superclass - pparent = parent.superclass + def fixup_subclass_hierarchy(bnl_class) + parent = bnl_class.superclass + if obj_attribute(parent, :endian) == :big_and_little + be_subclass = class_with_endian(bnl_class, :big) + be_parent = class_with_endian(parent, :big) + be_fields = obj_attribute(be_parent, :fields) - obj_attribute(parent, :endian) == :big_and_little and - obj_attribute(pparent, :endian) == :big_and_little and - [:big, :little].include?(@the_class.endian) - end + le_subclass = class_with_endian(bnl_class, :little) + le_parent = class_with_endian(parent, :little) + le_fields = obj_attribute(le_parent, :fields) - def class_with_endian(class_name, endian) - RegisteredClasses.lookup(class_name, endian) - end + be_subclass.dsl_parser.define_singleton_method(:parent_fields) do + be_fields + end + le_subclass.dsl_parser.define_singleton_method(:parent_fields) do + le_fields + end + end + end - def obj_attribute(obj, attr, default = nil) - parser = obj.respond_to?(:dsl_parser) ? obj.dsl_parser : nil - if parser and parser.respond_to?(attr) - parser.send(attr) - else - default + def class_with_endian(class_name, endian) + hints = { + :endian => endian, + :search_prefix => class_name.dsl_parser.search_prefix, + } + RegisteredClasses.lookup(class_name, hints) end + + def obj_attribute(obj, attr, default = nil) + parser = obj.respond_to?(:dsl_parser) ? obj.dsl_parser : nil + if parser and parser.respond_to?(attr) + parser.send(attr) + else + default + end + end end end # Extracts the details from a field declaration. class DSLFieldParser - def initialize(endian, symbol, *args, &block) - @endian = endian + def initialize(hints, symbol, *args, &block) + @hints = hints @type = symbol @name = name_from_field_declaration(args) @params = params_from_field_declaration(args, &block) end @@ -353,10 +395,11 @@ :struct => BinData::Struct } if bindata_classes.include?(@type) parser = DSLParser.new(bindata_classes[@type], @type) - parser.endian(@endian) + parser.endian(@hints[:endian]) + parser.search_prefix(*@hints[:search_prefix]) parser.instance_eval(&block) parser.dsl_params else {}