docs: def read_struct_file(struct_data):
      """Interpret a struct file defining the location of variables in memory.
  
      Parameters
      ----------
      struct_data : :py:class:`bytes`
          String of :py:class:`bytes` containing data to interpret as the struct
          definition.
  
      Returns
      -------
      {struct_name: :py:class:`~.Struct`}
          A dictionary mapping the struct name to a :py:class:`~.Struct`
          instance. **Note:** the struct name will be a string of bytes, e.g.,
          `b"vcpu"`.
      """
      # Holders for all structs
      structs = dict()
  
      # Holders for the current struct
      name = None
  
      # Iterate over every line in the file
      for i, l in enumerate(struct_data.splitlines()):
          # Empty the line of comments, if the line is empty then skip to the
          # next line.  Split on whitespace to get the tokens.
          tokens = re_comment.sub(b"", l).strip().split()
          if len(tokens) == 0:
              continue
          elif len(tokens) == 3:
              # 3 tokens implies header data
              (key, _, value) = tokens
  
              if key == b"name":
                  if name is not None:
                      if structs[name].size is None:
                          raise ValueError(
                              "size value missing for struct '{}'".format(name))
                      if structs[name].base is None:
                          raise ValueError(
                              "base value missing for struct '{}'".format(name))
                  name = value
                  structs[name] = Struct(name)
              elif key == b"size":
                  structs[name].size = num(value)
              elif key == b"base":
                  structs[name].base = num(value)
              else:
                  raise ValueError(key)
          elif len(tokens) == 5:
              # 5 tokens implies entry in struct.
              (field, pack, offset, printf, default) = tokens
  
              # Convert the packing character from Perl to Python standard
              num_pack = re_numbered_pack.match(pack)
              if num_pack is not None:
                  pack = (num_pack.group("num") +
                          perl_to_python_packs[num_pack.group("char")])
              else:
                  pack = perl_to_python_packs[pack]
  
              # If the field is an array then extract the length
              length = 1
              field_exp = re_array_field.match(field)
              if field_exp is not None:
                  field = field_exp.group("field")
                  length = num(field_exp.group("length"))
  
              structs[name][field] = StructField(pack, num(offset), printf,
                                                 num(default), length)
          else:
              raise ValueError(
                  "line {}: Invalid syntax in struct file".format(i))
  
      # Final check for setting size and base
      if structs[name].size is None:
          raise ValueError(
              "size value missing for struct '{}'".format(name))
      if structs[name].base is None:
          raise ValueError(
              "base value missing for struct '{}'".format(name))
  
      return structs