docs: def add_field(self, identifier, length=None, start_at=None, tags=None):
          """Add a new field to the BitField.
  
          If any existing fields' values are set, the newly created field will
          become a child of those fields. This means that this field will exist
          only when the parent fields' values are set as they are currently.
  
          Parameters
          ----------
          identifier : str
              A identifier for the field. Must be a valid python identifier.
              Field names must be unique within the scope in which they exist and
              are only valid within that scope. For example::
  
                  >>> bf = BitField(32)
                  >>> bf.add_field("a")
  
                  >>> # Can add multiple fields with the same name if they exist
                  >>> # in different scopes
                  >>> bf0 = bf(a=0)
                  >>> bf0.add_field("b", length=4)
                  >>> bf1 = bf(a=1)
                  >>> bf1.add_field("b", length=8)
  
                  >>> # Can't add multiple fields with the same name which exist
                  >>> # within the same or nested scopes.
                  >>> bf.add_field("a")
                  Traceback (most recent call last):
                  ValueError: Field with identifier 'a' already exists
                  >>> bf.add_field("b")
                  Traceback (most recent call last):
                  ValueError: Field with identifier 'b' already exists
  
              Here *three* fields are defined, one called "a" and the other two
              called "b". The two fields called "b" are completely unrelated
              (they may differ in size, position and associated set of tags) and
              are distinguished by the fact that one exists when a=0 and the
              other when a=1.
          length : int or None
              The number of bits in the field. If None the field will be
              automatically assigned a length long enough for the largest value
              assigned.
          start_at : int or None
              0-based index of least significant bit of the field within the
              bit field. If None the field will be automatically located in free
              space in the bit field.
          tags : string or collection of strings or None
              A (possibly empty) set of tags used to classify the field.  Tags
              should be valid Python identifiers. If a string, the string must be
              a single tag or a space-separated list of tags. If *None*, an empty
              set of tags is assumed. These tags are applied recursively to all
              fields of which this field is a child.
  
          Raises
          ------
          ValueError
              If any the field overlaps with another one or does not fit within
              the bit field. Note that fields with unspecified lengths and
              positions do not undergo such checks until their length and
              position become known when :py:meth:`.assign_fields` is called.
          """
          # Check for zero-length fields
          if length is not None and length <= 0:
              raise ValueError("Fields must be at least one bit in length.")
  
          # Check for fields which don't fit in the bit field
          if (start_at is not None and
              (0 <= start_at >= self.length or
               start_at + (length or 1) > self.length)):
              raise ValueError(
                  "Field doesn't fit within {}-bit bit field.".format(
                      self.length))
  
          # Check for fields which occupy the same bits
          if start_at is not None:
              end_at = start_at + (length or 1)
              for other_identifier, other_field in \
                      self.fields.potential_fields(self.field_values):
                  if other_field.start_at is not None:
                      other_start_at = other_field.start_at
                      other_end_at = other_start_at + (other_field.length or 1)
  
                      if end_at > other_start_at and other_end_at > start_at:
                          raise ValueError(
                              "Field '{}' (range {}-{}) "
                              "overlaps field '{}' (range {}-{})".format(
                                  identifier,
                                  start_at, end_at,
                                  other_identifier,
                                  other_start_at, other_end_at))
  
          # Normalise tags type
          if type(tags) is str:
              tags = set(tags.split())
          elif tags is None:
              tags = set()
          else:
              tags = set(tags)
  
          # Add the field (checking that the identifier is unique in the process)
          field = type(self)._Field(length, start_at, tags)
          self.fields.add_field(field, identifier, self.field_values)
  
          # Add tags to all parents of this field
          for parent_identifier in self.fields.get_field_requirements(
                  identifier, self.field_values):
              parent = self.fields.get_field(parent_identifier,
                                             self.field_values)
              parent.tags.update(tags)