module BinData module DSLMixin def self.included(base) #:nodoc: base.extend ClassMethods end module ClassMethods # Defines the DSL Parser for this BinData object. Allowed +args+ are: # # [:only_one_field] Only one field may be declared. # [:multiple_fields] Multiple fields may be declared. # [:hidden_fields] Hidden fields are allowed. # [:sanitize_fields] Fields are to be sanitized. # [:mandatory_fieldnames] Fieldnames are mandatory. # [:optional_fieldnames] Fieldnames are optional. # [:no_fieldnames] Fieldnames are prohibited. # [:all_or_none_fieldnames] All fields must have names, or # none may have names. def dsl_parser(*args) @dsl_parser ||= DSLParser.new(self, *args) 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 # 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 end attr_reader :options def endian(endian = nil) if endian.nil? @endian elsif endian.respond_to? :endian @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 def hide(*args) if option?(:hidden_fields) @hide.concat(args.collect { |name| name.to_s }) @hide end end def fields @fields end def field @fields[0] 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 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 dsl_raise(exception, message) backtrace = caller backtrace.shift while %r{bindata/dsl.rb} =~ backtrace.first raise exception, message + " in #{@the_class}", backtrace end def option?(opt) @options.include?(opt) 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 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 = nil if name.is_a?(Hash) name.to_s 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)) else params end end def params_from_args(args) name, params = args 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" end ensure_valid_name(name) fields.add_field(type, name, params, endian) rescue UnRegisteredTypeError => err dsl_raise TypeError, "unknown type '#{err.message}'" end def ensure_valid_name(name) if must_not_have_a_name_failed?(name) dsl_raise SyntaxError, "field must not have a name" end if all_or_none_names_failed?(name) dsl_raise SyntaxError, "fields must either all have names, or none must have names" end if must_have_a_name_failed?(name) dsl_raise SyntaxError, "field must have a name" end 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 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 != "" end def must_have_a_name_failed?(name) option?(:mandatory_fieldnames) and name == "" 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 != "" } (name != "" and all_names_blank) or (name == "" and no_names_blank) else false end end def malformed_name?(name) name != "" and /^[a-z_]\w*$/ !~ name end def duplicate_name?(name) name != "" and fields.field_names.include?(name) end def name_shadows_method?(name) name != "" and @the_class.method_defined?(name) end def name_is_reserved?(name) name != "" and BinData::Struct::RESERVED.include?(name) 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) parser.to_struct_params 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} else {:type => [:struct, parser.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) parser.endian endian parser.instance_eval(&block) if all_blank?(parser.fields.field_names) {:choices => parser.fields.collect { |f| f.to_type_params }} else choices = {} parser.fields.each { |f| choices[f.name] = f.to_type_params } {:choices => choices} end end def self.all_blank?(array) array.all? { |el| el == "" } end end BlockParsers = { :struct => StructBlockParser, :array => ArrayBlockParser, :choice => ChoiceBlockParser, } end end