about summary refs log tree commit diff stats
path: root/js/games/nluqo.github.io/~bh/v2ch2/v2ch2.html
diff options
context:
space:
mode:
authorelioat <elioat@tilde.institute>2023-08-23 07:52:19 -0400
committerelioat <elioat@tilde.institute>2023-08-23 07:52:19 -0400
commit562a9a52d599d9a05f871404050968a5fd282640 (patch)
tree7d3305c1252c043bfe246ccc7deff0056aa6b5ab /js/games/nluqo.github.io/~bh/v2ch2/v2ch2.html
parent5d012c6c011a9dedf7d0a098e456206244eb5a0f (diff)
downloadtour-562a9a52d599d9a05f871404050968a5fd282640.tar.gz
*
Diffstat (limited to 'js/games/nluqo.github.io/~bh/v2ch2/v2ch2.html')
-rw-r--r--js/games/nluqo.github.io/~bh/v2ch2/v2ch2.html645
1 files changed, 645 insertions, 0 deletions
diff --git a/js/games/nluqo.github.io/~bh/v2ch2/v2ch2.html b/js/games/nluqo.github.io/~bh/v2ch2/v2ch2.html
new file mode 100644
index 0000000..46490cc
--- /dev/null
+++ b/js/games/nluqo.github.io/~bh/v2ch2/v2ch2.html
@@ -0,0 +1,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>