7a5d7382c59d2b96fb77f97c183f28206dc2c17a
2 * PROJECT: ReactOS FC Command
3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4 * PURPOSE: Comparing text files
5 * COPYRIGHT: Copyright 2021 Katayama Hirofumi MZ (katayama.hirofumi.mz@gmail.com)
9 #define IS_SPACE(ch) ((ch) == TEXT(' ') || (ch) == TEXT('\t'))
13 #define PrintLine PrintLineW
14 #define TextCompare TextCompareW
17 #define PrintLine PrintLineA
18 #define TextCompare TextCompareA
21 static LPTSTR
AllocLine(LPCTSTR pch
, DWORD cch
)
23 LPTSTR pszNew
= malloc((cch
+ 1) * sizeof(TCHAR
));
26 memcpy(pszNew
, pch
, cch
* sizeof(TCHAR
));
31 static NODE
*AllocNode(LPTSTR psz
, DWORD lineno
)
36 node
= calloc(1, sizeof(NODE
));
43 node
->lineno
= lineno
;
47 static __inline VOID
DeleteNode(NODE
*node
)
57 static VOID
DeleteList(struct list
*list
)
61 while ((ptr
= list_head(list
)) != NULL
)
64 node
= LIST_ENTRY(ptr
, NODE
, entry
);
69 static __inline LPCTSTR
SkipSpace(LPCTSTR pch
)
71 while (IS_SPACE(*pch
))
76 static __inline LPCTSTR
FindLastNonSpace(LPCTSTR pch
)
78 LPCTSTR pchLast
= NULL
;
88 static VOID
DeleteDuplicateSpaces(LPTSTR psz
)
91 for (pch0
= pch1
= psz
; *pch0
; ++pch0
)
99 } while (IS_SPACE(*pch0
));
106 static LPTSTR
CompressSpace(LPCTSTR line
)
111 line
= SkipSpace(line
);
112 pchLast
= FindLastNonSpace(line
);
114 return AllocLine(NULL
, 0);
116 pszNew
= AllocLine(line
, (DWORD
)(pchLast
- line
) + 1);
120 DeleteDuplicateSpaces(pszNew
);
126 static INT
ExpandTabLength(LPCTSTR line
)
130 for (pch
= line
; *pch
; ++pch
)
132 if (*pch
== TEXT('\t'))
133 cch
+= TAB_WIDTH
- (cch
% TAB_WIDTH
);
140 static LPTSTR
ExpandTab(LPCTSTR line
)
142 INT spaces
, cch
= ExpandTabLength(line
), ich
;
143 LPTSTR pszNew
= malloc((cch
+ 1) * sizeof(TCHAR
));
148 for (pch
= line
; *pch
; ++pch
)
150 if (*pch
== TEXT('\t'))
152 spaces
= TAB_WIDTH
- (ich
% TAB_WIDTH
);
155 pszNew
[ich
++] = TEXT(' ');
160 pszNew
[ich
++] = *pch
;
167 #define HASH_EOF 0xFFFFFFFF
168 #define HASH_MASK 0x7FFFFFFF
170 static DWORD
GetHash(LPCTSTR psz
, BOOL bIgnoreCase
)
172 DWORD ret
= 0xDEADFACE;
175 ret
+= (bIgnoreCase
? towupper(*psz
) : *psz
);
179 return (ret
& HASH_MASK
);
182 static NODE
*AllocEOFNode(DWORD lineno
)
184 NODE
*node
= AllocNode(AllocLine(NULL
, 0), 0);
187 node
->pszComp
= AllocLine(NULL
, 0);
188 if (node
->pszComp
== NULL
)
193 node
->lineno
= lineno
;
194 node
->hash
= HASH_EOF
;
198 static __inline BOOL
IsEOFNode(NODE
*node
)
200 return !node
|| node
->hash
== HASH_EOF
;
203 static BOOL
ConvertNode(const FILECOMPARE
*pFC
, NODE
*node
)
205 if (!(pFC
->dwFlags
& FLAG_T
))
207 LPTSTR tmp
= ExpandTab(node
->pszLine
);
212 if (!(pFC
->dwFlags
& FLAG_W
))
213 node
->hash
= GetHash(node
->pszLine
, !!(pFC
->dwFlags
& FLAG_C
));
215 if (pFC
->dwFlags
& FLAG_W
)
217 node
->pszComp
= CompressSpace(node
->pszLine
);
220 node
->hash
= GetHash(node
->pszComp
, !!(pFC
->dwFlags
& FLAG_C
));
225 static FCRET
CompareNode(const FILECOMPARE
*pFC
, const NODE
*node0
, const NODE
*node1
)
230 if (node0
->hash
!= node1
->hash
)
231 return FCRET_DIFFERENT
;
233 psz0
= (pFC
->dwFlags
& FLAG_W
) ? node0
->pszComp
: node0
->pszLine
;
234 psz1
= (pFC
->dwFlags
& FLAG_W
) ? node1
->pszComp
: node1
->pszLine
;
235 dwCmpFlags
= ((pFC
->dwFlags
& FLAG_C
) ? NORM_IGNORECASE
: 0);
236 ret
= CompareString(LOCALE_USER_DEFAULT
, dwCmpFlags
, psz0
, -1, psz1
, -1);
237 return (ret
== CSTR_EQUAL
) ? FCRET_IDENTICAL
: FCRET_DIFFERENT
;
240 static BOOL
FindNextLine(LPCTSTR pch
, DWORD ich
, DWORD cch
, LPDWORD pich
)
244 if (pch
[ich
] == TEXT('\n') || pch
[ich
] == TEXT('\0'))
256 ParseLines(const FILECOMPARE
*pFC
, HANDLE
*phMapping
,
257 LARGE_INTEGER
*pib
, const LARGE_INTEGER
*pcb
, struct list
*list
)
259 DWORD lineno
= 1, ich
, cch
, ichNext
, cbView
, cchNode
;
264 if (*phMapping
== NULL
)
265 return FCRET_NO_MORE_DATA
;
267 if (pib
->QuadPart
>= pcb
->QuadPart
)
269 CloseHandle(*phMapping
);
271 return FCRET_NO_MORE_DATA
;
274 cbView
= (DWORD
)min(pcb
->QuadPart
- pib
->QuadPart
, MAX_VIEW_SIZE
);
275 psz
= MapViewOfFile(*phMapping
, FILE_MAP_READ
, pib
->HighPart
, pib
->LowPart
, cbView
);
278 return OutOfMemory();
282 cch
= cbView
/ sizeof(TCHAR
);
283 fLast
= (pib
->QuadPart
+ cbView
>= pcb
->QuadPart
);
285 (FindNextLine(psz
, ich
, cch
, &ichNext
) ||
286 (ichNext
== cch
&& (fLast
|| ich
== 0))))
288 bCR
= (ichNext
> 0) && (psz
[ichNext
- 1] == TEXT('\r'));
289 cchNode
= ichNext
- ich
- bCR
;
290 pszLine
= AllocLine(&psz
[ich
], cchNode
);
291 node
= AllocNode(pszLine
, lineno
++);
292 if (!node
|| !ConvertNode(pFC
, node
))
295 UnmapViewOfFile(psz
);
296 return OutOfMemory();
298 list_add_tail(list
, &node
->entry
);
302 UnmapViewOfFile(psz
);
304 pib
->QuadPart
+= ichNext
* sizeof(WCHAR
);
306 if (pib
->QuadPart
< pcb
->QuadPart
)
307 return FCRET_IDENTICAL
;
310 node
= AllocEOFNode(lineno
);
312 return OutOfMemory();
313 list_add_tail(list
, &node
->entry
);
315 return FCRET_NO_MORE_DATA
;
319 ShowDiff(FILECOMPARE
*pFC
, INT i
, struct list
*begin
, struct list
*end
)
322 struct list
*list
= &pFC
->list
[i
];
323 struct list
*first
= NULL
, *last
= NULL
;
324 PrintCaption(pFC
->file
[i
]);
325 if (begin
&& end
&& list_prev(list
, begin
))
326 begin
= list_prev(list
, begin
);
329 node
= LIST_ENTRY(begin
, NODE
, entry
);
335 if (!(pFC
->dwFlags
& FLAG_A
))
336 PrintLine(pFC
, node
->lineno
, node
->pszLine
);
337 begin
= list_next(list
, begin
);
339 if ((pFC
->dwFlags
& FLAG_A
) && first
)
341 node
= LIST_ENTRY(first
, NODE
, entry
);
342 PrintLine(pFC
, node
->lineno
, node
->pszLine
);
343 first
= list_next(list
, first
);
346 if (list_next(list
, first
) == last
)
348 node
= LIST_ENTRY(first
, NODE
, entry
);
349 PrintLine(pFC
, node
->lineno
, node
->pszLine
);
356 node
= LIST_ENTRY(last
, NODE
, entry
);
357 PrintLine(pFC
, node
->lineno
, node
->pszLine
);
362 SkipIdentical(FILECOMPARE
*pFC
, struct list
**pptr0
, struct list
**pptr1
)
364 struct list
*ptr0
= *pptr0
, *ptr1
= *pptr1
;
367 NODE
*node0
= LIST_ENTRY(ptr0
, NODE
, entry
);
368 NODE
*node1
= LIST_ENTRY(ptr1
, NODE
, entry
);
369 if (CompareNode(pFC
, node0
, node1
) != FCRET_IDENTICAL
)
371 ptr0
= list_next(&pFC
->list
[0], ptr0
);
372 ptr1
= list_next(&pFC
->list
[1], ptr1
);
379 SkipIdenticalN(FILECOMPARE
*pFC
, struct list
**pptr0
, struct list
**pptr1
,
380 DWORD nnnn
, DWORD lineno0
, DWORD lineno1
)
382 struct list
*ptr0
= *pptr0
, *ptr1
= *pptr1
;
386 NODE
*node0
= LIST_ENTRY(ptr0
, NODE
, entry
);
387 NODE
*node1
= LIST_ENTRY(ptr1
, NODE
, entry
);
388 if (node0
->lineno
>= lineno0
)
390 if (node1
->lineno
>= lineno1
)
392 if (CompareNode(pFC
, node0
, node1
) != FCRET_IDENTICAL
)
394 ptr0
= list_next(&pFC
->list
[0], ptr0
);
395 ptr1
= list_next(&pFC
->list
[1], ptr1
);
406 ScanDiff(FILECOMPARE
*pFC
, struct list
**pptr0
, struct list
**pptr1
,
407 DWORD lineno0
, DWORD lineno1
)
409 struct list
*ptr0
= *pptr0
, *ptr1
= *pptr1
, *tmp0
, *tmp1
;
414 node0
= LIST_ENTRY(ptr0
, NODE
, entry
);
415 node1
= LIST_ENTRY(ptr1
, NODE
, entry
);
416 if (node0
->lineno
>= lineno0
)
417 return FCRET_DIFFERENT
;
418 if (node1
->lineno
>= lineno1
)
419 return FCRET_DIFFERENT
;
422 count
= SkipIdenticalN(pFC
, &tmp0
, &tmp1
, pFC
->nnnn
, lineno0
, lineno1
);
423 if (count
>= pFC
->nnnn
)
432 ptr0
= list_next(&pFC
->list
[0], ptr0
);
433 ptr1
= list_next(&pFC
->list
[1], ptr1
);
438 return FCRET_IDENTICAL
;
442 Resync(FILECOMPARE
*pFC
, struct list
**pptr0
, struct list
**pptr1
)
445 struct list
*ptr0
, *ptr1
, *save0
= NULL
, *save1
= NULL
;
447 struct list
*list0
= &pFC
->list
[0], *list1
= &pFC
->list
[1];
448 DWORD lineno0
, lineno1
;
449 INT penalty
, i0
, i1
, min_penalty
= MAXLONG
;
451 node0
= LIST_ENTRY(*pptr0
, NODE
, entry
);
452 node1
= LIST_ENTRY(*pptr1
, NODE
, entry
);
453 lineno0
= node0
->lineno
+ pFC
->n
;
454 lineno1
= node1
->lineno
+ pFC
->n
;
456 // ``If the files that you are comparing have more than pFC->n consecutive
457 // differing lines, FC cancels the comparison,,
458 // ``If the number of matching lines in the files is less than pFC->nnnn,
459 // FC displays the matching lines as differences,,
460 for (ptr1
= list_next(list1
, *pptr1
), i1
= 0; ptr1
; ptr1
= list_next(list1
, ptr1
), ++i1
)
462 node1
= LIST_ENTRY(ptr1
, NODE
, entry
);
463 if (node1
->lineno
>= lineno1
)
465 for (ptr0
= list_next(list0
, *pptr0
), i0
= 0; ptr0
; ptr0
= list_next(list0
, ptr0
), ++i0
)
467 node0
= LIST_ENTRY(ptr0
, NODE
, entry
);
468 if (node0
->lineno
>= lineno0
)
470 if (CompareNode(pFC
, node0
, node1
) == FCRET_IDENTICAL
)
472 penalty
= min(i0
, i1
) + abs(i1
- i0
);
473 if (min_penalty
> penalty
)
475 min_penalty
= penalty
;
487 ret
= ScanDiff(pFC
, &save0
, &save1
, lineno0
, lineno1
);
496 for (ptr0
= *pptr0
; ptr0
; ptr0
= list_next(list0
, ptr0
))
498 node0
= LIST_ENTRY(ptr0
, NODE
, entry
);
499 if (node0
->lineno
== lineno0
)
502 for (ptr1
= *pptr1
; ptr1
; ptr1
= list_next(list1
, ptr1
))
504 node1
= LIST_ENTRY(ptr1
, NODE
, entry
);
505 if (node1
->lineno
== lineno1
)
510 return FCRET_DIFFERENT
;
514 Finalize(FILECOMPARE
* pFC
, struct list
*ptr0
, struct list
* ptr1
, BOOL fDifferent
)
519 return Different(pFC
->file
[0], pFC
->file
[1]);
520 return NoDifference();
524 ShowDiff(pFC
, 0, ptr0
, NULL
);
525 ShowDiff(pFC
, 1, ptr1
, NULL
);
527 return FCRET_DIFFERENT
;
531 // FIXME: "cmd_apitest fc" has some failures.
532 FCRET
TextCompare(FILECOMPARE
*pFC
, HANDLE
*phMapping0
, const LARGE_INTEGER
*pcb0
,
533 HANDLE
*phMapping1
, const LARGE_INTEGER
*pcb1
)
535 FCRET ret
, ret0
, ret1
;
536 struct list
*ptr0
, *ptr1
, *save0
, *save1
, *next0
, *next1
;
537 NODE
* node0
, * node1
;
538 BOOL fDifferent
= FALSE
;
539 LARGE_INTEGER ib0
= { .QuadPart
= 0 }, ib1
= { .QuadPart
= 0 };
540 struct list
*list0
= &pFC
->list
[0], *list1
= &pFC
->list
[1];
546 ret0
= ParseLines(pFC
, phMapping0
, &ib0
, pcb0
, list0
);
547 if (ret0
== FCRET_INVALID
)
552 ret1
= ParseLines(pFC
, phMapping1
, &ib1
, pcb1
, list1
);
553 if (ret1
== FCRET_INVALID
)
559 ptr0
= list_head(list0
);
560 ptr1
= list_head(list1
);
566 // skip identical (sync'ed)
567 SkipIdentical(pFC
, &ptr0
, &ptr1
);
570 node0
= LIST_ENTRY(ptr0
, NODE
, entry
);
571 node1
= LIST_ENTRY(ptr1
, NODE
, entry
);
572 if (IsEOFNode(node0
) || IsEOFNode(node1
))
578 ret
= Resync(pFC
, &ptr0
, &ptr1
);
579 if (ret
== FCRET_INVALID
)
581 if (ret
== FCRET_DIFFERENT
)
584 ret
= ResyncFailed();
585 // show the difference
586 ShowDiff(pFC
, 0, save0
, ptr0
);
587 ShowDiff(pFC
, 1, save1
, ptr1
);
592 // show the difference
594 next0
= ptr0
? list_next(list0
, ptr0
) : ptr0
;
595 next1
= ptr1
? list_next(list1
, ptr1
) : ptr1
;
596 ShowDiff(pFC
, 0, save0
, (next0
? next0
: ptr0
));
597 ShowDiff(pFC
, 1, save1
, (next1
? next1
: ptr1
));
602 } while (ret0
!= FCRET_NO_MORE_DATA
|| ret1
!= FCRET_NO_MORE_DATA
);
605 ret
= Finalize(pFC
, ptr0
, ptr1
, fDifferent
);