/**
   * Method for tracking macro expansions.
   *
   * @since 5.0
   */
  public void expand(
      String beforeExpansion,
      MacroExpansionTracker tracker,
      String filePath,
      int lineNumber,
      boolean protectDefinedConstructs) {
    fImplicitMacroExpansions.clear();
    fImageLocationInfos.clear();
    fFixedInput = beforeExpansion.toCharArray();
    fFixedCurrentFilename = filePath;
    fFixedLineNumber = lineNumber;
    Lexer lexer = new Lexer(fFixedInput, fLexOptions, fLog, this);

    try {
      tracker.start(lexer);
      Token identifier = lexer.nextToken();
      if (identifier.getType() != IToken.tIDENTIFIER) {
        tracker.fail();
        return;
      }
      PreprocessorMacro macro = fDictionary.get(identifier.getCharImage());
      if (macro == null) {
        tracker.fail();
        return;
      }
      lexer.nextToken();

      fStartOffset = identifier.getOffset();
      fEndOffset = identifier.getEndOffset();
      fCompletionMode = false;
      IdentityHashMap<PreprocessorMacro, PreprocessorMacro> forbidden =
          new IdentityHashMap<PreprocessorMacro, PreprocessorMacro>();

      // setup input sequence
      TokenSource input = new TokenSource(lexer);
      TokenList firstExpansion = new TokenList();

      firstExpansion.append(new ExpansionBoundary(macro, true));
      expandOne(identifier, macro, forbidden, input, firstExpansion, tracker);
      firstExpansion.append(new ExpansionBoundary(macro, false));
      input.prepend(firstExpansion);

      TokenList result = expandAll(input, forbidden, protectDefinedConstructs, tracker);
      tracker.finish(result, fEndOffset);
    } catch (OffsetLimitReachedException e) {
    }
  }
  /** Expects that the identifier has been consumed. */
  private Token parseArguments(
      TokenSource input,
      FunctionStyleMacro macro,
      IdentityHashMap<PreprocessorMacro, PreprocessorMacro> forbidden,
      TokenSource[] result,
      MacroExpansionTracker tracker)
      throws OffsetLimitReachedException, AbortMacroExpansionException {
    final int argCount = macro.getParameterPlaceholderList().length;
    final boolean hasVarargs = macro.hasVarArgs() != FunctionStyleMacro.NO_VAARGS;
    final int requiredArgs = hasVarargs ? argCount - 1 : argCount;
    int idx = 0;
    int nesting = -1;
    for (int i = 0; i < result.length; i++) {
      result[i] = new TokenSource(null);
    }

    boolean missingRParenthesis = false;
    boolean tooManyArgs = false;

    boolean isFirstOfArg = true;
    Token lastToken = null;
    TokenList spaceMarkers = new TokenList();
    loop:
    while (true) {
      Token t = input.fetchFirst();
      if (t == null) {
        missingRParenthesis = true;
        break loop;
      }
      if (tracker != null) {
        switch (t.getType()) {
          case IToken.tEND_OF_INPUT:
          case IToken.tCOMPLETION:
          case ObjCPreprocessor.tSCOPE_MARKER:
          case Lexer.tNEWLINE:
            break;
          default:
            tracker.addFunctionStyleMacroExpansionToken((Token) t.clone());
            break;
        }
      }
      lastToken = t;
      switch (t.getType()) {
        case IToken.tEND_OF_INPUT:
          assert nesting >= 0;
          if (fCompletionMode) {
            if (idx < result.length) {
              throw new CompletionInMacroExpansionException(ORIGIN, t, result[idx]);
            }
            throw new OffsetLimitReachedException(ORIGIN, null);
          }
          missingRParenthesis = true;
          break loop;
        case IToken.tCOMPLETION:
          if (idx < result.length) {
            result[idx].append(t);
            throw new CompletionInMacroExpansionException(ORIGIN, t, result[idx]);
          }
          throw new OffsetLimitReachedException(ORIGIN, t);

        case Lexer.tNEWLINE:
          continue loop;

        case IToken.tLPAREN:
          // the first one sets nesting to zero.
          if (++nesting == 0) {
            continue;
          }
          break;

        case IToken.tRPAREN:
          assert nesting >= 0;
          if (--nesting < 0) {
            break loop;
          }
          break;

        case IToken.tCOMMA:
          assert nesting >= 0;
          if (nesting == 0) {
            if (idx < argCount - 1) { // next argument
              isFirstOfArg = true;
              spaceMarkers.clear();
              idx++;
              continue loop;
            } else if (!hasVarargs) {
              tooManyArgs = true;
              break loop;
            }
          }
          break;

        case ObjCPreprocessor.tSCOPE_MARKER:
          if (argCount == 0) {
            ((ExpansionBoundary) t).execute(forbidden);
          } else {
            result[idx].append(t);
          }
          continue loop;

        case ObjCPreprocessor.tSPACE:
        case ObjCPreprocessor.tNOSPACE:
          if (!isFirstOfArg) {
            spaceMarkers.append(t);
          }
          continue loop;

        default:
          assert nesting >= 0;
      }
      if (argCount == 0) {
        tooManyArgs = true;
        break loop;
      }
      result[idx].appendAll(spaceMarkers);
      result[idx].append(t);
      isFirstOfArg = false;
    }

    if (missingRParenthesis) {
      handleProblem(IProblem.PREPROCESSOR_MISSING_RPAREN_PARMLIST, macro.getNameCharArray());
      throw new AbortMacroExpansionException();
    }

    if (tooManyArgs) {
      handleProblem(IProblem.PREPROCESSOR_MACRO_USAGE_ERROR, macro.getNameCharArray());
    } else if (idx + 1 < requiredArgs) {
      handleProblem(IProblem.PREPROCESSOR_MACRO_USAGE_ERROR, macro.getNameCharArray());
    }
    return lastToken;
  }
  /**
   * Expects that the identifier of the macro expansion has been consumed. Expands the macro
   * consuming tokens from the input (to read the parameters) and stores the resulting tokens
   * together with boundary markers in the result token list. Returns the last token of the
   * expansion.
   *
   * @throws AbortMacroExpansionException
   */
  private Token expandOne(
      Token lastConsumed,
      PreprocessorMacro macro,
      IdentityHashMap<PreprocessorMacro, PreprocessorMacro> forbidden,
      TokenSource input,
      TokenList result,
      MacroExpansionTracker tracker)
      throws OffsetLimitReachedException {
    if (macro.isFunctionStyle()) {
      final int paramCount = macro.getParameterPlaceholderList().length;
      final TokenSource[] argInputs = new TokenSource[paramCount];
      final BitSet paramUsage = getParamUsage(macro);
      if (tracker != null) {
        tracker.startFunctionStyleMacro((Token) lastConsumed.clone());
      }
      try {
        lastConsumed =
            parseArguments(input, (FunctionStyleMacro) macro, forbidden, argInputs, tracker);
      } catch (AbortMacroExpansionException e) {
        // ignore this macro expansion
        for (int i = 0; i < argInputs.length; i++) {
          TokenSource argInput = argInputs[i];
          executeScopeMarkers(argInput, forbidden);
          if (tracker != null) {
            tracker.setExpandedMacroArgument(null);
          }
        }
        if (tracker != null) {
          if (tracker.isRequestedStep()) {
            tracker.storeFunctionStyleMacroReplacement(macro, new TokenList(), result);
          } else if (tracker.isDone()) {
            tracker.appendFunctionStyleMacro(result);
          }
          tracker.endFunctionStyleMacro();
        }
        return null;
      }

      TokenList[] clonedArgs = new TokenList[paramCount];
      TokenList[] expandedArgs = new TokenList[paramCount];
      for (int i = 0; i < paramCount; i++) {
        final TokenSource argInput = argInputs[i];
        final boolean needCopy = paramUsage.get(2 * i);
        final boolean needExpansion = paramUsage.get(2 * i + 1);
        clonedArgs[i] = needCopy ? argInput.cloneTokens() : EMPTY_TOKEN_LIST;
        expandedArgs[i] =
            needExpansion ? expandAll(argInput, forbidden, false, tracker) : EMPTY_TOKEN_LIST;
        if (!needExpansion) {
          executeScopeMarkers(argInput, forbidden);
        }

        if (tracker != null) {
          tracker.setExpandedMacroArgument(needExpansion ? expandedArgs[i] : null);
          // make sure that the trailing arguments do not get
          // expanded.
          if (tracker.isDone()) {
            paramUsage.clear();
          }
        }
      }
      if (tracker == null) {
        replaceArgs(macro, clonedArgs, expandedArgs, result);
      } else {
        if (tracker.isRequestedStep()) {
          TokenList replacement = new TokenList();
          replaceArgs(macro, clonedArgs, expandedArgs, replacement);
          tracker.storeFunctionStyleMacroReplacement(macro, replacement, result);
        } else if (tracker.isDone()) {
          tracker.appendFunctionStyleMacro(result);
        } else {
          replaceArgs(macro, clonedArgs, expandedArgs, result);
        }
        tracker.endFunctionStyleMacro();
      }
    } else {
      if (tracker == null) {
        objStyleTokenPaste(macro, result);
      } else {
        if (tracker.isRequestedStep()) {
          TokenList replacement = new TokenList();
          objStyleTokenPaste(macro, replacement);
          tracker.storeObjectStyleMacroReplacement(macro, lastConsumed, replacement, result);
        } else {
          objStyleTokenPaste(macro, result);
        }
        tracker.endObjectStyleMacro();
      }
    }
    return lastConsumed;
  }
  private TokenList expandAll(
      TokenSource input,
      IdentityHashMap<PreprocessorMacro, PreprocessorMacro> forbidden,
      boolean protectDefinedConstructs,
      MacroExpansionTracker tracker)
      throws OffsetLimitReachedException {
    final TokenList result = new TokenList();
    boolean protect = false;
    Token l = null;
    Token t = input.removeFirst();
    while (t != null) {
      switch (t.getType()) {
        case ObjCPreprocessor.tSCOPE_MARKER:
          ((ExpansionBoundary) t).execute(forbidden);
          break;
        case IToken.tIDENTIFIER:
          final char[] image = t.getCharImage();
          PreprocessorMacro macro = fDictionary.get(image);
          if (protect || (tracker != null && tracker.isDone())) {
            result.append(t);
          } else if (protectDefinedConstructs && Arrays.equals(image, Keywords.cDEFINED)) {
            t.setType(ObjCPreprocessor.tDEFINED);
            result.append(t);
            protect = true;
          }
          // tricky: don't mark function-style macros if you don't
          // find the left parenthesis
          else if (macro == null || (macro.isFunctionStyle() && !input.findLParenthesis())) {
            result.append(t);
          } else if (forbidden.containsKey(macro)) {
            t.setType(ObjCPreprocessor.tEXPANDED_IDENTIFIER); // prevent
            // any
            // further
            // expansion
            result.append(t);
          } else {
            if (fLocationMap != null) {
              ImageLocationInfo info = null;
              if (fLexOptions.fCreateImageLocations) {
                info = createImageLocationInfo(t);
              }
              fImplicitMacroExpansions.add(
                  fLocationMap.encounterImplicitMacroExpansion(macro, info));
            }
            TokenList replacement = new TokenList();

            addSpacemarker(l, t, replacement); // start expansion
            replacement.append(new ExpansionBoundary(macro, true));
            Token last = expandOne(t, macro, forbidden, input, replacement, tracker);
            replacement.append(new ExpansionBoundary(macro, false));
            addSpacemarker(last, input.first(), replacement); // end
            // expansion

            input.prepend(replacement);
          }
          break;
        case IToken.tLPAREN:
        case ObjCPreprocessor.tNOSPACE:
        case ObjCPreprocessor.tSPACE:
          result.append(t);
          break;
        default:
          protect = false;
          result.append(t);
          break;
      }
      l = t;
      t = input.removeFirst();
    }
    return result;
  }