require 'bindata' # An example of a recursively defined data format. # # This format is used to describe atoms and lists. # It is recursive because lists can contain other lists. # # Atoms - contain a single integer # Lists - contain a mixture of atoms and lists # # The binary representation is: # # Atoms - A single byte 'a' followed by an int32 containing the value. # Lists - A single byte 'l' followed by an int32 denoting the number of # items in the list. This is followed by all the items in the list. # # All integers are big endian. # # # A first attempt at a declaration would be: # # class Atom < BinData::Record # string :tag, :length => 1, :check_value => 'a' # int32be :val # end # # class List < BinData::Record # string :tag, :length => 1, :check_value => 'l' # int32be :num, :value => lambda { vals.length } # array :vals, :initial_length => :num do # choice :selection => ??? do # atom # list # end # end # end # # Notice how we get stuck on attemping to write a declaration for # the contents of the list. We can't determine if the list item is # an atom or list because we haven't read it yet. It appears that # we can't proceed. # # The cause of the problem is that the tag identifying the type is # coupled with that type. # # The solution is to decouple the tag from the type. We introduce a # new type 'Term' that is a thin container around the tag plus the # type (atom or list). # # The declaration then becomes: # # class Term < BinData::Record; end # forward declaration # # class Atom < BinData::Wrapper # int32be # end # # class List < BinData::Record # int32be :num, :value => lambda { vals.length } # array :vals, :type => :term, :initial_length => :num # end # # class Term < BinData::Record # string :tag, :length => 1 # choice :term, :selection => :tag do # atom 'a' # list 'l' # end # end class Term < BinData::Record; end # Forward declaration class Atom < BinData::Wrapper int32be def decode snapshot end def self.encode(val) Atom.new(val) end end class List < BinData::Record int32be :num, :value => lambda { vals.length } array :vals, :initial_length => :num, :type => :term def decode vals.collect { |v| v.decode } end def self.encode(val) List.new(:vals => val.collect { |v| Term.encode(v) }) end end class Term < BinData::Record string :tag, :length => 1 choice :term, :selection => :tag do atom 'a' list 'l' end def decode term.decode end def self.encode(val) if Fixnum === val Term.new(:tag => 'a', :term => Atom.encode(val)) else Term.new(:tag => 'l', :term => List.encode(val)) end end end p Term.encode(4) p Term.encode(4).decode puts p Term.encode([1, [2, 3], 4]) p Term.encode([1, [2, 3], 4]).decode