docs: private void parseAttribute(ParsedScheduleExpression parsedExpr,
                                  Attribute attr,
                                  String string)
      {
          // Reset state.
          ivAttr = attr;
          ivString = string;
          ivPos = 0;
  
          if (string == null)
          {
              // d660135 - Per CTS, null values must be rejected rather than being
              // translated to the default attribute value.
              error(ScheduleExpressionParserException.Error.VALUE);
          }
  
          // Loop for lists: "x, x, ..."
          for (boolean inList = false;; inList = true)
          {
              skipWhitespace();
              int value = readSingleValue();
              skipWhitespace();
  
              // Ranges: "x-x"
              if (ivPos < ivString.length() && ivString.charAt(ivPos) == '-')
              {
                  ivPos++;
                  skipWhitespace();
  
                  int maxValue = readSingleValue();
                  skipWhitespace();
  
                  if (value == ENCODED_WILD_CARD || maxValue == ENCODED_WILD_CARD)
                  {
                      error(ScheduleExpressionParserException.Error.RANGE_BOUND); // F743-506
                  }
  
                  attr.addRange(parsedExpr, value, maxValue);
              }
              // Increments: "x/x"
              else if (ivPos < ivString.length() && ivString.charAt(ivPos) == '/')
              {
                  ivPos++;
                  skipWhitespace();
  
                  int interval = readSingleValue();
                  skipWhitespace();
  
                  if (interval == ENCODED_WILD_CARD)
                  {
                      error(ScheduleExpressionParserException.Error.INCREMENT_INTERVAL); // F743-506
                  }
  
                  if (inList)
                  {
                      error(ScheduleExpressionParserException.Error.LIST_VALUE); // F743-506
                  }
  
                  if (!ivAttr.isIncrementable())
                  {
                      error(ScheduleExpressionParserException.Error.UNINCREMENTABLE); // F743-506
                  }
  
                  if (value == ENCODED_WILD_CARD)
                  {
                      value = 0;
                  }
  
                  if (interval == 0)
                  {
                      // "30/0" is interpreted as the single value "30".
                      attr.add(parsedExpr, value);
                  }
                  else
                  {
                      for (; value <= ivAttr.getMax(); value += interval)
                      {
                          attr.add(parsedExpr, value);
                      }
                  }
                  break;
              }
              // Wild cards: "*"
              else if (value == ENCODED_WILD_CARD)
              {
                  if (inList)
                  {
                      error(ScheduleExpressionParserException.Error.LIST_VALUE); // F743-506
                  }
  
                  attr.setWildCard(parsedExpr);
                  break;
              }
              // Single values
              else
              {
                  attr.add(parsedExpr, value);
              }
  
              if (ivPos >= ivString.length() || ivString.charAt(ivPos) != ',')
              {
                  break;
              }
  
              // Skip the comma
              ivPos++;
          }
  
          skipWhitespace();
          if (ivPos < ivString.length())
          {
              error(ScheduleExpressionParserException.Error.VALUE); // F743-506
          }
      }