Please consider a donation to the Higher Intellect project. See or the Donate to Higher Intellect page for more info.

Turbo Pascal for DOS Tutorial

From Higher Intellect Vintage Wiki
Jump to navigation Jump to search
                        Turbo Pascal for DOS Tutorial
                              Table of Contents
                   Copyright (c) 1996 by Glenn Grotzinger

Part 1: The basics.
        A) Basic discussion of what a program is and some rudimentary
           operations such as assign statements, simple variables,
           computations, and screen writes and reads.

Part 2: IF statements, FOR loops, and format codes.
        A) Concepts of the usage of IF statements, FOR loops, and
           format codes that may be placed with writes.

Part 3: WHILE loops, REPEAT loops, CASE statements, string addressing.
        A) Concepts of the usage of WHILE loops, REPEAT loops, CASE
        B) Methods on how to address a string or parts of a string.

Part 4: Procedures and Functions; TYPE and CONST declarations.
        A) Methods of defining procedures and functions in a program.
        B) The usage of TYPE and CONSTant declarations.

Part 5: Reading and Writing to Text Files; writing to the printer
        A) Concepts of reading from text files.
        B) Concepts of writing to text files and to the printer.

Part 6: Arrays and their Usage; ASCII conversion functions
        A) Concepts and usage of arrays.
        B) Usage of the functions chr() and ord().

Part 7: Records and their usage; Mathematics Concepts of the Computer.
        A) Concepts and usage of records.
        B) Concepts of Mathematics.
           1) Mathematics functions offered by TP.
           2) Binary and Hexadecimal notation.

Part 8: DOS file functions and their usage.
        A) A basic summary of Turbo Pascal functions and procedures that
           are equivalent to DOS commands.
        B) Concepts of most of the procedures and functions in the DOS
           or WinDOS units.

Part 9: Applications Development.
        A) Concepts of applications development.  The usage of pseudocode
           to solve a problem.

Part 10: Reading and Writing to Binary Files; Units and Overlays.
        A) Concepts of reading binary files.
        B) Concepts of writing to binary files.
        C) Concepts and usage of units.
        D) Concepts and usage of overlays.

Part 11: Interfacing with a Common Format; or how is different
variables stored by Pascal in memory, and on disk files?
        A) Discussions of storage formats in memory and disk by Turbo
        B) Interpretation of meanings of data presented on a commonly
           used format.

Part 12: Stacks; Queues
        A) Concepts and usage of stacks and queues.

Part 13: Use of Recursion
        A) Concepts and usage of recursion.

Part 14: The CRT Unit commands not already covered; Reading of extended
keys on the keyboard.
        A) Changing colors on the screen (text mode).
        B) Working with the PC speaker.
        C) Controlling the screen appearance and cursor position.

Part 15: Three different designed array sorts.
        A) Definition and concept of usage of the BubbleSort.
        A) Definition and concept of usage of the QuickSort.
        B) Definition of the ShellSort.

Part 16: Methods of searching arrays for data (binary search).
        A) Concepts of the Serial Search.
        B) Concepts of the Binary Search.

Part 17: Use of Pointers in variables, and procedures; designing a 
set exit procedure (exitproc). (Dynamic Variables)
        A) Concepts of the addressing of pointers in variables.
        B) Concepts of addressing pointers in procedures and functions.
        C) Design of an exit procedure (use of exitproc).

Part 18: Design and use of chained lists, or linked lists; the linked
list sort.
        A) Concepts of SLLLs, DLLLs, SLCLs, DLCLs.
        B) Sorting data using a linked-list.

Part 19: Descriptions of other types of pointer-linked structures;
        A) Concepts of a binary tree.
        B) Usage of a binary tree.

Part 20: Linking assembler into Pascal code; special topics.
        A) Linking OBJ files into Pascal code.
        B) Hooking a hardware interrupt and writing interrupt code.
        C) Calling a specified interrupt procedure.
        D) Including inline statements in pascal code.
        E) Including assembler statements in pascal code.
        F) Conditional compiler directives.

Part 21: BGI graphics; plotting graphics.
        A) Concepts of usage of the Borland Graphics Interface.
        B) Plotting graphics.
        C) Concepts behind drawing of lines, circles, and other
           geometric figures.
                        Pascal Tutorial: Learning Pascal
                             by Glenn Grotzinger
                           Part 1 -- Starting Out.
             all parts copyright (c) 1995-1996 by Glenn Grotzinger.

        I am writing this tutorial as a means for people to start out and
learn a great deal about Pascal.  I write this part with the understanding
that you have looked at the compiler, and have figured out how to use it to
enter a program, how to compile and run a program within the IDE, save a
source code file, or compile at the DOS prompt, or in the IDE to create an
EXE file; but do not have any understanding of Pascal programming as a whole.
Any subsequent parts will be written assuming that you have read prior parts, and fully understand all the examples.  Please work through
the examples, entering them in yourself, to get used to programming in Pascal.
I also recommend that you print all parts out, so you may have reference
for any future parts that we see.
        As I am writing each part as we go, for right now, I will try
to have each part written and posted on a weekly basis.  

About this part
This will be a lengthy part, since we are trying to introduce enough material
to get to the point of writing a simple program.

The basics
        My intent in this section is to familiarize you with the proper
starting structure to remember for to get ready to write a program.  Let's
look at the short example below. (I numbered the lines with {}'s for purposes
of this explanation.)

{1} program tutorial1; uses crt;
{2}   var
{3}     world_stmt: string;
{4}   begin
{5}     world_stmt := 'Hello world!';
{6}     writeln(world_stmt);
{7}   end.

This is a relatively simple program for starting out.  Let us run through
what each line has.

{1} this is the programID, and the uses clause.
    program <identifer>; uses <library>;
    is the syntax.  <identifier> may be anything that we may decide to
    call the program. <library> is a specification of additional libraries,
    or *units* of commands that we may want to use.

    crt is the name of one library we will use a lot in our programming.
    The commands that can be accessed in this library will be detailed later
    on in the tutorial.  If we want to use any of the commands in a library,
    we must tell the compiler to use the library, and the uses statement
    does this.  I will specify if any commands we use will come out of a

{2} var is a signal we use at the beginning of a program block to tell the
    compiler that we want to start defining variables.

{3} This is a definition of a string, or sequence of text.  Variable defs.
    will be covered later in this part.  We are defining a variable named
    world_stmt to be a string.

{4} begin says we want to begin a block of code.

{5} & {6} are some program commands.  We will explain them later.

{7} end; ends a program block.

A few basic commands
I will now discuss variable definitions.
    string: a section of text. "Hello world!" would be a string.
    integer: a number which does not have a decimal part. 12 is an integer.
    char: one part of a string.  "G" is a character, while "GG" is not.
    real: a number which has a decimal part.  3.25 would be a real.

    If you wish to make some text as a remark to what something does,
    use the { key or (* to start and the } or *) to end it.

The assign.
    We use the := to assign a value to a variable.
    Examples of that would be such as:
       world_stmt := 'Hello world.';   { a definition of a string.  The
                                        ' s must be there on each side }
       choice_char := 'a';             { a definition of a character The
                                        ' s must be there on each side }
       money := 3.25;                  { a definition of a real }
       coins := 10;                    { a definition of an integer }
Arithmetic computations.
    We often have to do arithmetic to program and solve a problem.  I will
    illustrate addition, subtraction, multiplication, and division.

       sum := 3 + 2;   { we're telling the computer to add 3 and 2 and then
                         place 5 in an integer called sum.  Assignments can
                         also work with this way }

       sub := 10 - 7;  { we're telling the computer to subtract 7 from 10
                         and then place 3 in an integer called sub. }

       mult := 3 * 2;  { we're telling the computer to multiply 3 by 2. }

       divisn := 10 / 2; {dividing 10 by 2 }

     Any of these can be combined in one statement, with the order of
     operations being /, *, -, +.  ('s may be used to force one group of
     numbers.  For example:

       answer := 3 + (2 + 6) * 4;

     Rules:  We must use the following idea to determine whether things are
     OK to do for arithmetic.
        1) If we perform an arithmetic operation with anything with a real
           in it, the receiving variable in the assign must be defined as
           a real.
        2) If we perform a division with two integers that have a chance
           of dividing to become a real, we must use a real for a receiving
           variable.  12 / 7 would be an example and be 1.71 (rounded to 2
           decimal places).
     Note: dividing and getting a real or using a real in the other stuff
     will result in answers such as 3.232133412E+02.  I will cover later
     the way to make that look readable and normal.

We will now look at an example of some of the stuff above.

program tutorial2;
    first_number, second_number: integer;
    result: integer;
    first_number := 3;
    {assign first number the value of 3}
    second_number := first_number * 2;
    {assign 2nd number-multiply first number by 2}
    first_number := second_number - 5;
    {assign 1st number-old value of 1st number - 5}
    result := first_number + second_number;
    {assign result to be 1st number + 2nd number}

A question to understand what is going on.  Answer this one, and you
understand everything up to this point.  What is the value of each and
every one of the integer variables as listed in the tutorial2 after all
the statements execute? (Answer will appear in the next part).

     These are special operators.  Div places the whole number of a division
     in the receiving variable, and MOD places the remainder.  For example:

     whole := 12 div 7;  {whole becomes 1}
     remainder := 12 mod 7; {remainder becomes 5}

     7 goes into 12 one time with a remainder of five.

reading and writing information.
     We will stick to use of the keyboard for reading information, and
     the monitor for writing information right now.  Remember for any
     variable, we must not define it to be the name of a command, when we
     do this.


     This command stops the program and waits for the user to input data
     which will be placed in a_number and does NOT produce a movement to
     a new line.


     This command does exactly as read, but produces a movement to a new


     This command writes the contents of a_number to the screen without a
     new line.


     This command does exactly as write, but produces a movement to a new
     line on the screen.

   These commands can be used with any combination of literal, variable,
   or arithmetic expression. A literal is a defined statement.
   3 is a literal in write(3);.  write(3); will write a 3 on the monitor.

   program tutorial3;
       some_text: string;
       write('Type some text and press ENTER when done: ');
       writeln('You just typed the following: ', some_text);

   Proper events in this program will be (as it will appear on the screen):

   Type some text and press ENTER when done: <input text here>
   You just typed the following: <text here>

   The readln prompts you to enter text which is rewritten with the last
   writeln command.  If you type the examples in and compile them up to this
   point (as I recommend -- it will help you learn), you will see exactly
   how tutorial3 is supposed to work.

The End of Part 1
        All of my tutorial parts will be formatted much like this one.  First
I will cover some new topics, giving examples, then as the final act will
always be a programming problem, which I will leave you, the reader, to
solve, learning on your own.  The best thing to learn and get competent in
any language is to actually sit down and program.  Any of the programming
problems I leave you will not involve concepts that I have not covered in
previous text, though I will try my best to make them challenging to further
the reader's programming ability.  A solution to each of the problems  in each
part will be presented in the next part.  My rules to you.  These are for
your benefit.

1) Do not ask anyone else to help you in any programming sub, or anywhere
   in programming these little practice programs I give. You will not
   learn anything, if at all, and your time looking through this will be
2) Anyone who does know, please do not help anyone who is going through
   this tutorial.  They will not learn if someone else gives them the answer!
3) Try and at least attempt the practice programming problem.  Do not just
   sit and wait until I present a solution.  The syntax is easy to learn
   from having notes and such, but the logic of actually programming some-
   thing is only gotten by practice.

If you wish me to give your code from the practice programming problems in
this tutorial a quick look, send 'em to me at [email protected]

Practice Programming Problem for Tutorial Part 1
        Write a Pascal program (and entirely Pascal) which will accept
two integers from the keyboard, presenting the user with a prompt to
enter a number for each integer.  Then print out statements which tell us
what the two digits add up to, subtract to, and multiply to.
To be correct, you must act on the first number and then the second
number in your computations.  For example, 14 and 7 (in that order) would
be treated as 14+7, 14-7, and 14*7.

  Example Monitor Screen (using 14 and 7):
  Please enter the 1st number: 14
  Please enter the 2nd number: 7

  Adding 14 and 7 gives 21.
  Subtracting 7 from 14 gives 7.
  Multiplying 14 and 7 gives 98.

Good luck!  And a solution to this problem will appear in part two!

Next Time
Next time, we will discuss the use of decision-making (IF statements) in
Pascal programming, as well as loops which repeat a defined, set number of
times (FOR loops).  Eventually, when the tutorial is over, we will have
covered most, if not all of the data and control structures, and a few
special topics of interest to you (in Pascal).  If you have any comments
please send them to [email protected]
                        Turbo Pascal for DOS Tutorial
                     Part 2 -- IF statements and FOR loops.
                             by Glenn Grotzinger
            all parts copyright 1995-96 (c) by Glenn Grotzinger.

        Hello again.  This part got in a little late (finals in COBOL
programming).  We'll get started with IF statements and FOR loops after
we get the old business taken care of.  Before, we saw this set of code:

    first_number := 3;
    second_number := first_number * 2;
    first_number := second_number - 5;
    result := first_number + second_number;

The question was what the value of all the variables would be at the end of
the code.  Let's look.  The first statement is a simple assign statement,
assigning first_number the value of 3.  The second statement assigns second_
number the value of first_number (3) * 2 which is 6.  The third statement
then assigns the first_number the value of second_number (6) - 5 which is
1.  And then the final statement assigns result to the addition of first and
second number.  So, the values of all the variables would be
first_number := 1; second_number := 6; and result := 7;.

The example of a solution of last weeks programming question:

program part1;

  { This program accompanies part1.  It is a program designed to take 2
    numbers as input from the keyboard and perform an addition,
    multiplication, and subtraction on the number, writing the
    results out to the monitor. }

    num1, num2: integer;

  begin { part1 }

    { take input for the 2 numbers }
    write('Please enter the 1st number: ');
    write('Please enter the 2nd number: ');

    writeln;  { place an empty line }
    writeln('Adding ', num1, ' and ', num2, ' gives ', num1 + num2, '.');
    writeln('Subtracting ', num2, ' from ', num1, ' gives ',
             num1 - num2, '.');
    {It is OK to break a command-line of code line this, as long as you
     don't break it on a literal statement}
    writeln('Multiplying ', num1, ' and ', num2, ' gives ', num1 * num2,

  end. { part1 }

OK.  Let's move on to other topics....

Format Codes for Read, Write, Readln, and Writeln
For all of these commands, we can use formatting codes to place our output,
or input from any source, or to any source (these are always applicable).
Typically, remember that a monitor in text mode has 80 columns and 25 rows.

     writeln('I am centered on the screen.':62);

This is a good first example.  What will happen is that writeln will place
the end of this text (the .) on screen position 62 columns relative to where
the write pointer is before this command is executed.  Another example.

     write(1 / 3 :8:3);
     write(1 / 4 :8:3);

Remember that for the screen placement that write/read will invalidate them
if they are not compliant (your code for that line must be greater than the
actual # of characters in the statement you write/read).  With the example
above, we see that it can be done with any type of variable we can write,
or read that is useful.  Above, I'm using expressions of division to
illustrate a point I mentioned last time about regular non-integer division
using decimals.  We'd get something like 3.3333333333E-01 for the first state-
ment, that is if we didn't use the second :# statement.  Obviously, if we
write a program we want to be understandable, we have to use something to
get regular decimal output (incidentally, the 3.33... stuff is how Pascal
stores real numbers in memory -- as scientific notation.).  So we use the
second statement.  The number is the number of decimal places we want to
use for the number.  So, to use those examples above, with a nice little
counter rule (so we can see what is happening only) would look like this:

   0.333   0.250

We see that the first number is kicking in...The last position is the 8th
column from the last write position (if we had writeln'd the first one, they
would be lined up on 2 separate lines).  And the answers are reported for
us in decimal format to the 3rd decimal place.  Just play around with
writing different things with these codes, and you'll get the idea of how
to use these codes when writing or reading something.

Quick note
We use ' marks to enclose statements.  What if we want to write one?  Pascal
recognizes that if you place two of them together inside those ' marks, it
will write the actual ' to the output file.  Also, if you can locate an
ASCII table, you can write ASCII codes (lines and such) to screen like this:
will write ASCII character 225.  You should be able to locate an ASCII chart
in your DOS manual, or your Pascal references that came with your compiler.

IF statements
A lot of times, we need to make decisions on a particular course of action.
IF statements are one of those tools.  If a condition is true, then perform
action is basically the logic behind this statement.  We can also assign an
alternate action for the condition.  We will see with this provisional little

program tutorial4;
    first_number, second_number: integer;
    writeln('Type an integer in, please.');
    writeln('Type another integer in, please.');
    if first_number > second_number then
       writeln(first_number, ' is greater than ', second_number, '.')
       writeln(first_number, ' is not greater than ', second_number, '.');

Basically, we made a decision based on the size of the two numbers and
wrote the result of that decision.  Note the format of the test statement.
You can use any of the symbols you remember to relate things together.
                Not equals is <> in pascal.
                Greater than or equal to is >= in pascal.
                Less than or equal to is <= in pascal.

        This type also introduces a new type of variable called boolean.
It can only have two values: true, and false.  This type is often good to
test conditions of run-time, such as you see with a lot of programs out
there which can be configured.  IF statements can be used with boolean
variables easily as well.
        Begin and end (end with a ; after it) can also be used in if-else
statements to execute MORE than one line of code if the condition is met,
or not met.  We use the next example.  We also use a multiple-choice
option for function.  We see also a way (not a good one, since there will
be better methods we will cover later.), to allow a catch-all system.
This is also an illustration of something we can most definitely do, is
nest if-else statements.

program tutorial5;
    one, two: integer;
    option: char;
    writeln('Enter an integer.');
    writeln('Enter another integer.');
    writeln('Use a mathematical symbol to indicate what you want to do');
    writeln('with these two numbers.');
    if option = '+' then
         writeln(one, ' + ', two, ' = ', one + two, '.');
         writeln('See, I can add.');
       if option = '-' then
          writeln(one, ' - ', two, ' = ', one - two, '.')
          if option = '*' then
              writeln(one, ' * ', two, ' = ', one * two, '.')
              if option = '/' then
                  writeln(one, ' / ', two, ' = ', one / two :0:3, '.')
             { we want to have the decimal point, so we MUST have the
               first one as well to be set to 0. }
                writeln('Use +, -, *, or / as your operator.  Try again.');

We can do pretty much as many nested ifs as we can, though we need to mini-
mize it as much as possible.  Also, keep in mind, we can use AND or OR to
multiply conditions.  Say, on the division, if we wanted to only honor a
division if the first number was greater than the second number  For that
section of code...

      if (option = '/') and (one > two) then

Code that's applicant to the IF statement above will execute ONLY when both
conditions are true.  If there are problems that come up in a program using
this, keep in mind the {$B+} compiler directive.  Compiler directives are
placed above the program; part of the program.  Type it as I typed it in the
illustration.  What this does is make it evaluate all the statements of what
I showed above.  Default for Pascal w/o this directive is short-circuit
evaluation.  If option is not / in the above statement, it will not bother to look at the
one > two part of it.  This won't hurt the statement I made above, but it
may for others.  It's something to experiment with...there are no set rules
on when to use the {$B+}.

FOR loops
This is a means to repetively execute commands a SET number of times.
They are implemented by an index variable, generally, an integer, but
OUTSIDE A FOR LOOP!  Always remember that, though one of those index
variables may be used as a "clean slate" variable for other things.
Examples of FOR loops are...

      for i := 1 to 7 do
      { starts at 1, counts to 7, stepping/adding by 1 each execution }
      for letter := 'a' to 'z' do
      { starts at a, counts through the alphabet, stepping to z by 1 letter }

      for i := 10 downto 1 do
      { starts at 10, counts down to 1, stepping/subtracting by 1 }

For this short example, keep in mind that the index variables CAN be
addressed inside of the for loop for the values we want....  For counter
variables, it's generally OK to use some letter like i or j or whatever...
Do make your variables DESCRIPTIVE, though.  That's a good programming

program tutorial6;
    i: integer;
    writeln('I''m going to write something 10 times.');
    for i := 1 to 10 do
      writeln('something', '(Time #':15, i, ')');

Type this one in and run it, and you'll see what it does.  It writes the
first statement above, then writes...

   something         (Time #1)
   something         (Time #2)
   something         (Time #10)

We can see a good, real use for this, and keep in mind that groups of
statements can be executed in a for loop, just like if statements by
using begin and end; operators, and for loops can be nested as well...

Practice Programming Problem Notes
Have people been able to do them adequately?  Please tell me.  Always be
sure to type the examples I give out to see how they work.  Experimentation
and doing the practice programming problems are the best things you can do
to learn by using this tutorial.  Remember, also, to print each of these
parts out as reference for later parts.  Make as a goal for the problems I
present at the end to follow all the guidelines, and try to make them as
SHORT as possible in # of lines of code.  Also, someone suggested a mailing
list to continue this.  If someone would tell me how to set it up (hint,
hint), we may go to that.  Any comments, questions, requests to look at
practice problem code, may be sent to [email protected]  Always keep in
mind that there are many different solutions possible to one problem, and
don't be upset if your program didn't look EXACTLY like mine.  If the
output is ACCURATE and your output looks exactly like mine, then your program
is good, and correct.

Practice Programming Problem #2
        Create a program in Pascal and entirely Pascal that will query the
user for a dimension number to be entered from the keyboard which can not
be greater than 15 (write an error message if it is), and then write out
a multiplication table with the dimension given by the user, with 3 column
spaces between each digit and the double-line (you must use the formatting
codes to accomplish this) to the screen.  Your program must calculate
everything, and nothing can be hard-coded (for example:
write('1   2   3   4   5   6') or something like that).

Example output for this program after execution should be something
like this on your screen (if you see garbage around the lookup columns,
those are continuous double lines <ASCII codes>):
What dimension number do you want for a table? 4

     1   2   3   4
 1К  1   2   3   4
 2К  2   4   6   8
 3К  3   6   9  12
 4К  4   8  12  16

Note how I have the numbers lined up in the table.  Your program should do
this as well.  If a dimension is greater than 9, your program should allow
for it by lining up the row counters like you have the answer columns.
Treat what you see above for the table literally (the left side of the file
as the left side of the screen) For example:


Good luck, and a solution to this problem will appear in part 3!  Remember
to document your code (sort of like I did with the part 1 answer), and try
to keep it short.  The shorter program is the better program, of two programs
that give correct output.  On this one, cosmetics are tedious, but to get
the job done remember that FOR loops are for things that repeat a set number
of times (if you want a goal for practice on getting this one the least
number of lines possible, my solution for this problem is 50 lines long for

Next Time
Next time, we will discuss the use of while and repeat loops, and the
case statement.  By all means, send comments, and queries on problems
you may be encountering with problems in this tutorial to [email protected]

                   Turbo Pascal for DOS Beginning Tutorial
                             by Glenn Grotzinger
             Part 3 -- While and Repeat Loops; Case Statements
            all parts copyright 1995-96 (c) by Glenn Grotzinger.

        Hello again.  I haven't gotten much input about continued
interest in this tutorial.  I may discontinue it if the interest
doesn't pick up. I am planning on taking this tutorial through all
of the Pascal data structures, and maybe special topics (advanced
ones, too, listen up self-professed Pascal experts -- you may even
learn something. :>).  I haven't gotten any suggestions on the special
topics!!!  Please give comments to [email protected]

An example of a solution of last week's programming problem:

program part2;

  { This program accompanies part 2.  It is a program designed to take a
    number representing the dimensions of a multiplication table from the
    user, and then write a multiplication table out to the screen. }

    dimension: integer;
    i, j: integer;

  begin { part 2 }

    {take input for the dimension}
    write('What dimension number do you want for a table? ');

    { if dimension not greater than 15, process table }
    if dimension <= 15 then

        { write top line of table }
        write('   ');
        for i := 1 to dimension do

        { write top rule line }
        for i := 1 to (dimension * 4) do

        { write rest of table }
        for i := 1 to dimension do
            if i < 10 then
              write(' ');
            for j := 1 to dimension do
              write(i*j :4);

    { else it's greater than 15, write an error message }
      writeln('You must give a dimension that''s 15 or lower.');

If there are any questions as to understanding, or difficulties
in solving the practice problems I pose (only way to improve your
own programming talent is to practice...), write [email protected]

On to the new stuff....

WHILE loops
        It is possible to have a loop to perform a set of
commands a non-set number of times, until a condition is met.  It
can be a contrived one (we can code the basic idea of a FOR loop
using a while loop or the repeat loop. We see this in the recode
of tutorial6 from last time I will make using a while loop instead
of a for loop.

program tutorial7;
    i: integer;
    writeln('I''m going to write something 10 times.');
    i := 1;
    while i <= 10 do
        writeln('something', '(Time #':15, i, ')');
        i := i + 1;

As we see when we run this program, it produces the same output
as program tutorial6 did.  The statements in the while loop
function while the condition is true.  When i becomes 11, the loop
breaks off and the program ends. Like the IF statements, we can
place multiple conditions by connecting them like before with the
AND or OR identifiers...

REPEAT loops
        This is another loop we can use.  The WHILE loop will
function while a condition is true. The REPEAT loop stops fun-
ctioning when a condition is true. We see the idea of this again,
when we reconstruct program tutorial6 using the repeat loop...

program tutorial8;
    i: integer;
    writeln('I''m going to write something 10 times.');
    i := 1;
      writeln('something', '(Time #':15, i, ')');
      i := i + 1;
    until i > 10;

The programs tutorial6, tutorial7, and tutorial8 perform the same
things, with loops, using different ideas.  The difference of the
while and repeat loops over the for loop is that UNDER NO CIRCUM-
INSIDE THE LOOP. The conditional variable for a while or repeat
loop can be easily changed as we saw in tutorial7 and tutorial8.
There are many choices and options we can implement with the while
and repeat loops. Menu systems are often implemented like this
Continue until user wants to quit. is the basic logic.).

CASE statement
        As we saw in tutorial5, we may want to make a choice based
on many, multiple options.  This statement is analogous to a series
of IF statements on the same variable.  The CASE statement reduces
the wordiness of such a construct, and makes things easier.  I was
eluding to this statement before when I mentioned then that there
is a better way of doing it.  Well, here it is.  We use the example
of tutorial9 below, which is a rewrite of tutorial5, to illustrate
the use and syntax of a case statement.  Keep in mind that the operator
we use in the case statement (like option below) must be a character
or an integer...

program tutorial9;
    one, two: integer;
    option: char;
    writeln('Enter an integer.');
    writeln('Enter another integer.');
    writeln('Use a mathematical symbol to indicate what you want to do');
    writeln('with these two numbers.');
    case option of
      '+': begin   { sub procedures can be coded as well }
             writeln(one, ' + ', two, ' = ', one + two, '.');
             writeln('See, I can add.');
      '-': writeln(one, ' - ', two, ' = ', one - two, '.');
      '*': writeln(one, ' * ', two, ' = ', one * two, '.');
      '/': writeln(one, ' / ', two, ' = ', one / two :0:3, '.');
    else {catch rest}
      writeln('Use +, -, *, or / as your operator.  Try again.');
    end; {case includes an implied begin.  We MUST end. }

As I said in the note, case statements include an implied BEGIN.  We must
say end; to complete the case statement.  The case statement is formatted
for each of our choices above, and the else can be used as a catch-all in
case the user places something in there that we don't account for in the
program, so we can write an error message to the user.  The syntax of the
case statement is basically as above.  You see everything that can be done
with the case statement under Pascal.

Random and Randomize
        We can generate random numbers by doing the following.
At the beginning of the program, call randomize.  Then do
random(<a number>).  What will happen is it will produce an
integer from 0 and less than the number you put in.  Random(3)
will produce a random number from 0-2.  Example.

program tutorial10;
  { write 10 random numbers between 1 and 20 }
    number: integer;
    i: integer;
    { start the random number generator.  Only call once, but must call! }
    for i := 1 to 10 do
      writeln('Random number (#', i,') = ', random(20) + 1);
      { produce random number from 1-20 instead of 0-19 like random(20) only
        does }

The Upcase and Length Functions, addressing strings

        upcase(char) command will place a letter into uppercase.  This
command is useful for input prompts where the user is asked to give input
that involves a character or a string.  To illustrate the use of this
command, which may be placed in a write statement, or an assign (it is
what is defined as a function.  We will see what that is next time.).
write(upcase('c'));  will produce a C on the screen.  Another example,
which uppercases a string.  We use the function length(string) which is
useful for that purpose.  Given a string into that function, it will
return an integer length of the string.

     for i := 1 to length(inputstring) do
       upcasestring := upcasestring + upcase(inputstring[i]);

This for loop will accomplish it.  The thing we see also, with the illus-
trations of the upcase and length functions, is that we can address any
part of the string by stringname[position in string].  For example, the
string "Charleston" can be there as inputstring.  If we wanted the 5th
character of the string, we say inputstring[5].  inputstring[5] would
be equal to 'l', since it's the 5th character of the string.

Programming Practice Problem Notes
        I am beginning to see the problems get more difficult as there are
more things we know, and we can do more useful and fun things with our
coding.  You may even want to start setting out and solving simple
mathematical problems for homework, or coding up a simple program that can
figure up your checkbook ... it can be done with what you know now, believe
it or not.  Think about what you can do with your new found knowledge.  Do
not try and overextend yourself trying to do things you have no knowledge of
as of yet.  Attempt this practice programming problem as you hopefully have
the others.  It's a rather fun one, as it's an actual game.  We see we are
coming far and there is a lot farther road to go.  There's lots more
concepts we have to learn, which will enable us to do a whole lot more.
Look forward to several more parts, hopefully...

Practice Programming Problem #3
        Create a program in Pascal and entirely Pascal that will enable the
user to play a guessing game using the keyboard and the monitor as input and
output.  The points to be addressed in programming this game:

1) The number range of the guessing game must be from 1 to 100.
2) The user must be given 6 opportunities to guess the number.
3) After the user guesses at the number, the program is to answer the
   user by saying whether their guess was high or low and tell them the
   number of guesses they have remaining.
4) If they guess the correct number, give them a congrats message.  If
   they exhaust their attempts, give them a try again message, revealing
   the correct number.
5) Give them the opportunity to play again by asking whether they want to
   play again (Give them a Y/N prompt.).  Be sure to take care of all 4
   variants of this choice by using the most efficient method you have
   available to you.
6) Remember as always to code as efficiently as possible.  This one can
   be printed out on one page (38 lines to be exact for my sample of this
   one).  Use that as a coding goal for your learning.
7) If you get stuck, e-mail me about it, and I'll see if I can help.  Have
   faith.  You have the ability to do it.

sample output
I'm thinking of a number between 1 and 100.  What is it?
It's higher. (5 guesses remaining)
It's lower.  (4 guesses remaining)
It's higher. (3 guesses remaining)

If right:
Congratulations!  You got the number right!

If wrong:
Sorry, you ran out of choices.  The number I was thinking of is 68.

Play again:
Do you want to play again? (Y/N)

The right / wrong / play again messages are not statically required.  I am
only giving examples of what they may be.  Your goal for this game should
be to program it so it is playable and user-friendly.  I recommend you to
use the prompt structure right above, though.

Next Time
We will discuss functions and procedures and their use next time.  Please
refer your comments to [email protected]

                  Turbo Pascal for DOS Beginning Tutorial
                              by Glenn Grotzinger
                 Part 4 -- Writing Procedures and Functions
           all parts copyright 1995-96 (c) by Glenn Grotzinger.

        Hello, again, and lets get started...

An example of a solution of last week's programming problem:

program part3;

  { This program enables a user to play a guessing game.
    Range: 1-100     Guesses: 6 }

    choice: char;
    randnum, number_given, attempts: integer;

    randomize; { start random # generator }
    while upcase(choice) <> 'N' do   { while we want to play }
        randnum := random(100) + 1;  { generate random number for game }
        attempts := 6; { set initial number of attempts }
        number_given := 1000; { stuff number_given so while kicks in }
        writeln('I''m thinking of a number between 1 and 100.  What is it?');
        while (attempts > 0) and (number_given <> randnum) do
           { while we still have attempts to guess }
            if number_given = randnum then
               writeln('Congratulations!  You got the number right!')
                 attempts := attempts - 1;
                 if attempts = 0 then
                   writeln('Sorry, you ran out of choices. ',
                           ' The number I was thinking of is ',
                           randnum, '.')
                   if number_given > randnum then
                     writeln('It''s lower.  (', attempts,
                           ' guesses remaining)')
                     writeln('It''s higher. (', attempts,
                           ' guesses remaining)');
        writeln('Do you want to play again? (Y/N)');
If there are any questions or difficulties, write [email protected]

On to the new stuff...

        This is a way a lot of things work in Pascal.  For example, the
write command is a procedure.  We say the procedure name, then a list of
parameters which describe what we want to write.  Write is defined in
Pascal itself, but we can write our own procedures to do things.  Before
the main part of the program, and after our variable declarations, we
place our procedures.  Procedures are essentially whole separate programs
which perform small tasks in a bigger program. Here is a short example of
what I mean:

program tutorial11;

    one, two: integer;
    final: integer;

  procedure addtwonumbers(first, second: integer; var answer: integer);
      answer := first + second;

    writeln('Adding two numbers:');
    writeln('Type two numbers in (space after each number) to add.');
    readln(one, two);
    addtwonumbers(one, two, final);
    writeln('The answer is ', final, '.');

The procedure enables us to perform more modular, understandable, and
easier to debug programs.  We always code logical parts of the program
together in procedures and functions (will be discussed later).  This
example is pretty simplistic, as we include only one statement in a
procedure (generally a waste of time), which could easily be in the body
of the program.  We can use one statement procedures in more logical things,
such as conversions of one set of unit to another.  We describe the parts
of the procedure declaration above, and the calling of the procedure.

We can define a series of statements as long as they are the same type, and
we don't want to keep them without the use of the VAR section of the
procedure.  We don't care about, and don't modify first, and second, so we
place them in there without the var.  We modify the variable used as
answer, so we MUST have the VAR before it in order to have the variable
survive in a modified state by calling the procedure.  Play around with
what happens in tutorial11 when you remove the var from in front of the
third statement.  When we call the procedure, we use variables which match
and make sense to what we want each variable we want. we use one, and two
for the numbers to add, because our procedure is designed
to add those first two numbers.  Then we put final in the last one, because
that is our final answer.  We can SAFELY use the same variable names for
the procedure and the globals, but it is a good idea not to, as it will not
always be possible to correlate our items in that way.  We should always
have a goal to write procedures/functions with ability to drop them into
another program where all the parameters are passed to it.  We can easily
drop the procedure written above into any other program that needs a similar
function.  It is possible to have a parameterless procedure as well, if it
is predicted as required.  Say...In a multi-function menu system or something
that does a set thing (clrscr is an example).

NOTE: It is always good to keep your old code when you write and use
parameter passing in your procedures and functions, so you can easily 
re-use your code and save time in having to rewrite old code.

FUNCTION writing
Function declarations are exactly like procedures, except that functions
have the capability to return a value.  We see in the example, below, which
is a rewrite of tutorial11.

program tutorial12;
    one, two: integer;
    final: integer;

  function addtwonumbers(first, second: integer):integer;
      answer: integer;
      answer := first + second;
      addtwonumbers := answer;
      {we can also do addtwonumbers := first + second;}

    writeln('Adding two numbers:');
    writeln('Type two numbers in (space after each number) to add.');
    readln(one, two);
    final := addtwonumbers(one, two);
    writeln('The answer is ', final, '.');

In the function, we see we MUST define the final answer to the value of
the function.  The end part :integer; defines the return value of the
function.  The way to set up a function should be evident from this example.

It's possible in an IF/WHILE/REPEAT to set up a test on a group of statements.
For example:
   IF character in ['a'..'z'] then
   { if character is in the lowercase alphabet }
   IF character in ['1','2','3','5']
   { if character is 1, 2, 3, or 5 }
   IF number in [0..23] then
   { if number is between 0 and 23 }

TYPE and CONST statements.
It's possible to type-delineate variables.  Such as for example, we may want
to limit a string from it's standard 255 characters (if we say just string),
to say 10 characters (string[10]), we can not use something like that in
a procedure or function (we will see the types used a lot).  Therefore, we
use the type section to redefine this so we can use them in procedures/

It's also possible to set constants in a pascal program.  Say, if we want
to set up a constant tax rate, we just define it in a constant section.

We see this in an example:

program tutorial13;
    tax_rate = 0.14; {14% tax rate}
    string[15] = string15;
    { string[15] redefined so we can use it in a procedure/function,
      though we will not have procedures or functions here. }
    total_pay: real;
    your_name: string15;
    {if we make type dec, we must carry it across.  This variable is
     a string with a limit of 15 characters. }
    writeln('Who are you? (15 char. max)');
    writeln('How much did you earn this paycheck?');
    writeln('Assuming, you have a ', tax_rate * 100 :0:0, '% income tax ',
            'rate, you will have to pay $', total_pay * tax_rate :0:2,
            ' in income tax this paycheck.');

The use of tax_rate is a prime reason that we would want to define a
constant.  Instead of using that number everywhere we needed it, we used
the reference of tax_rate.  Why?  If the income tax ever changed in this
example, we would not need to go through the whole program and change that
number.  All we need to do is change that reference.
Clearing the screen
This will be the first example of a command that we must call a unit for
to get.  The unit you call (as I did in tutorial1) would be CRT for TP/DOS,
and WINCRT for TP/WIN.  You use the statement clrscr; for this.  This
program example clears the screen.

program tutorial14; uses crt;
    writeln('I cleared the screen for you.');

Final Note
You should lay out your programs in logical units using functions and
procedures, making them parameter passing if possible, and logical.
The best may be (especially for programs that do multiple things which
are unrelated on code level) to not parameter pass.  Whatever works
functionally is the best, and if you can't figure out a way to parameter
pass to the procedure or function....

Programming Practice Problem Notes
        This is the first program, that I would expect you to write using
functions and procedures.  All programs you should write in the future
should strive to use parameter-passed procedures.  The reasons are many-
fold for this, which I stated before.  Be sure you do this.  This program
should not be any more difficult than any of the previous programming
problems I gave.  All I expect this one to be is an exercise for you to
learn in using procedures and functions.  If you want more practice beyond
this problem in writing things using procedures and parameters, I suggest
you recode the programming problem out of part 2.  If you can get it more
easier understood to look at, and using parameter-passed procedures for the
logical parts of it, and make it work right, then you got it.

Practice Programming Problem #4
        Write a program in pascal and entirely Pascal which will present
a simple menu driven system (readable, and by pages -- use clrscr in the
appropriate spots) which will enable the user by choice to do the following,
as indicated by sample output for the menu.

sample output for menu
1. Convert a number of seconds to hours, minutes, and seconds.
2. Convert a given military time to AM/PM time.
3. Quit the program.

Please enter your choice now:

sample output for option 1
Please enter a number of seconds: 3600

3600 seconds is 1 hour, 0 minutes, and 0 seconds.

Press ENTER to continue:

sample output for option 2
Enter a military time's hours: 14
Enter a miltiary time's seconds: 00

It is 2:00 PM.

Press ENTER to continue:

Notes: 1. there are 60 seconds in a minute, 60 minutes in an hour.
       2. "military time" is 24-hr time.  For example, 16:00 would be
          4 P.M.
       3. The program must continue to function in the menu system until
          the user wishes to quit.
       4. Remember to ask the user for appropriate data that you need.
       5. Be sure to pause the screen to enable the user to see the results
          of their query.  You may accomplish this by just calling readln;.
          It will call the computer to wait until the user presses a key.
       6. Be SURE your program is easily understood.

The solution to this program will be presented in the next part.  Good luck!

Next time
        Next time, we will discuss the usage of text files for reading in
data and writing out results. We really are moving along with the concepts
rather readily...

                   Turbo Pascal for DOS Beginning Tutorial
                             by Glenn Grotzinger
                Part 5 -- Reading and Writing to Text Files.
             All parts copyright 1995-96 (c) by Glenn Grotzinger

program part4; uses crt;

  { This program offers a menu-type system in order to convert a number
    of seconds to hours, minutes, and seconds; and to convert a military
    time to AM/PM time }

    choice: integer;

  procedure showmenu;
    { This procedure shows the main menu. }
      writeln('1. Convert a number of seconds to hours, minutes, and',
              ' seconds.');
      writeln('2. Convert a given military time to AM/PM time.');
      writeln('3. Quit the program.');
      writeln('Please enter a choice:');

  procedure convseconds;
       totalseconds: integer;
       hours, minutes, seconds: integer;
       temp: integer;
       write('Enter a total number of seconds: ');
       temp := totalseconds div 60;
       seconds := totalseconds mod 60;
       hours := temp div 60;
       minutes := temp mod 60;
       writeln(totalseconds, ' seconds is ', hours, ' hours, ', minutes,
              ' minutes, ', seconds, ' seconds.');
       writeln('Press ENTER to continue:');

  procedure convmilitary;
    { Version 1.1.  Bug found and e-mailed to me.  It is appreciated. }
    { Involved the reporting of times from 0000 to 0059...Looks to }
    { be fixed now. (wrote this in a very dazed state -- post-surgery)}

      hours, minutes: integer;
      meridian: char;
      write('Enter a military time''s hours: ');
      write('Enter a military time''s minutes: ');
      if hours >= 12 then
          meridian := 'P';
          hours := hours - 12;
        meridian := 'A';
      if hours = 0 then
         hours := 12;
      write('It is ');
      if minutes < 10 then
      writeln(' ', meridian, 'M.');
      writeln('Press ENTER to continue:');

    choice := 10;
    while choice <> 3 do
        case choice of
          1: convseconds;
          2: convmilitary;
OK.  On to new stuff...

Addressing Files
To address a file, we use the assign statement.  We use a text variable to
refer to the file, and a string to refer to the actual pathname and filename
of the file.

To open a file, we use a reset(<file variable>); statement to read it.

To write a new file, we use a rewrite(<file variable>); statement.

To close a file after we are done accessing it for read or write, we use
the close(<filevar>).

Special Identifiers
There are functions defined in standard Pascal which aid us in processing
text files.  They are eof(<filevar>) and eoln(<filevar>).  They return
boolean values.  They are signals we can use in loops to aid us in finding
our way in text files.  EOF signals when we are at the end of a file.
EOLN signals when we are at the end of a line of text in a file.

NOTE: Keep in mind, we can have as many files defined as we need.  The only
limitation is that we can only have a maximum of the number of files defined
in the FILES= statement of the config.sys of the particular machine we are
on open at any one time.  It is always prudent to close files after you are
done with them.

Text file concepts and reading/writing to text files
A sample program to illustrate all the items above.  We will be doing a
character by character read on an input file named DATA.TXT using eoln
and eof properly, and uppercasing the output of the file using the
standard upcase function. The output will be written to another output file

program tutorial15;
    infile, outfile: text;
    inputchar: char;
    assign(infile, 'DATA.TXT'); { associate var infile with DATA.TXT }
    reset(infile); { open DATA.TXT for read }
    assign(outfile, 'UPCASED.TXT'); { assoc. var outfile with UPCASED.TXT }
    rewrite(outfile); { open UPCASED.TXT for write }
    while not eof(infile) do { while we're not at the EOF for infile }
        while not eoln(infile) do { while not EOLN for infile }
            read(infile, inputchar);
            write(outfile, upcase(inputchar)); { process and output }
        writeln(outfile);  { when end of line, advance both files down }
        readln(infile);    { one line }
    close(infile);     { we're done with both of these files.  Close 'em }
    close(outfile);    { time. }

This program is primarily for illustration purposes of all the new concepts
we need to know in order to access files (there is a lot better way out
there of doing it).  As long as we remember what exactly is read and
considered as each type of variable (integer, char, string[limit], etc,
etc), we can read all sorts of data from a text file and write data back
out to a text file.  To illustrate:  If we have the following input file
as on disk...

14 23 34 53 32 Glenn Grotzinger
23 23 12 33 23 Clinton Sucks!

If we perform ONE read from a varied amount of different types from the
first position of the first line, we will see the following:

 CHARread: 1
 INTEGERread: 14
 STRING[20]read: 14 23 34 53 32 Glenn
 STRINGread: 14 23 34 53 32 Glenn Grotzinger

readln/writeln goes to the next line, whichever references what we are doing
to the text file.

We must keep these general rules in mind (I hope you played around with the
read and write commands a lot, that playing will help you A LOT!).

Another illustration to see usage of files.  It's the BETTER rewrite of
tutorial15.  We must also keep in mind that to read a text file, EOLN
is not necessarily required, but EOF is ALWAYS REQUIRED.  Improvements:
We can use a string and write a function to uppercase the whole string.
Plus, there's one little logic error above...Figure out why I do the
reads and writes different below and you'll have mastered the idea of
reading/writing files...(I intended to just demo the commands earlier,
this is a demo of how they should be used logically...)

program tutorial16;

    inputstring: string;
    infile, outfile: string;

  function upstr(instring: string):string;
    { This function uppercases a whole string }
      i: integer;
      newstr: string;
      newstr := '';
      for i := 1 to length(instring) do
        newstr := newstr + upcase(instring[i]);
      { we can piece strings together using +.  Keep it in mind }

     assign(infile, 'DATA.TXT');
     assign(outfile, 'UPCASED.TXT');
     readln(infile, inputstring);
     while not eof(infile) do
         writeln(outfile, upstr(inputstring));
         readln(infile, inputstring);
     writeln(outfile, upstr(inputstring));

Remember to play with the logic in accessing text files.  And in reading and
BE ABLE TO RECOVER THE FILE!!!!!!!!!!!   I will cover in a later part
how to find out whether files exist on the drive as well as other commands
and functions used in Pascal to perform DOS-like functions (delete files,
make directories, remove directories, and so forth).

Printer Output
        The printer can basically be treated as a write-only file (you only
rewrite it, not reset it).  To use the printer?  Use the unit printer, like
you did the unit crt or wincrt before then write to a text file variable
named lst....Printer defines everything you need to write to the printer.
Printer assumes LPT1, so if your printer is on something else, you can
define the text file variable to be the port address for the printer....

program tutorial17; uses printer;
    str: string;
    infile: text;
    assign(infile, 'PRINTME.TXT');
    readln(infile, str);
    while not eof(infile) do
        writeln(lst, str);
        readln(infile, str);
    writeln(lst, str);
    writeln('File sent to printer on LPT1..');

Practice Programming Problem Notes
        Probably ALL programs I pose on these in the future will involve
at least ONE data file off of disk.  If it is a binary one that I have
created for express purpose of these problems, I will be attaching it to
the tutorial message as a binary file attachment.  If its a text file,
I will probably ask you to create it, giving you the format.  If you have
been doing source code, you should be able to use a text file editor.

Practice Programming Problem #5
        Write a program in Pascal and entirely Pascal which will conduct
a worker-pay recording.  Be sure the program is modular and uses functions
and procedures to the best benefit, as well as efficiently coded (be sure
you are using format codes!).  We will be reading worker names and data
from a file in the current directory with the program named WORKER.TXT.
Format of input and output files will be covered later.  What we will be
doing is figuring out how much to pay each of these employees in our data
file.  Points to keep in mind:

   1) Gross pay is hourly rate * hours-worked for hours worked below 40.
   2) 40 hours of time is full-time pay, beyond 40 hours is time and a half.
      Therefore, we must pay them 1/2 more than we normally would at the
      hourly computed rate for any hours above 40 from point 1.
   3) Income Taxes are 15% of computed gross pay (before taxes, etc).
   4) We must indicate all of these deductions by computations in the output.
   5) We may have 1 employee, we may have 10,000.  We need to give the
      user some indication as to what we are doing (all we will see is
      a blank prompt otherwise.) so they won't think our program has hung
      or crashed.  Write a message such as "Processing <employee name>" to
      the screen for each employee.

Write a report of what our deductions are from each person's salary, and
what we are paying each employee to a file named PAYOUT.TXT.

Format of WORKER.TXT (I recommend you type this in *EXACTLY* and use it)
Glenn Grotzinger    44.25  7.34
Joe Schmoe          65.32  4.35
Jim Nabors          40.00  10.01
Sheila Roberts      32.12  6.25
Kathy York          23.21  11.10

(the area between all the --- is the WORKER.TXT to use.... -- another note:
be sure there aren't any blank lines at the bottom of the file -- those
cause problems...)
First thing on each line is employee name (max of 20 chars).
Second thing is total hours worked.
Third thing is pay rate.

Format of PAYOUT.TXT
                      The International Widget, Inc.
                       PayOut And Deductions Sheet

Employee              Hours  Rate  GrossPay  Overtime  IncomeTax  NetPay
Glenn Grotzinger      34.25  7.34   345.23     32.34     15.34    305.23

(The numbers should be correct, and the file should appear sort of like
this, but with more entries (equivalent to the number of entries in the
WORKER.TXT file.  This is only a sample illustration.)

Note:  In any program, you should always make accounts for changes in the
number of lines of text in an input file.  Use EOF.  Do not use a
defined set loop.  Also, as a tip, to get the output the way you want it
to look, it doesn't hurt to type it out as a sample, so you know how to
space it when you go into the programming part of it.

Next Time
We will discuss arrays and their usage.  If there is a difficulty in inter-
preting data files for text files, tell me, and I will probably attach them
as binaries in the future.  Please send all comments, inquiries, etc, etc
to [email protected]  P.S.  Sorry this one ain't too fun...Couldn't think
of anything better to get you the practice in using text files...
                        Turbo Pascal for DOS Tutorial
                             by Glenn Grotzinger
                      Part 6 -- Arrays and Their Usage.
             All parts copyright 1995-96 (c) by Glenn Grotzinger

        Hello again...Let's get started...An example of the solution of
the program presented in part 5 is below.

program part5;
    income_tax = 0.15;    { set our 15% tax rate }
    string20 = string[20];  { not needed, but good to demonstrate }
    employee: string20;
    hours, rate: real;
    infile, outfile: text;
    grosspay, overtimepay, intax, netpay: real;

  procedure writeheaders;   { writes the headers on our report }
      writeln(outfile, 'The International Widget, Inc.':51);
      writeln(outfile, 'PayOut And Deductions Sheet':49);
      writeln(outfile, 'Employee', 'Hours':17, 'Rate':7, 'GrossPay':10,
                       'Overtime':10, 'IncomeTax':11, 'NetPay':9);

  function writeline(fillchar: char; strlength: integer):string;
  { handy function that fills a certain length of a string with a fixed
    character }
      i: integer;
      str: string;
      str := '';
      for i := 1 to strlength do
        str := str + fillchar;
      writeline := str;

  procedure processrecord(hrs, rate: real;var gpay, opay, itax, npay: real);
    { does all of our required calculations for us }
       rpay: real;
       if hrs > 40 then  { if overtime then }
           rpay := rate * 40;                { regular pay up to 40   }
           opay := (hrs - 40) * rate * 1.5;  { overtime rate after 40 }
           rpay := hrs * rate; { figure as normal }
           opay := 0;          { set overtime pay to 0, no overtime hours }
       gpay := opay + rpay;        { get our gross pay }
       itax := gpay * income_tax;  { get our deduction }
       npay := gpay - itax;        { actual pay to worker }

    assign(infile, 'WORKER.TXT');     { prepare input file  }
    assign(outfile, 'PAYOUT.TXT');    { prepare output file }
    writeheaders;                         { write headers of report }
    writeln(outfile, writeline('=', 72));
    readln(infile, employee, hours, rate);
    while not eof(infile) do { while not end of file, read,process&write }
        writeln('Processing ', employee);
        processrecord(hours, rate, grosspay, overtimepay, intax, netpay);
        writeln(outfile, employee, hours:5:2, rate:7:2, grosspay:9:2,
                         overtimepay:10:2, intax:10:2, netpay:11:2);
        readln(infile, employee, hours, rate);
    writeln('Processing ', employee);
    processrecord(hours, rate, grosspay, overtimepay, intax, netpay);
    writeln(outfile, employee, hours:5:2, rate:7:2, grosspay:9:2,
                     overtimepay:10:2, intax:10:2, netpay:11:2);

    close(outfile);    { close files }

The Structure of an Array
        For a lot of things, it is very useful to know exactly how things
are done by the computer in programming.  The array is one of them.  An
array is simply a group, or set of groups of like defined items.  An example
of such a thing is:

firstarray: array[1..3] of integer;

What we are doing is defining three integers that we want to indicate by
numbers as 1(1st), 2(2nd), and 3(3rd) in the set of integers.  Arrays are
often used to correlate like items in a program to make them easier to
process.  Often it is with such data as this:  Say we want to work with
the high temperatures for each day of one month.  It is correlated info,
so it is a very good candidate for an array.  The numbers between the []
are minimum..maximum index values for the array (to keep track of which
part of the array we want).  There are 31 days in a month(maximum), so we
would store them as something like this:

temperatures: array[1..31] of integer;

We are defining the total number of integer units we want in the array.
Which brings up the question: How is this stored in memory?


This rule is always helpful to remember when we work with arrays.  It
does point out logic rather readily of handling arrays.  Now I will
illustrate how to access items of the array.  Keep in mind that array
items can be read from the keyboard or text file, and written just like
variables we have worked with before.

To read the 5th temperature in the array from the keyboard, we would use
something like this: readln(temperatures[5]);

Note: in addressing an array, we can even use mathematical expressions...

Usage of Arrays
If we wished to write out (one per line) to the screen, all the contents
in the array, we would do something like this...keep in mind that array
variables work exactly the same way as the variables have that we have
been working with so far:

for i := 1 to 31 do
  writeln(i, temperatures[i]);

Note: often for/while/repeat loops are used to work with arrays to process
the whole array using an index variable in the counter.  Keep this in mind
for your programming.

The indexes can be defined to be anything that was valid for use in the for
loop, such as:
     array1: array[1..10] of integer;
     array2: array[-15..0] of real;
     array3: array['a'..'z'] of char;

This naturally has limits, which you will find if you cross them (compiler
warning).  What the array is of can be any valid data type.  Can we define
an array of an array?  Yes.

Double Level Tables and Above
        We can define infinitely, as our memory allows, the number of arrays
of arrays we want to define.  Say, for our temperature example, we wanted
to define the limits of an array for the whole year.  We know there are
12 months in the year, so we would need 12 occurences of the array we
defined previously for the high temperatures for each day:  A definition
of this array would be:

temperatures: array[1..12] of array[1..31] of integer;

This is a very definable array that we could use to store all of our high
temperatures for each day of the year.

Is there a shortcut, though?  Yes, there is.  You can use a comma in the
first set of [] to define the limits of the 2nd array.  Why?  We see in
the next paragraph.  An example of a better definition of this array is:

temperatures: array[1..12, 1..31] of integer;

We see the parts of each array in the first one there...Remember above
that I said computers store things linearly.  It helps us when we know
this in processing such a thing above.  Actually, for double
level, triple level, etc, tables, the 2nd array, 3rd array, etc is seen as
minor units within the previous defined array.  Say, we had this array:

array1: array[1..2, 1..2, 1..2] of char;

It would be set up in linear memory like this:

array1    char   char    char  char        char   char  char   char
        [1,1,1] [1,1,2] [1,2,1] [1,2,2] [2,1,1] [2,1,2] [2,2,1] [2,2,2]
           array1[1,1]  array[1,2]        array1[2,1]  array1[2,2]
                  array1[1]                       array1[2]

I placed labels on there so we could see the layout.  It would continue
ad infinitum for a 3rd level or a 4th level on anything.  So, as an
example of how we can address the array of temperatures above, and work
with one, here is a little code that writes 'em all to the screen, 12
columns of integers (one for each month), 31 lines(one for each day
in the month):

for i := 1 to 12 do
    for j := 1 to 31 do
      write(temperatures[i, j], ' ');

Defining Constant Arrays
It can be done.  It's done like this:

  months: array[1..12] of string[3] = ('Jan', 'Feb', 'Mar', 'Apr', 'May',
          'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec');

Ord And Chr
These are two functions we can use to work with the ASCII table.  They
are system functions.

Ord(char) gives us an integer that represents the character we put in
the ()'s.  Chr(integer) gives us a character which corresponds to the
ASCII code number that is given in integer.

Boolean type
A type called boolean exists.  It can either be set to true or false.

Final Notes
As a final note, defining arrays, if you play around with doing that and
using them (I hope you do..:>), you may get an error from the compiler
that your data structure as defined is too large.  The reason is that
Pascal defaults to operating and compiling your programs like they were
going to work on an 8088 class machine.  The maximum addressable memory
there is 64KB.  If that happens, it means you have too many variables
defined.  There is a way to get around this, which will be covered in
a future part of this tutorial.  Don't give up on this, and remember:
Experimentation is the best tool for learning how this stuff works...

Practice Programming Problem #6
        Once upon a time, a friend of yours did you a really big favor.
Now they want you to repay that favor.  They are in college and doing
a statistical study on the average number of times that each letter of
the alphabet is used.  To accomplish that, to your unfortunate ears,
your friend wants you to sit down with a sheet of paper and a book
and start tallying letters on a table that's written on that sheet of
paper.  You know this is very time consuming and there is probably a
better way.  You realize that with your programming knowledge that you
have and several text files that you have on your hard drive that you
can make a program that will accomplish the task much faster.  They
asked you to count through a 200 page book originally, so you decide
that documentation of some shareware (5 text files) that you have are
counted and summarized will do sufficiently for what your friend intends.

Write a program in Pascal and entirely Pascal that will read no more
than 10 text file names from a file named FILES.TXT (you can create it,
one filename (full path) per line, be sure you can find them on your
drive (I recommend to get them as a whole to be no less than 200KB in
size (all files combined), and as large as you want them to be as a
whole -- I am only stating a good set of minimums -- the no more than 10
part is a good required part of the program to do.).  For each and every
file in this list, count the number of times each letter is used (whether
the letter is uppercased or lowercased is irrelevant <do not differentiate,
say, between A and a), do not count different cases based on that.)
Then output to FRNDDATA.TXT a listing of the total numbers of each letter
encountered.  Also, output other statistical data such as total number of
letters encountered (better define this one as a longint or real) and
total number of files used.

Notes and Hints:
1) We want counts for EACH file with EACH letter to be printed out.
2) Hint: Use an array to store the data for your numbers counted.
3) Total number of letters encountered DO NOT include numbers, wierd
characters, etc, etc.  If you are foreign to the U.S. and have other
characters in your alphabet than A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,
R,S,T,U,V,W,X,Y, and Z and their lowercased cousins, program this
utility using your alphabet.
4) Hint: Remember to take totals, we need data from ALL of our
samples to be used.
5) Hint: We don't need 52 IF statements or a large case statement.
Look at the ASCII table and see why I say this.
6) The program specifications say that FILES.TXT should not have more
than 10 files.  If it does, just design your program to stop at file
#10 and write an error message at the end of the output.
7) As with the last program, we need to write action indications to the
screen so we know the program is running and has not crashed.
8) If you see negative numbers for sums, or screwy numbers, remember
to initialize any arrays you may be adding things to.  Also, if you
see the negative numbers, integer type overflowed (Integer max: 32000).
If this happens, define all integers associated with that area to
longint (Integer max: 2 billion).

Sample of FILES.TXT (It should look like this, just be sure the files
you list are on your drive and are TEXT files, and not word processor

                        Alphabetical Count Data
                        for c:\mouse\readme.txt

Letter             Count                    Letter              Count
  A                 12                        N                   6
  B                 10                        O                   7
  C                  4                        P                   2
  D                 92                        Q                   2
  E                 20                        R                   4
  F                 32                        S                   2
  G                 23                        T                   3
  H                  2                        U                   4
  I                  2                        V                   2
  J                 34                        W                   2
  K                 21                        X                   4
  L                123                        Y                   5
  M              12341                        Z                   2

(repeat above for rest of files in FILES.TXT)

At end:
5 files processed.
15232 letters encountered.

                        Alphabetic Count Data
                           for all files

(repeat a letter count system like we did for each file we did)

Next time
We will cover the use of records, and a few other nice commands, plus a
little mathematics.  Inevitably, in the sciences, math is used somewhere.
We're getting to the point of needing that.  I've tried to show partial
code to do things now that we're getting farther.  If people are having
problems integrating the code into a program to see how it works (if you
do, maybe you should work on the previous parts?), tell me and I'll go
back to posting complete programs.  With reference to mathematics, if
you do not know how to convert number bases, ask a parent or your favorite
mathematics professor. :>

After that, we will have most of the basics covered.  That does not mean I
will quit.  This is, my guess, about 2 parts away from the halfway point
(don't quote me on that:>).  Anyway, there's lots more to cover, and a few
special topics coming up.  To illustrate, the tutorial after the records
tutorial will involve the use of DOS compatible commands, in other words
those things you do in DOS (the first special topic) -- we're gonna go
over how to do those things in Pascal.

Please send any comments, questions, requests for help with the sample
programs (you SHOULD be doing them as you get each of these parts) to
[email protected]
                         Turbo Pascal for DOS Tutorial
                              by Glenn Grotzinger
         Part 7 -- Records usage and Mathematics Concepts of Computers
                All parts copyright 1995 (c) by Glenn Grotzinger

        Here is a solution to part 6...

program part6;

    ltrtype = array['A'..'Z'] of longint; {type dec for arrays}

    ltrstorage, sums: ltrtype; { hold array and sums array }
    filelist, counts: text;
    filename: string;
    filenums: integer;
    ltrs: longint;

  procedure countletters(str: string; var ltrstorage, sums: ltrtype);
      i: integer; {65..90}
      j: char;
      for i := 1 to length(str) do  { for length of string }
        for j := 'A' to 'Z' do    { for 26 letters }
          if upcase(str[i]) = j then
              inc(ltrstorage[j]); {var = var + 1 }
              ltrs := ltrs + 1;

  procedure countfile(filename: string; var ltrstorage:ltrtype; var
                      filenums: integer);
      cntfile: text;
      chkstr: string;

      writeln('Processing ', filename);
      filenums := filenums + 1;
      assign(cntfile, filename);
      readln(cntfile, chkstr);
      while not eof(cntfile) do
          countletters(chkstr, ltrstorage, sums);
          readln(cntfile, chkstr);
      countletters(chkstr, ltrstorage, sums);

  procedure writefdata(filename: string; var ltrstorage: ltrtype);
      i: integer;
      j: char;
      { write headers }
      writeln(counts, 'Alphabetical Count Data':53);
      writeln(counts, 'for ':40, filename);
      writeln(counts, 'Letter':10, 'Count':10, 'Letter':25, 'Count':10);
      for i := 1 to 13 do
        writeln(counts, chr(i+64):8, ltrstorage[chr(i+64)]:12,
                chr(i+77):23, ltrstorage[chr(i+77)]:12);
      for j := 'A' to 'Z' do
        ltrstorage[j] := 0; { zero back out storage array }

  procedure doend(filenums: integer; ltrstorage: ltrtype);
      i: integer;
      j: char;

      writeln(counts, filenums, ' files processed.');
      writeln(counts, ltrs, ' letters processed.');
      writefdata('all files', sums);

    assign(filelist, 'FILES.TXT');
    assign(counts, 'FRNDDATA.TXT');
    readln(filelist, filename);
    filenums := 0;
    while (not eof(filelist)) and (filenums < 9) do
        countfile(filename, ltrstorage, filenums);
        writefdata(filename, ltrstorage);
        readln(filelist, filename);
    countfile(filename, ltrstorage, filenums);
    writefdata(filename, ltrstorage);
    doend(filenums, ltrstorage);
    if not eof(filelist) then
      writeln(counts, 'You gave me more than 10 files to process!');

On with the show....

Records Definition
        You can group up different types of data using records.  We MUST
use the type statement pretty well to use this record type.  Here is an
example of the defining of a record type in the type section.

    employeerecord = record
      number: longint;
      surname: string[18];
      firstname: string[10];
      position: string[10];
      yrsworked: integer;

As you can see, we are grouping many different types of information into
one record type.  Then for the var section, all we need to do is define
ONE variable to be of type employeerecord and we'll have the record variable.

Accessing Records in a Program
        You can work with sections of a record variable, or the whole record
variable.  The whole record variable can be accessed just by calling the
record variable used.  The parts of it require the use of a . followed by
the name of the part as specified in the type statement.  Also, the WITH
command may be used to simplify typing in working with a specific record.
The WITH command specifies a specific record variable to work with.  An
example can be seen below.

program tutorial18;

    {Employeerecord as above}
    workrecord: employeerecord;
    { get workrecord into memory here. }
    writeln('I''m getting sick of typing workrecord all the time!');
    with workrecord do

Hopefully, you can see what exactly is going on in working with records.
WITH only reduces typing.  It's good to use to reduce clutter if you do
a lot with one type of record in a section of code.  Also, keep in mind
that record types can be used in an array....

    workinfo: array[1..10] of employeerecord;

Each section of the record works just like ordinary variables, and are
addressable like ordinary variables.  Also the whole record itself can
be addressable and moved around (written possibly to binary files?) to
other like records....The above array can be addressed like this:

3rd record in array, surname: workinfo[3].surname

Mathematics Concepts in Computers
Hopefully, you got the mathematics lesson from someone, or are already
familiar with conversion of number bases.  If not, I will run through
a quick example...

Convert 10 (base 10) to base 2.

A base tells us how many numbers are used in counting.  Typical numerical
usage is base 10 (we use the numbers 0-9 in that order -- we always start
from zero in counting.   To look at this problem, we look at the right
and move to the left with regards to number bases.  To use the example of
543 in base 10, it's 5X10^2+4X10^1+3X10^0. Remember, anything to the 0th
power is 1. All number bases work this way.  The exception is the changing
of the multiple.  We use 10 in the example above.  Using that background,
10 in base 10 is 1X10^1+0x10^0.  So, if we go through the process of actually
converting a base from base 10....

10 / 2 = 5 rem 0. (we keep going until the quotient is 0.  Right now it is 5.)
5 /  2 = 2 rem 1.
2 / 2 = 1 rem 0.
1 / 2 = 0 rem 1. (We quit here.  Then look at the remainders from down to
 up to get our converted base).

1010 is 10 in base 2....Now, if we want to go to base 10 from another number.

Convert 4A (base 16 to base 10).
Here, since we want to go to base 10, all we need to do is extend out the
base into something we can understand...

4 X 16 + A(10) X 1 = 64 + 10 = 74 (base 10)

What does all of this have to do with computers in programming.  Let's
analyze a few things...

If you've seen $ and # and what do they mean?
$ is a typical designator in computers that a number is in base 16. # is
a typical designator in computers that a number is in base 10.  You
probably have seen it by now in the ASCII table studies we have done.
For example, Z is ASCII character $5A and #90.  These are both one in the
same.  As we see below:

5X16^1+A(10)X16^0 = 80 + 10 = 90 (base 10).
9x10^1+0x10^0 = 5x16^1+10(A)x16^0 (base 16).  {90/16 = 5 rem 10}

How does my computer store data?
Why do we bother to mess with the different number systems in computing?
base 10 is human-understandable, so we use it sometimes.  Base 2 is
obvious, since this is how the computer talks. (hence BI-nary)  All
computer storage is actually a series of 1's and 0's or ON's and OFF's.
(2 possible combinations, hence base 2). For example, lets convert #65 to
base 2 and see how our computers store an A.  Let's start from the highest
we know we can on the power list for 2's. There are 256 ASCII characters
because it was decided sometime in the computer stone age that there would be a
total of 8 bits, the elementary unit of storage in a computer, per byte,
or next major unit that you all should be familar with in using DOS/
Windows/whatever.  If we analyze that using a good permutation scheme..
2 possible orientations, 8 positions...2^8 or 256 total combinations.
So, we will start from 7, since there is no byte #256.  128 goes into 65
0 times.  64 goes into 65 one time with 1 left.  32 goes into 1 0 times.
16 goes into 1 0 times.  8 goes into 1 0 times.  4 goes into 1 0 times.
2 goes into 1 0 times. 1 goes into 1 one time. (stepping down powers of
2.). So typically in expressing a list of bits for a byte, we use 0's
for the filler for all 8 spaces, since we NEED to work for 8 bits with
a respective byte.  So an ASCII related system (there are others) would
represent #65 as 0 1 0 0 0 0 0 1.  8 bits, all 0 or 1.  If your computer
deals with an A, it actually deals with the bit series 01000001.

In using a computer, we don't need to know about bits, since each
completely meaningful unit to most of us comprises 8 bits.  We don't
need to normally think of 01000001, the way the computer does it.  But
for some things in programming, we do, though.

Base 16 is also used a lot.  Reason?  It's a real handy way to define
a byte.  Let's look at the A.  According to the ASCII table, A is $41.
16 = 2^4 so we can see it's a lot handier way to tag around the implied
bits of a byte with this... If we look at the bit sequence, a high end
of 4 bits would have to be multiplied by 16.  So if we look at the meaning
of 0100 and 0001, we will see why we use base 16... 0100 is 4.  0001 is 1.
Concenate them together, we get 41...Neat, eh?

We don't really have all that much concern for base 16 in this tutorial,
because in pascal, base 10 will work as well.  we do need to be concerned
about base 2, though, because the computer uses it.

Reasons for knowing what this stuff is...
We have reasons that we need to know how to convert between the number
bases, and be familar with what a bit is...You may be familiar with what
is called the high and low orders of a byte.  To use the example of the A
bit series we converted earlier:

0      1      0       0                     0       0       0       1      
      high order                                    low order

There are pascal commands which use the bits for things.  Also, some
fixed file formats use these (such as compression -- that's actually whats
going on -- it works at bit level to compress the number of bytes used) and
if you wish to if you ever develop something....)  One must have an idea
of where the bits are coming from, hence all the stuff I was going through
before.  If you read through your TP programmer's reference and see it
talking about orders of bytes and what goes on with those particular
commands, now, you should have the background to know about it and predict
what would happen on those commands.

First good commands to know about for working with bits
        We always want to go for the most efficient code available.  If we
ever need to work with multiplying or dividing powers of 2, we can do it
much speedier and easier by having the computer do bit shifts instead of
doing an actual multiply or divide command when we are dealing with small
numbers.  Shifting information around is much easier for the CPU than
actually doing the computation.  Let us demonstrate.

00000010 (base 2) = 2 (base 10)
00000010 (shift 1 byte to the left) => 00000100 or 4 (base 10)
00000010 (shift 1 byte to the right) => 00000001 or 1 (base 10)

Going one way or the other in shifting bytes have the effect of multiplying
or dividing by 2^(# of bytes we move).  Play with these commands and you
will see.  It's a bit shift that it does, and is MUCH faster than an actual
divide when it comes to any power of 2. Examples:

writeln(2 shl 1);   {or 2 X 2^1}
writeln(2 shr 1);   {or 2 / 2^1}

Those two commands work this way: <number> command <# of bits to shift>
# of bytes to shift turns out to be the power of 2 we do the work on...

HI(byte) pulls the high order of the expression.
LO(byte) pulls the low order of the expression.
swap(byte) swaps the orders.

These are all functions.

Other Math Functions offered by Pascal
Abs(X)          Takes absolute value of X.
Arctan(X)       Takes arctangent of X.
Cos(X)          Takes cosine of X.
Exp(X)          Takes exponential (base e) of argument.
Frac(X)         Returns fraction of X.
Int(X)          Returns integer part of X.
Ln(x)           Takes base e logarithm of X.
Pi              Returns value of pi.
Round(X)        Rounds X(real) to an integer.
sin(X)          Takes sine of X.
sqr(x)          Takes square of X.
sqrt(x)         Takes square root of X.
trunc(x)        Truncates X w/o rounding.

It is a good idea to learn basic trigonometry and analytical geometry in
programming.  For example, I know from my studies that Tan(x) would be
1/Arctan(x) or sin(X) / cos(X).  This stuff is good to know. The reasons?
Graphics.  All one needs to draw anything, really, is a good spot placement
procedure and knowledge of trigonometry, and analytical geometry.  Just
know and define a resolution and then start drawing knowing your knowledge.
As a test, to help out....How would one draw a circle given a central point
and radius?  (Gotoxy in the CRT or WinCRT unit will be of use.  It will
place the cursor at a specified position so you can write something.).
If anyone wants a text-based solution on this one if they can't figure
it out, e-mail [email protected]  I will place one in the next part.

Other stuff that may prove useful
In your programming experience, you may have wondered if there is a way
to get a string to an integer, or an integer to a string to do things
with it?  Well, there is.  VAL and STR.

Val is used like this:  Val(string, integer, errorinteger);
string is the string you want to try and convert to an integer.
integer is the integer that holds the successful conversion.
errorinteger is <> 0 when there is an error in conversion (always check
   this before you move on after using a VAL!!!

Str is used like this: string := str(integer);
string is the string you want to hold the conversion in...
integer is the integer that we want to convert...

Another good command to know is POS: integer := pos(substr, str);
It returns 0, if substr isn't there, but it returns a positive value
corresponding to the start of the substring in the string, if it's there.
For example, if we want to find the first use of the word AT in a string...

int := pos('AT', 'THAT');

int will be 3.

I know all of this mathematics, and talk of bits is probably confusing.
If you don't understand it right now, don't worry about it and go back
at leisure and study it.  It is the basic extent of mathematics behind
the actual operation of a computer and what we all as programmers need
to keep in the back of our minds for some things.  You should know what
a bit is, an order of a byte is, and that there are 8 bits in a byte.
These things should help out in some matters.  You should know the why
parts of these things in some cases...You should especially, though,
understand the parts about the commands SHR, SHL, HI, LO, and SWAP,
and the concepts of orders based on the storage of a byte, as, though
we will not see these in this part, we will undoubtedly see them some-
time before this tutorial is over, more than likely, even we may not
see them.  But you may need to work at bit level with bytes sometime,
so this information is present here now for both the novices and the
experts that may see this.  Also, converting the number bases is a
needed thing.

Practice Programming Problem #7
        The concept of a record is relatively straight forward.  So I
will not try to come up with a problem for basic usage of records.
But the concept of writing code to do some of the number conversions
is not.  These functions can be very useful for later use in your
programming (save them after you write them!).  Write a function for
your code library that will perform a base 10 to base X number
conversion (X being a variable we can define in the function header to
be an integer.) on an integer.  Also write a function that will perform a
base x to a base 10 conversion.  Inbed these two functions in a program
that will take a number from the keyboard (you can use whatever prompt
you deem to be the least confusing as possible) and a base to convert
the number to.  Put out a prompt as to the answer of what the new base
number of the input is, as well as a restatement of the original number
inputted using a reverse conversion of base (Show us that both of the
functions work and are correct.  If the 2nd computed statement DOES
not equal what is put in, there is an error.), not a direct reprint of
the input variable.  To prove we are not writing out the original input
number that we placed in the keyboard, place a 0 in the keyboard input
variable after you perform the function to convert it to the user's
desired base.

Sample Output
Enter a number: 10
What base do you want to convert it to? 5

10 (base 10) is 20 (base 5).
To check: 20 (base 5) is 10 (base 10).

1) You will have to build the converted number base as a string,
because any base beyond 10 requires the use of the alphabet for parts
of the number.
2) The best way I see to handle the "What do we use for the number
in the result?" question is to define a constant string in the function
to be something like '0123456789ABC...', and address a proper part
of the constant string when we build the converted number for conversion.
3) Remember we count from x^0 units on the right!!!!!
4) You will need to probably make the high end limit to be base 36.
5) Hint: the base x to base 10 function will need to input a string
for the input number and output a longint, while the base 10 to base
x function will need to input a longint and output a string.
6) A side note for usage.  You can link these two to get from any base
to any base using base 10 as the intermediary.

Next Time
We will cover the DOS file function commands out of TP.  Hold on to
your newsreaders because part 8 (right now) is 531 lines, and I'm not
through yet.  This will be one of our special topics.  If you not
familiar with the following concepts in DOS, look up in your DOS
manual and try and pick it up before next time: Read-only file,
hidden file, system file, volume label, command-line parameter, MD,
RD, delete (or erase), the use of * and ? as wildcards.  As always,
any comments, questions, gripes, etc, send them to [email protected]
                        Turbo Pascal for DOS Tutorial
                              by Glenn Grotzinger
                 Part 8 -- DOS file functions (special topic 1)
               All parts copyright (c) 1995 by Glenn Grotzinger

Here's a solution to part 7....

program part7;

  { demos 2 functions defined in part 7 to be written.  Convert base 10
    to anybase, and convert anybase to base 10 }

  function power(int, ord: integer):longint;
    { support function required for xbase2dec.  Simplistic
      implementation of taking a power. }
      i, endit: longint;
      endit := 1;
      for i := 1 to ord do
        endit := endit * int;
      power := endit;

  function dec2xbase(int: longint; base: integer):string;
    { converts base 10 to any base < 37 }
      numguide: string = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
      { the guide string I mentioned as a hint }
      i: integer;
      hold, chkprod1, chkprod2: longint;
      endstr: string;
      if base > 36 then
        dec2xbase := '!' { a signal character to say our function failed }
          endstr := '';
          hold := int;
          while chkprod1 <> 0 do
              chkprod1 := hold div base; { using method described when I }
              chkprod2 := hold mod base; { demoed doing it manually }
              endstr := numguide[chkprod2 + 1] + endstr;
                        { actual representation }
              hold := chkprod1;
          dec2xbase := endstr;

  function xbase2dec(int: string; base: integer):longint;
    { converts any base < 37 to base 10 }
      numguide: string = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
      i, powr: integer;
      endresult: longint;
      intlength: integer;
      if base > 36 then
        xbase2dec := -1 {signal fucntion has failed }
          endresult := 0;
          i := 1;
          powr := length(int) - 1;
          while i <> length(int)+1 do   { compute back to base 10 }
              endresult := endresult + (pos(int[i], numguide)-1) *
                           power(base, powr);
              i := i + 1;
              powr := powr - 1;
      xbase2dec := endresult;

    numread, numbase: longint;
    convbase: string;
    write('Enter a number (base 10): ');
    write('What base do you want to convert to? ');
    convbase := dec2xbase(numread, numbase);
    if convbase = '!' then
      writeln('Use a base less than 37')
    else begin
      writeln(numread, ' (base 10) is ', convbase, ' (base ', numbase, ').');
      numread := 0;
  { required setting of initial read var to 0 to prove our functions work }
      writeln('To check: ', convbase, ' (base ', numbase, ') is ',
               xbase2dec(convbase, numbase), ' (base 10).');

Now on with the show....This is going to be more of a special topics practice
thing.  As a note, all of the commands I use here will require you to use
the dos or windos unit....also, have your TP Programmers Reference handy to
look up several of the basic functions that I will list here.

A simple if successful...
How do we check if any of these, or our prior reset/rewrite calls are
successful?  We toggle the I compiler option before a valid file oper-
ator, then we check the variable in IOResult.  If IOResult <> 0, then
we know there is a problem.  For example, how do we check if a file
exists on the drive before we read from it?

function exists(filename: string):boolean;
    ourfile: text; { can be defined to be any type we want }
    assign(ourfile, filename);  { let the program know about the file }
    {$I-}reset(ourfile);{$I+}   { toggle I, on a reset. Both MUST be there }
    if IOResult <> 0
    { did something go wrong?  With reset, if file did not exist, something
      would be wrong }
      exists := false           { indicate file doesn't exist }
    else  { file exists }
        close(ourfile); { we need to close the file since if it's there, }
        exist := true;  { we just opened it.  Also, indicate it's there. }

This method can be used with any file function like that as long as the
system is aware of the file, to indicate success, possibilities, etc.,
in whatever logical means the command indicates.  For reset, one logically
can say that if something goes wrong with it, the file isn't there, or the
path is invalid.  For rewrite, one logically can say if the path isn't
right or available, or if there's no disk space, or a variety of reasons.

The I compiler directive indicates turning ON or OFF input/output checking.
The default for most compilers is {$I+}.  As compiler directives go, we
must toggle it back, because the area after the change is in effect, and
we only want it for the case of the command we want to check.  If I/O
checking is on, the program would end in a run-time error (you may have
noticed this, if you have tried to access non-existent files in your
experimentation with reading/writing text files). If I/O checking is off
({$I-}, the result of the command is logged in a global variable named

Being able to subvert run-time errors is good to be able to do, especially
for a DOS file function type program, where you even deal with files.
If the user specifies an incorrect file to read, you can return an intel-
ligible, user-understandable error message that the filename they gave
does not exist on the drive.

A list of run-time error possibilities may be found on page 260 of the
TP7 programmers reference.

Straight Forward Things
        I will list several commands for working with files and DOS
which are relatively straight-forward to use.  Unit used, then command,
then a short description.  Detailed descriptions follow if needed.  I
will mainly cover the DOS unit variants.  If you use TPW, look up the
WinDOS variant equivalents...Be sure to look each of these up in any
case.... By all means, play with each of these to understand them.
Notes on some of these things will appear later.

System: ChDir(Str: string);  { changes the current directory to path in Str }
DOS/WinDos: DiskFree(Drive: byte):longint; { free bytes on Drive }
DOS/WinDos: DiskSize(Drive: byte):longint; { total bytes on Drive }
DOS/WinDos: DosVersion: word; { tells us what version of DOS we have }
DOS: EnvCount: integer; { how many environment strings? }
DOS: EnvStr(index: integer): string; {return a specific environment string}
System: erase(file: filetype);  {erases an external file}
System: FilePos(file: filetype): longint; {returns current position in file}
System: FileSize(file: filetype): longint; {returns a file's size}
DOS: FSearch(filename: PathStr; dirlist: string):Pathstr {search for a file}
DOS: FSplit(...  {split a filename into a dir, name, and ext}
DOS/WinDos: Getdate(... { gets the current date of the operating system.}
System:GetDir(drive; str: string); {gets current directory}
DOS: GetEnv(...  {gets the specified environment variable}
DOS/WinDos: GetFAttr (... { returns file attributes of a file }
DOS/WinDos: GetFTime (... { get the date and time of a file }
DOS/WinDos: GetTime(... { get current time }
System: Halt(code); { quits program immediately with an errorlevel }
System: MkDir(str: string); { makes a directory named str }
DOS/WinDos: PackTime(... { packs a time/date }
System: Rename(file: filetype); { renames an external file }
System: RmDir(str: string); { removes a dir named str }
System: Seek(file: filetype); { finds an element # in a file }
DOS/WinDos: SetDate( ... { sets the date on the machine }
DOS/WinDos: SetFTime(... { sets the date and time of a file }
DOS/WinDos: SetTime(...  { set the time on the machine }
DOS/WinDos: UnpackTime(... { unpacks a time/date to datetime record }

Notes on some of the commands listed above that aren't that self-
Diskfree(Drive: byte): longint;
DiskSize(Drive: byte): longint;
     Drive is a numerical byte: 0 is the current drive.
                                1 is A drive.
                                2 is B drive.
                                3 is C drive.
                                and so on and so forth.

NOTE: DiskFree and DiskSize are limited from what I understand to < 1 gig?
(Please correct me if I am not quoting this right).  I know there is a
limit there somewhere...

DosVersion(version: word);
     You have to use HI and LO functions here as described in part 7.
     The high order of this expression would be a minor revision number
     and the low order of this expression would be a major revision
     number.  For example, if you have DOS 6.20, the high order would be
     20, and the low order would be 6.

Any expression that uses packed and unpacked date and time.
     There is two of the functions listed above called packtime and
     unpacktime.  There is a special record already defined in DOS
     called DateTime (or TDateTime in WinDOS).  Both of these are defined
     like this:

     DateTime { or TDateTime -- they're the same } = record
        Year, Month, Day, Hour, Min, Sec: word;
     A packed time is stored as a longint;

Good use of an erase/rename, etc, etc...
     Make a procedure that does the assign, and error checks, then do
     the command, if the command requires the file to be an assigned
     file variable.  For example:

     procedure deletefile(filename: string);
         afile: text; { It can be anything we will find out }
         assign(afile, filename);
         if IOResult <> 0 then
           writeln('Erasure unsuccessful.');
           writeln(filename, ' has been erased from your drive!');

Note: This erase command is recoverable by use of an undelete program.
The rewrite is not (all you'll see is a 0 byte file that replaces the
file you rewritten).

Parameter Passing
You may have noted that we can get parameters in from the command-
line to some programs we have used, such as PKZIP and PKUNZIP.
We can write and design our programs to do such a similar thing.

Paramcount: integer;  { holds the number of command-line parameters used }
Paramstr(num: integer): string { specific command-line parameter }

An example: Write a text file out to the screen using full error-checking,
and taking the filename from the command-line.  Describes a command-line
parameter describing a single file.

program typefile;
    param1, instr: string;
    thefile: text;
    if paramcount <> 1 then  { if there is not one command-line parameter }
        { show some help to the user }
        halt(1);    { quit the program right here }
    param1 := paramstr(1); { corresponds to %1 in batch file processing }
    { always good to do -- found that addressing the function directly as
      a string causes problems }
    assign(thefile, param1);
    if IOResult <> 0 then
        writeln('This file doesn''t exist!');
    readln(thefile, instr);
    { if the file is there, no need to error check reads, if our logic
      is correct }
    while not eof(thefile) do
        readln(thefile, instr);

Now, what if we want to determine a file based on a command-line parameter.
This isn't exactly complete error checking, as we aren't processing whether
the file we specify is a directory or not (EVERYTHING to DOS is a file of
some sort, including a directory, we can't exactly TYPE a directory...).
That was a demo of picking up command-line parameters, here's how we
process a filename parameter so we are addressing the correct thing,
EVEN a directory, and series of files (Remember wildcards in DOS?  The
usage of * and ? in specification of files).

We use Fexpand, Fsplit, and GetFattr with doing this, as well as some DOS
defined constants which distinguish between different attributes of files.
They are additive, BTW.

     File Attribute Constants
        ReadOnly       $01
        Hidden         $02
        System File    $04
        Volume ID      $08
        Directory      $10
        Archive        $20
        AnyFile        $3F

Look at the DOS dir command for an example.  Vary what you type in,
including directory names, and variants (.., \, and just a dirname).
See how it acts.  That's what we want to emulate.  I will lead in
to part 9 by placing something used in planning called pseudocode
here to do such a thing.


You will have to interpret this into viable code for the programming
problem at the end of the part.

Listing a Sequence of Files
Now, to answer the question, what if we want to actually do something
with multiple files (specified with * and ?), instead of just one file
(if we want just one file, if we do the i/o checking on reset or
rewrite, we will get an error if they use * or ? in the name.).

We need to define the usage of a record as a searchrec type for DOS
(or TSearchRec for WinDos).

      searchrec = record
         fill: array[1..21] of byte;
         attr: byte;       { additive from the file attribute constants }
         time: longint;    { Packed Time }
         size: longint;    { Size of file }
         name: string[12]; { name of file }

This record, as well as DateTime is defined in the DOS unit, and we don't
NEED to define these in our programs...They are used with the FindFirst
and FindNext procedures, which are demoed below, listing all filenames
in the current directory.

program tutorial19; uses dos;
    fileinfo: searchrec;
    findfirst('*.*', $37, fileinfo);
    while doserror = 0 do         { 18 = no more files }

As a note: . and .. are filenames.  . is the base of the file system, ..
is the next directory up....

Executing a Program
You can execute a program from your program by using the exec procedure.
You also have to use the $M compiler directive.  It's a directive to
determine the stack size, as well as the minimum and maximum heap size.
You must set this for to get the memory to run the program.  The format
of the $M compiler directive is this:

  {$M <stacksize>,<minheapsize>,<maxheapsize>}

If you don't set this, maxheapsize defaults to all of your conventional
memory, definitely not good if we want to give the memory to a program
to even run (nothing will happen if the program doesn't have the memory
to run).  For example, we will use {$M $4000,0,0}.  Here is the format
of the EXEC command.

  exec(<command interpreter path>, <command>);

command interpreter path -> Where is COMMAND.COM?  We can use getenv
to get the environment variable named COMSPEC to get this.  I've had
people try to rebunk me to say that you could just say exec(<command>);.
It DOES NOT WORK! (the compiler says something about expecting a ,.
You must do it this way!) -- I'm only trying to head off this issue.

command -> This must be /C + whatever command you call....

Another command used in combination with this is called swapvectors.
It is a parameterless procedure which makes sure our program we call
doesn't literally stomp all over anything we may have done with the
system.  We call it before and after our exec procedure.

Here's an example:

{$M $4000,0,0}
program tutorial20; uses dos;
    command: string;
    write('Enter a DOS command: ');
    command := '/C' + command;
    { we must put the /C there to satisfy COMMAND.COM }
    exec(getenv('COMSPEC'), command);
    if doserror <> 0 then
      writeln('Dos Error ', doserror, ' occurred.')
      writeln('Successful shell to DOS.');

Use DosExitCode in addition to doserror to determine the results of the
exec'd procedure.  This function returns a word, which we have to use
the HI and LO functions on.  LO code results correspond to errorlevel
returned out of DOS (remember batch file programming?).  A list of HI
code results follow:

            0       Normal Termination
            1       Terminated by Ctrl+C
            2       Terminated by Device Error
            3       Terminated as a TSR.
We have covered the major issues in DOS file commands, as well as
listing some of the straight-forward commands that are in Pascal
to work with DOS.  If you looked those up, you would have found that
most of those things are pretty straight-forward.  Here is a practice
programming problem that duplicates the function of the DIR command
in DOS, showing us files with specified information.  It will be a
clone with different functions, though.  Let us see....

Practice Programming Problem #8
        Write a program in Pascal and entirely Pascal that will show
us a listing of all files in a directory given on the command-line.
It should also support command line parameters that change function,
such as '/?' and '/P'.  Those are the two command-line parameters that
we should support (be sure we make it case insensitive).  /? or -?
will show us help and a listing of who wrote the program and then
terminate the program.  /P or -p should make it so we pause the screen
output on each page.  It also should handle any error-checking that
it may need to perform as presented in this part.

1) Show us for each filename on one line, a size, file attributes, date
and time.
2) For the whole listing of files, show us: The volume label, Size of the
drive we listed, bytes free on the drive we listed, total number of
files listed, total number of directories listed, and DOS version that
is being used at the current time.
3) All integers or longints > 999 should be delinated by commas, or
periods, whatever you use.
4) Write r for read-only, a for archive, s for system, h for hidden.
Put the proper ones that apply by each file that it lists.
5) For reading command-line parameters, be sure to make the order
non-specific.  For example, MYDIR c:\windows /p and MYDIR /p c:\windows
should accomplish the same thing, viewing the windows directory with
page pausing.

1) If a file with a volumeID attribute exists in the main directory of
a drive, the name of the file is the volumeID of the drive.
2) Work with the number as a string and go from the right end to the
left end to place commas as strings for function point 3.
3) You may want to use a designed record type to hold the final data
you are going to write as you go obtain it to make things easier.
4) This DIR listing command you are writing should function from the
command-line with regards to filespec exactly like the DOS dir
command.  Check this one by playing around with the DOS dir command.
5) Hint for the #5 function.  Hold the parameter strings in an array
and write some code to differentiate between command-line params and
the actual path you want to view.
6) For the string with the attributes, build your string.  You can
directly address and build a string with certain portions as long
as you start with a valid length of something.  I will explain this
in the next part.

Sample output for yourdir command.
C:\>mydir /?

MYDIR (c) 1996 by Glenn Grotzinger.  { put your name here, of course }

  MYDIR <filespec> /<parameters>
  filespec is the filename/dirname(s) we want to list.
  parameters are ? or P
       ? --> this help.
       P --> pause on each screen page.


MYDIR (c) 1996 by Glenn Grotzinger.

File listing for: C:\*.*

.                                                  [DIR]
..                                                 [DIR]
CONFIG.SYS              122    12-12-95    03:45pm  rash
DESCRIPT.ION            182    12-28-95    12:14am  -a--    
ARCHIVE.ZIP      14,432,322    03-24-93    09:15pm  r--h  

Volume label:          Total files: 3      Total dirs: 2
DOS Version: 6.22

14,432,626 bytes.
214,232,123 bytes used out of 543,212,123 total bytes.

You get the general idea....BTW, for me, this one is 310 lines..
The next practice program given will be in part 10.  This one
and the one in part 10 will be the longest and probably most
complex ones in the whole set of pascal tutorials.  It would be
good for ANYBODY who wants the practice to do these.

Next time
We will do the first part of the tutorial covering applications
development.  In doing that, we will develop this DIR equivalent
that I posed above.  Do practice, though, and do this one.  Any
comments, questions, etc, may go to [email protected]
                        Turbo Pascal for DOS Tutorial
                             by Glenn Grotzinger
                     Part 9 -- Applications Development
             All parts copyright (c) 1995-6 by Glenn Grotzinger

Hello.  This time, I'm not going to go present the solution to part 8
immediately, because I want to sue the part 8 problem to demonstrate
applications development.  The basic reason I'm doing this is because
programs should be designed and then programmed, normally, and not the
other way around.  Normally in design, we have different tools available
for us to use.  The ones I'm aware of are pseudocode and flowcharting.
I can't cover flowcharting through text, but I can cover pseudocode.

What is pseudocode?
Pseudocode is basically English-like descriptions of what is going on.
I gave you an example of it in part 8 (with capital letters for emphasis,
as I will continue to do so) as it pertains to a good line parsing
function.  We will go through and design a solution to part 8 in this

How to start out?
It is good to start with the global way of things.  Let's start with a
few facts we know from our knowledge from part 8.

        1) We're wanting listings of files.
        2) We're wanting stats on the operating system as well as the
           drive we access.

Knowing our purposes in these two statements, we know we need usage of
the DOS or WinDOS unit.

Globally, by looking at the way we know the output needs to be, we can
figure out that globally, the program is going to have to do the

        1) Write the ID of the program. {mydir by...}
        2) Parse the command-line to determine what needs to be done.
        3) Write resultant headers based on results in 2.
        4) If successful, while there are valid files, list the files
           as dictated in 2.
        5) Show the global drive and operating system stats.

Starting to break down the global stuff and writing pseudocode
We know this is the basic idea of things that we will have to do in
order to complete this program.  We need to move from this down to
the specific details.  Let's start with 1.

We need to write the ID of the program (Global 1).
Also, we need to look at factors for initialization code.  We need
some filespec variable clear...Also we know we need to count files
and dirs, as well as set a pause flag to our default (false).

           SET PAGE COUNT TO # OF LINES USED (page pausing!)

Parse the command-line (Global 2).
I will start globally, and move down to specifics here.  Keep in mind
that the functions said that path/filename and parameter should be
interchangeable (Function 5 as specified in the problem).  Here, what
seems logical is to use the most definable command-line parameter and
eliminate things down to the least specific.  Here, the command params
? and P are the most specific while the path is the least specific.

           FIRST CHARACTER IS - OR /

Now, this is a general description of what's going on in part 2 of our
global listing we made.  Now for specifics here.  The only ones I see
are showing the help and quitting, and parsing the filespec.

Show help and quit.
        SET PAGE COUNT TO NUMBER OF LINES USED (Page count for pause!)

Parsing the filespec.  I gave you this pseudocode in part 8.

Write resultant headers based on 2. (Global 3)

List files as dictated by filespec in 2. (Global 4)
We know, basically, in the DIR implementation we want to list all files
except ones with the volume lables.  We know the file constants are
additive but to ease things in typing, we can go ahead and add them up.
All files except volumeID in the constants is equal to $37.  Also, we will
keep in mind the following points that were brought up in the directions.

        1) (Function 1) Show us for each filename on one line a size,
           file attributes, date and time.
        2) (Function 3) All integers or longints > 999 should be
           delineated by commas, or periods, whichever you use.
        3) (Function 4) Write r for read-only, a for archive, s for
           system, and h for hidden.
        4) If we need to pause, be sure to implement it!
        5) Be sure to indicate if there are no files.

              INCREMENT # OF DIRS BY 1.
              INCREMENT # OF FILES BY 1.
              COUNTER BACK TO 0.

There's our global listing for part 4.  Now we need to consider the
individual actions, basically, obtaining numbers with commas, the date,
the time, and the file attributes.

Numbers with commas (Code explained later)

The date and time.  Basically, the issue here is pulling the information
for the date, except the year, where we must consider the last 2 digits
instead of 4 digits.  In getting the last 2 digits, we also need to keep
in mind that the year 2000 will be coming in 4 years...  With the
time, it will be in military time, so we may recycle code, say from part
4 (It is always good to save code and copy so you do not have to
invent the wheel and then turn around and invent it again...:)).  Note
the repeated appearances of the strings "Make XXX a string." and "If
XXX < 10, pad number with zero."  To pad a number, I mean, if I have
9, then padding it with a zero would make it 09.  In good programming
planning, if repeated code happens, isolate that code as a function or
procedure to save lines of code.  AT the end, you will see that I have
done that.

        IF MONTH < 10, PAD NUMBER WITH 0.
        IF DAY < 10, PAD NUMBER WITH 0.
        IF YEAR > OR = 2000
           2YEAR IS YEAR - 2000
           2YEAR IS YEAR - 1900
        IF YEAR < 10, PAD NUMBER WITH 0.
                          (or DAY-MONTH-2YEAR, whichever you prefer)

        IF HOUR > OR = 12
           HOUR IS HOUR - 12
           MERIDIAN IS PM.
           MERIDIAN IS AM.
        IF HOUR = 0 THEN HOUR = 12.
        IF HOUR < 10, PAD STRING WITH 0.
        IF MIN < 10, PAD STRING WITH 0.
        TIME IS HOUR:MINmeridian.

Get file attributes.  I gave hint #6 for this part to build your string
and said I'd explain later.  A string in pascal (a pascal string),
actually is stored using length + 1 bytes.  The first byte is a number
representing the total length of the string.  The rest of it is the
string.  Since we use a background of -'s, that's all we need to start
the string from...Then use proper position to assign things...and again
using the file attribute constants, remembering that they are additive.
Starting from the largest to smallest...Say, we want to reassign the
3rd character of STR, we just say str[3] := 's' ro something like that.

        SET STRING TO ----.
        IF FILEATTR >= $20 THEN
           FILEATTR = FILEATTR - $20.
        IF FILEATTR >= $04 THEN
           FILEATTR = FILEATTR - $04.
        IF FILEATTR >= $02 THEN
           FILEATTR = FILEATTR - $02.
        IF FILEATTR >= $01 THEN
           FILEATTR = FILEATTR - $01.

Show the global stats and operating system info (Global 5)
Basically, this is a write operation.  But we need to know how to get
the information...

Volume label.  Using the hint in the problem.


Total files and total dirs and total size of the files listed are
simply writing variables (be sure to run these numbers through the
comma delineator function).

Total size used on drive.  The drive is designated as a number, and the
nice thing about our pase_filespec thing is that the first character
always ends up being the drive letter of the drive we want to work with.
So, to go from drive letter to number, we can always set a constant
guide string (sort of like my suggestion in part 7 to do this for bases
> 11 as to ease things) defined as: ABCDEFGHIJKLMNOPQRSTUVWXYZ.  I know
this is probably overkill, but it will cover things OK so we don't have
to write a large case statement.  Run this one through the delineator.


Total size on drive.  Similar to total siz eused.  Uses constant guide
string...also ran through delineator.


DOS version.  Getting this is exactly like described in part 8.  Nothing
out of the ordinary.

This finishes up my pseudocode description for the part 8 problem.  Now,
for my best solution that I could come up, with the suggestion (probably
could be faster if I didn't do it, but I went ahead and used the save
format for the record as some help, and allowed for 999,999,999 as a
possible total file size in the layout -- too big to really worry, though
it probably slows it up a little...)

Keep in mind too, that this pseudocode was revised, after I found out that
some of my original statements turned out to be wrong in my logic planning.
It's OK to create incorrect pseudocode.  It is only a planning tool, and
does not have to be correct.  The only thing anyone will really care about
being correct is your final source code -- correct meaning that it works

The source code for my implementation of MYDIR.

program part8; uses dos;

  { a dir command.  Supports command params /? and /P.
    shows filename, filesizes with commas, time, date, attributes.
    For total, shows drivesize in bytes, bytes used on drive.
    Volume label of drive, total number of files, total numbers of
    directories. }

    writerec = record     { format to write out the dirinfo }
      filename: string[12];
      filesize: string[11]; { largest size => 999,999,999 bytes }
      filedate: string[8];
      filetime: string[7];
      fileattr: string[4];

    dirinfo: searchrec;
    writeinfo: writerec;
    params: array[1..2] of string;
    i: integer;
    pause: boolean;
    filespec: string;
    dirs, files, totalsize: longint;
    pagelen: integer;

  function parse_filespec(filename: string):string;

      all_files = $37;
      { all file constants in base 16 added together but VolumeID }
      dir: dirstr;   { required types for some of the commands as }
      name: namestr; { defined in the DOS unit. }
      ext: extstr;
      attr: word;    { attribute must be a word. }
      f: text;       { required for a command we had to assign file for }

      filename := fexpand(filename);  { expand filename }
      if filename[length(filename)] <> '\' then { if end not \ }
          assign(f, filename);
          getfattr(f, attr);            { get the file attribute }
          if (doserror = 0) and (attr = $10) then
            filename := filename + '\'; { if it's a directory put \ }
      fsplit(filename, dir, name, ext); { split filename up. }
      if name = '' then name := '*'; { if it's still a directory, }
      if ext = '' then ext := '.*';  { specify ALL FILES }
      parse_filespec := dir + name + ext;  { re-form filename }

  function zero(innum: integer):string;
      tstr: string[2];
      str(innum, tstr);
      if innum < 10 then
        tstr := '0' + tstr;
      zero := tstr;

  procedure showid;
      writeln('MYDIR (c) 1996 by Glenn Grotzinger.');
      pagelen := pagelen + 2;

  procedure writeheader(filespec: string);
      writeln('File listing for: ', filespec);
      pagelen := pagelen + 2;

  function number(n: longint):string;
      i, j: integer;
      s, r: string;
      m: integer;
      str(n, s); { get the longint to a workable string }
      r := '';   { r is a holding string for our delineated number }
      i := length(s);
      { set an integer to the length of our number.  We will be going
      from the right end and moving to the left in placing our delin-
      eations, and building r from the right to the left. }
      if i > 3 then begin
      while i > 3 do   { while we don't have 3 numbers left }
          for j := i downto i-2 do  { count off 3 digits and move to r}
            r := s[j] + r;
          r := ',' + r;  { write a comma or period to r }
          i := i - 3;    { subtract 3 digits }
      for j := i downto 1 do
      { we only have 3 digits left, or less now.  Just count off the
        digits }
        r := s[j] + r;
      number := r;  { feed r to the function }
      number := s;

  procedure getdatetime(dirinfo: searchrec; var writeinfo: writerec);
    { type uses datetime record }
      dt: datetime;
      a,b,c: string; { temp strings for use }
      tmpyr: integer; { we need to define this one to do the year }
      unpacktime(dirinfo.time, dt); { unpack date and time }
      { start with date }
      { month }
      a := zero(dt.month);
      { day -- same as month }
      b := zero(;
      { year -- reported as 4 digits.  We need to get it down to 2. }
      { keep in mind the valid range that TP will report year. }
      if dt.year > 1999 then      { if year 2000 or above }
        tmpyr := dt.year - 2000
      else                        { else would be before 2000 }
        tmpyr := dt.year - 1900;
      { now that we have our last 2 digit year, perform similar to day
        & month }
      c := zero(tmpyr);
      writeinfo.filedate := a + '-' + b + '-' + c; { set final date }
      { now start with the time -- it's reported as military time --
        we need to deal with it as such }
      { military time determination }
      if dt.hour >= 12 then
          dt.hour := dt.hour - 12;
          c := 'pm';
        c := 'am';
      if dt.hour = 0 then
        dt.hour := 12;
      { hours -- deal with as we did days now }
      a := zero(dt.hour);
      { minutes }
      b := zero(dt.min);
      writeinfo.filetime := a + ':' + b + c;

  function getfileattr(dirinfo: searchrec):string;
      left: word;
      str: string;

      str := '----';
      left := dirinfo.attr;
      if left >= $20 then  { archive file }
          str[2] := 'a';
          left := left - $20;
      if left >= $04 then  { system file }
          str[3] := 's';
          left := left - $04;
      if left >= $02 then  { hidden file }
          str[4] := 'h';
          left := left - $02;
      if left >= $01 then  { read-only file }
          str[1] := 'r';
          left := left - $01;
      getfileattr := str;

  procedure writefinfo(dirinfo: searchrec;writeinfo: writerec);
      i: integer;
      with writeinfo do
          filename :=;
          filesize := number(dirinfo.size);
          getdatetime(dirinfo, writeinfo);
          fileattr := getfileattr(dirinfo);
          for i := length(filename) to 12 do
            write(' ');
          writeln(filesize:12, filedate:12, filetime:12, fileattr:12);

  function getvolumeID(drive: char):string;
    { volume label exists as a file in the root directory with a special
      attribute called VolumeID and the name of the file is the volume
      label }
      fstr: string;
      dinfo: searchrec;
      fstr := drive + ':\*.*';
      findfirst(fstr, VolumeID, dinfo);
      if doserror = 0 then  { Volume label exists so...}
         getvolumeID :=
         getvolumeID := ''; { leave it blank if no volume label }

  procedure showhelp;
      writeln('  MYDIR <filespec> /<parameters>');
      writeln('  filespec is the filename/dirname(s) we want to list.');
      writeln('  parameters are ? or P (case insensitive)');
      writeln('       ? --> This help.');
      writeln('       P --> pause on screen page.');

  function bytesfree(drive: char):longint;
      dno: integer;
      dno := pos(upcase(drive), guide);
      bytesfree := diskfree(dno);

  function bytesthere(drive: char):longint;
    { in TP, this won't work if you have a partition > 1 gigabyte }
      dno: integer;
      dno := pos(upcase(drive), guide);
      bytesthere := disksize(dno);

    { initialization code }
    showid; { should be the first thing we do }
    pause := false;
    filespec := '';
    totalsize := 0;
    dirs := 0;
    files := 0;
    { check # of parameters }
    if paramcount > 2 then
    { pull in parameters }
    for i := 1 to paramcount do
      params[i] := paramstr(i);
    { check 'em for ? and P parameters, and filespec }
    for i := 1 to paramcount do
      if (length(params[i]) = 2) and
         (params[i][1] in ['-','/']) then
         case upcase(params[i][2]) of
           '?': showhelp;
           'P': pause := true;
        filespec := params[i];
    filespec := parse_filespec(filespec);

    { go into start }
    findfirst(filespec, $37, dirinfo);
    while doserror = 0 do
        if dirinfo.attr = $10 then
            for i := length( to 12 do
              write(' ');
            writefinfo(dirinfo, writeinfo);
            totalsize := totalsize + dirinfo.size;
        if (pagelen > 23) and (pause) then
            pagelen := 0;
    if (doserror in [1..17]) or ((dirs = 0) and (files = 0)) then
      writeln('No files found.');
    writeln('Volume label: ', getvolumeid(filespec[1]), 'Total Files: ':20,
             number(files), 'Total Dirs: ':20, number(dirs));
    writeln('DOS Version: ', lo(dosversion), '.', hi(dosversion));
    writeln(number(totalsize), ' bytes.');
    writeln(number(bytesfree(filespec[1])), ' bytes free out of ',
            number(bytesthere(filespec[1])), ' total bytes.');

Next Time
We will cover reading and writing from binary files in part 10, as well
as use of units, overlays, and include files in programming.  Part 10
will be a long program, which will stress the ideas represented here
mainly, but will also involve material in part 10.  It will be a tedious
one, but does not require a lot of programming knowledge to complete.
This problem will also be set up for a programming contest.  If there
are any comments, please write [email protected]
                         Turbo Pascal for DOS Tutorial
                             by Glenn Grotzinger
        Part 10 -- binary files; units, overlays, and include files.
             All parts copyright 1995-6 (c) by Glenn Grotzinger.

        There was no prior problem, so lets get started...

Typed binary files
We know that files can be of type text.  We can also make them type "file
of <datatype>".  We can read and write binary data types to disk.  Here's
an example.   Keep in mind that with typed binary files, you can only
read and write the type of file you define it to be.  For the example
below, we can only deal with integers with this file.  The type we may
use may be anything that we have covered up to this point.  We also
will see that reading, accessing and writing of typed binary files will
be no different than accessing text files, except we can not make use
of readln and writeln (as those are for text files only).

program integers2disk;
{ writing integers 1 thru 10 to a disk data file, then reading 'em back }
     datafile: file of integer;
     i: integer;
     assign(datafile, 'INTEGERS.DAT');
     for i := 1 to 10 do
       write(datafile, i);
     close(datafile);  { done with write }
     reset(datafile);  { now lets start reading }
     read(datafile, i);
     while not eof(datafile) do { we can use the same concept }
         read(datafile, i);

You will notice the numbers 1 through 10 come up.  Look for the file
named INTEGERS.DAT, and then load it up in a text editor.  You will
notice that the file is essentially garbage to the human eye.  That,
as you see, is how the computer sees integers.  In part 11, I will
explain storage methods of many many different variables, and introduce
a few new types of things we can define.  We can use records, integers,
characters, strings, whatever...with a typed file as long as we comply
with the specific type we assign a file to be in the var line.

Untyped Binary Files
We can also open binary files as an untyped, unscratched (essentially)
file.  There we simply use the declaration "file". (I think this is ver7
dependent, am I right?)  Anyway, in addition to this, we have to learn
a few new commands in order to use untyped files.

BLOCKREAD(filevar, varlocation, size of varlocation, totalread);

filevar is the untyped file variable.
varlocation is the location of where we read the variable into.
size of varlocation is how big varlocation is.
totalread is how much of varlocation that was readable. (optional)

BLOCKWRITE(filevar, varlocation, totalread, totalwritten);

filevar is the untyped file variable.
varlocation is the location of where we read the variable into.
totalread is how much of varlocation was readable. (optional)
totalwritten is how much of varlocation that was written. (optional)


Function that gives total size of a variable in bytes.

Maximum readable by BlockRead: 64KB.

Reset and Rewrite have a record size parameter if we deal with an untyped

Probably, the best thing to make things clearer is to give an example.
This program does the same thing as the last one does, but only with
an untyped file.  See the differences in processing...

program int2untypedfile;

    datafile: file;
    i: integer;
    numread: integer;

    assign(datafile, 'INTEGERS.DAT');
    rewrite(datafile, 1);
    for i := 1 to 10 do
      blockwrite(datafile, i, sizeof(i));
    reset(datafile, 1);
    blockread(datafile, i, sizeof(i), numread);
    while numread <> 0 do
        blockread(datafile, i, sizeof(i), numread);
This program performs essentially the same function as the first example
program, but we are using an untyped file.  Blockread and blockwrite are
used in very limited manners here.  It's *VERY GOOD* for you to experiment
with their use!!!!!!!   As far as the EOF goes on a comparison, blockread
returns how many records it actually read.  We use that as an equivalent.

The 2 missing DOS file functions
We now have the tools to perform the 2 missing DOS file functions that you
probably recognized were gone from part 8, copying files, and moving files.

Copying files essentially, is repeated blockreads and blockwrites until
all the input file is read and all the output file is written.  We can
do it with either typed or untyped files.  An untyped file example may
be found on page 14 of the Turbo Pascal 7.0 Programmer's Reference.
For those who do not have this reference...Snippet of my own...untested...

while (numread <> 0) or (bytesw = bytesr)
    blockread(infile, rarray, sizeof(rarray), bytesr);
    blockwrite(outfile, rarray, bytesr, bytesw);

Moving files is a copy of an input file to a new location, followed by
erasure of the input file.

A unit is what you see probably on your drive in the TP/units directory.
Compiled units are TPU files.  They are accessed via USES clauses at the
start.  CRT, DOS, and WinDos are some of the provided units we have already
encountered.  Nothing is stopping us from writing our own, though.  The
actual coding of procedures/functions that we place into units is no
different.  The format of the unit, though, is something we need to think
about.  An example is the best thing for that.  This is a simple
implementation of a unit, with examples to give you some idea of a
skeleton to place procedures and functions into.

unit myunit;

     { all global const, type, and var variables go here as well as any
       code we may want to run as initialization for starting the unit. }

     { procedures and function headers are listed here }

     procedure writehello(str: string);

     { actual procedural code goes here, as it would in a regular program }

     procedure writehello(str: string);  { must be same as above }


The unit up above is compilable to disk/memory, but unrunable.  Essentially,
what it is is a library of procedures/functions that we may use in other
programs.  Let's get an example out on how to use one of our own units.

program tmyunit; uses myunit; { must match ID at beginning }
    str: string;
    str := 'Hello!  Courtesy of myunit!';

Though this program/unit combination is ludicrous, it does illustrate
exactly how to incorporate your own unit with MANY functions into your
programming, if your project gets too big, or for portability's sake
on some of your frequently used procedures.

This will describe how to use TP's overlay facility.  It must be used with
units.  Typically, my thoughts are that if you get a large enough project
to dictate the use of overlays (we can use 'em on anysize projects, but
the memory taken up by the overlay manager far uses more memory on smaller
projects to make it an advantage to habitually do this).  We will use
the overlay facility with the unit/program set above for example purposes.

and Overlay (if I remember right) are non-overlayable.

{$O+} is a compiler directive for UNITS only which designate a unit which
is OK to overlay.  {$O-} is the default, which says it's not OK to overlay
a unit.

To get to the overlay manager, we must use the overlay unit.

After the overlay unit, we need to use the {$O <TPU name>} compiler
directive to specify which units that we want to compile as an overlay.

WARNING: It is good to check your conversion to overlays in a program
with a copy of your source code.  If you alter it with overlays in mind
and it doesn't work (it's known to happen -- a procedure works ONLY when
it's not overlaid...), you won't have to go through the work to alter
it back if it doesn't work right...

NOTE: You must compile to disk, then run when you work with overlays.

Results come back in the OvrResult variable.  Here's a list...

                  0       Success
                 -1       Overlay manager error.
                 -2       Overlay file not found.
                 -3       Not enough memory for overlay buffer.
                 -4       Overlay I/O error.
                 -5       No EMS driver installed.
                 -6       Not enough EMS memory.

As for examples, let's look at the unit set up to overlay.  As we can
see, the only real difference (which is a good policy to make), is that
there is the {$O+} compiler directive there now...

unit myunit;

     { all global const, type, and var variables go here as well as any
       code we may want to run as initialization for starting the unit. }

     { procedures and function headers are listed here }

     procedure writehello(str: string);

     { actual procedural code goes here, as it would in a regular program }

     procedure writehello(str: string);  { must be same as above }


Now lets look into the program itself.  It's error-reporting from the
overlay manager isn't great.  It stops the program if the overlay won't
load, but doesn't do a thing, really, with the ems section.

program tmyunit; uses myunit, overlay;

  {$O MYUNIT.TPU}        { include myunit in the overlay }
    str: string;
    ovrinit('TMYUNIT.OVR'); { final overlay file name/init for program. }
    if OvrResult <> 0 then
        writeln('There is a problem');
      write('Overlay installed ');
    ovrinitems;        {init overlay for EMS.  Usable after ovrinit}
    if OvrResult <> 0 then
      writeln('There was a problem putting the overlay in EMS')
      writeln('in EMS memory.');
    str := 'Hello!  Courtesy of myunit!';

EXE Overlays
Here's how to set up EXE overlays.  The DOS copy command features the B
switch.  For example, to take the programs source file above and attach the
overlay to the end of the EXE (be sure you run any exe packers/encryptors
before you do this!), use the following:


Then the change that needs to be made in the source for the program is to
change the overinit line to read TMYUNIT.EXE instead of TMYUNIT.OVR.  You
should be able to handle doing this and understanding what is going on.

Include Files
Use the {$I <filename>} compiler directive at the position the include
file is to be placed.  An include file is code that is in another file,
which may be considered as "part of the program" at the position the
{$I ..} compiler directive is at.

Copy function
You can use the copy function to get a portion of a string into another
part of a string.  For example...

        str := copy('TurboPascal', 5, 3);
        writeln(str);      { writes oPa }

Programming Practice for Part #10
We have opened ourselves a business selling computer equipment in 1993.
Since we have occupied ourselves with working on computers, and not on
bookkeeping (we wanted to save the funds instead of hiring someone), and
rather not use the cash registers, we have done everything on paper over
the last two years.  It's the beginning of 1996, and any accurate records
of sales progression, as well as records of our customers has become
almost impossible, since our records are represented by a closet-full of
paper.  So, we finally have decided to get things into computer.

To do the typing, we have temporarily hired interns from a nearby business
college.  Unfortunately, with our limited funds, we could not draw in
people who had sufficient typing skill and accuracy, but we took what
we could get.  We now have things typed in as text files with 80 columns
a line.  Unfortunately, the interns' attention to detail has been as bad
as their typing skill, and nothing makes sense in their work.

Our purposes is to save our money in hiring these interns and locate the
badly entered records, while writing the good records to a solid binary
data file by the name of COMPHVN.DAT.  For the bad records, on EACH AND
EVERY error we encounter, we should write a text message with the first
20 characters of the problem line and a description of what is wrong with
the data set for that particular error so we may go back through and make
the interns redo what they did wrong to a text file named ERRORS.LOG.

The data format for the output file COMPHVN.DAT is as follows.  For
interest of efficiency, we shall write this program using COMPHVN.DAT
as an untyped file.  As the person posing this problem, I realize that
some of the data types in this record will not be recognizable at this
point, but with the variable description, you will know how to handle
them, and in part 11, you will see what they are exactly.  In creating
a binary file, we must always be concerned with using the least amount
of space as effectively as possible.   Uses of the variables will be
explained later.  For interest of typing efficiency on your parts,
I am asking that you cut and paste this record description out of this
description and save it as a text file named COMPHVN.INC, which may be
used as an include file in our compilation.

  comphvndata = record
      datacode: string[7];  
      acct_classification: char;
      phone_area: integer;   {area+prefix+exchange = phone number}
      phone_prefix: integer;
      phone_exchange: integer;
      work_area: integer;
      work_prefix: integer;
      work_exchange: integer;
      other_area: integer;
      other_prefix: integer;
      other_exchange: integer;
      cnct1_lname: string[16];
      cnct1_fname: string[11];
      cnct1_minit: char;
      cnct1_pobox: integer;
      cnct1_sname: string[8];
      cnct1_stype: string[4];
      cnct1_apt: integer;
      cnct1_city: string[10];
      cnct1_state: string[2];
      cnct1_zip: longint;
      cnct1_birthm: byte;
      cnct1_birthd: byte;
      cnct1_birthy: integer;
      accept_check: boolean;
      accept_credt: boolean;
      balnce_credt: real;
      total_sold: real;
      cnct1_emp_code: string[4];
      total_sales: integer;
      emp_name: string[10];
      emp_stnum: integer;
      emp_sttype: string[4];
      emp_city: string[10];
      emp_state: string[2];
      emp_zip: longint;
      emp_area: integer;
      emp_prefix: integer;
      emp_exchange: integer;
      emp_yrs: byte;
      compu: boolean;
      compu_type: string[9];
      compu_mon: char;
      compu_cdr: boolean;
      compu_cdt: char;
      compu_mem: byte;
      minor: boolean;

The format for our INPUT file, which will be named INDATA.TXT, will be as
follows (80 characters).  Since we had 15 interns doing the typing at once
we also had them merge their work.  They were careless, and may have not
accomplished it properly.  There will be three lines for each customer
that we have encountered.

Line 1                                 Line 2
datacode              columns 1-7     datacode         columns 1-7
acct_classification   column 8        accept_check     column 8
sequence number       column 9        sequence number  column 9
phone_area            columns 10-12   cnct1_stype      columns 10-13
phone_prefix          columns 13-15   cnct1_apt        columns 14-17
phone_exchange        columns 16-19   cnct1_city       columns 18-27
work_area             columns 20-22   cnct1_state      columns 28-29
work_prefix           columns 23-25   cnct1_zip        columns 30-38
work_exchange         columns 26-29   cnct1_birthm     columns 39-40
other_area            columns 30-32   cnct1_birthd     columns 41-42
other_prefix          columns 33-35   cnct1_birthy     columns 43-46
other_exchange        columns 36-39   balnce_credt     columns 47-55
cnct1_lname           columns 40-55   total_sold       columns 56-63
cnct1_fname           columns 56-66   cnct1_emp_code   columns 64-67
cnct1_minit           column 67       total_sales      columns 68-70
cnct1_pobox           columns 68-72   emp_name         columbs 71-80
cnct1_sname           columns 73-80

Line 3
datacode              columns 1-7
accept_credt          column 8
sequence number       column 9
emp_stnum             column 10-13
emp_sttype            column 14-17
emp_city              column 18-27
emp_state             column 28-29
emp_zip               column 30-38
emp_area              column 39-41
emp_prefix            column 42-44
emp_exchange          column 45-48
emp_yrs               column 49-50
compu                 column 51
compu_type            column 52-60
compu_mon             column 61
compu-cdr             column 62
compu_cdt             column 63
compu_mem             column 64-65
minor                 column 66
spaces                column 67-80

Now, a description as to what is defined as a correct set that we should
write to COMPHVN.DAT.

1) Each 3 lines that are read are considered for errors.  Check the sequence
numbers.  The first line's sequence number should be 1, for example.  A
successful read of 3 lines should say 1, 2 and 3 in that order.  For example,
in our error reporting, if you have a read of 1,2,2 , you should not write
the group to the binary file, and report a duplicate line #2 and a missing
line #3.  There will not ever be a circumstance where these sequence numbers
will all be the same...The cases covered in this paragraph would be the only
cases that would ever forstall processing of error-checks listed in points

2) Datacode on lines 1, 2 and 3 should MATCH exactly and be checked for the
following: It has the format, for example, with my name of GROTZ*G, and
should be verified using the cnct1_names...

3) phone_area, phone_prefix, phone_exchange, work_area, work_prefix, work_
exchange, other_area, other_prefix, other_exchange, pobox, emp_zip, emp_
area, emp_prefix, emp_exchange, emp_yrs, cnct1_zip, cnct1_birthm, cnct1_
birthd, cnct1_birthy, balance_credt, total_sold, total_sales, compu_mem
all should be checked to verify that they are numeric in origin.

4) phone_prefix, work_prefix, other_prefix, emp_prefix all should not start
with a 1 or a 0.

5) cnct1_birthy should be in this century 1900-1999.

6) acct_classification should be B,C,G,P, or O.

7) accept_check, accept_credt, compu, compu_cdr, and minor should be
Y or N.

8) emp_yrs (employed how many years?) should be checked with cnct1_birthy
for sanity (a person who was born in 1980 cant have worked 20 years).

9) If compu is N, then compu_type, compu_mon, compu_cdr, compu_cdt, and
compu_mem should be either blank or 0 depending upon the type of field.

10) cnct1_emp_code should be GOVT, RET, STUD, or BUS.  If this field is
RET, then emp_* should either be blank or 0 depending on the type of field.

11) compu_mon should be S, V, E, C, H, or I.

12) compu_cdt should be 1, 2, 4, 6, or 8.

13) emp_sttype and cnct1_stype should be BLVD, LANE, ST, AVE, CT, LOOP,
    DR, CIRC, or RR.

14) minor should be Y if person listed in cnct1_?name is < 21 years old 
and N otherwise.  Check to be sure that this field is correct in being
Y or N.

Format of ERRORS.LOG (also solution to the INDATA.TXT posted below)

                        Error Report -- INDATA.TXT

  First 20 characters of line    Problem
  ---------------------------    --------------------------
  GROA2*GN334  ST  WAR           Datacode does not agree with name.
  GROT2*GP181612932918           Work-exchange is not numeric.
  GROT2*GP181612932918           phone-prefix started with a 0 or 1.
  GROT2*GT2ST  314 SED           accept-check is invalid.
  GROT3*GP181642932918           Duplicate line #1
  GROT3*GN234  ST  WAR           Missing line #3
  GROT4*GI181642932918           Datacode does not agree with name.
  GROT4*GY2ST  314 SED           Datacode does not agree with name.
  GROT4*GN334  ST  WAR           Datacode does not agree with name.
  GROT4*GY2ST  314 SED           cnct1-birthy is not in this century.
  GROT4*GI181642932918           acct-classification is invalid.
  GROT7*GN334  ST  WAR           emp-zip is not numeric.
  GROT7*GN334  ST  WAR           compu-cdr is invalid.
  GROT7*GN334  ST  WAR           The emp-yrs doesn't make sense.
  GROT7*GN334  ST  WAR           There were fields present when compu was N.
  GROT7*GN334  ST  WAR           compu-mon is invalid.
  GROT7*GN334  ST  WAR           compu-cdt is invalid.
  GROT8*GN334  ST  WAR           empcodes are present when RET is true.
  GROT8*GN334  ST  WAR           compu-mon is invalid.
  GROT0*GN334  STR WAR           compu-cdt is invalid.
  GROT0*GN334  STR WAR           emp-sttype is invalid.

Remember to be as general as possible on your error messages. Use the
example listed above as a guide. Your program can not predict everything.
Also, in the interest of finding out your programming skill, we ask that
you code this program using the pascal overlay system with EMS load
capability, with all error codes and status statements active and visible
to the user, for at least one procedure or function.  Also note, that many
of the separate integer fields are put together in the input file, so we
can not just plain read the input file.

Here is a copy of the current input file, INDATA.TXT
(keep in mind it's 80 characters per line, and the character
positions MATTER)
GROT1*GP1816429329181674700008163475753GROT1INGER      GLENN      K232  34th
GROT1*GY2ST  314 SEDALIA   MO64093    062519742.34     3245.23 STUD32 CMSU
GROT1*GN334  ST  WARRENSBURMO65337    81654341114 YHOMEBUILTVY18 N
GROT2*GP18161293291816747000A8163475753GROT2INGER      GLENN      K232  34th
GROT2*GT2ST  314 SEDALIA   MO64093    062519742.34     3245.23 STUD32 CMSU
GROA2*GN334  ST  WARRENSBURMO65337    81654341114 YHOMEBUILTVY18 N
GROT3*GP1816429329181674700008163475753GROT3INGER      GLENN      K232  34th
GROT3*GY1ST  314 SEDALIA   MO64093    062519742.34     3245.23 STUD32 CMSU
GROT3*GN234  ST  WARRENSBURMO65337    81654341114 YHOMEBUILTVY18 N
GROT4*GI1816429329181674700008163475753BROT4INGER      GLENN      K2E2  34th
GROT4*GY2ST  314 SEDALIA   MO64093    062518742.34     3245.23 STUD32 CMSU
GROT4*GN334  ST  WARRENSBURMO65337    81654341114 YHOMEBUILTVY18 N
GROT5*GP1816429329181674700008163475753GROT5INGER      GLENN      K232  34th
GROT5*GY2ST  314 SEDALIA   MO64093    062519742.34     3245.23 STUD32 CMSU
GROT5*GN334  ST  WARRENSBURMO65337    81654341114 YHOMEBUILTVY18 N
GROT6*GP1816429329181674700008163475753GROT6INGER      GLENN      K232  34th
GROT6*GY2ST  314 SEDALIA   MO64093    062519742.34     3245.23 STUD32 CMSU
GROT6*GN334  ST  WARRENSBURMO65337    81654341114 YHOMEBUILTVY18 N
GROT7*GP1816429329181674700008163475753GROT7INGER      GLENN      K232  34th
GROT7*GY2ST  314 SEDALIA   MO64093    062519742.34     3245.23 STUD32 CMSU
GROT7*GN334  ST  WARRENSBURMO65W37    816543411134NHOMEBUILT  00 N
GROT8*GP1816429329181674700008163475753GROT8INGER      GLENN      K232  34th
GROT8*GY2ST  314 SEDALIA   MO64093    062519742.34     3245.23 RET 32 CMSU
GROT8*GN334  ST  WARRENSBURMO65337    81654341114 YHOMEBUILTZY18 N
GROT9*GP1816429329181674700008163475753GROT9INGER      GLENN      K232  34th
GROT9*GY2ST  314 SEDALIA   MO64093    062519742.34     3245.23 STUD32 CMSU
GROT9*GN334  ST  WARRENSBURMO65337    81654341114 YHOMEBUILTVY18 N
GROT0*GP1816429329181674700008163475753GROT0INGER      GLENN      K232  34th
GROT0*GY2ST  314 SEDALIA   MO64093    062519742.34     3245.23 STUD32 CMSU

1) You may use a for loop to read each set of 3 lines.  I will not throw
an error of omission of lines into the data file.  There will always
be multiples of 3 lines to work with.

2) The included data file in this text file includes errors from all 14
points listed above.  The data file I use for the contest will be different,
but will as well cover all 14 points listed above...

3) Be sure to get good use of your debugger, as you will NEED it...Also, be
sure to plan the program -- this is an easy one, yet it's complex because
of the amount of planning it requires...plan well, it's easy.  Don't plan
well, it's a bugger...:>

4) ONE hint: remember string addressing, and use of the copy procedure.

5) Another hint.  You can have what is referred to as "next sentence"
IF THEN ELSE statements.  It is very good in this program to be able to
use them.  (if condition then else) is essentially, a do nothing if con-
dition is true situation.  I suggest it because the pascal operator NOT
seems to not work right in all cases. :<

Also, keep in mind that this is the part 10 practice, too, so be sure to
at least attempt it!

Next Time
Interfacing with a common format; how data types are stored in memory and
on disk.  You may wish to obtain use of a hex viewer for this next part.
Send comments to [email protected]

                        Turbo Pascal for DOS Tutorial
                             by Glenn Grotzinger
            Part 11 -- data representation; reading specs sheets
                  copyright (c) 1995-96 by Glenn Grotzinger

Is there still any interest in this tutorial?  If so, tell me! :>

Here is the solution I got from part 10...No one tried it and sent it to me.
Here's the UNIT.  Keep in mind that I said to JUST CREATE ONE.  You don't
have to have the same functions that I had in there.  Just as long as it

unit unit10;

    strtype = array[1..3] of string[80];

    function numeric(str: string): boolean;
    procedure writerecord(var outfile: file; strs: strtype);


  function numeric(str: string): boolean;
      num: boolean;
      i: integer;

      i := 1;
      num := true;
      while (num) and (i <= length(str)) do
          num := (str[i] in ['0'..'9','.',' ']);
      numeric := num;

  procedure writerecord(var outfile: file; strs: strtype);
      numerr: integer;
      writerec: comphvndata;
      with writerec do
          {part 1}
          datacode := copy(strs[1], 1, 7);
          acct_classification := strs[1][8];
          val(copy(strs[1], 10, 3), phone_area, numerr);
          val(copy(strs[1], 13, 3), phone_prefix, numerr);
          val(copy(strs[1], 16, 4), phone_exchange, numerr);
          val(copy(strs[1], 20, 3), work_area, numerr);
          val(copy(strs[1], 23, 3), work_prefix, numerr);
          val(copy(strs[1], 26, 4), work_exchange, numerr);
          val(copy(strs[1], 30, 3), other_area, numerr);
          val(copy(strs[1], 33, 3), other_prefix, numerr);
          val(copy(strs[1], 36, 4), other_exchange, numerr);
          cnct1_lname := copy(strs[1], 40, 16);
          cnct1_fname := copy(strs[1], 56, 11);
          cnct1_minit := strs[1][67];
          val(copy(strs[1], 68, 5), cnct1_pobox, numerr);
          cnct1_sname := copy(strs[1], 73, 8);

          accept_check := (strs[2][8] = 'Y');
          cnct1_stype := copy(strs[2], 10, 4);
          val(copy(strs[2], 14, 4), cnct1_apt, numerr);
          cnct1_city := copy(strs[2], 18, 10);
          cnct1_state := copy(strs[2], 28, 2);
          val(copy(strs[2], 30, 9), cnct1_zip, numerr);
          val(copy(strs[2], 39, 2), cnct1_birthm, numerr);
          val(copy(strs[2], 41, 2), cnct1_birthd, numerr);
          val(copy(strs[2], 43, 4), cnct1_birthy, numerr);
          val(copy(strs[2], 47, 9), balnce_credt, numerr);
          val(copy(strs[2], 56, 8), total_sold, numerr);
          cnct1_emp_code := copy(strs[2], 64, 4);
          val(copy(strs[2], 68, 3), total_sales, numerr);
          emp_name := copy(strs[2], 71, 10);

          accept_credt := (strs[3][8] = 'Y');
          val(copy(strs[3], 10, 4), emp_stnum, numerr);
          emp_sttype := copy(strs[3], 14, 4);
          emp_city := copy(strs[3], 18, 10);
          emp_state := copy(strs[3], 28, 2);
          val(copy(strs[3], 39, 3), emp_area, numerr);
          val(copy(strs[3], 42, 3), emp_prefix, numerr);
          val(copy(strs[3], 45, 4), emp_exchange, numerr);
          val(copy(strs[3], 49, 2), emp_yrs, numerr);
          compu := (strs[3][51] = 'Y');
          compu_type := copy(strs[3], 52, 9);
          compu_mon := strs[3][61];
          compu_cdr := (strs[3][62] = 'Y');
          compu_cdt := strs[3][63];
          val(copy(strs[3], 64, 2), compu_mem, numerr);
          minor := (strs[3][66] = 'Y');
        blockwrite(outfile, writerec, sizeof(writerec));

And now here is the main program I got for part 10.  What I can see that is
not readily explainable here (I used every method I could think of that
falls into the rules I outlined for the thing.).  The two compiler directives
you see below turn off stack checking and range checking respectively.
With regards to data intensive applications such as sorting, large #'s of
comparisons, and so forth, adding these speeds it up.

program part10; uses dos, unit10, overlay;

  {$O UNIT10.TPU}
    strs: strtype;
    outfile: file;
    infile, errfile: text;
    i: integer;
    errwritten: boolean;

  procedure writeerror(errline, errtype: string);
      temp1: string;
      temp1 := copy(errline, 1, 20);
      writeln(errfile, temp1,'':10, errtype);
      errwritten := true;

  function checkstatus(strs: strtype): boolean;
      check: boolean;
      seqs: array[1..3] of char;
      cnts: array[1..3] of byte;
      i: byte;
      errtype: string;
      check := true;
      for i := 1 to 3 do
        cnts[i] := 0;
      for i := 1 to 3 do
          seqs[i] := strs[i][9];
          case seqs[i] of
            '1': inc(cnts[1]);
            '2': inc(cnts[2]);
            '3': inc(cnts[3]);
      for i := 1 to 3 do
          if cnts[i] = 0 then
              errtype := 'Missing line #' + chr(i+48);
              writeerror(strs[i], errtype);
              check := false;
          if cnts[i] = 2 then
              errtype := 'Duplicate line #' + chr(i+48);
              writeerror(strs[i], errtype);
              check := false;
      checkstatus := check;

  procedure checkdatacodes(strs: strtype);
      datacodes: array[1..3] of string[7];
      check1: string[5];
      check2: char;
      error: string[20];
      for i := 1 to 3 do
        datacodes[i] := copy(strs[i], 1, 7);
      check1 := copy(strs[1], 40, 5);
      check2 := strs[1][56];
      for i := 1 to 3 do
          if datacodes[i][6] <> '*' then
            writeerror(strs[i], 'Invalid datacode.');
          if (check1 <> copy(datacodes[i], 1, 5)) or
             (check2 <> datacodes[i][7]) then
            writeerror(strs[i], 'Datacode does not agree with name.');

  procedure checknumeric(strs: strtype);
      temp1, temp2: string;
      int, numerr, numdiff: integer;
      year, month, day, dayofweek: word;
      empyrs, birthy, bmo, bday: word;
      age: byte;
      isminor: boolean;

      if numeric(copy(strs[1], 10, 3)) = false then
        writeerror(strs[1], 'Phone-area is not numeric.');
      if numeric(copy(strs[1], 13, 3)) = false then
        writeerror(strs[1], 'Phone-prefix is not numeric.');
      if numeric(copy(strs[1], 16, 4)) = false then
        writeerror(strs[1], 'Phone-exchange is not numeric.');
      if numeric(copy(strs[1], 20, 3)) = false then
        writeerror(strs[1], 'Work-area is not numeric.');
      if numeric(copy(strs[1], 23, 3)) = false then
        writeerror(strs[1], 'Work-prefix is not numeric.');
      if numeric(copy(strs[1], 26, 4)) = false then
        writeerror(strs[1], 'Work-exchange is not numeric.');
      if numeric(copy(strs[1], 30, 3)) = false then
        writeerror(strs[1], 'Other-area is not numeric.');
      if numeric(copy(strs[1], 33, 3)) = false then
        writeerror(strs[1], 'Other-prefix is not numeric.');
      if numeric(copy(strs[1], 36, 4)) = false then
        writeerror(strs[1], 'Other-exchange is not numeric.');
      if numeric(copy(strs[1], 68, 5)) = false then
        writeerror(strs[1], 'cnct1-pobox is not numeric.');
      if numeric(copy(strs[3], 30, 9)) = false then
        writeerror(strs[3], 'emp-zip is not numeric.');
      if numeric(copy(strs[3], 39, 3)) = false then
        writeerror(strs[3], 'emp-area is not numeric.');
      if numeric(copy(strs[3], 42, 3)) = false then
        writeerror(strs[3], 'emp-prefix is not numeric.');
      if numeric(copy(strs[3], 45, 4)) = false then
        writeerror(strs[3], 'emp-exchange is not numeric.');
      temp2 := copy(strs[3], 49, 2);
      if numeric(temp2) = false then
        writeerror(strs[3], 'emp-yrs is not numeric.')
        val(temp2, empyrs, numerr);
      if numeric(copy(strs[2], 30, 9)) = false then
        writeerror(strs[2], 'cnct1-zip is not numeric.');
      temp2 := copy(strs[2], 39, 2);
      if numeric(temp2) = false then
        writeerror(strs[2], 'cnct1-birthm is not numeric.')
        val(temp2, bmo, numerr);
      temp2 := copy(strs[2], 41, 2);
      if numeric(temp2) = false then
        writeerror(strs[2], 'cnct1-birthd is not numeric.')
        val(temp2, bday, numerr);

      temp1 := copy(strs[2], 43, 4);
      if numeric(temp1) = false then
        writeerror(strs[2], 'cnct1-birthy is not numeric.')
          val(temp1, int, numerr);
          if (int < 1900) or (int > 1999) then
            writeerror(strs[2], 'cnct1-birthy is not in this century.');

      getdate(year, month, day, dayofweek);
      val(temp1, birthy, numerr);
      numdiff := year - birthy;
      if numdiff < empyrs then
        writeerror(strs[3], 'The emp-yrs doesn''t make sense.');

      age := year - birthy;
      if month < bmo then dec(age);
      if month = bmo then
        if day < bday then
      isminor := (age < 21);
      if ((isminor = true) and (strs[3][66] = 'N')) or
         ((isminor = false) and (strs[3][66] = 'Y')) then
        writeerror(strs[3], 'Minor is set incorrectly.');

      if numeric(copy(strs[2], 47, 9)) = false then
        writeerror(strs[2], 'balance-credt is not numeric.');
      if numeric(copy(strs[2], 56, 8)) = false then
        writeerror(strs[2], 'total-sold is not numeric.');
      if numeric(copy(strs[2], 68, 3)) = false then
        writeerror(strs[2], 'total-sales is not numeric.');
      if numeric(copy(strs[3], 64, 2)) = false then
        writeerror(strs[3], 'compu-mem is not numeric.');

 procedure checkprefix(strs: strtype);
     if strs[1][13] in ['0'..'1'] then
       writeerror(strs[1], 'phone-prefix started with a 0 or 1.');
     if strs[1][23] in ['0'..'1'] then
       writeerror(strs[1], 'work-prefix started with a 0 or 1.');
     if strs[1][33] in ['0'..'1'] then
       writeerror(strs[1], 'other-prefix started with a 0 or 1.');
     if strs[3][42] in ['0'..'1'] then
       writeerror(strs[3], 'emp-prefix started with a 0 or 1.');

 procedure checkacct(strs: strtype);
     if strs[1][8] in ['B','C','G','P','O'] then
       writeerror(strs[1], 'acct-classification is invalid.');

 procedure checkyn(strs: strtype);
     if strs[2][8] in ['Y', 'N'] then
       writeerror(strs[2], 'accept-check is invalid.');
     if strs[3][8] in ['Y', 'N'] then
       writeerror(strs[3], 'accept-credt is invalid.');
     if strs[3][51] in ['Y', 'N'] then
       writeerror(strs[3], 'compu is invalid.');
     if strs[3][62] in ['Y', 'N'] then
       writeerror(strs[3], 'compu-cdr is invalid.');
     if strs[3][66] in ['Y', 'N'] then
       writeerror(strs[3], 'minor is invalid.');

 procedure checkcompun(strs: strtype);
     if strs[3][51] = 'N' then
       if (copy(strs[3], 52, 14) <> '            0 ') then
         writeerror(strs[3], 'There were fields present when compu was N.');

 procedure checkempcode(strs: strtype);
     if copy(strs[2], 64, 4) = 'RET ' then
       if (copy(strs[2], 71, 10) <> '          ') and
          (copy(strs[3], 10, 20) <> '0                   ') and
          (copy(strs[3], 30, 20) <> '0         0  0  0   ') then
          writeerror(strs[3], 'empcodes are present when RET is true.');

 procedure checkcompumon(strs: strtype);
     if strs[3][61] in ['S','V','E','C','H','I'] then
       writeerror(strs[3], 'compu-mon is invalid.');

 procedure checkcompucdt(strs:strtype);
     if strs[3][63] in ['1','2','4','6','8'] then
       writeerror(strs[3], 'compu-cdt is invalid.');

 procedure checksttype(strs: strtype);
     a: string;
     a:=copy(strs[3], 14, 4);
     if (a <> 'BLVD') and (a <> 'LANE') and (a <> 'ST  ') and (a <> 'AVE ')
       and (a <> 'CT  ') and (a <> 'LOOP') and (a <> 'DR  ') and
       (a <> 'CIRC') and (a <> 'RR  ') then
       writeerror(strs[3], 'emp-sttype is invalid.');
     a:=copy(strs[2], 10, 4);
     if (a <> 'BLVD') and (a <> 'LANE') and (a <> 'ST  ') and (a <> 'AVE ')
       and (a <> 'CT  ') and (a <> 'LOOP') and (a <> 'DR  ') and
       (a <> 'CIRC') and (a <> 'RR  ') then
       writeerror(strs[2], 'cnct1-stype is invalid.');

 procedure writeheader;
     writeln(errfile, 'Error Report -- INDATA.TXT':50);
     writeln(errfile, '--------------------------':50);
     writeln(errfile, 'First 20 characters','':10, 'Problem');
     writeln(errfile, '-------------------','':10, '-------');

   if ovrresult <> 0 then
       case ovrresult of
         -1: writeln('Overlay manager error.');
         -2: writeln('Overlay file not found.');
         -3: writeln('Not enough memory for overlay buffer.');
         -4: writeln('Overlay I/O error.');
   case ovrresult of
      0: writeln('Overlay loaded to EMS memory!');
     -5: writeln('No EMS driver found! Loading to conventional memory!');
     -6: writeln('Not enough EMS memory to load! Loading to conventional',
                 ' memory!');
   assign(infile, 'INDATA.TXT');
   assign(errfile, 'ERRORS.LOG');
   assign(outfile, 'COMPHVN.DAT');
   rewrite(outfile, 1);
   while not eof(infile) do
       errwritten := false;
       for i := 1 to 3 do
         readln(infile, strs[i]);
       if checkstatus(strs) then
       if errwritten = false then
         writerecord(outfile, strs);

Basically, the plan for this tutorial is to go through all standard data
types and show how TP stores them in memory, so we will be able to interpret
a binary data file we may create.  Then, using the information we presented
on how things are stored, we will get a spec sheet on a common format, and
interpret the data, so we may be able to obtain data from that file through
a Pascal program.

The basic idea of a byte has been covered in part 7 of the tutorial.
Please refer back to there for a refresher on the concept of what a byte is.

Basically, a byte can be used to substitute or represent a char type,
or a number type....The number types, stored as a byte, have a limit of
0..255 as an unsigned number (byte), and -128..127 with a signed number
(shortint).  The terms in the parentheses are what we would define the
byte to be to get the specified range.  I will explain later in this
tutorial the difference between a "signed" and "unsigned" number, actually
signed and unsigned data space for numbers.

The number for an unsigned number is formed, much like we were doing the
binary computations in part 7.

Numbers as Integers
There are several types of numbers we can define...

Byte, and ShortInt: as described above in the byte description...

Integer types are either as signed (range: -32768..32767) or unsigned
(0..65535) words.  A word is a unit of 2 bytes in TP.  Basically, in
a unsigned integer, the number is calculated from right to left in binary
much like a byte.  Since it is easier, for us to show binary storage
repsentation as hexadecimal units of bytes, we will use hexadecimal values
for an example.

An integer type is written to disk, to test something.  The sequence of
two bytes is:  3F 4D  .  What is the number in base 10 form?

If we remember from our hex notation, and the way things work:

   3 * 16^3 + 15 * 16^2 + 4 * 16^1 + 13 * 16^0 = 16205

An integer is ALWAYS two bytes, whether or not the number may technically
fill two bytes in this manner described before.  For example, 13 in base
10 is stored in memory and on disk by TP in integer form as 00 0D .  We
could equally store this number as a byte if we knew that this variable
location would never be requested to hold a number that is greater than
255.  For example, if we knew we were going to hold days of the month
in memory, we would never have a number greater than 31, so a byte type
for this number would be appropriate.

A longint type is always a signed number, but we do need to know, that
it is what is called a double word, or two words put together.  Therefore,
we know that a longint type is 4 bytes, and has a maximum limit of 2bil.
A longint is ALWAYS 4 bytes, whether or not the number may fill 4 bytes.
13 in base 10 would be stored in a longint as 00 00 00 0D .

A character is stored in memory as a byte, with the byte value
representing the corresponding character as it refers to the
ASCII chart.

byte representation 67 as a char is C.

Signed vs. Unsigned Integer Numbers
We have talked before in this part about signed and unsigned numbers.  In
part 7, essentially, we have covered unsigned numbers, when we were
describing binary logic.  Let's describe what a signed number is.

A signed number is represented by either using a base system of 0..1, or
1..0 in binary.  In a signed number, the leftmost bit is either 0 for a
positive number, and 1 for a negative number.  For positive numbers, the
remaining bits are considered using a 0..1 scheme as we did in part 7
with the binary computations.  For a negative number, we count starting
from 1 and go down to 0.

Let's observe what that difference is, by demonstrating how 3 and -3 would
be shown in a shortint (signed) type in binary.

Let's start with 3 in binary as a signed number. That is a positive number
so the leftmost bit will be 0.  Then we will use a 0..1 counting system
to finish out the number using standard binary notation.  So, 3 will be
represented in a shortint value as:

                             0000     0010 

just like it was an unsigned number.  Now, lets observe what -3 would look
like.  That is a negative number, so the leftmost bit would be a 1.  Since
we use a 1..0 system, we would start with 1's in everything.  We know 3 is
10 in binary, so we use a reverse system and come up with:

                             1111     1101

To illustrate further, in binary counting (to a computer) -1..-5 would be
(represented in hex) as $FF,$FE,$FD,$FC,$FB; as opposed to $01,$02,$03,
$04,$05 for 1..5 .  In a signed system, negative number counting starts at
-1, which explains why the equal range of a shortint is -128..127, and
there are equally 128 items (positive and negative).

As practice, look at the integers.dat now, in a hex viewer, and see exactly
how the numbers are stored.  They are two bytes in length, and should count
from one to ten, as we did in the program.  It should look like this in
your hex viewer....

00 01 00 02 00 03 00 04 00 05 00 06 00 07 00 08 00 09 00 0A

I recommend very heavily that you get a hex viewer to be able to help out.

0 = False; 1 = True

Common Pascal Strings
Now we will start to discuss the format of grouped data.  The first format
we will discuss will be the common pascal string.  The format of a pascal
string formatted group of characters is the following.

When we define a string without a subscript, we are defining a maximum of
255 characters. When we define for example, a string[20], we are defining
a max of 20 characters.  In reality, we are defining the number of char-
acters + 1.  A string has a first byte (0th byte) that represents the
actual character length of the data stored in the string, as an unsigned
byte. (Actually, the length function reads this byte when you call it,
but it's also possible to read and refer to the byte, and even SET it.)

Refer back to part 8 where I said you could individually set each part
of the string.  It's possible to actually build a string by setting the
length byte, then setting the rest of the string as characters.  Say,
to set the dynamic length of a string to 4, we can do this, as in this
example, which will only write "Hello" instead of "Hello world!".  We
are setting the 0'th part of the string as a character, which we need
to do:

program test; 
    p: string;
    p := 'Hello world!';
    p[0] := #5;

Blocked Character Strings
It's also possible to work with an array of characters as a string, by
referring to the whole array.  The maximum length must be set by the
length of the array, essentially.

str: array[1..20] of char;

if we read this structure in as a whole, it will have 20 characters in it,
in which we can write back out to the screen by saying WRITE(STR);, or
actually convert to a string by counting through to find the actual length
and then setting the length byte.

Null Terminated Strings
This is a length of characters, which are terminated by a null character,
or #0.  The strings unit is documented to work with these strings, but it's
easier to get away with simply converting it into something we can work
with through Pascal itself, if we have to do much with it.

The general structure of an array was described in part 6.  It is basically
usable as a grouping of items.

A record is stored in memory basically as a grouping of data types.  The
record type below:

  datarecord = record
    int: integer;
    character: char;

would be stored in memory like this:


Now, we have described all of the pertinent items we need to know to be
able to work through a spec sheet on a common format.  Basically, data
for spec sheets are sometimes presented as the record format, in either
Pascal or C format.  We don't need to really do any work for that, but
most of the time, it will be presented in a byte by byte offset format.

Let us look at this structure file for an example.  It is of the standard
sound file called a MOD file. (you can find them anywhere, almost)  We
will cover just the header of the file for now...

Protracker 1.1B Song/Module Format:
Offset  Bytes  Description
   0     20    Songname. Remember to put trailing null bytes at the end...
Information for sample 1-31:
Offset  Bytes  Description
  20     22    Samplename for sample 1. Pad with null bytes.
  42      2    Samplelength for sample 1. Stored as number of words.
               Multiply by two to get real sample length in bytes.
  44      1    Lower four bits are the finetune value, stored as a signed
               four bit number. The upper four bits are not used, and
               should be set to zero.
               Value:  Finetune:
                 0        0
                 1       +1
                 2       +2
                 3       +3
                 4       +4
                 5       +5
                 6       +6
                 7       +7
                 8       -8
                 9       -7
                 A       -6
                 B       -5
                 C       -4
                 D       -3
                 E       -2
                 F       -1
  45      1    Volume for sample 1. Range is $00-$40, or 0-64 decimal.
  46      2    Repeat point for sample 1. Stored as number of words offset
               from start of sample. Multiply by two to get offset in bytes.
  48      2    Repeat Length for sample 1. Stored as number of words in
               loop. Multiply by two to get replen in bytes.
Information for the next 30 samples starts here. It's just like the info for
sample 1.
Offset  Bytes  Description
  50     30    Sample 2...
  80     30    Sample 3...
 890     30    Sample 30...
 920     30    Sample 31...
Offset  Bytes  Description
 950      1    Songlength. Range is 1-128.
 951      1    Well... this little byte here is set to 127, so that old
               trackers will search through all patterns when loading.
               Noisetracker uses this byte for restart, but we don't.
 952    128    Song positions 0-127. Each hold a number from 0-63 that
               tells the tracker what pattern to play at that position.
1080      4    The four letters "M.K." - This is something Mahoney & Kaktus
               inserted when they increased the number of samples from
               15 to 31. If it's not there, the module/song uses 15 samples
               or the text has been removed to make the module harder to
               rip. Startrekker puts "FLT4" or "FLT8" there instead.
Source: Lars "ZAP" Hamre/Amiga Freelancers


Building the basic record format
We will need to essentially, for any standard file, built record format(s)
for the file.  Let us start with this one.

It says above that the first 20 bytes would be a song title.  The description
best seems to define it as a null-terminated string, but we know the maximum
limit.  So, lets just call it a blocked character string of length 20.  So
we will use this description for part of our component record:

songname: array[1..20] of char;

If we read on through the file, it states data for samples 1-31.  They are
varied data, so that infers that we need to build another record format.
But for the position so far in our current record format, we will use
this, since we know that there are a group of 31 samples...we will call
our alternate record for the sample data, samplerec.

sampledata: array[1..31] of samplerec;

Alternate Sample Record
22 bytes are defined for a samplename.  Same logic for songname.  So this
unit of our sample record will be

samplename: array[1..22] of char;

The next part is a sample length defined by 2 bytes.  So, we could either
call this a word, or an integer:

samplelength: integer;

One byte for a finetune value.  Logical definition:

finetune: byte;

Volume is defined as one byte.  So...

volume: byte;

Repeat point and Repeat length are both defined as words above so...

repeatpoint: integer;
repeatlength: integer;

It is indicated above that we are done with the samples.  Therefore, our
final record format for the samples would be:

samplerec = record
  samplename: array[1..22] of char;
  samplelength: integer;
  finetune: byte;
  volume: byte;
  repeatpoint: integer;
  repeatlength: integer;

Finishing the Record Format
Continuing on...

songlength is a byte.  So

songlength: byte;

A byte is defined next that seems like a filler byte.  So...

filler: byte;

The next set of 128 bytes are defined as "song positions 0-127".  Each byte
is also defined to hold a number, so lets keep to that:

positions: array[0..127] of byte;

The next 4 bytes to finish out our headers is the moduleid.  it's 4 bytes,
text, so...

id: array[1..4] of char;

Having gone through the header format for this file, we have come up with
a record we can use to read all the header data, that we would need to read
for a mod file.  Therefore, we can use a final type listing in our program
to read a mod file header of:

samplerec = record
  samplename: array[1..22] of char;
  samplelength: integer;
  finetune: byte;
  volume: byte;
  repeatpoint: integer;
  repeatlength: integer;

modrecord = record
  songname: array[1..20] of char;
  sampledata: array[1..31] of samplerec;
  songlength: byte;
  filler: byte;
  positions: array[0..127] of byte;
  id: array[1..4] of char;

We can use this format to gain any information we know about a file.  To
test this format, we need to write a program that pulls the information
out of a standard file, that we know, and check to see if they're identical.
For MODs, we would need to get a player, and load up the player, in order
to get data we are familiar with from the spec sheet, such as the id being

All the data we need to know to extract data out, and get formatted data
from standards files, have been presented.

Practice Programming Problem #11
In keeping with the topic of this tutorial, I am asking you to write a
program entirely in Pascal that will take a ZIP file name from the command-
line and then print a list of all the files within that zip file.  With
each filename, list the compressed size, uncompressed size, date, time,
and compression method, one per file.  At the end, list the total number
of files, total compressed and uncompressed size, effective compression
ratio, and write the comment. Then output this list to a file taken on
the command-line.  For example, a command-line will always be:


Be sure to design this program with any error checks you may need to perform.
Don't forget to devise a check to be sure you are dealing with a ZIP file
on the first parameter.  Here, we have gone to look at some references,
and have found the following out of a specs list:

The ZIP archives are created by the PkZIP/PkUnZIP combo produced
by the PkWare company. The PkZIP programs have with LHArc and ARJ
the best compression.
The directory information is stored at the end of the archive, each local
file in the archive begins with the following header; This header can be used
to identify a ZIP file as such :
OFFSET              Count TYPE   Description
0000h                   4 char   ID='PK',03,04
0004h                   1 word   Version needed to extract archive
0006h                   1 word   General purpose bit field (bit mapped)
                                      0 - file is encrypted
                                      1 - 8K/4K sliding dictionary used
                                      2 - 3/2 Shannon-Fano trees were used
                                    3-4 - unused
                                   5-15 - used internally by ZIP
                                 Note:  Bits 1 and 2 are undefined if the
                                        compression method is other than
                                        type 6 (Imploding).
0008h                   1 word   Compression method (see table 0010)
000Ah                   1 dword  Original DOS file date/time (see table 0009)
000Eh                   1 dword  32-bit CRC of file (inverse??)
0012h                   1 dword  Compressed file size
0016h                   1 dword  Uncompressed file size
001Ah                   1 word   Length of filename
001Ch                   1 word   Length of extra field
001Eh               "LEN" char   path/filename
001Eh               "XLN" char   extra field
After all the files, there comes the central directory structure.

(Table 0009)
Format of the MS-DOS time stamp (32-bit)
The MS-DOS time stamp is limited to an even count of seconds, since the
count for seconds is only 5 bits wide.

  31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16
 |<---- year-1980 --->|<- month ->|<--- day ---->|

  15 14 13 12 11 10  9  8  7  6  5  4  3  2  1  0
 |<--- hour --->|<---- minute --->|<- second/2 ->|

(Table 0010)
PkZip compression types
0 - Stored / No compression
1 - Shrunk / LZW, 8K buffer, 9-13 bits with partial clearing
2 - Reduced-1 / Probalistic compression, lower 7 bits
3 - Reduced-2 / Probalistic compression, lower 6 bits
4 - Reduced-3 / Probalistic compression, lower 5 bits
5 - Reduced-4 / Probalistic compression, lower 4 bits
6 - Imploded / 2/3 Shanno-Fano trees, 4K/8K sliding dictionary
[7..9 also exist] => note added by Glenn Grotzinger from Phil Katz's

--- Central directory structure
The CDS is at the end of the archive and contains additional information
about the files stored within the archive.
OFFSET              Count TYPE   Description
0000h                   4 char   ID='PK',01,02
0004h                   1 byte   Version made by
0005h                   1 byte   Host OS (see table 0011)
0006h                   1 byte   Minimum version needed to extract
0007h                   1 byte   Target OS
                                 see above "Host OS"
0008h                   1 word   General purpose bit flag
                                 see above "General purpose bit flag"
000Ah                   1 word   Compression method
                                 see above "Compression method"
000Ch                   1 dword  DOS date / time of file (see table 0009)
0010h                   1 dword  32-bit CRC of file 
0014h                   1 dword  Compressed size of file
0018h                   1 dword  Uncompressed size of file
001Ch                   1 word   Length of filename
001Eh                   1 word   Length of extra field
0020h                   1 word   Length of file comment
0022h                   1 word   Disk number ??
0024h                   1 word   Internal file attributes (bit mapped)
                                    0 - file is apparently an ASCII/binary file
                                 1-15 - unused
0026h                   1 dword  External file attributes (OS dependent)
002Ah                   1 dword  Relative offset of local header from the
                                 start of the first disk this file appears on
002Eh               "LEN" char   Filename / path; should not contain a drive
                                 or device letter, all slashes should be forward
                                 slashes '/'.
002Eh+              "XLN" char   Extra field
002Eh               "CMT" char   File comment

(Table 0011)
PkZip Host OS table
0 - MS-DOS and OS/2 (FAT)
1 - Amiga
2 - VMS
3 - *nix
4 - VM/CMS
5 - Atari ST
6 - OS/2 1.2 extended file sys
7 - Macintosh
8-255 - unused

--- End of central directory structure
The End of Central Directory Structure header has following format :
OFFSET              Count TYPE   Description
0000h                   4 char   ID='PK',05,06
0004h                   1 word   Number of this disk
0006h                   1 word   Number of disk with start of central directory
0008h                   1 word   Total number of file/path entries on this disk
000Ah                   1 word   Total number of entries in central dir
000Ch                   1 dword  Size of central directory
0010h                   1 dword  Offset of start of central directory relative
                                 to starting disk number
0014h                   1 word   Archive comment length
0016h               "CML" char   Zip file comment


Source: FILEFMTS (c) 1994,1995 Max Maischein

Sample output for this program

                       Files contained in FILENAME.ZIP

FOOBAR10.TXT       732  10-01-1993  02:30          1021       7
 FOBAR11.ZIP     11021  12-01-1995  22:03         23923       6
               1520032                          4023232

15 files; Effective compression ratio: 65.3%

< write the comment here, or write "No ZIP comment." if there is no
ZIP comment >

1) Note how I have the sample output.  It needs to be EXACTLY as I have
listed it.
2) The orders and the methods you need to use to read files should be
evident from the specs file.  They will be random reads...
3) With the random reads, it is about impossible to tell what you got
read unless you check first...Notice a good way to check out of the
first field of each record...
4) The "MS-DOS Time Format" can be done really easily.  Just think DOS
5) Do not read anything unless you HAVE to....
6) Since this is a program that happens to use another program's data,
more than likely, the data are correct, so there is no need to check
any of the data beyond determining whether it's an actual ZIP file.

With this data listed, you should be able to do anything related to listing,
stripping comments, showing comments, and so on and so forth.

Next Time
We will cover the use of stacks and queues.  Send any comments,
encouragements, problems, etc, etc. to [email protected]  Haven't
seen anything of that type, ya know....

                       Turbo Pascal for DOS Tutorial
                            by Glenn Grotzinger
                        Part 12: Stacks and Queues
                 copyright(c) 1995-96 by Glenn Grotzinger

Solution to part 11's problem....

program part11; uses dos;

  { Lists files and data in a ZIP file. }

    ziplocfileheader = record
      zipver: integer;
      bitfield: word;
      compmethod: integer;
      packtime: longint;
      crc32: longint;
      compsize: longint;
      uncompsize: longint;
      filelen: integer;
      xtralen: integer;

    zipcds = record
      ver: byte;
      hostos: byte;
      verxtr: byte;
      targos: byte;
      bitfield: word;
      compmethod: integer;
      packtime: longint;
      crc32: longint;
      compsize: longint;
      uncompsize: longint;
      filelen: integer;
      xtralen: integer;
      comtlen: integer;
      disknum: integer;
      intattr: integer;
      extattr: longint;
      roffset: longint;

    endcdsheader = record
      numdisk: integer;
      numdiska: integer;
      numfiles: integer;
      entries: integer;
      size: longint;
      ofs: longint;
      comtlength: integer;

    zipfile: file;
    outfile: text;
    locfile: ziplocfileheader;
    dirstr: zipcds;
    endcds: endcdsheader;
    filechar: char;
    filelength: array[1..20] of char;
    id: array[1..4] of char;
    i: integer;
    totsize, compsize, uncompsize: longint;
    param1, param2: string;

  { Example structure of a ZIP file.
      endofcentraldirectorystructure }

  procedure readzip;

    { ZIP is a random data binary file.  We read the ID to check it, then
      differentiate it from there.  We also devise a test of whether the
      "ZIP file" is a valid ZIP file here. }

      blockread(zipfile, id, sizeof(id));
      totsize := totsize + 4; { totsize is a seek index }
      if (id[1] <> 'P') and (id[2] <> 'K') then
          writeln('Corrupted ZIP file, or not a ZIP file.');
      case id[3] of
        #05: begin
               blockread(zipfile, endcds, sizeof(endcds));
               totsize := totsize + sizeof(endcds);
        #03: begin
               blockread(zipfile, locfile, sizeof(locfile));
               totsize := totsize + sizeof(locfile);
        #01: begin
               blockread(zipfile, dirstr, sizeof(dirstr));
               totsize := totsize + sizeof(dirstr);

  function zero(int: integer):string;
    {places zero on the beginning of a number if it's < 10 }
      strng: string;
      numerr: integer;
      str(int, strng);
      if int < 10 then
        strng := '0' + strng;
      zero := strng;

  function strip(str: string):string;

    { removes null characters from random-read filename }

      tstr: string;
      i: integer;
      tstr := '';
      for i := 1 to length(str) do
        if str[i] <> #0 then
          tstr := tstr + str[i];
      strip := tstr;

  function exist(filename: string):boolean;
    { file existence test }
      thefile: text;
      assign(thefile, filename);
      if IOResult <> 0 then
        exist := false
          exist := true;

  procedure header(filename: string);
    { writes header of data }
      s: string;
      writeln(outfile, 'Files contained in ':41, filename);
      writeln(outfile, 'NAME', 'COMP-SIZE':21, 'DATE':9,'TIME':12,
              'UNCOMP-SIZE':15, 'COMP-METHOD':13);
      fillchar(s, 75, '-');
      { fillchar fills a set of contiguous bytes of any type defined by
        the position s is in, for x number of spaces (75), with a char-
        acter represented by - }
      s[0] := #74;  { set length byte }
      writeln(outfile, s);

  procedure footer(compsize, uncompsize: longint);
    { write footer of data }
      s: string;
      fillchar(s, 75, '-');
      s[0] := #74;
      writeln(outfile, s);
      writeln(outfile, compsize:22, uncompsize:34);
      writeln(outfile, endcds.numfiles, ' files; Effective compression',
                       ' ratio: ', (compsize / uncompsize)*100 :0:1, '%');

  procedure writehelp;
    { help for incorrect usage }
      writeln('ZIPLIST <zipfile> <datafile>');

  procedure writedata(locfile: ziplocfileheader);
      dt: datetime;
      unpacktime(locfile.packtime, dt);
      { this handles the MS-DOS time format }
      writeln(outfile, strip(filelength):12, locfile.compsize:10,
      zero(dt.hour):6,':', zero(dt.min),
      locfile.uncompsize:10, locfile.compmethod:12);

    if paramcount <> 2 then  { incorrect parameters? }
    totsize := 0;compsize := 0;uncompsize := 0;
    param1 := paramstr(1);
    param2 := paramstr(2);
    assign(zipfile, param1);
    if exist(param1) = false then
        writeln(param1, ' does not exist!');
    reset(zipfile, 1);
    assign(outfile, param2);


    while id[3] = #03 do
     { process local file headers and compressed data }
        blockread(zipfile, filelength, locfile.filelen);
        compsize := compsize + locfile.compsize;
        uncompsize := uncompsize + locfile.uncompsize;
        totsize := totsize + locfile.filelen;
        totsize := totsize + locfile.xtralen;
        fillchar(filelength, sizeof(filelength),#0);
        seek(zipfile, totsize);
        totsize := totsize + locfile.compsize;
        seek(zipfile, totsize);

    while id[3] = #01 do
      { process central directory structure }
        totsize := totsize + dirstr.filelen;
        seek(zipfile, totsize);
        totsize := totsize + dirstr.xtralen;
        seek(zipfile, totsize);

    footer(compsize, uncompsize);


    { handle ZIP comment }
    if endcds.comtlength = 0 then
      writeln(outfile, 'No comment in ZIP file.')
      for i := 1 to endcds.comtlength do
          blockread(zipfile, filechar, sizeof(filechar));
          write(outfile, filechar);

Hello.  Basically, we are dealing in this part with specialized types of
data structures called stacks and queues.

A stack is best analagous to a stack of papers on a desk, only able to
either place a sheet on the top of the stack, or take a sheet off the
top of the stack.  It is generally defined as a record format, which
can take on the following:

stack = record
  elements: array[1..maxstacksize] of <whatever>;
  capacity: integer; {range of 0..maxstacksize}

It is best described as a last in/ first out data structure, much like the
sheet of paper analogy.  The elements array holds whatever information
we need, while capacity tells us how many elements are still in the array.

The best description I can use for this and queues is that it's a limited
defined access structure.

I will now describe the basic actions of working with stacks.  The best
way to think about coding this is to use the stack of paper as a view-
point, and using the sample stack record above.

To initialize or effectively kill a stack currently in usage is to set
capacity to 0.  We can do this, because all work with a stack ultimately
comes down to usage of capacity as an index.  If we have 0 sheets of paper
on an imaginary stack, essentially, the stack is empty.

Now, let's add a sheet of paper to our imaginary stack.  We recognize that
there is one more sheet of paper on the stack now.  Then we have to place
that sheet on the stack...also, we have to keep in mind if we have hit our
arbitrarily set limit of sheets on the stack, and notify of such thing.

What now, if we have to remove the sheet of paper from our imaginary stack
of paper?  We would need to check if we had a sheet of paper to remove.
Then after that, we would need to pull the sheet off of the stack, then
recognize that we removed the sheet.

How do we recognize if we have a full stack or an empty stack?  Look at the
value of capacity.  If it's 0, then it's empty.  If it's the value for the
maximum capacity of the stack we decide, then we know it's full.

Basically, in the last four paragraphs, I have, in discussing the actions
of working with stacks, have given you all the pseudocode you need to
be able to come up with the code to work with a stack.

It is basically good to develop all of these components as procedures.
If you come across a problem where a last in, first out solution is
needed, a stack may be the proper way to go.

A queue is set up much like a line at the local shop to pay the merchant
at the cash register.  The first element in is the first element out.
Much like a stack, a queue may be defined as a record format.

queue = record
  elements: array[1..maxqueuesize] of <whatever>;
  front, back: integer;
  count: integer;

Notice that the differences we see here, are that to take from the front
of the so called line we need to know where the front is in the order.
The same idea occurs with the back.  We need to know where the back is
in the array.  Count just helps us in knowing how many values we currently
have in the queue.

To start up a queue, basically, we need to start the front at 1, the rear
at the maximum size, and count at zero.

To check whether it's full or  empty is basically set through count.  Check
count to be zero for a queue to be empty and maxsize for a queue to be full.

One sticky mess comes up when we try to add an element to the back of the
line.  How do we keep track of things?  We increment the back unless it's
maxqueuesize, then we set it to 1, essentially, to continue reusing the
array storage as a loop....

To add to the back of the line, we do like we did with the stack.  We add
the element to the back, then we increment the back count as described

To take from the front of the line, we pull the element from the front of
the queue, and then increment the front count as described above.

Basically, the effect we are getting is much like a message scrolling
across a surface, like you may have seen on your television, and at
Times Square in NY (on several movies).

Both of these data storage schemes work beautifully, when you have a lot of
data to deal with that can be passed through quickly, but needs to be
released at other times...beyond that, at this point, I can't think of
anything that can be done by only using a stack or a queue, so my
suggestion for practice is to attempt coding each of these specialized
data types to do simple things.

Practice Programming Problem(s) #12
Here are a couple of things for this one.

1) Reverse a string of text inputted from the screen (not necessarily
a string) of a maximum of 500 characters, and output the text back to
the screen.

Enter a string: AbCdE

Your string reversed is: EdCbA

2) Use a queue to store a maximum set of numbers that is 50 numbers long.
These numbers will come from a series of randomly generated numbers by your
program.  The random numbers you will generate are from 1..1000, and
the ones that are from 1..50 will be written out to the screen when the
queue is filled up with those numbers.  Write the numbers in the queue out
in 5 rows of 10, and at the bottom, write out the total number of random
numbers you had to go through to get those 50 numbers in the proper range.

23 23 11 22 33 1 9 12 23 11
23 3 11 22 33 11 9 12 23 11
23 23 11 22 33 11 9 12 23 11
23 29 11 2 33 11 9 12 23 11
23 23 11 22 33 11 9 50 23 11

987 numbers were generated from 1-1000 to get the 50 numbers from 1-50.

Next Time
Recursion. (n) See Recursion. :>

Any comments?  Write [email protected]
                        Turbo Pascal for DOS Tutorial
                             by Glenn Grotzinger
          Part 13: Recursion; system unit commands not covered yet.
                 copyright(c) 1995-96 by Glenn Grotzinger

There were 2 simple problems presented...The first one...

program part12_1;

    maxstacksize = 500;

    stack = record
      elements: array[1..maxstacksize] of char;
      capacity: byte;

    thestack: stack;
    i: byte;
    ch: char;

  procedure startstack(var thestack: stack);
      with thestack do
        capacity := 0;

  procedure place(var thestack: stack; element: char);
      with thestack do
          if capacity = maxstacksize then
            writeln('**ERROR**  Trying to place element on full stack!')
              elements[capacity] := element;

  procedure remove(var thestack: stack; var element: char);
      with thestack do
          if capacity = 0 then
            writeln('**ERROR**  Trying to remove element from empty stack!')
              element := elements[capacity];

   write('Enter a string: ');
   while ch <> #10 do
       place(thestack, ch);
   write('The string reversed is: ');
   while thestack.capacity <> 0 do
       remove(thestack, ch);
And now the second one...

program part12_2; uses crt;

    maxqueuesize = 50;

    queue = record
      elements: array[1..maxqueuesize] of integer;
      front, back: integer;
      count: integer;

  procedure startqueue(var thequeue: queue);
      with thequeue do
          front := 1;
          back := maxqueuesize;
          count := 0;

  procedure incqueue(var thenum: integer);
      if thenum = maxqueuesize then
        thenum := 1

  procedure place(var thequeue: queue; element: integer);
      with thequeue do
        if count = maxqueuesize then
          writeln('**ERROR** Trying to place element in full queue.')
            elements[back] := element;

  procedure remove(var thequeue: queue; var element: integer);
      with thequeue do
        if count = 0 then
          writeln('**ERROR** Trying to remove element from empty queue.')
            element := elements[front];

    thequeue: queue;
    int, count: integer;

    while thequeue.count <> maxqueuesize do
        int := random(1000) + 1;
        if int <= 50 then
          place(thequeue, int);
    while thequeue.count <> 0 do
        remove(thequeue, int);
        write(int, ' ');
        if thequeue.count mod 10 = 0 then

    writeln(count, ' numbers generated from 1-1000 to get these 50 ',
                   'numbers from 1-50');

Very useful to defeat the fact with the compiler that a function has to be
defined above another function in order to use the first function in that
second function.  Above the function that it needs to be in, say something

function a(var input: integer): string; forward;

Then on down below in the code, you need to go ahead and define the
procedure or function entirely, with code.  For the header, you would
use something like this:

function a;

Doing this would accomplish being able to use a function defined after
a code call of the function for some purpose.  Successive recursion using
two seperate functions (a calls b; b calls a; and so on and so forth),
would be one example of having to use a forward.

The $M compiler directive
I described the $M compiler directive back in part 8.  Please review it
there.  Basically, the first number of the compiler directive is the
program stack size.  It is very important.  The default, if the $M is not
defined is 16K.  The maximum it can be defined to is 64K.  You may have to
use this compiler directive to increase the stack size so a recursion may
complete.  If the stack is filled, a runtime 202 error occurs.

Recursion, to put it simply, is the execution of a function or procedure
directly within that same function or procedure.  It is hard, sometimes,
logically to see use of recursion, but when you see a thoroughly repetitive
action, recursion could be used.

Recursion should be used with a procedure which basically has a small
number or NO parameters, since the recursion places a new occurrence of
the procedure on the stack, along with those variables.  It is quite
possible for a procedure to recurse itself upwards of thousands of times.
Therefore, you could easily run out of memory in the stack in running your

Basically, in logic, recursion is the repetition of a procedure as a regular
or irregular loop by calling the procedure inside of the procedure, with
some regulated terminating code.

NOTE: Recursion must be done with relative caution in Pascal.  It is
really, REALLY easy to shoot oneself in the foot, literally, by use
of recursion.  It has it's advantages, but since Pascal is unlimited
by how, and why you use recursion, versus other languages, which may
limit it or not allow it all together.

As an example, we will look at taking a factorial of a number.  Basically,
to use an example, 4! (factorial) is 4 X 3 X 2 X 1.  Algebraically, we
could see a factorial (n!) as n X (n-1) X (n-2) X (n-3)...(n - (n+1)).
Let's try looking at a code example that does it...after that, I will
explain the process in detail that goes behind how it works.  It's a
simple, elegant one line set of code in a function that does all the
multiplications for any number we put in there.

I will try my best to explain what exactly is going on here in this
example of recursion.  That, I find, is the hardest thing to see in
the concept of how recursion works, is because it is hard to
conceptualize how the variables and functions work, in a manner that
is understandable.

In all of the books I have read, I have not seen an adequate description
of the actual logic and action of recursion -- enough to allow people to
understand the idea of what is going on.  Most teachers I've heard of and
talked to, just say what I have already said to this point, and shy away
from actually requiring written code using recursion, or explaining code
using recursion to enable people to truly understand what is going on.

I seek to change those observed facts, by trying to fully explain this
example below, so people may be able to understand them sufficiently.
I hope this explanation could be the best people have ever seen, and
I *definitely* want e-mail and feedback on how well I do in explaining
the concepts of what is going on (via showing all variable changes at
all points, and order of execution of the code), because it is one of
the hardest concepts in programming that I have come across in
understanding.  (I will also ask for input like this in the part I write
later on readdressing pointers)

program example_of_recursion;

  function factorial(a: longint): longint;
      c: longint;
{1}   c := 1;  
{2}   if a > 1 then {
{3}     c := a * factorial(a-1);
{4}   factorial := c;

to explain the path of logic in this program in calling the function
factorial with regard to the variables, the biggest problem, I think,
with understanding recursion -- if you don't understand the main body
of the program, something is definitely wrong with you! :>  Line #'s
are placed in {}'s above this paragraph, and below this paragraph.

   { call to factorial }
   {1} a = 4             c = 1
   {2} 4 > 1 = true
   {3} c = 4 * factorial(3);
      { call to factorial }
      {1} a = 3             c = 1
      {2} 3 > 1 = true
      {3} c = 3 * factorial(2);
         { call to factorial }
         {1} a = 2             c = 1
         {2} 2 > 1 = true
         {3} c = 2 * factorial(1);
            { call to factorial }
            {1} a = 1             c = 1
            {2} 1 > 1 = false (so the chain of calls ends...)
            {3} skipped because 2 is false.
            {4} c = 1; factorial function is 1.
            { end call to factorial }
         {4} c = (2 * 1) = 2; factorial function is 2.
         { end call to factorial }
      {4} c = (3 * 2) = 6; factorial function is 6.
      { end call to factorial }
   {4} c = (4 * 6) = 24; factorial function is 24.
   { **FINAL** termination of factorial -- return of value 24 }

If you check the code, the final value is correct.  4! = 24.  Basically,
with the layout I used, you can see especially also, why memory (stack
space allocated for procedure and functions specifically) runs out
quick, and why I say to keep the parameters and local variables for that
matter to a minimum....

Recursion can be used in procedures, as well as functions, for any
repetitive action.  They are just like the function call above, which
recursed when or until an action became true.

To be able to extend for example, the part 8 dir clone to list and search
for files (list all files in all dirs), we would need to add another
boolean variable to get permission to run through all dirs encountered.
We can re-call the directory list procedure with a regulating if variable
of it being a directory.

For more practice (I won't post the solution for these ones), you may wish
to do this.  As another practice, you may wish to try and recode an integer
power function (the "simplistic" power function) that I included in my
solution to part 7's programming problem to use recursion.

Practice Programming Problem #13
Code a program in Pascal and entirely Pascal that will make a catalog of
the additive size of all files in all dirs on a drive specified on the
command-line to a text file named FILESLST.TXT.

Sample output
c:\>sizelist c:

Drive: C
C:                       131,123
C:\DOS                 5,231,131
...                     ...
C:\UTIL                3,212,985
C:\UTIL\ACAD             131,123

1) The additive end of the listing (under the ='s) is the total size of
the files on the drive.  You may use the function offered by the DOS
unit to check yourself in your addition.
2) The spacing is not exact above.  Make it look SIMILIAR to what I have
above, but make it reasonably attractive...
3) Use a forward.
4) Be sure to check to be sure the drive is specified EXACTLY like above.
5) Please use recursion for going through the drive (actually, recursion
is probably the best way to do this).  But, be sure you put the directories
in the order listed above.
6) Sizes of subdirectories are not counted in the size of a main directory.
7) Be sure to error-check the command-line.

Next Time
We will discuss the functions of the CRT unit.  Be sure to write comments,
encouragements, problems, errors, to [email protected]

                       Turbo Pascal for DOS Tutorial
                           by Glenn Grotzinger
                          Part 14: The CRT unit.
                copyright (c) 1995-96 by Glenn Grotzinger

Hello.  Here is a solution from last time...

{$M 65520,0,655360}
program part13; uses dos;

    drive, s: string;
    drivesize: longint;
    thefile: text;

  function number(n: longint): string; forward;

  procedure listdirsizes(filedir: string);
      size: longint;
      f: string;
      dirinfo: searchrec;
      size := 0;
      f := filedir + '\*.*';
      findfirst(f, $27, dirinfo); { allfiles - VolumeID - directory }
      while doserror = 0 do
          size := size + dirinfo.size;
      writeln(thefile, filedir, number(size):50-length(filedir));
      drivesize := drivesize + size;
      findfirst(f, $10, dirinfo);
      while doserror = 0 do
         { for some reason, the findfirst above doesn't work right. }
         { Unable to determine exactly why...:<                     }
          if (dirinfo.attr = $10) and ( <> '.')
             and ( <> '..') then
              s := filedir + '\' +;

  function number;
      i, j: integer;
      s, r: string;
      m: integer;
      str(n, s);
      r := '';
      i := length(s);
      if i > 3 then
          while i > 3 do
              for j := i downto i-2 do
                r := s[j] + r;
              r := ',' + r;
              i := i - 3;
          for j := i downto 1 do
            r := s[j] + r;
          number := r;
        number := s;

  procedure writehelp;
      writeln('Include a drive like this: c:');

    assign(thefile, 'FILESLST.TXT');
    if paramcount <> 1 then
    drive := paramstr(1);
    if (length(drive) <> 2) or (drive[2] <> ':') then
    drive[1] := upcase(drive[1]);
    writeln(thefile, 'Drive: ', drive[1]);
    writeln(thefile, '===============':50);
    writeln(thefile, number(drivesize):50);

In this part, we will try to discuss the complete usage of the CRT unit.
The CRT unit will allow with text different things, such as PC speaker
control, control of the screen for screen clears, positioning of the
cursor, changing the color of text and background, and so forth.

Basically, when the uses crt, or wincrt, is used, direct writes to the
screen are initiated, and can not be redirected to text file.

Boolean variables.
There is a list of boolean control variables accessible through CRT.

CheckBreak.  It is true by default. If it's false, CTRL+BREAK will be

CheckEOF.  It is true by default.  If it's false, CTRL+Z when pressed
will not represent an EOF in the program.

CheckSnow.  Enables and disables "snow checking" on CGA adapters.

DirectVideo.  Enables and disables direct video writes.

Keypressed.  Will return true if a key has been pressed.  Usable in a
loop for doing something until the user stops it.  A function like this
is similarly used for example in screen savers to stop the display.
Here is a small example to see how it acts.

program the_key_pressed; uses crt;
    until keypressed;

This program should write ascii character #254 to the screen repeatedly
until the user presses a key.

Screen Manipulation Commands
Clreol;  Clears to the end of the line for the currently active window
from the current cursor position.  The concept of "currently active
window" will be explained later.

Clrscr;  Clears the screen.  Already used and demonstrated.

Delline;  Deletes the current line containing the cursor.

Insline;  Inserts line at current position.

PC Speaker Sounds and Machine Delay
These are the basics of controlling the PC Speaker to make sounds.
The first thing is that there is an ASCII character #7 which will cause
a standard DOS PC Speaker beep.

Then there are some pascal procedures which will offer much more control.
You will have to experiment with it using different numbers in the delay
and sound value.

Delay(milliseconds: word); will delay the program for approximately the
number time in Milliseconds.
Sound(hertz: word); will start up the PC speaker at a specified frequency.
NoSound; will stop the PC speaker.

Here is an example of use of the PC speaker through TP.

program test_of_pc_speaker; uses crt;
Basically, we're emitting a sound of 1500 Hz, for a time of .5 seconds.

Note: The delay procedure can be used anytime to "freeze" up the system
for a specified period of time, say if we want to delay between writes.   

Screen Positioning
These is a command to control positioning of writing on the screen.

gotoxy(x, y); is a procedure that will position the cursor at the point
defined by x, y.  The standard defintion of the screen positioning is
a cartesian system like this for the standard video mode (on most systems
when you boot them up).

(1, 1)(2,1)----------------------------------------------...  (x, 1)
(1, 2)
(1, 4)
(1, y)

x and y is defined by the current video mode, which is settable.  We will
discuss how to set the current video mode.   The standard video mode at
bootup is 80x25.

wherex is the current x position of the cursor, while wherey is the current
position of the y variable.  gotoxy(wherex, wherey+1) is valid as an
example, and will move the cursor DIRECTLY down one unit, irreguardless
of where it is (as long as x and y are valid, most of these commands are
inactive if they get bad data)

The view window can also actually be changed.  The window function can
be used for this purpose.  The window is measured by the coordinate of
the upper left hand corner of the window (x1, y1), and the lower right
hand corner of the window(x2, y2).  The syntax if we call window is
window(x1,y1,x2,y2);  This is one procedure that you really need to
experiment with to see its effects.  As a note, most, if not all of the
screen writing commands are affected by this window...

Changing Text Color, Appearance, and Mode
These are functions and procedures which control the appearance of text.

HighVideo; will select high intensity characters.
LowVideo; will select low intensity characters.
NormVideo; will select the original intensity at startup.

Textcolor(color); and Textbackground(color); are used to control the
foreground and background color respectively.  Foreground colors
work from 0-15 and the background colors are from 0-8.  Blinking
foreground text adds 128 to the value.  A list of text color constants
which are equated with these numbers can be found on page 192 of the
TP programmer's reference.

LastMode is a word variable which will hold the current video mode that
the system was in when the program started execution.

Changing text mode actually involves changing what x and y as was originally
stated in the description of gotoxy.  textmode(modeconstant); actually
does this.  As with everything in this part, you should experiment with
all of the commands and procedures defined, because you can't exactly
be showed the effects of these commands in this tutorial.  A list of
mode constants can be found on page 26 of the TP programmer's reference.

Here is a sample program which demonstrates several of the functions and
procedures listed, and gives us an idea of what can be done with
manipulation of text windows, colors, and so forth.  Be sure to run this
one, and experiment with changing some of the values and actions to see
what happens.

program demo_of_fun_text_manipulation; uses crt;
    oldx, oldy: byte;
    { save original position of cursor so we can return there later }
    oldx := wherex;
    oldy := wherey;

    clrscr; { blue background now b/c blue when clrscr was called }
    textcolor(LightCyan + blink);
    write('Hello, how are we doing today?');
    gotoxy(oldx, oldy);
Reading Characters from the Keyboard
You may have been wondering, how to directly read things from the keyboard.
The CRT offers a function called readkey, which is a parameterless function
that returns a character.  Unlike read or readln, it does not require a
CR to initiate.  You may see this by replacing a character read from the
keyboard somewhere in your program with something like this:

                answer := readkey;

You will notice that you did not have to press ENTER to move on.  Also,
you will notice that the character didn't echo.  Very useful for password

The next advantage of readkey is that it can be used to read about every
key or key combination on the keyboard.  For this tutorial, we will discuss
simply the keys that can be accessed via one keypress using a basic standard
to be discussed in the next paragraph.

Extended keys, like the function keys, or the arrow keypad may be read
using readkey.  These keys generate a #0 character as their first character
followed by an extended scan code.  These vary depending upon which key
is depressed.  To read extended keys, we would have to use a structure
such as this:

            char := readkey;
            if char = #0 then
              char := readkey;

Most of the one press keys on the keyboard may be detected using this
method.  Examples of those keys which can't be detected using the
method described by the snippet of code above are the shift keys,
capslock, ScrollLock, NumLock, F11, F12, the CTRL keys, and the ALT
keys.  There are other methods available for those keys and key comb-
inations.  I recommend as practice that you set up a reader program that
will tell you what the extended character codes are for each of the
extended keys (any key not on the basic keyboard).

Practice Programming Problem #14
Create a program in Pascal and entirely Pascal that will do the following:

1) Using normal text mode, set up a screen that has a green background
all the way around the screen as a frame of 4 units in size all the way
around.  Inside of that, place a red frame of 1 unit all the way around.

2) Inside all of the frames described in part 1, set up a title that
states "Keypress Detector" on the first line available below the red
frame, in an high-intensity light cyan.  Be sure this title is NEVER
overwritten by scrolling text.

3) The "Keypress Detector" title should be on a black background.  Also,
the text which shows what keys we have pressed are should be a dull
lightblue on a black background.  The exception to the last statment is
that any standard key identity in the statement should be in a bright
green, and any extended key identity in the statment should be in a
bright red.  Key identity will be evident as I give a sample.

(properly inside the "frame")

Keypress Detector (press ESC 5 times to terminate)
You pressed the F1 key.
You pressed the SPACE key.
You pressed the LEFT key.
You pressed the Backspace key.

Note: Some of the keys on the keyboard are singular control characters.
Look at your ASCII chart.  For example, the TAB key is actually ASCII
character #9, and character #9 should be recognized as such...

F1, SPACE, LEFT, and BACKSPACE are the key identities as I described

4) Make sure that in order to terminate the program, the ESC key gets
pressed 5 times, not consecutively (be sure to indicate the ESC key has
been pressed each time).  Upon termination, the entire screen should
visibly scroll up until none of what we wrote is visible anymore (clue:
use delay).

Next Time
We will discuss the usage of the QuickSort and ShellSort algorithms.
Please by all means send comments to [email protected]

                       Turbo Pascal for DOS Tutorial
                            by Glenn Grotzinger
                   Part 15: Concepts of Sorting Arrays
                copyright (c) 1995-96 by Glenn Grotzinger

Here is a solution from last time...keep in mind to be as efficient as
possible in coding...I figure that many will try simply writing those frames

program part14; uses crt;

    esccount: byte;
    chr: char;

  procedure setitup;
      { green border of 4 units }
      window(5,5, 76, 21);

      { red border of one unit }

      { set up black background }

      { write keypress statement }
      writeln('Keypress Detector (Press ESC 5 times to quit)');

      { preserve Keypress detector title. }

  function status(char1: char;var esccount: byte): string;
      char2: char;
      if char1 = #0 then
          char2 := readkey;
          case char2 of
            #59: status := 'F1';
            #60: status := 'F2';
            #61: status := 'F3';
            #62: status := 'F4';
            #63: status := 'F5';
            #64: status := 'F6';
            #65: status := 'F7';
            #66: status := 'F8';
            #67: status := 'F9';
            #68: status := 'F10';
            #82: status := 'Insert';
            #71: status := 'Home';
            #73: status := 'PageUp';
            #81: status := 'PageDown';
            #83: status := 'Delete';
            #79: status := 'End';
            #77: status := 'Right';
            #72: status := 'Up';
            #80: status := 'Down';
            #75: status := 'Left';
        case char1 of
          #8: status := 'Backspace';
          #9: status := 'TAB';
          #13: status := 'ENTER';
          #27: begin
                 status := 'ESC';
                 inc(esccount, 1);
                 { the 1 is not required here, but the inc and dec
                   functions can be used with values greater than
                   one like this in this case }
          #32: status := 'SPACE';
          status := char1;

  procedure endit;
      i: byte;
      for i := 1 to 25 do

    esccount := 0;

    while esccount <> 5 do
        chr := readkey;
        write('You pressed the ');
        write(status(chr, esccount));
        writeln(' key.');

Now, we will discuss the use of sorting with regards to arrays into a
particular order.  Sometimes we may need numbers, such as dates in
chronological order, or a list of names in alphabetical order.

Swapping Units
The integral part of a sorting routine is a unit swap.  As a rule, we
MUST have a temporary variable, because a simple assign set will not work.

For example, to swap the contents in variables a and b, we need a temporary
variable we will call temp.  Then code such as what is below will do...

temp := b;
b := a;
a := temp;

The swap should ideally be performed with the smallest units possible,
based on the sorting key.  The idea of a sorting key will be explained
later.  You may end up using pointers to get it to move the smallest
amount of data, which will be explained in a future part.  The less
amount of data the computer can move, the better.

The BubbleSort (or the brute force sort)
Basically, in this sorting method, each and every item in the array is
compared each and every other item in the array.  This is a largely
inefficient method for sorting items, but easy to code, and useful for
small amounts of data.

program example_of_bubblesort;

    thearray: array[1..20] of integer;
    temp: integer;
    i, j: integer;

    { generate numbers for array and write them as unsorted }
    write('The unsorted array: ');
    for i := 1 to 20 do
        thearray[i] := random(50) + 1;
        write(thearray[i], ' ');

    { the bubblesort. }
    for i := 1 to 20 do
      for j := i+1 to 20 do
        if thearray[i] > thearray[j] then { compare and swap }
            temp := thearray[i];
            thearray[i] := thearray[j];
            thearray[j] := temp;

    write('The sorted array: ');
    for i := 1 to 20 do
      write(thearray[i], ' ');

As it is a purely iterative solution, you should have no exact trouble
seeing what is going on.  But to further another point as to exactly
how it works, and why it is so inefficient, we will sort a sample set
of numbers manually according to this algorithm to see what is going on.

1       3       2       5       4

We will start out with the following short description of what is going
on within the two for loops for 5 values of data:

1) i = 1; j = 2; Position1 = 1; Position2 = 3; 1 > 3 = false;
2) i = 1; j = 3; Position1 = 1; Position3 = 2; 1 > 2 = false;
3) i = 1; j = 4; Position1 = 1; Position4 = 5; 1 > 5 = false;
4) i = 1; j = 5; Position1 = 1; Position5 = 4; 1 > 4 = false;

5) i = 2; j = 3; Position2 = 3; Position3 = 2; 3 > 2 = true; we swap the
two our resultant array is...

1       2       3       5       4

6) i = 2; j = 4; Position2 = 2; Position4 = 5; 2 > 5 = false;
7) i = 2; j = 5; Position2 = 2; Position5 = 4; 2 > 4 = false;

8) i = 3; j = 4; Position3 = 3; Position4 = 5; 3 > 5 = false;
9) i = 3; j = 5; Position3 = 3; Position5 = 4; 3 > 4 = false;

10) i = 4; j = 5; Position4 = 5; Position5 = 4; 5 > 4 = true; we swap
the two the resultant array is...

1       2       3       4       5

11) Effective termination of loop;

As we can see, we took 10 steps in the algorithm to swap 2 elements of
the array in order to sort it.  Basically, this is a very inefficient
algorithm in comparison to other types that are available, as we are
considering portions of the array that are already sorted.  As a note,
to sort the items in descending order instead of ascending order, change
the comparison between the two positions i and j of the array from > to <.

This is a much faster recursive solution for sorting than the bubblesort.
It makes use of a pivot marker, which moves according to what exactly is
contained in the array.  It also will make use of a "divide and conquer"
approach.  Here is a short example...

program example_of_quicksort;

    thearray: array[1..20] of integer;
    i, j, PIVOT, t: integer;

  procedure quicksort(L, R: integer);
      if L < R then
          i := L + 1;
          j := R;
          PIVOT := thearray[L];

            while thearray[i] <= PIVOT do inc(i);
            while thearray[j] > PIVOT do dec(j);
            if i < j then
                t := thearray[i];
                thearray[i] := thearray[j];
                thearray[j] := t;
          until i > j;

          thearray[L] := thearray[j];
          thearray[j] := PIVOT;

          quicksort(L, j-1);
          quicksort(i, R);

    write('The unsorted array is: ');
    for i := 1 to 20 do
        thearray[i] := random(50) + 1;
        write(thearray[i], ' ');

    quicksort(1, 20);

    write('The sorted array is: ');
    for i := 1 to 20 do
      write(thearray[i], ' ');

This is a recursive solution, so it will be a little different.  Let's
start with the same number sequence we had above and see how quicksort
works...keep in mind that quicksort is about as inefficient as bubble-
sort with smaller sets...Once you get into larger sets, quicksort beats
bubblesort hands down -- it more intelligently seeks the proper array
units to swap.

1       3       2       5       4

1) 1 < 5 = true so continue.... i = 2; j = 5; PIVOT = 4;
   3 <= 4 = true so i = 3; 2 <= 4 = true so i = 4;
   5 <= 4 = false so quit;

   4 > 5 so quit;
   4 < 5 = true, so swap values.  The resulting swap is...

1       3       2       4       5

   4 > 5 = false so continue on repeat loop...

   i = 4; j = 5; PIVOT = 4; 4 <= 4 = true so i = 5;
   5 <= 4 = false so quit;

   5 > 4 = true so j = 4; 4 > 4 = false so quit;

   5 > 4 = true so quit repeat loop.

   j = 4; i = 5; 4 is left side. 4th element is 4.

2) Quicksort called for left side of 1, and right side of 3.  Then quicksort
for left side of 5, and right side of 5. Essentially, we keep cutting the
array in half based by where the pivot lands.  We will process the quicksort
for the left side as 2a) and the quicksort for the right side as 2b).

2a) Our number set for this instance of quicksort is:

1       3       2

    1 < 3 = true so continue.

    i = 2; j = 3; PIVOT = 2; 3 <= 2 = false so quit;
    i = 2; j = 3; PIVOT = 2; 2 > 2 = false so quit;

    2 < 3 = true so swap values...the resulting swap is...

1       2       3

    2 > 3 = false so quit repeat loop.

    Quicksort called twice for left side of 1, 2 and 2,3.  The results of
    both of these sorts end up being false, so they will terminate

2b) 5 < 5 = false so terminate quicksort.

As we can see, the algorithm sets itself up so it ignores portions of the
array that are already sorted.  For larger arrays, it will provide a great
performance boost as we are ignoring the parts of the array that happen
to be sorted by using this particular algorithm.

The version of quicksort pictured sorts in ascending order.  To make it
sort in descending order, change the two while loops to read the following:

while thearray[i] <= PIVOT do inc(i);
while thearray[j] > PIVOT do dec(j);

This is a non-recursive sort that performs close in performance to quicksort.
We can follow what is going on, so I will just simply write an example of
the use of the shellsort, and describe how to change it to sort in descending
order instead of ascending order...

program example_of_shellsort;

    thearray: array[1..20] of integer;
    i: integer;

  procedure shellsort(n: integer);
      m = 3;   { total number of sort passes }
      i: array[1..m] of integer;
      j, k, p, s, t, incr: integer;
      i[m] := 1;
      for j := m - 1 downto 1 do i[j] := 2 * i[j];
      for j := 1 to m do
          incr := i[j];
          for k := 1 to incr do
              s := incr + k;
              while s <= n do
                  p := s;
                  t := thearray[p];
                  thearray[k-incr] := t;
                  while t < thearray[p-incr] do
                      thearray[p] := thearray[p-incr];
                      dec(p, incr);
                  thearray[p] := t;
                  inc(s, incr);

    write('The unsorted array is: ');
    for i := 1 to 20 do
        thearray[i] := random(50) + 1;
        write(thearray[i], ' ');

    shellsort(20);  { 20 is high end of array }

    write('The sorted array is: ');
    for i := 1 to 20 do
      write(thearray[i], ' ');
To get it to sort in ascending order instead of descending order, change the

while t < a[p-inc] do


while t > a[p-inc] do

You should practice using these sorting methods.  They stay basically as
indicated for any use, except for changing the identities of the item
type in the array we sort.  For strings, we can alphabetize them by using
the strings in the sorting routine for ascending order.  Look at the ASCII
chart...It sees characters as numbers... a < b < c al...  With
strings, be sure to sort them case insensitively, especially, if we
alphabetize a list or something like that....

Next Time
We will cover methods of searching an array for data.  send comments to
[email protected]

                       Turbo Pascal for DOS Tutorial
                            by Glenn Grotzinger
                      Part 16: Searching an Array
                copyright(c) 1995-96 by Glenn Grotzinger

There was no defined problem in part 15, so we will start.

Sequential Array Search
This method for searching arrays for items is much like the bubblesort
is for sorting arrays.  It works well for small arrays, but for larger
arrays, it is very inefficient, if we are merely interested in the
existence of an item in the array.  Basically, it's a very simple
proposition to set up a sequential array search.

program example_of_sequential_search;

    a: array[1..15] of integer;
    i, number: integer;
    found: boolean;

    found := false;

    write('The array is: ');
    for i := 1 to 15 do
        a[i] := random(10) + 1;
        write(a[i], ' ');

    writeln('Enter a number, and we shall see if it is in the array');

    i := 1;
    while i <= 15 do
        if a[i] = number then
            writeln(number, ' was found at position ', i);
            { i := 15; can be done to break the loop on the first
              encounter of the one if we are interested in just
              whether the number exists in the array }
            found := true;
    if not found then
      writeln(number, ' was not found in the array.');

Binary Search
The other type of algorithm we will discuss is the binary search.  It is
to searching an array what the quicksort is to sorting an array.  Basically,
what it will do is keep halfing the array...

This method of array search has a prerequisite of having the data sorted.
Basically, we probably would want to sort data anyway to make it in
a readily presentable format for the user, so this doesn't necessarily
matter  (searches and sorts are products of data processing programs
normally, anyway).

program example_of_binary_search;

    first = 1;
    last = 15;
    a: array[first..last] of integer;
    i, number: integer;
    found: boolean;
    location: integer;

  procedure bsearch(var number, location: integer;
                      lowend, highend: integer; var found: boolean);
      midpoint: integer;
      if lowend > highend then
        found := false
          midpoint := (lowend + highend) div 2;
          if number = a[midpoint] then
              found := true;
              location := midpoint;
          else if number < a[midpoint] then
            bsearch(number, location, lowend, midpoint-1, found)
          else if number > a[midpoint] then
            bsearch(number, location, midpoint+1, highend, found);

    found := false;

    write('The array is: ');
    for i := 1 to 15 do
        { to insure we have sorted data }
        a[i] := 3*i;
        write(a[i], ' ');

    writeln('Enter a number, and we shall see if it is in the array');

    bsearch(number, location, first, last, found);

    if found then
      writeln(number, ' was found at position ', location)
      writeln(number, ' was not found. ');


As you may be able to pick out of this, it is possible to write an iterative
version of the bsearch procedure.  With sorting the array, though, this one
is a little better than simply going through the array one by one, but it
still has it's drawback of having to actually sort the information.

Another method available for use, which we will not discuss here is called
hashing, which is a very complex matter.

What should you use?  The serial search I described originally could be
very good, for a limited number of searches on a small array size.

If it's not good to do the serial search, and you needed to sort the data
anyway, the binary search is best.

Another method you may see as possible to use will be covered later, called
the binary search tree.  The cost in that approach will be basically building
the tree initially, then traversing it.  The tree is built based on specific
rules, which we can exploit to cause a search.

Practice Programming Problem #16
Randomly generate 1000 numbers from 1-10000 into an array.
Then generate another 500 numbers from 1-10000.
If there is a number from the second set of numbers that happens
to be in the first set of numbers, write out to the file named
LOCATION.TXT something such as:

5322 was found in position 532.

Only indicate the first instance of the number you encounter.

Next Time
We will cover some basic concepts of the use of pointers.  Please
write any comments you have to [email protected]  As I look at
my formatted version, so far there has been 116 pages written of
this tutorial, from part 1, not counting this one.  As I look through
my line count figures, this one is the smallest. (interesting facts,

I will ask for feedback on how I do with regards to any of the pointer
related texts coming up.  Please do that.  Thank you.

                        Turbo Pascal for DOS Tutorial
                            by Glenn Grotzinger
                     Part 17: Primary Concepts of Pointers
                   copyright(c) 1995-96 by Glenn Grotzinger

Hello.  Here is a solution from last time...

program part16;

    a: array[1..1000] of integer;
    number: integer;
    c, i, j, PIVOT, t: integer;
    found: boolean;
    location: integer;
    outfile: text;

  procedure quicksort(L, R: integer);
    { nothing to say we couldn't sort the data, especially with the
      fact that we will be performing 500 search hits on this 1000
      unit array...the overhead of sorting the data will be of good
      benefit in the long run... }

      if L < R then
          i := L + 1;
          j := R;
          PIVOT := A[L];

            while a[i] <= PIVOT do inc(i);
            while a[j] > PIVOT do dec(j);
            if i < j then
                t := A[i];
                A[i] := a[j];
                A[j] := t;
          until i > j;

          a[l] := A[j];
          a[j] := PIVOT;

          quicksort(L, j-1);
          quicksort(i, R);

  procedure bsearch(number, lowend, highend: integer; var location: integer;
                    var found: boolean);
      midpoint: integer;
      if lowend > highend then
        found := false
          midpoint := (lowend + highend) div 2;
          if number = a[midpoint] then
              found := true;
              location := midpoint;
          else if number < a[midpoint] then
            bsearch(number, lowend, midpoint-1, location, found)
          else if number > a[midpoint] then
            bsearch(number, midpoint+1, highend, location, found);

    assign(outfile, 'LOCATION.TXT');

    for c := 1 to 1000 do
      a[c] := random(10000) + 1;

    quicksort(1, 1000);

    for c := 1 to 500 do
        number := random(10000) + 1;
        bsearch(number, 1, 1000, location, found);
        if found then
          writeln(outfile, c, ') ', number, ' was found at position ',
                  location, '.');



This will be the first of several parts on the use of pointers...This
part will contain the basic idea behind pointers and their use.  I
would like feedback from anyone about how well they felt they learned
from this tutorial, and the next two.  They all will be about pointers,
and I want to be sure I am doing OK in explaining them coherently. :>

The Concept of a Pointer
A pointer is simply what it implies by the name.  Something that points.
A pointer is a 4 byte double word that stores the segment and offset in
memory where the variable is located.

We have referred to before a method of getting more than 64K in a data
structure, in the array section.  This is a way.  A pointer is placed on
what is referred to as the heap.  The heap is the remainder of conventional
memory not used by the program itself, the data stack, or any TSRs that
may be on the system at any one time.  The data stack is where variables
are normally allocated directly by name and can be a maximum of 64K in size.

Why would we want to use pointers?  Well, we have already eluded to one
reason, to get around the 64K data limit.  Another reason is to dynamically
allocate memory space.  Basically, any variables declared by name(s) in a
program is allocated at the initial start-up of a program, and is allocated
until the end of the program.  For a lengthy program with a lot of variables,
we may not want all variables to be allocated for something at the start.
Therefore, we can, with pointers, allocate and deallocate memory anytime
we wish in a program.

If we want to save memory in the data stack, 1 4-byte pointer compared to,
say, an array of 1000 integers would be preferable.  That is one function
of a pointer.

In using pointers, we must remember that they are not direct variables,
but addresses of variables.  Let us look at the first example program
below to see exactly what I mean by the last statement.  We also will
see how to properly create variable space address pointers in memory,
address that variable space, and then deallocate it.

program pointers_one;

    strptr = ^string;
    a, b, c: strptr;

    a^ := 'Turbo Pascal';
    b^ := 'is fun!!!!!!';

    writeln('a is now "', a^, '".');
    writeln('b is now "', b^, '".');

    c^ := a^;
    a^ := b^;
    b^ := c^;

    writeln('a is now "', a^, '".');
    writeln('b is now "', b^, '".');


We see the use of several use conventions in the program above.  We have
three pointers listed to be pointers of strings.  (as a note, any data
type that will eventually be a pointer should NORMALLY be defined under
the type declaration to facilitate the use of pointers in procedures.
We will see this later.)

In the declaration to allocate the pointers, we used the ^ sign first, then
the datatype.  We say that the pointer will eventually point to a string

To address each of the pointer variables, we used the ^ sign after the
particular variable name.  The ^ sign references the variable that the
pointer is pointing to, and NOT the pointer itself.  You may see this
by doing a debug trace for a, b, and c on that program.  You will
clearly see that the pointer's contents is NOT what the variable's
contents is...

The next thing to note is the assignment statements.  For a pointer
variable, we are not assigning direct values, but MOVING THE POINTER
address.  When we say a^ := c^, we are moving the pointer c to point
to the value that a points to.  **WE ARE NOT DIRECTLY CHANGING THE

The last thing to note is the strategically placed uses of the functions
new() and dispose().  These are new functions to us that we will learn
and make use of in the system unit.  The new() function will allocate
the space for the pointer listed in the variable on the heap.  The
dispose() function will deallocate the space for the pointer on the heap.
As you can see, we created and removed the variables as we needed them.

The next thing you were probably wondering with pointers is how to get
a pointer to not point to anything.  Make it address a reserved word
called nil.  If I want pointer p to not point to anything, then I would

        p := nil;

Whatever you do, do not set a pointer to nil before you dispose of the
pointer.  The system will lose track of the variable on the heap, and
therefore is LOST to the system.  Keep this issue in mind as we do
our work and examples with pointers.

Look up getmem() and freemem().

Another issue that wasn't covered above is how we determine how much
memory is in the heap.  Use the functions memavail and maxavail for
the purpose of finding out how much memory you can allocate and if the
pointer data structure is big enough, BE SURE TO CHECK AND SEE IF YOU
HAVE THE MEMORY TO ALLOCATE before you allocate it for a big structure.

Procedure or Function Pointers
A procedure or function may also be addressed as a pointer.  For that,
as you will see in the example, we can use a declaration for a variable
exactly like the procedure or function declaration.  It is very useful
to minimize code usage, and make things multi-functional.  This sheds
some light into the inner workings of the write command, as was wondered
in c.l.p.b. a little while ago...

program pointer_two;
    compfunc = function(a, b: integer):integer;
    compute: compfunc;
    first, second: integer;
    choice: char;

  function compadd(a, b: integer): integer;
      compadd := a + b;
  function compsub(a, b: integer): integer;
      compsub := a - b;
  function compmult(a, b: integer): integer;
      compmult := a * b;
  function compdiv(a, b: integer): integer;
      compdiv := a div b;


    @compute := nil;
    write('Enter a first number: ');
    write('Enter a second number: ');
    write('Enter +, -, *, or / as a operation: ');
    case choice of
      '+': compute := compadd;
      '-': compute := compsub;
      '*': compute := compmult;
      '/': compute := compdiv;
      writeln('Enter a correct option.');
    compute(first, second);
    writeln(first, ' ', choice, ' ',second,' = ',compute(first, second));

As you can see, we are assigning a particular function to the function
named compute.  After that, compute performs the specified function of
the function that we just assigned to it.  The rule is that those FAR
declarations MUST be present.  As well, you can see how to refer to the
procedure pointer (as @procedure).  As long as the declarations are
similar for all the functions, we can readdress functions/procedures
using a variable like we did above.

We see that we can probably cut down on a lot of code by using this
method if we had several different ways of doing things, with large
amounts of code for each thing.  For example, if we wanted to sort
something based on user input dependent upon many different methods
or types (say sort increasing by name, decreasing by name, increasing
on size, by date,, we can set up our swapping procedure to be
the way the compute procedure is above.

Now, we will lead into a special usage of a pointered procedure called

This is a special defined procedure pointer in Pascal that will determine
the procedures that will be performed upon an exit, NO MATTER what happens,
EVEN IF THERE IS A RUN-TIME ERROR.  Here is your method on being able to
catch and log those run-time errors either to the screen, or to an errors
log.  The primary usage of an exit procedure, though, is to perform any
maintenance that needs to be done upon termination that may not get done
upon an abnormal termination, such as closing files. The way things work
are basically the same as above...

Here is a short example...I will force a run-time error in this program,
so we can see that the end gets run anyway.  As you may see, exitproc
is defined as a pointered procedure when we work with it.  The rest is
basically documented.

program pointer_three;

    exitsave: procedure;
    afile: text;

  {$F+}    { must be far }
  procedure myexit; {must be parameterless}
      if exitcode <> 0 then
        writeln('There was a problem.');
      writeln('We are exiting...');

    @exitsave := exitproc;  { saving the original exit procedure }
    exitproc := @myexit;    { set to new exit procedure }

    { I am placing a couple of error situations in here so you can
      experiment with what is going on...try it without errors too! }

    {1 - here is a standard file not found RTE.}
    assign(afile, '()()()().!!!');

    {2 - here is a division by zero.}
    { writeln(3 / 0); }

    exitproc := @exitsave;  { set the original exit procedure back }

Here is enough material that I believe that is enough to digest for right
now on use of pointers.  Practice allocating and using pointers very
heavily, as we will get into more advanced issues of using pointers in the
next two parts...Remember that ANY structure can be placed as a pointer,
except for files, and remember to define types for all pointers.

Practice Programming Problem #17
Randomly generate 15000 numbers from 1-25000 into an array.
Then generate another 10000 numbers from 1-25000.
If there is a number from the second set of numbers that happens to be in
the first set of numbers, write out to the file named LOCAT2.TXT something
such as:

13131 was found at position 12000.

Only indicate the first instance of the number you encounter.
You may wish to write a "redrawing bar" for a process indicator.

Note: Be sure with the allocation for those first 15000 numbers that you
keep in line with the topic of this tutorial.

Next Time
We will cover the concept of linked lists, or chained lists.  Practice
very heavily the idea of pointers, because you will need use of them
again in the next section.  E-mail [email protected] with your comments.

                       Turbo Pascal for DOS Tutorial
                            by Glenn Grotzinger
          Part 18: Chained or Linked lists, the linked list sort
                copyright(c) 1995-96 by Glenn Grotzinger

Here is a solution from last time...

{$M 64000,0,655360}
program part17; uses crt;

    arptr = ^atype;
    atype = array[1..15000] of integer;
    a: arptr;
    number: integer;
    c, i, j, PIVOT, t: integer;
    found: boolean;
    location: integer;
    outfile: text;

  procedure quicksort(L, R: integer);
    { nothing to say we couldn't sort the data... }
      if wherex = 79 then
          gotoxy(1, wherey);

      if L < R then
          i := L + 1;
          j := R;
          PIVOT := A^[L];

            while a^[i] <= PIVOT do inc(i);
            while a^[j] > PIVOT do dec(j);
            if i < j then
                t := A^[i];
                A^[i] := a^[j];
                A^[j] := t;
          until i > j;

          a^[l] := A^[j];
          a^[j] := PIVOT;

          quicksort(L, j-1);
          quicksort(i, R);

  procedure bsearch(number, lowend, highend: integer; var found: boolean);
      midpoint: integer;
      if lowend > highend then
        found := false
          midpoint := (lowend + highend) div 2;
          if number = a^[midpoint] then
              found := true;
              location := midpoint;
          else if number < a^[midpoint] then
            bsearch(number, lowend, midpoint-1, found)
          else if number > a^[midpoint] then
            bsearch(number, midpoint+1, highend, found);

    if maxavail - sizeof(a) > 0 then
        writeln('Out of memory!');
    assign(outfile, 'LOCAT2.TXT');

    for c := 1 to 15000 do
      a^[c] := random(25000) + 1;

    quicksort(1, 15000);

    for c := 1 to 15000 do
        number := random(25000) + 1;
        bsearch(number, 1, 15000, found);
        if found then
          writeln(outfile, c, ') ', number, ' was found at position ',
                  location, '.');


Now we will discuss the idea of the linked list or chained list.  Basically,
there are 4 types of linked lists that we can discuss, the singularly linked
linear list (SLLL), singularly linked circular list (SLCL), doubly linked
linear list (DLLL), and the doubly linked circular list (DLCL).  I will
use the abbreviations I placed in the parentheses for any future references
to these data types.

These are basically the preferred ways to allocate large amounts of storage
space on the heap.  All linked lists are basically describable in the best
analogy as a chain.  They are record structures which have pointers that
interconnect them.  The method that these structures are connected
distinguish the type of linked list it is.  We will look at an example of
the use of SLLL's, observe the advantages of linked lists through what we
do with the example, and study the things to look out for on all 4 types.

SLLL Concepts
This is the simplest type, in sense.  It involves a record structure which
is connected in a chain in a linear fashion with one link forward to the
next link.  A sample record structure for an SLLL follows below.

  nodeptr = ^node;
  nodetype = record
    ourinfo: integer;
    nextnode: nodeptr;

An example of an SLLL conceptually is something like this:


As we remember from earlier, nil is what we set a pointer to, if we do not
want it to point to anything...In the use of an SLLL, it is also what we
will use to indicate whether we are at the end of the list or not.

We will see from the slll_demo program that there are several specialized
issues we need to take into consideration with working with any linked or
chained list.

1) We need to make a special case to insert or delete a node from the start
of the list.
2) We need to be sure to maintain nil pointers in any insert or delete
As a result, you will cause what is called a heap leak.  This is where
the pointer loses track of where the data it points to is stored.  Logically,
looking at the model above, if we disconnect one of the pointers, represented
by the arrows, we lose track of the rest of the list, or chain.  Work with
a temporary pointer for each linked list function. What I say by recursion,
you will see later in this document.
4) With regards to the example I wrote, I tried to demonstrate any and all
functions that we might need with an SLLL.
5) Pointers that point at nil CAN NOT be deallocated.  You will see this
fact manifest itself by the memory statement at the end being 8 bytes
smaller than it was at the start.

SLLLs Demonstrated
Here is the SLLL_DEMO program.  I will place stop notes in there, as well
as comments.

Advantages of linked lists: We will see here, that the data is not static,
we can place data independently at different positions WITHOUT shuffling
data, remove data in the same fashion, and definitely are capable of handling
*A LOT* more data than 64KB, since we only have a 4 byte stub in that area.

Take a good look at this program and seek to understand EXACTLY how it works.
As you will remember from last time, a direct assignment to a pointer is
making it point to something while a reference to the pointer (via ^) changes
the contents of the data it points to.  I recommend you draw out what is
going on via pencil and paper, using boxes to represent the records and
arrows representing pointers.  It will help you VASTLY to do this in
understanding what is going on.  Remember a pointer can only point to one
thing at a time.  When you look at this program, seek to answer the
following questions taking any "housekeeping functions" out of consideration:

1) Why is the insert code different than the build code?
2) Why is the delete code different than the cleanup code?
3) On the "divisible by 8" search, why is the NEXT node being searched for
this and not the current node?
4) Why did I say to always use a temporary variable? Or Why does the
statement p := list; always occur?
5) Observe methods of moving through the list.

program slll_demo; uses crt;

  { Program written by Glenn Grotzinger for a demonstration of all of the
    functions/uses of a linked list that the author could think of.
    the variable used throughout called p, and sometimes t, are temporary

    Note: This probably isn't completely optimized. }

    nodeptr = ^nodetype;
    nodetype = record
      thenum: integer;
      nextnode: nodeptr;

    list: nodeptr;

  procedure buildlist(var list: nodeptr);

    { This procedure builds up the list for us. }

      p: nodeptr;
      i: integer;
      new(list);       { This is creating the head of the list }
      list^.thenum := 1;
      p := list;       { Set and move temporary pointer }
      for i := 2 to 18 do
          p^.nextnode^.thenum := i;
          p := p^.nextnode;
         { p := p^.nextnode advances the temporary pointer to the next node.
           this is a memory storage address or pointer and not a direct
           variable, referencing a node of the linked list.  Anything, in
           reality does not become a pointer until the new function is used. }
      p^.nextnode := nil;   { set last pointer to nothing }

  procedure writelist(list: nodeptr);

    { This procedure serves the function of writing out the list for us
      to the screen when called }

      p: nodeptr;
      p := list;
      while p <> nil do { while we're not at the end of the list }
          p := p^.nextnode;

  procedure insert(var list: nodeptr);

    { This procedure will serve to insert a node into the list either in
      the middle or the end.  The logic can be done for the head of the
      list. }

      p: nodeptr;
      p^.thenum := 20;
      p^.nextnode := list;
      if p^.nextnode = nil then  { maintenance of the end of list marker }
        p^.nextnode^.nextnode := nil;
      list := p;

  procedure delete(var list: nodeptr);
    { This is a procedure that will serve to delete a node from the list,
      and consequently deallocate the memory.  It is possible to remove
      the node without deallocating the memory, though it is a bad practice
      to do so }

      p, t: nodeptr;
      p := list;
      t := p^.nextnode^.nextnode;
      p^.nextnode := t;

  procedure insertbythree(var list: nodeptr);

    { This procedure moves through the linked list and determines where
      the new nodes needs to be inserted, then calls the insert function
      written before }

      p: nodeptr;
      i: integer;
      p := list;
      i := 1;
      while p <> nil do
          p := p^.nextnode;
          if i mod 3 = 0 then

  procedure findanddispose(var list: nodeptr);

    { This procedure finds and disposes the first number in the list
      divisible by 8. }

      p, t: nodeptr;

      p := list;
      while (p^.nextnode <> nil) and (p^.nextnode^.thenum mod 8 <> 0) do
        p := p^.nextnode;

  procedure cleanup(var list: nodeptr);

    { This procedure removes the list from memory. }

      p, t: nodeptr;
      p := list;
      while p <> nil do
          t := p^.nextnode;
          p := t;

    writeln('Free memory available: ', memavail, ' bytes.');
    writeln('Free memory available: ', memavail, ' bytes.');
    write('The list is: ');
    writeln('Now we will insert a 20 in every third position');
    writeln('Free memory available: ', memavail, ' bytes.');
    write('The list is: ');
    write('Now we will search for and take the first # divisible by 8 ');
    writeln('out of the list.');
    writeln('Free memory available: ', memavail, ' bytes.');
    write('The list is: ');
    writeln('Now we will be good little programmers and clean up our list. :)');
    writeln('Free memory available: ', memavail, ' bytes.');

Hopefully, you can go through here, and follow the logic (actually, you will
need to do that successfully to understand what is going on).

Linked lists are very modular in nature.  Therefore, a good understanding
of what is going on here is essential.  As a proof to be able to think
through the logic of using pointers in linked structures, write out and
logically explain on your sheet of paper how to perform the following
(I will not provide a solution for this one -- code it up yourself and
try and figure it out -- this is an important skill you will need to start
developing as a programmer at this point, since you're at a pretty advanced
level now (:))), and then code it up as a program:

1) Create 1000 nodes in an SLLL that consists of integers numbered from 1
to 1000.
2) Print this list to a text file.
3) Reverse the direction of the linked list.  By doing this, I mean, instead
of the linked list looking like this conceptually:


make it look like this:



4) Print the new list to the same text file.  Instead of it being from 1
to 1000 as the first printing was, it should be from 1000 to 1.

CLUE: Think about how many temporary variables you will need (1?  Maybe 2?,
Possibly 3?).

SLCL Concepts
This is essentially the same as an SLLL, except instead of being nil at the
end of the list, the end of the list points at the beginning of the list.
This type uses the same record format as the SLLL.

Conceptually, an SLCL looks like this:

   ^                               |
   |                               V
  NODE                            NODE
   ^                               |
   |                               V

As before with the SLLL, one of these nodes would be denoted as the head
of the list.

The only consideration that would differ that I could note, is that you
would use a comparison of your temporary pointer with your head pointer
in order to move through the list.

So instead of while p <> nil, it would be while p <> list in the above
example to make it that way.

DLLL Concepts
This type of linked list uses a different kind of record format.  It looks
like this:

  nodeptr = ^node;
  node = record
    ourinfo: integer;
    lastnode, nextnode: nodeptr;

Conceptually, a DLLL looks like this:

                nil <--    <--    <--    <--
                       NODE   NODE   NODE   NODE
                           -->    -->    -->    --> nil

If you study up your logic from previously, this one shouldn't be too awfully
bad to figure out.

DLCL Concepts
This type of list uses the same record format as the DLLL.  The conceptual
diagram looks much like the SLCL diagram, but with double links much like
the DLLL diagram, instead of single links.

Final Thoughts on Linked Lists
I did not provide examples of SLCLs, DLLLs, and DLCLs , merely for space,
and also by the fact that I have never had reason to use the other three
types.  I am presenting their basics here, merely for people's study, and
learning.  Using the knowledge learned from doing those logic problems
presented in the SLLLs, and references (though I find those to be VERY
sparse on the types other than SLLL), you should be able to come up with
the code to do the other three types pretty readily.  Always remember that
the best thing to do to work out the logic of what to do with the pointers
is to draw it out using the boxes and the arrows.

An Idea on Sorting Data Using Linked Lists
Here, I will now present the reasoning behind my "recursion" statement,
plus an idea of sorting data upon build.  I don't have any stats on
this being more or less efficient than using one of the array sorts,
but if you can't use an array to sort in memory, you would have to resort
to this.

Here is a little code/pseudocode (with a bent toward sorting names
alphabetically)  For purposes of the recursion, we will call the
function INSERT:

  GET DATA TO PUT INTO NODE (read data from file, or elsewhere)
        p^.nextnode := LIST;
        if p^.nextnode = nil then
          p^.nextnode^.nextnode = nil;
        list := p;
        GET INFO.
        DO NOT PUT NODE HERE (boolean variable set to false).

This general code does work for a high capacity.  I have used this code 
to sort a maximum of an 86KB list of 150 char items per line alphabetically
using memory alone, no disk swapping.

For practice: Do things as I have suggested throughout this document.
No real practice problem.

Next Time
We will talk about binary trees.  Be sure to send comments to
[email protected]  I will say again that I apologize for the long
period of time it took to get this out.  I also apologize for the length
this document has become.  Be sure to please comment on how this
part is.
                        Turbo Pascal for DOS Tutorial
                            by Glenn Grotzinger
                           Part 19: Binary Trees
                 copyright (c) 1995-96 by Glenn Grotzinger

OK.  I stated no real problem that needed solving earlier, so we will start
getting into some new material.  This is an extension to part 18, in a small
way, since it involves pointered structures similar to the linked lists,
but the procedures for handling them are much different, so I am covering
them in a seperate section.

What is a Binary Tree?
A binary tree is a set of nodes with 2 possible paths for each node, left
or right.  Hence, a binary tree node would look something like this:

  nodeptr = ^node;
  node = record
    ourinfo: integer;
    left, right: nodeptr;                                            

Conceptually (my best low ASCII rendering, trust me!), a binary tree would
look something like this.  We can see from below that the possible paths of
those two directions are any numerous.  As with the linked lists, nil ends
the path (knowing, it is probably garbled, but....).

                   _________/  \_________
                  /                      \
               NODE                      NODE
           ____/  \____              ____/  \____
          /            \            /            \
       NODE            nil        nil             NODE
   ____/  \____                               ____/  \____
  /            \                             /            \
nil           nil                         NODE           nil
                                      ____/  \____
                                     /            \
                                  NODE            nil
                              ____/  \____
                             /            \
                           nil           nil

Rules of a Binary Tree
Now that we see what the basic idea of a binary tree is, we will now study
the rules for it.  The basic rules of a binary tree is the following for
any node in a binary tree commonly used for the purpose that they are used
for a lot of times -- as a binary search tree or BST.  Any program code
in this section will make use of BSTs...:

        1) values smaller than the value currently stored in a questioned
           node goes to the left.
        2) values larger than the value currently stored in a questioned
           node goes the right.
        3) values which are equal to other values in the tree are not placed
           into the tree.

Let us see this by manually examining how we might build a small binary tree.

Example of a Binary Tree Manual Build
Let us examine how we would build a binary tree for a set of values and
how a program would traverse the tree with a very short example.

1) Let us start examining a tree build with a value set data of 27, 54,
   32, 18, and 5.
   a) 27 is naturally the first or root node of the tree.  There is no
      issue there.
   b) 54 > 27 so 54 would go to the right.  Our resultant tree looks now
      like this:

   c) 32 > 27.  So we would move to the right.  Now we see 54 as the main
      node  32 < 54, so we would go to the left.  So the resultant tree
      after this addition looks like this:


   d) 18 < 27.  So we would move to the left.  So our tree looks like this:

                                /  \
                              18    54

   e) 5 < 27.  So we would move to the left.  Now we see 18 as the current
      node.  5 < 18.  Then we would move to the left again.  So the final
      binary tree for those 5 values looks like this:

                                /  \
                              18    54
                             /     /
                           5     32

This is a reasonable binary tree structure.  Any paths not used are
set to nil in the process.  We should see now how binary trees are

2) Now lets study traversal of the tree.  It would depend on if we
visit the actual node first or last.  (I will give a detailed explanation
of that last statement later.)  Normally, as a standard, it's good to
use the left side first.  Therefore, lets look at the tree built in #1.

The order the numbers would come up in is 5-18-27-32-54 on a basis that we
handle or visit the head node of the tree last in moving through the binary
tree. If you remember the discussion of the array binary search, we had to
sort the data to accomplish it.  Here it is taken care of already for us.

Basic Idea of Programming for Binary Trees
You may have figured out already from our preliminary discussions, that
recursion is a _major_ staple of binary tree programming (read ALWAYS use
it!).  Note that a binary tree can be divided up into smaller and smaller
trees, hence our ability to use recursion.  Any functions involved almost
always ends up using some order of the following pseudocode, assuming a
name of PROCEDURE for the procedure:

PERFORM ACTION ON ROOT NODE (the top node of the tree)

We will see this idea be paramount in all the code we use.  If you have
noticed in your previous work about recursion, there is always a control
statement present.  Here, it will always be searching for the nil nodes.
*ALWAYS* remember to replace the nil nodes if they come up....binary
search trees are the perferred method generally, for setting up searches.

program binary_tree_example;

    nodeptr = ^node;
    node = record
      somedata: integer;
      left, right: nodeptr;

    tree: nodeptr;
    afile: text;
    i: integer;

  procedure deletetree(var tree: nodeptr);
     { This procedure is a function designed to remove a binary tree
       from memory }

      { Given the background information, and other procedures as examples,
        you should be able to come up with this one }

  procedure inserttree(anumber: integer; var tree: nodeptr);
     { This procedure is a function designed to create a binary tree
       in memory by inserting a node in the proper position in the tree }

      if tree = nil then
          tree^.somedata := anumber;
          tree^.left := nil;
          tree^.right := nil;
          if anumber > tree^.somedata then
            inserttree(anumber, tree^.right);
          if anumber < tree^.somedata then
            inserttree(anumber, tree^.left);

  procedure searchthrutree(anumber: integer; tree: nodeptr);
    { This procedure is designed to search through a binary tree for a
      particular value given in the variable anumber. }
      { Given the background information and other procedures as examples,
        you should be able to come up with this one. }

  procedure writeouttree(tree: nodeptr);
    { This procedure outputs a binary tree. }
      if tree <> nil then
          write(afile, tree^.somedata, ',');

    tree := nil;
    { the last statement is required for the nil markers set up via the
      BST procedures listed above }

    assign(afile, 'BTEST.TXT');
    writeln(memavail, ' bytes available before tree creation.');
    for i := 1 to 10 do
      inserttree(random(5)+1, tree);  {This creates the tree. }
    writeln(memavail, ' bytes available after tree created.');
    for i := 1 to 10 do
      searchthrutree(random(5)+1, tree);
    writeln(afile, 'I will now output the binary tree for the example.');
    writeln('Now deleting tree out of memory');
    writeln(memavail, ' bytes available after tree deletion.');

This is a whole program for working with a binary tree.  I purposely left
out 2 of the procedures, because the background information I gave earlier,
plus pointer logic you should have learned from part 18 should allow you to
logically come up with it.  Do like I recommended in part 18 and draw it
out.  Example code is often not available for many problems, but the
information _is_ there to be used.  To program it is a needed skill to be
able to figure out things on your own given some information.  Code should
not be handed out readily, IMO, unless there is no other way.

Practice Programming Problem #19
One noted thing I have noticed with the binary search tree method especially,
is that searches can be done very quickly with any kind of data -- working
with arrays have a small way of being kludgy.  Here we will see an example.

You need to create a binary data file named SOMEWRDS.DAT which should
contain 50 non-duplicated words, randomly defined (do not alphabetize them)
out of 70 total words in the file.

Allow users to type words into the keyboard, until they type the word
"quit".  Case-sensitivity should not matter in any usage of words.
Write out to the user whether the word they typed was found in your
database or not.

Do not be concerned with reporting of run-time errors.  Do be concerned,
though, with the removal of the final binary-tree you use.

Also for the delete code you write for binary trees, explain why your
code does what it is supposed to do.

1) Exercise prudent practice as programmers.  In the event it was not
covered, accuracy is a programmer's #1 concern, followed by efficiency
in speed, memory usage, and disk usage.
2) This program should be friendly to the user, letting the user know
precisely what is going on in all cases, even if a common error occurs,
and cleaning up anything that may be of a problematic nature from those
errors.  You should have enough experience from previous parts to be
able to determine what those errors are you would commonly encounter here.
3) The goal here for this program is to create a high-quality application.

Final Statements for this Part
I know many people may wish to someday release their creations to the public,
or make programs for themselves or other's general usage. This is why I am
being so vague now about describing programs, and will be from now on.
Creativity and neatness is one of the major qualities that need to be
developed by those who program.

Also for all who program: The way to solve the problem is not always handed
to you.  You have to identify problems that are encountered, sit down and
solve them.  I estimate I'm probably 4-6 parts away from completing this
whole tutorial, and 2 parts away from completing the parts of the tutorial
regarding conventional Pascal programming.  By now, most, if not all the
tools have been presented for you to do this, and not need me to give you
clues, and point you in the direction of what to do to solve the problem.
Most if not all problems you will encounter in normal programming practice
anywhere are normally presented in the form that this one is in -- a
statement of what the program is expected to do.

In short, I feel those who have been new programmers that have been following
my tutorial from part 1 are about ready for the *REAL* world of programming.

Next Time
I will cover several short random topics, which some of which may be
suggested by those who e-mail me.  I know I will cover hooking interrupts,
calling an interrupt procedure, and linking an OBJ into Pascal code,
incorporating assembler code into pascal code, and including inline
statements in pascal program code.  Is there any more people would like to
see?  E-mail me.  Also, I haven't gotten any comments from anyone
about how I am doing.  Is anyone interested?  Email [email protected]

Note: I am sorry if I offended anyone by the "Final Statements for This
Part" section.  I am apologizing in advance if I come off too harsh.
                        Turbo Pascal for DOS Tutorial
                             by Glenn Grotzinger
                       Part 20: Miscallaneous Topics
                     copyright (c) by Glenn Grotzinger

Hello.  Here is a solution for the programming problem that was given in
part 19 with a little background explanation.

This was the program written to generate the binary data file for use with
the actual word checker.  A text file was written to facilitate reading in
the data, one word per line, to ease implementation.

program part19a;

    infile: text;
    outfile: file;
    datastring: string[14];
    assign(infile, 'SOMEWRDS.TXT');
    assign(outfile, 'SOMEWRDS.DAT');
    rewrite(outfile, 1);

    readln(infile, datastring);
    while not eof(infile) do
        blockwrite(outfile, datastring, sizeof(datastring));
        readln(infile, datastring);
    blockwrite(outfile, datastring, sizeof(datastring));


This is the actual word checker.  It is made so the words typed into the
keyboard do not have to be case sensitive with reference to the datafile.
All prevalent errors have error-checking code.

program part19b;

    strtype = string[14];
    nodeptr = ^node;
    node = record
      str: strtype;
      left, right: nodeptr;

    tree: nodeptr;
    datastring: strtype;
    infile: file;

  procedure opendatafile(var infile: file; afile: string);
      assign(infile, afile);
      {$I-}reset(infile, 1);{$I+}
      if IOResult <> 0 then
          writeln('Terminating.  ', afile, ' does not exist!');

  function upstr(instr: strtype):strtype;
      i: byte;
      tempstr: strtype;
      tempstr := '';
      for i := 1 to length(instr) do
        tempstr := tempstr + upcase(instr[i]);
      upstr := tempstr;

  procedure memoryerror;
      writeln('Out of memory.');

  procedure deletetree(var tree: nodeptr);
      if tree <> nil then

  procedure inserttree(datastring: strtype; var tree: nodeptr);
      if tree = nil then
          if memavail - sizeof(tree) > 0 then
          tree^.str := datastring;
          tree^.left := nil;
          tree^.right := nil;
          if upstr(datastring) > tree^.str then
            inserttree(upstr(datastring), tree^.right);
          if upstr(datastring) < tree^.str then
            inserttree(upstr(datastring), tree^.left);

  procedure buildsearch(var infile: file; var tree: nodeptr);
      datastring: strtype;
      blockread(infile, datastring, sizeof(datastring));
      while not eof(infile) do
          inserttree(datastring, tree);
          blockread(infile, datastring, sizeof(datastring));
      inserttree(datastring, tree);

  procedure searchtree(datastring: strtype; tree: nodeptr);
      if tree = nil then
        writeln(datastring, ' was not found in the data file.')
          if datastring = tree^.str then
            writeln(datastring, ' was found in the data file.')
              if datastring > tree^.str then
                searchtree(datastring, tree^.right);
              if datastring < tree^.str then
                searchtree(datastring, tree^.left);

  procedure promptwrite;
      writeln('Type QUIT to terminate.');
      writeln('Type a word, and we''ll see if it''s in the database.');

    writeln('Database checker.');
    opendatafile(infile, 'SOMEWRDS.DAT');
    buildsearch(infile, tree);
    while (upstr(datastring) <> 'QUIT') do
        searchtree(upstr(datastring), tree);
The written explanation of the delete code that was written in the example,
to completely remove the BST from memory...

We know that in a pointer-linked structure if we remove a node that has
links assigned to it, that we will cause a heap leak and render the rest
of the data structure unremovable from memory.  In a BST, that case becomes
when both right and left branches of the node are nil.  We have to use
recursion, evidently, since the problem of deleting the entire binary tree
comes down to many smaller problems of deleting nodes with both sides
assigned to nil, basically.  So basically, if we observe those lines of
code, recursion occurs until both the left and right sides of nodes become
nil, and then a dispose occurs.  In working back up in returning from the
procedure calls, the tree is essentially disposed of from the bottom up to
the root node, in reverse from the way the insert code illustration created

Contents for Part 20
Since this part contains many random varied topics, a table of contents is
in order....

        A) Linking OBJ code into Pascal programs.
        B) Including ASM or inline statements in Pascal programs.
        C) Hooking an interrupt in pascal programs.
        D) Calling an interrupt procedure.
        E) Conditional Compiler Compilation.

Linking OBJ code into Pascal programs
This is a short program, which describes the exact process, USING A C
program's function (the topic of using C OBJ's came up).  If you do
this with C OBJ, the code must not make any calls to any C libraries,
or make any calls to a DLL.

A description of what's going on...


int far pascal add2numbers(int num1, int num2)

    return (num1 + num2);

This is a C header file with a small defined function in it.  You MUST
define it using either void far pascal for a procedure or <datatype> far
pascal for a function.  I describe a function in test1.h.  For those who
wonder, a header file in C functions much like a unit does in Pascal.

For an OBJ you intend to use, each and every function must be defined in
the resultant pascal program.  (I know it is not liked, and you SHOULD
NEVER normally post attachments in c.l.p.b.) With differences
between so many different C compilers and syntaxes that exist in the world,
I will post the OBJ file I got from the compiler I use, for purposes of
enabling others to test this.  I recommend you to cut and paste it before
you uu or xx encode it if you need to obtain it.

begin 644 test1.obj
[email protected]`D`!U1%4U0Q+D..B!\````;5$,X-B!";W)L86YD(%1U<F)O($,K*R`S+C`P
[email protected]/``#I=G%"(`=415-4,2Y#[email protected]&``#E`0``C(@5``#F!&YU;3($"@8`!&YU
M;3$$"@@`2(@&``#E`08`[email protected]@``#F!F%N<W=E<@0"_O\$;G5M,00*"``$;G5M
M,@0*[email protected]"DB`4``.<:`'*(!0``YQH`<[email protected]*``#N`0``[email protected]`4`&6(`P``Z8R(!0``
[email protected]$+?98H```%7U1%6%0$0T]$105?1$%4001$051!!%]"4U,#0E-3!D1'4D]5
M4-&8!P`H&@`"`P$9F`<`2```!`4!#[email protected]'`[email protected]```8'`0N:[email protected]`(_P+_`[email protected]``
M`0M!1$0R3E5-0D524P```#N("P``[email protected]````C!`4`[email protected]%``#A&!ABH!X``0``
[email protected]^P"BT8(`T8&B4;^BT;^ZP"+Y5W*!`"ZB!```[email protected]`!U1%4U0Q+D-V<4(@
<3I03```!`@````8`[email protected]`'``\`"``4`!B*`@``=```


program test2c;

  {$L TEST1.OBJ}
  function add2numbers(num1, num2: integer): integer;external;
    writeln(add2numbers(2, 3));

This is the pascal code using the OBJ.  Basically, the $F+ compiler
directive is required.  Then observe the usage of the $L compiler
directive.  The $L specifies the OBJ file name to link in.
Also note the use of the external; statement at the end of the function
declaration.  That specifies that the function is defined externally,
and not in any Pascal unit or defined in the program itself.  The
function or procedure declaration listed in the pascal program must
match EXACTLY with the one defined in the OBJ file.  It does in this

Including ASM or inline statements in Pascal
Pascal is a compiler, which translates the data into ASM, and then into
machine language.  Naturally, if it takes the data to ASM, it can use
straight assembler code as as well.  Take a look at the short procedure
mockup below.

procedure dothis; assembler;
   { assembler code goes in here }

asm is a keyword that may be used anywhere in code, which
tells the compiler to start looking for assembler code instead of pascal code.
The assembler keyword is one that can optionally be added to make the
compiler look for an asm keyword instead of the begin at a beginning of a
procedure or function.

An inline statement works much like a function call.  It makes it so
that machine language can be used in pascal code.  For example (the hex
codes are garbage I'm making up so don't use this in a real program),
an inline statement can look like this...


The codes can go on infinitely, and are separated by /'s...

There is so much specific information out there that is required to know
about computers that references are almost always needed.  Here are a few
of my recommendations.

   Ralf Brown's Interrupt List (INTERXXY.ZIP  XX being a version #, y
       being a through e. -- this is a big set of text files!)
       This is a listing of all known interrupt functions of a PC.

   The PC Game Programmer's Encyclopedia (PCGPE.ZIP)  This is a
       collection of text files...some hold tutorials about how to
       program in assembler and VGA, a lot of them hold specific
       information about how to program peripherals of your system.
       Another big download

   The SourceWare Archival Group collection (SWAG).  Very much known,
       this is a collection of pascal code which has been donated by
       programmers from all over the world that reference how to do
       various topics.  I recommend you use this code only as a reference
       for instruction and do not copy the code.  This is almost a 7.5MB

Hooking an Interrupt
An interrupt is a signal made from the computer to the CPU for many
functions that occur in the computer.  There are many different types
and numbers of interrupts.  There are two basic types, though, hardware
and software interrupts.

A interrupt is put in simpler terms is an "attention order" from some
component of the system.  In most common systems, there are 255 of these
available, both software and hardware.

Below is an example where we hook an interrupt.  Hooking an interrupt
involves the replacement and augmentation of the basic system interrupt
process with something else, or nothing at all (if we want to temporarily
disable a piece of hardware for a security program, for example).

What this program does is hook the interrupt that handles the internal
PC timer.  That function is augmented by a count that is added by the
procedure "count" listed below.  As a background, for the PC timer,
approximately 18.2 timer ticks occur every second, or 0.054945 seconds
per tick.  Ultimately, you can see by running this program, that all it
does is wait for you to press a key. 

program writehello; uses dos;

    intsave: procedure;
    counter: integer;

 procedure count; interrupt;
     inline($9C); { prods an interrupt }

   writeln('Press a key.');
   getintvec($1C, @intsave); { get current int. procedure. }
   setintvec($1C, @count);   { replace it with our procedure. }
   setintvec($1C, @intsave); { save the regular int. procedure back }
   writeln(counter, ' timer ticks have passed.');

We will now describe what is different here....

getintvec() is a TP DOS unit function which pulls the procedure currently
  defined on that interrupt.
setintvec() is a TP DOS unit function that resets the function defined by
  the interrupt.
The interrupt keyword must be placed on the procedure definition, and the
  procedure definition must be defined as far, like you see.

A word of note for a procedure defined as an interrupt...the original
interrupt procedure must be called within that interrupt procedure.  The
inline($9C) seems to also be a required element.

NOTE: Hooking an interrupt is essentially a dangerous proposition, to a
degree.  Be sure that you are careful and learn what happens on each
interrupt and know what the interrupt does before you try to hook it.
Doing this actually CHANGES the hardware behavior of your system.
If attempting to hook an interrupt causes the system to behave adversely,
STOP the program right there (reboot the system even), and do not attempt
the code again until you are aware of what exactly happened and what is the
right thing to do to accomplish what you want.  For example, I once tried
hooking a replacement procedure on the interrupt for the hard disk, and I
had garbled output from the directory commands until I rebooted....


Calling Software Interrupt Procedures
The system (hardware and operating system) has varied software interrupts
which may be called to perform certain functions in the system.  This is
how most of the file access works in the other functions of the DOS unit,
such as assign, reset, rewrite, and close; for example.

The record registers or tregisters are defined in the DOS unit or WinDOS
unit respectively. It looks like:

  registers = record
    case integer of
      0: (AX, BX, CX, DX, BP, SI, DI, DS, ES, Flags: Word);
      1: (AL, AH, BL, BH, CL, CH, DL, DH: byte);

This is considered a variant record, which may be used to discriminate
the latter part of the field if data dictates it.  If you are familiar
with the assembler language, you should recognize the fields from the

Here is a sample program, which holds an example of a software interrupt
call.  In this case, the program is calling function 9 (?) of interrupt
$21, which involves writing a string out via regular BIOS calls (this is
what TP does when it writes out a string).  Note the conversion of the
pascal string to a blocked array string.  A string must end with a "$"
when using this function.

program writetest; uses dos;

    strtype = array[0..255] of char;

  procedure writestring(astring: string);
      tstr: strtype;
      i: byte;
      regs: registers;
      i := 1;
        tstr[i] := astring[i];
      until i = length(astring);
      astring[i+1] := '$'; 

      Regs.Ah := 9;
      Regs.Ds := Seg(astring);
      Regs.Dx := Ofs(astring);
      intr($21, regs);  {call interrupt $21 using regs register set }

    writestring('Hello all!');

Amazingly, this program is 304 bytes smaller as an EXE than the
equivalent program  listed below written using exclusively the TP write
procedure...remember, the most efficient code is USUALLY the code with
the shortest # of lines, but not ALWAYS.

program writetest1;
    write('Hello all!');

This program also introduces a few new function calls.  Intr() is a
Pascal function in the DOS unit which executes a function call.  It is
called like the example program.

Memory addresses, as you may have already seen from following pointers
using the debugger, are set up in a 20-bit segment/offset manner (20-bit
memory = 1024KB addressable -- they did this for the original 8088's...).
Seg() shows the memory segment of any variable you place in there.
Ofs() shows the memory offset of any variable you place in there.
Addr() shows the complete memory address of any variable you place in

Consequently, we may also use the function MsDos() in this case to replace
the Intr() call above.  MsDos() is an Intr() call to interrupt $21 (the
DOS operating system control interrupt).

Conditional Compiler Compilation
Let us first define a few more compiler directives for your collection of
knowledge....defaults marked with ~

                + state                      - state
                --------                    ----------
$N               FPU on                     ~FPU off
$G               286 code gen. on           ~286 code gen. off
$E              ~FPU emulation on            FPU emulation off

These are compiler directives often used conditionally.  Conditional
compilation is essentially another language, independent of the actual
language code.  Identifiers may not be used from code in compiler
directives and vice versa.  Let's look at a few of the pertinent
constants that Pascal defines in relation to this....

MSDOS => boolean: indicates that the operating system is MS-DOS or PC-DOS
CPU87 => boolean: true if there is a math coprocessor (FPU) present.

The only construct you will typically see is an if then or if then else.

The compiler directives I listed above are most commonly used with this
type of situation.  There are additional floating point data types available
called comp, single, double, and extended; which are usable via the FPU
generally.  The definitions of these variables may be defined using the
conditional compiler directives....

The conditional compiler directives we need to know are:

{$IFDEF <name>}                                If item defined
{$IFNDEF <name> }                              if item not defined 
{$ELSE}                                        ELSE directive
{$IFOPT <compiler directive>}                  If compiler opt..
{$ENDIF}                                       END IF STATEMENT
{$DEFINE <name>}                               define a statement
{$UNDEF <name>}                                undefine a statement.

Functionally, these work and compile the source code if certain things are
true.  For example, {$IFOPT N+} <some code> {$ENDIF} in some code will make
it so the code represented by <some code> will only be used if N+ is defined.

Here is a short example program using conditional compiler directives....

program condcomptest;

    {$IFDEF CPU87}
      writeln('There is a math coprocessor in the system.');
      writeln('There is not a math coprocessor in the system.');
You will notice that only ONE writeln statement will execute itself.  The
dependence will be whether a FPU exists in your system.

Practice Programming Problem #20
Write a program which will count the number of keystrokes a user presses
in a period of 15 seconds.  Use proper programming interest in this
program, and be sure to be friendly to the user with this program.

Background information.
   Keyboard interrupt is $09.  Interrupt occurs 2 times for every
   key pressed.

Notes: Unfortunately, in all attempts I've made, to make it TERMINATE
exactly on 15 seconds, it would cause a nasty problem.  The program will
have to read in at least one key after termination.  Do not worry about

Next Time
We use the BGI.  Write comments to [email protected]

                       Turbo Pascal for DOS Tutorial
                           by Glenn Grotzinger
                         Part 21: Use of the BGI.
                 copyright (c) 1995-6 by Glenn Grotzinger

Here is a solution to the problem presented last time.

program part20; uses dos;

    oneminute = 273;  { # of timer ticks that happens in 15 seconds }

    saveint09, saveint1c: procedure;
    timecounter, keycounter: integer;
    achar: char;
    timeup: boolean;

  procedure counttime; interrupt;
      if timecounter = oneminute then
        timeup := true;

  procedure countkeys; interrupt;


    timeup := false;
    timecounter := 0;
    keycounter := 0;
    getintvec($09, @saveint09);
    getintvec($1C, @saveint1C);
    setintvec($09, @countkeys);
    setintvec($1C, @counttime);

    while timeup = false do

    setintvec($09, @saveint09);
    setintvec($1C, @saveint1C);
    writeln('There were ', keycounter div 2, ' keys pressed.');

BGI Intro
This part is basically about using the Borland Graphics Interface.  It is
not generally recommended to use it -- use assembler instead to make it a
lot quicker, and smaller.... but due to demand, and the ability to use the
BGI for basic graphics, we will talk about use of the BGI.  For heavy use
of graphics, assembler is indeed better....

BGI loader
Here, I will describe how to make the BGI files functionally more useable
than in their current state....Compile with looking for the external BGI
files is fine, but will run into an annoyance quick, especially with a
distributed is easier to have the EXE available with the BGI
files binded to it than to make a user keep track of the BGI files....

There is a utility available in TP called BINOBJ.  The BGI files are video
control files, while CHR files are font definition files.  They both are
usable in graphics as binary files...and that is what BINOBJ
converts binary files to OBJect files, which may be handled like described
before.  The proper usage of this utility is...

BINOBJ <binary file name> <object file name> <internal procedure name>

Here is a sample command-line.  To convert VESA16.BGI to an OBJ, do this:


Locate the BGI files that should be in your TP/BGI directory.
You will need to copy them off to a separate directory.

After you convert all BGI files like this, I recommend you write a unit
to link the BGI files in -- optionally, if you need fonts, you may write
a unit to link those in likewise.  Be sure to define your proper external
procedures like it needs to be done.  Make use of meaningful
names, as you will have to remember them to make use of the graphics set.
For example, I call my unit bgivideo; for each procedure, I put the first
8 chars of the name of the file, and then driver for the BGI files.  These
function names will be easy to remember when we need to use them.

Going into Graphics Mode
Now, hopefully you have your BGI video unit ready now, and sitting in
whatever directory you use to place your pascal code.  Now we will discuss
about how to go into graphics mode using the BGI.

Generally, going to graphics mode initially would require setting up the
BGI unit you created, and then performing an autodetect on the graphics,
followed by an init into graphics mode.

Let us look at some sample code...

program video_example_1;{1} uses graph, bgivideo;

  graphicsdriver, graphicsmode: integer;

procedure errormessage(driver: string);
    writeln('There was an error: ', grapherrormsg(graphresult), driver);

{2}if (registerbgidriver(@attdriver) < 0) then
   if (registerbgidriver(@cgadriver) < 0) then
   if (registerbgidriver(@egavgadriver) < 0) then
   if (registerbgidriver(@hercdriver) < 0) then
   if (registerbgidriver(@pc3270driver) < 0) then
     errormessage('PC 3270');

{3}detectgraph(graphicsdriver, graphicsmode);
   graphicsdriver := Detect;
{4}initgraph(graphicsdriver, graphicsmode, '');
{5}if GraphResult <> grOk then
      writeln('Video error.');

    putpixel(random(getmaxx), random(getmaxy), random(getmaxcolor));
   until keypressed;


This is basically a random pixel place system using the video mode in the
recommended auto-detect.  Let's go through a few of the features of the
code, which were marked by {}'s.

{1} As we see, the graph, and bgivideo units are used here.  The graph
unit is the basic function interface for the BGI system.  We are familiar
with bgivideo from earlier.

{2} RegisterBGIDriver() must be called for any and all realistic
possibilities. I recommend that only the ones listed really need to be
checked.  If the function is less than zero, then there is a problem.
The errormessage function holds a function called grapherrormsg() which
will output a direct error as to why things aren't working right.

{3} detectgraph detects the graphics card.  it takes integers represented
by graphicsdriver, and graphicsmode....graphicsdriver will hold the
recommended video type, and graphicsmode will hold the recommended video
mode (it can be changed).  This is why we registered all the
will use whichever one it needs, and ultimately, the program will work with
all video modes.

{4} initgraph() takes us into graphics mode.  It is called basically as
indicated in the program.  the third param '', is for when we load the
BGI files externally.  To do that, do not include BGIVIDEO and provide
a path to the BGI get a full auto-detect capability, just
make all the BGI files is easier in the long run to have
the BGI files combined in the exec.

{5} For almost any graphics function, a variable called graphresult is
changed.  There are graphics mode constants, which will be covered later,
which are in there representing different things.  grok is the one which
indicates that things are OK.

{6} This is the real meat of the procedure.  It keeps placing pixels of
random position and color on the screen until a key is pressed.  getmaxx
is the maximum screen position on the x axis.  getmaxy is the maximum screen
position on the y axis.  Graphics screens have the same kind of dimensional
setup as the crt graphics screens do.  The putpixel procedure takes a
x, y coordinate, then a color...getmaxcolor is a constant that holds the
maximum # of colors present in the current video mode.

{7} Closegraph is pretty much self-explanatory.  It shuts down graphics mode
and takes us back to text.

BGI Font Usage
The CHR files you saw, are font files, which have an ability to be used in
graphics programs....

These files can be converted to OBJs, and I recommend that you do so for
purposes of using the fonts.....

Here is a small example of loading and using fonts -- I'm not repeating
the video load code, so I will omit that....

program video_example; uses graph, bgivideo;

  graphicsdriver, graphicsmode: integer;
  i: integer;

{1} {$L GOTH.OBJ}
procedure Gothfont; external;

procedure errormessage(driver: string);
    writeln('There was an error: ', grapherrormsg(graphresult), driver);

  { This is the video load code }

{2}  if registerbgifont(@gothfont) < 0 then
      errormessage('Script font');

    i := 100;
{3}  settextstyle(DefaultFont, HorizDir, 1);
{4}  setcolor(blue);
{5}  outtextxy(20, i, 'This is the DEFAULT font.');
{6}  inc(i, textheight('T')+2);
{7}  cleardevice;
     settextstyle(Gothicfont, horizdir, 2);
     outtextxy(20, i, 'This is the GOTHIC font.');

This basically goes into graphic mode, and writes those two statements to
the screen.  We will go through the areas marked by {}'s..

{1} This is exactly like I described before.  This is how we load the CHR
file to not make it separate.  We do it exactly like the BGI files, and
set them up as external OBJ files.

{2} registerbgifont works exactly like registerbgidriver registers
the font into the program -- load fonts judiciously -- only if you need

{3} settextstyle() changes the font, direction, and size....things can
be written out either horizontally, or vertically.  Fonts letters are 8X8
pixels in it is also possible to zoom the fonts...the third
is the factor in which it may be done...a 2 in the third parameter makes the
letters 16X16 pixels, and so on and so forth.

{4} setcolor(blue) sets the foreground graphics draw color to blue.

{5} outtextxy() puts out a text statement at coordinate x, y for graphics

{6} textheight is a function which guages the height in pixels of a text
placed in the statement.

Font file names
DefaultFont; TriplexFont; SmallFont; SanSerifFont; GothicFont; represent
HorizDir; VertDir;

A VERY QUICK overview of commands available from BGI
Due to the volume of commands available, all of them can not be sufficiently
covered in the space of this document.  Most if not all of them are
straight-forward to use.  Look at page 185 of the Turbo Pascal language
guide for a list of all of the BGI commands.

IMO, Borland made the BGI very hard to use.  I have stumbled across many
things that looked like bugs in their system (I couldn't use their included
script font).  Beyond that, it works OK for light-duty graphics.  Anything
beyond that truly needs assembler.

There is enough knowledge here to set up and make use of BGI (not with-
standing the basic commands in that list -- for example, rectangle....
draws a rectangle....).

As another side note, you may have noticed that executables you create
using methods described here are large.  Get a program such as PKLITE,
or LZEXE, and compress it.  They compress down about 45-55%, in my
experience.  Graphics programs using BGI seem to characteristically
compile to be large.

Practice Programming Problem #21
Make a program which will successively place rectangles on the screen in
random colors.  Since it is hard to illustrate what I'm wanting, given
this medium, I will place the final executable, named part21.EXE in the
file at Garbo.   Cut and paste the document after you save this, please.
Here are the basic stats behind the program:

1) Squares are used.  use the rectangle() function.  it takes for arguments,
the coordinates of the upper left hand corner, and the lower right hand
2) Each square drawn successively is one pixel larger than the previous....
3) Continually draw the squares until the user presses a key.
4) The colors are randomly determined.

Look at the program to get an idea of what I am looking for.  Also please
send comments back to [email protected] if it happens to not work on your
system for no readily apparent reason.  I need to get an idea of how well
these sample video routines work.

Next Time
Things will be indeterminate.  I will be covering object-oriented program-
ming.  As I have not set down and figured out how many parts object-oriented
programming will take to cover, I do not know.

E-mail [email protected] and suggest anything that has to do with TP, which
I may have not covered.  If it sounds good, I will cover it!
                       Turbo Pascal for DOS Tutorial
                           by Glenn Grotzinger
                             Part 22: Finale
                 copyright(c) 1995-96 by Glenn Grotzinger

Here is a solution of the graphics problem from last time...

program part21; uses graph, bgivideo, crt;
                   { must be in this order }
  graphicsdriver, graphicsmode: integer;
  x1, x2, y1, y2: integer;

procedure errormessage(driver: string);
    writeln('There was an error: ', grapherrormsg(graphresult), driver);


  if (registerbgidriver(@attdriver) < 0) then
  if (registerbgidriver(@cgadriver) < 0) then
  if (registerbgidriver(@egavgadriver) < 0) then
  if (registerbgidriver(@hercdriver) < 0) then
  if (registerbgidriver(@pc3270driver) < 0) then
    errormessage('PC 3270');
  detectgraph(graphicsdriver, graphicsmode);
  graphicsdriver := Detect;
  initgraph(graphicsdriver, graphicsmode, '');
  if GraphResult <> grOk then
      writeln('Video error.');

    x1 := getmaxx div 2 - 15; x2 := getmaxx div 2 + 15;
    y1 := getmaxy div 2 - 15; y2 := getmaxy div 2 + 15;
    { center of screen is always (getmaxx div 2, getmaxy div 2) --
      look at geometric properties of a rectangle }
      rectangle(x1, y1, x2, y2);
      inc(x2, 1);inc(y2, 1);dec(x1, 1); dec(y1, 1);
    until (keypressed) or (x1 <= 0);
  until keypressed;



As you can see in this example, it's always good to have a good background
in analytical geometry to be able to do graphics well.  It is good for you
to find a mathematical reference and learn a few concepts of it, if you
do not already know something about it.

Ultimately, it was decided that object-oriented programming will not be
covered at this time for the tutorial.  I apologize.

Be sure to read license.txt!