DiffLib.Mod 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. MODULE DiffLib; (** AUTHOR "negelef"; PURPOSE "Simple text diff tool"; *)
  2. IMPORT
  3. Streams, Texts, TextUtilities, Commands, Strings;
  4. CONST
  5. lineBufferSize = 1000;
  6. maxLineSize = 256;
  7. dirNone = 0;
  8. dirLeft = 1;
  9. dirUp = 2;
  10. dirRight = 4;
  11. dirDown = 5;
  12. dirDiag = 6;
  13. VAR
  14. separator: BOOLEAN;
  15. TYPE
  16. LineBuffer = POINTER TO RECORD
  17. lines: ARRAY lineBufferSize OF LONGINT;
  18. next: LineBuffer;
  19. size: LONGINT;
  20. END;
  21. Element = RECORD
  22. val: LONGINT;
  23. dir: LONGINT;
  24. END;
  25. Handler* = PROCEDURE {DELEGATE} (pos, line: LONGINT; string: Strings.String; out : Streams.Writer);
  26. EmptyHandler* = PROCEDURE {DELEGATE};
  27. SetupHandler* = PROCEDURE {DELEGATE} (nofLines: LONGINT);
  28. PROCEDURE GetLinePos (lineBuffer: LineBuffer; offset: LONGINT): LONGINT;
  29. BEGIN
  30. WHILE offset >= lineBuffer.size DO
  31. DEC (offset, lineBuffer.size);
  32. lineBuffer := lineBuffer.next;
  33. END;
  34. RETURN lineBuffer.lines[offset];
  35. END GetLinePos;
  36. PROCEDURE GetLineBuffer (reader: Texts.TextReader; VAR size: LONGINT): LineBuffer;
  37. VAR
  38. first, current: LineBuffer;
  39. ch: LONGINT;
  40. BEGIN
  41. NEW (first);
  42. current := first;
  43. current.size := 0;
  44. size := 0;
  45. REPEAT
  46. IF (current.size = lineBufferSize) THEN
  47. NEW (current.next);
  48. current := current.next;
  49. current.size := 0;
  50. END;
  51. current.lines[current.size] := reader.GetPosition ();
  52. INC (current.size);
  53. INC (size);
  54. REPEAT
  55. reader.ReadCh (ch);
  56. UNTIL reader.eot OR (ch = Texts.NewLineChar);
  57. UNTIL reader.eot;
  58. RETURN first;
  59. END GetLineBuffer;
  60. PROCEDURE ReadLine (pos: LONGINT; reader: Texts.TextReader): Strings.String;
  61. VAR
  62. ch, i: LONGINT;
  63. string: Strings.String;
  64. BEGIN
  65. reader.SetPosition (pos);
  66. i := 0;
  67. NEW (string, maxLineSize + 1);
  68. LOOP
  69. reader.ReadCh (ch);
  70. IF reader.eot OR (ch = Texts.NewLineChar) OR (i = maxLineSize) THEN
  71. EXIT
  72. ELSE
  73. string[i] := CHR (ch);
  74. INC (i);
  75. END;
  76. END;
  77. string[i] := 0X;
  78. RETURN string;
  79. END ReadLine;
  80. PROCEDURE Diff* (
  81. leftFile, rightFile: ARRAY OF CHAR;
  82. setup: SetupHandler; leftDiff, rightDiff, leftEqual, rightEqual: Handler; emptyLeft, emptyRight: EmptyHandler;
  83. out : Streams.Writer);
  84. VAR
  85. leftText, rightText: Texts.Text;
  86. leftReader, rightReader: Texts.TextReader;
  87. format, res: LONGINT; textRes: WORD;
  88. leftBuffer, rightBuffer, left, right: LineBuffer;
  89. width, height : LONGINT;
  90. table: POINTER TO ARRAY OF ARRAY OF Element;
  91. x, y: LONGINT;
  92. PROCEDURE CompareLines (left, right: LONGINT): BOOLEAN;
  93. VAR
  94. leftCh, rightCh: LONGINT;
  95. BEGIN
  96. leftReader.SetPosition (GetLinePos (leftBuffer, left));
  97. rightReader.SetPosition (GetLinePos (rightBuffer, right));
  98. LOOP
  99. leftReader.ReadCh (leftCh);
  100. rightReader.ReadCh (rightCh);
  101. IF leftReader.eot & rightReader.eot THEN RETURN TRUE END;
  102. IF leftCh # rightCh THEN RETURN FALSE END;
  103. IF (leftCh = Texts.NewLineChar) OR (rightCh = Texts.NewLineChar) THEN
  104. RETURN leftCh = rightCh;
  105. END;
  106. END;
  107. END CompareLines;
  108. BEGIN
  109. NEW (leftText);
  110. TextUtilities.LoadAuto(leftText, leftFile, format, textRes);
  111. leftText.AcquireRead;
  112. NEW (leftReader, leftText);
  113. leftReader.SetPosition (0);
  114. NEW (rightText);
  115. TextUtilities.LoadAuto(rightText, rightFile, format, textRes);
  116. rightText.AcquireRead;
  117. NEW (rightReader, rightText);
  118. rightReader.SetPosition (0);
  119. leftBuffer := GetLineBuffer (leftReader, width);
  120. rightBuffer := GetLineBuffer (rightReader, height);
  121. IF setup # NIL THEN setup(width + height); END;
  122. NEW (table, width + 1, height + 1);
  123. table[0, 0].val := 0;
  124. table[0, 0].dir := 0;
  125. FOR x := 1 TO width DO
  126. table[x, 0].val := 0;
  127. table[x, 0].dir := dirLeft;
  128. END;
  129. FOR y := 1 TO height DO
  130. table[0, y].val := 0;
  131. table[0, y].dir := dirUp;
  132. END;
  133. left := leftBuffer;
  134. right := rightBuffer;
  135. FOR y := 1 TO height DO
  136. FOR x := 1 TO width DO
  137. IF CompareLines (x - 1, y - 1) THEN
  138. table[x, y].val := table[x - 1, y - 1].val + 1;
  139. table[x, y].dir := dirDiag;
  140. ELSE
  141. format := table[x - 1, y].val;
  142. res := table[x, y - 1].val;
  143. IF format > res THEN
  144. table[x, y].val := format;
  145. table[x, y].dir := dirLeft;
  146. ELSE
  147. table[x, y].val := res;
  148. table[x, y].dir := dirUp;
  149. END;
  150. END;
  151. END;
  152. END;
  153. (* DEC (x); DEC (y); *)
  154. x := width; y := height;
  155. WHILE (x # 0) OR (y # 0) DO
  156. CASE table[x, y].dir OF
  157. dirUp:
  158. DEC (y); table[x, y].val := dirDown;
  159. | dirLeft:
  160. DEC (x); table[x, y].val := dirRight;
  161. | dirDiag:
  162. DEC (x); DEC (y); table[x, y].val := dirDiag;
  163. END
  164. END;
  165. WHILE (x # width) OR (y # height) DO
  166. CASE table[x, y].val OF
  167. dirDown:
  168. INC (y); Handle (y, rightReader, rightBuffer, rightDiff, out); IF emptyLeft # NIL THEN emptyLeft; END;
  169. | dirRight:
  170. INC (x); Handle (x, leftReader, leftBuffer, leftDiff, out); IF emptyRight # NIL THEN emptyRight; END;
  171. | dirDiag:
  172. INC (x); Handle (x, leftReader, leftBuffer, leftEqual, out);
  173. INC (y); Handle (y, rightReader, rightBuffer, rightEqual, out);
  174. END
  175. END;
  176. END Diff;
  177. PROCEDURE Handle (line: LONGINT; reader: Texts.TextReader; buffer: LineBuffer; handler: Handler; out : Streams.Writer);
  178. VAR
  179. pos: LONGINT;
  180. BEGIN
  181. IF handler # NIL THEN
  182. pos := GetLinePos (buffer, line - 1);
  183. handler (pos, line, ReadLine (pos, reader), out);
  184. END
  185. END Handle;
  186. PROCEDURE Left (pos, line: LONGINT; string: Strings.String; out : Streams.Writer);
  187. BEGIN
  188. out.String ( "< ("); out.Int (line, 0); out.Char (':');
  189. out.Int (pos, 0); out.String (") "); out.String (string^); out.Ln;
  190. separator := TRUE;
  191. END Left;
  192. PROCEDURE Right (pos, line: LONGINT; string: Strings.String; out : Streams.Writer);
  193. BEGIN
  194. out.String ("> ("); out.Int (line, 0); out.Char (':');
  195. out.Int (pos, 0); out.String (") "); out.String (string^); out.Ln;
  196. separator := TRUE;
  197. END Right;
  198. PROCEDURE Equal (pos, line: LONGINT; string: Strings.String; out : Streams.Writer);
  199. BEGIN
  200. IF separator THEN out.Ln; separator := FALSE END
  201. END Equal;
  202. PROCEDURE Compare* (context : Commands.Context);
  203. VAR
  204. left, right: ARRAY 64 OF CHAR;
  205. BEGIN
  206. context.arg.SkipWhitespace; context.arg.String(left);
  207. context.arg.SkipWhitespace; context.arg.String(right);
  208. context.out.String ("< "); context.out.String (left); context.out.Ln;
  209. context.out.String ("> "); context.out.String (right); context.out.Ln;
  210. context.out.Ln;
  211. separator := FALSE;
  212. Diff (left, right, NIL, Left, Right, Equal, Equal, NIL, NIL, context.out);
  213. END Compare;
  214. END DiffLib.
  215. System.Free DiffLib~
  216. DiffLib.Compare DiffTest1.Text DiffTest2.Text~
  217. DiffLib.Compare Configuration.XML Configuration.XML.Bk ~