PROGRAM  relabeli; {Relabel program for IBM-compatible computers, to
be compiled by Turbo Pascal.
 Written by Peter Ungar, 914 723 7187, June 7th, 94, Mar. 5th, 95,
Mar. 31, 95}
{Change of Mar. 5, 1995: In case of a reference to a nonexistent label,
RELABEL is supposed to print out the nonexistent label. Before this
correction this failed to work in most cases.
 Change of Mar. 31: Message asking for starting chapter number
and section number changed to disallow -1. RELABEL is programmed
to regard the character - as not part of a label.
 Aslo, the message in case of a reference to a nonexistent label
was made clearer. }
{$N-}
LABEL 1,3,8,9;

TYPE stringarray=array[1..6] of string[12];

      (* LaTeX calls the identifier "label" when it occurs next to
      the item, and "reference" elsewhere. I loosely follow this usage,
      and use "identifier" for an occurrence of a string which
      could be a label or a reference. In text written
      for RELABEL, as in typeset books, labels and references
      are not marked as such by the author. RELABEL regards the first
      occurrence of an identifier as the label, unless it is marked as a
      (forward) reference.
        The program makes two passes over the text. The first
      involves reading only. The program takes down each identifier at its
      first occurrence which is not marked as a reference
      and assigns to it the next
      available serial number. These pairs of old and new labels are
      stored in the labelentry file. Next, the text is read again and
      written out to a new file, with each identifier changed to its new
      value.

      Stringarrays will hold identifiers, including those parts of the
      identifiers which are implicit, i.e. if we omit the identifier(s)
      of the current text unit, RELABEL will put them in.
         The entries of the stringarray are:
         a) If SectionReset=TRUE (3-level mode): chapteri.d., section i.d.,
      subsection  i.d. and the identifier of the object.
      If the item does not apply, e.g. if we are not in a subsection,
      the corresponding entry is the empty string '';
         b) If SectionReset=FALSE, i.e. sections are numbered
         consecutively, then chapter numbers are not needed in  i.d.s and
         the first entry of the string array is empty except in the
          i.d. of a chapter. *)

      (* We use the following pointer construction to get around the fact
         that Turbo Pascal allots a space of only 64K to
         all regularly declared variables, but can utilize memory outside
         that limited space for  variables created by  NEW statements. *)

    labelentry=RECORD oldlabel:stringarray;
                      newlabel:array[1..4] of integer;
                      secondpassoccurred:boolean;
               END;
    labelentryp=^labelentry;
    sublistp=array[-1..2000] of labelentryp;
    mainlistp=^sublistp;

       (* Oldlabel is a stringarray. The labels in the old text may
       contain lowercase letters as well as digits.
          Newlabel is represented by an array of integers, the
       chapter number etc. This will be made into a string when there is
       a reference to the label.*)

VAR  i,j,k,n,implicit,ImplAdj,SerialNo,
     FirstChapter,FirstSection,LabelKind:integer;
       (* ImplAdj (ImplicitAdjustment) is 0 if section numbers are reset
       at the beginning of each chapter and 1 if they are not, and
       hence chapter numbers are not needed in labels other than chapter
       labels.
         SerialNo will hold the serial number of the first occurrence
       of a label in the array holding all labels of its kind.
         FirstChapter, FirstSection are the numbers to be assigned to the
       first chapter and the first section of the file. RELABEL asks an
       input of this when the program starts, in case you want 0, 1 or
       larger numbers if the file is from the middle of a manuscript. *)
     totalcount, currentcount:array[0..9] of integer;
     s,filename,outputfilename:string;
     labelstring:string[51];
     sta,empty,current:stringarray;

       (* totalcount will say how many labels of the 9 kinds we have
       so far. currentcount gives the counts of the 9 kinds since they
       were last reset.
         current contains the chapter, section
       and subsection identifiers of the current text unit. *)

     mainlist:array[0..9] of mainlistp;
     SectionReset,ForwardRef, LabeltagsInOutput, omit,
     PeriodFollows, FirstChapterlabel, FirstSectionlabel: boolean;
(* Sectionreset: Each chapter starts with section 1.*)

     incoming, outgoing:text;
     c,ans:char;

FUNCTION min(i,j:integer):integer;
BEGIN
 IF i<j THEN min:=i ELSE min:=j
END;

FUNCTION max(i,j:integer):integer;
BEGIN
 IF i<j THEN max:=j ELSE max:=i
END;

PROCEDURE IntToStr(a:integer; VAR s:string);
    (*Writes  a  as a string s*)
 LABEL 9;
 VAR d:integer;
 BEGIN
   s:='';
   IF a<0 THEN BEGIN s:='-'; a:=-a END;
   IF a=0 THEN BEGIN s:='0'; GOTO 9 END;
   d:=10000;
   WHILE (a DIV d)=0 DO d:=d DIV 10;
   WHILE d>0 DO BEGIN s:=s+chr(48+(a DIV d)); a:= a MOD d; d:=d DIV 10 END;
 9:END;

 FUNCTION labelsymbol(c:char):boolean;
(* TRUE if  c  is a symbol allowed in a label, i.e. a digit or
  a lowercase letter. *)
 VAR x:integer;
 BEGIN x:=ord(c);
   labelsymbol:=(((48<=x) AND (x<=57)) OR ((97<=x) AND (x<=122))
                 OR ((65<=x) AND (x<=90)))
 END;

 PROCEDURE ReadLabel(VAR star:stringarray; VAR c:char);
    (* Reads the components of a label from the incoming stream and
    puts its components into the stringarray star, including
    the implicit items. If section numbers are not reset in each
    chapter then the empty string is put in the chapter designator
    location star[1] except in the case of chapter labels. *)
 LABEL 2,5,9;
 VAR i,implicit:integer; str:string[12];
 BEGIN
   k:=0; star:=empty; PeriodFollows:=FALSE;
     (* k will count the number of parts separated by .'s in the label. *)
2: str:='';
   WHILE labelsymbol(c)  DO BEGIN
     str:=str+c;
     read(incoming,c);
   END;
   k:=k+1; star[k+ImplAdj]:=str;
   IF c='.' THEN BEGIN
       (* Another part of the label is coming, or there is a . after
       the label. *)
     read(incoming,c);
     IF labelsymbol(c) THEN GOTO 2
     ELSE PeriodFollows:=TRUE;
   END;
   (* Now we have all  k  items of the label in the stringarray star.
    We want the program to work so that, if desired, the chapter, section
    and subsection labels may be omitted and then they are understood to
    refer to the current chapter, section or subsection.
    We reconstruct the omitted parts of the label. We assemble it
    in  star  and then if it is a new label we put it into the mainlist.
      If the number of levels is 3 then the
    components of  star  are: Chapter label, Section label, Subsection
    label and item label. If any of these are missing or not applicable,
    the corresponding label is ''. A subsection label is missing
    if the section is not divided into subsections. A subsection
    label is inapplicable if the label is a chapter or section label.
      If the number of levels is 2
    then a label of anything except a chapter is complete without the
    chapter label, and star[1]:='' for all such items.
      The label of a nonreset item should not contain chapter, section
    or subsection identifiers. *)
   IF (LabelKind > 1) AND ((k+ImplAdj) > min(LabelKind,4)) THEN BEGIN
     writeln('Found a label with too many parts. If you have 2 levels');
     writeln('section numbers then nonchapter labels should not contain');
     writeln('a chapter identifier.');
     GOTO 5;
   END;
   IF ((LabelKind=0) AND (k>1)) THEN BEGIN
     writeln(' An at-large item label with more than one part. ');
5:    write('LabelKind= '); write(LabelKind,'  ');
     FOR i:=1+ImplAdj TO k+ImplAdj-1 DO write(sta[i],'.');
     writeln(sta[k+ImplAdj]);
     write('  Continue?  y  or  n '); readln(ans);
     IF ans='n' THEN BEGIN close(incoming); halt END;
   END; (* of error handling. *)
   IF LabelKind=0 THEN BEGIN
     star[4]:=star[1+implAdj]; star[1+ImplAdj]:='';
   END;
   IF LabelKind=1 THEN BEGIN
     star[1]:=star[1+ImplAdj]; star[2]:='';GOTO 9 END;
   IF (2<=LabelKind) AND (LabelKind<=3) THEN BEGIN
     implicit:=LabelKind-k-ImplAdj;
     IF implicit>0 THEN BEGIN
       FOR i:=LabelKind DOWNTO implicit+1 DO star[i]:=star[i-implicit];
       FOR i:=1+ImplAdj TO implicit+ImplAdj DO star[i]:=current[i];
     END;
   END;
   IF (labelkind >= 4) THEN BEGIN
     IF k+ImplAdj<4 THEN BEGIN
       star[4]:=star[k+ImplAdj]; star[k+ImplAdj]:='' END;
     IF k=1 THEN FOR i:=1+ImplAdj to 3 DO star[i]:=current[i];
       (*This must be a label of an item in the current text unit.*)
   END;
9:END;  (* of procedure readlabel. Note that  c  is the first symbol after
          the label at this stage, or the first symbol after the period
          if there was a period immediately after the label. *)

 FUNCTION InList(st:StringArray;VAR SerialNo:integer):boolean;
 (* TRUE if the label st is already in the mainlist.
    This function is also used to compute the index SerialNo of
    the label in the array of that LabelKind*)
 LABEL 9;
 VAR i:integer;  bool:boolean;
 BEGIN
   IF totalcount[LabelKind] = 0 THEN InList:=FALSE
   ELSE
   FOR i:=totalcount[LabelKind] DOWNTO 1 DO
   BEGIN
     bool:=TRUE;
     WITH  mainlist[LabelKind]^[i]^ DO
       FOR j:=1 TO 4 DO bool:=bool AND (oldlabel[j]=st[j]);
     IF bool THEN BEGIN
       InList:=TRUE;
       SerialNo:=i;
          (*  mainlist[LabelKind]^[SerialNo] is the record where this
         label and the new label which is to replace it can be found.*)
     GOTO 9 END;
   END;
   InList:=FALSE; (* The label is not in mainlist. *)
9:END;


 PROCEDURE AddToList(sta:stringarray);
(* We have a new label which is not a forward reference. Add it to mainlist*)
 VAR i:integer;
 BEGIN
   totalcount[LabelKind]:=totalcount[LabelKind]+1;
   currentcount[LabelKind]:=currentcount[LabelKind]+1;
     (* If the new label is a chapter, section or subsection label then
        the counts of subordinate items have to be reset.*)
   IF (0<LabelKind) AND (LabelKind <=3) THEN BEGIN
     current[labelkind]:=sta[labelkind];
      FOR i:=max(labelkind+1,ImplAdj+2) TO 9 DO BEGIN
        currentcount[i]:=0; current[i]:=''
      END;
      IF (FirstChapterlabel AND (Labelkind=1)) THEN BEGIN
        currentcount[1]:=Firstchapter; FirstChapterlabel:=FALSE
      END;
      IF (FirstSectionlabel AND (Labelkind=2)) THEN BEGIN
        currentcount[2]:=Firstsection; FirstSectionlabel:=FALSE
      END;
    END; (* of adjusting the label counts and the array of current chapter,
   section and subsection to account for the new label.
   Next add a new blank record where we will put the label we just found,
   and the label which will replace it. *)

   new(mainlist[LabelKind]^[totalcount[LabelKind]]);
   WITH mainlist[LabelKind]^[totalcount[LabelKind]]^ DO BEGIN
     oldlabel:=sta;
     FOR i:=1 to 4 DO newlabel[i]:=0;
     IF LabelKind=0 THEN newlabel[4]:=currentcount[0];
     IF LabelKind=1 THEN newlabel[1]:=currentcount[1];
     IF LabelKind>=2 THEN
       FOR i:=1+ImplAdj TO min(3,LabelKind) DO newlabel[i]:=currentcount[i];
     IF LabelKind>=4 THEN newlabel[4]:=currentcount[labelkind];
       (* Newlabel[1] is 0 if sections are numbered
          consecutively and chapter numbers occur only in chapter labels.*)
     SecondPassOccurred:=FALSE;
   END; (* of preparing new mainlist entry *)
 END; (* of processing the new label *)

 PROCEDURE MakeNewLabelstring(VAR newlabelstring:string;
                              labelkind,SerialNo:integer);
 LABEL 9;
 VAR j:integer;s:string;
     InUnitRef:boolean;
 BEGIN
   WITH mainlist[LabelKind]^[SerialNo]^ DO
   BEGIN
     newlabelstring:='';
     IF LabelKind=0 THEN
       BEGIN IntToStr(newlabel[4],newlabelstring);
       GOTO 9 END; (* Of LabelKind=0 (at-large item label) case *)

     IF LabelKind=1 THEN
       BEGIN IntToStr(newlabel[1],newlabelstring);
       GOTO 9 END; (* Of LabelKind=1 (i.e. chapterlabel) case *)

     IF omit AND (LabelKind>3) THEN BEGIN
       (* Find whether the reference is to the current unit of the text.*)
       InUnitRef:=TRUE;
       FOR j:=1+ImplAdj TO 3 DO
         InUnitRef:=InUnitRef AND (newlabel[j]=currentcount[j]);
       IF InUnitRef THEN BEGIN
         IntToStr(newlabel[4],newlabelstring); GOTO 9 END;
     END; (* Of making label without chapter,section and subsection
       number. Note we exit to 9 only if such a label has been made. *)
     FOR j:=1+ImplAdj TO min(LabelKind,4) DO
       IF OldLabel[j]<>'' THEN BEGIN
         IntToStr(newlabel[j],s);
         IF newlabelstring='' THEN newlabelstring:=s
         ELSE newlabelstring:=newlabelstring+'.'+s;
       END;
   END; (* of WITH statement *)
9:END;

 FUNCTION LabelTag(LabelKind:integer):char; (* For error message.*)
 BEGIN
   CASE LabelKind OF
      0: LabelTag:= chr(220); (* at-large item *)
      1: LabelTag:= chr(227); (* chapter *)
      2: LabelTag:= chr(228); (* section *)
      3: LabelTag:= chr(229); (* subsection *)
      4: LabelTag:= chr(221); (* figure *)
      5: LabelTag:= chr(222); (* formula *)
      6: LabelTag:= chr(223); (* problem *)
      7: LabelTag:= chr(224); (* definition *)
      8: LabelTag:= chr(225); (* theorem *)
      9: LabelTag:= chr(226); (* lemma *)
   END;
 END;

(* Main program begins here.*)
BEGIN
1:  FOR i:=1 TO 4 DO empty[i]:=''; current:=empty;
 writeln('File to be renumbered. If it is not in the same directory as');
 writeln('RELABEL, then give path name.');
 readln(filename);
 writeln(' Name of relabeled file: ');
 readln(OutputFileName);
 writeln('Is the labeling based on 3 levels (Chapter, Section, Subsection');
 writeln('or 2 levels (Section, Subsection)? (Input  3  or  2)');
 readln(i); SectionReset:=(i=3); ImplAdj:=3-i;
   (*ImplAdj (Implicit Adjustment) = 1 if chapter numbers are superfluous
     in labels, 0 otherwise.*)
 writeln('Do you want the chapter, section and subsection numbers in');
 writeln('the new file if they are all the current ones (y or n)');
 writeln('(Chapter numbers are included only in 3-level mode.)');
  readln(ans); omit:=ans='n';
 writeln('Do you want the label tags to remain in the output?');
 readln(ans); LabeltagsInOutput:=ans='y';
 FOR i:=0 TO 9 DO currentcount[i]:=0;
 TotalCount:=CurrentCount;
 writeln('Input the starting number >= 0 you want for chapters');
 readln(FirstChapter);
 currentcount[1]:=FirstChapter-1; FirstChapterlabel:=TRUE;
  writeln('Input the number >= 0 you want for the first section');
 readln(FirstSection);
 currentcount[2]:=FirstSection-1; FirstSectionlabel:=TRUE;
 writeln('I will beep when ready');
 (* Next create the arrays for storing the various types of labels. *)

 FOR i:=0 TO 9 DO new(mainlist[i]);

 Assign(incoming,filename);
 Reset(incoming);
 read(incoming, c);
 WHILE eof(incoming)=FALSE DO
 BEGIN
   IF ord(c) > 128 THEN BEGIN
     CASE c OF             (* cases of Macintosh labels *)
       '�': LabelKind:=0;
       '�': LabelKind:=1;
       '�': LabelKind:=2;
       '�': LabelKind:=3;
       '�': LabelKind:=4;
       '�': LabelKind:=5;
       '�': LabelKind:=6;
       '�': LabelKind:=7;
       '�': LabelKind:=8;
       '�': LabelKind:=9;
       ELSE (* In case the file comes from MS-DOS user of RELABEL *)
         IF ((227 <= ord(c)) AND (ord(c)<= 229))
           THEN LabelKind:= ord(c)-226
         ELSE IF ((221 <= ord(c)) AND (ord(c)<= 226))
                THEN LabelKind:= ord(c)-217
              ELSE IF ord(c)=220 THEN LabelKind:=0
                   ELSE GOTO 8;  (* c is not a label tag *)
     END;
     read(incoming, c);
     IF ((ord(c) = 196) OR (ord(c)=222)) THEN GOTO 8
     ELSE readlabel(sta,c);

      (* Do readlabel and the work below only if this is not
         labeled to be a reference.
           Next, we check if this is a repeat occurrence of a label.
         If not, enter in the list of labels and increase the count
         of labels of the kind we found.*)

     IF NOT InList(sta,SerialNo) THEN AddToList(sta);
   END; (* of processing a label *)
 8:read(incoming,c)
 END;  (* of WHILE loop which reads characters of the file *)

 writeln('First pass completed. I am starting to write the new file.');

 Reset(incoming);

 Assign(outgoing, outputfilename);
 Rewrite(outgoing);

 current:=empty;
 FOR i:=0 TO 9 DO currentcount[i]:=0;
 currentcount[1]:=FirstChapter-1;
 currentcount[2]:=FirstSection-1;

   (* We need to redo  current  to be able to restore the implicit
      parts of the labels as we encounter them on the second pass. *)

3:read(incoming,c);
 WHILE eof(incoming)=FALSE DO
 BEGIN
   IF ord(c) >=128 THEN BEGIN (* A label may begin here. *)
     CASE c OF
(* If we have a labelkind indicator used with Macintosh, convert it. *)
       '�': c:=chr(220);
       '�': c:=chr(227);
       '�': c:=chr(228);
       '�': c:=chr(229);
       '�': c:=chr(225);
       '�': c:=chr(226);
       '�': c:=chr(224);
       '�': c:=chr(222);
       '�': c:=chr(221);
       '�': c:=chr(223);
     END; (* Of replacing Macintosh labels. *)
     ForwardRef:=FALSE;
     LabelKind:=ord(c)-220;
     IF ((labelkind < 0) OR (LabelKind > 9)) THEN BEGIN
           (*The symbol is not a labelkind indicator. Write & exit.*)
       write(outgoing,c); GOTO 3
     END
     ELSE IF LabelKind > 6 THEN LabelKind:=LabelKind-6
          ELSE IF LabelKind>0 THEN LabelKind:= LabelKind+3;
              (*  End of determining LabelKind *)
     IF LabeltagsInOutput THEN write(outgoing,c);
     read(incoming,c);
     IF c=chr(196) THEN c:=chr(222); {Translate Macintosh forward ref. ind.}
     IF c=chr(222) THEN BEGIN
       ForwardRef:=TRUE;
       IF LabeltagsInOutput THEN write(outgoing,c);
       read(incoming,c)
     END;
     readlabel(sta,c);
     IF NOT(InList(sta,SerialNo)) THEN BEGIN
       writeln('Label not found in list on second pass.');
       writeln('You may have referred to a nonexistent label');
       writeln('or the level number you entered may be wrong.');
       write('LabelKind: ', LabelKind,'  Label: ');
       FOR i:=1+ImplAdj TO min(LabelKind-1, 3) DO
         IF sta[i] <> '' THEN write(sta[i],'.');
       IF ((LabelKind = 0) OR (LabelKind > 3)) THEN writeln(sta[4])
         ELSE writeln(sta[LabelKind]);
       write('Should I go on, with "???" in this reference?  y  or  n ');
       readln(ans);
       IF ans='n' THEN BEGIN close(incoming);close(outgoing);halt END;
       write(outgoing,'???'); GOTO 9;
     END; (* Of message and output, we had a reference to unknown label.*)
     IF NOT ForwardRef THEN BEGIN
       WITH mainlist[labelkind]^[SerialNo]^ DO BEGIN
         IF (NOT secondpassoccurred)THEN BEGIN
(* Should be here? currentcount[labelkind]:=currentcount[labelkind]+1; *)
          IF (0<labelkind) AND (LabelKind<4) THEN BEGIN
             currentcount[labelkind]:=currentcount[labelkind]+1;
              (* This is not a reference but the label of a new *)
                    (* subdivision. Adjust current subdiv.*)
             current[labelkind]:=oldlabel[labelkind];
             IF (currentcount[1]>FirstChapter)
                      OR (currentcount[2]>FirstSection) THEN BEGIN
               FOR i:=max(labelkind+1,ImplAdj+2) TO 3 DO BEGIN
                 current[i]:=''; currentcount[i]:=0;
               END;
             END;
           END; (* Of dealing with the label of a new heading. *)
           secondpassoccurred:=TRUE;
         END
         ELSE IF LabeltagsInOutput THEN write(outgoing,'�');
              (* We have a reference.*)
       END; (* Of WITH mainlist...*)
     END; (* Of NOT ForwardRef *)
      (* Now we are ready to get the new label and convert it to a
         string. This can not be done once for all since the string
         may be shorter in a reference to the item from within
         the same subunit. Doing the job from scratch each time just
         because we may need one or the other of two expressions
         is admittedly inelegant but seldom are there many references
         to one item. *)
     MakeNewLabelstring(s,LabelKind, SerialNo);
     write(outgoing,s);
9:    IF PeriodFollows THEN write(outgoing,'.');
   END (* of assembling and writing the new label. *)
   ELSE BEGIN (* ord(c)<128, certainly not a labelkind symbol*)
     write(outgoing,c);
     read(incoming,c);
   END;  (* of IF ord(c)>=128..ELSE statement. *)
 END; (* Return to the beginning of the WHILE eof(incoming)=FALSE cycle *)
 write(outgoing,c); (* write the last symbol of the file *)
 close(outgoing);close(incoming);
 writeln(chr(7),'"',outputfilename,'"', 'written on disk.');
 FOR i:=0 TO 9 DO dispose(mainlist[i]);
 write('Relabel another file? (y or n)');
 readln(c); IF c='y' THEN GOTO 1;
 END.