[Back to FAQ SWAG index] [Back to Main SWAG index] [Original]
ANSWERS TO FREQUENTLY ASKED PASCAL QUESTIONS
============================================
1...
Q. How do I pass an error level code when my program finishes?
A. The halt procedure takes an optional parameter of type word. Thus -
halt(1);
terminates the program with an errorlevel of 1. If halt is used without
a parameter it is the same as -
halt(0);
Note: When a program is terminated using the halt procedure any exit
procedure that has previously been set up is executed.
2...
Q. How do I empty the keyboard buffer?
A. There are several ways that this can be achieved. However the safest
is -
while Keypressed do ch := ReadKey;
This requires that a variable ch of type char is declared and the crt
unit be used. To do it without using a variable -
while Keypressed do while ReadKey = #0 do;
or if using TP6 with extended syntax enabled -
while KeyPressed do ReadKey;
If you do not wish to incur the substantial overhead involved with the
use of the CRT unit and there is no requirement for the program to run
under a multi-tasker -
var
head : byte absolute $40:$1c;
tail : byte absolute $40:$1e;
tail := head;
3...
Q. When I redirect the screen output of my programs to a file the file is
empty and the output still appears on the screen. What am I doing
wrong?
A. You are probably using the CRT unit and its default method of writing
to stdout is by direct screen writes. In order to enable output to be
redirected all writes must be done by DOS. Setting the variable
DirectVideo to false has no effect on redirection as all it does is use
the BIOS for screen writes - not DOS.
To enable redirection you must not use the CRT unit
OR
assign(output,'');
rewrite(output);
This will make all output go through DOS and thus can be redirected if
desired. To restore the default situation -
AssignCRT(output); rewrite(output);
4...
Q. How do I make a string that is lower or mixed case all uppercase?
A. There are several ways to convert lower case characters to upper case.
Here are some of them.
As a procedure (excluding asm code this is the fastest way)
procedure StrUpper(var st: string);
var x : byte;
begin
for x := 1 to length(st) do
st[x] := UpCase(st[x]);
end;
As a function (slower but sometimes more convenient) -
function StrUpper(st: string): string;
var x : byte;
begin
StrUpper[0] := st[0];
for x := 1 to length(st) do
StrUpper[x] := UpCase(st[x]);
end;
Both the above are suitable for the English language . However from
version 4.0 onwards, DOS has had the facility to do this in a way that
is country (language) specific. I am indebted to Norbert Igl for the
basic routine. I have modified his code slightly. For the anti-goto
purists this is a good example of a goto that is convenient, efficient,
self-documenting and structured. The dos calls would make this method
the slowest of all.
function StrUpper(s: string): string;
{ Country specific string-to-uppercase conversion. Requires DOS unit }
label
fail;
var
regs : registers;
x : byte;
begin
if lo(DosVersion) >= 4 then begin
with regs do begin
ax := $6521;
ds := seg(s);
dx := ofs(s[1]);
cx := length(s);
msdos(regs);
if odd(flags) then { the attempted conversion failed so }
goto fail;
end; { with }
end { if DOS >= 4.0 } else
fail:
for x := 1 to length(s) do
s[x] := UpCase(s[x]);
StrUpper := s;
end; { StrUpper }
5...
Q. When I include ANSI codes in a string and write that string to the
screen the actual codes appear on the screen, rather than the results
they are supposed to achieve.
A. In order for ANSI codes to be interpreted, screen writes must be
directed through DOS and there must have been a suitable driver loaded
via the config.sys file at boot time. All output can be directed
through DOS and the driver by -
Not using the crt unit
OR -
assign(output,'');
rewrite(output);
in which case ALL screen writes are "ANSI code sensitive"
OR -
You can set up write procedures that will be "ANSI code sensitive".
(You will need an initialisation procedure to set this up.)
var
ansi : text;
procedure AssignANSI(var ansifile : text);
begin
assign(ansifile,'CON');
rewrite(ansifile);
end; { AssignANSI }
procedure WriteANSI(var st: string);
begin
write(ansi,st)
end; { WriteANSI }
procedure WriteLnANSI(var st: string);
begin
writeANSI(st);
writeln(ansi);
end; { WriteANSI }
ObviousLy, if the ANSI.SYS driver (or an equivalent) is not installed
none of the above can work.
Setting the variable DirectVideo in the CRT unit to false will not
achieve the desired result as this merely turns off direct screen
writes and uses the BIOS for all screen output.
6...
Q. When I try to shell to DOS nothing happens. What am I doing wrong?
A. In order to be able to execute any child process there must be
sufficient memory available for it to load and execute. Unless you
advise differently at compile time, a Turbo Pascal program grabs all
available memory for itself when it is loaded. To reserve memory for a
child process use the compiler memory directive -
{$M 16384,0,0)
the default is -
{$M 16384,0,655360}
The first figure - StackMin - is the amount of memory to be allocated
for the stack:
Minimum is: 1024
Default is: 16384
Maximum is: 65520
The next figure - HeapMin -is the minumum amount of memory to be
allocated for the heap. If there is less memory available than this
figure the program will not load.
Minimum is: 0
Default is: 0
Maximum is: 655360 In practice it will be the amount of free
memory less the space required for the stack,
less the code space of the program. You should
set this to 0 unless your program uses the
heap. In that case, set it to the lowest
possible figure to prevent heap allocation
errors. In most cases it is best to leave it
at zero and do error checking within the
program for sufficient memory at allocation
time.
The last figure is the crucial on as regards child processes. It
should always be low enough to leave memory left over for a child
process and high enough not to cause problems for the program when
allocating heap memory.
Minimum is: HeapMin
Default is: 655360
Maximum is: 655360 If less than the requested amount is available
no error is reorted. Instead all available
memory is allocated for heap use.
7...
Q. How do I shell to DOS?
A. SwapVectors;
exec(GetEnv('COMSPEC','');
SwapVectors;
Read previous section on memory allocation.
I find that it is a good idea to write my own Exec function which will
do everything that is needed for me. I have it return an integer value
that is the DosError code.
function Exec(p1,p2: string);
begin
SwapVectors;
Dos.Exec(p1,p2);
SwapVectors;
Exec := DosError;
end;
This enables me to have a statement such as -
ReportError(Exec(GetEnv('COMPSEC'),''));
Now you can have an empty ReportError procedure or you can make it
report the error - whatever is suitable for you application.
8...
Q. When I execute a child process redirection does not work. Why?
A. Redirection of a child process's output only works if it is run under
another copy of the command processor. So -
exec('YourProg.exe',' > nul'); will not work but
exec(GetEnv('COMSPEC'),'/c YourProg > nul'); will work.
9...
Q. How do I read an errorlevel from a child process?
A. After executing a child process the errorlevel returned can be read
by calling the DosExitCode function which returns a word. The low
byte is the errorlevel. A full description is in the manual.
If the command interpreter is the child process and it in turn
executes a child process then the errorlevel of the second child
process cannot be read without resorting to some trickery.
10...
Q. When I read a text file that has lines exceeding 255 characters I
lose all those characters from the 256th one on each time there is a
line that exceeds that length. How can I prevent this?
A. Turbo Pascal's readln procedure reads a line up to the 255th
character then skips to the next line. To get around this you
should declare a buffer at least as large as the longest possible
line and then use the read procedure. The best size for the buffer
is a multiple of 2048 bytes.
const
BufferSize = 2048;
LineLength = 78;
type
textbuffer = array[1..BufferSize] of char;
var
st : string;
f : text;
buffer : textbuffer;
function ReadTxtLn(var tf: text; var s: string; max: byte): integer;
{ Reads a string of a maximum length from a text file }
var
len : byte absolute s;
begin
len := 0;
{$I-}
while (len < max) and not eoln(tf) do begin
inc(len);
read(tf);
end;
if eoln(tf) then
readln(tf);
ReadTxtLn := IOResult;
{$I+}
end; { ReadTxtLn }
begin
assign(f,filename);
reset(f);
SetTextBuf(f,buffer);
while not eof(f) and (ReadTxtLn(f,st,LineLength) = 0) do
writeln(st);
close(f);
end.
11...
Q. How do I convert nul terminated asciiz strings to Turbo Pascal
strings?
A. Here is a function that will do that -
function Asc2Str(var s; max: byte): string;
{ Converts an ASCIIZ string to a Turbo Pascal string }
{ with a maximum length of max. }
var starray : array[1..255] of char absolute s;
len : integer;
begin
len := pos(#0,starray)-1; { Get the length }
if (len > max) or (len < 0) then { length exceeds maximum }
len := max; { so set to maximum }
Asc2Str := starray;
Asc2Str[0] := chr(len); { Set length }
end; { Asc2Str }
12...
Q. How can I tell if a particular bit of a variable is set or not? How can
I set it? How can I turn it off? How can I make a large bit map and
then determine if a particular bit - say bit 10000 is on/of?
A. This question, or a variation of it, is one of the most commonly asked
questions in the echo and there are several ways of doing what is
wanted. None are necessarily right or wrong. The way I will describe
is designed to take up as little code/data space as possible. I do not
attempt to explain the theory behind these functions as this can be
obtained from any good book. Question 16 also contains valuable extra
help on the subject of truth tables.
The use of sets can be the best bit manipulation method if you have
control over the data being used. Here is an example of a byte variable
for a BBS program which sets various user access level flags.
Bit 0 = Registered User
1 = Twit
2 = Normal
3 = Extra
4 = Privileged
5 = Visiting Sysop
6 = Assistant Sysop
7 = Sysop
type
status_type = (Registered,
Twit,
Normal,
Extra,
Privileged,
VisitingSysop,
AssistantSysop,
Sysop);
status_level = set of status_type;
var
access_flags : status_level;
Let us assume you have someone who logs on and you wish to determine
his user access level. After reading access_flags from the user data
file -
if Sysop in access_flags then ....
To set the sysop flag -
access_flags := access_flags + [Sysop];
To reset the sysop flag -
access_flags := access_flags - [Sysop];
However on many occasions using a set may not be a suitable method.
You may simply need to know if bit 5 is set or not. Here is the method
that I consider the best -
function BitIsSet(var V, bit: byte): boolean;
begin
BitIsSet := odd(V shr bit);
end;
To set a bit -
procedure SetBit(var V: byte; bit: byte);
begin
V := V or (1 shl bit);
end;
To reset a bit -
procedure ResetBit(var V: byte; bit: byte);
begin
V := V and not(1 shl bit);
end;
To toggle (flip) a bit -
procedure ToggleBit(var V: byte; bit: byte);
begin
V := V xor (1 shl bit);
end;
Now a bit map can be made up from an array of bytes. If stored on the
heap you can test any bit up to number 524159 (zero based). Here's
how.
type
map = array[0..maxsize] of byte;
{ set maxsize to number of bits div 8 -1 needed in the bit map }
function BitSetInBitMap(var x; numb : longint): boolean;
{ Tests the numb bit in the bitmap array }
var m: map absolute x;
begin
BitSetInBitMap := odd(m[numb shr 3] shr (numb and 7));
end;
procedure SetBitInBitMap(var x; numb: word);
{ Sets the numb bit in the bitmap array }
var m: map absolute x;
begin
m[numb shr 3] := m[numb shr 3] or (1 shl (numb and 7))
end;
procedure ResetBitInBitMap(var x; numb : longint);
{ Resets the numb bit in the bitmap array }
var m: map absolute x;
begin
m[numb shr 3] := m[numb shr 3] and not(1 shl (numb and 7));
end;
procedure ToggleBitInBitMap(var x; numb : longint);
{ Toggles (flips) the numb bit in the bitmap array }
var m: map absolute x;
begin
m[numb shr 3] := m[numb shr 3] xor (1 shl (numb and 7));
end;
13...
Q. How can I find a particular string in any file - text or binary?
A. The Boyer-Moore string search algorithm is considered to be the fastest
method available. However in a rare worst-case scenario it can be
slightly slower than a linear brute-force method. The following
demonstration program will show how it works and could easily be
modified to allow for command line paramters etc.
program BMSearchDemo;
type
bigarray = array[0..32767] of byte;
baptr = ^bigarray;
BMTable = array[0..255] of byte;
const
KeyStr : string = 'Put whatever you want found here';
fname : string = 'f:\Filename.txt';
var
Btable : BMtable;
buffer : baptr;
f : file;
result,
position : word;
offset : longint;
finished,
Strfound : boolean;
procedure MakeBMTable(var t : BMtable; var s);
{ Makes a Boyer-Moore search table. s = the search string t = the table }
var
st : BMtable absolute s;
slen: byte absolute s;
x : byte;
begin
FillChar(t,sizeof(t),slen);
for x := slen downto 1 do
if (t[st[x]] = slen) then
t[st[x]] := slen - x
end;
function BMSearch(var buff,st; size : word): word;
{ Not quite a standard Boyer-Moore algorithm search routine }
{ To use: pass buff as a dereferenced pointer to the buffer}
{ st is the string being searched for }
{ size is the size of the buffer }
{ If st is not found, returns $ffff }
var
buffer : bigarray absolute buff;
s : array[0..255] of byte absolute st;
len : byte absolute st;
s1 : string absolute st;
s2 : string;
count,
x : word;
found : boolean;
begin
s2[0] := chr(len); { sets the length to that of the search string }
found := false;
count := pred(len);
while (not found) and (count < (size - len)) do begin
if (buffer[count] = s[len]) then { there is a partial match } begin
if buffer[count-pred(len)] = s[1] then { less partial! } begin
move(buffer[count-pred(len)],s2[1],len);
found := s1 = s2; { if = it is a complete match }
BMSearch := count - pred(len); { will stick unless not found }
end;
inc(count); { bump by one char - match is irrelevant }
end
else
inc(count,Btable[buffer[count]]); { no match so increment maximum }
end;
if not found then
BMSearch := $ffff;
end; { BMSearch }
begin
new(buffer);
assign(f,fname);
reset(f,1);
offset := 0;
MakeBMTable(Btable,KeyStr);
repeat
BlockRead(f,buffer^,sizeof(buffer^),result);
position := BMSearch(buffer^,KeyStr,result);
finished := (result < sizeof(buffer^)) or (position <> $ffff);
if position = $ffff then
inc(offset,result);
Strfound := position <> $ffff;
until finished;
close(f);
if Strfound then
writeln('Found at offset ',offset)
else
writeln('Not found');
end.
14...
Q. How can I put a apostrophe in a string?
A. Just put in extra apostrophes. If you want st to be equal to the
string -
The word 'quoted' is in quotes
do this -
st := 'The word ''quoted'' is in quotes';
if you want the following to be written to screen -
'This is a quoted string'
do this -
writeln('''This is a quoted string''');
15...
Q. What are the best books to purchase to help me learn Turbo Pascal?
A. There are many good books for learners. Here are a few -
Complete Turbo Pascal - Third Edition - Jeff Duntemann
Mastering Turbo Pascal 6 - Tom Swann
Turbo Pascal - The Complete Reference - O'Brien.
For advanced users there are also many good books. Here are a few
that I have found useful - (Those marked with an asterisk are not
purely for Turbo Pascal)
Turbo Pascal 6 - Techniques and Utilities - Rubenking
Turbo Pascal Internals - Tischer
* PC System Programming for Developers - Tischer
* Undocumented DOS - Schulman
Any learner would be well advised to obtain a well known library
such as Technojock's Turbo Toolkit (TTT) which is shareware and
study the source code.
16.
Q. hat are "truth tables" and how do they work?
A. Truth tables are a set of rules that are used to determine the result of
logical operations. The logical operators are -
NOT
AND
OR
XOR.
Here is a brief explanation of truth tables. When two values are
logically compared by using a logical operator each bit of one value is
directly compared to the corresponding bit in the other value and the
same bit in the returned value is set or reset according to the
following truth table.
NOT AND OR XOR
not 1 = 0 0 and 0 = 0 0 or 0 = 0 0 xor 0 = 0
not 0 = 1 0 and 1 = 0 0 or 1 = 1 0 xor 1 = 1
1 and 0 = 0 1 or 0 = 1 1 xor 0 = 1
1 and 1 = 1 1 or 1 = 1 1 xor 1 = 0
NOT reverses the bit.
AND sets the returned bit if both compared bits are set.
OR sets the returned bit if either of the compared bits are set.
XOR sets the returned bit if the compared bits are not the same.
17.
Q. What are pointers and how can I use them? I have heard that they are
variables that can be created and discarded as required thus saving
memory. Is this true?
A. A pointer is a variable that contains a memory address.
The heap is all of that memory allocated by DOS to a program for its
use that has not been used by the program for its code, global data or
stack.
Dynamic variables are variables that have had space allocated for them
on the heap.
Dynamic variables have no identifier (are unnamed). Because of this
they need an associated variable that can be used to find where they
reside in memory. Pointers are ideal for this but need some method to
define what type of data it is that they are pointing at. Pascal
provides this method.
type
Str10Ptr = ^string[10];
{ This means Str10Ptr is a pointer that points to data of type }
{ string[10]. }
var
S : Str10Ptr;
In the above example S is a pointer that has been defined as pointing
to an address in memory that will contain (or should contain) data of
type string[10].
However how does S get this value? How does it know where that data's
address is supposed to be? Well until the programmer allocates memory
for that data S's value is undefined, so it could be literally
pointing anywhere. So it is *vital* that before we try to use it to
use/assign data from/to that memory location we give S a memory
address that is not being used for any other purpose at the moment and
that is big enough to hold the data that we want to place into it - in
this case at least 11 bytes. We do this by -
new(S);
Pascal has now allocated at least 11 bytes of heap and has allocated S
with the address of the FIRST byte of that allocation.
Ok... so far so good! How do we access that data (remembering that it
has no name). Well we "dereference" the pointer. This is done by
placing a carat sign immediately following the pointer's identifier.
S^ := 'Joe Bloggs';
This statement actually means "Place the string 'Joe Bloggs' into the
memory address that S contains". This is referred to as "derferencing"
the pointer S.
To "reference" a dynamic variable we "dereference" its associated
pointer variable. We cannot say -
S := 'Joe Bloggs';
because S is a pointer and that would be trying to give a pointer a
string type value - a compiler "type mismatch" would occur. So every
time we wish to access that dynamic variable we dereference it.
To delete the dynamic variable once it is of no further use is just a
matter of -
dispose(S);
What this statement does is release the memory previously used by S^
and make it available to be used for other purposes by the program.
Depending on the version of Pascal you are using it may not erase or
alter the contents of that memory and it may not give S a new value.
However any attempt to dereference S is an error as the integrity of
that memory location has been lost - it may have been allocated to
other data.
Pointers do not *have* to point to a memory location in the heap or
even have their value always allocated by using the New procedure. Any
valid memory address can be assigned to them and then they can be
dereferenced as shown above. As a simple example of this lets say you
want to examine the contents of the 16 byte area at $40:$f0 (the ICA
area). You could - (TP specific)
type
ICA_Ptr = ^array[0..15] of byte;
var
B : byte;
ICA : ICA_Ptr;
ICA := ptr($40,$f0);
Now ICA points to the address specified and you can dereference it -
B := ICA^[10];
Hope that helps get you started into the complex world of memory
management and manipulation using pointers. There are countless
permutations and methods that can be used.
18.
Q. How do I do word wrap?
A. The demo program WRAP.PAS in this archive demonstrates both word wrap
and the justifying of text.
[Back to FAQ SWAG index] [Back to Main SWAG index] [Original]