home *** CD-ROM | disk | FTP | other *** search
/ CD Actual 14 / CDACTUAL.iso / cdactual / demobin / share / program / asm / RPSRT102.ZIP / RPTAB.PAS < prev   
Encoding:
Pascal/Delphi Source File  |  1991-10-02  |  30.4 KB  |  677 lines

  1. {$A+,B-,D-,E-,F-,G-,I-,L-,N-,O-,R-,S-,V-,X-}
  2.  
  3. program RPTab;
  4.  
  5. {-------------------------Syntax Of RPTAB ----------------------------------}
  6.  
  7. { RPTAB   [/T]   input-filespec   output-filespec   [tabstop...]
  8.  
  9.  The two filespecs are required parameters while the /T and tabstops are
  10.  optional.  The parameters must be entered in the sequence indicated above.
  11.  
  12.  The input file is a text file that may or may not contain tabs.  The
  13.  contents of the output file will be the same except that:
  14.  
  15.  . If /T is NOT specified, any tabs in the input will be expanded to the
  16.    appropriate number of spaces in the output file.
  17.  
  18.  . If /T is specified,  spaces in the input file will be contracted to tabs
  19.    in the output file, wherever possible.
  20.  
  21.  If you don't specify any tab stops, the default tab stops are at columns
  22.  1, 9, 17, 25, 33 and so on at intervals of 8 columns.  If you specify tab
  23.  stops, they must be a sequence of integers each greater than the preceding
  24.  one.  The first tab stop is always at column 1 and you need not specify it.
  25.  RPTAB follows the rule that the interval between the last two tab stops,
  26.  you specify, implies subsequent tab stops at the same interval.  For
  27.  example, the command:
  28.  
  29.     RPTAB  MYTABS.DAT  MYSPACES.DAT  6 15 27
  30.  
  31.  tells RPTAB that the tab stops are at columns 1, 6, 15, 27, 39, 51 and etc.
  32.  The interval of 12 between 15 and 27 is propagated to subsequent tab stops.}
  33.  
  34.  {-------------- Const, Type and Variable Declarations ---------------------}
  35.   const
  36.     BuffSize = 32768;
  37.     StartFiles : Byte = 1;
  38.     StartTabs  : Byte = 3;
  39.     Contract   : Boolean = False;
  40.   type
  41.     TabArray = array[1..50] of Word;
  42.     DataArray = array[0..BuffSize-1] of Char;
  43.     DataPtr = ^DataArray;
  44.   var
  45.     Tab : TabArray;         {This array holds the tab stops to be used.}
  46.     TabCt : Byte;           {Number of tab stops specified or implied.}
  47.     IpFile, OpFile : file;
  48.     IpPtr, OpPtr : DataPtr; {Pointers to buffers for input and output files.}
  49.     IpNext, OpNext : Word;  {Offset of next byte in input and output buffers.}
  50.     IpRead, OpWritten : Word; {Actual bytes read/written by each I/O request.}
  51.     MoreData : Boolean;      {Set to False at end of input file.}
  52.     Column : Word;          {Current column in current output line.}
  53.     FillCt : Word;          {Spaces required to fill out tab.}
  54.     SpaceCt : Word;         {Spaces at end of input buffer.}
  55.  
  56. {----------------------- function GotFiles ---------------------------------
  57.  Function GotFiles returns the value True if it successfully opens both the
  58.  input and output files.  Otherwise it returns False.}
  59.   function GotFiles(var IpFile, OpFile : file; Start : Byte) : Boolean;
  60.     var
  61.       HoldIOResult : Word;
  62.     begin
  63.     {Must have enough parameters to include both input and output filespecs.}
  64.       if ParamCount < (Start + 1) then
  65.         begin
  66.           Writeln('Must specify an input file and an output file.');
  67.           GotFiles := False;
  68.           Exit
  69.         end;
  70.     {Setting FileMode=0 tells the Reset procedure to open file as read only.}
  71.       FileMode := 0;
  72.       Assign(IpFile, ParamStr(Start));
  73.       Assign(OpFile, ParamStr(Start+1));
  74.     {If Reset fails, display error message and set function result to False.}
  75.       Reset(IpFile, 1);
  76.       HoldIOResult := IOResult;
  77.       if HoldIOResult > 0 then
  78.         begin
  79.           case HoldIOResult of
  80.             2 :   Writeln('Input file not found: ', ParamStr(Start));
  81.             3 :   Writeln('Invalid input file spec: ', ParamStr(Start));
  82.             else  Writeln('Unable to open input file: ', ParamStr(Start));
  83.           end;
  84.           GotFiles := False;
  85.           Exit
  86.         end;
  87.     {If Rewrite fails, display error message, set function result to False.}
  88.       Rewrite(OpFile, 1);
  89.       HoldIOResult := IOResult;
  90.       if HoldIOResult > 0 then
  91.         begin
  92.           case HoldIOResult of
  93.             3 :   Writeln('Invalid output file spec: ', ParamStr(Start+1));
  94.             else  Writeln('Unable to open output file: ', ParamStr(Start+1));
  95.           end;
  96.           GotFiles := False;
  97.           Exit
  98.         end;
  99.     {If both files opened successfully, return function result True.}
  100.       GotFiles := True
  101.     end; {GotFiles}
  102.  
  103.  {------------------- procedure CloseDelete --------------------------------}
  104.   procedure CloseDelete;
  105.     begin
  106.       Close(IpFile);
  107.       Close(OpFile);
  108.       Erase(OpFile)
  109.     end;
  110.  
  111.  {--------------------- function GotTabs -----------------------------------
  112.   Function GotTabs returns the value True if it successfully creates the
  113.    array of tab stops.  Otherwise it returns False.}
  114.   function GotTabs(var Tab:TabArray; var TabCt:Byte; Start:Byte) : Boolean;
  115.     var
  116.       Temp : LongInt;
  117.       Code : Integer;
  118.       I    : Byte;
  119.     begin
  120.     {The default tab stops are at columns 1, 9, 17, 25 (and so on at
  121.      intervals of eight columns).  Internally, RPTab represents these as 0,
  122.      8, 16, 24 etc.  Since the interval between the last two explicit tab
  123.      stops is propagated to subsequent tab stops, EXPTABS sets two tab stops
  124.      at columns 0 and 8 in the Tab array and sets TabCT = 2.  It also sets
  125.      GotTabs to True on the assumption that tab stops will be OK.}
  126.       Tab[1] := 0;
  127.       Tab[2] := 8;
  128.       TabCt  := 2;
  129.       GotTabs := True;
  130.     {If ParamCount is less than Start, no tab stops were specified.  Thus,
  131.      RPTAB sticks with the default tab stops set above.}
  132.       if ParamCount < Start then Exit;
  133.     {If the first specified tab stop (ParamStr(Start)) is a valid integer and
  134.      equals 1, then having already set the first tab stop at 1, we will
  135.      increment Start.}
  136.       Val(ParamStr(Start), Temp, Code);
  137.       if (Code = 0) and (Temp = 1) then
  138.         if ParamCount > Start
  139.           then Start := Start + 1
  140.           else Exit; {Exit if the only tab stop specified is 1.}
  141.       TabCt := ParamCount - Start + 2;
  142.     {Get each tab stop in turn.  Check that it is an integer between 1 and
  143.      65535 and that it is greater than the previous tab stop.  If not,
  144.      display an error message and return with GotTabs = False.
  145.      If a tab stop is OK, decrement it by 1 and store it in the corresponding
  146.      Tab array bucket.  I decrement it because internally I count columns
  147.      starting with zero while externally I count them starting with one.}
  148.       for I := 2 to TabCt do
  149.         begin
  150.           Val(ParamStr(Start + I - 2), Temp, Code);
  151.           if (Code <> 0) or (Temp < 1) or (Temp > 32767) then
  152.             begin
  153.               Writeln('Tab stop must be integer between 1 and 32767: ',
  154.                       ParamStr(Start + I - 2));
  155.               GotTabs := False;
  156.               CloseDelete;
  157.               Exit
  158.             end;
  159.           if Tab[I - 1] >= (Temp - 1) then
  160.             begin
  161.               Writeln('Tab stop at ', Temp, ' must exceed the ',
  162.                       'previous tab stop at ', Tab[I - 1]+1, '.');
  163.               GotTabs := False;
  164.               CloseDelete;
  165.               Exit
  166.             end;
  167.           if ((Temp - 1) - Tab[I - 1]) > 255 then
  168.             begin
  169.               Writeln('Tab stop at ', Temp, ' must not exceed ',
  170.                       'previous tab stop at ', Tab[I - 1]+1,
  171.                       ' by more than 255.');
  172.               GotTabs := False;
  173.               CloseDelete;
  174.               Exit
  175.             end;
  176.           Tab[I] := Temp - 1
  177.         end
  178.     end;
  179.  
  180.  {-------------------- function  ReadOk ------------------------------------
  181.   Function ReadOk returns the value True if it successfully reads from the
  182.   input file.  Otherwise it displays an error message and returns False.}
  183.   function ReadOK(var IpFile : file; var Buff : DataArray; BuffSize : Word;
  184.                   var IpRead : Word) : Boolean;
  185.     var
  186.       HoldIOResult : Word;
  187.     begin
  188.       BlockRead(IpFile, Buff, BuffSize, IpRead);
  189.       HoldIOResult := IOResult;
  190.       if HoldIOResult <> 0 then
  191.         begin
  192.           Writeln('Error reading input file.');
  193.           ReadOK := False;
  194.           CloseDelete
  195.         end
  196.       else ReadOK := True
  197.     end;
  198.  
  199.  {---------------------- function WriteOK ----------------------------------
  200.   Function WriteOk returns the value True if it successfully writes to the
  201.   output file.  Otherwise it displays an error message and returns False.}
  202.   function WriteOK(var OpFile : file; var Buff : DataArray; WriteLen : Word;
  203.                    var OpWritten : Word) : Boolean;
  204.     var
  205.       HoldIOResult : Word;
  206.     begin
  207.       WriteOK := True;
  208.       BlockWrite(OpFile, Buff, WriteLen, OpWritten);
  209.       HoldIOResult := IOResult;
  210.       if HoldIOResult <> 0 then
  211.         begin
  212.           Writeln('Error writing output file.');
  213.           CloseDelete;
  214.           WriteOk := False
  215.         end;
  216.       if OpWritten <> WriteLen then
  217.         begin
  218.           Writeln('Ran out of space on disk writing output file.');
  219.           CloseDelete;
  220.           WriteOk := False
  221.         end;
  222.     end;
  223.  
  224.  {-------------------- function ExpandTabs --------------------------------
  225.   I coded ExpandTabs in assembly language for efficiency.  It scans the data
  226.   in the input buffer and copies it to the output buffer expanding tabs as
  227.   required.  It continues until it has filled up the output buffer or used
  228.   the entire input buffer.  Values are returned in the four var parameters as
  229.   follows:
  230.     IpNext : The offset of the next byte in the input buffer.  If this is
  231.              at buffer end, the entire buffer was used.  Else, it will be the
  232.              first byte used the next time  ExpandTabs is called.
  233.     OpNext : The offset of the next byte in the output buffer.  If this is
  234.              at buffer end, the entire buffer was filled.  Else, it will be
  235.              the first byte to be filled the next time ExpandTabs is called.
  236.     Column : The last line in the output buffer will often be incomplete.
  237.              Column is the offset, within that line, of the next byte to be
  238.              moved to it.  ExpandTabs will use this, the next time, to
  239.              correctly expand any subsequent tabs in the line.  Column
  240.              reflects the expansion of any earlier tabs in the line.
  241.     FillCt:  If a tab, in the input buffer, expands to more spaces than can
  242.              be held in the remainder of the output buffer,  the number of
  243.              additional spaces, required, is returned in FillCt.
  244.   Also, the result False is returned if a line longer than 32767 bytes is
  245.   found otherwise the result True is returned.}
  246.   function ExpandTabs(IpPtr, OpPtr : DataPtr; var IpNext, OpNext : Word;
  247.                        IpLen, OpLen : Word; TabCt : Byte; Tab : TabArray;
  248.                        var Column, FillCt : Word) : Boolean;
  249.     begin
  250.       asm
  251.         cld
  252.         mov  @Result,1      {Assume no long lines.}
  253.         push ds
  254.         les   bx,FillCt     {Address of FillCt.}
  255.         mov   cx,es:[bx]    {Value of FillCt.  If FillCt zero, then didn't}
  256.         jcxz @GetCol        {have unfinished tab at end of last op buffer.}
  257.         dec   IpLen         {Decrement Iplen because tab now used.}
  258.         mov   es:word ptr[bx],0 {Set FillCt to zero.}
  259.         les   bx,IpNext
  260.         inc   es:word ptr[bx] {Increment IpNext pointer past the tab.}
  261.         les   bx,Column     {Address of Column.}
  262.         add   es:[bx],cx    {Add fill length to Column.}
  263.         les   di,OpPtr      {Points to output  buffer.}
  264.         lds   bx,OpNext     {Address of OpNext.}
  265.         add   di,ds:[bx]    {Offset of next byte in output buffer.}
  266.         add   ds:[bx],cx    {Add fill length to OpNext.}
  267.         sub   OpLen,cx      {Reduce OpLen by length of fill.}
  268.         mov   al,20h
  269.         rep   stosb         {Fill with spaces.}
  270.         mov   bx,IpLen
  271.         or    bx,bx
  272.         jz   @Finished      {Finished if IpLen = 0.}
  273.       @GetCol:
  274.         les   bx,Column     {Address of Column.}
  275.         mov   cx,es:[bx]    {Value of Column.}
  276.         lds   si,IpPtr      {Points to input  buffer.}
  277.         les   bx,IpNext     {Address of IpNext.}
  278.         add   si,es:[bx]    {Offset of next byte in input buffer.}
  279.         les   bx,OpNext     {Address of OpNext.}
  280.         mov   ax,es:[bx]    {Value of OpNext.}
  281.         les   di,OpPtr      {Points to output buffer.}
  282.         add   di,ax         {Offset of next byte in output buffer.}
  283.         mov   bx,IpLen      {Length of data in input buffer.}
  284.         mov   dx,OpLen      {Available space in output buffer.}
  285.         mov   ah,TabCt      {Number of specified tab stops.}
  286.         push  bp            {Save stack frame pointer.}
  287.         lea   bp,Tab        {Offset in SS of Tab array.}
  288.       @NextByte:
  289.         lodsb               {Get next input byte.}
  290.         cmp  al,0dh
  291.         jbe  @IsItCR
  292.       @DoReg:              {If above CR (0dh) it is a regular character.}
  293.         inc  cx            {Increment Column.}
  294.         js  @LongLine2     {Jump if line exceeds 32767 bytes.}
  295.       @StoreOP:
  296.         stosb              {Store character in output buffer.}
  297.         dec  bx            {Decrement IpLen.}
  298.         jz  @FinishUp      {We are done if IpLen is used up.}
  299.         dec  dx            {Decrement OpLen.}
  300.         jnz @NextByte      {If more room in op buffer, go and get next byte.}
  301.         jmp @FinishUp      {We are done if OpLen is used up.}
  302.       @IsItCr:
  303.         jnz @IsItLF
  304.         xor  cx,cx         {Set Column = 0 when we find CR.}
  305.         jmp @StoreOp
  306.       @LongLine:
  307.         add  sp,4          {Clear TabCt and output pointer from stack.}
  308.       @LongLine2:
  309.         pop  bp            {Retore stack frame pointer.}
  310.         mov @Result,0      {If line exceeds 32767 bytes, set Result NG.}
  311.         jmp @Finished
  312.       @IsItLF:
  313.         cmp  al,0ah
  314.         jz  @StoreOp       {If LF, then don't change Column.}
  315.       @IsItTab:
  316.         cmp  al,09h
  317.         jnz  @DoReg        {If not CR, LF or Tab it is a regular character.}
  318.         push ax            {Save TabCt.}
  319.         push di            {Save offset of next op byte.}
  320.         mov  di,-2         {Index for tab array search.}
  321.       @ScanTabs:
  322.         inc  di
  323.         inc  di            {Point to next tab stop in Tab array.}
  324.         cmp  cx,[bp+di]    {Compare Column to tab stop.}
  325.         jb   @FoundTab     {The first tab stop greater than Column is the}
  326.                            {tab stop we want to space out to.}
  327.         dec  ah            {Decrement TabCt.}
  328.         jnz  @ScanTabs     {If more tabs in table, continue scan.}
  329.     {Column is beyond the last tab in the Tab array, so we must propagate the
  330.      interval between the last two explicit tab stops to find the tab stop to
  331.      space out to.  To do this we compute:
  332.      1. Column MINUS NextToLastTabStop
  333.      2. LastTabStop MINUS NextToLastTabStop
  334.      3. The result of line 1 MOD the result of line 2
  335.      4. The result of line 2 MINUS the result of line 3
  336.      If the interval from NextToLastStop to Column (line 1) was an exact
  337.      multiple of the interval from the NextToLastTabStop to the LastTabStop
  338.      (line 2) then clearly Column would fall on one of the propagated tab
  339.      stops.  In this case we would want to tab to the next tab stop or the
  340.      full interval between two tab stops.  Since the MOD (line3) would be
  341.      zero, in this case, line 4 will produce the correct result for the
  342.      number of spaces.  In any other case, the MOD will not be zero and we
  343.      will tab less than the full interval to the next tab stop as we should.}
  344.         push dx            {Save OpLen.}
  345.         mov  ax,[bp+di-2]  {Next to last tab stop in Tab array.}
  346.         mov  di,[bp+di]    {Last tab stop in Tab array.}
  347.         sub  di,ax         {Difference between last two tab stops.}
  348.         sub  ax,cx         {Next to last tab stop - Column.}
  349.         neg  ax            {Column - next to last tab stop.}
  350.         xor  dx,dx         {High word of zero.}
  351.         div  di            {dx=((Column-NextLast) mod (Last-NextLast))}
  352.         sub  di,dx         {di = Number of spaces required for tab.}
  353.         mov  ax,di
  354.         pop  dx            {Retrieve OpLen.}
  355.         add  di,cx         {di = value for column at next tab stop.}
  356.         jns @DoSpaces
  357.         jmp @LongLine      {Jump if line exceeds 32767 bytes.}
  358.       @FoundTab:
  359.         mov  ax,[bp+di]    {Tab stop to space out to.}
  360.         sub  ax,cx         {Spaces required = tab stop - Column.}
  361.       @DoSpaces:
  362.         pop  di            {Restore offset of next output byte.}
  363.         cmp  ax,dx         {Compare spaces required to OpLen.}
  364.         ja  @SpaceBeyond
  365.         xchg ax,cx         {ax = Column, cx = spaces required.}
  366.         add  ax,cx         {ax = adjusted Column.}
  367.         sub  dx,cx         {dx = adjusted OpLen.}
  368.         push ax            {Save Column.}
  369.         mov  al,20h
  370.         rep  stosb         {Store spaces.}
  371.         pop  cx            {Restore Column.}
  372.         pop  ax            {Restore TabCt.}
  373.         jz  @FinishUp      {Jump if OpLen reduced to zero.}
  374.         dec  bx            {Decrement IpLen.}
  375.         jz  @FinishUp      {We are done if IpLen is used up.}
  376.         jmp @NextByte      {Else go and get next ip byte.}
  377.     {This routine is executed if the number of spaces for the tab would carry
  378.      beyond the end of the output buffer.  In this case, I fill as many
  379.      spaces as possible and then set FillCt to the number of spaces needed to
  380.      finish the tab before returning.}
  381.       @SpaceBeyond:
  382.         dec  si            {Point back to tab.}
  383.         sub  ax,dx         {Value for FillCt.}
  384.         add  cx,dx         {Adjust Column for OpLen.}
  385.         push ax            {Save FillCt.}
  386.         push cx            {Save Column.}
  387.         mov  cx,dx         {cx = OpLen.}
  388.         mov  al,20h
  389.         rep  stosb         {Store spaces.}
  390.         pop  cx            {Restore Column.}
  391.         pop  dx            {Restore FillCt.}
  392.         pop  ax            {Restore TabCt.}
  393.         pop  bp            {Restore stack frame pointer.}
  394.         les  bx,FillCt
  395.         mov  es:[bx],dx    {Set FillCt to remaining spaces for tab.}
  396.         jmp @FinishUp1
  397.       @FinishUp:
  398.         pop  bp            {Restore stack frame pointer}
  399.       @FinishUp1:
  400.         les  bx,Column
  401.         mov  es:[bx],cx    {Update Column}
  402.       @FinishUp2:
  403.         les  bx,IpPtr      {Points to input buffer}
  404.         sub  si,bx         {New value of IpNext}
  405.         les  bx,IpNext     {Address of IpNext}
  406.         mov  es:[bx],si    {Update IpNext.}
  407.         les  bx,OpPtr      {Points to output buffer}
  408.         sub  di,bx         {New value of OpNext}
  409.         les  bx,OpNext     {Address of OpNext}
  410.         mov  es:[bx],di
  411.       @Finished:
  412.         pop  ds
  413.       end
  414.     end; {ExpandTabs}
  415.  
  416.  {-------------------- function ContractSpaces -----------------------------
  417.   I coded ContractSpaces in assembly language for efficiency.  It scans the
  418.   data in the input buffer and copies it to the output buffer contracting
  419.   spaces where possible.  It continues until it has filled up the output
  420.   buffer or used the entire input buffer.  Values are returned in the four
  421.   var parameters as follows:
  422.     IpNext : The offset of the next byte in the input buffer.  If this is
  423.              at buffer end, the entire buffer was used.  Else, it will be the
  424.              first byte used the next time  ExpandTabs is called.
  425.     OpNext : The offset of the next byte in the output buffer.  If this is
  426.              at buffer end, the entire buffer was filled.  Else, it will be
  427.              the first byte to be filled the next time ExpandTabs is called.
  428.     Column : The last line in the input buffer will often be incomplete.
  429.              Column is the offset, within that line, of the next byte.
  430.              ContractSpaces will use this, the next time, to correctly
  431.              contract any subsequent spaces in the line.  If the line
  432.              contained any tabs, Column reflects the position in the line as
  433.              if the tabs had been expanded.
  434.     SpaceCt: If there are one or more spaces at the end of an input buffer
  435.              and if the next position after the end of the input buffer is
  436.              not a tab stop, the count of these spaces is returned in
  437.              SpaceCt.
  438.   Also, the result False is returned if a line longer than 32767 bytes is
  439.   found otherwise the result True is returned.}
  440.   function ContractSpaces(IpPtr, OpPtr : DataPtr; var IpNext, OpNext : Word;
  441.                        IpLen, OpLen : Word; TabCt : Byte; Tab : TabArray;
  442.                        var Column, SpaceCt : Word) : Boolean;
  443.     var
  444.       PrevTab : Word;
  445.     begin
  446.       asm
  447.         cld
  448.         mov  @Result,1      {Assume no long lines.}
  449.         mov   PrevTab,0
  450.         push  ds
  451.         les   bx,SpaceCt    {Address of SpaceCt.}
  452.         mov   dx,es:[bx]    {Value of SpaceCt.}
  453.         les   bx,Column     {Address of Column.}
  454.         mov   cx,es:[bx]    {Value of Column.}
  455.         lds   si,IpPtr      {Points to input  buffer.}
  456.         les   bx,IpNext     {Address of IpNext.}
  457.         add   si,es:[bx]    {Offset of next byte in input buffer.}
  458.         les   bx,OpNext     {Address of OpNext.}
  459.         mov   ax,es:[bx]    {Value of OpNext.}
  460.         les   di,OpPtr      {Points to output buffer.}
  461.         add   di,ax         {Offset of next byte in output buffer.}
  462.       @NextByte:
  463.         lodsb               {Get next input byte.}
  464.         dec   IpLen
  465.         cmp   al,20h
  466.         je   @DoSpace
  467.         cmp   al,09h
  468.         je   @DoTab
  469.         or    dx,dx         {Is SpaceCt equal to zero.}
  470.         jz   @StoreOp       {If not then jump else store spaces.}
  471.         mov   ah,al         {Hold ip character.}
  472.         mov   al,20h
  473.         mov   bx,cx         {Hold Column.}
  474.         cmp   dx,OpLen
  475.         jb   @StoreSpaces   {Jump if SpaceCt < OpLen.}
  476.         dec   si            {Point back to ip character.}
  477.         mov   cx,OpLen
  478.         sub   dx,cx         {Subtract OpLen from SpaceCt.}
  479.         rep stosb           {Store OpLen spaces.}
  480.         mov   cx,bx         {Recover Column.}
  481.         jmp  @FinishUp
  482.       @StoreSpaces:
  483.         mov   cx,dx         {SpaceCt.}
  484.         sub   OpLen,dx      {Adjust remaining OpLen for spaces.}
  485.         rep stosb           {Store SpaceCt spaces.}
  486.         mov   cx,bx         {Recover Column.}
  487.         mov   al,ah         {Recover ip character.}
  488.         xor   dx,dx         {SpaceCt = 0.}
  489.       @StoreOp:
  490.         stosb               {Store character in output buffer.}
  491.         dec   OpLen
  492.         cmp   al,0ah
  493.         jz   @CheckDone     {Jump if character is linefeed.}
  494.         cmp   al,0dh
  495.         jnz  @StoreOp2
  496.         xor   cx,cx         {If carriage return, Column is set to zero.}
  497.         mov   PrevTab,0
  498.         jmp  @CheckDone
  499.       @StoreOp2:
  500.         inc   cx            {Increment Column}
  501.         js   @LongLine      {Jump if line exceeds 32767 bytes.}
  502.       @CheckDone:
  503.         cmp   IpLen,0
  504.         jz   @FinishUp      {We are done if IpLen is used up.}
  505.         Cmp   OpLen,0
  506.         jz   @FinishUp      {We are done if OpLen is used up.}
  507.         jmp  @NextByte
  508.       @DoTab:
  509.         inc   cx            {Increment Column.}
  510.         js   @LongLine      {Jump if line exceeds 32767 bytes.}
  511.         call @GetNextStop
  512.         mov   PrevTab,ax
  513.         mov   cx,ax         {Set Column equal to next tab stop.}
  514.       @StoreTab:
  515.         mov   al,09h
  516.         stosb               {Store the tab or space.}
  517.         dec   OpLen
  518.         xor   dx,dx         {Set SpaceCt to zero.}
  519.         jmp  @CheckDone
  520.       @DoSpace:
  521.         inc   dx            {Increment SpaceCt.}
  522.         inc   cx            {Increment Column.}
  523.         js   @LongLine      {Jump if line exceeds 32767 bytes.}
  524.         cmp   cx,PrevTab    {Compare Column to prev tab stop.}
  525.         jb   @CheckDone     {If before tab stop, not yet time to store tab.}
  526.         je   @StoreTab      {If at tab stop, then store tab.}
  527.         call @GetNextStop
  528.         mov   PrevTab,ax
  529.         cmp   cx,ax         {Compare Column to next tab stop.}
  530.         je   @StoreTab      {If at tab stop, then store tab.}
  531.         jmp  @CheckDone     {Else not yet time to store tab.}
  532.       @LongLine:
  533.         mov  @Result,0      {If line exceeds 32767 bytes, set result NG.}
  534.         jmp  @Finished
  535.       @GetNextStop:
  536.         lea   bx,Tab-2      {Index for tab array search.}
  537.         mov   ah,Tabct
  538.       @ScanTabs:
  539.         inc   bx
  540.         inc   bx            {Point to next tab stop in Tab array.}
  541.         cmp   cx,ss:[bx]    {Compare Column to tab stop.}
  542.         jbe  @FoundTab      {We want the first tab stop GE Column.}
  543.         dec   ah            {Decrement TabCt.}
  544.         jnz  @ScanTabs      {If more tabs in table, continue scan.}
  545.         push  dx            {Save SpaceCt.}
  546.         mov   ax,ss:[bx-2]  {Next to last tab stop in Tab array.}
  547.         mov   bx,ss:[bx]    {Last tab stop in Tab array.}
  548.         sub   bx,ax         {Difference between last two tab stops.}
  549.         sub   ax,cx         {Next to last tab stop - Column.}
  550.         not   ax            {Column - next to last tab stop.}
  551.         xor   dx,dx         {High word of zero.}
  552.         div   bx            {dx=((Column-NextLast) mod (Last-NextLast))}
  553.         sub   bx,dx         {bx = Number of spaces required for tab.}
  554.         mov   ax,bx
  555.         add   ax,cx         {NextTabStop = Number of spaces plus Column.}
  556.         dec   ax
  557.         pop   dx            {Retrieve SpaceCt.}
  558.         jns  @ScanTabsRet
  559.         mov   ax,32767      {Never return a tab stop greater than 32767.}
  560.       @ScanTabsRet:
  561.         ret
  562.       @FoundTab:
  563.         mov  ax,ss:[bx]    {Next tab stop.}
  564.         ret
  565.       @FinishUp:
  566.         les  bx,SpaceCt
  567.         mov  es:[bx],dx
  568.         les  bx,Column
  569.         mov  es:[bx],cx    {Update Column}
  570.         les  bx,IpPtr      {Points to input buffer}
  571.         sub  si,bx         {New value of IpNext}
  572.         les  bx,IpNext     {Address of IpNext}
  573.         mov  es:[bx],si    {Update IpNext.}
  574.         les  bx,OpPtr      {Points to output buffer}
  575.         sub  di,bx         {New value of OpNext}
  576.         les  bx,OpNext     {Address of OpNext}
  577.         mov  es:[bx],di
  578.       @Finished:
  579.         pop  ds
  580.       end
  581.     end; {ContractSpaces}
  582.  
  583.  {-------------- procedure LongLineMsgAndHalt ------------------------------
  584.   This procedure displays an error message to the effect that a line exceeded
  585.   32767 bytes.  It then calls the CloseDelete procedure which closes the
  586.   files and deletes the output file.  Finally it executes Halt.}
  587.   procedure LongLineMsgAndHalt;
  588.     begin
  589.       Write('Error: Input line exceeds 32767 bytes.  ');
  590.       Writeln('Input is probably not a text file.');
  591.       CloseDelete;
  592.       Halt
  593.     end;
  594.  
  595.  {------------------- Main program block -----------------------------------}
  596.   begin
  597.     Writeln; {Leave a blank line before completion or error message}
  598.   {If /T is specified, then we will contract spaces to tabs.}
  599.     if (ParamCount >= 1) and
  600.        ((ParamStr(1) = '/T') or (ParamStr(1) = '/t')) then
  601.       begin
  602.         Contract   := True;
  603.         StartFiles := 2;  {Input file parameter must be ParamStr(2).}
  604.         StartTabs  := 4   {First tab stop parameter must be ParamStr(4).}
  605.       end;
  606.   {If unable to open the files or to create the table of tab stops, I halt
  607.    since the error message would have been displayed by the called routine.}
  608.     if not GotFiles(IpFile, OpFile, StartFiles) then Halt;
  609.     if not GotTabs(Tab, Tabct, StartTabs) then Halt;
  610.   {Get 32K buffers for input and output. Reading and writing 32K at a time is
  611.    more efficient than a line at a time.}
  612.     New(IpPtr);
  613.     New(OpPtr);
  614.     OpNext := 0; {Start at position zero of output buffer.}
  615.     Column := 0; {Start at position zero of the first line.}
  616.     FillCT := 0; {Indicate no tab to be finished from previous time.}
  617.     SpaceCt := 0; {Indicate no spaces unused from previous time.}
  618.   {Repeat until entire input file has been read and processed.}
  619.     repeat
  620.       IpNext := 0; {Reading new input, so start position in buffer is zero.}
  621.   {Read 32K (BuffSize) into the input buffer.  If read is nogood, halt.}
  622.       if not ReadOK(IpFile, IpPtr^, BuffSize, IpRead) then Halt;
  623.   {If read full buffer then MoreData is True, else False.}
  624.       MoreData := IpRead = BuffSize;
  625.   {Repeat until all data in the input buffer has been copied to the output
  626.    buffer with tabs expanded.}
  627.       repeat
  628.   {ContractSpaces copies input to output buffer with spaces contracted until
  629.    output buffer is full or entire input buffer has been used.
  630.    ExpandTabs copies input output buffer with tabs expanded until output
  631.    buffer is full or entire input buffer has been used.
  632.    The if statement, below, takes advantage of Turbo Pascal's short circuit
  633.    Boolean evaluation which proceeds left to right and stops as soon as the
  634.    result of an expression is known.  This means that the boolean function
  635.    ContractSpaces is only executed if Contract is True.  If ContractSpaces
  636.    is successful, it returns a True value and the null "then" clause is
  637.    executed.  If it fails (only possible error is too long a line), then
  638.    the entire expression is False since "not Contract" in the second half of
  639.    the expression is False.  This means that the "else" clause will be
  640.    executed.  If Contract is False, we do ExpandTabs and the explanation is
  641.    similar.}
  642.         if  (Contract and
  643.              ContractSpaces(IpPtr, OpPtr, IpNext, OpNext, IpRead-IpNext,
  644.                             BuffSize-OpNext, TabCt, Tab, Column, SpaceCt))
  645.          or (not Contract and
  646.              ExpandTabs(IpPtr, OpPtr, IpNext, OpNext, IpRead-IpNext,
  647.                         BuffSize-OpNext, TabCt, Tab, Column, FillCt)) then
  648.         else
  649.           LongLineMsgAndHalt;
  650.   {If output buffer full, write it to the output file.}
  651.         if OpNext = BuffSize then
  652.           begin
  653.             if not WriteOK(OpFile, OpPtr^, BuffSize, OpWritten) then Halt;
  654.             OpNext := 0
  655.           end
  656.       until IpNext = IpRead;
  657.     until not MoreData;
  658.   {If have partial unwritten output buffer, at end, then write it.}
  659.     if OpNext <> 0 then
  660.       if not WriteOK(OpFile, OpPtr^, OpNext, OpWritten) then Halt;
  661.   {If input file ended with one or more trailing spaces, write them to the
  662.    output file.}
  663.     while Contract and (SpaceCt <> 0) do
  664.       begin
  665.         if SpaceCt > BuffSize then OpNext := BuffSize else OpNext := SpaceCt;
  666.         SpaceCt := SpaceCt - OpNext;
  667.         FillChar(OpPtr^, OpNext, Chr(32));
  668.         if not WriteOK(OpFile, OpPtr^, OpNext, OpWritten) then Halt
  669.       end;
  670.     Close(IpFile);
  671.     Close(OpFile);
  672.     if Contract then
  673.       Writeln('Contraction of spaces to tabs completed.')
  674.     else
  675.       Writeln('Tab expansion completed.')
  676.   end. {Main program block.}
  677.