Migrating BASIC Programs to Delphi
Author: J. W. Rider (mailto:development@jwrider.com)
Overview
Spoken and written languages can exercise a strong influence over the
way that all people think. Likewise,
programming languages can constrain the way that software developers approach
their craft. Migrating programs from
one language to another is not so much an operation of semantics and syntax
translations as it is an issue of
rethinking how one programmer thought about a problem in one programming
language for another.
Delphi provides a very straight-forward approach to translating the
visual aspects of other MS Windows
programming languages. However, complex
programs will require significant non-visual conversions, and conversion
efforts may find direct translation to be somewhat cumbersome until programmers
start thinking about problems in terms of Delphi rather than some other
language.
This secret to migrating BASIC programs to Delphi is to migrate BASIC
programmers to Object Pascal.
A Very High-Level Language Comparison
Programming languages are
all alike (kind of)
As far as programming languages go, the modern versions of BASIC are
relatively similar to standard Pascal. Both languages contain ways of
organizing memory to represent information in the form of named variables, to
perform operations on the variables by storing into them the results of
expressions, and to organize the operations in a modular and structured
fashion. Elementary arithmetic
operations like addition, subtraction, multiplication and division are handled
almost identically. There are some
cosmetic differences where BASIC includes type information for variables with a
single character suffix, a feature that doesn't exist in Pascal.
Language-specific features
favor designed use
Beyond those cosmetic differences lay some more intrinsic
differences. BASIC is designed to
formulate expressions easily. Pascal is
designed to make compilation efficient.
In order to formulate expressions easily, BASIC programs become
sequences of statements that get executed a line at a time. (Sometimes, it is easy to forget that the
original BASICs were intended to be interpreted rather than compiled. Executing statements a line at a time was
much easier for interpreters.) In order
to compile efficiently, Pascal programs become a nested combination of control
structures that get executed a statement at a time. Of course, modern BASICs have adopted
similar control structures in a way that complements the classic versions of
BASIC; the execution of statements a line at a time is not lost.
Think language efficiency
It is this business about arranging the program to make compilation
efficient that is the biggest obstacle in getting BASIC programmers to rethink
their code in Pascal. Indeed, it
affects programmers in many languages that were designed to be mainly
interpreted. The syntactic rules for
Pascal can seem a little confusing for the programmer who has a handle on
BASIC. For instance, the necessity of
using the colon/equals operator (":=") rather than the simpler
equals operator ("=") for assignments can seem a little
distant at first glance. Furthermore,
there is the business with semi-colons because it looks to the line-oriented
BASIC programmer that sometimes the semi-colons need to be put at the end of lines and sometimes the semi-colons
shouldn't be put at the end of them.
BASIC programmers already
know about different languages
These syntactic "pecularities" turn out to be not
particularly hard for migrating programmers to accept. As a language, BASIC itself has evolved and
been adapted to new environments and applications. Each generation has provided a additional set of syntactic
peculiarities of their own with which programmers would have to come to grips. Even under MS Windows, several dialects of
BASIC are thriving: MicroSoft's
VisualBasic, CA's Realizer, PowerSoft's PowerBuilder, and Borland's ReportSmith
all show how the BASIC language can be changed to make the language useful.
You have to shift gears for
higher performance
However, when you are trying to get sheer performance out of a system, compilation down to native machine
code is hard to beat, and that is where Object Pascal has a ready-made
advantage. However, programmers are
going to need to shift gears in order to make the best use of this
advantage. The shifting of gears
involves thinking about how to arrange program components to be more efficient
and improve performance. In the long
run, the programmer who masters the skill of "thinking" in Pascal
will discover that skill carries well to other programming languages.
An Example
for Trimming Strings
Later in this paper, a comparison is made to distinguish between
features in BASIC and corresponding features in Object Pascal. Sometimes, there just isn't any
corresponding feature. For instance,
some BASICs have an RTRIM$()
function that accepts a string expression as an argument and returns a string
value that is the same as the string argument except that all trailing spaces
have been removed; i.e., the string is right-trimmed. This particular feature is easily emulated
in Object Pascal by taking advantage of
the knowledge of how strings are stored in memory and a couple of other
"tricks":
function RTrim( S:string): string; { Right trim string function }
Note: You can't use the dollar sign as a part of the function
name. In BASIC, the dollar sign serves
the same purpose as the ":string" following the end
parenthesis, and is an intrinsic part of the name. In Pascal, only alphanumerics and underscores can be used for
identifiers, and the type must be explicitly stated when the identifier is
defined. This kind of Pascal string is
limited to a maximum length of 255 characters.
var i:integer;
begin
for
i := length(s) downto 1 do { start looking at the last character }
{ and move back until all of
the }
{ characters have been
examined.}
Note: The "for" clause is not an Object Pascal
statement by itself. It must be
combined with some kind of action statement to be complete. Don't put a semicolon between the
"do" and the body of the loop. Since there is no "begin"
following the "do", a single statement (in this case, a
conditional statement) forms the body of the loop.
{ if the character being examined is a space, ignore it, and }
{ check the next character }
if s[i]<>' ' then begin
{ aha. We've found the last }
{ non-blank character in the
string. }
Note: The "begin" indicates that more than one
statement will be executed if the "if" expression is
true. Indentation is somewhat a matter
of personal taste. The author has a
preference for locating the "begin" on the same line as
any prefixed clauses. This is, by no means,
conventional wisdom.
s[0] := char(i); { this "trick" shortens the string. }
result := s; { set the string
return value }
exit; { return to the
function caller.}
end; { end of block,
end of if, and end for }
Note: The semicolon before the "end" is
optional. Again, it's a matter of
personal preference as to whether you insert strictly optional semicolons. The semicolon after the "end"
is not optional.
{ if we get to this point, it means that the whole string }
{ was composed of blanks, so we return a string of length zero. }
result := ''; { yet
another optional semi-colon }
end; { end of function }
That does the job, and you can
use that in most places that you might have used the BASIC RTRIM$()
function. However, it's not
particularly efficient for a Pascal implementation. An unknown number of characters are being moved when the function
is called and when the return value is set.
Sure, it's limited to 255 characters, but it's the mind set for
efficient and high-performing programming that we're trying to establish. The way to do that is to avoid slow and
wasteful practices.
For instance, less stack space will be used by the code if the string
is passed by reference as var parameter and modify the string in place. Of course, if we really wanted to use the
name RTrim to indicate a right trimming function, another name
will be needed so that we can tell the difference between the function and
procedure. For instance, in the
following example, the name is prefixed with an underscore to create a new
name:
procedure _RTrim( var S: string); { Right
trim the string variable S, }
{ in place }
var i: integer;
begin
for i := length(s) downto 1 do
if s[i] <> ' ' then begin { if isn't a space char }
s[0] := char(i);
exit;
end;
{
String is all blanks; get rid of them all }
S:= ''; { an empty string}
end;
Which is hardly distinguishable from the function defined as RTrim
earlier. Of course, it is hardly
efficient to have two routines do basically the same thing. Instead, have the less efficient code call
the more efficient code:
function RTrim( S:string):string; { right trim string function }
begin
_RTrim(S); { call the faster and
more efficient procedure }
{ that modifies the local string variable in place
}
result := S; { return the string value }
end;
This approach let's the migrating programmer have it both ways. An RTRIM$() like function is
available for use in string expressions.
On the other hand, more speed and efficiency are available readily
whenever the programmer needs to tighten code.
A Feature-by-Feature Language Comparison
In the section that follows, BASIC features are compared with features
in Object Pascal that do much the same thing.
Where there might be some ambiguity, the interpretation of MS
VisualBasic is followed. There are six
general categories:
1. OK.
The BASIC feature can be used in Object Pascal without significant
changes. Object Pascal may have
additional uses of the same feature, and may even have more efficient features
that can be used, but the programmer isn't going to have to change gears when
dealing with the feature.
2. Renamed.
Object Pascal has a feature that does something equivalent to the BASIC
feature, but in Object Pascal the feature is called something else.
3. Similar.
The feature in Object Pascal does something along the same lines as the
BASIC feature, but there is a signficant difference in the behavior.
4. Different.
The named feature exists in Object Pascal, but means something unrelated
to how it would be used in BASIC.
5. Not used.
The BASIC feature doesn't correspond to anything available in Object Pascal. Sometimes the effect can be minor. For instance, a missing function can be
created. Other times, the code will
need to be reworked in order to duplicate the behavior.
BASIC Feature Comparable
Usage in Object Pascal
Operators
Note:
Operator precedence is different with Object Pascal from BASIC.
& (ampersand) To
concatenate strings use the Concat function or the + operator.
* (asterisk) OK. Object Pascal uses a similar scheme for
mixed-precision arithmetic. Also used
for set intersection.
+ (plus) OK. Also used for string concatenation and set union.
- (minus) OK.
Also used for set difference.
/ (slash) Similar. Object Pascal always returns a floating
point result.
\ (backslash) To
divide two integers, use the div operator. For mixed floating point operands, translate
a\b into round(a) div round(b).
^ (caret) The
easiest translation of a^b is exp( b * ln(a)) as long as
a>0. a^2 should be
translated as sqr(a).
Polynomial expressions should not expressed as powers. Instead use Horner's method.
< OK.
<= OK. Object
Pascal also uses this to mean "subset of" in comparing sets.
> OK.
>= OK. Object
Pascal also uses this to mean "superset of" in comparing sets.
= Similar. For testing equality, the operator is the
same. For assignment, Object Pascal
uses := rather than = .
<> OK.
AND OK.
EQV For
logical equivalence, use = .
For bitwise equivalence a EQV b, use not (a xor b).
IMP For
logical implication, use <=.
For bitwise implication a IMP b, use (not a) or b.
IS Different. In
Object Pascal, the Is operator checks the ancestry of a class
variable instance. (In VB, a similar
feature is the TYPEOF .... IS .... clause available in the IF...THEN....ELSE...
statement.) To check if two such
instances reference the same object, use =.
LIKE Not
used.
MOD Similar. Can have integer operands only.
NOT OK.
OR OK.
Functions
Note: In BASIC, the same name may be used for both
a function and a statement,
and
the two forms may work differently. In
Object Pascal, a single name can be used as
either
a function or a procedure, but not both (at least, within the same scope).
ABS OK.
ASC Translate asc(s)
as ord(s[1]) as long as length(s)>0.
ATN Called ArcTan.
CHOOSE Not used.
CHR OK.
COMMAND Object
Pascal uses the ParamStr to return separate components from the
command line. See also, GetArgStr
.
COS OK.
CURDIR Use
GetCurDir function or GetDir procedure.
CVDATE Not
used.
CCUR, CDBL, CINT, CLNG, CSNG, CSTR, CVAR
In most cases, it is not
necessary to do any conversion of types whatsoever.
DATE Use
GetDate procedure.
DATEADD, DATEDIFF,
DATEPART, DATESERIAL, DATEVALUE
Not used.
DAY, HOUR, MINUTE,
MONTH, SECOND
Not used.
DDB, FV, IPMT, IRR,
MIRR, NPER, NPV, PMT, PPMT, PV, RATE, SLN, SYD
Not used.
Derived Math Functions. Not
automatically defined in Object Pascal either.
DIR Use
FindFirst and FindNext.
ENVIRON Use
GetEnv, GetEnvVar, or EnvStr.
EOF OK.
Object Pascal uses file variables instead of file handle numbers.
ERR, ERL Not used.
ERROR Not used.
EXP OK.
FILEATTR Use GetFAttr procedure or FileGetAttr
function.
FILEDATETIME Use
GetFTime procedure or FileGetDate function.
FILELEN Use
FileSize function.
FORMAT Similar.
See SysUtils.Format.
FREEFILE Not used.
Object Pascal uses file variables instead of file handle numbers.
GETATTR Use
GetFAttr procedure or FileGetAttr function.
HEX Use
IntToHex function.
IIF Not
used.
INPUT Different. In
Object Pascal, Input is a predefined text file
variable that is the default source for Read and ReadLn
statements. For transferring
information from a file to variables, use Read or ReadLn. Object Pascal interprets then incoming character stream more in the fashion of
the INPUT function than the INPUT statement.
INPUTBOX Similar.
INSTR The Pos function is used to search for
the existence, of one string within another.
INT, FIX Similar. The
Object Pascal Int function rounds in the same way that FIX does. There is no FIX in Object Pascal.
ISDATE, ISEMPTY, ISNULL,
ISNUMERIC
Not used.
LBOUND Use
Low.
LCASE Not used.
LEFT For
LEFT$(x,n), use System.Copy(x,1,n).
LEN For
strings, called Length.
For variables, called SizeOf.
LOC Use FilePos.
LOF Use FileSize.
LOG Called
Ln.
LTRIM, RTRIM, TRIM Not
used.
MID Called
Copy. For MID$(s,a,b), use System.Copy(s,a,b).
NOW Use
GetDate.
OCT Not
used.
PARTITION Not
used.
RGB Not
used.
RIGHT Not
used.
RND Use
Random to return a random number between 0 and 1. Random(n) returns a random
integer between 0 and n-1, inclusive.
SEEK Different. In Object Pascal, Seek
positions a file to a given record number.
To find the location of the current position in a file, use FilePos.
SGN Not
used.
SIN OK.
SPACE Not used.
SPC Translate
SPC(x) as a width qualifier for an empty string.
SQR Different. In Object Pascal, Sqr returns
the square of a number. To find the
square root, use Sqrt.
STR Similar.
A procedure rather than a function.
STRCOMP Similar.
In Object Pascal, StrComp compares pchars vice string
expressions. Object Pascal does not
support different compare modes.
STRING Different. In Object Pascal, String is a
reserved word. There is no facility for
creating a string of all the same character.
SWITCH Not
used.
TAB Not
used.
TAN To
get the tangent of x, use sin(x)/cos(x).
TIME Use
GetTime.
TIMER Not
used.
TIMESERIAL, TIMEVALUE
Not used.
UBOUND Use
High.
UCASE UpCase
will raise a single character to upper case.
VAL Similar. In Object Pascal, Val is a
procedure rather than a function.
VARTYPE Not
used.
Statements
Object
Pascal distinguishes between statements (which organize code together)
and
procedures (which are executed). BASIC
includes a number of statements
that
would be called procedures in Object Pascal.
BEEP Not
used.
CALL Not
used. Procedures are always called when
they are referenced. Arguments must be
enclosed in parentheses.
CHDIR OK. In Object Pascal, the ChDir
procedure also changes the drive.
CHDRIVE Not
used. Translate CHDRIVE
"D" as ChDir('D:').
CLOSE Used to close a file.
Object Pascal uses file variables rather than file handle numbers.
CONST Almost identical in both Object Pascal and VB. Object Pascal does permit the use of string
concatenation, and reference of some library functions in evaluating the
expression. For GLOBAL CONST,
simply define the constant in the interface of a shared unit. Object Pascal also has "typed
constants" which provides a
mechanism for initializing variables at compile-time.
DATE Use
SetDate procedure.
DECLARE Not
used.
DEFxxxx Never
used. The types of variables must be
declared individually.
DIM Use
Var.
DO...LOOP Use While...do
begin... end or Repeat ... Until
.... For EXIT DO,
use Break.
ELSE OK. Object Pascal does not use ELSEIF, but Else If has same effect.
END Different. For END FUNCTION, END IF, END
SELECT, END SUB, or END TYPE, simply use End. To terminate a program, use Halt.
ERASE Different. The
Object Pascal Erase is used to kill external files. To free
memory used by arrays, use the Dispose or FreeMem
procedures.
ERR Not
used.
ERROR Use RunError.
EXIT Similar. For EXIT SUB or EXIT FUNCTION,
simply use Exit. For EXIT
DO or EXIT FOR, use Break.
FILECOPY Substitutes
are available.
FOR...NEXT Similar. The counter must be a local or global
ordinal variable. Object Pascal does
not use the STEP clause. DownTo
replaces To to iterate in the opposite direction. Object Pascal does not use the NEXT
statement. For EXIT FOR, use Break.