401 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			401 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| //===----- EditedSource.cpp - Collection of source edits ------------------===//
 | |
| //
 | |
| //                     The LLVM Compiler Infrastructure
 | |
| //
 | |
| // This file is distributed under the University of Illinois Open Source
 | |
| // License. See LICENSE.TXT for details.
 | |
| //
 | |
| //===----------------------------------------------------------------------===//
 | |
| 
 | |
| #include "clang/Edit/EditedSource.h"
 | |
| #include "clang/Basic/CharInfo.h"
 | |
| #include "clang/Basic/SourceManager.h"
 | |
| #include "clang/Edit/Commit.h"
 | |
| #include "clang/Edit/EditsReceiver.h"
 | |
| #include "clang/Lex/Lexer.h"
 | |
| #include "llvm/ADT/SmallString.h"
 | |
| #include "llvm/ADT/Twine.h"
 | |
| 
 | |
| using namespace clang;
 | |
| using namespace edit;
 | |
| 
 | |
| void EditsReceiver::remove(CharSourceRange range) {
 | |
|   replace(range, StringRef());
 | |
| }
 | |
| 
 | |
| StringRef EditedSource::copyString(const Twine &twine) {
 | |
|   SmallString<128> Data;
 | |
|   return copyString(twine.toStringRef(Data));
 | |
| }
 | |
| 
 | |
| bool EditedSource::canInsertInOffset(SourceLocation OrigLoc, FileOffset Offs) {
 | |
|   FileEditsTy::iterator FA = getActionForOffset(Offs);
 | |
|   if (FA != FileEdits.end()) {
 | |
|     if (FA->first != Offs)
 | |
|       return false; // position has been removed.
 | |
|   }
 | |
| 
 | |
|   if (SourceMgr.isMacroArgExpansion(OrigLoc)) {
 | |
|     SourceLocation
 | |
|       DefArgLoc = SourceMgr.getImmediateExpansionRange(OrigLoc).first;
 | |
|     SourceLocation
 | |
|       ExpLoc = SourceMgr.getImmediateExpansionRange(DefArgLoc).first;
 | |
|     llvm::DenseMap<unsigned, SourceLocation>::iterator
 | |
|       I = ExpansionToArgMap.find(ExpLoc.getRawEncoding());
 | |
|     if (I != ExpansionToArgMap.end() && I->second != DefArgLoc)
 | |
|       return false; // Trying to write in a macro argument input that has
 | |
|                  // already been written for another argument of the same macro. 
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool EditedSource::commitInsert(SourceLocation OrigLoc,
 | |
|                                 FileOffset Offs, StringRef text,
 | |
|                                 bool beforePreviousInsertions) {
 | |
|   if (!canInsertInOffset(OrigLoc, Offs))
 | |
|     return false;
 | |
|   if (text.empty())
 | |
|     return true;
 | |
| 
 | |
|   if (SourceMgr.isMacroArgExpansion(OrigLoc)) {
 | |
|     SourceLocation
 | |
|       DefArgLoc = SourceMgr.getImmediateExpansionRange(OrigLoc).first;
 | |
|     SourceLocation
 | |
|       ExpLoc = SourceMgr.getImmediateExpansionRange(DefArgLoc).first;
 | |
|     ExpansionToArgMap[ExpLoc.getRawEncoding()] = DefArgLoc;
 | |
|   }
 | |
|   
 | |
|   FileEdit &FA = FileEdits[Offs];
 | |
|   if (FA.Text.empty()) {
 | |
|     FA.Text = copyString(text);
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   Twine concat;
 | |
|   if (beforePreviousInsertions)
 | |
|     concat = Twine(text) + FA.Text;
 | |
|   else
 | |
|     concat = Twine(FA.Text) +  text;
 | |
| 
 | |
|   FA.Text = copyString(concat);
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool EditedSource::commitInsertFromRange(SourceLocation OrigLoc,
 | |
|                                    FileOffset Offs,
 | |
|                                    FileOffset InsertFromRangeOffs, unsigned Len,
 | |
|                                    bool beforePreviousInsertions) {
 | |
|   if (Len == 0)
 | |
|     return true;
 | |
| 
 | |
|   SmallString<128> StrVec;
 | |
|   FileOffset BeginOffs = InsertFromRangeOffs;
 | |
|   FileOffset EndOffs = BeginOffs.getWithOffset(Len);
 | |
|   FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);
 | |
|   if (I != FileEdits.begin())
 | |
|     --I;
 | |
| 
 | |
|   for (; I != FileEdits.end(); ++I) {
 | |
|     FileEdit &FA = I->second;
 | |
|     FileOffset B = I->first;
 | |
|     FileOffset E = B.getWithOffset(FA.RemoveLen);
 | |
| 
 | |
|     if (BeginOffs == B)
 | |
|       break;
 | |
| 
 | |
|     if (BeginOffs < E) {
 | |
|       if (BeginOffs > B) {
 | |
|         BeginOffs = E;
 | |
|         ++I;
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   for (; I != FileEdits.end() && EndOffs > I->first; ++I) {
 | |
|     FileEdit &FA = I->second;
 | |
|     FileOffset B = I->first;
 | |
|     FileOffset E = B.getWithOffset(FA.RemoveLen);
 | |
| 
 | |
|     if (BeginOffs < B) {
 | |
|       bool Invalid = false;
 | |
|       StringRef text = getSourceText(BeginOffs, B, Invalid);
 | |
|       if (Invalid)
 | |
|         return false;
 | |
|       StrVec += text;
 | |
|     }
 | |
|     StrVec += FA.Text;
 | |
|     BeginOffs = E;
 | |
|   }
 | |
| 
 | |
|   if (BeginOffs < EndOffs) {
 | |
|     bool Invalid = false;
 | |
|     StringRef text = getSourceText(BeginOffs, EndOffs, Invalid);
 | |
|     if (Invalid)
 | |
|       return false;
 | |
|     StrVec += text;
 | |
|   }
 | |
| 
 | |
|   return commitInsert(OrigLoc, Offs, StrVec.str(), beforePreviousInsertions);
 | |
| }
 | |
| 
 | |
| void EditedSource::commitRemove(SourceLocation OrigLoc,
 | |
|                                 FileOffset BeginOffs, unsigned Len) {
 | |
|   if (Len == 0)
 | |
|     return;
 | |
| 
 | |
|   FileOffset EndOffs = BeginOffs.getWithOffset(Len);
 | |
|   FileEditsTy::iterator I = FileEdits.upper_bound(BeginOffs);
 | |
|   if (I != FileEdits.begin())
 | |
|     --I;
 | |
| 
 | |
|   for (; I != FileEdits.end(); ++I) {
 | |
|     FileEdit &FA = I->second;
 | |
|     FileOffset B = I->first;
 | |
|     FileOffset E = B.getWithOffset(FA.RemoveLen);
 | |
| 
 | |
|     if (BeginOffs < E)
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   FileOffset TopBegin, TopEnd;
 | |
|   FileEdit *TopFA = 0;
 | |
| 
 | |
|   if (I == FileEdits.end()) {
 | |
|     FileEditsTy::iterator
 | |
|       NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));
 | |
|     NewI->second.RemoveLen = Len;
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   FileEdit &FA = I->second;
 | |
|   FileOffset B = I->first;
 | |
|   FileOffset E = B.getWithOffset(FA.RemoveLen);
 | |
|   if (BeginOffs < B) {
 | |
|     FileEditsTy::iterator
 | |
|       NewI = FileEdits.insert(I, std::make_pair(BeginOffs, FileEdit()));
 | |
|     TopBegin = BeginOffs;
 | |
|     TopEnd = EndOffs;
 | |
|     TopFA = &NewI->second;
 | |
|     TopFA->RemoveLen = Len;
 | |
|   } else {
 | |
|     TopBegin = B;
 | |
|     TopEnd = E;
 | |
|     TopFA = &I->second;
 | |
|     if (TopEnd >= EndOffs)
 | |
|       return;
 | |
|     unsigned diff = EndOffs.getOffset() - TopEnd.getOffset();
 | |
|     TopEnd = EndOffs;
 | |
|     TopFA->RemoveLen += diff;
 | |
|     if (B == BeginOffs)
 | |
|       TopFA->Text = StringRef();
 | |
|     ++I;
 | |
|   }
 | |
| 
 | |
|   while (I != FileEdits.end()) {
 | |
|     FileEdit &FA = I->second;
 | |
|     FileOffset B = I->first;
 | |
|     FileOffset E = B.getWithOffset(FA.RemoveLen);
 | |
| 
 | |
|     if (B >= TopEnd)
 | |
|       break;
 | |
| 
 | |
|     if (E <= TopEnd) {
 | |
|       FileEdits.erase(I++);
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     if (B < TopEnd) {
 | |
|       unsigned diff = E.getOffset() - TopEnd.getOffset();
 | |
|       TopEnd = E;
 | |
|       TopFA->RemoveLen += diff;
 | |
|       FileEdits.erase(I);
 | |
|     }
 | |
| 
 | |
|     break;
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool EditedSource::commit(const Commit &commit) {
 | |
|   if (!commit.isCommitable())
 | |
|     return false;
 | |
| 
 | |
|   for (edit::Commit::edit_iterator
 | |
|          I = commit.edit_begin(), E = commit.edit_end(); I != E; ++I) {
 | |
|     const edit::Commit::Edit &edit = *I;
 | |
|     switch (edit.Kind) {
 | |
|     case edit::Commit::Act_Insert:
 | |
|       commitInsert(edit.OrigLoc, edit.Offset, edit.Text, edit.BeforePrev);
 | |
|       break;
 | |
|     case edit::Commit::Act_InsertFromRange:
 | |
|       commitInsertFromRange(edit.OrigLoc, edit.Offset,
 | |
|                             edit.InsertFromRangeOffs, edit.Length,
 | |
|                             edit.BeforePrev);
 | |
|       break;
 | |
|     case edit::Commit::Act_Remove:
 | |
|       commitRemove(edit.OrigLoc, edit.Offset, edit.Length);
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| // \brief Returns true if it is ok to make the two given characters adjacent.
 | |
| static bool canBeJoined(char left, char right, const LangOptions &LangOpts) {
 | |
|   // FIXME: Should use TokenConcatenation to make sure we don't allow stuff like
 | |
|   // making two '<' adjacent.
 | |
|   return !(Lexer::isIdentifierBodyChar(left, LangOpts) &&
 | |
|            Lexer::isIdentifierBodyChar(right, LangOpts));
 | |
| }
 | |
| 
 | |
| /// \brief Returns true if it is ok to eliminate the trailing whitespace between
 | |
| /// the given characters.
 | |
| static bool canRemoveWhitespace(char left, char beforeWSpace, char right,
 | |
|                                 const LangOptions &LangOpts) {
 | |
|   if (!canBeJoined(left, right, LangOpts))
 | |
|     return false;
 | |
|   if (isWhitespace(left) || isWhitespace(right))
 | |
|     return true;
 | |
|   if (canBeJoined(beforeWSpace, right, LangOpts))
 | |
|     return false; // the whitespace was intentional, keep it.
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| /// \brief Check the range that we are going to remove and:
 | |
| /// -Remove any trailing whitespace if possible.
 | |
| /// -Insert a space if removing the range is going to mess up the source tokens.
 | |
| static void adjustRemoval(const SourceManager &SM, const LangOptions &LangOpts,
 | |
|                           SourceLocation Loc, FileOffset offs,
 | |
|                           unsigned &len, StringRef &text) {
 | |
|   assert(len && text.empty());
 | |
|   SourceLocation BeginTokLoc = Lexer::GetBeginningOfToken(Loc, SM, LangOpts);
 | |
|   if (BeginTokLoc != Loc)
 | |
|     return; // the range is not at the beginning of a token, keep the range.
 | |
| 
 | |
|   bool Invalid = false;
 | |
|   StringRef buffer = SM.getBufferData(offs.getFID(), &Invalid);
 | |
|   if (Invalid)
 | |
|     return;
 | |
| 
 | |
|   unsigned begin = offs.getOffset();
 | |
|   unsigned end = begin + len;
 | |
| 
 | |
|   // FIXME: Remove newline.
 | |
| 
 | |
|   if (begin == 0) {
 | |
|     if (buffer[end] == ' ')
 | |
|       ++len;
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (buffer[end] == ' ') {
 | |
|     if (canRemoveWhitespace(/*left=*/buffer[begin-1],
 | |
|                             /*beforeWSpace=*/buffer[end-1],
 | |
|                             /*right=*/buffer[end+1],
 | |
|                             LangOpts))
 | |
|       ++len;
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!canBeJoined(buffer[begin-1], buffer[end], LangOpts))
 | |
|     text = " ";
 | |
| }
 | |
| 
 | |
| static void applyRewrite(EditsReceiver &receiver,
 | |
|                          StringRef text, FileOffset offs, unsigned len,
 | |
|                          const SourceManager &SM, const LangOptions &LangOpts) {
 | |
|   assert(!offs.getFID().isInvalid());
 | |
|   SourceLocation Loc = SM.getLocForStartOfFile(offs.getFID());
 | |
|   Loc = Loc.getLocWithOffset(offs.getOffset());
 | |
|   assert(Loc.isFileID());
 | |
| 
 | |
|   if (text.empty())
 | |
|     adjustRemoval(SM, LangOpts, Loc, offs, len, text);
 | |
| 
 | |
|   CharSourceRange range = CharSourceRange::getCharRange(Loc,
 | |
|                                                      Loc.getLocWithOffset(len));
 | |
| 
 | |
|   if (text.empty()) {
 | |
|     assert(len);
 | |
|     receiver.remove(range);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (len)
 | |
|     receiver.replace(range, text);
 | |
|   else
 | |
|     receiver.insert(Loc, text);
 | |
| }
 | |
| 
 | |
| void EditedSource::applyRewrites(EditsReceiver &receiver) {
 | |
|   SmallString<128> StrVec;
 | |
|   FileOffset CurOffs, CurEnd;
 | |
|   unsigned CurLen;
 | |
| 
 | |
|   if (FileEdits.empty())
 | |
|     return;
 | |
| 
 | |
|   FileEditsTy::iterator I = FileEdits.begin();
 | |
|   CurOffs = I->first;
 | |
|   StrVec = I->second.Text;
 | |
|   CurLen = I->second.RemoveLen;
 | |
|   CurEnd = CurOffs.getWithOffset(CurLen);
 | |
|   ++I;
 | |
| 
 | |
|   for (FileEditsTy::iterator E = FileEdits.end(); I != E; ++I) {
 | |
|     FileOffset offs = I->first;
 | |
|     FileEdit act = I->second;
 | |
|     assert(offs >= CurEnd);
 | |
| 
 | |
|     if (offs == CurEnd) {
 | |
|       StrVec += act.Text;
 | |
|       CurLen += act.RemoveLen;
 | |
|       CurEnd.getWithOffset(act.RemoveLen);
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     applyRewrite(receiver, StrVec.str(), CurOffs, CurLen, SourceMgr, LangOpts);
 | |
|     CurOffs = offs;
 | |
|     StrVec = act.Text;
 | |
|     CurLen = act.RemoveLen;
 | |
|     CurEnd = CurOffs.getWithOffset(CurLen);
 | |
|   }
 | |
| 
 | |
|   applyRewrite(receiver, StrVec.str(), CurOffs, CurLen, SourceMgr, LangOpts);
 | |
| }
 | |
| 
 | |
| void EditedSource::clearRewrites() {
 | |
|   FileEdits.clear();
 | |
|   StrAlloc.Reset();
 | |
| }
 | |
| 
 | |
| StringRef EditedSource::getSourceText(FileOffset BeginOffs, FileOffset EndOffs,
 | |
|                                       bool &Invalid) {
 | |
|   assert(BeginOffs.getFID() == EndOffs.getFID());
 | |
|   assert(BeginOffs <= EndOffs);
 | |
|   SourceLocation BLoc = SourceMgr.getLocForStartOfFile(BeginOffs.getFID());
 | |
|   BLoc = BLoc.getLocWithOffset(BeginOffs.getOffset());
 | |
|   assert(BLoc.isFileID());
 | |
|   SourceLocation
 | |
|     ELoc = BLoc.getLocWithOffset(EndOffs.getOffset() - BeginOffs.getOffset());
 | |
|   return Lexer::getSourceText(CharSourceRange::getCharRange(BLoc, ELoc),
 | |
|                               SourceMgr, LangOpts, &Invalid);
 | |
| }
 | |
| 
 | |
| EditedSource::FileEditsTy::iterator
 | |
| EditedSource::getActionForOffset(FileOffset Offs) {
 | |
|   FileEditsTy::iterator I = FileEdits.upper_bound(Offs);
 | |
|   if (I == FileEdits.begin())
 | |
|     return FileEdits.end();
 | |
|   --I;
 | |
|   FileEdit &FA = I->second;
 | |
|   FileOffset B = I->first;
 | |
|   FileOffset E = B.getWithOffset(FA.RemoveLen);
 | |
|   if (Offs >= B && Offs < E)
 | |
|     return I;
 | |
| 
 | |
|   return FileEdits.end();
 | |
| }
 | 
