Index: llvm/lib/Passes/StandardInstrumentations.cpp =================================================================== --- llvm/lib/Passes/StandardInstrumentations.cpp +++ llvm/lib/Passes/StandardInstrumentations.cpp @@ -37,6 +37,7 @@ #include "llvm/Support/Regex.h" #include "llvm/Support/Signals.h" #include "llvm/Support/raw_ostream.h" +#include #include #include #include @@ -316,6 +317,217 @@ return true; } +class DiffHandler { +public: + DiffHandler(std::string RColourOn, std::string RPrefix, + std::string RColourOff, std::string AColourOn, + std::string APrefix, std::string AColourOff, + std::string CColourOn, std::string CPrefix, + std::optional NCPrefix, std::string CColourOff, + std::string NewLine) + : RColourOn(RColourOn), RPrefix(RPrefix), RColourOff(RColourOff), + AColourOn(AColourOn), APrefix(APrefix), AColourOff(AColourOff), + CColourOn(CColourOn), CPrefix(CPrefix), NCPrefix(NCPrefix), + CColourOff(CColourOff), NewLine(NewLine), + SearchRPrefix(RColourOn + RPrefix), SearchRSuffix(RColourOff + NewLine), + SearchAPrefix(AColourOn + APrefix), SearchASuffix(AColourOff + NewLine), + Removed(RColourOn + RPrefix + "%l" + RColourOff + NewLine), + Added(AColourOn + APrefix + "%l" + AColourOff + NewLine), + NoChange(CColourOn + CPrefix + "%l" + CColourOff +NewLine) {} + + // Try to combine lines that only differ in an variable name or attribute. + // \p In is the result of the system diff so find a group of lines (possibly + // a single line) that are being removed. If this is followed by a group + // of lines that are being added. While the first of each group can be + // merged, keep trying to merge them. A line can be merged by splitting + // it up on any of the following chars: "%@!#" If the lines only differ + // by the id (or number) following the char, then merge the lines by + // replacing these sections with the before and after parts in their + // appropriate (possibly colourized) formats and mark the line with NCPrefix. + std::string mergeLines(StringRef In) { + std::string Out; + while (true) { + // output everything until a removal line + auto SP = In.split(SearchRPrefix); + Out += SP.first; + if (SP.second == "") + // nothing left + break; + In = SP.second; + // collect series of removals + SmallVector Removals; + do { + auto RS = In.split(SearchRSuffix); + Removals.emplace_back(RS.first); + In = RS.second; + } while (In.consume_front(SearchRPrefix)); + + // match against as many addition lines as possible + unsigned J = 0; + std::optional UnmatchedAddition; + while (J < Removals.size()) { + // Is there an addition line? + if (!In.consume_front(SearchAPrefix)) + break; + // Get addition line + auto AP = In.split(SearchASuffix); + UnmatchedAddition = AP.first; + In = AP.second; + auto Line = merge(Removals[J], AP.first); + if (!Line) + break; + Out += *Line; + UnmatchedAddition.reset(); + J++; + } + // Dump the remaining removal lines + while (J < Removals.size()) + Out += RColourOn + RPrefix + Removals[J++].str() + RColourOff + NewLine; + // Is there a waiting addition line? + if (UnmatchedAddition) + Out += SearchAPrefix + UnmatchedAddition->str() + SearchASuffix; + } + return Out; + } + + // Return the merging of lines \p S1 and \p S2 or None if they cannot + // be merged. + std::optional merge(StringRef S1, StringRef S2) { + SmallVector Rs, As; + SmallVector RPrefixChars, APrefixChars; + splitString(S1, Rs, RPrefixChars); + assert(Rs.size() == RPrefixChars.size() && + "Invalid results from splitString"); + // Does it have an id or attribute in it? + if (Rs.size() == 1) + return {}; + splitString(S2, As, APrefixChars); + assert(As.size() == APrefixChars.size() && + "Invalid results from splitString"); + // Didn't split up same way so not a match + if (Rs.size() != As.size() || Rs[0] != As[0] || + RPrefixChars[0] != APrefixChars[0]) + return {}; + std::string S; + if (NCPrefix) + S += RColourOn + *NCPrefix + RColourOff; + S += CColourOn + Rs[0].str(); + for (size_t I = 1; I < Rs.size(); ++I) { + // Did they split on the same character? + if (RPrefixChars[I] != APrefixChars[I]) + return {}; + + // Just add it if they match + if (Rs[I] == As[I]) { + S += RPrefixChars[I] + Rs[I].str(); + continue; + } + // Pull out the id or number + Regex IdRx("[-a-zA-Z$._0-9]+"); + std::string Error; + // Get the removed part other than the id + std::string Removed = IdRx.sub("", Rs[I].str(), &Error); + if (Error != "") + return {}; + // Get the added part other than the id + std::string Added = IdRx.sub("", As[I].str(), &Error); + if (Error != "") + return {}; + // Do the added and removed parts match? + if (Removed != Added) + return {}; + // merge it + S += CColourOff + SearchRPrefix + RPrefixChars[I] + + Rs[I].drop_back(Removed.size()).str() + RColourOff + SearchAPrefix + + RPrefixChars[I] + As[I].drop_back(Added.size()).str() + AColourOff + + CColourOn + Removed; + } + return {S + NewLine + CColourOff}; + } + +protected: + void splitString(StringRef S, SmallVectorImpl &A, + SmallVectorImpl &Prefixes) { + Prefixes.push_back('\0'); + while (true) { + size_t Idx = S.find_first_of("%@!#"); + if (Idx == StringRef::npos) + break; + + // Push this split. + A.push_back(S.slice(0, Idx)); + Prefixes.push_back(S[Idx]); + + // Jump forward. + S = S.slice(Idx + 1, StringRef::npos); + } + + // Push the tail. + A.push_back(S); + } + + const std::string RColourOn; + const std::string RPrefix; + const std::string RColourOff; + const std::string AColourOn; + const std::string APrefix; + const std::string AColourOff; + const std::string CColourOn; + const std::string CPrefix; + const std::optional NCPrefix; + const std::string CColourOff; + const std::string NewLine; + const std::string SearchRPrefix; + const std::string SearchRSuffix; + const std::string SearchAPrefix; + const std::string SearchASuffix; + const std::string Removed; + const std::string Added; + const std::string NoChange; +}; + +class InlineDiffHandler : public DiffHandler { +public: + InlineDiffHandler(bool UseColour) + : DiffHandler(UseColour ? "\033[31m" : "", "-", + UseColour ? "\033[0m" : "", UseColour ? "\033[32m" : "", + "+", UseColour ? "\033[0m" : "", "", " ", {"*"}, "", "\n") { + } + + std::string diff(StringRef Before, StringRef After) { + return mergeLines(doSystemDiff(Before, After, Removed, Added, NoChange)); + } +}; + +class DotCfgDiffHandler : public DiffHandler { +public: + DotCfgDiffHandler() + : DiffHandler("", "", "", + "", "", "", + "", "", {}, + "", "
") {} + + std::string diff(StringRef Before, StringRef After) { + std::string Diff = doSystemDiff(Before, After, Removed, Added, NoChange); + + // Diff adds in some empty colour changes which are not valid HTML + // so remove them. Colours are all lowercase alpha characters (as + // listed in https://graphviz.org/pdf/dotguide.pdf). + Regex R(""); + while (true) { + std::string Error; + std::string S = R.sub("", Diff, &Error); + if (Error != "") + return Error; + if (S == Diff) { + return mergeLines(Diff); + } + Diff = S; + } + llvm_unreachable("should not get here"); + } +}; + } // namespace template ChangeReporter::~ChangeReporter() { @@ -1202,11 +1414,8 @@ [&](const BlockDataT *B, const BlockDataT *A) { StringRef BStr = B ? B->getBody() : "\n"; StringRef AStr = A ? A->getBody() : "\n"; - const std::string Removed = - UseColour ? "\033[31m-%l\033[0m\n" : "-%l\n"; - const std::string Added = UseColour ? "\033[32m+%l\033[0m\n" : "+%l\n"; - const std::string NoChange = " %l\n"; - Out << doSystemDiff(BStr, AStr, Removed, Added, NoChange); + static InlineDiffHandler DH(UseColour); + Out << DH.diff(BStr, AStr); }); } @@ -1568,31 +1777,10 @@ SR[I] = SR[I].drop_until([](char C) { return C == '\n'; }).drop_front(); } - SmallString<80> OldLineFormat = formatv( - "%l
", BeforeColour); - SmallString<80> NewLineFormat = formatv( - "%l
", AfterColour); - SmallString<80> UnchangedLineFormat = formatv( - "%l
", CommonColour); - std::string Diff = Data[0]->getLabel().str(); - Diff += ":\n
" + - doSystemDiff(makeHTMLReady(SR[0]), makeHTMLReady(SR[1]), - OldLineFormat, NewLineFormat, UnchangedLineFormat); + static DotCfgDiffHandler DH; - // Diff adds in some empty colour changes which are not valid HTML - // so remove them. Colours are all lowercase alpha characters (as - // listed in https://graphviz.org/pdf/dotguide.pdf). - Regex R(""); - while (true) { - std::string Error; - std::string S = R.sub("", Diff, &Error); - if (Error != "") - return Error; - if (S == Diff) - return Diff; - Diff = S; - } - llvm_unreachable("Should not get here"); + return Data[0]->getLabel().str() + ":\n
" + + DH.diff(makeHTMLReady(SR[0]), makeHTMLReady(SR[1])); } // Put node out in the appropriate colour.