require 'bindata' # An example of a recursively defined data format. # # This example format describes 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, assert: 'a' # int32be :val # end # # class List < BinData::Record # string :tag, length: 1, assert: 'l' # int32be :num, value: -> { 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::Int32be # end # # class List < BinData::Record # int32be :num, value: -> { 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::Int32be def decode snapshot end def self.encode(val) Atom.new(val) end end class List < BinData::Record int32be :num, value: -> { vals.length } array :vals, initial_length: :num, type: :term def decode vals.collect(&: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 puts "A single Atom" p Term.encode(4) p Term.encode(4).decode puts puts "A nested List" p Term.encode([1, [2, 3], 4]) p Term.encode([1, [2, 3], 4]).decode