VirtualBox

source: vbox/trunk/src/bldprogs/scmdiff.cpp@ 107044

Last change on this file since 107044 was 106061, checked in by vboxsync, 2 months ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 16.3 KB
Line 
1/* $Id: scmdiff.cpp 106061 2024-09-16 14:03:52Z vboxsync $ */
2/** @file
3 * IPRT Testcase / Tool - Source Code Massager.
4 */
5
6/*
7 * Copyright (C) 2010-2024 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28
29/*********************************************************************************************************************************
30* Header Files *
31*********************************************************************************************************************************/
32#include <iprt/assert.h>
33#include <iprt/ctype.h>
34#include <iprt/message.h>
35#include <iprt/stream.h>
36#include <iprt/string.h>
37
38#include "scmdiff.h"
39
40
41/*********************************************************************************************************************************
42* Global Variables *
43*********************************************************************************************************************************/
44static const char g_szTabSpaces[16+1] = " ";
45
46
47
48/**
49 * Prints a range of lines with a prefix.
50 *
51 * @param pState The diff state.
52 * @param chPrefix The prefix.
53 * @param pStream The stream to get the lines from.
54 * @param iLine The first line.
55 * @param cLines The number of lines.
56 */
57static void scmDiffPrintLines(PSCMDIFFSTATE pState, char chPrefix, PSCMSTREAM pStream, size_t iLine, size_t cLines)
58{
59 while (cLines-- > 0)
60 {
61 SCMEOL enmEol;
62 size_t cchLine;
63 const char *pchLine = ScmStreamGetLineByNo(pStream, iLine, &cchLine, &enmEol);
64
65 RTStrmPutCh(pState->pDiff, chPrefix);
66 if (pchLine && cchLine)
67 {
68 if (!pState->fSpecialChars)
69 RTStrmWrite(pState->pDiff, pchLine, cchLine);
70 else
71 {
72 size_t offVir = 0;
73 const char *pchStart = pchLine;
74 const char *pchTab = (const char *)memchr(pchLine, '\t', cchLine);
75 while (pchTab)
76 {
77 RTStrmWrite(pState->pDiff, pchStart, pchTab - pchStart);
78 offVir += pchTab - pchStart;
79
80 size_t cchTab = pState->cchTab - offVir % pState->cchTab;
81 switch (cchTab)
82 {
83 case 1: RTStrmPutStr(pState->pDiff, "."); break;
84 case 2: RTStrmPutStr(pState->pDiff, ".."); break;
85 case 3: RTStrmPutStr(pState->pDiff, "[T]"); break;
86 case 4: RTStrmPutStr(pState->pDiff, "[TA]"); break;
87 case 5: RTStrmPutStr(pState->pDiff, "[TAB]"); break;
88 default: RTStrmPrintf(pState->pDiff, "[TAB%.*s]", cchTab - 5, g_szTabSpaces); break;
89 }
90 offVir += cchTab;
91
92 /* next */
93 pchStart = pchTab + 1;
94 pchTab = (const char *)memchr(pchStart, '\t', cchLine - (pchStart - pchLine));
95 }
96 size_t cchLeft = cchLine - (pchStart - pchLine);
97 if (cchLeft)
98 RTStrmWrite(pState->pDiff, pchStart, cchLeft);
99 }
100 }
101
102 if (!pState->fSpecialChars)
103 RTStrmPutCh(pState->pDiff, '\n');
104 else if (enmEol == SCMEOL_LF)
105 RTStrmPutStr(pState->pDiff, "[LF]\n");
106 else if (enmEol == SCMEOL_CRLF)
107 RTStrmPutStr(pState->pDiff, "[CRLF]\n");
108 else
109 RTStrmPutStr(pState->pDiff, "[NONE]\n");
110
111 iLine++;
112 }
113}
114
115
116/**
117 * Reports a difference and propels the streams to the lines following the
118 * resync.
119 *
120 *
121 * @returns New pState->cDiff value (just to return something).
122 * @param pState The diff state. The cDiffs member will be
123 * incremented.
124 * @param cMatches The resync length.
125 * @param iLeft Where the difference starts on the left side.
126 * @param cLeft How long it is on this side. ~(size_t)0 is used
127 * to indicate that it goes all the way to the end.
128 * @param iRight Where the difference starts on the right side.
129 * @param cRight How long it is.
130 */
131static size_t scmDiffReport(PSCMDIFFSTATE pState, size_t cMatches,
132 size_t iLeft, size_t cLeft,
133 size_t iRight, size_t cRight)
134{
135 /*
136 * Adjust the input.
137 */
138 if (cLeft == ~(size_t)0)
139 {
140 size_t c = ScmStreamCountLines(pState->pLeft);
141 if (c >= iLeft)
142 cLeft = c - iLeft;
143 else
144 {
145 iLeft = c;
146 cLeft = 0;
147 }
148 }
149
150 if (cRight == ~(size_t)0)
151 {
152 size_t c = ScmStreamCountLines(pState->pRight);
153 if (c >= iRight)
154 cRight = c - iRight;
155 else
156 {
157 iRight = c;
158 cRight = 0;
159 }
160 }
161
162 /*
163 * Print header if it's the first difference
164 */
165 if (!pState->cDiffs)
166 RTStrmPrintf(pState->pDiff, "diff %s %s\n", pState->pszFilename, pState->pszFilename);
167
168 /*
169 * Emit the change description.
170 */
171 char ch = cLeft == 0
172 ? 'a'
173 : cRight == 0
174 ? 'd'
175 : 'c';
176 if (cLeft > 1 && cRight > 1)
177 RTStrmPrintf(pState->pDiff, "%zu,%zu%c%zu,%zu\n", iLeft + 1, iLeft + cLeft, ch, iRight + 1, iRight + cRight);
178 else if (cLeft > 1)
179 RTStrmPrintf(pState->pDiff, "%zu,%zu%c%zu\n", iLeft + 1, iLeft + cLeft, ch, iRight + 1);
180 else if (cRight > 1)
181 RTStrmPrintf(pState->pDiff, "%zu%c%zu,%zu\n", iLeft + 1, ch, iRight + 1, iRight + cRight);
182 else
183 RTStrmPrintf(pState->pDiff, "%zu%c%zu\n", iLeft + 1, ch, iRight + 1);
184
185 /*
186 * And the lines.
187 */
188 if (cLeft)
189 scmDiffPrintLines(pState, '<', pState->pLeft, iLeft, cLeft);
190 if (cLeft && cRight)
191 RTStrmPrintf(pState->pDiff, "---\n");
192 if (cRight)
193 scmDiffPrintLines(pState, '>', pState->pRight, iRight, cRight);
194
195 /*
196 * Reposition the streams (safely ignores return value).
197 */
198 ScmStreamSeekByLine(pState->pLeft, iLeft + cLeft + cMatches);
199 ScmStreamSeekByLine(pState->pRight, iRight + cRight + cMatches);
200
201 pState->cDiffs++;
202 return pState->cDiffs;
203}
204
205/**
206 * Helper for scmDiffCompare that takes care of trailing spaces and stuff
207 * like that.
208 */
209static bool scmDiffCompareSlow(PSCMDIFFSTATE pState,
210 const char *pchLeft, size_t cchLeft, SCMEOL enmEolLeft,
211 const char *pchRight, size_t cchRight, SCMEOL enmEolRight)
212{
213 if (pState->fIgnoreTrailingWhite)
214 {
215 while (cchLeft > 0 && RT_C_IS_SPACE(pchLeft[cchLeft - 1]))
216 cchLeft--;
217 while (cchRight > 0 && RT_C_IS_SPACE(pchRight[cchRight - 1]))
218 cchRight--;
219 }
220
221 if (pState->fIgnoreLeadingWhite)
222 {
223 while (cchLeft > 0 && RT_C_IS_SPACE(*pchLeft))
224 pchLeft++, cchLeft--;
225 while (cchRight > 0 && RT_C_IS_SPACE(*pchRight))
226 pchRight++, cchRight--;
227 }
228
229 if ( cchLeft != cchRight
230 || (enmEolLeft != enmEolRight && !pState->fIgnoreEol)
231 || memcmp(pchLeft, pchRight, cchLeft))
232 return false;
233 return true;
234}
235
236/**
237 * Compare two lines.
238 *
239 * @returns true if the are equal, false if not.
240 */
241DECLINLINE(bool) scmDiffCompare(PSCMDIFFSTATE pState,
242 const char *pchLeft, size_t cchLeft, SCMEOL enmEolLeft,
243 const char *pchRight, size_t cchRight, SCMEOL enmEolRight)
244{
245 if ( cchLeft != cchRight
246 || (enmEolLeft != enmEolRight && !pState->fIgnoreEol)
247 || memcmp(pchLeft, pchRight, cchLeft))
248 {
249 if ( pState->fIgnoreTrailingWhite
250 || pState->fIgnoreLeadingWhite)
251 return scmDiffCompareSlow(pState,
252 pchLeft, cchLeft, enmEolLeft,
253 pchRight, cchRight, enmEolRight);
254 return false;
255 }
256 return true;
257}
258
259/**
260 * Compares two sets of lines from the two files.
261 *
262 * @returns true if they matches, false if they don't.
263 * @param pState The diff state.
264 * @param iLeft Where to start in the left stream.
265 * @param iRight Where to start in the right stream.
266 * @param cLines How many lines to compare.
267 */
268static bool scmDiffCompareLines(PSCMDIFFSTATE pState, size_t iLeft, size_t iRight, size_t cLines)
269{
270 for (size_t iLine = 0; iLine < cLines; iLine++)
271 {
272 SCMEOL enmEolLeft;
273 size_t cchLeft;
274 const char *pchLeft = ScmStreamGetLineByNo(pState->pLeft, iLeft + iLine, &cchLeft, &enmEolLeft);
275
276 SCMEOL enmEolRight;
277 size_t cchRight;
278 const char *pchRight = ScmStreamGetLineByNo(pState->pRight, iRight + iLine, &cchRight, &enmEolRight);
279
280 if (!scmDiffCompare(pState, pchLeft, cchLeft, enmEolLeft, pchRight, cchRight, enmEolRight))
281 return false;
282 }
283 return true;
284}
285
286
287/**
288 * Resynchronize the two streams and reports the difference.
289 *
290 * Upon return, the streams will be positioned after the block of @a cMatches
291 * lines where it resynchronized them.
292 *
293 * @returns pState->cDiffs (just so we can use it in a return statement).
294 * @param pState The state.
295 * @param cMatches The number of lines that needs to match for the
296 * stream to be considered synchronized again.
297 */
298static size_t scmDiffSynchronize(PSCMDIFFSTATE pState, size_t cMatches)
299{
300 size_t const iStartLeft = ScmStreamTellLine(pState->pLeft) - 1;
301 size_t const iStartRight = ScmStreamTellLine(pState->pRight) - 1;
302 Assert(cMatches > 0);
303
304 /*
305 * Compare each new line from each of the streams will all the preceding
306 * ones, including iStartLeft/Right.
307 */
308 for (size_t iRange = 1; ; iRange++)
309 {
310 /*
311 * Get the next line in the left stream and compare it against all the
312 * preceding lines on the right side.
313 */
314 SCMEOL enmEol;
315 size_t cchLine;
316 const char *pchLine = ScmStreamGetLineByNo(pState->pLeft, iStartLeft + iRange, &cchLine, &enmEol);
317 if (!pchLine)
318 return scmDiffReport(pState, 0, iStartLeft, ~(size_t)0, iStartRight, ~(size_t)0);
319
320 for (size_t iRight = cMatches - 1; iRight < iRange; iRight++)
321 {
322 SCMEOL enmEolRight;
323 size_t cchRight;
324 const char *pchRight = ScmStreamGetLineByNo(pState->pRight, iStartRight + iRight,
325 &cchRight, &enmEolRight);
326 if ( scmDiffCompare(pState, pchLine, cchLine, enmEol, pchRight, cchRight, enmEolRight)
327 && scmDiffCompareLines(pState,
328 iStartLeft + iRange + 1 - cMatches,
329 iStartRight + iRight + 1 - cMatches,
330 cMatches - 1)
331 )
332 return scmDiffReport(pState, cMatches,
333 iStartLeft, iRange + 1 - cMatches,
334 iStartRight, iRight + 1 - cMatches);
335 }
336
337 /*
338 * Get the next line in the right stream and compare it against all the
339 * lines on the right side.
340 */
341 pchLine = ScmStreamGetLineByNo(pState->pRight, iStartRight + iRange, &cchLine, &enmEol);
342 if (!pchLine)
343 return scmDiffReport(pState, 0, iStartLeft, ~(size_t)0, iStartRight, ~(size_t)0);
344
345 for (size_t iLeft = cMatches - 1; iLeft <= iRange; iLeft++)
346 {
347 SCMEOL enmEolLeft;
348 size_t cchLeft;
349 const char *pchLeft = ScmStreamGetLineByNo(pState->pLeft, iStartLeft + iLeft,
350 &cchLeft, &enmEolLeft);
351 if ( scmDiffCompare(pState, pchLeft, cchLeft, enmEolLeft, pchLine, cchLine, enmEol)
352 && scmDiffCompareLines(pState,
353 iStartLeft + iLeft + 1 - cMatches,
354 iStartRight + iRange + 1 - cMatches,
355 cMatches - 1)
356 )
357 return scmDiffReport(pState, cMatches,
358 iStartLeft, iLeft + 1 - cMatches,
359 iStartRight, iRange + 1 - cMatches);
360 }
361 }
362}
363
364/**
365 * Creates a diff of the changes between the streams @a pLeft and @a pRight.
366 *
367 * This currently only implements the simplest diff format, so no contexts.
368 *
369 * Also, note that we won't detect differences in the final newline of the
370 * streams.
371 *
372 * @returns The number of differences.
373 * @param pszFilename The filename.
374 * @param pLeft The left side stream.
375 * @param pRight The right side stream.
376 * @param fIgnoreEol Whether to ignore end of line markers.
377 * @param fIgnoreLeadingWhite Set if leading white space should be ignored.
378 * @param fIgnoreTrailingWhite Set if trailing white space should be ignored.
379 * @param fSpecialChars Whether to print special chars in a human
380 * readable form or not.
381 * @param cchTab The tab size.
382 * @param pDiff Where to write the diff.
383 */
384size_t ScmDiffStreams(const char *pszFilename, PSCMSTREAM pLeft, PSCMSTREAM pRight, bool fIgnoreEol,
385 bool fIgnoreLeadingWhite, bool fIgnoreTrailingWhite, bool fSpecialChars,
386 size_t cchTab, PRTSTREAM pDiff)
387{
388#ifdef RT_STRICT
389 ScmStreamCheckItegrity(pLeft);
390 ScmStreamCheckItegrity(pRight);
391#endif
392
393 /*
394 * Set up the diff state.
395 */
396 SCMDIFFSTATE State;
397 State.cDiffs = 0;
398 State.pszFilename = pszFilename;
399 State.pLeft = pLeft;
400 State.pRight = pRight;
401 State.fIgnoreEol = fIgnoreEol;
402 State.fIgnoreLeadingWhite = fIgnoreLeadingWhite;
403 State.fIgnoreTrailingWhite = fIgnoreTrailingWhite;
404 State.fSpecialChars = fSpecialChars;
405 State.cchTab = cchTab;
406 State.pDiff = pDiff;
407
408 /*
409 * Compare them line by line.
410 */
411 ScmStreamRewindForReading(pLeft);
412 ScmStreamRewindForReading(pRight);
413 const char *pchLeft;
414 const char *pchRight;
415
416 for (;;)
417 {
418 SCMEOL enmEolLeft;
419 size_t cchLeft;
420 pchLeft = ScmStreamGetLine(pLeft, &cchLeft, &enmEolLeft);
421
422 SCMEOL enmEolRight;
423 size_t cchRight;
424 pchRight = ScmStreamGetLine(pRight, &cchRight, &enmEolRight);
425 if (!pchLeft || !pchRight)
426 break;
427
428 if (!scmDiffCompare(&State, pchLeft, cchLeft, enmEolLeft, pchRight, cchRight, enmEolRight))
429 scmDiffSynchronize(&State, 3);
430 }
431
432 /*
433 * Deal with any remaining differences.
434 */
435 if (pchLeft)
436 scmDiffReport(&State, 0, ScmStreamTellLine(pLeft) - 1, ~(size_t)0, ScmStreamTellLine(pRight), 0);
437 else if (pchRight)
438 scmDiffReport(&State, 0, ScmStreamTellLine(pLeft), 0, ScmStreamTellLine(pRight) - 1, ~(size_t)0);
439
440 /*
441 * Report any errors.
442 */
443 if (RT_FAILURE(ScmStreamGetStatus(pLeft)))
444 RTMsgError("Left diff stream error: %Rrc\n", ScmStreamGetStatus(pLeft));
445 if (RT_FAILURE(ScmStreamGetStatus(pRight)))
446 RTMsgError("Right diff stream error: %Rrc\n", ScmStreamGetStatus(pRight));
447
448 return State.cDiffs;
449}
450
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette