You are on page 1of 150

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 statements. 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 dis files? A) Discussions of storage formats in memory and dis by Turbo Pascal. B) Interpretation of meanings of data presented on a commonly used format.

Part 12: Stac s; Queues A) Concepts and usage of stac s 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 eys on the eyboard. A) Changing colors on the screen (text mode). B) Wor ing with the PC spea er. 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 Quic Sort. 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 lin ed lists; the lin ed list sort. A) Concepts of SLLLs, DLLLs, SLCLs, DLCLs. B) Sorting data using a lin ed-list. Part 19: Descriptions of other types of pointer-lin ed structures; A) Concepts of a binary tree. B) Usage of a binary tree. Part 20: Lin ing assembler into Pascal code; special topics. A) Lin ing OBJ files into Pascal code. B) Hoo ing 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 loo ed 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, an d fully understand all the examples. Please wor 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 wee ly 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 loo 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 unit.

{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 bloc of code. {5} & {6} are some program commands. We will explain them later. {7} end; ends a program bloc . A few basic commands ==================== I will now discuss variable definitions.

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

to tell the

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. Comments. If you wish to ma e some text as a remar to what something does, use the { ey 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 wor 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 ma e that loo readable and normal.

program tutorial2; var first_number, second_number: integer; result: integer; begin

We will now loo

at an example of some of the stuff above.

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} end. 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). DIV and MOD 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 stic to use of the eyboard 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. read(a_number); 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. readln(a_number); This command does exactly as read, but produces a movement to a new line. write(a_number); This command writes the contents of a_number to the screen without a new line. writeln(a_number); 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; var some_text: string; begin write('Type some text and press ENTER when done: ');

readln(some_text); writeln('You just typed the following: ', some_text); end. 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 wor . The End of Part 1 ================= All of my tutorial parts will be formatted much li e 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 ma e 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 as 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 loo ing through this will be wasted. 2) Anyone who does now, 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 something is only gotten by practice. If you wish me to give your code from the practice programming problems in this tutorial a quic loo , send 'em to me at ggrotz@2sprint.net. Practice Programming Problem for Tutorial Part 1 ================================================ Write a Pascal program (and entirely Pascal) which will accept two integers from the eyboard, 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 luc ! And a solution to this problem will appear in part two! Next Time ========= Next time, we will discuss the use of decision-ma ing (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 ggrotz@2sprint.net. 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 ta en 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 loo . 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 wee s programming question: program part1; { This program accompanies part1. It is a program designed to ta e 2 numbers as input from the eyboard and perform an addition, multiplication, and subtraction on the number, writing the results out to the monitor. } var num1, num2: integer; begin { part1 } { ta e input for the 2 numbers } write('Please enter the 1st number: '); readln(num1); write('Please enter the 2nd number: '); readln(num2); 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 brea a command-line of code line this, as long as you

don't brea 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 li e 3.3333333333E-01 for the first statement, 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 loo li e this: 0000000001111111 1234567890123456 0.333 0.250 We see that the first number is ic ing 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. Quic note ========== We use ' mar s to enclose statements. What if we want to write one? Pascal recognizes that if you place two of them together inside those ' mar s, 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 li e this: write(#225); 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 ma e 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 example. program tutorial4; var first_number, second_number: integer; begin writeln('Type an integer in, please.'); readln(first_number); writeln('Type another integer in, please.'); readln(second_number); writeln; if first_number > second_number then writeln(first_number, ' is greater than ', second_number, '.') else writeln(first_number, ' is not greater than ', second_number, '.'); end. 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; var one, two: integer; option: char; begin writeln('Enter an integer.'); readln(one); writeln('Enter another integer.'); readln(two); writeln('Use a mathematical symbol to indicate what you want to do'); writeln('with these two numbers.'); readln(option); if option = '+' then begin writeln(one, ' + ', two, ' = ', one + two, '.'); writeln('See, I can add.'); end else

if option = '-' then writeln(one, ' - ', two, ' = ', one - two, '.') else if option = '*' then writeln(one, ' * ', two, ' = ', one * two, '.') else 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. } else writeln('Use +, -, *, or / as your operator. Try again.'); end. We can do pretty much as many nested ifs as we can, though we need to minimize it as much as possible. Also, eep 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, eep 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 ma e 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 lo o 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 can also be a character. THE INDEX VARIABLE VALUE IS NOT ADDRESSABLE 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, eep 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 li e i or j or whatever... Do ma e your variables DESCRIPTIVE, though. That's a good programming practice... program tutorial6;

var i: integer; begin writeln('I''m going to write something 10 times.'); for i := 1 to 10 do writeln('something', '(Time #':15, i, ')'); end. Type this one in and run it, and you'll see what it does. It writes the first statement above, then writes... something something ... something (Time #1) (Time #2) (Time #10)

We can see a good, real use for this, and eep in mind that groups of statements can be executed in a for loop, just li e 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 wor . 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. Ma e as a goal for the problems I present at the end to follow all the guidelines, and try to ma e 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 loo at practice problem code, may be sent to ggrotz@2sprint.net. Always eep in mind that there are many different solutions possible to one problem, and don't be upset if your program didn't loo EXACTLY li e mine. If the output is ACCURATE and your output loo s exactly li e 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 eyboard 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 li e that). Example output for this program after execution should be something li e this on your screen (if you see garbage around the loo up 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 li e 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: 9 10 Good luc , and a solution to this problem will appear in part 3! Remember to document your code (sort of li e I did with the part 1 answer), and try to eep 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 code.) 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 ggrotz@2sprint.net. 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 pic up. I am planning on ta ing 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 ggrotz@2sprint.net. An example of a solution of last wee 's programming problem: program part2; { This program accompanies part 2. It is a program designed to ta e a number representing the dimensions of a multiplication table from the user, and then write a multiplication table out to the screen. } var dimension: integer; i, j: integer; begin { part 2 } {ta e input for the dimension} write('What dimension number do you want for a table? '); readln(dimension); writeln; { if dimension not greater than 15, process table } if dimension <= 15 then begin

{ write top line of table } write(' '); for i := 1 to dimension do write(i:4); writeln; { write top rule line } write(#201:3); for i := 1 to (dimension * 4) do write(#205); writeln; { write rest of table } for i := 1 to dimension do begin if i < 10 then write(' '); write(i,#186); for j := 1 to dimension do write(i*j :4); writeln; end; end { else it's greater than 15, write an error message } else writeln('You must give a dimension that''s 15 or lower.'); end. 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 ggrotz@2sprint.net. 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 ma e using a while loop instead of a for loop. program tutorial7; var i: integer; begin writeln('I''m going to write something 10 times.'); i := 1; while i <= 10 do begin writeln('something', '(Time #':15, i, ')'); i := i + 1; end; end. 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 brea s off and the program ends. Li e the IF statements, we can place multiple conditions by connecting them li e 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 functioning when a condition is true. We see the idea of this again, when we reconstruct program tutorial6 using the repeat loop... program tutorial8; var i: integer; begin writeln('I''m going to write something 10 times.'); i := 1; repeat writeln('something', '(Time #':15, i, ')'); i := i + 1; until i > 10; end. 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 CIRCUMSTANCES IS THE INDEX VARIABLE FOR A FOR LOOP TO BE CHANGED WHILE 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 li e this Continue until user wants to quit. is the basic logic.). CASE statement ============== As we saw in tutorial5, we may want to ma e 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 ma es 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 (li e option below) must be a character or an integer... program tutorial9; var one, two: integer; option: char; begin writeln('Enter an integer.'); readln(one); writeln('Enter another integer.'); readln(two); writeln('Use a mathematical symbol to indicate what you want to do'); writeln('with these two numbers.'); readln(option); case option of

'+': begin { sub procedures can be coded as well } writeln(one, ' + ', two, ' = ', one + two, '.'); writeln('See, I can add.'); end '-': 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. } 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 } var number: integer; i: integer; begin randomize; { 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 li e random(20) only does } end. 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 as ed 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 illustrations 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 now, 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 homewor , or coding up a simple program that can figure up your chec boo ... it can be done with what you now now, believe it or not. Thin about what you can do with your new found nowledge. Do not try and overextend yourself trying to do things you have no nowledge 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. Loo 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 eyboard 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 as ing whether they want to play again (Give them a Y/N prompt.). Be sure to ta e 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 stuc , 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 thin ing of a number between 1 and 100. What is it? 50 It's higher. (5 guesses remaining) 75 It's lower. (4 guesses remaining) 63 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 thin ing 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 ggrotz@2sprint.net. 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 wee 's programming problem: program part3; { This program enables a user to play a guessing game. Range: 1-100 Guesses: 6 } var choice: char; randnum, number_given, attempts: integer; begin randomize; { start random # generator } while upcase(choice) <> 'N' do { while we want to play } begin randnum := random(100) + 1; { generate random number for game } attempts := 6; { set initial number of attempts } number_given := 1000; { stuff number_given so while ic s in } writeln('I''m thin ing 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 } begin readln(number_given); if number_given = randnum then writeln('Congratulations! You got the number right!') else begin attempts := attempts - 1; if attempts = 0 then writeln('Sorry, you ran out of choices. ', ' The number I was thin ing of is ', randnum, '.') else if number_given > randnum then writeln('It''s lower. (', attempts, ' guesses remaining)') else writeln('It''s higher. (', attempts, ' guesses remaining)');

end; end; writeln; writeln('Do you want to play again? (Y/N)'); readln(choice); end; end. If there are any questions or difficulties, write ggrotz@2sprint.net. On to the new stuff... PROCEDURE writing ================= This is a way a lot of things wor 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 tas s in a bigger program. Here is a short example of what I mean: program tutorial11; var one, two: integer; final: integer; procedure addtwonumbers(first, second: integer; var answer: integer); begin answer := first + second; end; begin 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, '.'); end. 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 eep 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 ma e 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 eep 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 li e 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; var one, two: integer; final: integer; function addtwonumbers(first, second: integer):integer; var answer: integer; begin answer := first + second; addtwonumbers := answer; {we can also do addtwonumbers := first + second;} end; begin 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, '.'); end. 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. SETS ==== 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 li e 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/ functions. 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; const tax_rate = 0.14; {14% tax rate} type string[15] = string15; { string[15] redefined so we can use it in a procedure/function, though we will not have procedures or functions here. } var total_pay: real; your_name: string15; {if we ma e type dec, we must carry it across. This variable is a string with a limit of 15 characters. } begin writeln('Who are you? (15 char. max)'); readln(your_name); writeln('How much did you earn this paychec ?'); readln(total_pay); 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 paychec .'); end. 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; begin clrscr; writeln('I cleared the screen for you.'); end. Final Note -========= You should lay out your programs in logical units using functions and procedures, ma ing 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 wor s 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 manyfold 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 loo at, and using parameter-passed procedures for the logical parts of it, and ma e it wor 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 as 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 ey. 6. Be SURE your program is easily understood.

The solution to this program will be presented in the next part. Good luc ! 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 } var choice: integer; procedure showmenu; { This procedure shows the main menu. } begin 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; writeln('Please enter a choice:'); end; procedure convseconds; var totalseconds: integer; hours, minutes, seconds: integer; temp: integer; begin clrscr; write('Enter a total number of seconds: '); readln(totalseconds); writeln; temp := totalseconds div 60; seconds := totalseconds mod 60; hours := temp div 60; minutes := temp mod 60; writeln; writeln(totalseconds, ' seconds is ', hours, ' hours, ', minutes, ' minutes, ', seconds, ' seconds.'); writeln; writeln('Press ENTER to continue:'); readln; end; procedure convmilitary; { Version 1.1. Bug found and e-mailed to me. It is appreciated. }

{ Involved the reporting of times from 0000 to 0059...Loo s to } { be fixed now. (wrote this in a very dazed state -- post-surgery)} var hours, minutes: integer; meridian: char; begin clrscr; write('Enter a military time''s hours: '); readln(hours); write('Enter a military time''s minutes: '); readln(minutes); if hours >= 12 then begin meridian := 'P'; hours := hours - 12; end else meridian := 'A'; if hours = 0 then hours := 12; writeln; write('It is '); write(hours); write(':'); if minutes < 10 then write('0'); write(minutes); writeln(' ', meridian, 'M.'); writeln; writeln('Press ENTER to continue:'); readln; end; begin choice := 10; while choice <> 3 do begin clrscr; showmenu; readln(choice); case choice of 1: convseconds; 2: convmilitary; end; end; end. 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 named UPCASED.TXT. program tutorial15; var infile, outfile: text; inputchar: char; begin 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 } begin while not eoln(infile) do { while not EOLN for infile } begin read(infile, inputchar); write(outfile, upcase(inputchar)); { process and output } end; writeln(outfile); { when end of line, advance both files down } readln(infile); { one line } end; close(infile); { we're done with both of these files. Close 'em } close(outfile); { time. } end. This program is primarily for illustration purposes of all the new concepts we need to now 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 bac out to a text file. To illustrate: If we have the following input file as on dis ... 14 23 34 53 32 Glenn Grotzinger 23 23 12 33 23 Clinton Suc s! 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 eep 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 eep 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; var inputstring: string; infile, outfile: string; function upstr(instring: string):string; { This function uppercases a whole string } var i: integer; newstr: string; begin newstr := ''; for i := 1 to length(instring) do newstr := newstr + upcase(instring[i]); { we can piece strings together using +. Keep it in mind } end; begin assign(infile, 'DATA.TXT'); reset(infile); assign(outfile, 'UPCASED.TXT'); rewrite(outfile); readln(infile, inputstring); while not eof(infile) do begin writeln(outfile, upstr(inputstring)); readln(infile, inputstring); end; writeln(outfile, upstr(inputstring)); close(infile); close(outfile); end. Remember to play with the logic in accessing text files. And in reading and writing files, BE SURE YOU USE FILES THAT YOU CAN STAND TO LOSE IF YOU ARE NOT COMPLETELY COMFORTABLE WITH PROGRAMMING FOR FILE ACCESS. IF A FILE BY A NAME YOU USE FOR A PROGRAM ALREADY EXISTS ON THE DRIVE, AND YOU REWRITE

IT, IT WILL BE COMPLETELY LOST! THIS MEANS ANY UNDELETE PROGRAM WILL *NOT* 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-li e functions (delete files, ma e 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, li e 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; var str: string; infile: text; begin assign(infile, 'PRINTME.TXT'); reset(infile); readln(infile, str); while not eof(infile) do begin writeln(lst, str); readln(infile, str); end; writeln(lst, str); writeln('File sent to printer on LPT1..'); end. Practice Programming Problem Notes ================================== Probably ALL programs I pose on these in the future will involve at least ONE data file off of dis . 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 as 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 wor er-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 wor er 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 eep in mind: 1) Gross pay is hourly rate * hours-wor ed for hours wor ed 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 blan prompt otherwise.) so they won't thin 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 Yor 23.21 11.10 -------------------(the area between all the --- is the WORKER.TXT to use.... -- another note: be sure there aren't any blan 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 wor ed. 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 li e 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 ma e 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 loo , it doesn't hurt to type it out as a sample, so you now 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 interpreting 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 ggrotz@2sprint.net. P.S. Sorry this one ain't too fun...Couldn't thin 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; const income_tax = 0.15; { set our 15% tax rate } type string20 = string[20]; { not needed, but good to demonstrate } var employee: string20; hours, rate: real; infile, outfile: text; grosspay, overtimepay, intax, netpay: real; procedure writeheaders; { writes the headers on our report } begin writeln(outfile, 'The International Widget, Inc.':51); writeln(outfile, 'PayOut And Deductions Sheet':49); writeln(outfile); writeln(outfile, 'Employee', 'Hours':17, 'Rate':7, 'GrossPay':10, 'Overtime':10, 'IncomeTax':11, 'NetPay':9); end; function writeline(fillchar: char; strlength: integer):string; { handy function that fills a certain length of a string with a fixed character } var i: integer; str: string; begin str := ''; for i := 1 to strlength do str := str + fillchar; writeline := str; end; procedure processrecord(hrs, rate: real;var gpay, opay, itax, npay: real); { does all of our required calculations for us } var rpay: real; begin if hrs > 40 then { if overtime then } begin rpay := rate * 40; { regular pay up to 40 } opay := (hrs - 40) * rate * 1.5; { overtime rate after 40 } end else begin rpay := hrs * rate; { figure as normal } opay := 0; { set overtime pay to 0, no overtime hours } end; gpay := opay + rpay; { get our gross pay } itax := gpay * income_tax; { get our deduction } npay := gpay - itax; { actual pay to wor er } end; begin assign(infile, 'WORKER.TXT'); reset(infile); assign(outfile, 'PAYOUT.TXT'); rewrite(outfile); writeheaders; { prepare input file } { prepare output file } { 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 } begin 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); end; 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(infile); close(outfile); end. { close files }

The Structure of an Array ========================= For a lot of things, it is very useful to now 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 li e 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 li e items in a program to ma e them easier to process. Often it is with such data as this: Say we want to wor 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 eep trac of which part of the array we want). There are 31 days in a month(maximum), so we would store them as something li e 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? RULE: ALL MEMORY STORAGE BY COMPUTER OF ANY ITEM IS LINEAR!!!! This rule is always helpful to remember when we wor 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 eyboard or text file, and written just li e variables we have wor ed with before. To read the 5th temperature in the array from the eyboard, we would use something li e 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 li e this... eep in mind that array

variables wor exactly the same way as the variables have that we have been wor ing with so far: for i := 1 to 31 do writeln(i, temperatures[i]); Note: often for/while/repeat loops are used to wor 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 now 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 now 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 li e 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 wor 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 begin for j := 1 to 31 do write(temperatures[i, j], ' '); writeln; end; Defining Constant Arrays ======================== It can be done. It's done li e this: const 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 wor 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 li e they were going to wor 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 wor s... 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 boo and start tallying letters on a table that's written on that sheet of paper. You now this is very time consuming and there is probably a better way. You realize that with your programming nowledge that you have and several text files that you have on your hard drive that you can ma e a program that will accomplish the tas much faster. They as ed you to count through a 200 page boo 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,

with the ASCII table. They

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 ta e 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. Loo 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 now 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 loo li e this, just be sure the files you list are on your drive and are TEXT files, and not word processor files) --------------------------------------------------------------------c:\mouse\readme.txt c:\4dos\4dos.doc d:\pastutr\pascal1.txt c:\config.sys d:\ezycom\ezy1.prn Sample of FRNDDATA.TXT --------------------------------------------------------------------Alphabetical Count Data for c:\mouse\readme.txt Letter A B C D E F G H Count 12 10 4 92 20 32 23 2 Letter N O P Q R S T U Count 6 7 2 2 4 2 3 4

I J K L M

2 34 21 123 12341

V W X Y Z

2 2 4 5 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 li e 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 wor s (if you do, maybe you should wor on the previous parts?), tell me and I'll go bac to posting complete programs. With reference to mathematics, if you do not now how to convert number bases, as 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 ggrotz@2sprint.net. 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... {$B+} program part6; type ltrtype = array['A'..'Z'] of longint; {type dec for arrays} var 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); var i: integer; {65..90} j: char; begin 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 begin inc(ltrstorage[j]); {var = var + 1 } inc(sums[j]); ltrs := ltrs + 1; end; end; procedure countfile(filename: string; var ltrstorage:ltrtype; var filenums: integer); var cntfile: text; ch str: string; begin writeln('Processing ', filename); filenums := filenums + 1; assign(cntfile, filename); reset(cntfile); readln(cntfile, ch str); while not eof(cntfile) do begin countletters(ch str, ltrstorage, sums); readln(cntfile, ch str); end; countletters(ch str, ltrstorage, sums); close(cntfile); end; procedure writefdata(filename: string; var ltrstorage: ltrtype); var i: integer; j: char; begin { write headers } writeln(counts, 'Alphabetical Count Data':53); writeln(counts, 'for ':40, filename); writeln(counts); 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); writeln(counts); writeln(counts); for j := 'A' to 'Z' do ltrstorage[j] := 0; { zero bac out storage array } end; procedure doend(filenums: integer; ltrstorage: ltrtype); var

i: integer; j: char; begin writeln(counts, filenums, ' files processed.'); writeln(counts, ltrs, ' letters processed.'); writeln(counts); writeln(counts); writefdata('all files', sums); end; begin assign(filelist, 'FILES.TXT'); reset(filelist); assign(counts, 'FRNDDATA.TXT'); rewrite(counts); readln(filelist, filename); filenums := 0; while (not eof(filelist)) and (filenums < 9) do begin countfile(filename, ltrstorage, filenums); writefdata(filename, ltrstorage); readln(filelist, filename); end; 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!'); close(filelist); close(counts); end. 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. type employeerecord = record number: longint; surname: string[18]; firstname: string[10]; position: string[10]; yrswor ed: integer; end; 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 wor 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 wor ing with a specific record. The WITH command specifies a specific record variable to wor with. An example can be seen below. program tutorial18; type {Employeerecord as above} var wor record: employeerecord; begin { get wor record into memory here. } writeln(wor record.number); writeln(wor record.surname); writeln('I''m getting sic of typing wor record all the time!'); with wor record do writeln(firstname); writeln(position); writeln(yrswor ed); end; end. Hopefully, you can see what exactly is going on in wor ing 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, eep in mind that record types can be used in an array.... var wor info: array[1..10] of employeerecord; Each section of the record wor s just li e ordinary variables, and are addressable li e ordinary variables. Also the whole record itself can be addressable and moved around (written possibly to binary files?) to other li e records....The above array can be addressed li e this: 3rd record in array, surname: wor info[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 quic 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 loo at this problem, we loo 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 wor this way. The exception is the changing of the multiple. We use 10 in the example above. Using that bac ground, 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 eep 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 loo 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 tal s. (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 now 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 wor 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 now about bits, since each completely meaningful unit to most of us comprises 8 bits. We don't need to normally thin 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 loo 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 loo at the bit sequence, a high end of 4 bits would have to be multiplied by 16. So if we loo 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 wor as well. we do need to be concerned about base 2, though, because the computer uses it. Reasons for nowing what this stuff is... ========================================= We have reasons that we need to now 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 high order 0

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 wor s 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 tal ing about orders of bytes and what goes on with those particular commands, now, you should have the bac ground to now about it and predict what would happen on those commands. First good commands to now about for wor ing with bits ======================================================= We always want to go for the most efficient code available. If we ever need to wor 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); writeln(2 shr 1); {or 2 X 2^1} {or 2 / 2^1}

Those two commands wor this way: <number> command <# of bits to shift> # of bytes to shift turns out to be the power of 2 we do the wor 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) Ta es absolute value of X. Arctan(X) Ta es arctangent of X. Cos(X) Ta es cosine of X. Exp(X) Ta es exponential (base e) of argument.

0 0 low order

It is a good idea to learn basic trigonometry and analytical geometry in programming. For example, I now from my studies that Tan(x) would be 1/Arctan(x) or sin(X) / cos(X). This stuff is good to now. The reasons? Graphics. All one needs to draw anything, really, is a good spot placement procedure and nowledge of trigonometry, and analytical geometry. Just now and define a resolution and then start drawing nowing your nowledge. 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 ggrotz@2sprint.net. 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 li e 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 chec this before you move on after using a VAL!!! Str is used li e 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 now 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. Conclusion ========== I now all of this mathematics, and tal of bits is probably confusing. If you don't understand it right now, don't worry about it and go bac 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 eep in the bac of our minds for some things. You should now 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 now 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-

Frac(X) Int(X) Ln(x) Pi Round(X) sin(X) sqr(x) sqrt(x) trunc(x)

Returns fraction of X. Returns integer part of X. Ta es base e logarithm of X. Returns value of pi. Rounds X(real) to an integer. Ta es sine of X. Ta es square of X. Ta es square root of X. Truncates X w/o rounding.

time before this tutorial is over, more than li ely, even we may not see them. But you may need to wor 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 ta e a number from the eyboard (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 wor 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 eyboard, place a 0 in the eyboard 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 chec : 20 (base 5) is 10 (base 10). Notes: 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 li e '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 ma e 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 lin 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, loo up in your DOS manual and try and pic 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 ggrotz@2sprint.net. 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 ta ing a power. } var i, endit: longint; begin endit := 1; for i := 1 to ord do endit := endit * int; power := endit; end; function dec2xbase(int: longint; base: integer):string; { converts base 10 to any base < 37 } const numguide: string = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; { the guide string I mentioned as a hint } var i: integer; hold, ch prod1, ch prod2: longint; endstr: string; begin if base > 36 then dec2xbase := '!' { a signal character to say our function failed } else begin endstr := ''; hold := int; while ch prod1 <> 0 do begin ch prod1 := hold div base; { using method described when I } ch prod2 := hold mod base; { demoed doing it manually } endstr := numguide[ch prod2 + 1] + endstr; { actual representation } hold := ch prod1; end; dec2xbase := endstr; end; end; function xbase2dec(int: string; base: integer):longint; { converts any base < 37 to base 10 } const numguide: string = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; var

i, powr: integer; endresult: longint; intlength: integer; begin if base > 36 then xbase2dec := -1 {signal fucntion has failed } else begin endresult := 0; i := 1; powr := length(int) - 1; while i <> length(int)+1 do { compute bac to base 10 } begin endresult := endresult + (pos(int[i], numguide)-1) * power(base, powr); i := i + 1; powr := powr - 1; end; end; xbase2dec := endresult; end; var numread, numbase: longint; convbase: string; begin write('Enter a number (base 10): '); readln(numread); write('What base do you want to convert to? '); readln(numbase); writeln; 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 wor } writeln('To chec : ', convbase, ' (base ', numbase, ') is ', xbase2dec(convbase, numbase), ' (base 10).'); end; end. 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 loo up several of the basic functions that I will list here. A simple if successful... ========================= How do we chec if any of these, or our prior reset/rewrite calls are successful? We toggle the I compiler option before a valid file operator, then we chec the variable in IOResult. If IOResult <> 0, then we now there is a problem. For example, how do we chec if a file exists on the drive before we read from it? function exists(filename: string):boolean; var ourfile: text; { can be defined to be any type we want } begin

assign(ourfile, filename); { let the program now 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 } begin close(ourfile); { we need to close the file since if it's there, } exist := true; { we just opened it. Also, indicate it's there. } end; end; This method can be used with any file function li e 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 dis space, or a variety of reasons. The I compiler directive indicates turning ON or OFF input/output chec ing. The default for most compilers is {$I+}. As compiler directives go, we must toggle it bac , because the area after the change is in effect, and we only want it for the case of the command we want to chec . If I/O chec ing 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 chec ing is off ({$I-}, the result of the command is logged in a global variable named IOResult. 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 intelligible, 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 wor ing 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, loo up the WinDOS variant equivalents...Be sure to loo 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: Dis Free(Drive: byte):longint; { free bytes on Drive } DOS/WinDos: Dis Size(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: M Dir(str: string); { ma es a directory named str } DOS/WinDos: Pac Time(... { pac s a time/date } System: Rename(file: filetype); { renames an external file } System: RmDir(str: string); { removes a dir named str } System: See (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: Unpac Time(... { unpac s a time/date to datetime record } Notes on some of the commands listed above that aren't that selfexplanatory --------------------------------------------------------------------Dis free(Drive: byte): longint; Dis Size(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: Dis Free and Dis Size are limited from what I understand to < 1 gig? (Please correct me if I am not quoting this right). I now 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 pac ed and unpac ed date and time. There is two of the functions listed above called pac time and unpac time. There is a special record already defined in DOS called DateTime (or TDateTime in WinDOS). Both of these are defined li e this: DateTime { or TDateTime -- they're the same } = record Year, Month, Day, Hour, Min, Sec: word; end; A pac ed time is stored as a longint; Good use of an erase/rename, etc, etc... Ma e a procedure that does the assign, and error chec s, then do the command, if the command requires the file to be an assigned file variable. For example: procedure deletefile(filename: string); var afile: text; { It can be anything we will find out } begin assign(afile, filename);

{$I-}erase(afile);{$I+} if IOResult <> 0 then writeln('Erasure unsuccessful.'); else writeln(filename, ' has been erased from your drive!'); end; 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 commandline 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-chec ing, and ta ing the filename from the command-line. Describes a command-line parameter describing a single file. program typefile; var param1, instr: string; thefile: text; begin if paramcount <> 1 then { if there is not one command-line parameter } begin { show some help to the user } halt(1); { quit the program right here } end; 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); {$I-}reset(thefile);{$I+} if IOResult <> 0 then begin writeln('This file doesn''t exist!'); halt(1); end; readln(thefile, instr); { if the file is there, no need to error chec reads, if our logic is correct } while not eof(thefile) do begin writeln(instr); readln(thefile, instr); end; writeln(instr); end. Now, what if we want to determine a file based on a command-line parameter. This isn't exactly complete error chec ing, 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 pic ing 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 Loo 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. EXPAND FILENAME ON COMMAND LINE TO FULL DIR, NAME, AND EXTENSION. IF THE END OF THE FILENAME DOES NOT HAVE A \ THEN GET FILE ATTRIBUTE. IF NO DOSERROR AND THE FILE IS A DIRECTORY ADD A \ TO THE END OF THE FILENAME. END-IF. SPLIT FILENAME INTO PROPER DIR, NAME AND EXT. IF NO NAME, THEN PLACE A * THERE. IF NO EXTENSION, THEN PLACE A .* THERE. FULL FILENAME = DIR, NAME, AND EXTENSION TOGETHER. 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 chec ing 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; { Pac ed Time } size: longint; { Size of file } name: string[12]; { name of file } end; 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; var fileinfo: searchrec; begin findfirst('*.*', $37, fileinfo); while doserror = 0 do { 18 = no more files } begin writeln(fileinfo.name); findnext(fileinfo); end; end. 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 stac 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 <stac size>,<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 rebun 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....

Here's an example: {$M $4000,0,0} program tutorial20; uses dos; var command: string; begin write('Enter a DOS command: '); readln(command); command := '/C' + command; { we must put the /C there to satisfy COMMAND.COM } swapvectors; exec(getenv('COMSPEC'), command);

Another It is a doesn't system.

command used in combination with this is called swapvectors. parameterless procedure which ma es sure our program we call literally stomp all over anything we may have done with the We call it before and after our exec procedure.

swapvectors; if doserror <> 0 then writeln('Dos Error ', doserror, ' occurred.') else writeln('Successful shell to DOS.'); end. 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 1 2 3 Normal Termination Terminated by Ctrl+C Terminated by Device Error Terminated as a TSR.

Conclusion ========== 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 wor with DOS. If you loo ed 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 ma e 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 ma e it so we pause the screen output on each page. It also should handle any error-chec ing that it may need to perform as presented in this part. Functions: 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 ma e 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. Hints: 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) Wor 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 ma e things easier. 4) This DIR listing command you are writing should function from the command-line with regards to filespec exactly li e the DOS dir command. Chec 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 } Help: 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. C:\>mydir MYDIR (c) 1996 by Glenn Grotzinger. File listing for: C:\*.* . .. CONFIG.SYS DESCRIPT.ION ARCHIVE.ZIP Volume label: DOS Version: 6.22 [DIR] [DIR] 03:45pm rash 12:14am -a-09:15pm r--h Total dirs: 2

122 182 14,432,322

12-12-95 12-28-95 03-24-93

Total files: 3

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 ggrotz@2sprint.net. 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-li e 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 part. How to start out? ================= It is good to start with the global way of things. Let's start with a few facts we now from our nowledge 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.

Globally, by loo ing at the way we now the output needs to be, we can figure out that globally, the program is going to have to do the following: 1) 2) 3) 4) Write the ID of the program. {mydir by...} Parse the command-line to determine what needs to be done. Write resultant headers based on results in 2. 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 brea down the global stuff and writing pseudocode ============================================================== We now 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 loo at factors for initialization code. We need some filespec variable clear...Also we now we need to count files and dirs, as well as set a pause flag to our default (false). PROCEDURE WRITEID; WRITE PROGRAM NAME AND AUTHOR NAME, COPYRIGHT INFO... SET PAGE COUNT TO # OF LINES USED (page pausing!) SET PAUSE TO FALSE, AND SET #DIRS AND #FILES TO 0. Parse the command-line (Global 2). ----------------------------------

Knowing our purposes in these two statements, we the DOS or WinDOS unit.

now we need usage of

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. IF PARAMETERS > 2 THEN SHOW SOME HELP AND QUIT. PULL ALL PARAMETERS INTO AN ARRAY (MAX 2). FOR EACH PARAMETER GIVEN TO PROGRAM IN ARRAY IF THE PARAMETER IS 2 CHARACTERS LONG AND THE FIRST CHARACTER IS - OR / IF SECOND CHARACTER OF PARAMETER IS ? SHOW SOME HELP AND QUIT. IF SECOND CHARACTER IS P, SET A PAUSE FLAG TO TRUE. ELSE SHOW SOME HELP AND QUIT. END-IF. ELSE SET FILESPEC TO THE COMMAND-LINE PARAMETER. END-IF. PARSE THE FILESPEC. 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. WRITE THE HELP. SET PAGE COUNT TO NUMBER OF LINES USED (Page count for pause!) HALT THE PROGRAM. Parsing the filespec. I gave you this pseudocode in part 8. Write resultant headers based on 2. (Global 3) ---------------------------------------------WRITE THE HEADER WITH PARSED PATH INTERPRETATION. List files as dictated by filespec in 2. (Global 4) --------------------------------------------------We now, basically, in the DIR implementation we want to list all files except ones with the volume lables. We now 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 eep 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. START FILE LISTING. WHILE WE STILL HAVE FILES IF A FILENAME IS A DIRECTORY ($10) THEN WRITE DIRECTORY NAME WIHT [DIR] DESIGNATION. INCREMENT # OF DIRS BY 1. ELSE WRITE FILENAME, FILESIZE, DATE, TIME AND ATTRIBUTES. INCREMENT # OF FILES BY 1.

INCREMENT SIZE OF FILES IN DIR BY SIZE OF THIS FILE. END-IF. INCREMENT PAGELENGTH BY 1. IF PAGELENGTH > 23 AND PAUSE IS TRUE WRITE PAUSE INDICATOR, READ FOR KEY, AND SET LINE COUNTER BACK TO 0. END-IF. IF THERE IS A DOS ERROR OR THERE ARE NO DIRS AND NO FILES WRITE THAT THERE ARE NO FILES THERE. 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) MAKE A STRING OUT OF THE NUMBER. FIND LENGTH OF NUMBER. WHILE THERE ARE MORE THAN 3 DIGITS TO CONSIDER COUNT OFF 3 DIGITS FROM THE RIGHT TO THE LEFT. PLACE A COMMA, AND SUBTRACT 3 DIGITS FROM LENGTH TOTAL. END-WHILE. COUNT OFF REST OF DIGITS. 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 eep 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 "Ma e 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 ma e 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. UNPACK THE DATE AND TIME FROM THE SYSTEM. MAKE MONTH A STRING. IF MONTH < 10, PAD NUMBER WITH 0. MAKE DAY A STRING. IF DAY < 10, PAD NUMBER WITH 0. IF YEAR > OR = 2000 2YEAR IS YEAR - 2000 ELSE 2YEAR IS YEAR - 1900 MAKE 2YEAR A STRING. IF YEAR < 10, PAD NUMBER WITH 0. FINAL-FILE-DATE IS MONTH-DAY-2YEAR. (or DAY-MONTH-2YEAR, whichever you prefer) IF HOUR > OR = 12 HOUR IS HOUR - 12 MERIDIAN IS PM. ELSE MERIDIAN IS AM. END-IF. IF HOUR = 0 THEN HOUR = 12. MAKE HOUR A STRING. IF HOUR < 10, PAD STRING WITH 0.

MAKE MIN A STRING. 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 bac ground 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 li e that. SET STRING TO ----. IF FILEATTR >= $20 THEN FILE HAS ARCHIVE BIT. FILEATTR = FILEATTR - $20. END-IF. IF FILEATTR >= $04 THEN FILE HAS SYSTEM BIT. FILEATTR = FILEATTR - $04. END-IF. IF FILEATTR >= $02 THEN FILE HAS HIDDEN BIT. FILEATTR = FILEATTR - $02. END-IF. IF FILEATTR >= $01 THEN FILE HAS READ-ONLY BIT. FILEATTR = FILEATTR - $01. END-IF. Show the global stats and operating system info (Global 5) ---------------------------------------------------------Basically, this is a write operation. But we need to now how to get the information... Volume label. Using the hint in the problem. SEARCH FOR FILE WITH VOLUMEID ATTRIBUTE IN ROOT DIR OF DRIVE WE ARE ACCESSING. IF THE FILE EXISTS, FILENAME IS THE VOLUMEID ELSE LEAVE IT BLANK. 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 wor with. So, to go from drive letter to number, we can always set a constant guide string (sort of li e my suggestion in part 7 to do this for bases > 11 as to ease things) defined as: ABCDEFGHIJKLMNOPQRSTUVWXYZ. I now this is probably over ill, but it will cover things OK so we don't have to write a large case statement. Run this one through the delineator. DRIVE NUMBER IS LETTER POSITION IN CONSTANT STRING. FREE-ON-DISK FOR DRIVE NUMBER.

Total size on drive. Similar to total siz eused. Uses constant guide string...also ran through delineator. DRIVE NUMBER IS LETTER POSITION IN CONSTANT STRING. TOTAL-ON-DISK FOR DRIVE NUMBER. DOS version. Getting this is exactly li e 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 wor s properly. 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. } type 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]; end; var 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; const all_files = $37; { all file constants in base 16 added together but VolumeID } var dir: dirstr; { required types for some of the commands as } name: namestr; { defined in the DOS unit. }

ext: extstr; attr: word; f: text;

{ attribute must be a word. } { required for a command we had to assign file for }

begin filename := fexpand(filename); { expand filename } if filename[length(filename)] <> '\' then { if end not \ } begin 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 \ } end; 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 } end; function zero(innum: integer):string; var tstr: string[2]; begin str(innum, tstr); if innum < 10 then tstr := '0' + tstr; zero := tstr; end; procedure showid; begin writeln('MYDIR (c) 1996 by Glenn Grotzinger.'); writeln; pagelen := pagelen + 2; end; procedure writeheader(filespec: string); begin writeln('File listing for: ', filespec); writeln; pagelen := pagelen + 2; end; function number(n: longint):string; var i, j: integer; s, r: string; m: integer; begin str(n, s); { get the longint to a wor able 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 delineations, 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 } begin 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 } end; 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 } end else number := s; end; procedure getdatetime(dirinfo: searchrec; var writeinfo: writerec); { type uses datetime record } var dt: datetime; a,b,c: string; { temp strings for use } tmpyr: integer; { we need to define this one to do the year } begin unpac time(dirinfo.time, dt); { unpac date and time } { start with date } { month } a := zero(dt.month); { day -- same as month } b := zero(dt.day); { year -- reported as 4 digits. We need to get it down to 2. } { eep 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 begin dt.hour := dt.hour - 12; c := 'pm'; end else 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; end; function getfileattr(dirinfo: searchrec):string; var left: word; str: string;

begin str := '----'; left := dirinfo.attr; if left >= $20 then { archive file } begin str[2] := 'a'; left := left - $20; end; if left >= $04 then { system file } begin str[3] := 's'; left := left - $04; end; if left >= $02 then { hidden file } begin str[4] := 'h'; left := left - $02; end; if left >= $01 then { read-only file } begin str[1] := 'r'; left := left - $01; end; getfileattr := str; end; procedure writefinfo(dirinfo: searchrec;writeinfo: writerec); var i: integer; begin with writeinfo do begin filename := dirinfo.name; filesize := number(dirinfo.size); getdatetime(dirinfo, writeinfo); fileattr := getfileattr(dirinfo); write(filename); for i := length(filename) to 12 do write(' '); writeln(filesize:12, filedate:12, filetime:12, fileattr:12); end; end; 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 } var fstr: string; dinfo: searchrec; begin fstr := drive + ':\*.*'; findfirst(fstr, VolumeID, dinfo); if doserror = 0 then { Volume label exists so...} getvolumeID := dinfo.name else getvolumeID := ''; { leave it blan if no volume label } end; procedure showhelp;

begin writeln('Help:'); 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.'); halt(1); end; function bytesfree(drive: char):longint; const guide = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; var dno: integer; begin dno := pos(upcase(drive), guide); bytesfree := dis free(dno); end; function bytesthere(drive: char):longint; { in TP, this won't wor if you have a partition > 1 gigabyte } const guide = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; var dno: integer; begin dno := pos(upcase(drive), guide); bytesthere := dis size(dno); end; begin { initialization code } showid; { should be the first thing we do } pause := false; filespec := ''; totalsize := 0; dirs := 0; files := 0; { chec # of parameters } if paramcount > 2 then showhelp; { pull in parameters } for i := 1 to paramcount do params[i] := paramstr(i); { chec '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; else showhelp; end else filespec := params[i]; filespec := parse_filespec(filespec); { go into start }

writeheader(filespec); findfirst(filespec, $37, dirinfo); while doserror = 0 do begin if dirinfo.attr = $10 then begin write(dirinfo.name); for i := length(dirinfo.name) to 12 do write(' '); writeln('[DIR]':49); inc(dirs); end else begin writefinfo(dirinfo, writeinfo); inc(files); totalsize := totalsize + dirinfo.size; end; inc(pagelen); if (pagelen > 23) and (pause) then begin write('--Pause--'); readln; pagelen := 0; end; findnext(dirinfo); end; if (doserror in [1..17]) or ((dirs = 0) and (files = 0)) then writeln('No files found.'); writeln; writeln('Volume label: ', getvolumeid(filespec[1]), 'Total Files: ':20, number(files), 'Total Dirs: ':20, number(dirs)); writeln('DOS Version: ', lo(dosversion), '.', hi(dosversion)); writeln; writeln(number(totalsize), ' bytes.'); writeln(number(bytesfree(filespec[1])), ' bytes free out of ', number(bytesthere(filespec[1])), ' total bytes.'); end. 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 nowledge to complete. This problem will also be set up for a programming contest. If there are any comments, please write ggrotz@2sprint.net. 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 now that files can be of type text. We can also ma e them type "file of <datatype>". We can read and write binary data types to dis . 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 ma e use of readln and writeln (as those are for text files only). program integers2dis ; { writing integers 1 thru 10 to a dis data file, then reading 'em bac } var datafile: file of integer; i: integer; begin assign(datafile, 'INTEGERS.DAT'); rewrite(datafile); 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 } begin writeln(i); read(datafile, i); end; writeln(i); close(datafile); end. You will notice the numbers 1 through 10 come up. Loo 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 thin 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) SizeOf(varlocation)

Function that gives total size of a variable in bytes. Maximum readable by Bloc Read: 64KB. Reset and Rewrite have a record size parameter if we deal with an untyped file. Probably, the best thing to ma e 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; var datafile: file; i: integer; numread: integer; begin clrscr; assign(datafile, 'INTEGERS.DAT'); rewrite(datafile, 1); for i := 1 to 10 do bloc write(datafile, i, sizeof(i)); close(datafile); reset(datafile, 1); bloc read(datafile, i, sizeof(i), numread); while numread <> 0 do begin writeln(i); bloc read(datafile, i, sizeof(i), numread); end; close(datafile); end. This program performs essentially the same function as the first example program, but we are using an untyped file. Bloc read and bloc write 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, bloc read 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 bloc reads and bloc writes 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) begin bloc read(infile, rarray, sizeof(rarray), bytesr); bloc write(outfile, rarray, bytesr, bytesw); end; Moving files is a copy of an input file to a new location, followed by

erasure of the input file. Units ===== 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 thin 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 s eleton to place procedures and functions into. unit myunit; interface { 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); implementation { actual procedural code goes here, as it would in a regular program } procedure writehello(str: string); { must be same as above } begin writeln(str); end; end. The unit up above is compilable to dis /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 } var str: string; begin str := 'Hello! Courtesy of myunit!'; writehello(str); end. 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 sa e on some of your frequently used procedures. Overlays ======== 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 ta en up by the overlay manager far uses more memory on smaller projects to ma e it an advantage to habitually do this). We will use the overlay facility with the unit/program set above for example purposes.

ONLY CODE IN UNITS HAVE AN OPPORTUNITY TO BE OVERLAID! System, CRT, Graph, 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 chec 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 wor (it's nown to happen -- a procedure wor s ONLY when it's not overlaid...), you won't have to go through the wor to alter it bac if it doesn't wor right... NOTE: You must compile to dis , then run when you wor with overlays. Results come bac in the OvrResult variable. Here's a list...

0 -1 -2 -3 -4 -5 -6

Success Overlay manager error. Overlay file not found. Not enough memory for overlay buffer. Overlay I/O error. No EMS driver installed. Not enough EMS memory.

As for examples, let's loo at the unit set up to overlay. As we can see, the only real difference (which is a good policy to ma e), is that there is the {$O+} compiler directive there now... {$O+} unit myunit; interface { 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); implementation { actual procedural code goes here, as it would in a regular program } procedure writehello(str: string); { must be same as above } begin writeln(str); end; end. Now lets loo 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 } var str: string; begin ovrinit('TMYUNIT.OVR'); { final overlay file name/init for program. } if OvrResult <> 0 then begin writeln('There is a problem'); halt(1); end else 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') else writeln('in EMS memory.'); str := 'Hello! Courtesy of myunit!'; writeln; writehello(str); end. EXE Overlays ============ Here's how to set up EXE overlays. The DOS copy command features the B switch. For example, to ta e the programs source file above and attach the overlay to the end of the EXE (be sure you run any exe pac ers/encryptors before you do this!), use the following: COPY /B TMYUNIT.EXE+TMYUNIT.OVR 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 wor ing on computers, and not on boo eeping (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 s ill and accuracy, but we too 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 s ill, and nothing ma es sense in their wor . 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 bac through and ma e 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 now 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 as ing 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; wor _area: integer; wor _prefix: integer; wor _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_chec : 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; end; 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 wor . 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 --------------------------------------------------------------------

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

datacode acct_classification sequence number phone_area phone_prefix phone_exchange wor _area wor _prefix wor _exchange other_area other_prefix other_exchange cnct1_lname cnct1_fname cnct1_minit cnct1_pobox cnct1_sname

columns 1-7 column 8 column 9 columns 10-12 columns 13-15 columns 16-19 columns 20-22 columns 23-25 columns 26-29 columns 30-32 columns 33-35 columns 36-39 columns 40-55 columns 56-66 column 67 columns 68-72 columns 73-80

datacode accept_chec sequence number cnct1_stype cnct1_apt cnct1_city cnct1_state cnct1_zip cnct1_birthm cnct1_birthd cnct1_birthy balnce_credt total_sold cnct1_emp_code total_sales emp_name

columns 1-7 column 8 column 9 columns 10-13 columns 14-17 columns 18-27 columns 28-29 columns 30-38 columns 39-40 columns 41-42 columns 43-46 columns 47-55 columns 56-63 columns 64-67 columns 68-70 columbs 71-80

emp_yrs compu compu_type compu_mon compu-cdr compu_cdt compu_mem minor spaces

column column column column column column column column column

49-50 51 52-60 61 62 63 64-65 66 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. Chec 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-chec s listed in points 2-14. 2) Datacode on lines 1, 2 and 3 should MATCH exactly and be chec ed 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, wor _area, wor _prefix, wor _ 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 chec ed to verify that they are numeric in origin. 4) phone_prefix, wor _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_chec , accept_credt, compu, compu_cdr, and minor should be Y or N. 8) emp_yrs (employed how many years?) should be chec ed with cnct1_birthy for sanity (a person who was born in 1980 cant have wor ed 20 years). 9) If compu is N, then compu_type, compu_mon, compu_cdr, compu_cdt, and compu_mem should be either blan 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 blan 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. Chec 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 --------------------------GROA2*GN334 ST WAR GROT2*GP181612932918 GROT2*GP181612932918 GROT2*GT2ST 314 SED GROT3*GP181642932918 GROT3*GN234 ST WAR GROT4*GI181642932918 GROT4*GY2ST 314 SED GROT4*GN334 ST WAR GROT4*GY2ST 314 SED GROT4*GI181642932918 GROT7*GN334 ST WAR GROT7*GN334 ST WAR GROT7*GN334 ST WAR GROT7*GN334 ST WAR GROT7*GN334 ST WAR GROT7*GN334 ST WAR GROT8*GN334 ST WAR GROT8*GN334 ST WAR GROT0*GN334 STR WAR GROT0*GN334 STR WAR Problem -------------------------Datacode does not agree with name. Wor -exchange is not numeric. phone-prefix started with a 0 or 1. accept-chec is invalid. Duplicate line #1 Missing line #3 Datacode does not agree with name. Datacode does not agree with name. Datacode does not agree with name. cnct1-birthy is not in this century. acct-classification is invalid. emp-zip is not numeric. compu-cdr is invalid. The emp-yrs doesn't ma e sense. There were fields present when compu was N. compu-mon is invalid. compu-cdt is invalid. empcodes are present when RET is true. compu-mon is invalid. compu-cdt is invalid. 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 s ill, we as 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 ( eep 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 GROT0*GN334 STR WARRENSBURMO65337 81654341114 YHOMEBUILTVYA8 N Notes ----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 wor 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 condition is true situation. I suggest it because the pascal operator NOT seems to not wor right in all cases. :< Also, eep 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 dis . You may wish to obtain use of a hex viewer for this next part. Send comments to ggrotz@2sprint.net.

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 wor s.... {$O+} unit unit10; interface type strtype = array[1..3] of string[80]; {$I COMPHVN.INC} function numeric(str: string): boolean; procedure writerecord(var outfile: file; strs: strtype); implementation function numeric(str: string): boolean; var num: boolean; i: integer; begin i := 1; num := true; while (num) and (i <= length(str)) do begin num := (str[i] in ['0'..'9','.',' ']); inc(i); end; numeric := num; end; procedure writerecord(var outfile: file; strs: strtype); var numerr: integer; writerec: comphvndata; begin with writerec do begin {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), wor _area, numerr); val(copy(strs[1], 23, 3), wor _prefix, numerr); val(copy(strs[1], 26, 4), wor _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); {part2}

accept_chec := (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); {part3} 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'); end; bloc write(outfile, writerec, sizeof(writerec)); end; end. 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 thin of that falls into the rules I outlined for the thing.). The two compiler directives you see below turn off stac chec ing and range chec ing respectively. With regards to data intensive applications such as sorting, large #'s of comparisons, and so forth, adding these speeds it up. {$S-} {$R-} program part10; uses dos, unit10, overlay; {$O UNIT10.TPU} var strs: strtype; outfile: file; infile, errfile: text; i: integer; errwritten: boolean; procedure writeerror(errline, errtype: string); var temp1: string; begin

temp1 := copy(errline, 1, 20); writeln(errfile, temp1,'':10, errtype); errwritten := true; end; function chec status(strs: strtype): boolean; var chec : boolean; seqs: array[1..3] of char; cnts: array[1..3] of byte; i: byte; errtype: string; begin chec := true; for i := 1 to 3 do cnts[i] := 0; for i := 1 to 3 do begin seqs[i] := strs[i][9]; case seqs[i] of '1': inc(cnts[1]); '2': inc(cnts[2]); '3': inc(cnts[3]); end; end; for i := 1 to 3 do begin if cnts[i] = 0 then begin errtype := 'Missing line #' + chr(i+48); writeerror(strs[i], errtype); chec := false; end; if cnts[i] = 2 then begin errtype := 'Duplicate line #' + chr(i+48); writeerror(strs[i], errtype); chec := false; end; end; chec status := chec ; end; procedure chec datacodes(strs: strtype); var datacodes: array[1..3] of string[7]; chec 1: string[5]; chec 2: char; error: string[20]; begin for i := 1 to 3 do datacodes[i] := copy(strs[i], 1, 7); chec 1 := copy(strs[1], 40, 5); chec 2 := strs[1][56]; for i := 1 to 3 do begin if datacodes[i][6] <> '*' then writeerror(strs[i], 'Invalid datacode.'); if (chec 1 <> copy(datacodes[i], 1, 5)) or (chec 2 <> datacodes[i][7]) then

writeerror(strs[i], 'Datacode does not agree with name.'); end; end; procedure chec numeric(strs: strtype); var temp1, temp2: string; int, numerr, numdiff: integer; year, month, day, dayofwee : word; empyrs, birthy, bmo, bday: word; age: byte; isminor: boolean; begin 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], 'Wor -area is not numeric.'); if numeric(copy(strs[1], 23, 3)) = false then writeerror(strs[1], 'Wor -prefix is not numeric.'); if numeric(copy(strs[1], 26, 4)) = false then writeerror(strs[1], 'Wor -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.') else 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.') else val(temp2, bmo, numerr); temp2 := copy(strs[2], 41, 2); if numeric(temp2) = false then writeerror(strs[2], 'cnct1-birthd is not numeric.') else val(temp2, bday, numerr);

temp1 := copy(strs[2], 43, 4); if numeric(temp1) = false then writeerror(strs[2], 'cnct1-birthy is not numeric.') else begin val(temp1, int, numerr); if (int < 1900) or (int > 1999) then writeerror(strs[2], 'cnct1-birthy is not in this century.'); end; getdate(year, month, day, dayofwee ); val(temp1, birthy, numerr); numdiff := year - birthy; if numdiff < empyrs then writeerror(strs[3], 'The emp-yrs doesn''t ma e sense.'); age := year - birthy; if month < bmo then dec(age); if month = bmo then if day < bday then dec(age); 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.'); end; procedure chec prefix(strs: strtype); begin 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], 'wor -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.'); end; procedure chec acct(strs: strtype); begin if strs[1][8] in ['B','C','G','P','O'] then else writeerror(strs[1], 'acct-classification is invalid.'); end; procedure chec yn(strs: strtype); begin if strs[2][8] in ['Y', 'N'] then else

writeerror(strs[2], 'accept-chec is invalid.'); if strs[3][8] in ['Y', 'N'] then else writeerror(strs[3], 'accept-credt is invalid.'); if strs[3][51] in ['Y', 'N'] then else writeerror(strs[3], 'compu is invalid.'); if strs[3][62] in ['Y', 'N'] then else writeerror(strs[3], 'compu-cdr is invalid.'); if strs[3][66] in ['Y', 'N'] then else writeerror(strs[3], 'minor is invalid.'); end; procedure chec compun(strs: strtype); begin 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.'); end; procedure chec empcode(strs: strtype); begin 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.'); end; procedure chec compumon(strs: strtype); begin if strs[3][61] in ['S','V','E','C','H','I'] then else writeerror(strs[3], 'compu-mon is invalid.'); end; procedure chec compucdt(strs:strtype); begin if strs[3][63] in ['1','2','4','6','8'] then else writeerror(strs[3], 'compu-cdt is invalid.'); end; procedure chec sttype(strs: strtype); var a: string; begin a:=copy(strs[3], 14, 4); if (a <> 'BLVD') and (a <> 'LANE') and (a <> 'ST ') and (a <> 'CT ') and (a <> 'LOOP') and (a <> 'DR (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 <> 'CT ') and (a <> 'LOOP') and (a <> 'DR (a <> 'CIRC') and (a <> 'RR ') then writeerror(strs[2], 'cnct1-stype is invalid.'); end;

and (a <> 'AVE ') ') and

and (a <> 'AVE ') ') and

procedure writeheader; begin writeln(errfile, 'Error Report -- INDATA.TXT':50); writeln(errfile, '--------------------------':50); writeln(errfile); writeln(errfile, 'First 20 characters','':10, 'Problem'); writeln(errfile, '-------------------','':10, '-------'); end; begin ovrinit('PART10.OVR'); if ovrresult <> 0 then begin 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.'); end; halt(1); end; ovrinitems; 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!'); end; assign(infile, 'INDATA.TXT'); reset(infile); assign(errfile, 'ERRORS.LOG'); rewrite(errfile); assign(outfile, 'COMPHVN.DAT'); rewrite(outfile, 1); writeheader; while not eof(infile) do begin errwritten := false; for i := 1 to 3 do readln(infile, strs[i]); if chec status(strs) then begin chec datacodes(strs); chec numeric(strs); chec prefix(strs); chec acct(strs); chec yn(strs); chec empcode(strs); chec compun(strs); chec compumon(strs); chec compucdt(strs); chec sttype(strs); end; if errwritten = false then writerecord(outfile, strs); end; close(infile); close(errfile); close(outfile);

end. 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. Byte ==== The basic idea of a byte has been covered in part 7 of the tutorial. Please refer bac 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 li e 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 li e 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 dis , 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 wor : 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 dis by TP in integer form as 00 0D . We could equally store this number as a byte if we new that this variable location would never be requested to hold a number that is greater than 255. For example, if we new 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 now, that it is what is called a double word, or two words put together. Therefore, we now 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 .

Char ==== 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 tal ed 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 li e it was an unsigned number. Now, lets observe what -3 would loo li e. 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 now 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, loo 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 loo li e 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. Boolean ======= 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 characters + 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 bac 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; var p: string; begin p := 'Hello world!'; p[0] := #5; writeln(p); end. Bloc ed Character Strings ========================= It's also possible to wor 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 bac 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 or #0. The strings unit is documented to easier to get away with simply converting with through Pascal itself, if we have to

Arrays ====== The general structure of an array was described in part 6. It is basically usable as a grouping of items. Records ======= A record is stored in memory basically as a grouping of data types. The record type below: datarecord = record int: integer;

terminated by a null character, wor with these strings, but it's it into something we can wor do much with it.

character: char; end; would be stored in memory li e this: INT CHARACTER Now, we have described all of the pertinent items we need to now to be able to wor 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 wor for that, but most of the time, it will be presented in a byte by byte offset format. Let us loo 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... ------------------------------------------------------------------------Protrac er 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 46 48 1 2 2 Volume for sample 1. Range is $00-$40, or 0-64 decimal. Repeat point for sample 1. Stored as number of words offset from start of sample. Multiply by two to get offset in bytes. 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 li e 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 trac ers will search through all patterns when loading. Noisetrac er 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 trac er what pattern to play at that position. 1080 4 The four letters "M.K." - This is something Mahoney & Ka tus 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 ma e the module harder to rip. Startre er 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 now the maximum limit. So, lets just call it a bloc ed 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 now 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; end; Finishing the Record Format =========================== Continuing on... songlength is a byte. So songlength: byte; A byte is defined next that seems li e 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 eep 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; end; 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; end; We can use this format to gain any information we now about a file. To test this format, we need to write a program that pulls the information out of a standard file, that we now, and chec 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 "M.K.". All the data we need to now to extract data out, and get formatted data from standards files, have been presented. Practice Programming Problem #11 ================================ In eeping with the topic of this tutorial, I am as ing you to write a program entirely in Pascal that will ta e a ZIP file name from the commandline 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 ta en on the command-line. For example, a command-line will always be: ZIPLIST FILENAME.ZIP OUTPUT.TXT Be sure to design this program with any error chec s you may need to perform. Don't forget to devise a chec to be sure you are dealing with a ZIP file on the first parameter. Here, we have gone to loo at some references, and have found the following out of a specs list: --------A-ZIP------------------------------The ZIP archives are created by the P ZIP/P UnZIP combo produced by the P Ware company. The P ZIP 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 001Ch

1 word 1 word

001Eh "LEN" char 001Eh "XLN" char +"LEN" 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) P Zip compression types 0 - Stored / No compression 1 - Shrun / 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 description --- 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 ="LEN" 001Eh 1 word Length of extra field ="XLN" 0020h 1 word Length of file comment ="CMT" 0022h 1 word Dis number ?? 0024h 1 word Internal file attributes (bit mapped) 0 - file is apparently an ASCII/binary file 1-15 - unused

Length of filename ="LEN" Length of extra field ="XLN" path/filename extra field

0026h 002Ah 002Eh 002Eh+ +"LEN" 002Eh +"LEN" +"XLN"

1 dword External file attributes (OS dependent) 1 dword Relative offset of local header from the start of the first dis this file appears on "LEN" char Filename / path; should not contain a drive or device letter, all slashes should be forward slashes '/'. "XLN" char Extra field "CMT" char File comment

(Table 0011) P Zip 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 dis 0006h 1 word Number of dis with start of central directory 0008h 1 word Total number of file/path entries on this dis 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 dis number 0014h 1 word Archive comment length ="CML" 0016h "CML" char Zip file comment EXTENSION:ZIP OCCURENCES:PC,Amiga,ST PROGRAMS:P ZIP,WinZIP REFERENCE:Technote.APP Source: FILEFMTS (c) 1994,1995 Max Maischein Sample output for this program ============================== Files contained in FILENAME.ZIP NAME COMP-SIZE DATE TIME UNCOMP-SIZE COMP-METHOD --------------------------------------------------------------------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 > Notes: 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 chec first...Notice a good way to chec out of the first field of each record... 4) The "MS-DOS Time Format" can be done really easily. Just thin DOS unit. 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 li ely, the data are correct, so there is no need to chec 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 stac s and queues. Send any comments, encouragements, problems, etc, etc. to ggrotz@2sprint.net. Haven't seen anything of that type, ya now.... Turbo Pascal for DOS Tutorial by Glenn Grotzinger Part 12: Stac s 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. } type ziplocfileheader = record zipver: integer; bitfield: word; compmethod: integer; pac time: longint; crc32: longint; compsize: longint; uncompsize: longint; filelen: integer; xtralen: integer; end; zipcds = record ver: byte; hostos: byte; verxtr: byte; targos: byte; bitfield: word;

compmethod: integer; pac time: longint; crc32: longint; compsize: longint; uncompsize: longint; filelen: integer; xtralen: integer; comtlen: integer; dis num: integer; intattr: integer; extattr: longint; roffset: longint; end; endcdsheader = record numdis : integer; numdis a: integer; numfiles: integer; entries: integer; size: longint; ofs: longint; comtlength: integer; end; var 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. localheader1 loccompresseddata1 localheader2 loccompresseddata2 centraldirectorystructure1 centraldirectorystructure2 endofcentraldirectorystructure } procedure readzip; { ZIP is a random data binary file. We read the ID to chec it, then differentiate it from there. We also devise a test of whether the "ZIP file" is a valid ZIP file here. } begin bloc read(zipfile, id, sizeof(id)); totsize := totsize + 4; { totsize is a see index } if (id[1] <> 'P') and (id[2] <> 'K') then begin writeln('Corrupted ZIP file, or not a ZIP file.'); halt(1); end;

case id[3] of #05: begin bloc read(zipfile, totsize := totsize end; #03: begin bloc read(zipfile, totsize := totsize end; #01: begin bloc read(zipfile, totsize := totsize end; end; end;

endcds, sizeof(endcds)); + sizeof(endcds); locfile, sizeof(locfile)); + sizeof(locfile); dirstr, sizeof(dirstr)); + sizeof(dirstr);

function zero(int: integer):string; {places zero on the beginning of a number if it's < 10 } var strng: string; numerr: integer; begin str(int, strng); if int < 10 then strng := '0' + strng; zero := strng; end; function strip(str: string):string; { removes null characters from random-read filename } var tstr: string; i: integer; begin tstr := ''; for i := 1 to length(str) do if str[i] <> #0 then tstr := tstr + str[i]; strip := tstr; end; function exist(filename: string):boolean; { file existence test } var thefile: text; begin assign(thefile, filename); {$I-}reset(thefile);{$I+} if IOResult <> 0 then exist := false else begin exist := true; close(thefile); end; end; procedure header(filename: string);

{ writes header of data } var s: string; begin writeln(outfile, 'Files contained in ':41, filename); writeln(outfile); 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 character represented by - } s[0] := #74; { set length byte } writeln(outfile, s); end; procedure footer(compsize, uncompsize: longint); { write footer of data } var s: string; begin fillchar(s, 75, '-'); s[0] := #74; writeln(outfile, s); writeln(outfile, compsize:22, uncompsize:34); writeln(outfile); writeln(outfile, endcds.numfiles, ' files; Effective compression', ' ratio: ', (compsize / uncompsize)*100 :0:1, '%'); end; procedure writehelp; { help for incorrect usage } begin writeln('ZIPLIST <zipfile> <datafile>'); halt(1); end; procedure writedata(locfile: ziplocfileheader); var dt: datetime; begin unpac time(locfile.pac time, dt); { this handles the MS-DOS time format } writeln(outfile, strip(filelength):12, locfile.compsize:10, zero(dt.month):7,'-',zero(dt.day),'-',dt.year, zero(dt.hour):6,':', zero(dt.min), locfile.uncompsize:10, locfile.compmethod:12); end; begin if paramcount <> 2 then { incorrect parameters? } writehelp; totsize := 0;compsize := 0;uncompsize := 0; param1 := paramstr(1); param2 := paramstr(2); assign(zipfile, param1); if exist(param1) = false then begin writeln(param1, ' does not exist!'); halt(1);

end; reset(zipfile, 1); assign(outfile, param2); rewrite(outfile); header(param1); readzip; while id[3] = #03 do { process local file headers and compressed data } begin bloc read(zipfile, filelength, locfile.filelen); compsize := compsize + locfile.compsize; uncompsize := uncompsize + locfile.uncompsize; writedata(locfile); totsize := totsize + locfile.filelen; totsize := totsize + locfile.xtralen; fillchar(filelength, sizeof(filelength),#0); see (zipfile, totsize); totsize := totsize + locfile.compsize; see (zipfile, totsize); readzip; end; while id[3] = #01 do { process central directory structure } begin totsize := totsize + dirstr.filelen; see (zipfile, totsize); totsize := totsize + dirstr.xtralen; see (zipfile, totsize); readzip; end; footer(compsize, uncompsize); writeln(outfile); { handle ZIP comment } if endcds.comtlength = 0 then writeln(outfile, 'No comment in ZIP file.') else for i := 1 to endcds.comtlength do begin bloc read(zipfile, filechar, sizeof(filechar)); write(outfile, filechar); end; close(zipfile); close(outfile); end. Hello. Basically, we are dealing in this part with specialized types of data structures called stac s and queues. Stac s ====== A stac is best analagous to a stac of papers on a des , only able to either place a sheet on the top of the stac , or ta e a sheet off the top of the stac . It is generally defined as a record format, which

can ta e on the following: stac = record elements: array[1..maxstac size] of <whatever>; capacity: integer; {range of 0..maxstac size} end; It is best described as a last in/ first out data structure, much li e 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 wor ing with stac s. The best way to thin about coding this is to use the stac of paper as a viewpoint, and using the sample stac record above. To initialize or effectively ill a stac currently in usage is to set capacity to 0. We can do this, because all wor with a stac ultimately comes down to usage of capacity as an index. If we have 0 sheets of paper on an imaginary stac , essentially, the stac is empty. Now, let's add a sheet of paper to our imaginary stac . We recognize that there is one more sheet of paper on the stac now. Then we have to place that sheet on the stac ...also, we have to eep in mind if we have hit our arbitrarily set limit of sheets on the stac , and notify of such thing.

How do we recognize if we have a full stac or an empty stac ? Loo at the value of capacity. If it's 0, then it's empty. If it's the value for the maximum capacity of the stac we decide, then we now it's full. Basically, in the last four paragraphs, I have, in discussing the actions of wor ing with stac s, have given you all the pseudocode you need to be able to come up with the code to wor with a stac . 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 stac may be the proper way to go. Queues ====== A queue is set up much li e a line at the local shop to pay the merchant at the cash register. The first element in is the first element out. Much li e a stac , a queue may be defined as a record format. queue = record elements: array[1..maxqueuesize] of <whatever>; front, bac : integer; count: integer; end; Notice that the differences we see here, are that to ta e from the front of the so called line we need to now where the front is in the order. The same idea occurs with the bac . We need to now where the bac is

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

in the array. Count just helps us in nowing 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 chec whether it's full or empty is basically set through count. Chec count to be zero for a queue to be empty and maxsize for a queue to be full. One stic y mess comes up when we try to add an element to the bac of the line. How do we eep trac of things? We increment the bac unless it's maxqueuesize, then we set it to 1, essentially, to continue reusing the array storage as a loop.... To add to the bac of the line, we do li e we did with the stac . We add the element to the bac , then we increment the bac count as described above. To ta e 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 li e a message scrolling across a surface, li e you may have seen on your television, and at Times Square in NY (on several movies). Conclusion ========== Both of these data storage schemes wor beautifully, when you have a lot of data to deal with that can be passed through quic ly, but needs to be released at other times...beyond that, at this point, I can't thin of anything that can be done by only using a stac 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 bac to the screen. Sample -----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. Sample -----23 23 11 22 33 1 9 12 23 11

23 23 23 23

3 11 22 33 11 9 12 23 11 23 11 22 33 11 9 12 23 11 29 11 2 33 11 9 12 23 11 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 ggrotz@2sprint.net. 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; const maxstac size = 500; type stac = record elements: array[1..maxstac size] of char; capacity: byte; end; var thestac : stac ; i: byte; ch: char; procedure startstac (var thestac : stac ); begin with thestac do capacity := 0; end; procedure place(var thestac : stac ; element: char); begin with thestac do begin if capacity = maxstac size then writeln('**ERROR** Trying to place element on full stac !') else begin inc(capacity); elements[capacity] := element; end; end; end; procedure remove(var thestac : stac ; var element: char); begin with thestac do begin if capacity = 0 then

writeln('**ERROR** Trying to remove element from empty stac !') else begin element := elements[capacity]; dec(capacity); end; end; end; begin randomize; startstac (thestac ); write('Enter a string: '); while ch <> #10 do begin read(ch); place(thestac , ch); end; writeln; writeln; write('The string reversed is: '); while thestac .capacity <> 0 do begin remove(thestac , ch); write(ch); end; end. And now the second one... program part12_2; uses crt; const maxqueuesize = 50; type queue = record elements: array[1..maxqueuesize] of integer; front, bac : integer; count: integer; end; procedure startqueue(var thequeue: queue); begin with thequeue do begin front := 1; bac := maxqueuesize; count := 0; end; end; procedure incqueue(var thenum: integer); begin if thenum = maxqueuesize then thenum := 1 else inc(thenum); end; procedure place(var thequeue: queue; element: integer);

begin with thequeue do if count = maxqueuesize then writeln('**ERROR** Trying to place element in full queue.') else begin elements[bac ] := element; incqueue(bac ); inc(count); end; end; procedure remove(var thequeue: queue; var element: integer); begin with thequeue do if count = 0 then writeln('**ERROR** Trying to remove element from empty queue.') else begin element := elements[front]; incqueue(front); dec(count); end; end; var thequeue: queue; int, count: integer; begin randomize; startqueue(thequeue); clrscr; while thequeue.count <> maxqueuesize do begin int := random(1000) + 1; if int <= 50 then place(thequeue, int); inc(count); end; while thequeue.count <> 0 do begin remove(thequeue, int); write(int, ' '); if thequeue.count mod 10 = 0 then writeln; end; writeln(count, ' numbers generated from 1-1000 to get these 50 ', 'numbers from 1-50'); end. Forward ======= 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 li e: 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 li e 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 bac in part 8. Please review it there. Basically, the first number of the compiler directive is the program stac 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 stac size so a recursion may complete. If the stac is filled, a runtime 202 error occurs. Recursion ========= 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 stac , 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 stac in running your program. 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 loo at ta ing 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 loo ing at a code example that does it...after that, I will explain the process in detail that goes behind how it wor s. 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 wor s, is because it is hard to conceptualize how the variables and functions wor , in a manner that is understandable. In all of the boo s 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 tal ed 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 see 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 feedbac 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 as for input li e this in the part I write later on readdressing pointers) program example_of_recursion; function factorial(a: longint): longint; var c: longint; begin {1} c := 1; {2} if a > 1 then { {3} c := a * factorial(a-1); {4} factorial := c; end; begin writeln(factorial(4)); end. to explain the path of logic in this program in calling the function factorial with regard to the variables, the biggest problem, I thin , 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} s ipped 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 chec the code, the final value is correct. 4! = 24. Basically, with the layout I used, you can see especially also, why memory (stac space allocated for procedure and functions specifically) runs out quic , and why I say to eep 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 li e 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 ma e 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: C:\DOS ... C:\UTIL C:\UTIL\ACAD 131,123 5,231,131 ... 3,212,985 131,123 ============= 527,212,122

Notes: 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 chec yourself in your addition. 2) The spacing is not exact above. Ma e it loo SIMILIAR to what I have above, but ma e it reasonably attractive... 3) Use a forward. 4) Be sure to chec to be sure the drive is specified EXACTLY li e 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-chec the command-line. Next Time ========= We will discuss the functions of the CRT unit. Be sure to write comments, encouragements, problems, errors, to ggrotz@2sprint.net.

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} {$B+} program part13; uses dos; var drive, s: string; drivesize: longint; thefile: text; function number(n: longint): string; forward; procedure listdirsizes(filedir: string); var size: longint; f: string; dirinfo: searchrec; begin size := 0; f := filedir + '\*.*'; findfirst(f, $27, dirinfo); { allfiles - VolumeID - directory } while doserror = 0 do begin size := size + dirinfo.size; findnext(dirinfo); end; writeln(thefile, filedir, number(size):50-length(filedir)); drivesize := drivesize + size; findfirst(f, $10, dirinfo); while doserror = 0 do begin { for some reason, the findfirst above doesn't wor right. } { Unable to determine exactly why...:< } if (dirinfo.attr = $10) and (dirinfo.name <> '.') and (dirinfo.name <> '..') then begin s := filedir + '\' + dirinfo.name; listdirsizes(s); end; findnext(dirinfo); end; end; function number; var i, j: integer; s, r: string; m: integer; begin str(n, s); r := ''; i := length(s); if i > 3 then

begin while i > 3 do begin for j := i downto i-2 do r := s[j] + r; r := ',' + r; i := i - 3; end; for j := i downto 1 do r := s[j] + r; number := r; end else number := s; end; procedure writehelp; begin writeln('Include a drive li e this: c:'); halt(1); end; begin assign(thefile, 'FILESLST.TXT'); rewrite(thefile); if paramcount <> 1 then writehelp; drive := paramstr(1); if (length(drive) <> 2) or (drive[2] <> ':') then writehelp; drive[1] := upcase(drive[1]); writeln(thefile, 'Drive: ', drive[1]); listdirsizes(drive); writeln(thefile, '===============':50); writeln(thefile, number(drivesize):50); close(thefile); end. 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 spea er control, control of the screen for screen clears, positioning of the cursor, changing the color of text and bac ground, 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. Chec Brea . It is true by default. If it's false, CTRL+BREAK will be unfunctional. Chec EOF. It is true by default. If it's false, CTRL+Z when pressed will not represent an EOF in the program. Chec Snow. Enables and disables "snow chec ing" on CGA adapters. DirectVideo. Enables and disables direct video writes.

Keypressed. Will return true if a ey has been pressed. Usable in a loop for doing something until the user stops it. A function li e 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_ ey_pressed; uses crt; begin repeat write(#254); until eypressed; end. This program should write ascii character #254 to the screen repeatedly until the user presses a ey. 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 Spea er Sounds and Machine Delay =================================== These are the basics of controlling the PC Spea er to ma e sounds. The first thing is that there is an ASCII character #7 which will cause a standard DOS PC Spea er 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 spea er at a specified frequency. NoSound; will stop the PC spea er. Here is an example of use of the PC spea er through TP. program test_of_pc_spea er; uses crt; begin sound(1500); delay(500); nosound; end. 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 li e 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 Textbac ground(color); are used to control the foreground and bac ground color respectively. Foreground colors wor from 0-15 and the bac ground colors are from 0-8. Blin ing 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; var oldx, oldy: byte; begin { save original position of cursor so we can return there later } oldx := wherex; oldy := wherey; window(2,2,20,20); textbac ground(Blue); clrscr; { blue bac ground now b/c blue when clrscr was called } highvideo; window(10,10,15,15); textbac ground(Green); clrscr; textcolor(LightCyan + blin ); write('Hello, how are we doing today?'); window(1,1,80,25); gotoxy(oldx, oldy); end. Reading Characters from the Keyboard ==================================== You may have been wondering, how to directly read things from the eyboard. The CRT offers a function called read ey, which is a parameterless function that returns a character. Unli e read or readln, it does not require a CR to initiate. You may see this by replacing a character read from the eyboard somewhere in your program with something li e this: answer := read ey; 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 applications. The next advantage of read ey is that it can be used to read about every ey or ey combination on the eyboard. For this tutorial, we will discuss simply the eys that can be accessed via one eypress using a basic standard to be discussed in the next paragraph.

char := read ey; if char = #0 then char := read ey; Most of the one press eys on the eyboard may be detected using this method. Examples of those eys which can't be detected using the method described by the snippet of code above are the shift eys, capsloc , ScrollLoc , NumLoc , F11, F12, the CTRL eys, and the ALT eys. There are other methods available for those eys and ey comb-

Extended eys, using read ey. followed by an is depressed. such as this:

li e the function eys, or the arrow eypad may be read These eys generate a #0 character as their first character extended scan code. These vary depending upon which ey To read extended eys, we would have to use a structure

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 eys (any ey not on the basic eyboard). 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 bac ground 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 blac bac ground. Also, the text which shows what eys we have pressed are should be a dull lightblue on a blac bac ground. The exception to the last statment is that any standard ey identity in the statement should be in a bright green, and any extended ey 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 ey. You pressed the SPACE ey. You pressed the LEFT ey. You pressed the Bac space ey. Note: Some of the eys on the eyboard are singular control characters. Loo at your ASCII chart. For example, the TAB ey is actually ASCII character #9, and character #9 should be recognized as such... F1, SPACE, LEFT, and BACKSPACE are the ey identities as I described earlier... 4) Ma e sure that in order to terminate the program, the ESC ey gets pressed 5 times, not consecutively (be sure to indicate the ESC ey 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 Quic Sort and ShellSort algorithms. Please by all means send comments to ggrotz@2sprint.net. 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... eep in mind to be as efficient as possible in coding...I figure that many will try simply writing those frames out. program part14; uses crt;

var esccount: byte; chr: char; procedure setitup; begin { green border of 4 units } textbac ground(green); clrscr; window(5,5, 76, 21); { red border of one unit } textbac ground(red); clrscr; window(6,6,75,20); { set up blac bac ground } textbac ground(blac ); clrscr; { write eypress statement } highvideo; textcolor(lightcyan); writeln('Keypress Detector (Press ESC 5 times to quit)'); { preserve Keypress detector title. } window(6,7,75,20); end; function status(char1: char;var esccount: byte): string; var char2: char; begin if char1 = #0 then begin char2 := read ey; 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'; end; end

else case char1 of #8: status := 'Bac space'; #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 li e this in this case } end; #32: status := 'SPACE'; else status := char1; end; end; procedure endit; var i: byte; begin window(1,1,80,25); gotoxy(1,1); for i := 1 to 25 do begin delline; delay(100); end; end; begin esccount := 0; setitup; while esccount <> 5 do begin chr := read ey; normvideo; textcolor(lightblue); write('You pressed the '); highvideo; textcolor(green); write(status(chr, esccount)); normvideo; textcolor(lightblue); writeln(' ey.'); end; endit; end. 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 wor .

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 ey. The idea of a sorting ey 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; var thearray: array[1..20] of integer; temp: integer; i, j: integer; begin randomize; { generate numbers for array and write them as unsorted } write('The unsorted array: '); for i := 1 to 20 do begin thearray[i] := random(50) + 1; write(thearray[i], ' '); end; writeln; { the bubblesort. } for i := 1 to 20 do for j := i+1 to 20 do if thearray[i] > thearray[j] then { compare and swap } begin temp := thearray[i]; thearray[i] := thearray[j]; thearray[j] := temp; end; write('The sorted array: '); for i := 1 to 20 do write(thearray[i], ' '); writeln; end. 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 wor s, 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

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) 2) 3) 4) i i i i = = = = 1; 1; 1; 1; j j j j = = = = 2; 3; 4; 5; Position1 Position1 Position1 Position1 = = = = 1; 1; 1; 1; Position2 Position3 Position4 Position5 = = = = 3; 2; 5; 4; 1 1 1 1 > > > > 3 2 5 4 = = = = false; false; false; false;

5) i = 2; j = 3; Position2 = 3; Position3 = 2; 3 > 2 = true; we swap the two values...so 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 values...so the resultant array is... 1 2 3 4 5

11) Effective termination of loop; As we can see, we too 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 <. Quic Sort ========= This is a much faster recursive solution for sorting than the bubblesort. It ma es use of a pivot mar er, which moves according to what exactly is contained in the array. It also will ma e use of a "divide and conquer" approach. Here is a short example... program example_of_quic sort; var thearray: array[1..20] of integer; i, j, PIVOT, t: integer; procedure quic sort(L, R: integer); begin if L < R then begin i := L + 1; j := R; PIVOT := thearray[L]; repeat while thearray[i] <= PIVOT do inc(i); while thearray[j] > PIVOT do dec(j); if i < j then begin

t := thearray[i]; thearray[i] := thearray[j]; thearray[j] := t; end; until i > j; thearray[L] := thearray[j]; thearray[j] := PIVOT; quic sort(L, j-1); quic sort(i, R); end; end; begin randomize; write('The unsorted array is: '); for i := 1 to 20 do begin thearray[i] := random(50) + 1; write(thearray[i], ' '); end; quic sort(1, 20); write('The sorted array is: '); for i := 1 to 20 do write(thearray[i], ' '); end. 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 quic sort wor s... eep in mind that quic sort is about as inefficient as bubblesort with smaller sets...Once you get into larger sets, quic sort beats bubblesort hands down -- it more intelligently see s 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) Quic sort called for left side of 1, and right side of 3. Then quic sort

for left side of 5, and right side of 5. Essentially, we eep cutting the array in half based by where the pivot lands. We will process the quic sort for the left side as 2a) and the quic sort for the right side as 2b). 2a) Our number set for this instance of quic sort 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. Quic sort 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 readily. 2b) 5 < 5 = false so terminate quic sort. 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 quic sort pictured sorts in ascending order. To ma e 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); ShellSort ========= This is a non-recursive sort that performs close in performance to quic sort. 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; var thearray: array[1..20] of integer; i: integer; procedure shellsort(n: integer); const m = 3; { total number of sort passes } var i: array[1..m] of integer; j, , p, s, t, incr: integer; begin i[m] := 1; for j := m - 1 downto 1 do i[j] := 2 * i[j];

for j := 1 to m do begin incr := i[j]; for := 1 to incr do begin s := incr + ; while s <= n do begin p := s; t := thearray[p]; thearray[ -incr] := t; while t < thearray[p-incr] do begin thearray[p] := thearray[p-incr]; dec(p, incr); end; thearray[p] := t; inc(s, incr); end; end; end; end; begin randomize; write('The unsorted array is: '); for i := 1 to 20 do begin thearray[i] := random(50) + 1; write(thearray[i], ' '); end; writeln; shellsort(20); { 20 is high end of array } write('The sorted array is: '); for i := 1 to 20 do write(thearray[i], ' '); writeln; end. To get it to sort in ascending order instead of descending order, change the line: while t < a[p-inc] do to while t > a[p-inc] do Practice ======== 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. Loo at the ASCII chart...It sees characters as numbers... a < b < c ...et al... With strings, be sure to sort them case insensitively, especially, if we alphabetize a list or something li e that.... Next Time

========= We will cover methods of searching an array for data. send comments to ggrotz@2sprint.net 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 li e the bubblesort is for sorting arrays. It wor s 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; var a: array[1..15] of integer; i, number: integer; found: boolean; begin randomize; found := false; write('The array is: '); for i := 1 to 15 do begin a[i] := random(10) + 1; write(a[i], ' '); end; writeln; writeln('Enter a number, and we shall see if it is in the array'); readln(number); i := 1; while i <= 15 do begin if a[i] = number then begin writeln(number, ' was found at position ', i); { i := 15; can be done to brea the loop on the first encounter of the one if we are interested in just whether the number exists in the array } found := true; end; inc(i); end; if not found then writeln(number, ' was not found in the array.'); end. Binary Search

============= The other type of algorithm we will discuss is the binary search. It is to searching an array what the quic sort is to sorting an array. Basically, what it will do is eep 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 ma e 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; const first = 1; last = 15; var 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); var midpoint: integer; begin if lowend > highend then found := false else begin midpoint := (lowend + highend) div 2; if number = a[midpoint] then begin found := true; location := midpoint; end 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); end; end; begin randomize; found := false; write('The array is: '); for i := 1 to 15 do begin { to insure we have sorted data } a[i] := 3*i; write(a[i], ' '); end; writeln; writeln('Enter a number, and we shall see if it is in the array'); readln(number);

bsearch(number, location, first, last, found); if found then writeln(number, ' was found at position ', location) else writeln(number, ' was not found. '); end. As you may be able to pic 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 drawbac 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 ggrotz@2sprint.net. As I loo at my formatted version, so far there has been 116 pages written of this tutorial, from part 1, not counting this one. As I loo through my line count figures, this one is the smallest. (interesting facts, huh?) I will as for feedbac on how I do with regards to any of the pointer related texts coming up. Please do that. Than 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; var a: array[1..1000] of integer; number: integer; c, i, j, PIVOT, t: integer; found: boolean; location: integer; outfile: text; procedure quic sort(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... } begin if L < R then begin i := L + 1; j := R; PIVOT := A[L]; repeat while a[i] <= PIVOT do inc(i); while a[j] > PIVOT do dec(j); if i < j then begin t := A[i]; A[i] := a[j]; A[j] := t; end; until i > j; a[l] := A[j]; a[j] := PIVOT; quic sort(L, j-1); quic sort(i, R); end; end; procedure bsearch(number, lowend, highend: integer; var location: integer; var found: boolean); var midpoint: integer; begin if lowend > highend then found := false else begin midpoint := (lowend + highend) div 2; if number = a[midpoint] then begin found := true; location := midpoint;

end 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); end; end; begin randomize; assign(outfile, 'LOCATION.TXT'); rewrite(outfile); for c := 1 to 1000 do a[c] := random(10000) + 1; quic sort(1, 1000); for c := 1 to 500 do begin number := random(10000) + 1; bsearch(number, 1, 1000, location, found); if found then writeln(outfile, c, ') ', number, ' was found at position ', location, '.'); end; close(outfile); end. Intro ===== 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 li e feedbac 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 stac , or any TSRs that may be on the system at any one time. The data stac 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 stac , 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 loo 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; type strptr = ^string; var a, b, c: strptr; begin new(a); a^ := 'Turbo Pascal'; new(b); b^ := 'is fun!!!!!!'; writeln('a is now "', a^, '".'); writeln('b is now "', b^, '".'); new(c); c^ := a^; a^ := b^; b^ := c^; dispose(c); writeln; writeln('a is now "', a^, '".'); writeln('b is now "', b^, '".'); dispose(a);dispose(b); end. 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 variable. 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 VALUE OF THAT LOCATION IN MEMORY!!!**. 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 ma e 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. Ma e it address a reserved word called nil. If I want pointer p to not point to anything, then I would write: p := nil; Whatever you do, do not set a pointer to nil before you dispose of the pointer. The system will lose trac of the variable on the heap, and therefore is LOST to the system. Keep this issue in mind as we do our wor and examples with pointers. Loo 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 li e the procedure or function declaration. It is very useful to minimize code usage, and ma e things multi-functional. This sheds some light into the inner wor ings of the write command, as was wondered in c.l.p.b. a little while ago... program pointer_two; type compfunc = function(a, b: integer):integer; var compute: compfunc; first, second: integer; choice: char; {$F+} function compadd(a, b: integer): integer; begin compadd := a + b; end; function compsub(a, b: integer): integer; begin compsub := a - b; end; function compmult(a, b: integer): integer; begin compmult := a * b; end;

function compdiv(a, b: integer): integer; begin compdiv := a div b; end; {$F-} begin @compute := nil; write('Enter a first number: '); readln(first); write('Enter a second number: '); readln(second); write('Enter +, -, *, or / as a operation: '); readln(choice); case choice of '+': compute := compadd; '-': compute := compsub; '*': compute := compmult; '/': compute := compdiv; else writeln('Enter a correct option.'); end; compute(first, second); writeln(first, ' ', choice, ' ',second,' = ',compute(first, second)); end. 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 li e 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, et.al), 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 exitproc. ExitProc ======== 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 wor 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 wor with it. The rest is

basically documented. program pointer_three; var exitsave: procedure; afile: text; {$F+} { must be far } procedure myexit; {must be parameterless} begin if exitcode <> 0 then writeln('There was a problem.'); writeln('We are exiting...'); end; {$F-} begin @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, '()()()().!!!'); reset(afile); {2 - here is a division by zero.} { writeln(3 / 0); }

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 eep in line with the topic of this tutorial.

Next Time ========= We will cover the concept of lin ed lists, or chained lists. Practice very heavily the idea of pointers, because you will need use of them

exitproc := @exitsave; { set the original exit procedure bac end.

again in the next section. E-mail ggrotz@2sprint.net with your comments. Turbo Pascal for DOS Tutorial by Glenn Grotzinger Part 18: Chained or Lin ed lists, the lin ed list sort copyright(c) 1995-96 by Glenn Grotzinger Here is a solution from last time... {$M 64000,0,655360} program part17; uses crt; type arptr = ^atype; atype = array[1..15000] of integer; var a: arptr; number: integer; c, i, j, PIVOT, t: integer; found: boolean; location: integer; outfile: text; procedure quic sort(L, R: integer); { nothing to say we couldn't sort the data... } begin if wherex = 79 then begin gotoxy(1, wherey); clreol; end else write(#254); if L < R then begin i := L + 1; j := R; PIVOT := A^[L]; repeat while a^[i] <= PIVOT do inc(i); while a^[j] > PIVOT do dec(j); if i < j then begin t := A^[i]; A^[i] := a^[j]; A^[j] := t; end; until i > j; a^[l] := A^[j]; a^[j] := PIVOT; quic sort(L, j-1); quic sort(i, R); end; end;

procedure bsearch(number, lowend, highend: integer; var found: boolean); var midpoint: integer; begin if lowend > highend then found := false else begin midpoint := (lowend + highend) div 2; if number = a^[midpoint] then begin found := true; location := midpoint; end else if number < a^[midpoint] then bsearch(number, lowend, midpoint-1, found) else if number > a^[midpoint] then bsearch(number, midpoint+1, highend, found); end; end; begin if maxavail - sizeof(a) > 0 then new(a) else begin writeln('Out of memory!'); halt(1); end; randomize; assign(outfile, 'LOCAT2.TXT'); rewrite(outfile); for c := 1 to 15000 do a^[c] := random(25000) + 1; quic sort(1, 15000); for c := 1 to 15000 do begin number := random(25000) + 1; bsearch(number, 1, 15000, found); if found then writeln(outfile, c, ') ', number, ' was found at position ', location, '.'); end; dispose(a); close(outfile); end. Now we will discuss the idea of the lin ed list or chained list. Basically, there are 4 types of lin ed lists that we can discuss, the singularly lin ed linear list (SLLL), singularly lin ed circular list (SLCL), doubly lin ed linear list (DLLL), and the doubly lin ed 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 lin ed 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 lin ed list it is. We will loo at an example of the use of SLLL's, observe the advantages of lin ed lists through what we do with the example, and study the things to loo 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 lin forward to the next lin . A sample record structure for an SLLL follows below. type nodeptr = ^node; nodetype = record ourinfo: integer; nextnode: nodeptr; end; An example of an SLLL conceptually is something li e this: NODE-->NODE-->NODE-->NODE-->NODE-->NODE-->NODE-->nil 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 ta e into consideration with wor ing with any lin ed or chained list. 1) We need to ma e 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 operation. 3) NEVER NEVER NEVER WORK DIRECTLY WITH THE HEAD TRACKING POINTER WE ORIGINALLY ALLOCATE UNLESS WE DESIGN OUR CODE COMPLETELY AROUND RECURSION. As a result, you will cause what is called a heap lea . This is where the pointer loses trac of where the data it points to is stored. Logically, loo ing at the model above, if we disconnect one of the pointers, represented by the arrows, we lose trac of the rest of the list, or chain. Wor with a temporary pointer for each lin ed 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 lin we can place data data, remove data *A LOT* more data

ed lists: We will see here, that the data is not static, independently at different positions WITHOUT shuffling in the same fashion, and definitely are capable of handling than 64KB, since we only have a 4 byte stub in that area.

Ta e a good loo at this program and see to understand EXACTLY how it wor s. As you will remember from last time, a direct assignment to a pointer is ma ing 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 loo at this program, see to answer the following questions ta ing any "house eeping 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 lin ed list that the author could thin of. the variable used throughout called p, and sometimes t, are temporary variables. Note: This probably isn't completely optimized. } type nodeptr = ^nodetype; nodetype = record thenum: integer; nextnode: nodeptr; end; var list: nodeptr; procedure buildlist(var list: nodeptr); { This procedure builds up the list for us. } var p: nodeptr; i: integer; begin 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 begin new(p^.nextnode); 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 lin ed list. Anything, in reality does not become a pointer until the new function is used. } end; p^.nextnode := nil; { set last pointer to nothing } end;

procedure writelist(list: nodeptr); { This procedure serves the function of writing out the list for us to the screen when called } var p: nodeptr; begin p := list; while p <> nil do { while we're not at the end of the list } begin write(p^.thenum:3); p := p^.nextnode; end; end; 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. } var p: nodeptr; begin new(p); p^.thenum := 20; p^.nextnode := list; if p^.nextnode = nil then { maintenance of the end of list mar er } p^.nextnode^.nextnode := nil; list := p; end; 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 } var p, t: nodeptr; begin p := list; t := p^.nextnode^.nextnode; dispose(p^.nextnode); p^.nextnode := t; end; procedure insertbythree(var list: nodeptr); { This procedure moves through the lin ed list and determines where the new nodes needs to be inserted, then calls the insert function written before } var p: nodeptr; i: integer; begin p := list;

i := 1; while p <> nil do begin p := p^.nextnode; inc(i); if i mod 3 = 0 then insert(p^.nextnode); end; end; procedure findanddispose(var list: nodeptr); { This procedure finds and disposes the first number in the list divisible by 8. } var p, t: nodeptr; begin p := list; while (p^.nextnode <> nil) and (p^.nextnode^.thenum mod 8 <> 0) do p := p^.nextnode; delete(p); end; procedure cleanup(var list: nodeptr); { This procedure removes the list from memory. } var p, t: nodeptr; begin p := list; while p <> nil do begin t := p^.nextnode; dispose(p); p := t; end; end; begin clrscr; writeln;writeln; writeln('Free memory available: ', memavail, ' bytes.'); buildlist(list); writeln('Free memory available: ', memavail, ' bytes.'); write('The list is: '); writelist(list); writeln;writeln; writeln('Now we will insert a 20 in every third position'); insertbythree(list); writeln('Free memory available: ', memavail, ' bytes.'); write('The list is: '); writelist(list); writeln;writeln; write('Now we will search for and ta e the first # divisible by 8 '); writeln('out of the list.'); findanddispose(list); writeln('Free memory available: ', memavail, ' bytes.');

write('The list is: '); writelist(list); writeln;writeln; writeln('Now we will be good little programmers and clean up our list. :)'); cleanup(list); writeln('Free memory available: ', memavail, ' bytes.'); end. Hopefully, you can go through here, and follow the logic (actually, you will need to do that successfully to understand what is going on). Lin ed 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 thin through the logic of using pointers in lin ed 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 s ill 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) to 2) 3) of Create 1000 nodes in an SLLL that consists of integers numbered from 1 1000. Print this list to a text file. Reverse the direction of the lin ed list. By doing this, I mean, instead the lin ed list loo ing li e this conceptually: NODE-->NODE-->NODE-->nil ma e it loo li e this:

nil<--NODE<--NODE<--NODE DO NOT CREATE ANOTHER LINKED LIST IN MEMORY. USE THE CURRENT ONE YOU HAVE BUILT. 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: Thin 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 loo s li e this: NODE--->NODE--->NODE--->NODE--->NODE ^ | | V NODE NODE ^ | | V NODE<---NODE<---NODE<---NODE<---NODE 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 ma e it that way. DLLL Concepts ============= This type of lin ed list uses a different li e this: type nodeptr = ^node; node = record ourinfo: integer; lastnode, nextnode: nodeptr; end; Conceptually, a DLLL loo s li e 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 loo s much li e the SLCL diagram, but with double lin s much li e the DLLL diagram, instead of single lin s. Final Thoughts on Lin ed 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 nowledge 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 wor 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 Lin ed 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: IF WE ARE TO PUT NODE HERE GET DATA TO PUT INTO NODE (read data from file, or elsewhere)

ind of record format. It loo s

WHILE DATA IS NOT DONE DO BEGIN IF NIL LIST THEN PUT NODE HERE ELSE PUT NODE HERE = NEWNAME <= NODE^.NAME IF WE ARE TO PUT NODE HERE THEN BEGIN new(p); SET DATA TO NODE p^.nextnode := LIST; if p^.nextnode = nil then p^.nextnode^.nextnode = nil; list := p; END ELSE INSERT(LIST^.NEXTNODE); IF WE ARE TO PUT NODE HERE THEN BEGIN GET INFO. DO NOT PUT NODE HERE (boolean variable set to false). END. END. This general code does wor 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 dis swapping. For practice: Do things as I have suggested throughout this document. No real practice problem. Next Time ========= We will tal about binary trees. Be sure to send comments to ggrotz@2sprint.net. I will say again that I apologize for the long period of time it too 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 lin ed 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 loo something li e this: type nodeptr = ^node; node = record ourinfo: integer; left, right: nodeptr; end;

NODE _________/ \_________ / NODE ____/ \____ / NODE ____/ \____ / nil \ nil \ nil / nil \ NODE ____/ \____ \ NODE ____/ \____ / NODE ____/ \____ / NODE ____/ \____ / nil \ 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 ma e 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 loo s now li e this: 27 \ 54 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 loo s li e this:

Conceptually (my best low ASCII rendering, loo something li e this. We can see from those two directions are any numerous. As the path ( nowing, it is probably garbled,

trust me!), a binary tree would below that the possible paths of with the lin ed lists, nil ends but....).

27 \ 54 / 32 d) 18 < 27. So we would move to the left. So our tree loo s li e this: 27 / \ 18 / 32 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 loo s li e this: 27 / \ 18 / 5 32 / 54 54

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 constructed. 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 loo 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 ta en 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 PROCEDURE ON LEFT SIDE OF TREE PERFORM ACTION ON ROOT NODE (the top node of the tree) PERFORM PROCEDURE ON RIGHT SIDE OF TREE We will see this idea be paramount in all the code we use. If you have noticed in your previous wor 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; type

nodeptr = ^node; node = record somedata: integer; left, right: nodeptr; end; var tree: nodeptr; afile: text; i: integer; procedure deletetree(var tree: nodeptr); { This procedure is a function designed to remove a binary tree from memory } begin { Given the bac ground information, and other procedures as examples, you should be able to come up with this one } end; 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 } begin if tree = nil then begin new(tree); tree^.somedata := anumber; tree^.left := nil; tree^.right := nil; end else begin if anumber > tree^.somedata then inserttree(anumber, tree^.right); if anumber < tree^.somedata then inserttree(anumber, tree^.left); end; end; 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. } begin { Given the bac ground information and other procedures as examples, you should be able to come up with this one. } end; procedure writeouttree(tree: nodeptr); { This procedure outputs a binary tree. } begin if tree <> nil then begin writeouttree(tree^.left); write(afile, tree^.somedata, ','); writeouttree(tree^.right); end; end;

begin randomize; tree := nil; { the last statement is required for the nil mar ers set up via the BST procedures listed above } assign(afile, 'BTEST.TXT'); rewrite(afile); 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); writeln(afile); writeln(afile, 'I will now output the binary tree for the example.'); writeouttree(tree); writeln; close(afile); writeln('Now deleting tree out of memory'); deletetree(tree); writeln(memavail, ' bytes available after tree deletion.'); end. This is a whole program for wor ing with a binary tree. I purposely left out 2 of the procedures, because the bac ground information I gave earlier, plus pointer logic you should have learned from part 18 should allow you to logically come up with it. Do li e 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 s ill 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 quic ly with any ind of data -- wor ing with arrays have a small way of being ludgy. 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 eyboard, 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. Note ---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 dis usage.

2) This program should be friendly to the user, letting the user now 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 now many people may wish to someday release their creations to the public, or ma e 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. <grin> Next Time --------I will cover several short random topics, which some of which may be suggested by those who e-mail me. I now I will cover hoo ing interrupts, calling an interrupt procedure, and lin ing 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 li e to see? E-mail me. Also, I haven't gotten any comments from anyone about how I am doing. Is anyone interested? Email ggrotz@2sprint.net. 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 bac ground explanation. PART19A.PAS ----------This was the program written to generate the binary data file for use with the actual word chec er. A text file was written to facilitate reading in the data, one word per line, to ease implementation. program part19a; var infile: text;

outfile: file; datastring: string[14]; begin assign(infile, 'SOMEWRDS.TXT'); assign(outfile, 'SOMEWRDS.DAT'); reset(infile); rewrite(outfile, 1); readln(infile, datastring); while not eof(infile) do begin bloc write(outfile, datastring, sizeof(datastring)); writeln(datastring); readln(infile, datastring); end; bloc write(outfile, datastring, sizeof(datastring)); writeln(datastring); close(infile); close(outfile); end. PART19B.PAS ----------This is the actual word chec er. It is made so the words typed into the eyboard do not have to be case sensitive with reference to the datafile. All prevalent errors have error-chec ing code. program part19b; type strtype = string[14]; nodeptr = ^node; node = record str: strtype; left, right: nodeptr; end; var tree: nodeptr; datastring: strtype; infile: file; procedure opendatafile(var infile: file; afile: string); begin assign(infile, afile); {$I-}reset(infile, 1);{$I+} if IOResult <> 0 then begin writeln('Terminating. ', afile, ' does not exist!'); halt(1); end; end; function upstr(instr: strtype):strtype; var i: byte; tempstr: strtype; begin tempstr := '';

for i := 1 to length(instr) do tempstr := tempstr + upcase(instr[i]); upstr := tempstr; end; procedure memoryerror; begin writeln('Out of memory.'); halt(1); end; procedure deletetree(var tree: nodeptr); begin if tree <> nil then begin deletetree(tree^.right); deletetree(tree^.left); dispose(tree); end; end; procedure inserttree(datastring: strtype; var tree: nodeptr); begin if tree = nil then begin if memavail - sizeof(tree) > 0 then new(tree) else memoryerror; tree^.str := datastring; tree^.left := nil; tree^.right := nil; end else begin if upstr(datastring) > tree^.str then inserttree(upstr(datastring), tree^.right); if upstr(datastring) < tree^.str then inserttree(upstr(datastring), tree^.left); end; end; procedure buildsearch(var infile: file; var tree: nodeptr); var datastring: strtype; begin bloc read(infile, datastring, sizeof(datastring)); while not eof(infile) do begin inserttree(datastring, tree); bloc read(infile, datastring, sizeof(datastring)); end; inserttree(datastring, tree); close(infile); end; procedure searchtree(datastring: strtype; tree: nodeptr); begin if tree = nil then writeln(datastring, ' was not found in the data file.')

else begin if datastring = tree^.str then writeln(datastring, ' was found in the data file.') else begin if datastring > tree^.str then searchtree(datastring, tree^.right); if datastring < tree^.str then searchtree(datastring, tree^.left); end; end; end; procedure promptwrite; begin writeln('Type QUIT to terminate.'); writeln('Type a word, and we''ll see if it''s in the database.'); writeln; end; begin writeln('Database chec er.'); writeln; opendatafile(infile, 'SOMEWRDS.DAT'); buildsearch(infile, tree); promptwrite; readln(datastring); while (upstr(datastring) <> 'QUIT') do begin searchtree(upstr(datastring), tree); promptwrite; readln(datastring); end; deletetree(tree); end. The written explanation of the delete code that was written in the example, to completely remove the BST from memory... We now that in a pointer-lin ed structure if we remove a node that has lin s assigned to it, that we will cause a heap lea 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 wor ing bac 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 it. Contents for Part 20 ==================== Since this part contains many random varied topics, a table of contents is in order.... A) Lin ing OBJ code into Pascal programs. B) Including ASM or inline statements in Pascal programs. C) Hoo ing an interrupt in pascal programs.

D) Calling an interrupt procedure. E) Conditional Compiler Compilation. Lin ing 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 ma e any calls to any C libraries, or ma e any calls to a DLL. A description of what's going on... TEST1.H 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 li e 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 now it is not li ed, 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 M@`D`!U1%4U0Q+D..B!\````;5$,X-B!";W)L86YD(%1U<F)O($,K*R`S+C`P MD8@/``#I=G%"(`=415-4,2Y#3H@&``#E`0``C(@5``#F!&YU;3($"@8`!&YU M;3$$"@@`2(@&``#E`08`AH@@``#F!F%N<W=E<@0"_O\$;G5M,00*"``$;G5M M,@0*!@"DB`4``.<:`'*(!0``YQH`<H@*``#N`0``!@`4`&6(`P``Z8R(!0`` MZ@$+?98H```%7U1%6%0$0T]$105?1$%4001$051!!%]"4U,#0E-3!D1'4D]5 M4-&8!P`H&@`"`P$9F`<`2```!`4!#Y@'`$@```8'`0N:!@`(_P+_`U60$@`` M`0M!1$0R3E5-0D524P```#N("P``XQ@````C!`4`1H@%``#A&!ABH!X``0`` M58OL@^P"BT8(`T8&B4;^BT;^ZP"+Y5W*!`"ZB!```.@`!U1%4U0Q+D-V<4(@ <3I03```!`@````8`!@`'``\`"``4`!B*`@``=``` ` end TEST2C.PAS {$F+} program test2c; {$L TEST1.OBJ} function add2numbers(num1, num2: integer): integer;external; begin writeln(add2numbers(2, 3)); end. 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 lin 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 case. 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 ta es the data to ASM, it can use straight assembler code as as well. Ta e a loo at the short procedure moc up below. procedure dothis; assembler; asm { assembler code goes in here } end; asm is a eyword that may be used anywhere in code, which tells the compiler to start loo ing for assembler code instead of pascal code. The assembler eyword is one that can optionally be added to ma e the compiler loo for an asm eyword instead of the begin at a beginning of a procedure or function. An inline statement wor s much li e a function call. It ma es it so that machine language can be used in pascal code. For example (the hex codes are garbage I'm ma ing up so don't use this in a real program), an inline statement can loo li e this... inline($80/$23/$14/$36); The codes can go on infinitely, and are separated by /'s... References ========== There is so much specific information out there that is required to now 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 nown 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 nown, 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 download. Hoo ing 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 hoo an interrupt. Hoo ing 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 hoo 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 bac ground, for the PC timer, approximately 18.2 timer tic s occur every second, or 0.054945 seconds per tic . Ultimately, you can see by running this program, that all it does is wait for you to press a ey. program writehello; uses dos; var intsave: procedure; counter: integer; {$F+} procedure count; interrupt; begin inc(counter); inline($9C); { prods an interrupt } intsave; end; {$F-} begin writeln('Press a ey.'); getintvec($1C, @intsave); { get current int. procedure. } setintvec($1C, @count); { replace it with our procedure. } readln; setintvec($1C, @intsave); { save the regular int. procedure bac writeln(counter, ' timer tic s have passed.'); end. 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 eyword must be placed on the procedure definition, and the procedure definition must be defined as far, li e 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: Hoo ing an interrupt is essentially a dangerous proposition, to a

degree. Be sure that you are careful and learn what happens on each interrupt and now what the interrupt does before you try to hoo it. Doing this actually CHANGES the hardware behavior of your system. If attempting to hoo 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 hoo ing a replacement procedure on the interrupt for the hard dis , and I had garbled output from the directory commands until I rebooted.... Basically, BE VERY CAREFUL IN HOOKING INTERRUPTS. 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 wor s 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 loo s li e: type 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); end; 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 record. 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 bloc ed array string. A string must end with a "$" when using this function. program writetest; uses dos; type strtype = array[0..255] of char; procedure writestring(astring: string); var tstr: strtype; i: byte; regs: registers; begin i := 1; repeat tstr[i] := astring[i]; inc(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 } end; begin writestring('Hello all!'); end. 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; begin write('Hello all!'); end. 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 li e 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 there. 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 nowledge....defaults mar ed with ~ + state -------FPU on 286 code gen. on ~FPU emulation on

$N $G $E

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 loo 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

- state ---------~FPU off ~286 code gen. off FPU emulation off

generally. The definitions of these variables may be defined using the conditional compiler directives....

{$IFDEF <name>} {$IFNDEF <name> } {$ELSE} {$IFOPT <compiler directive>} {$ENDIF} {$DEFINE <name>} {$UNDEF <name>}

If item defined if item not defined ELSE directive If compiler opt.. END IF STATEMENT define a statement undefine a statement.

Functionally, these wor and compile the source code if certain things are true. For example, {$IFOPT N+} <some code> {$ENDIF} in some code will ma e 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; begin {$IFDEF CPU87} writeln('There is a math coprocessor in the system.'); {$ELSE} writeln('There is not a math coprocessor in the system.'); {$ENDIF} end. 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 eystro es 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. Bac ground information. Keyboard interrupt is $09. Interrupt occurs 2 times for every ey pressed. Notes: Unfortunately, in all attempts I've made, to ma e it TERMINATE exactly on 15 seconds, it would cause a nasty problem. The program will have to read in at least one ey after termination. Do not worry about this. Next Time ========= We use the BGI. Write comments to ggrotz@2sprint.net.

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.

The conditional compiler directives we need to

now are:

program part20; uses dos; const oneminute = 273; { # of timer tic s that happens in 15 seconds } var saveint09, saveint1c: procedure; timecounter, eycounter: integer; achar: char; timeup: boolean; {$F+} procedure counttime; interrupt; begin inc(timecounter); if timecounter = oneminute then timeup := true; inline($9C); saveint1c; end; procedure count eys; interrupt; begin inc( eycounter); inline($9C); saveint09; end; {$F-} begin timeup := false; timecounter := 0; eycounter := 0; getintvec($09, @saveint09); getintvec($1C, @saveint1C); setintvec($09, @count eys); setintvec($1C, @counttime); while timeup = false do read(achar); setintvec($09, @saveint09); setintvec($1C, @saveint1C); writeln('There were ', eycounter div 2, ' eys pressed.'); end. BGI Intro ========= This part is basically about using the Borland Graphics Interface. It is not generally recommended to use it -- use assembler instead to ma e it a lot quic er, and smaller.... but due to demand, and the ability to use the BGI for basic graphics, we will tal about use of the BGI. For heavy use of graphics, assembler is indeed better.... BGI loader ========== Here, I will describe how to ma e the BGI files functionally more useable than in their current state....Compile with loo ing for the external BGI

files is fine, but will run into an annoyance quic , especially with a distributed utility...it is easier to have the EXE available with the BGI files binded to it than to ma e a user eep trac 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 does...it converts binary files to OBJect files, which may be handled li e 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: BINOBJ VESA16.BGI VESA16.OBJ Vesa16driver 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 li e this, I recommend you write a unit to lin the BGI files in -- optionally, if you need fonts, you may write a unit to lin those in li ewise. Be sure to define your proper external procedures li e it needs to be done. Ma e use of meaningful names, as you will have to remember them to ma e 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 loo at some sample code... program video_example_1;{1} uses graph, bgivideo; var graphicsdriver, graphicsmode: integer; procedure errormessage(driver: string); begin writeln('There was an error: ', grapherrormsg(graphresult), driver); halt(1); end; begin {2}if (registerbgidriver(@attdriver) < 0) then errormessage('ATT'); if (registerbgidriver(@cgadriver) < 0) then errormessage('CGA'); if (registerbgidriver(@egavgadriver) < 0) then errormessage('EGA/VGA'); if (registerbgidriver(@hercdriver) < 0) then errormessage('Herc');

if (registerbgidriver(@pc3270driver) < 0) then errormessage('PC 3270'); {3}detectgraph(graphicsdriver, graphicsmode); graphicsdriver := Detect; {4}initgraph(graphicsdriver, graphicsmode, ''); {5}if GraphResult <> grO then begin writeln('Video error.'); halt(1); end; {6}repeat putpixel(random(getmaxx), random(getmaxy), random(getmaxcolor)); until eypressed; readln; {7}closegraph; end. 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 mar ed 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 chec ed. 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 wor ing right. {3} detectgraph detects the graphics card. it ta es 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 drivers...it will use whichever one it needs, and ultimately, the program will wor with all video modes. {4} initgraph() ta es 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 files....to get a full auto-detect capability, just ma e all the BGI files available....it 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. gro is the one which indicates that things are OK. {6} This is the real meat of the procedure. It eeps placing pixels of random position and color on the screen until a ey 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 ind of dimensional setup as the crt graphics screens do. The putpixel procedure ta es 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 ta es us bac 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; var graphicsdriver, graphicsmode: integer; i: integer; {1} {$L GOTH.OBJ} procedure Gothfont; external; procedure errormessage(driver: string); begin writeln('There was an error: ', grapherrormsg(graphresult), driver); halt(1); end; begin { This is the video load code } {2} if registerbgifont(@gothfont) < 0 then errormessage('Script font'); i := 100; settextstyle(DefaultFont, HorizDir, 1); setcolor(blue); outtextxy(20, i, 'This is the DEFAULT font.'); inc(i, textheight('T')+2); readln; {7} cleardevice; settextstyle(Gothicfont, horizdir, 2); setcolor(green); outtextxy(20, i, 'This is the GOTHIC font.'); readln; closegraph; end. {3} {4} {5} {6} This basically goes into graphic mode, and writes those two statements to the screen. We will go through the areas mar ed by {}'s.. {1} This is exactly li e I described before. This is how we load the CHR file to not ma e it separate. We do it exactly li e the BGI files, and set them up as external OBJ files. {2} registerbgifont wor s exactly li e registerbgidriver does...it registers the font into the program -- load fonts judiciously -- only if you need them.

{3} settextstyle() changes the font, direction, and size....things can be written out either horizontally, or vertically. Fonts letters are 8X8 pixels in size...so 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 ma es 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 mode. {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 fonts. 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. Loo at page 185 of the Turbo Pascal language guide for a list of all of the BGI commands. Conclusion ========== IMO, Borland made the BGI very hard to use. I have stumbled across many things that loo ed li e bugs in their system (I couldn't use their included script font). Beyond that, it wor s OK for light-duty graphics. Anything beyond that truly needs assembler. There is enough nowledge here to set up and ma e use of BGI (not withstanding 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 ================================ Ma e 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 ta es for arguments, the coordinates of the upper left hand corner, and the lower right hand corner. 2) Each square drawn successively is one pixel larger than the previous.... 3) Continually draw the squares until the user presses a ey. 4) The colors are randomly determined.

Loo at the program to get an idea of what I am loo ing for. Also please send comments bac to ggrotz@2sprint.net if it happens to not wor on your system for no readily apparent reason. I need to get an idea of how well these sample video routines wor . Next Time ========= Things will be indeterminate. I will be covering object-oriented programming. As I have not set down and figured out how many parts object-oriented programming will ta e to cover, I do not now. E-mail ggrotz@2sprint.net 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 } var graphicsdriver, graphicsmode: integer; x1, x2, y1, y2: integer; procedure errormessage(driver: string); begin writeln('There was an error: ', grapherrormsg(graphresult), driver); halt(1); end; begin randomize; if (registerbgidriver(@attdriver) < 0) then errormessage('ATT'); if (registerbgidriver(@cgadriver) < 0) then errormessage('CGA'); if (registerbgidriver(@egavgadriver) < 0) then errormessage('EGA/VGA'); if (registerbgidriver(@hercdriver) < 0) then errormessage('Herc'); if (registerbgidriver(@pc3270driver) < 0) then errormessage('PC 3270'); detectgraph(graphicsdriver, graphicsmode); graphicsdriver := Detect; initgraph(graphicsdriver, graphicsmode, ''); if GraphResult <> grO then begin writeln('Video error.'); halt(1); end; repeat 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) -loo at geometric properties of a rectangle } repeat

setcolor(random(getmaxcolor)); rectangle(x1, y1, x2, y2); inc(x2, 1);inc(y2, 1);dec(x1, 1); dec(y1, 1); delay(50); until ( eypressed) or (x1 <= 0); until eypressed; readln; closegraph; end.

Finale ====== Ultimately, it was decided that object-oriented programming will not be covered at this time for the tutorial. I apologize. Note ==== Be sure to read license.txt!

As in to do

you can see in this example, it's always good to have a good bac ground analytical geometry to be able to do graphics well. It is good for you find a mathematical reference and learn a few concepts of it, if you not already now something about it.

You might also like