module Gdsii::Read

This module will be extended into all classes descended of Group (i.e. the high-level GDSII access classes). The extension brings a “read” class method (i.e. singleton) into these classes which enables a GDSII file to be read into these data structures using the BNF specification.

Public Instance Methods

read(file, parent_records=nil, parent_bnf_item=nil, yield_at=nil) { |group| ... } click to toggle source

Accepts a file handle and reads data from that file handle into an object descended of Group. The parent_records and parent_bnf_item arguments are for internal use only.

# File lib/gdsii/mixins.rb, line 288
    def read(file, parent_records=nil, parent_bnf_item=nil, yield_at=nil)
      # Set BNF index at 0, get the BNF items, create an empty grouping object
      i = 0
      bnf_items = bnf_spec.bnf_items
      group = self.new
#      puts "#{self}"
      
      # Loop through the BNF spec for this grouping...
      while i < bnf_items.length do
        bnf_item = bnf_items[i]

        # see what kind of a BNF item this is - a Class or a record type (Fixnum)
        if bnf_item.key.class == Class
          # return if stop_at_class is set to true (used internally for
          # Library#read_header and Cell#read_header).
          yield group if yield_at == :before_group

          # Determine the class to use
          klass = bnf_item.key
          
          # Read from the class
          if bnf_item.multiple?
            if (rec = klass.read(file, group.records, bnf_item))
              # if a record was returned, then add it
              group.records.add(bnf_item.key, rec)
            else
              # if nil was returned, then we're done with the record; next
              i += 1
            end
          else
            # If the record is singular, then get it from the class and
            # increment the counter
            rec = klass.read(file, group.records, bnf_item)
            group.records.set(bnf_item.key, rec)
            i += 1
          end
        else
          # ELSE, a record type is expected (Fixnum)
          rec = Record.read(file)
#          puts "  --> expect #{Gdsii::grt_name(bnf_item.key)}; rec == #{rec.name}"
          if rec.type == bnf_item.key
            # This record matches the grouping BNF item that was expected, so
            # store the data
            if bnf_item.multiple?
              group.records.add(bnf_item.key, rec)
            else
              group.records.set(bnf_item.key, rec)
              i += 1
            end
          else
            # Record does not match expected record as per BNF.  Check that we
            # have data already set in this record or that the record is
            # optional.
            if group.records.has_data?(bnf_item.key) or bnf_item.optional?
              # Already has data - just move to the next record and reset file
              # pointer
              i += 1
              file.seek(-rec.byte_size, IO::SEEK_CUR)
            elsif (parent_bnf_item and parent_bnf_item.key.class == Class and
                   (parent_records.has_data?(parent_bnf_item.key) or
                    parent_bnf_item.optional?))
              # OK, in this case, we are descended into a Class and did not
              # match the BNF expected.  Furthermore, the parent calling this
              # grouping either already got the data needed or this was an
              # optional class in the first place.  In either case, we're OK
              # and just need to get out - which is what we do by returning
              # nil.
              file.seek(-rec.byte_size, IO::SEEK_CUR)
              return nil
            else
              # Does not match the expected BNF... fail
              raise "Unexpected record while reading GDSII file starting at file position #{file.pos-rec.byte_size}\n" +
                "This record is in the wrong place according to the GDSII specification (from BNF)\n" +
                "Expected record was #{Gdsii::grt_name(bnf_item.key)}; instead, received:\n" +
                "Record type     = #{Gdsii::grt_name(rec.type)}\n" +
                "Data type       = #{Gdsii::gdt_name(rec.data.type)}\n" +
                "Record length   = #{rec.byte_size}\n"
            end
          end
          
        end
      end
      
      # Return this record grouping
      yield group if yield_at == :group
      group
    end