[Back to FAQ SWAG index] [Back to Main SWAG index] [Original]

            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.

[Back to FAQ SWAG index] [Back to Main SWAG index] [Original]