about summary refs log tree commit diff stats
path: root/js/games/nluqo.github.io/~bh/v2ch2/v2ch2.html
blob: 46490cc4fdddacecbe54324e6330b96ab0d70fc7 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
<HTML>
<HEAD>
<TITLE>Computer Science Logo Style vol 2 ch 2: Example: Finding File Differences</TITLE>
</HEAD>
<BODY>
<CITE>Computer Science Logo Style</CITE> volume 2:
<CITE>Advanced Techniques</CITE> 2/e Copyright (C) 1997 MIT
<H1>Example: Finding File Differences</H1>

<TABLE width="100%"><TR><TD>
<IMG SRC="../csls2.jpg" ALT="cover photo">
<TD><TABLE>
<TR><TD align="right"><CITE><A HREF="http://www.cs.berkeley.edu/~bh/">Brian
Harvey</A><BR>University of California, Berkeley</CITE>
<TR><TD align="right"><BR>
<TR><TD align="right"><A HREF="../pdf/v2ch02.pdf">Download PDF version</A>
<TR><TD align="right"><A HREF="../v2-toc2.html">Back to Table of Contents</A>
<TR><TD align="right"><A HREF="../v2ch1/v2ch1.html"><STRONG>BACK</STRONG></A>
chapter thread <A HREF="https://people.eecs.berkeley.edu/~bh/v2ch3/v2ch3.html"><STRONG>NEXT</STRONG></A>
<TR><TD align="right"><A HREF="https://mitpress.mit.edu/books/computer-science-logo-style-second-edition-volume-2">MIT
Press web page for <CITE>Computer Science Logo Style</CITE></A>
</TABLE></TABLE>

<HR><P>Program file for this chapter: <A HREF="diff.lg"><CODE>diff</CODE></A>


<P>As an example of a practical program that manipulates data files, this
chapter is about comparing two similar files to find the differences
between them.  This program is most often useful in comparing a current
version of a file with an earlier version to see what has changed.
On the next page is an example of two input files and the program's output.
The output shows only those lines that differ between the two files,
indicating changed lines, insertions, and deletions.  When several
consecutive lines are different, they are grouped together as a single
reported change.  (To demonstrate all of the program's capabilities, I've
used short input files with more lines different than identical, and so the
program's report is longer than the input files themselves.  In a more
realistic example, the input files might be hundreds of lines long, with only
a few differences, so it would be easier to read the program's output than
to examine the files directly.)

<P>
<CENTER><STRONG>Input Files</STRONG></CENTER>

<PRE>               Text1                                        Text2

My goal in this series of books         My goal in this series of books
is to make the goals and methods        is to make the goals and methods
of a serious computer scientist         of a mad computer scientist
accessible, at an introductory          accessible, at an introductory
level, to people who are                level, to people who are
interested in computer                  interested in playing computer
programming but are not computer        games.
science majors.                         If you're an
If you're an                            adult or teenaged hobbyist, 
adult or teenaged hobbyist,             you're definitely part of this
or a teacher who wants to use the       audience.
computer as an educational tool,        And I hope you appreciate
you're definitely part of this          the privilege!
audience.                               
</PRE>

<P>
<CENTER><STRONG>Output File</STRONG></CENTER>

<P><PRE>DIFF results:
&lt; File 1 = Text1
&gt; File 2 = Text2
==========
CHANGE 3-3 3-3
&lt; of a serious computer scientist
---
&gt; of a mad computer scientist
==========
CHANGE 6-8 6-7
&lt; interested in computer
&lt; programming but are not computer
&lt; science majors.
---
&gt; interested in playing computer
&gt; games.
==========
DELETE 11-12 10
&lt; or a teacher who wants to use the
&lt; computer as an educational tool,
==========
INSERT 15 12-13
&gt; and I hope you appreciate
&gt; the privilege!
==========
</PRE>


<P>I've called this program <CODE>diff</CODE> because it was inspired by a similar
program of that name in the Unix operating system.  The format of the
report that my <CODE>diff</CODE> generates is similar to that of the Unix version.
In particular, I've followed the Unix <CODE>diff</CODE> convention that when a line
from one of the input files appears in the report, it is marked by either
a &quot;<CODE>&lt;</CODE>&quot; character if it's from the first file or a &quot;<CODE>&gt;</CODE>&quot; character
if it's from the second file.

<P>The numbers in the lines that begin with <CODE>CHANGE</CODE>, <CODE>INSERT</CODE>, or
<CODE>DELETE</CODE> are line numbers, counting from one, in each of the two files.
For example, the line

<P><PRE>CHANGE 6-8 6-7
</PRE>

<P>indicates that lines 6 through 8 in the first file were replaced
by lines 6 through 7 in the second file.  (The program considers a change
to be finished when it finds two consecutive identical lines in the two
files.  In this case, lines 9 and 10 of the first file are identical to
lines 8 and 9 of the second file.)

<P>The <CODE>diff</CODE> procedure takes three inputs.  The first two are names of
the two input files; the third is either a name for an output file or an
empty list, in which case the program's results are printed on the screen.
For example, to see the results of my sample run, I'd say

<P><PRE>diff &quot;Text1 &quot;Text2 []
</PRE>

<P>I picked this project partly because it requires switching between
two input files, so you can see how the program uses <CODE>setread</CODE>
repeatedly.

<P><H2>Program Overview</H2>

<P><A HREF="v2ch2.html#diff"><CODE>Diff</CODE></A>
reads lines from the two input files in alternation.  As long
as the corresponding lines are equal, the program just moves on to the
next pair of lines.  (Procedure <A HREF="v2ch2.html#diff.same"><CODE>diff.same</CODE></A>
handles this process.)  When
a difference is found, the program's operation becomes more complicated.  It
must remember all the lines that it reads from both files until it finds two
consecutive equal pairs of lines.  (Procedures <A HREF="v2ch2.html#diff.differ"><CODE>diff.differ</CODE></A>
and <A HREF="v2ch2.html#diff.found"><CODE>diff.found</CODE></A> do this.)

<P>Life would be simple if the differences between the two files were only
changes within a line, without adding or removing entire lines.  (This
would be a realistic assumption if, for example, the second file had been
created by applying a spelling correction program to the first file.
Individual words would then be different, but each line of the second
file would correspond to one line of the first.)  In that case, the
structure of <CODE>diff.differ</CODE> could be similar to that of <CODE>diff.same</CODE>:
Read a line from each file, compare the two, and report the pairs that are
different.

<P>But in practice, a change to a paragraph may make the file longer or
shorter.  It may turn out, as in my sample run, that three lines from the
first file correspond to only two lines from the second one.  If that's the
case, then there's no guarantee that the equal lines that mark the end of a
change will be at the same line <EM>numbers</EM> in the two files.  (In the
sample, line 9 of the first file matches line 8 of the second.)  Whenever
the program reads a line from one file, therefore, it must compare that
line to <EM>every</EM> line that it's read from the other file since the
two started being different.  Therefore, <CODE>diff.differ</CODE> must <EM>
remember</EM> all of the lines that it reads from both files.

<P>Finally, when two pairs of equal lines are found, the program must
report the difference that it's detected.  That's the job of
procedure <A HREF="v2ch2.html#report"><CODE>report</CODE></A>.  Once the
change has been reported, the program continues in
<CODE>diff.same</CODE> until another difference is found.

<P>The program finishes its work when the ends of both input files have been
reached.

<P><H2>The File Information Block Abstract Data Type</H2>

<P>For each of the two input files, the program must remember several kinds of
information.  The <CODE>report</CODE> procedure must know which is file number 1
and which file number 2, in order to print the lines with the correct
starting character.  The name of each file is needed as the
input to <CODE>setread</CODE>.  The current line
number is needed in order to report the location
within each file of a changed section.  As I've just explained, there is a
collection of <EM>pending</EM> lines during the examination of a change;
we'll see shortly that another collection of <EM>saved</EM> lines is used for
another purpose.

<P>To keep all the information for a file collected together, <CODE>diff</CODE> uses
an abstract data type called a &quot;file information block,&quot; or FIB, that is
implemented as an array with five members.  The array is made by a
constructor procedure <CODE>makefile</CODE>, and there are selectors for four of
the five components:  <CODE>which</CODE>, <CODE>filename</CODE>, <CODE>linenum</CODE>, and <CODE>
lines</CODE>.  For the fifth component, the saved lines, instead of a selector for
the entire collection the program uses a selector <CODE>popsaved</CODE> that
outputs a single line each time it's invoked.  (This will make more sense
when you read about saved lines in the next section.)

<P>
The procedures within this program use these two FIBs as inputs
instead of just the filenames.  To read from one of the files, for
example, the program will say

<P><PRE>setread filename :fib1
</PRE>

<P><H2>Saving and Re-Reading Input Lines</H2>

<P>One further detail complicates the program.  Suppose that a change is found
in which the two groups of differing lines are of different lengths.  For
example, suppose three lines in the first file turn into six lines in the
second file, like this:

<P><PRE>Original line 1                    Original line 1
<U>Original line 2</U>                    <U>Changed line 2</U>
<U>Original line 3</U>                    <U>Changed line 3</U>
<U>Original line 4</U>                    <U>New line 3.1</U>
Original line 5                    <U>New line 3.2</U>
Original line 6                    <U>New line 3.3</U>
Original line 7                    <U>Changed line 4</U>
Original line 8                    Original line 5
Original line 9                    Original line 6
</PRE>

<P>The program has been reading lines alternately from the two
files.  It has just read the line saying &quot;Original line 6&quot; from the second
file, and that's the second consecutive match with a line previously read
from the first file.  So the program is ready to report a change from lines
2-4 of the first file to lines 2-7 of the second.

<P>The trouble is that the program has already read three lines of the first
file (the last three lines shown above) that have to be compared to lines
that haven't yet been read from the second file.  Suppose that the files
continue as follows:

<P><PRE>Original line 10                   Original line 7
</PRE>

<P>We can't just say, &quot;Okay,
we've found the end of a difference, so now we can go back to <CODE>diff.same</CODE>
and read lines from the two files.&quot;  If we did that, we'd read
&quot;Original line 10&quot; from file 1, but &quot;Original
line 7&quot; from file 2, and we'd think there is a difference when really the
two files are in agreement.

<P>To solve this problem we must arrange for <CODE>diff.same</CODE> to <EM>re-read</EM>
the three unused lines from file 1.  Logo allows a programmer to re-read part
of a file by changing the current <EM>position</EM> within the file (this
ability is called <EM>random access</EM>), but in this program I found
it easier to <EM>buffer</EM> the lines by saving them in a list and then,
the next time the program wants to read a line from the file, using one of
the saved lines instead.

<P>
<PRE>to readline :fib
if savedp :fib [output popsaved :fib]
setread filename :fib
output readword
end
</PRE>

<P>The first instruction of this procedure says, &quot;If there are any
saved lines for this file, remove the first one from the list and output
it.&quot;  Otherwise, if there are no saved lines, then the procedure directs the
Logo reader to the desired file (using <CODE>setread</CODE>) and uses <CODE>readword</CODE>
to read a line.  Because <CODE>popsaved</CODE> removes a line from the list of
saved lines, eventually the saved lines will be used up and then the program
will continue reading from the actual file.

<P><H2>Skipping <A NAME="diff.same">Equal</A> Lines</H2>

<P>Here is the procedure that skips over identical pairs of lines:

<P><PRE>to diff.same :fib1 :fib2
local [line1 line2]
do.while [make &quot;line1 getline :fib1
          make &quot;line2 getline :fib2
          if and listp :line1 listp :line2 [stop]    ; Both files ended.
] [equalp :line1 :line2]
addline :fib1 :line1                                 ; Difference found.
addline :fib2 :line2
diff.differ :fib1 :fib2
end

to getline :fib
nextlinenum :fib
output readline :fib
end
</PRE>

<P>Most of the names you don't recognize are selectors and <EM>mutators</EM>
for the FIB abstract data type.  (A mutator is a procedure that changes the
value of an existing datum, such as <CODE>setitem</CODE> for arrays.)  One new
Berkeley Logo primitive used here is <CODE>do.while</CODE>.  It takes two inputs,
an instruction list and an expression whose value is <CODE>true</CODE> or <CODE>
false</CODE>.  <CODE>Do.while</CODE> first carries out the instructions in the first
input.  Then it evaluates the predicate expression.  If it's <CODE>true</CODE>,
then <CODE>do.while</CODE> repeats the process, carrying out the instructions and
evaluating the predicate, until the predicate becomes <CODE>false</CODE>.  In this
case, the idea is &quot;Keep reading lines as long as <CODE>:line1</CODE>
and <CODE>:line2</CODE> are equal.&quot;

<P><CODE>Getline</CODE> reads a line, either from the file or from the saved lines, and
also adds one to the current line number by invoking <CODE>nextlinenum</CODE>:

<P><PRE>to nextlinenum :fib
setitem 3 :fib (item 3 :fib)+1
end
</PRE>

<P>This is a typical mutator; I won't show the others until the
complete program listing at the end of the chapter.

<P><H2>Comparing and <A NAME="diff.differ">Remembering</A> Unequal Lines</H2>

<P><PRE>to diff.differ :fib1 :fib2
local &quot;line
make &quot;line readline :fib1
addline :fib1 :line
ifelse memberp :line lines :fib2 ~
       [diff.found :fib1 :fib2] ~
       [diff.differ :fib2 :fib1]
end
</PRE>

<P><CODE>Diff.differ</CODE> reads a line (perhaps a saved line) from one of the files,
adds it to the collection of pending lines (not saved lines!) for that file,
then looks to see whether a line equal to this one is pending in the other
file.  If so, then we may have found the end of the changed area, and
<CODE>diff.found</CODE> is called to make sure there is a second pair of equal
lines following these two.  If not, we must read a line from the other file;
this is accomplished by a recursive call to <CODE>diff.differ</CODE> with the two 
inputs in reversed order.  What was <CODE>:fib1</CODE> this time will be <CODE>:fib2</CODE>
in the recursive call, and vice versa.  (This is why the FIB data type
must include a record of which is the original file 1 and file 2.)

<P>The reason that <CODE>diff.differ</CODE> uses <CODE>readline</CODE>
rather than <CODE>getline</CODE> to read from the input files is that it
doesn't advance the line
number.  When dealing with a difference between the files, we are keeping a
range of lines from each file, not just a single line.  The line number that
the program keeps in the FIB is that of the <EM>first</EM>
different line; the line number of the last different line will be computed
by the <CODE>report</CODE> procedure later.

<P><PRE>to <A NAME="diff.found">diff.found</A> :fib1 :fib2
local &quot;lines
make &quot;lines member2 (last butlast lines :fib1) ~
                    (last lines :fib1) ~
                    (lines :fib2)
ifelse emptyp :lines ~
       [diff.differ :fib2 :fib1] ~
       [report :fib1 :fib2 (butlast butlast lines :fib1)
               (firstn (lines :fib2) (count lines :fib2)-(count :lines))]
end
</PRE>

<P><CODE>Diff.found</CODE> is called when the last line read from file 1 matches some
line pending from file 2.  Its job is to find out whether the last <EM>
two</EM> lines from file 1 match two consecutive lines from file 2.  Most of
the work is done by the straightforward helper procedure <CODE>member2</CODE>, which
works this way:

<P><PRE>&gt; <U>show member2 &quot;and &quot;joy [she's my pride and joy etcetera]</U>
[and joy etcetera]

&gt; <U>show member2 &quot;pride &quot;joy [she's my pride and joy etcetera]</U>
[]
</PRE>

<P>If the first two inputs are consecutive members of the third, then
<CODE>member2</CODE> outputs the portion of its third input starting from the point
at which the first input was found.  If not, then <CODE>member2</CODE> outputs the
empty list.

<P>If <CODE>member2</CODE>'s output is empty, we continue reading lines from the
two files by invoking <CODE>diff.differ</CODE>.  If not, then we've found the end
of a change, and we invoke <CODE>report</CODE> to print the results.  The first two
inputs to <CODE>report</CODE> are the two files; the third and fourth are the
corresponding sets of unequal lines.  The unequal lines from file 1 are all
but the last two, the ones we just matched; the unequal lines from
file 2 are all but the ones that <CODE>member2</CODE> output.  Helper procedure
<CODE>firstn</CODE> is used to select those lines.

<P><H2>Reporting a <A NAME="report">Difference</A></H2>

<P>The <CODE>report</CODE> procedure is somewhat lengthy, but mostly because
differences in which one of the sets of lines is empty are reported
specially (as an insertion or a deletion, rather than as a change).

<P><PRE>to report :fib1 :fib2 :lines1 :lines2
local [end1 end2 dashes]
if equalp (which :fib1) 2 [report :fib2 :fib1 :lines2 :lines1 stop]
print &quot;==========
make &quot;end1 (linenum :fib1)+(count :lines1)-1
make &quot;end2 (linenum :fib2)+(count :lines2)-1
make &quot;dashes &quot;false
ifelse :end1 &lt; (linenum :fib1) [
    print (sentence &quot;INSERT :end1+1 (word (linenum :fib2) &quot;- :end2))
] [ifelse :end2 &lt; (linenum :fib2) [
    print (sentence &quot;DELETE (word (linenum :fib1) &quot;- :end1) :end2+1)
] [
    print (sentence &quot;CHANGE (word (linenum :fib1) &quot;- :end1)
                            (word (linenum :fib2) &quot;- :end2))
    make &quot;dashes &quot;true
]]
process :fib1 &quot;|&lt; | :lines1 :end1
if :dashes [print &quot;---]
process :fib2 &quot;|&gt; | :lines2 :end2
diff.same :fib1 :fib2
end

to process :fib :prompt :lines :end
foreach :lines [type :prompt print ?]
savelines :fib butfirst butfirst chop :lines (lines :fib)
setlines :fib []
setlinenum :fib :end+2
end
</PRE>

<P>Here's how to read <CODE>report</CODE>:  The first step is to ensure that the files
are in the proper order, so that <CODE>:fib1</CODE> is file number 1.  (If not,
<CODE>report</CODE> invokes itself with its inputs reordered.)  The next step is to
compute the ending line number for each changed section; it's the starting
line number (found in the file data structure) plus the number of unmatched
lines, minus one.  <CODE>Report</CODE> then prints a header, choosing <CODE>
INSERT</CODE>, <CODE>DELETE</CODE>, or <CODE>CHANGE</CODE> as appropriate.  Finally, it
invokes <CODE>process</CODE> once for each file.

<P><CODE>Process</CODE> prints the unmatched lines, with the appropriate file indicator
(<CODE>&lt;</CODE> or <CODE>&gt;</CODE>).  Then it takes whatever pending lines were not
included in the unmatched group and transfers them to the saved lines, so
that they will be read again.  (As a slight efficiency improvement, <CODE>
process</CODE> skips over the two lines that we know matched two lines in the
other file; there's no need to read those again.)  The set of pending lines
is made empty, since no file difference is pending.  Finally, the line
number in the file structure is increased to match the position following
the two lines that ended the difference.

<P>If <CODE>process</CODE> confuses you, look back at the example I gave earlier, when
I first talked about saving and re-reading lines.  In that example, the lines
from &quot;Original line 7&quot; to &quot;Original line 9&quot; in the first file are the
ones that must be moved from the list of pending lines to the list of saved
lines.  (No lines will be moved in the second file, since that one had the
longer set of lines in this difference, six lines instead of three.)

<P>By the way, in the places where the program adds or subtracts one or two
in a line number calculation, I didn't work those out in advance.  I wrote
the program without them, looked at the wrong results, and then figured out
how to correct them!

<P>
<TABLE width="100%"><TR><TD><A HREF="../v2-toc2.html">(back to Table of Contents)</A>
<TD align="right"><A HREF="../v2ch1/v2ch1.html"><STRONG>BACK</STRONG></A>
chapter thread <A HREF="https://people.eecs.berkeley.edu/~bh/v2ch3/v2ch3.html"><STRONG>NEXT</STRONG></A>
</TABLE>

<P><H2>Program Listing</H2>

<P>I've discussed the most important parts of this program, but not all of the
helper procedures.  If you want to understand the program fully, you can
read this complete listing:

<P><P>
<P><PRE>
to <A NAME="diff">diff</A> :file1 :file2 :output
local "caseignoredp
make "caseignoredp "false
openread :file1
openread :file2
if not emptyp :output [openwrite :output]
setwrite :output
print [DIFF results:]
print sentence [< File 1 =] :file1
print sentence [> File 2 =] :file2
diff.same (makefile 1 :file1) (makefile 2 :file2)
print "==========
setread []
setwrite []
close :file1
close :file2
if not emptyp :output [close :output]
end

;; Skip over identical lines in the two files.

to diff.same :fib1 :fib2
local [line1 line2]
do.while [make "line1 getline :fib1
          make "line2 getline :fib2
          if and listp :line1 listp :line2 [stop]    ; Both files ended.
] [equalp :line1 :line2]
addline :fib1 :line1                                 ; Difference found.
addline :fib2 :line2
diff.differ :fib1 :fib2
end

;; Remember differing lines while looking for ones that match.

to diff.differ :fib1 :fib2
local "line
make "line readline :fib1
addline :fib1 :line
ifelse memberp :line lines :fib2 ~
       [diff.found :fib1 :fib2] ~
       [diff.differ :fib2 :fib1]
end

to diff.found :fib1 :fib2
local "lines
make "lines member2 (last butlast lines :fib1) ~
                    (last lines :fib1) ~
                    (lines :fib2)
ifelse emptyp :lines ~
       [diff.differ :fib2 :fib1] ~
       [report :fib1 :fib2 (butlast butlast lines :fib1)
             (firstn (lines :fib2) (count lines :fib2)-(count :lines))]
end

to member2 :line1 :line2 :lines
if emptyp butfirst :lines [output []]
if and equalp :line1 first :lines equalp :line2 first butfirst :lines ~
   [output :lines]
output member2 :line1 :line2 butfirst :lines
end

to firstn :stuff :number
if :number = 0 [output []]
output fput (first :stuff) (firstn butfirst :stuff :number-1)
end

;; Read from file or from saved lines.

to getline :fib
nextlinenum :fib
output readline :fib
end

to readline :fib
if savedp :fib [output popsaved :fib]
setread filename :fib
output readword
end

;; Matching lines found, now report the differences.

to report :fib1 :fib2 :lines1 :lines2
local [end1 end2 dashes]
if equalp (which :fib1) 2 [report :fib2 :fib1 :lines2 :lines1 stop]
print "==========
make "end1 (linenum :fib1)+(count :lines1)-1
make "end2 (linenum :fib2)+(count :lines2)-1
make "dashes "false
ifelse :end1 < (linenum :fib1) [
    print (sentence "INSERT :end1+1 (word (linenum :fib2) "- :end2))
] [ifelse :end2 < (linenum :fib2) [
    print (sentence "DELETE (word (linenum :fib1) "- :end1) :end2+1)
] [
    print (sentence "CHANGE (word (linenum :fib1) "- :end1)
                            (word (linenum :fib2) "- :end2))
    make "dashes "true
]]
process :fib1 "|< | :lines1 :end1
if :dashes [print "-----]
process :fib2 "|> | :lines2 :end2
diff.same :fib1 :fib2
end

to process :fib :prompt :lines :end
foreach :lines [type :prompt print ?]
savelines :fib butfirst butfirst chop :lines (lines :fib)
setlines :fib []
setlinenum :fib :end+2
end

to chop :counter :stuff
if emptyp :counter [output :stuff]
output chop butfirst :counter butfirst :stuff
end

;; Constructor, selectors, and mutators for File Information Block (FIB)
;; Five elements: file number, file name, line number,
;; differing lines, and saved lines for re-reading.

to makefile :number :name
local "file
make "file array 5               ; Items 4 and 5 will be empty lists.
setitem 1 :file :number
setitem 2 :file :name
setitem 3 :file 0
output :file
end

to which :fib
output item 1 :fib
end

to filename :fib
output item 2 :fib
end

to linenum :fib
output item 3 :fib
end

to nextlinenum :fib
setitem 3 :fib (item 3 :fib)+1
end

to setlinenum :fib :value
setitem 3 :fib :value
end

to addline :fib :line
setitem 4 :fib (lput :line item 4 :fib)
end

to setlines :fib :value
setitem 4 :fib :value
end

to lines :fib
output item 4 :fib
end

to savelines :fib :value
setitem 5 :fib (sentence :value item 5 :fib)
end

to savedp :fib
output not emptyp item 5 :fib
end

to popsaved :fib
local "result
make "result first item 5 :fib
setitem 5 :fib (butfirst item 5 :fib)
output :result
end
</PRE><P>

<P><A HREF="../v2-toc2.html">(back to Table of Contents)</A>
<P><A HREF="../v2ch1/v2ch1.html"><STRONG>BACK</STRONG></A>
chapter thread <A HREF="https://people.eecs.berkeley.edu/~bh/v2ch3/v2ch3.html"><STRONG>NEXT</STRONG></A>

<P>
<ADDRESS>
<A HREF="../index.html">Brian Harvey</A>, 
<CODE>bh@cs.berkeley.edu</CODE>
</ADDRESS>
</BODY>
</HTML>