/** Parses an @ rule, stopping at a matching brace pair, or ;. */
  private void parseAtRule() throws IOException {
    // PENDING: make this more effecient.
    boolean done = false;
    boolean isImport =
        (tokenBufferLength == 7
            && tokenBuffer[0] == '@'
            && tokenBuffer[1] == 'i'
            && tokenBuffer[2] == 'm'
            && tokenBuffer[3] == 'p'
            && tokenBuffer[4] == 'o'
            && tokenBuffer[5] == 'r'
            && tokenBuffer[6] == 't');

    unitBuffer.setLength(0);
    while (!done) {
      int nextToken = nextToken(';');

      switch (nextToken) {
        case IDENTIFIER:
          if (tokenBufferLength > 0 && tokenBuffer[tokenBufferLength - 1] == ';') {
            --tokenBufferLength;
            done = true;
          }
          if (tokenBufferLength > 0) {
            if (unitBuffer.length() > 0 && readWS) {
              unitBuffer.append(' ');
            }
            unitBuffer.append(tokenBuffer, 0, tokenBufferLength);
          }
          break;

        case BRACE_OPEN:
          if (unitBuffer.length() > 0 && readWS) {
            unitBuffer.append(' ');
          }
          unitBuffer.append(charMapping[nextToken]);
          parseTillClosed(nextToken);
          done = true;
          // Skip a tailing ';', not really to spec.
          {
            int nextChar = readWS();
            if (nextChar != -1 && nextChar != ';') {
              pushChar(nextChar);
            }
          }
          break;

        case BRACKET_OPEN:
        case PAREN_OPEN:
          unitBuffer.append(charMapping[nextToken]);
          parseTillClosed(nextToken);
          break;

        case BRACKET_CLOSE:
        case BRACE_CLOSE:
        case PAREN_CLOSE:
          throw new RuntimeException("Unexpected close in @ rule");

        case END:
          done = true;
          break;
      }
    }
    if (isImport && !encounteredRuleSet) {
      callback.handleImport(unitBuffer.toString());
    }
  }