Contributor: SWAG SUPPORT TEAM        

        ANSWERS TO FREQUENTLY ASKED PASCAL QUESTIONS
        ============================================

1...
                                                                              
  Q. How do I pass an error level code when my program finishes?              
                                                                              
  A. The halt procedure takes an optional parameter of type word. Thus -      
                                                                              
         halt(1);                                                             
                                                                              
     terminates the program with an errorlevel of 1.  If halt is used without 
     a parameter it is the same as -                                          
                                                                              
         halt(0);                                                             
                                                                              
     Note:  When a program is terminated using the halt procedure any exit    
            procedure that has previously been set up is executed.            


2...
                                                                          
  Q. How do I empty the keyboard buffer?                                      
                                                                              
  A. There are several ways that this can be achieved.  However the safest    
     is -                                                                     
                                                                              
        while Keypressed do ch := ReadKey;                                    
                                                                              
     This requires that a variable ch of type char is declared and the crt    
     unit be used.  To do it without using a variable -                       
                                                                              
       while Keypressed do while ReadKey = #0 do;                             
                                                                              
     or if using TP6 with extended syntax enabled -                           
                                                                              
        while KeyPressed do ReadKey;                                          
                                                                              
     If you do not wish to incur the substantial overhead involved with the   
     use of the CRT unit and there is no requirement for the program to run   
     under a multi-tasker -                                                   
                                                                              
        var                                                                   
          head : byte absolute $40:$1c;                                       
          tail : byte absolute $40:$1e;                                       
                                                                              
        tail := head;                                                         

3...

  Q. When I redirect the screen output of my programs to a file the file is   
     empty and the output still appears on the screen. What am I doing        
     wrong?                                                                   
                                                                              
  A. You are probably using the CRT unit and its default method of writing    
     to stdout is by direct screen writes.  In order to enable output to be   
     redirected all writes must be done by DOS.  Setting the variable         
     DirectVideo to false has no effect on redirection as all it does is use  
     the BIOS for screen writes - not DOS.                                    
                                                                              
     To enable redirection you must not use the CRT unit                      
                                                                              
     OR                                                                       
                                                                              
     assign(output,'');                                                       
     rewrite(output);                                                         
                                                                              
     This will make all output go through DOS and thus can be redirected if   
     desired.  To restore the default situation -                             
                                                                              
     AssignCRT(output); rewrite(output);                                      


4...

   Q. How do I make a string that is lower or mixed case all uppercase?
                                                                              
   A. There are several ways to convert lower case characters to upper case.  
      Here are some of them.                                                  
                                                                              
      As a procedure (excluding asm code this is the fastest way)             
                                                                              
        procedure StrUpper(var st: string);                                   
          var x : byte;                                                       
          begin                                                               
            for x := 1 to length(st) do                                       
              st[x] := UpCase(st[x]);                                         
          end;                                                                
                                                                              
      As a function (slower but sometimes more convenient) -                  
                                                                              
        function StrUpper(st: string): string;                                
          var x : byte;                                                       
          begin                                                               
            StrUpper[0] := st[0];                                             
            for x := 1 to length(st) do                                       
              StrUpper[x] := UpCase(st[x]);                                   
          end;                                                                
                                                                              
      Both the above are suitable for the English language .  However from    
      version 4.0 onwards, DOS has had the facility to do this in a way that  
      is country (language) specific.  I am indebted to Norbert Igl for the   
      basic routine.  I have modified his code slightly.  For the anti-goto   
      purists this is a good example of a goto that is convenient, efficient, 
      self-documenting and structured.  The dos calls would make this method  
      the slowest of all.                                                     
                                                                              
     function StrUpper(s: string): string;                                    
       { Country specific string-to-uppercase conversion. Requires DOS unit } 
       label                                                                  
         fail;                                                                
       var                                                                    
         regs : registers;                                                    
         x    : byte;                                                         
       begin                                                                  
         if lo(DosVersion) >= 4 then begin                                    
           with regs do begin                                                 
             ax := $6521;                                                     
             ds := seg(s);                                                    
             dx := ofs(s[1]);                                                 
             cx := length(s);                                                 
             msdos(regs);                                                     
             if odd(flags) then { the attempted conversion failed so }        
               goto fail;                                                     
           end; { with }                                                      
         end { if DOS >= 4.0 } else                                           
       fail:                                                                  
           for x := 1 to length(s) do                                         
             s[x] := UpCase(s[x]);                                            
         StrUpper := s;                                                       
       end; { StrUpper }                                                      



5...
                                                                              
   Q. When I include ANSI codes in a string and write that string to the      
      screen the actual codes appear on the screen, rather than the results   
      they are supposed to achieve.                                           
                                                                              
   A. In order for ANSI codes to be interpreted, screen writes must be        
      directed through DOS and there must have been a suitable driver loaded  
      via the config.sys file at boot time.  All output can be directed       
      through DOS and the driver by -                                         
                                                                              
      Not using the crt unit                                                  
                                                                              
      OR -                                                                    
                                                                              
      assign(output,'');                                                      
      rewrite(output);                                                        
                                                                              
      in which case ALL screen writes are "ANSI code sensitive"               
                                                                              
      OR -                                                                    
                                                                              
      You can set up write procedures that will be "ANSI code sensitive".     
      (You will need an initialisation procedure to set this up.)             
                                                                              
      var                                                                     
        ansi : text;                                                          
                                                                              
      procedure AssignANSI(var ansifile : text);                              
        begin                                                                 
          assign(ansifile,'CON');                                             
          rewrite(ansifile);                                                  
        end; { AssignANSI }                                                   
                                                                              
      procedure WriteANSI(var st: string);                                    
        begin                                                                 
          write(ansi,st)                                                      
        end; { WriteANSI }                                                    
                                                                              
      procedure WriteLnANSI(var st: string);                                  
        begin                                                                 
          writeANSI(st);                                                      
          writeln(ansi);                                                      
        end; { WriteANSI }                                                    
                                                                              
      ObviousLy, if the ANSI.SYS driver (or an equivalent) is not installed   
      none of the above can work.                                             
                                                                              
      Setting the variable DirectVideo in the CRT unit to false will not      
      achieve the desired result as this merely turns off direct screen       
      writes and uses the BIOS for all screen output.                         


6...
                                                                              
   Q. When I try to shell to DOS nothing happens. What am I doing wrong?      
                                                                              
   A. In order to be able to execute any child process there must be          
      sufficient memory available for it to load and execute.  Unless you     
      advise differently at compile time, a Turbo Pascal program grabs all    
      available memory for itself when it is loaded.  To reserve memory for a 
      child process use the compiler memory directive -                       
                                                                              
        {$M 16384,0,0)                                                        
      the default is -                                                        
        {$M 16384,0,655360}                                                   
                                                                              
      The first figure - StackMin - is the amount of memory to be allocated   
      for the stack:                                                          
                                                                              
      Minimum is:    1024                                                     
      Default is:   16384                                                     
      Maximum is:   65520                                                     
                                                                              
      The next figure - HeapMin -is the minumum amount of memory to be        
      allocated for the heap. If there is less memory available than this     
      figure the program will not load.                                       
                                                                              
      Minimum is:          0                                                  
      Default is:          0                                                  
      Maximum is:     655360  In practice it will be the amount of free       
                              memory less the space required for the stack,   
                              less the code space of the program.  You should 
                              set this to 0 unless your program uses the      
                              heap.  In that case, set it to the lowest       
                              possible figure to prevent heap allocation      
                              errors.  In most cases it is best to leave it   
                              at zero and do error checking within the        
                              program for sufficient memory at allocation     
                              time.                                           
                                                                              
      The last figure is the crucial on as regards child processes.  It       
      should always be low enough to leave memory left over for a child       
      process and high enough not to cause problems for the program when      
      allocating heap memory.                                                 
                                                                              
      Minimum is:  HeapMin                                                    
      Default is:  655360                                                     
      Maximum is:  655360     If less than the requested amount is available  
                              no error is reorted.  Instead all available     
                              memory is allocated for heap use.               



7...
                                                                              
   Q. How do I shell to DOS?                                                  
                                                                              
   A. SwapVectors;                                                            
      exec(GetEnv('COMSPEC','');                                              
      SwapVectors;                                                            
                                                                              
      Read previous section on memory allocation.                             
                                                                              
      I find that it is a good idea to write my own Exec function which will  
      do everything that is needed for me.  I have it return an integer value 
      that is the DosError code.                                              
                                                                              
      function Exec(p1,p2: string);                                           
        begin                                                                 
          SwapVectors;                                                        
          Dos.Exec(p1,p2);                                                    
          SwapVectors;                                                        
          Exec := DosError;                                                   
        end;                                                                  
                                                                              
      This enables me to have a statement such as -                           
                                                                              
      ReportError(Exec(GetEnv('COMPSEC'),''));                                
                                                                              
      Now you can have an empty ReportError procedure or you can make it      
      report the error - whatever is suitable for you application.            


8...
                                                                              
   Q. When I execute a child process redirection does not work. Why?          
                                                                              
   A. Redirection of a child process's output only works if it is run under   
      another copy of the command processor.  So -                            
                                                                              
      exec('YourProg.exe',' > nul');    will not work but                     
      exec(GetEnv('COMSPEC'),'/c YourProg > nul'); will work.                 


9...

   Q. How do I read an errorlevel from a child process?

   A. After executing a child process the errorlevel returned can be read
      by calling the DosExitCode function which returns a word.  The low
      byte is the errorlevel.  A full description is in the manual.

      If the command interpreter is the child process and it in turn
      executes a child process then the errorlevel of the second child
      process cannot be read without resorting to some trickery.


10...

   Q. When I read a text file that has lines exceeding 255 characters I
      lose all those characters from the 256th one on each time there is a
      line that exceeds that length.  How can I prevent this?

   A. Turbo Pascal's readln procedure reads a line up to the 255th
      character then skips to the next line.  To get around this you
      should declare a buffer at least as large as the longest possible
      line and then use the read procedure.  The best size for the buffer
      is a multiple of 2048 bytes.

      const
        BufferSize = 2048;
        LineLength = 78;
      type
        textbuffer = array[1..BufferSize] of char;
      var
        st          : string;
        f           : text;
        buffer      : textbuffer;

      function ReadTxtLn(var tf: text; var s: string; max: byte): integer;
        { Reads a string of a maximum length from a text file }
        var
          len         : byte absolute s;
        begin
          len := 0;
          {$I-}
          while (len < max) and not eoln(tf) do begin
            inc(len);
            read(tf);
          end;
          if eoln(tf) then
            readln(tf);
          ReadTxtLn := IOResult;
          {$I+}
        end; { ReadTxtLn }

      begin
        assign(f,filename);
        reset(f);
        SetTextBuf(f,buffer);
        while not eof(f) and (ReadTxtLn(f,st,LineLength) = 0) do
          writeln(st);
        close(f);
      end.


11...

   Q. How do I convert nul terminated asciiz strings to Turbo Pascal
      strings?

   A. Here is a function that will do that -

      function Asc2Str(var s; max: byte): string;
        { Converts an ASCIIZ string to a Turbo Pascal string }
        { with a maximum length of max.                      }
        var starray  : array[1..255] of char absolute s;
            len      : integer;
        begin
          len        := pos(#0,starray)-1;              { Get the length }
          if (len > max) or (len < 0) then      { length exceeds maximum }
            len      := max;                         { so set to maximum }
          Asc2Str    := starray;
          Asc2Str[0] := chr(len);                           { Set length }
        end;  { Asc2Str }


12...

   Q. How can I tell if a particular bit of a variable is set or not? How can
      I set it?  How can I turn it off? How can I make a large bit map and
      then determine if a particular bit - say bit 10000 is on/of?

   A. This question, or a variation of it, is one of the most commonly asked
      questions in the echo and there are several ways of doing what is
      wanted.  None are necessarily right or wrong.  The way I will describe
      is designed to take up as little code/data space as possible.  I do not
      attempt to explain the theory behind these functions as this can be
      obtained from any good book. Question 16 also contains valuable extra
      help on the subject of truth tables.

      The use of sets can be the best bit manipulation method if you have
      control over the data being used. Here is an example of a byte variable
      for a BBS program which sets various user access level flags.

         Bit 0 = Registered User
             1 = Twit
             2 = Normal
             3 = Extra
             4 = Privileged
             5 = Visiting Sysop
             6 = Assistant Sysop
             7 = Sysop

       type
         status_type  = (Registered,
                         Twit,
                         Normal,
                         Extra,
                         Privileged,
                         VisitingSysop,
                         AssistantSysop,
                         Sysop);
          status_level = set of status_type;

       var
         access_flags  : status_level;

      Let us assume you have someone who logs on and you wish to determine
      his user access level.  After reading access_flags from the user data
      file -

           if Sysop in access_flags then ....

      To set the sysop flag -

           access_flags := access_flags + [Sysop];

      To reset the sysop flag -

           access_flags := access_flags - [Sysop];

      However on many occasions using a set may not be a suitable method.
      You may simply need to know if bit 5 is set or not.  Here is the method
      that I consider the best -

        function BitIsSet(var V,  bit: byte): boolean;
          begin
            BitIsSet := odd(V shr bit);
          end;

      To set a bit -

         procedure SetBit(var V: byte; bit: byte);
           begin
             V := V or (1 shl bit);
           end;

      To reset a bit -

         procedure ResetBit(var V: byte; bit: byte);
           begin
             V := V and not(1 shl bit);
           end;

      To toggle (flip) a bit -

         procedure ToggleBit(var V: byte; bit: byte);
           begin
             V := V xor (1 shl bit);
           end;

      Now a bit map can be made up from an array of bytes.  If stored on the
      heap you can test any bit up to number 524159 (zero based).  Here's
      how.

      type
        map = array[0..maxsize] of byte;
        { set maxsize to number of bits div 8 -1 needed in the bit map }

      function BitSetInBitMap(var x; numb : longint): boolean;
        { Tests the numb bit in the bitmap array }
        var m: map absolute x;
        begin
          BitSetInBitMap := odd(m[numb shr 3] shr (numb and 7));
        end;

      procedure SetBitInBitMap(var x; numb: word);
        { Sets the numb bit in the bitmap array }
        var m: map absolute x;
        begin
          m[numb shr 3] := m[numb shr 3] or (1 shl (numb and 7))
        end;

      procedure ResetBitInBitMap(var x; numb : longint);
        { Resets the numb bit in the bitmap array }
        var m: map absolute x;
        begin
         m[numb shr 3] := m[numb shr 3] and not(1 shl (numb and 7));
        end;

      procedure ToggleBitInBitMap(var x; numb : longint);
        { Toggles (flips) the numb bit in the bitmap array }
        var m: map absolute x;
        begin
          m[numb shr 3] := m[numb shr 3] xor (1 shl (numb and 7));
        end;


13...

   Q. How can I find a particular string in any file - text or binary?

   A. The Boyer-Moore string search algorithm is considered to be the fastest
      method available.  However in a rare worst-case scenario it can be
      slightly slower than a linear brute-force method.  The following
      demonstration program will show how it works and could easily be
      modified to allow for command line paramters etc.


      program BMSearchDemo;

      type
        bigarray = array[0..32767] of byte;
        baptr    = ^bigarray;
        BMTable  = array[0..255] of byte;

      const
        KeyStr : string = 'Put whatever you want found here';
        fname  : string = 'f:\Filename.txt';

      var
        Btable : BMtable;
        buffer : baptr;
        f      : file;
        result,
        position : word;
        offset : longint;
        finished,
        Strfound  : boolean;

      procedure MakeBMTable(var t : BMtable; var s);
        { Makes a Boyer-Moore search table. s = the search string t = the table }
        var
          st  : BMtable absolute s;
          slen: byte absolute s;
          x   : byte;
        begin
          FillChar(t,sizeof(t),slen);
          for x := slen downto 1 do
            if (t[st[x]] = slen) then
              t[st[x]] := slen - x
        end;

      function BMSearch(var buff,st; size : word): word;
        { Not quite a standard Boyer-Moore algorithm search routine }
        { To use:  pass buff as a dereferenced pointer to the buffer}
        {          st is the string being searched for              }
        {          size is the size of the buffer                   }
        { If st is not found, returns $ffff                         }
        var
          buffer : bigarray absolute buff;
          s      : array[0..255] of byte absolute st;
          len    : byte absolute st;
          s1     : string absolute st;
          s2     : string;
          count,
          x      : word;
          found  : boolean;
        begin
          s2[0] := chr(len);       { sets the length to that of the search string }
          found := false;
          count := pred(len);
          while (not found) and (count < (size - len)) do begin
            if (buffer[count] = s[len]) then { there is a partial match } begin
              if buffer[count-pred(len)] = s[1] then { less partial! } begin
                move(buffer[count-pred(len)],s2[1],len);
                found := s1 = s2;                   { if = it is a complete match }
                BMSearch := count - pred(len);      { will stick unless not found }
              end;
              inc(count);                { bump by one char - match is irrelevant }
            end
            else
              inc(count,Btable[buffer[count]]);   { no match so increment maximum }
          end;
          if not found then
            BMSearch := $ffff;
        end;  { BMSearch }


      begin
        new(buffer);
        assign(f,fname);
        reset(f,1);
        offset := 0;
        MakeBMTable(Btable,KeyStr);
        repeat
          BlockRead(f,buffer^,sizeof(buffer^),result);
          position := BMSearch(buffer^,KeyStr,result);
          finished := (result < sizeof(buffer^)) or (position <> $ffff);
          if position = $ffff then
            inc(offset,result);
          Strfound := position <> $ffff;
        until finished;
        close(f);
        if Strfound then
          writeln('Found at offset ',offset)
        else
          writeln('Not found');
      end.

14...

   Q. How can I put a apostrophe in a string?

   A. Just put in extra apostrophes.  If you want st to be equal to the
      string -
        The word 'quoted' is in quotes
      do this -
        st := 'The word ''quoted'' is in quotes';

      if you want the following to be written to screen -
        'This is a quoted string'
      do this -
        writeln('''This is a quoted string''');


15...

   Q. What are the best books to purchase to help me learn Turbo Pascal?

   A. There are many good books for learners.  Here are a few -

      Complete Turbo Pascal - Third Edition - Jeff Duntemann
      Mastering Turbo Pascal 6 - Tom Swann
      Turbo Pascal - The Complete Reference - O'Brien.

      For advanced users there are also many good books.  Here are a few
      that I have found useful - (Those marked with an asterisk are not
      purely for Turbo Pascal)

      Turbo Pascal 6 - Techniques and Utilities - Rubenking
      Turbo Pascal Internals - Tischer
      * PC System Programming for Developers - Tischer
      * Undocumented DOS - Schulman

      Any learner would be well advised to obtain a well known library
      such as Technojock's Turbo Toolkit (TTT) which is shareware and
      study the source code.

 16.

   Q. hat are "truth tables" and how do they work?

   A. Truth tables are a set of rules that are used to determine the result of
      logical operations.  The logical operators are -

        NOT
        AND
        OR
        XOR.

      Here is a brief explanation of truth tables.  When two values are
      logically compared by using a logical operator each bit of one value is
      directly compared to the corresponding bit in the other value and the
      same bit in the returned value is set or reset according to the
      following truth table.

             NOT         AND             OR            XOR
         not 1 = 0    0 and 0 = 0    0 or 0 = 0    0 xor 0 = 0
         not 0 = 1    0 and 1 = 0    0 or 1 = 1    0 xor 1 = 1
                      1 and 0 = 0    1 or 0 = 1    1 xor 0 = 1
                      1 and 1 = 1    1 or 1 = 1    1 xor 1 = 0

      NOT reverses the bit.
      AND sets the returned bit if both compared bits are set.
      OR  sets the returned bit if either of the compared bits are set.
      XOR sets the returned bit if the compared bits are not the same.


 17.

   Q. What are pointers and how can I use them?  I have heard that they are
      variables that can be created and discarded as required thus saving
      memory.  Is this true?

   A. A pointer is a variable that contains a memory address.

      The heap is all of that memory allocated by DOS to a program for its
      use that has not been used by the program for its code, global data or
      stack.

      Dynamic variables are variables that have had space allocated for them
      on the heap.

      Dynamic variables have no identifier (are unnamed).  Because of this
      they need an associated variable that can be used to find where they
      reside in memory. Pointers are ideal for this but need some method to
      define what type of data it is that they are pointing at.  Pascal
      provides this method.

        type
          Str10Ptr = ^string[10];
          { This means Str10Ptr is a pointer that points to data of type }
          { string[10].                                                  }
        var
          S : Str10Ptr;

      In the above example S is a pointer that has been defined as pointing
      to an address in memory that will contain (or should contain) data of
      type string[10].

      However how does S get this value?  How does it know where that data's
      address is supposed to be?  Well until the programmer allocates memory
      for that data S's value is undefined, so it could be literally
      pointing anywhere. So it is *vital* that before we try to use it to
      use/assign data from/to that memory location we give S a memory
      address that is not being used for any other purpose at the moment and
      that is big enough to hold the data that we want to place into it - in
      this case at least 11 bytes.  We do this by -

        new(S);

      Pascal has now allocated at least 11 bytes of heap and has allocated S
      with the address of the FIRST byte of that allocation.

      Ok... so far so good! How do we access that data (remembering that it
      has no name).  Well we "dereference" the pointer. This is done by
      placing a carat sign immediately following the pointer's identifier.

        S^ := 'Joe Bloggs';

      This statement actually means "Place the string 'Joe Bloggs' into the
      memory address that S contains". This is referred to as "derferencing"
      the pointer S.

      To "reference" a dynamic variable we "dereference" its associated
      pointer variable.  We cannot say -

        S := 'Joe Bloggs';

      because S is a pointer and that would be trying to give a pointer a
      string type value - a compiler "type mismatch" would occur. So every
      time we wish to access that dynamic variable we dereference it.

      To delete the dynamic variable once it is of no further use is just a
      matter of -

        dispose(S);

      What this statement does is release the memory previously used by S^
      and make it available to be used for other purposes by the program.
      Depending on the version of Pascal you are using it may not erase or
      alter the contents of that memory and it may not give S a new value.
      However any attempt to dereference S is an error as the integrity of
      that memory location has been lost - it may have been allocated to
      other data.

      Pointers do not *have* to point to a memory location in the heap or
      even have their value always allocated by using the New procedure. Any
      valid memory address can be assigned to them and then they can be
      dereferenced as shown above.  As a simple example of this lets say you
      want to examine the contents of the 16 byte area at $40:$f0 (the ICA
      area). You could - (TP specific)

         type
           ICA_Ptr = ^array[0..15] of byte;
         var
           B       : byte;
           ICA     : ICA_Ptr;

          ICA := ptr($40,$f0);

      Now ICA points to the address specified and you can dereference it -

          B := ICA^[10];

      Hope that helps get you started into the complex world of memory
      management and manipulation using pointers.  There are countless
      permutations and methods that can be used.


 18.

   Q. How do I do word wrap?

   A. The demo program WRAP.PAS in this archive demonstrates both word wrap
      and the justifying of text.